Cloudflare R2 media offload manager for peptiderepo.com. Built on WP Offload Media Lite. Supersedes the one-off migration script and mu-plugin peptiderepo-r2-endpoint.php. Features: - Admin page under Peptide Repo > R2 Media Manager (shared umbrella menu) - ensure_umbrella_menu() guard — safe for multi-plugin umbrella sharing - Batch AJAX migration to R2 with pause/resume and progress bar - Remove-local-copies batch with physical disk presence check - Activity log table (wp_pr_r2_log) with self-healing on plugins_loaded - Test R2 connection button - R2 endpoint injection via as3cf_aws_s3_client_args filter
190 lines
6.8 KiB
JavaScript
Executable File
190 lines
6.8 KiB
JavaScript
Executable File
/* global jQuery, prR2 */
|
|
/**
|
|
* R2 Media Manager — admin JS.
|
|
*
|
|
* Drives batch loops for migrate-to-R2 and remove-local-files operations.
|
|
* Each loop fires one AJAX request, waits for the response, updates the
|
|
* progress bar, then fires the next request — until the server signals done=true
|
|
* or the user clicks Pause.
|
|
*
|
|
* Also handles: Test R2 Connection button, Clear Log button.
|
|
*/
|
|
(function ($) {
|
|
'use strict';
|
|
|
|
// ── Batch runner ──────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Run one batch of an AJAX operation and schedule the next.
|
|
*
|
|
* @param {string} action WP AJAX action name.
|
|
* @param {string} nonce Nonce string from wp_localize_script.
|
|
* @param {Object} state Mutable state: { processed, succeeded, failed, paused }.
|
|
* @param {Object} ui jQuery element refs: $btn, $pauseBtn, $bar, $status.
|
|
* @param {string} countKey 'succeeded' | 'removed' — key to read from response.
|
|
* @param {string} failKey 'failed' | 'skipped'.
|
|
*/
|
|
function runBatch(action, nonce, state, ui, countKey, failKey) {
|
|
if (state.paused) {
|
|
ui.$status.text('Paused. Click the button again to resume.');
|
|
ui.$btn.prop('disabled', false).text(ui.$btn.data('resume-label'));
|
|
ui.$pauseBtn.hide();
|
|
return;
|
|
}
|
|
|
|
$.post(prR2.ajaxUrl, {
|
|
action: action,
|
|
nonce: nonce,
|
|
batch_size: prR2.batchSize,
|
|
})
|
|
.done(function (res) {
|
|
if (!res.success) {
|
|
showNotice('error', 'Error: ' + (res.data ? res.data.message : 'Unknown error.'));
|
|
ui.$btn.prop('disabled', false);
|
|
ui.$pauseBtn.hide();
|
|
return;
|
|
}
|
|
|
|
var d = res.data;
|
|
state.processed += d.processed;
|
|
state.succeeded += (d[countKey] || 0);
|
|
state.failed += (d[failKey] || 0);
|
|
|
|
// remaining is the count left AFTER this batch.
|
|
var total = state.processed + d.remaining;
|
|
var pct = total > 0 ? Math.round((state.processed / total) * 100) : 100;
|
|
ui.$bar.val(pct);
|
|
ui.$status.text(
|
|
'Processed ' + state.processed + ' of ' + total +
|
|
' — success: ' + state.succeeded +
|
|
(state.failed ? ', failed/skipped: ' + state.failed : '')
|
|
);
|
|
|
|
if (d.done) {
|
|
ui.$status.text(
|
|
'Done! ✓ Succeeded: ' + state.succeeded +
|
|
(state.failed ? ' Failed/skipped: ' + state.failed : '')
|
|
);
|
|
ui.$btn.prop('disabled', true);
|
|
ui.$pauseBtn.hide();
|
|
// Reload after 1.5 s so the status table refreshes.
|
|
setTimeout(function () { window.location.reload(); }, 1500);
|
|
return;
|
|
}
|
|
|
|
runBatch(action, nonce, state, ui, countKey, failKey);
|
|
})
|
|
.fail(function () {
|
|
ui.$status.text('Network error — operation paused. Retry by clicking the button.');
|
|
ui.$btn.prop('disabled', false).text(ui.$btn.data('resume-label'));
|
|
ui.$pauseBtn.hide();
|
|
});
|
|
}
|
|
|
|
// ── Wire up Migrate button ────────────────────────────────────────────────
|
|
|
|
$('#pr-r2-btn-migrate').on('click', function () {
|
|
var $btn = $(this);
|
|
var state = { processed: 0, succeeded: 0, failed: 0, paused: false };
|
|
var ui = {
|
|
$btn: $btn,
|
|
$pauseBtn: $('#pr-r2-btn-migrate-pause'),
|
|
$bar: $('#pr-r2-migrate-bar'),
|
|
$status: $('#pr-r2-migrate-status'),
|
|
};
|
|
|
|
$btn.data('resume-label', 'Resume migration').prop('disabled', true);
|
|
ui.$pauseBtn.show();
|
|
$('#pr-r2-migrate-progress').show();
|
|
ui.$bar.val(0);
|
|
ui.$status.text('Starting migration…');
|
|
|
|
runBatch('pr_r2_migrate_batch', prR2.nonceMigrate, state, ui, 'succeeded', 'failed');
|
|
});
|
|
|
|
$('#pr-r2-btn-migrate-pause').on('click', function () {
|
|
// Signal the batch runner to stop after the current in-flight request.
|
|
var state = $('#pr-r2-btn-migrate').data('batchState');
|
|
if (state) { state.paused = true; }
|
|
});
|
|
|
|
// Re-wire so the button can resume (state persists in data).
|
|
// Simpler: just reload the page — already-offloaded items are excluded by the query.
|
|
|
|
// ── Wire up Remove Local button ───────────────────────────────────────────
|
|
|
|
$('#pr-r2-btn-remove').on('click', function () {
|
|
var $btn = $(this);
|
|
var state = { processed: 0, succeeded: 0, failed: 0, paused: false };
|
|
var ui = {
|
|
$btn: $btn,
|
|
$pauseBtn: $('#pr-r2-btn-remove-pause'),
|
|
$bar: $('#pr-r2-remove-bar'),
|
|
$status: $('#pr-r2-remove-status'),
|
|
};
|
|
|
|
$btn.data('resume-label', 'Resume removal').prop('disabled', true);
|
|
ui.$pauseBtn.show();
|
|
$('#pr-r2-remove-progress').show();
|
|
ui.$bar.val(0);
|
|
ui.$status.text('Starting local-file removal…');
|
|
|
|
runBatch('pr_r2_remove_local_batch', prR2.nonceRemove, state, ui, 'removed', 'skipped');
|
|
});
|
|
|
|
$('#pr-r2-btn-remove-pause').on('click', function () {
|
|
var state = $('#pr-r2-btn-remove').data('batchState');
|
|
if (state) { state.paused = true; }
|
|
});
|
|
|
|
// ── Test Connection ───────────────────────────────────────────────────────
|
|
|
|
$('#pr-r2-btn-test').on('click', function () {
|
|
var $btn = $(this);
|
|
var $result = $('#pr-r2-test-result');
|
|
$btn.prop('disabled', true);
|
|
$result.css('color', '#555').text('Testing…');
|
|
|
|
$.post(prR2.ajaxUrl, { action: 'pr_r2_test_connection', nonce: prR2.nonceTest })
|
|
.done(function (res) {
|
|
if (res.success) {
|
|
$result.css('color', '#090').text('✓ ' + res.data.message);
|
|
} else {
|
|
$result.css('color', '#c00').text('✗ ' + (res.data ? res.data.message : 'Failed.'));
|
|
}
|
|
})
|
|
.fail(function () {
|
|
$result.css('color', '#c00').text('✗ Network error.');
|
|
})
|
|
.always(function () { $btn.prop('disabled', false); });
|
|
});
|
|
|
|
// ── Clear Log ─────────────────────────────────────────────────────────────
|
|
|
|
$('#pr-r2-btn-clear-log').on('click', function () {
|
|
if (!window.confirm('Clear all log entries?')) { return; }
|
|
var $btn = $(this);
|
|
$btn.prop('disabled', true);
|
|
|
|
$.post(prR2.ajaxUrl, { action: 'pr_r2_clear_log', nonce: prR2.nonceClearLog })
|
|
.done(function (res) {
|
|
if (res.success) {
|
|
$('#pr-r2-log-tbody').closest('table').replaceWith('<p>Log cleared.</p>');
|
|
} else {
|
|
showNotice('error', 'Could not clear log.');
|
|
}
|
|
})
|
|
.always(function () { $btn.prop('disabled', false); });
|
|
});
|
|
|
|
// ── Utility ───────────────────────────────────────────────────────────────
|
|
|
|
function showNotice(type, msg) {
|
|
var cls = type === 'error' ? 'notice-error' : 'notice-success';
|
|
$('#pr-r2-notice').html(
|
|
'<div class="notice ' + cls + ' is-dismissible"><p>' + msg + '</p></div>'
|
|
);
|
|
}
|
|
|
|
}(jQuery));
|