disk_restore.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>File Restore</title>
  5. <meta name="mobile-web-app-capable" content="yes">
  6. <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
  7. <meta charset="UTF-8">
  8. <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
  9. <script src="../../script/jquery.min.js"></script>
  10. <script src="../../script/semantic/semantic.min.js"></script>
  11. <script type="text/javascript" src="../../script/ao_module.js"></script>
  12. <style>
  13. .hidden{
  14. display:none;
  15. }
  16. .disabled{
  17. opacity: 0.5;
  18. pointer-events: none;
  19. }
  20. .colorblock{
  21. width: 20px;
  22. height: 20px;
  23. display: inline-block;
  24. vertical-align: bottom;
  25. margin-right: 12px;
  26. }
  27. .blue.colorblock{
  28. background-color: #52bdf2;
  29. }
  30. .grey.colorblock{
  31. background-color: #b9b9b9;
  32. }
  33. .yellow.colorblock{
  34. background-color: #e5e75c;
  35. }
  36. .ui.table tr td{
  37. border-top: 0px solid transparent !important;
  38. }
  39. .overlap.bar{
  40. position: absolute !important;
  41. top:0px;
  42. left: 0px;
  43. }
  44. .nointeract{
  45. pointer-events: none;
  46. user-select: none;
  47. }
  48. #restorableList{
  49. max-height: 300px;
  50. overflow-y: scroll;
  51. padding-right: 4px;
  52. }
  53. #snapshotList{
  54. max-height: 300px;
  55. overflow-y: scroll;
  56. padding-right: 4px;
  57. }
  58. .timeleft{
  59. width: 30px;
  60. height: 30px;
  61. display: inline-block;
  62. user-select: none;
  63. vertical-align: baseline;
  64. margin-bottom: -10px;
  65. margin-right: 4px;
  66. }
  67. .closebtn{
  68. position: absolute;
  69. right: 4px;
  70. top: 4px;
  71. }
  72. </style>
  73. </head>
  74. <body>
  75. <br>
  76. <div class="ui container">
  77. <h3 class="ui header">
  78. Backup & Restore
  79. <div class="sub header">Show restore points for the selected virtual disk</div>
  80. </h3>
  81. <div class="ui divider"></div>
  82. <div id="error" class="ui red inverted segment hidden">
  83. <h4 class="ui header">
  84. <i class="remove icon"></i>
  85. <div class="content">
  86. List Restore Failed
  87. <div class="sub reason header" style="color: white;">Unknown Vroot paramter given</div>
  88. </div>
  89. </h4>
  90. </div>
  91. <div id="restoreCancel" class="ui grey inverted segment hidden">
  92. <h4 class="ui header">
  93. <i class="remove icon"></i>
  94. <div class="content">
  95. Snapshot Restore Cancelled
  96. <div class="sub reason header" style="color: white;">Operation cancelled by user</div>
  97. </div>
  98. </h4>
  99. </div>
  100. <div id="succ" class="ui green segment" style="display:none;">
  101. <h4 class="ui header">
  102. <i class="green checkmark icon"></i>
  103. <div class="content">
  104. File Restored
  105. <div class="sub reason header openfolder"><a style="cursor: pointer" onclick="handleOpenRestoreLocation()"><i class="ui folder open icon"></i> Open Location</a></div>
  106. </div>
  107. </h4>
  108. </div>
  109. <div id="fileinfo" class="ui segment" style="display:none;">
  110. <h4 class="ui header">
  111. <div class="content">
  112. <span class="filename">No File Selected</span>
  113. <div class="sub header relpath">N/A</div>
  114. </div>
  115. </h4>
  116. <div class="ui divider"></div>
  117. <h5>Restorable File Properties</h5>
  118. <span class="backupDisk">N/A</span><br>
  119. <span class="restorePoint">N/A</span><br>
  120. <span class="deleteTime">N/A</span><br>
  121. <span class="deleteRemainingTime">N/A</span><br>
  122. <button id="snapshotSummaryBtn" class="ui mini button" style="display:none;" onclick="openSnapshotInfo();">View Snapshot Summary</button>
  123. <button class="circular ui icon basic small button closebtn" onclick='$("#fileinfo").slideUp("fast");'>
  124. <i class="icon remove"></i>
  125. </button>
  126. </div>
  127. <div id="snapshotList" class="ui middle aligned divided list">
  128. <div class="item">
  129. <img class="ui mini image nointeract" src="../../img/system/drive-backup.svg">
  130. <div class="content">
  131. No Restorable Snapshot(s) for this virtual disk.
  132. </div>
  133. </div>
  134. </div>
  135. <div id="restorableList" class="ui middle aligned divided list">
  136. <div class="item">
  137. <img class="ui mini image nointeract" src="../../img/system/file-notfound.svg">
  138. <div class="content">
  139. No Restorable File(s) for this virtual disk.
  140. </div>
  141. </div>
  142. </div>
  143. <div class="ui checkbox">
  144. <input type="checkbox" name="showhidden" autocomplete="false" onchange="toggleHiddenFiles(this.checked);">
  145. <label>Show hidden / cache files</label>
  146. </div>
  147. <div class="ui divider" style="margin-top: 8px; margin-bottom: 8px;"></div>
  148. <br>
  149. <div class="ui tiny green button" onclick="restoreAll();">Restore All</div>
  150. <button class="ui right floated button" onclick="ao_module_close();">Close</button>
  151. <br>
  152. </div>
  153. <br><br>
  154. <script>
  155. var vrootID = "";
  156. var viewingFileInfo = {};
  157. $(".ui.checkbox").checkbox();
  158. //Extract vroot id from the window hash
  159. if (window.location.hash.length > 1){
  160. vrootID = JSON.parse(decodeURIComponent(window.location.hash.substr(1)));
  161. if (vrootID.length >= 1){
  162. listRestorableFiles(vrootID[0]);
  163. }else{
  164. $("#error").removeClass("hidden");
  165. }
  166. }else{
  167. $("#error").removeClass("hidden");
  168. }
  169. function openSnapshotInfo(){
  170. var snapshotInfo = {
  171. SnapshotDisk: viewingFileInfo.BackupDiskUID,
  172. SnapshotName: viewingFileInfo.Filename
  173. };
  174. var hashPassthrough = encodeURIComponent(JSON.stringify(snapshotInfo));
  175. ao_module_newfw({
  176. url: "SystemAO/disk/disk_snapshot.html#" + hashPassthrough,
  177. width: 900,
  178. height: 520,
  179. appicon: "img/system/backup.svg",
  180. title: "Snapshot Summary",
  181. });
  182. }
  183. function showFileInfo(object){
  184. var filedata = $(object).attr("filedata");
  185. filedata = JSON.parse(decodeURIComponent(filedata));
  186. console.log(filedata);
  187. //Show information on page
  188. if (filedata.IsSnapshot){
  189. $("#fileinfo").find(".filename").text("Snapshot " + filedata.Filename);
  190. }else{
  191. $("#fileinfo").find(".filename").text(filedata.Filename + ` (${ao_module_utils.formatBytes(filedata.Filesize, 2)})`);
  192. }
  193. viewingFileInfo=filedata;
  194. //Hide the filename in relpath
  195. var shortenRelpath = filedata.RelpathOnDisk.split("/");
  196. shortenRelpath.pop();
  197. shortenRelpath = shortenRelpath.join("/") + "/";
  198. $("#fileinfo").find(".relpath").text(shortenRelpath);
  199. $("#fileinfo").find(".backupDisk").html('<i class="ui blue disk icon"></i> Restorable file from ' + filedata.BackupDiskUID + ":/");
  200. $("#fileinfo").find(".deleteTime").html('<i class="ui trash icon"></i> File deleted since ' + timeConverter(filedata.DeleteTime));
  201. $("#fileinfo").find(".restorePoint").html('<i class="ui refresh icon"></i> Restore to ' + (filedata.RestorePoint));
  202. $("#fileinfo").find(".deleteRemainingTime").html('<i class="ui red remove icon"></i> Auto delete in '+ secondsToHms(filedata.RemainingTime));
  203. if (filedata.IsSnapshot){
  204. $("#fileinfo").find(".deleteRemainingTime").hide();
  205. $("#fileinfo").find(".deleteTime").hide();
  206. $("#snapshotSummaryBtn").show();
  207. }else{
  208. $("#fileinfo").find(".deleteRemainingTime").show();
  209. $("#fileinfo").find(".deleteTime").show();
  210. $("#snapshotSummaryBtn").hide();
  211. }
  212. $("#fileinfo").slideDown('fast');
  213. }
  214. function secondsToHms(d) {
  215. d = Number(d);
  216. var h = Math.floor(d / 3600);
  217. var m = Math.floor(d % 3600 / 60);
  218. var s = Math.floor(d % 3600 % 60);
  219. var hDisplay = h > 0 ? h.toString().padStart(2, "0") + (h == 1 ? " hour, " : " hours, ") : "";
  220. var mDisplay = m > 0 ? m.toString().padStart(2, "0") + (m == 1 ? " minute, " : " minutes") : "";
  221. if (h > 24){
  222. var days = Math.floor(h/24);
  223. days = days + (days == 1 ? " day, " : " days, ")
  224. var hours = h % 24 + (h == 1 ? " hour, " : " hours, ");
  225. return days + hours + mDisplay;
  226. } else{
  227. return hDisplay + mDisplay;
  228. }
  229. }
  230. function timeConverter(UNIX_timestamp){
  231. var a = new Date(UNIX_timestamp * 1000);
  232. var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  233. var year = a.getFullYear();
  234. var month = months[a.getMonth()];
  235. var date = a.getDate();
  236. var hour = a.getHours().toString().padStart(2, "0");
  237. var min = a.getMinutes().toString().padStart(2, "0");
  238. var sec = a.getSeconds().toString().padStart(2, "0");
  239. var time = date + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec ;
  240. return time;
  241. }
  242. function toggleHiddenFiles(status=undefined){
  243. if (status == undefined){
  244. $(".hiddenfile").slideToggle("fast");
  245. }else if (status == true){
  246. $(".hiddenfile").slideDown("fast");
  247. }else if (status == false){
  248. $(".hiddenfile").slideUp("fast");
  249. }
  250. }
  251. //Restore this file
  252. var restoreFileInfo = undefined;
  253. function restoreThisFile(fileObject){
  254. var filedata = $(fileObject).attr("filedata");
  255. filedata = JSON.parse(decodeURIComponent(filedata));
  256. console.log(filedata);
  257. if (filedata.IsSnapshot == true && !confirm("Confirm snapshot restore? All newly created files will be kept.")){
  258. $("#restoreCancel").stop().finish().slideDown('fast').delay(3000).slideUp('fast');
  259. return;
  260. }
  261. $.ajax({
  262. url: "../../system/backup/restoreFile",
  263. method: "POST",
  264. data: {"bdid": filedata.BackupDiskUID, "relpath": filedata.RelpathOnDisk},
  265. success: function(data){
  266. if (data.error !== undefined){
  267. alert("Restore failed: " + data.error);
  268. }else{
  269. console.log(data);
  270. if (filedata.IsSnapshot){
  271. restoreFileInfo = filedata;
  272. }else{
  273. restoreFileInfo = data;
  274. }
  275. if (restoreFileInfo.RestoredVirtualPath == ""){
  276. $("#openfolder").hide();
  277. }else{
  278. $("#openfolder").show();
  279. }
  280. $("#succ").finish().stop().slideDown('fast').delay(10000).slideUp('fast');
  281. listRestorableFiles(vrootID[0]);
  282. }
  283. }
  284. })
  285. }
  286. function handleOpenRestoreLocation(){
  287. if (restoreFileInfo.RestorePoint !== undefined){
  288. //Snapshot
  289. ao_module_openPath(restoreFileInfo.RestorePoint + ":/");
  290. return
  291. }else if (restoreFileInfo.RestoredVirtualPath != undefined && restoreFileInfo.RestoredVirtualPath != ""){
  292. var filepath = restoreFileInfo.RestoredVirtualPath;
  293. var tmp = filepath.split("/");
  294. var filename = tmp.pop();
  295. var dirname = tmp.join("/");
  296. ao_module_openPath(dirname, filename);
  297. }
  298. }
  299. function listRestorableFiles(rootID){
  300. $.ajax({
  301. url: "/system/backup/listRestorable",
  302. data: {vroot: rootID},
  303. success: function(data){
  304. if (data.error !== undefined){
  305. $("#error").find(".reason").text(data.error);
  306. $("#error").removeClass("hidden");
  307. }else{
  308. //Filter out the restorable snapshot and files
  309. let restorableFiles = [];
  310. let restorableSnapshots = [];
  311. data.RestorableFiles.forEach(file => {
  312. if (file.IsSnapshot){
  313. restorableSnapshots.push(file);
  314. }else{
  315. restorableFiles.push(file);
  316. }
  317. });
  318. //Sort the result by latest first (file only)
  319. restorableFiles.sort(function(a, b) {
  320. return b.DeleteTime - a.DeleteTime;
  321. });
  322. restorableSnapshots.sort(function(a, b) {
  323. return b.Filename > a.Filename;
  324. });
  325. //Display the reuslt for restorableSnapshot
  326. $("#snapshotList").html("");
  327. restorableSnapshots.forEach(snapshot => {
  328. let snapshotData = encodeURIComponent(JSON.stringify(snapshot));
  329. $("#snapshotList").append(`<div class="item restorableFile snapshot" filedata="${snapshotData}">
  330. <div class="right floated content">
  331. <div class="ui tiny button" onclick="showFileInfo(this.parentNode.parentNode)">Info</div>
  332. <div class="ui tiny green button" onclick="restoreThisFile($(this).parent().parent());">Restore</div>
  333. </div>
  334. <div class="content">
  335. <p style="word-break: break-all; font-size: 97%"><img class="ui mini spaced image nointeract" src="../../img/system/drive-backup.svg">
  336. Snapshot ${snapshot.Filename}
  337. </p>
  338. </div>
  339. </div>`);
  340. })
  341. //Display the result for restorableFile
  342. $("#restorableList").html("");
  343. restorableFiles.forEach(fileObject => {
  344. let thisFileData = encodeURIComponent(JSON.stringify(fileObject));
  345. var timerIcon = "100.svg";
  346. if (fileObject.RemainingTime < 75600){
  347. timerIcon = "87_5.svg"
  348. }else if (fileObject.RemainingTime < 64800){
  349. timerIcon = "75.svg"
  350. }else if (fileObject.RemainingTime < 54000){
  351. timerIcon = "62_5.svg"
  352. }else if (fileObject.RemainingTime < 43200){
  353. timerIcon = "50.svg"
  354. }else if (fileObject.RemainingTime < 32400){
  355. timerIcon = "37_5.svg"
  356. }else if (fileObject.RemainingTime < 21600){
  357. timerIcon = "25.svg"
  358. }else if (fileObject.RemainingTime < 10800){
  359. timerIcon = "12_5.svg"
  360. }else if (fileObject.RemainingTime < 7200){
  361. timerIcon = "0.svg"
  362. }
  363. var deleteTimeLeftHumanReadable = secondsToHms(fileObject.RemainingTime);
  364. var fileIcon = "../../img/system/file-restore.svg";
  365. var isHidden = "";
  366. if (fileObject.IsHidden == true){
  367. fileIcon = "../../img/system/file-restore-hidden.svg";
  368. isHidden = "hiddenfile";
  369. }
  370. $("#restorableList").append(`<div class="item restorableFile file ${isHidden}" filedata="${thisFileData}">
  371. <div class="right floated content">
  372. <div class="timeleft" title="Will be deleted in ${deleteTimeLeftHumanReadable}">
  373. <img class="ui fluid image" src="../../img/icons/backup/${timerIcon}"/>
  374. </div>
  375. <div class="ui tiny button" onclick="showFileInfo(this.parentNode.parentNode)">Info</div>
  376. <div class="ui tiny green button" onclick="restoreThisFile($(this).parent().parent());">Restore</div>
  377. </div>
  378. <div class="content">
  379. <p style="word-break: break-all;"><img class="ui mini spaced image nointeract" src="${fileIcon}">
  380. ${fileObject.Filename}
  381. </p>
  382. </div>
  383. </div>`);
  384. });
  385. if (restorableFiles.length == 0){
  386. $("#restorableList").html(`<div class="item">
  387. <img class="ui mini image nointeract" src="../../img/system/file-notfound.svg">
  388. <div class="content">
  389. No Restorable File(s) for this virtual disk.
  390. </div>
  391. </div>`);
  392. }
  393. }
  394. $(".hiddenfile").hide();
  395. }
  396. })
  397. }
  398. function restoreAll(){
  399. if (confirm("Confirm restore all files from backup?")){
  400. $(".restorableFile").each(function(){
  401. //Restore this file if it is not a snapshot
  402. if ($(this).hasClass("snapshot") == false){
  403. restoreThisFile($(this));
  404. }
  405. });
  406. }
  407. }
  408. </script>
  409. </body>
  410. </html>