mirror of
https://github.com/getgrav/grav-plugin-admin.git
synced 2025-10-26 00:36:31 +02:00
dedicated endpoint for upgrade
This commit is contained in:
83
safe-upgrade-status.php
Normal file
83
safe-upgrade-status.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$root = dirname(__DIR__, 3);
|
||||
$jobsDir = $root . '/user/data/upgrades/jobs';
|
||||
$fallbackProgress = $root . '/user/data/upgrades/safe-upgrade-progress.json';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
$jobId = isset($_GET['job']) ? (string)$_GET['job'] : '';
|
||||
|
||||
if ($jobId !== '' && !preg_match('/^job-[A-Za-z0-9\\-]+$/', $jobId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid job identifier.',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$readJson = static function (string $path): ?array {
|
||||
if (!is_file($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode((string)file_get_contents($path), true);
|
||||
|
||||
return is_array($decoded) ? $decoded : null;
|
||||
};
|
||||
|
||||
$progress = null;
|
||||
$manifest = null;
|
||||
|
||||
if ($jobId !== '') {
|
||||
$jobPath = $jobsDir . '/' . $jobId;
|
||||
$progress = $readJson($jobPath . '/progress.json');
|
||||
$manifest = $readJson($jobPath . '/manifest.json');
|
||||
|
||||
if (!$progress && !$manifest && !is_dir($jobPath)) {
|
||||
$progress = $readJson($fallbackProgress) ?: [
|
||||
'stage' => 'idle',
|
||||
'message' => '',
|
||||
'percent' => null,
|
||||
'timestamp' => time(),
|
||||
];
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'message' => 'Safe upgrade job not found.',
|
||||
'data' => [
|
||||
'job' => null,
|
||||
'progress' => $progress,
|
||||
],
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($progress === null) {
|
||||
$progress = $readJson($fallbackProgress) ?: [
|
||||
'stage' => 'idle',
|
||||
'message' => '',
|
||||
'percent' => null,
|
||||
'timestamp' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
if ($jobId !== '' && is_array($progress) && !isset($progress['job_id'])) {
|
||||
$progress['job_id'] = $jobId;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'job' => $manifest ?: null,
|
||||
'progress' => $progress,
|
||||
],
|
||||
]);
|
||||
|
||||
exit;
|
||||
@@ -53,6 +53,9 @@ export default class SafeUpgrade {
|
||||
this.isPolling = false;
|
||||
this.active = false;
|
||||
this.jobId = null;
|
||||
this.statusFailures = 0;
|
||||
this.directStatusUrl = this.resolveDirectStatusUrl();
|
||||
this.preferDirectStatus = !!this.directStatusUrl;
|
||||
|
||||
this.registerEvents();
|
||||
}
|
||||
@@ -115,6 +118,8 @@ export default class SafeUpgrade {
|
||||
open() {
|
||||
this.active = true;
|
||||
this.decisions = {};
|
||||
this.statusFailures = 0;
|
||||
this.preferDirectStatus = !!this.directStatusUrl;
|
||||
this.renderLoading();
|
||||
this.modal.open();
|
||||
this.fetchPreflight();
|
||||
@@ -405,6 +410,8 @@ export default class SafeUpgrade {
|
||||
if (data.progress) {
|
||||
this.renderProgress(data.progress);
|
||||
}
|
||||
this.statusFailures = 0;
|
||||
this.preferDirectStatus = !!this.directStatusUrl;
|
||||
this.beginPolling(1200);
|
||||
} else {
|
||||
this.renderResult(data);
|
||||
@@ -413,6 +420,49 @@ export default class SafeUpgrade {
|
||||
});
|
||||
}
|
||||
|
||||
resolveDirectStatusUrl() {
|
||||
const scriptPath = '/user/plugins/admin/safe-upgrade-status.php';
|
||||
const join = (base, path) => {
|
||||
if (!base) {
|
||||
return path;
|
||||
}
|
||||
const trimmed = base.endsWith('/') ? base.slice(0, -1) : base;
|
||||
return `${trimmed}${path}`;
|
||||
};
|
||||
const normalize = (url) => url.replace(/([^:]\/)\/+/g, '$1');
|
||||
|
||||
const candidates = [
|
||||
config.base_url_simple || '',
|
||||
(config.base_url_relative || '').replace(/\/admin\/?$/, ''),
|
||||
''
|
||||
];
|
||||
|
||||
for (const base of candidates) {
|
||||
if (typeof base !== 'string') {
|
||||
continue;
|
||||
}
|
||||
const candidate = normalize(join(base, scriptPath));
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return scriptPath;
|
||||
}
|
||||
|
||||
resolveStatusEndpoint() {
|
||||
const useDirect = this.directStatusUrl && this.preferDirectStatus;
|
||||
let url = useDirect ? this.directStatusUrl : this.urls.status;
|
||||
if (this.jobId) {
|
||||
url += (url.includes('?') ? '&' : '?') + `job=${encodeURIComponent(this.jobId)}`;
|
||||
}
|
||||
|
||||
return {
|
||||
url,
|
||||
direct: useDirect
|
||||
};
|
||||
}
|
||||
|
||||
beginPolling(delay = 1200) {
|
||||
if (this.isPolling) {
|
||||
return;
|
||||
@@ -448,14 +498,26 @@ export default class SafeUpgrade {
|
||||
let jobComplete = false;
|
||||
let jobFailed = false;
|
||||
let shouldReload = false;
|
||||
let handled = false;
|
||||
|
||||
console.debug('[SafeUpgrade] poll status');
|
||||
|
||||
const statusUrl = this.jobId ? `${this.urls.status}?job=${encodeURIComponent(this.jobId)}` : this.urls.status;
|
||||
const endpoint = this.resolveStatusEndpoint();
|
||||
const statusUrl = endpoint.url;
|
||||
const usingDirect = endpoint.direct;
|
||||
const requestOptions = { silentErrors: true };
|
||||
|
||||
this.statusRequest = request(statusUrl, (response) => {
|
||||
this.statusRequest = request(statusUrl, requestOptions, (response) => {
|
||||
console.debug('[SafeUpgrade] status response', response);
|
||||
|
||||
if (!response) {
|
||||
this.statusFailures += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
handled = true;
|
||||
this.statusFailures = 0;
|
||||
|
||||
if (response.status === 'error') {
|
||||
if (!silent) {
|
||||
this.renderProgress({
|
||||
@@ -513,7 +575,16 @@ export default class SafeUpgrade {
|
||||
return;
|
||||
}
|
||||
|
||||
if (jobFailed) {
|
||||
if (!handled) {
|
||||
if (usingDirect && this.statusFailures >= 3) {
|
||||
this.preferDirectStatus = false;
|
||||
this.statusFailures = 0;
|
||||
this.schedulePoll();
|
||||
} else {
|
||||
const delay = Math.min(5000, 1200 * Math.max(1, this.statusFailures));
|
||||
this.schedulePoll(delay);
|
||||
}
|
||||
} else if (jobFailed) {
|
||||
this.stopPolling();
|
||||
this.jobId = null;
|
||||
} else if (jobComplete || nextStage === 'complete') {
|
||||
|
||||
@@ -8,6 +8,11 @@ let request = function(url, options = {}, callback = () => true) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
const silentErrors = !!options.silentErrors;
|
||||
if (options.silentErrors) {
|
||||
delete options.silentErrors;
|
||||
}
|
||||
|
||||
if (options.method && options.method === 'post') {
|
||||
let data = new FormData();
|
||||
|
||||
@@ -34,7 +39,16 @@ let request = function(url, options = {}, callback = () => true) {
|
||||
.then(parseJSON)
|
||||
.then(userFeedback)
|
||||
.then((response) => callback(response, raw))
|
||||
.catch(userFeedbackError);
|
||||
.catch((error) => {
|
||||
if (silentErrors) {
|
||||
console.debug('[Request] silent failure', url, error);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
userFeedbackError(error);
|
||||
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
export default request;
|
||||
|
||||
74
themes/grav/js/admin.min.js
vendored
74
themes/grav/js/admin.min.js
vendored
@@ -4609,6 +4609,9 @@ var SafeUpgrade = /*#__PURE__*/function () {
|
||||
this.isPolling = false;
|
||||
this.active = false;
|
||||
this.jobId = null;
|
||||
this.statusFailures = 0;
|
||||
this.directStatusUrl = this.resolveDirectStatusUrl();
|
||||
this.preferDirectStatus = !!this.directStatusUrl;
|
||||
this.registerEvents();
|
||||
}
|
||||
return safe_upgrade_createClass(SafeUpgrade, [{
|
||||
@@ -4671,6 +4674,8 @@ var SafeUpgrade = /*#__PURE__*/function () {
|
||||
value: function open() {
|
||||
this.active = true;
|
||||
this.decisions = {};
|
||||
this.statusFailures = 0;
|
||||
this.preferDirectStatus = !!this.directStatusUrl;
|
||||
this.renderLoading();
|
||||
this.modal.open();
|
||||
this.fetchPreflight();
|
||||
@@ -4875,6 +4880,8 @@ var SafeUpgrade = /*#__PURE__*/function () {
|
||||
if (data.progress) {
|
||||
_this4.renderProgress(data.progress);
|
||||
}
|
||||
_this4.statusFailures = 0;
|
||||
_this4.preferDirectStatus = !!_this4.directStatusUrl;
|
||||
_this4.beginPolling(1200);
|
||||
} else {
|
||||
_this4.renderResult(data);
|
||||
@@ -4882,6 +4889,46 @@ var SafeUpgrade = /*#__PURE__*/function () {
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: "resolveDirectStatusUrl",
|
||||
value: function resolveDirectStatusUrl() {
|
||||
var scriptPath = '/user/plugins/admin/safe-upgrade-status.php';
|
||||
var join = function join(base, path) {
|
||||
if (!base) {
|
||||
return path;
|
||||
}
|
||||
var trimmed = base.endsWith('/') ? base.slice(0, -1) : base;
|
||||
return "".concat(trimmed).concat(path);
|
||||
};
|
||||
var normalize = function normalize(url) {
|
||||
return url.replace(/([^:]\/)\/+/g, '$1');
|
||||
};
|
||||
var candidates = [external_GravAdmin_namespaceObject.config.base_url_simple || '', (external_GravAdmin_namespaceObject.config.base_url_relative || '').replace(/\/admin\/?$/, ''), ''];
|
||||
for (var i = 0; i < candidates.length; i += 1) {
|
||||
var base = candidates[i];
|
||||
if (typeof base !== 'string') {
|
||||
continue;
|
||||
}
|
||||
var candidate = normalize(join(base, scriptPath));
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return scriptPath;
|
||||
}
|
||||
}, {
|
||||
key: "resolveStatusEndpoint",
|
||||
value: function resolveStatusEndpoint() {
|
||||
var useDirect = this.directStatusUrl && this.preferDirectStatus;
|
||||
var url = useDirect ? this.directStatusUrl : this.urls.status;
|
||||
if (this.jobId) {
|
||||
url += (url.indexOf('?') !== -1 ? '&' : '?') + "job=".concat(encodeURIComponent(this.jobId));
|
||||
}
|
||||
return {
|
||||
url: url,
|
||||
direct: useDirect
|
||||
};
|
||||
}
|
||||
}, {
|
||||
key: "beginPolling",
|
||||
value: function beginPolling() {
|
||||
@@ -4926,10 +4973,22 @@ var SafeUpgrade = /*#__PURE__*/function () {
|
||||
var jobComplete = false;
|
||||
var jobFailed = false;
|
||||
var shouldReload = false;
|
||||
var handled = false;
|
||||
console.debug('[SafeUpgrade] poll status');
|
||||
var statusUrl = this.jobId ? "".concat(this.urls.status, "?job=").concat(encodeURIComponent(this.jobId)) : this.urls.status;
|
||||
this.statusRequest = utils_request(statusUrl, function (response) {
|
||||
var endpoint = this.resolveStatusEndpoint();
|
||||
var statusUrl = endpoint.url;
|
||||
var usingDirect = endpoint.direct;
|
||||
var requestOptions = {
|
||||
silentErrors: true
|
||||
};
|
||||
this.statusRequest = utils_request(statusUrl, requestOptions, function (response) {
|
||||
console.debug('[SafeUpgrade] status response', response);
|
||||
if (!response) {
|
||||
_this6.statusFailures += 1;
|
||||
return;
|
||||
}
|
||||
handled = true;
|
||||
_this6.statusFailures = 0;
|
||||
if (response.status === 'error') {
|
||||
if (!silent) {
|
||||
_this6.renderProgress({
|
||||
@@ -4980,7 +5039,16 @@ var SafeUpgrade = /*#__PURE__*/function () {
|
||||
if (!_this6.isPolling) {
|
||||
return;
|
||||
}
|
||||
if (jobFailed) {
|
||||
if (!handled) {
|
||||
if (usingDirect && _this6.statusFailures >= 3) {
|
||||
_this6.preferDirectStatus = false;
|
||||
_this6.statusFailures = 0;
|
||||
_this6.schedulePoll();
|
||||
} else {
|
||||
var delay = Math.min(5000, 1200 * Math.max(1, _this6.statusFailures));
|
||||
_this6.schedulePoll(delay);
|
||||
}
|
||||
} else if (jobFailed) {
|
||||
_this6.stopPolling();
|
||||
_this6.jobId = null;
|
||||
} else if (jobComplete || nextStage === 'complete') {
|
||||
|
||||
Reference in New Issue
Block a user