PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / public / js / restore.js
backup / public / js Last commit date
restore.js 7 months ago
restore.js
316 lines
1 var Restore = function(options) { this.init(options); };
2
3 const QUEUE_STATUS_PENDING = 1;
4 const QUEUE_STATUS_STARTED = 2;
5 const QUEUE_STATUS_PREPARING = 3;
6 const QUEUE_STATUS_DONE = 100;
7 const QUEUE_STATUS_PARTIALLY = 101;
8 const QUEUE_STATUS_FAILED = 102;
9 const QUEUE_STATUS_ABORTED = 103;
10 const QUEUE_STATUS_NEVER_FINISHED = 104;
11
12 const logBuffer = [];
13 let updateScheduled = false;
14 const maxLogLines = 500;
15 let logCursor = -1; // -1 = initial tail mode
16 let logAtBottom = true; // autoscroll only if already at bottom
17
18 let logEl = null;
19 const buffered = [];
20 let rafScheduled = false;
21
22 // Lazily resolve #log-container and bind scroll listener once
23 function ensureLogEl() {
24 if (!logEl) {
25 logEl = document.getElementById('log-container');
26 if (logEl && !logEl._scrollBound) {
27 logEl.addEventListener('scroll', () => {
28 logAtBottom = (logEl.scrollHeight - logEl.clientHeight - logEl.scrollTop) < 20;
29 });
30 logEl._scrollBound = true;
31 // initialize scroll state
32 logAtBottom = (logEl.scrollHeight - logEl.clientHeight - logEl.scrollTop) < 20;
33 }
34 }
35 return !!logEl;
36 }
37
38 // Try to resolve as soon as DOM is ready (covers header-enqueued scripts)
39 document.addEventListener('DOMContentLoaded', ensureLogEl);
40
41 // Append helpers (safe if element not ready yet)
42 function appendLines(lines) {
43 if (!lines || !lines.length) return;
44 if (!ensureLogEl()) return; // bail until container exists
45
46 const frag = document.createDocumentFragment();
47 for (const line of lines) {
48 const div = document.createElement('div');
49 div.className = 'log-entry';
50 div.textContent = line;
51 frag.appendChild(div);
52 }
53 logEl.appendChild(frag);
54
55 // cap nodes
56 while (logEl.children.length > maxLogLines) {
57 logEl.removeChild(logEl.firstChild);
58 }
59
60 if (logAtBottom) {
61 logEl.scrollTop = logEl.scrollHeight;
62 }
63 }
64
65 function bufferAppend(lines) {
66 if (!lines || !lines.length) return;
67 buffered.push(...lines);
68 if (!rafScheduled) {
69 rafScheduled = true;
70 requestAnimationFrame(() => {
71 appendLines(buffered.splice(0, buffered.length));
72 rafScheduled = false;
73 });
74 }
75 }
76
77 // --- legacy helpers kept, but made safe ---
78 function appendLogEntry(entry) {
79 if (!ensureLogEl()) return;
80 const newEntry = document.createElement("div");
81 newEntry.className = "log-entry";
82 newEntry.textContent = entry;
83 logEl.appendChild(newEntry);
84 while (logEl.children.length > maxLogLines) {
85 logEl.removeChild(logEl.firstChild);
86 }
87 logEl.scrollTop = logEl.scrollHeight;
88 }
89
90 function bufferedLog(entry) {
91 logBuffer.push(entry);
92 if (!updateScheduled) {
93 updateScheduled = true;
94 setTimeout(() => {
95 for (const e of logBuffer) appendLogEntry(e);
96 logBuffer.length = 0;
97 updateScheduled = false;
98 }, 300);
99 }
100 }
101
102 Restore.prototype = {
103 _queueId: '',
104 _interval: null,
105 _intervalTime: 3000,
106 _completed: false,
107 _restoreErrorCount: {},
108
109 _updateProgressBar: function (id, progress) {
110 if (isNaN(progress) || progress < 0) progress = 0;
111 else if (progress > 100) progress = 100;
112 const elm = document.getElementById(id);
113 if (!elm) return;
114 elm.style.width = progress + '%';
115 elm.innerText = progress + '%';
116 },
117
118 _ajax: function (options, name) {
119 if(this._restoreErrorCount[name] === undefined) this._restoreErrorCount[name] = 0;
120 if (options.data === undefined || typeof options.data != 'object') options.data = {};
121 if (options.success === undefined || typeof options.success != 'function') options.success = function(){};
122 if (options.fail === undefined || typeof options.fail != 'function') options.fail = function(){};
123 options.data.id = this._queueId;
124
125 const xhr = new XMLHttpRequest();
126 xhr.open("POST", location.protocol + '//' + location.host + location.pathname, true);
127 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
128 xhr.onreadystatechange = () => {
129 if (xhr.readyState === XMLHttpRequest.DONE) {
130 if (xhr.status === 200) {
131 try {
132 const response = JSON.parse(xhr.response);
133 if (response.success) {
134 this._restoreErrorCount[name] = 0;
135 options.success(response.message, response.data);
136 } else {
137 this._restoreErrorCount[name]++;
138 this._logError(`Error ${xhr.status}: ${response.message} (Attempt ${this._restoreErrorCount[name]}/10)`);
139 if (this._restoreErrorCount[name] >= 10) {
140 options.fail(xhr.status, response.message);
141 } else {
142 setTimeout(() => this._ajax(options, name), 3000);
143 }
144 }
145 } catch (e) {
146 this._restoreErrorCount[name]++;
147 this._logError(`Error ${xhr.status}: Invalid JSON response (Attempt ${this._restoreErrorCount[name]}/10)`);
148 if (this._restoreErrorCount[name] >= 10) {
149 options.fail(xhr.status, "Invalid response received from the server");
150 } else {
151 setTimeout(() => this._ajax(options, name), 3000);
152 }
153 }
154 } else {
155 this._restoreErrorCount[name]++;
156 this._logError(`HTTP Error ${xhr.status}: Attempt ${this._restoreErrorCount[name]}/10`);
157 if (this._restoreErrorCount[name] >= 10) {
158 options.fail(xhr.status, xhr.response);
159 } else {
160 setTimeout(() => this._ajax(options, name), 3000);
161 }
162 }
163 }
164 };
165 xhr.send(new URLSearchParams(options.data).toString());
166 },
167
168 // FIXED: this previously referenced undefined variables
169 _logError: function (message) {
170 bufferAppend([`[client] ${message}`]);
171 },
172
173 _redirect: function () {
174 setTimeout(function () {
175 let pathArray = window.location.pathname.split('/');
176 let wpIndex = pathArray.indexOf('wp-content');
177 if (wpIndex !== -1) pathArray = pathArray.slice(0, wpIndex);
178 else pathArray.pop();
179 let basePath = pathArray.join('/');
180 window.location.href = window.location.origin + basePath + '/wp-admin';
181 }, 3000);
182 },
183
184 cancel: function () {
185 this.stop();
186 this._ajax({
187 data: { action: 'cancel' },
188 success: () => {
189 this._success('Restore has been canceled. Redirecting...');
190 this._redirect();
191 },
192 fail: (status, message) => { this._error(message); }
193 }, 'cancel');
194 },
195
196 refresh: function () { window.location.reload(); },
197
198 completed: function () {
199 this._ajax({
200 data: { action: 'completed' },
201 success: () => {
202 this._success('Restore is completed! Redirecting to your website...');
203 this._redirect();
204 },
205 fail: (status, message) => { this._error(message); }
206 }, 'completed');
207 },
208
209 _success: function (message) {
210 const el = document.getElementById('success-message');
211 const box = document.getElementById('success-alert');
212 const progress_bars = document.getElementById('progress-bars-container');
213 if (el) el.innerText = message;
214 if (box) box.style.display = 'block';
215 if (progress_bars) progress_bars.style.display = 'none';
216 },
217
218 _error: function (message) {
219 const el = document.getElementById('error-message');
220 const box = document.getElementById('error-alert');
221 if (el) el.innerText = message;
222 if (box) box.style.display = 'block';
223 },
224
225 closeSuccess: function () {
226 const box = document.getElementById('success-alert');
227 if (box) box.style.display = 'none';
228 },
229
230 closeError: function () {
231 const box = document.getElementById('error-alert');
232 if (box) box.style.display = 'none';
233 },
234
235 stop: function () {
236 if (this._interval) {
237 clearInterval(this._interval);
238 this._interval = null;
239 }
240 },
241
242 _checkStatus: function () {
243 const self = this;
244 this._ajax({
245 data: { action: 'status', cursor: logCursor },
246 success: function (message, data) {
247 // progress bars
248 self._updateProgressBar('progress', data.progress.percentage);
249 self._updateProgressBar('subprogress', data.progress.sub_percentage);
250 const t = document.getElementById('subprogress-title');
251 const c = document.getElementById('subprogress-container');
252 if (t) t.innerText = data.progress.message + ':';
253 if (c) c.style.display = 'block';
254
255 // logs (incremental)
256 if (data.reset && ensureLogEl()) {
257 logEl.innerHTML = '';
258 }
259 if (typeof data.log_chunk === 'string' && data.log_chunk.length) {
260 const lines = data.log_chunk.replace(/\r/g, '').split('\n');
261 if (lines.length && lines[lines.length - 1] === '') lines.pop();
262 bufferAppend(lines);
263 }
264 if (typeof data.cursor === 'number') {
265 logCursor = data.cursor;
266 }
267
268 // re-poll
269 if (data.status < QUEUE_STATUS_DONE) {
270 setTimeout(function () { self._checkStatus(); }, self._intervalTime);
271 } else {
272 if (data.status > QUEUE_STATUS_DONE) {
273 if (data.status === QUEUE_STATUS_PARTIALLY) self._error("Restore partially completed");
274 else if (data.status === QUEUE_STATUS_FAILED) self._error("Error occurred during restore, please review the logs");
275 else if (data.status === QUEUE_STATUS_ABORTED) self._error("Restore aborted");
276 else if (data.status === QUEUE_STATUS_NEVER_FINISHED) self._error("Restore never finished");
277 } else {
278 self._success("Restore is completed!");
279 const btn = document.getElementById('finalize-btn');
280 if (btn) btn.style.display = 'inline-block';
281 }
282 }
283 },
284 fail: function (status, message) {
285 self._error(message);
286 }
287 }, '_checkStatus');
288 },
289
290 start: function () {
291 const self = this;
292 // try to resolve the log element before first poll
293 ensureLogEl();
294 setTimeout(function () { self._checkStatus(); }, 1000);
295 this._restore();
296 },
297
298 _restore: function () {
299 const self = this;
300 this._ajax({
301 data: { action: 'restore' },
302 success: function(message, data) {
303 if(!self._completed && data.status < QUEUE_STATUS_DONE) self._restore();
304 },
305 fail: function (status, message) {
306 self._error(message);
307 }
308 }, '_restore');
309 },
310
311 init: function (options) {
312 this._queueId = options.queue_id;
313 if (options.interval !== undefined) this._intervalTime = options.interval;
314 }
315 };
316