| 
									
										
										
										
											2025-10-18 13:48:40 -06:00
										 |  |  | import $ from 'jquery'; | 
					
						
							|  |  |  | import { config, translations } from 'grav-config'; | 
					
						
							|  |  |  | import request from '../utils/request'; | 
					
						
							|  |  |  | import toastr from '../utils/toastr'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const paramSep = config.param_sep; | 
					
						
							|  |  |  | const task = `task${paramSep}`; | 
					
						
							|  |  |  | const nonce = `admin-nonce${paramSep}${config.admin_nonce}`; | 
					
						
							|  |  |  | const base = `${config.base_url_relative}/update.json`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const urls = { | 
					
						
							|  |  |  |     restore: `${base}/${task}safeUpgradeRestore/${nonce}`, | 
					
						
							|  |  |  |     status: `${base}/${task}safeUpgradeStatus/${nonce}`, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RestoreManager { | 
					
						
							|  |  |  |     constructor() { | 
					
						
							|  |  |  |         this.job = null; | 
					
						
							|  |  |  |         this.pollTimer = null; | 
					
						
							| 
									
										
										
										
											2025-10-18 18:26:38 -06:00
										 |  |  |         this.pollFailures = 0; | 
					
						
							| 
									
										
										
										
											2025-10-18 13:48:40 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |         $(document).on('click', '[data-restore-snapshot]', (event) => { | 
					
						
							|  |  |  |             event.preventDefault(); | 
					
						
							|  |  |  |             const button = $(event.currentTarget); | 
					
						
							|  |  |  |             if (this.job) { | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             this.startRestore(button); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     startRestore(button) { | 
					
						
							|  |  |  |         const snapshot = button.data('restore-snapshot'); | 
					
						
							|  |  |  |         if (!snapshot) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         button.prop('disabled', true).addClass('is-loading'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const body = { snapshot }; | 
					
						
							|  |  |  |         request(urls.restore, { method: 'post', body }, (response) => { | 
					
						
							|  |  |  |             button.prop('disabled', false).removeClass('is-loading'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (!response) { | 
					
						
							|  |  |  |                 toastr.error(translations.PLUGIN_ADMIN?.RESTORE_GRAV_FAILED || 'Snapshot restore failed.'); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (response.status === 'error') { | 
					
						
							|  |  |  |                 toastr.error(response.message || translations.PLUGIN_ADMIN?.RESTORE_GRAV_FAILED || 'Snapshot restore failed.'); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const data = response.data || {}; | 
					
						
							|  |  |  |             const jobId = data.job_id || (data.job && data.job.id); | 
					
						
							|  |  |  |             if (!jobId) { | 
					
						
							|  |  |  |                 const message = response.message || translations.PLUGIN_ADMIN?.RESTORE_GRAV_FAILED || 'Snapshot restore failed.'; | 
					
						
							|  |  |  |                 toastr.error(message); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             this.job = { | 
					
						
							|  |  |  |                 id: jobId, | 
					
						
							|  |  |  |                 snapshot, | 
					
						
							|  |  |  |             }; | 
					
						
							| 
									
										
										
										
											2025-10-18 18:26:38 -06:00
										 |  |  |             this.pollFailures = 0; | 
					
						
							| 
									
										
										
										
											2025-10-18 13:48:40 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |             const runningMessage = translations.PLUGIN_ADMIN?.RESTORE_GRAV_RUNNING | 
					
						
							|  |  |  |                 ? translations.PLUGIN_ADMIN.RESTORE_GRAV_RUNNING.replace('%s', snapshot) | 
					
						
							|  |  |  |                 : `Restoring snapshot ${snapshot}...`; | 
					
						
							|  |  |  |             toastr.info(runningMessage); | 
					
						
							|  |  |  |             this.schedulePoll(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     schedulePoll(delay = 1200) { | 
					
						
							|  |  |  |         this.clearPoll(); | 
					
						
							|  |  |  |         this.pollTimer = setTimeout(() => this.pollStatus(), delay); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     clearPoll() { | 
					
						
							|  |  |  |         if (this.pollTimer) { | 
					
						
							|  |  |  |             clearTimeout(this.pollTimer); | 
					
						
							|  |  |  |             this.pollTimer = null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pollStatus() { | 
					
						
							|  |  |  |         if (!this.job) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const jobId = this.job.id; | 
					
						
							| 
									
										
										
										
											2025-10-18 18:26:38 -06:00
										 |  |  |         let handled = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 13:48:40 -06:00
										 |  |  |         request(`${urls.status}?job=${encodeURIComponent(jobId)}`, { silentErrors: true }, (response) => { | 
					
						
							| 
									
										
										
										
											2025-10-18 18:26:38 -06:00
										 |  |  |             handled = true; | 
					
						
							|  |  |  |             this.pollFailures = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 13:48:40 -06:00
										 |  |  |             if (!response || response.status !== 'success') { | 
					
						
							|  |  |  |                 this.schedulePoll(); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const data = response.data || {}; | 
					
						
							|  |  |  |             const job = data.job || {}; | 
					
						
							|  |  |  |             const progress = data.progress || {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const stage = progress.stage || null; | 
					
						
							|  |  |  |             const status = job.status || progress.status || null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (stage === 'error' || status === 'error') { | 
					
						
							|  |  |  |                 const message = job.error || progress.message || translations.PLUGIN_ADMIN?.RESTORE_GRAV_FAILED || 'Snapshot restore failed.'; | 
					
						
							|  |  |  |                 toastr.error(message); | 
					
						
							|  |  |  |                 this.job = null; | 
					
						
							|  |  |  |                 this.clearPoll(); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (stage === 'complete' || status === 'success') { | 
					
						
							|  |  |  |                 const snapshot = progress.snapshot || this.job.snapshot; | 
					
						
							|  |  |  |                 const version = (job.result && job.result.version) || progress.version || ''; | 
					
						
							|  |  |  |                 let successMessage; | 
					
						
							|  |  |  |                 if (translations.PLUGIN_ADMIN?.RESTORE_GRAV_SUCCESS_MESSAGE && version) { | 
					
						
							|  |  |  |                     successMessage = translations.PLUGIN_ADMIN.RESTORE_GRAV_SUCCESS_MESSAGE.replace('%1$s', snapshot).replace('%2$s', version); | 
					
						
							|  |  |  |                 } else if (translations.PLUGIN_ADMIN?.RESTORE_GRAV_SUCCESS_SIMPLE) { | 
					
						
							|  |  |  |                     successMessage = translations.PLUGIN_ADMIN.RESTORE_GRAV_SUCCESS_SIMPLE.replace('%s', snapshot); | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     successMessage = version ? `Snapshot ${snapshot} restored (Grav ${version}).` : `Snapshot ${snapshot} restored.`; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 toastr.success(successMessage); | 
					
						
							|  |  |  |                 this.job = null; | 
					
						
							|  |  |  |                 this.clearPoll(); | 
					
						
							|  |  |  |                 setTimeout(() => window.location.reload(), 1500); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             this.schedulePoll(); | 
					
						
							| 
									
										
										
										
											2025-10-18 18:26:38 -06:00
										 |  |  |         }).then(() => { | 
					
						
							|  |  |  |             if (!handled) { | 
					
						
							|  |  |  |                 this.handleSilentFailure(); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-10-18 13:48:40 -06:00
										 |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-10-18 18:26:38 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     handleSilentFailure() { | 
					
						
							|  |  |  |         if (!this.job) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.pollFailures += 1; | 
					
						
							|  |  |  |         const snapshot = this.job.snapshot || ''; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (this.pollFailures >= 3) { | 
					
						
							|  |  |  |             const message = snapshot | 
					
						
							|  |  |  |                 ? `Snapshot ${snapshot} restore is completing. Reloading...` | 
					
						
							|  |  |  |                 : 'Snapshot restore is completing. Reloading...'; | 
					
						
							|  |  |  |             toastr.info(message); | 
					
						
							|  |  |  |             this.job = null; | 
					
						
							|  |  |  |             this.clearPoll(); | 
					
						
							|  |  |  |             setTimeout(() => window.location.reload(), 1500); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const delay = Math.min(5000, 1200 * this.pollFailures); | 
					
						
							|  |  |  |         this.schedulePoll(delay); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-10-18 13:48:40 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Initialize restore manager when tools view loads.
 | 
					
						
							|  |  |  | $(document).ready(() => { | 
					
						
							|  |  |  |     new RestoreManager(); | 
					
						
							|  |  |  | }); |