👁 查看:restore.js
路径:/home/boguraj1/craftandcreation.xyz/wp-content/plugins/backup/public/js/restore.js
大小:8.56 KB · 修改:2025-10-23 15:51:45 · 权限:0644 · 可写
var Restore = function(options) { this.init(options); };

const QUEUE_STATUS_PENDING = 1;
const QUEUE_STATUS_STARTED = 2;
const QUEUE_STATUS_PREPARING = 3;
const QUEUE_STATUS_DONE = 100;
const QUEUE_STATUS_PARTIALLY = 101;
const QUEUE_STATUS_FAILED = 102;
const QUEUE_STATUS_ABORTED = 103;
const QUEUE_STATUS_NEVER_FINISHED = 104;

const logBuffer = [];
let updateScheduled = false;
const maxLogLines = 500;

function appendLogEntry(entry) {
	const container = document.getElementById("log-container");
	const newEntry = document.createElement("div");
	newEntry.className = "log-entry";
	newEntry.textContent = entry;
	container.appendChild(newEntry);
	while (container.children.length > maxLogLines) {
		container.removeChild(container.firstChild);
	}
	container.scrollTop = container.scrollHeight;
}

// Buffered version to avoid frequent DOM updates
function bufferedLog(entry) {
	logBuffer.push(entry);

	if (!updateScheduled) {
		updateScheduled = true;
		setTimeout(() => {
			flushLog();
			updateScheduled = false;
		}, 300); // throttle interval
	}
}

function flushLog() {
	for (const entry of logBuffer) {
		appendLogEntry(entry);
	}
	logBuffer.length = 0;
}


Restore.prototype = {

	_queueId: '',
	_interval: null,
	_intervalTime: 3000,
	_completed: false,
	_restoreErrorCount: {},



	_updateProgressBar: function (id, progress) {
		if (isNaN(progress) || progress < 0) progress = 0; // Default to 0 if the percentage is invalid
		else if (progress > 100) progress = 100; // Cap at 100 if percentage exceeds 100
		const elm = document.getElementById(id);
		elm.style.width = progress + '%';
		elm.innerText = progress + '%';
	},
	_ajax: function (options, name) {
		if(this._restoreErrorCount[name] === undefined) this._restoreErrorCount[name] = 0;
		if (options.data === undefined || typeof options.data != 'object') options.data = {};
		if (options.success === undefined || typeof options.success != 'function') options.success = function(){};
		if (options.fail === undefined || typeof options.fail != 'function') options.fail = function(){};
		options.data.id = this._queueId;
		const xhr = new XMLHttpRequest();
		xhr.open("POST", location.protocol + '//' + location.host + location.pathname, true);
		xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		xhr.onreadystatechange = () => {
			if (xhr.readyState === XMLHttpRequest.DONE) {
				if (xhr.status === 200) {
					try {
						const response = JSON.parse(xhr.response);
						if (response.success) {
							this._restoreErrorCount[name] = 0; // Reset error count on success
							options.success(response.message, response.data);
						} else {
							this._restoreErrorCount[name]++;
							this._logError(`Error ${xhr.status}: ${response.message} (Attempt ${this._restoreErrorCount[name]}/10)`);
							if (this._restoreErrorCount[name] >= 10) {
								options.fail(xhr.status, response.message);
							} else {
								setTimeout(() => this._ajax(options, name), 3000); // Retry after 3 seconds
							}
						}
					} catch (e) {
						this._restoreErrorCount[name]++;
						this._logError(`Error ${xhr.status}: Invalid JSON response (Attempt ${this._restoreErrorCount[name]}/10)`);
						if (this._restoreErrorCount[name] >= 10) {
							options.fail(xhr.status, "Invalid response received from the server");
						} else {
							setTimeout(() => this._ajax(options, name), 3000); // Retry after 3 seconds
						}
					}
				} else {
					this._restoreErrorCount[name]++;
					this._logError(`HTTP Error ${xhr.status}: Attempt ${this._restoreErrorCount[name]}/10`);
					if (this._restoreErrorCount[name] >= 10) {
						options.fail(xhr.status, xhr.response);
					} else {
						setTimeout(() => this._ajax(options, name), 3000); // Retry after 3 seconds
					}
				}
			}
		};
		xhr.send(new URLSearchParams(options.data).toString());
	},
	_logError: function (message) {
		const currentLogEntries = data.log_entries.length;
		if (!self._lastLogCount) self._lastLogCount = 0;
		// Append only new entries
		if (currentLogEntries > self._lastLogCount) {
			const newEntries = data.log_entries.slice(self._lastLogCount);
			newEntries.forEach(entry => bufferedLog(entry));
			self._lastLogCount = currentLogEntries;
		}
	},
	_redirect: function () {
		setTimeout(function () {
			let pathArray = window.location.pathname.split('/');
			let wpIndex = pathArray.indexOf('wp-content');
			if (wpIndex !== -1) {
				pathArray = pathArray.slice(0, wpIndex);
			} else {
				pathArray.pop();
			}
			let basePath = pathArray.join('/');
			window.location.href = window.location.origin + basePath + '/wp-admin';
		}, 3000);
	},
	cancel: function () {
		this.stop();
		this._ajax({
			data: { action: 'cancel' },
			success: () => { // Arrow function ensures 'this' remains in scope
				this._success('Restore has been canceled. Redirecting...');
				this._redirect();
			},
			fail: (status, message) => {
				this._error(message);
			}
		}, 'cancel');
	},
	refresh: function () {
		window.location.reload();
	},
	completed: function () {
		this._ajax({
			data: { action: 'completed' },
			success: () => {
				this._success('Restore is completed! Redirecting to your website...');
				this._redirect();
			},
			fail: (status, message) => {
				this._error(message);
			}
		}, 'completed');
	},
	_success: function (message) {
		document.getElementById('success-message').innerText = message;
		document.getElementById('success-alert').style.display = 'block';
	},
	_error: function (message) {
		document.getElementById('error-message').innerText = message;
		document.getElementById('error-alert').style.display = 'block';
	},
	closeSuccess: function () {
		document.getElementById('success-alert').style.display = 'none';
	},
	closeError: function () {
		document.getElementById('error-alert').style.display = 'none';
	},
	stop: function () {
		if(this._interval !== null) {
			this._interval.clearInterval();
			this._interval = null;
		}
	},
	_checkStatus: function () {
		const self = this;
		this._ajax({
			data: { action: 'status' },
			success: function (message, data) {
				// Update progress bars
				self._updateProgressBar('progress', data.progress.percentage);
				self._updateProgressBar('subprogress', data.progress.sub_percentage);
				document.getElementById('subprogress-title').innerText = data.progress.message + ':';
				document.getElementById('subprogress-container').style.display = 'block';

				// Handle log updates efficiently
				const log = document.getElementById('log-container');
				const currentLogEntries = data.log_entries.length;

				// Store last log entry count to prevent unnecessary updates
				if (!self._lastLogCount || self._lastLogCount !== currentLogEntries) {
					self._lastLogCount = currentLogEntries; // Update log count

					const logFragment = document.createDocumentFragment();
					data.log_entries.forEach(entry => {
						const logEntry = document.createElement("div");
						logEntry.classList.add("log-entry");
						logEntry.innerText = entry;
						logFragment.appendChild(logEntry);
					});

					// Append new logs only if there’s a change
					log.innerHTML = ''; // Clear previous log only when updating
					log.appendChild(logFragment);

					setTimeout(() => {
						log.scrollTop = log.scrollHeight;
					}, 50);

				}

				// Recheck status if the process is ongoing
				if (data.status < QUEUE_STATUS_DONE) {
					setTimeout(function () {
						self._checkStatus();
					}, self._intervalTime);
				} else {
					if (data.status > QUEUE_STATUS_DONE) {
						if (data.status === QUEUE_STATUS_PARTIALLY) self._error("Restore partially completed");
						else if (data.status === QUEUE_STATUS_FAILED) self._error("Error occurred during restore, please review the logs");
						else if (data.status === QUEUE_STATUS_ABORTED) self._error("Restore aborted");
						else if (data.status === QUEUE_STATUS_NEVER_FINISHED) self._error("Restore never finished");
					} else {
						self._success("Restore is completed!");
						document.getElementById('finalize-btn').style.display = 'inline-block';
					}
				}
			},
			fail: function (status, message) {
				self._error(message);
			}
		}, '_checkStatus');
	},
	start: function () {
		const self = this;
		setTimeout(function () { self._checkStatus(); }, 1000);
		this._restore();
	},
	_restore: function () {

		const self = this;
		this._ajax({
			data: { action: 'restore' },
			success: function(message, data) {
				if(!self._completed && data.status < QUEUE_STATUS_DONE) self._restore();
			},
			fail: function (status, message) {
				self._error(message);
			}
		}, '_restore');
	},
	init: function (options) {
		this._queueId = options.queue_id
		if(options.interval !== undefined) this._intervalTime = options.interval;
	}
};