Plugin automatic-ai-posts (v2.0.0) - Pipeline completo: coleta de feeds RSS/Atom, filtro por IA, reescrita, categoria, imagem de capa e agendamento - Página de configuração com teste real do provedor de IA - Editor: botão para refazer imagem de capa e metadados, textos em PT-BR - Ajuda com botão Pix (copia-e-cola + QR) e sem nota de avaliação - Cache-busting por mtime nos assets do admin Tema gustavoo-portfolio - Marca do cabeçalho reduzida a "Gustavo" - Suporte a título próprio da marca Setup (docker/wp-init.sh, compose.yaml) - Plugins de terceiros (Post Views Counter, Rank Math, Site Kit) instalados do WordPress.org em vez de versionados - Falha explícita ao final se algum plugin obrigatório não instalar - WPCLI_UID/WPCLI_GID configuráveis Seeder do tema - Links permanentes "Dia e nome" e avatar "Iniciais" aplicados apenas em instalação nova, com regravação do .htaccess
803 lines
31 KiB
JavaScript
803 lines
31 KiB
JavaScript
jQuery(function ($) {
|
|
|
|
if (typeof aapData === 'undefined') return;
|
|
|
|
var ajaxurl = aapData.ajaxurl;
|
|
var s = aapData.s; // translated strings
|
|
|
|
var $doc = $(document);
|
|
var $form = $('#aap-settings-form');
|
|
var $exec = $('#rap-auto-interval');
|
|
|
|
// O <form> é fechado logo após o grid de configurações (para que os botões de
|
|
// ação dos outros painéis não o enviem). Por isso os campos do formulário são
|
|
// mantidos em sincronia com os valores únicos exibidos na aba Execuções:
|
|
// - intervalo do automático: preenchido a partir do valor salvo
|
|
// - categorias GNews: os checkboxes só existem no formulário principal
|
|
function mirrorFromSaved() {
|
|
if (!$exec.length) return;
|
|
$exec.val(aapData.interval_value);
|
|
$('#rap-auto-unit').val(aapData.interval_unit);
|
|
}
|
|
|
|
function mirrorToForm() {
|
|
if (!$exec.length) return;
|
|
$('input[name="aap_auto_interval_value"]').val($exec.val());
|
|
$('select[name="aap_auto_interval_unit"]').val($('#rap-auto-unit').val());
|
|
}
|
|
|
|
// O destaque dos botões de categoria é controlado pela rolagem (scroll spy)
|
|
|
|
// ── Doação Pix: copiar a chave ──────────────────────────────────────────
|
|
$doc.on('click', '#aap-pix-btn', function () {
|
|
var key = String($(this).data('pix') || '');
|
|
if (!key) return;
|
|
|
|
var $feedback = $('#aap-pix-feedback');
|
|
|
|
function done() {
|
|
$feedback.removeClass('is-error').addClass('is-success').text(s.pix_copied || 'OK');
|
|
setTimeout(function () {
|
|
$feedback.text('').removeClass('is-success');
|
|
}, 4000);
|
|
}
|
|
|
|
function fallback() {
|
|
var $tmp = $('<textarea>').val(key).css({ position: 'fixed', opacity: 0 }).appendTo('body');
|
|
$tmp[0].select();
|
|
try { document.execCommand('copy'); done(); } catch (e) {}
|
|
$tmp.remove();
|
|
}
|
|
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
navigator.clipboard.writeText(key).then(done).catch(fallback);
|
|
} else {
|
|
fallback();
|
|
}
|
|
});
|
|
|
|
// ── Teste da conexão com a IA ───────────────────────────────────────────
|
|
$doc.on('click', '#aap-test-ai-btn', function () {
|
|
var $btn = $(this);
|
|
var $feedback = $('#aap-test-ai-feedback');
|
|
var i18n = aapData.ai_test || {};
|
|
|
|
if ($btn.data('busy')) return;
|
|
$btn.data('busy', true).prop('disabled', true);
|
|
|
|
$feedback.removeClass('is-error is-success').addClass('is-pending')
|
|
.text(i18n.testing || '...');
|
|
|
|
$.post(aapData.ajaxurl, {
|
|
action: 'aap_test_ai',
|
|
nonce: $btn.data('nonce')
|
|
})
|
|
.done(function (res) {
|
|
var data = (res && res.data) || {};
|
|
|
|
if (res && res.success) {
|
|
$feedback.removeClass('is-pending').addClass('is-success').text(
|
|
(i18n.ok || 'OK') + ' — ' + data.provider
|
|
+ (data.model ? ' · ' + data.model : '')
|
|
+ (data.ms ? ' (' + data.ms + ' ms)' : '')
|
|
);
|
|
} else {
|
|
$feedback.removeClass('is-pending').addClass('is-error')
|
|
.text(data.error || i18n.error || 'Error.');
|
|
}
|
|
})
|
|
.fail(function () {
|
|
$feedback.removeClass('is-pending').addClass('is-error')
|
|
.text(i18n.error || 'Error.');
|
|
})
|
|
.always(function () {
|
|
$btn.data('busy', false).prop('disabled', false);
|
|
});
|
|
});
|
|
|
|
// ── Provedor de IA: mostra apenas os campos do provedor ativo ───────────
|
|
(function providerFields() {
|
|
var $select = $('#aap_ai_provider');
|
|
if (!$select.length) return;
|
|
|
|
var defaults = aapData.provider_defaults || {};
|
|
|
|
function refresh() {
|
|
var id = $select.val();
|
|
|
|
$('.aap-provider-block').each(function () {
|
|
var $block = $(this);
|
|
$block.prop('hidden', String($block.data('provider')) !== String(id));
|
|
});
|
|
|
|
var $model = $('#aap_model');
|
|
if ($model.length && defaults[id]) {
|
|
$model.attr('placeholder', defaults[id]);
|
|
|
|
var known = $.map(defaults, function (value) { return value; });
|
|
var value = $model.val();
|
|
|
|
if (value === '' || $.inArray(value, known) !== -1) {
|
|
$model.val(defaults[id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
$select.on('change', refresh);
|
|
refresh();
|
|
})();
|
|
|
|
// ── Alterações não salvas: barra flutuante + descartar ──────────────────
|
|
(function unsavedChanges() {
|
|
if (!$form.length) return;
|
|
|
|
var $float = $('#aap-save-float');
|
|
var $discard = $('#aap-discard-all');
|
|
if (!$float.length) return;
|
|
|
|
var fields = function () {
|
|
return $form.find('input, select, textarea').filter(':enabled');
|
|
};
|
|
|
|
// Fotografia do estado inicial de cada campo
|
|
function snapshot() {
|
|
var snap = {};
|
|
fields().each(function () {
|
|
var el = this;
|
|
var key = el.name || el.id;
|
|
if (!key) return;
|
|
if (el.type === 'checkbox' || el.type === 'radio') {
|
|
snap[key + '::' + (el.value || '')] = el.checked;
|
|
} else {
|
|
snap[key] = el.value;
|
|
}
|
|
});
|
|
return snap;
|
|
}
|
|
|
|
var initial = snapshot();
|
|
|
|
function isDirty() {
|
|
var now = snapshot();
|
|
var k;
|
|
for (k in now) {
|
|
if (now[k] !== initial[k]) return true;
|
|
}
|
|
for (k in initial) {
|
|
if (!(k in now)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function refresh() {
|
|
$float.prop('hidden', !isDirty());
|
|
}
|
|
|
|
// Desfaz tudo o que foi digitado desde o carregamento
|
|
function restore() {
|
|
fields().each(function () {
|
|
var el = this;
|
|
var key = el.name || el.id;
|
|
if (!key) return;
|
|
if (el.type === 'checkbox' || el.type === 'radio') {
|
|
var k = key + '::' + (el.value || '');
|
|
if (k in initial) el.checked = initial[k];
|
|
} else if (key in initial) {
|
|
el.value = initial[key];
|
|
}
|
|
});
|
|
}
|
|
|
|
$form.on('input change', 'input, select, textarea', refresh);
|
|
|
|
$discard.on('click', function () {
|
|
restore();
|
|
refresh();
|
|
// Recoloca o foco no início do conteúdo para o usuário ver o estado original
|
|
var body = document.querySelector('#aap-app .aap-panel:not([hidden]) .aap-panel-body');
|
|
if (body) body.scrollTo({ top: 0, behavior: 'smooth' });
|
|
});
|
|
|
|
refresh();
|
|
})();
|
|
|
|
// ── Salva configurações antes de executar ────────────────────────────────
|
|
function saveSettings() {
|
|
mirrorToForm();
|
|
if (!$form.length) return $.Deferred().resolve().promise();
|
|
return $.ajax({
|
|
url: aapData.options_url,
|
|
type: 'POST',
|
|
data: $form.serialize(),
|
|
});
|
|
}
|
|
|
|
function feedback($el, text, type) {
|
|
$el.removeClass('is-error is-success is-warn is-pending').addClass(type ? 'is-' + type : '').html(text);
|
|
}
|
|
|
|
// ── Links de categoria (âncoras) ────────────────────────────────────────
|
|
$doc.on('click', '#aap-app .aap-cat-links a', function (e) {
|
|
var $card = $($(this).attr('href'));
|
|
if (!$card.length) return;
|
|
|
|
e.preventDefault();
|
|
|
|
// Rola até o card
|
|
// No layout de aplicação o conteúdo rola dentro do painel; fora dele,
|
|
// a página inteira é que rola.
|
|
var body = document.querySelector('#aap-app .aap-panel:not([hidden]) .aap-panel-body');
|
|
if (body && body.scrollHeight > body.clientHeight + 2) {
|
|
var delta = $card[0].getBoundingClientRect().top - body.getBoundingClientRect().top;
|
|
body.scrollTo({ top: Math.max(0, body.scrollTop + delta - 16), behavior: 'smooth' });
|
|
} else {
|
|
var top = $card[0].getBoundingClientRect().top + window.pageYOffset - aapHeadHeight() - 16;
|
|
window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' });
|
|
}
|
|
|
|
$('#aap-app .aap-cat-links a').removeClass('is-active');
|
|
$(this).addClass('is-active');
|
|
|
|
$card.addClass('aap-flash');
|
|
setTimeout(function () { $card.removeClass('aap-flash'); }, 1400);
|
|
});
|
|
|
|
// ── Destaque automático do botão da seção visível (scroll spy) ──────────
|
|
(function catScrollSpy() {
|
|
var links = Array.prototype.slice.call(
|
|
document.querySelectorAll('#aap-app .aap-cat-links a')
|
|
);
|
|
if (!links.length) return;
|
|
|
|
var body = document.querySelector('#aap-app .aap-panel:not([hidden]) .aap-panel-body');
|
|
var head = document.querySelector('#aap-app .aap-sticky-head');
|
|
|
|
function update() {
|
|
// Linha de referência: logo abaixo do cabeçalho visível
|
|
var threshold = (head ? head.getBoundingClientRect().bottom : 0) + 24;
|
|
var current = links[0];
|
|
|
|
links.forEach(function (link) {
|
|
var card = document.getElementById(link.getAttribute('data-cat'));
|
|
if (!card) return;
|
|
if (card.getBoundingClientRect().top <= threshold) current = link;
|
|
});
|
|
|
|
// A última seção costuma ser curta e nunca chega ao topo:
|
|
// no fim da rolagem ela é a ativa.
|
|
var atBottom;
|
|
if (body && body.scrollHeight > body.clientHeight + 2) {
|
|
atBottom = body.scrollTop + body.clientHeight >= body.scrollHeight - 2;
|
|
} else {
|
|
atBottom = window.innerHeight + window.pageYOffset >=
|
|
document.documentElement.scrollHeight - 2;
|
|
}
|
|
if (atBottom) current = links[links.length - 1];
|
|
|
|
links.forEach(function (link) {
|
|
link.classList.toggle('is-active', link === current);
|
|
});
|
|
}
|
|
|
|
// Evita recalcular a cada pixel: no máximo um cálculo por quadro
|
|
var ticking = false;
|
|
function onScroll() {
|
|
if (ticking) return;
|
|
ticking = true;
|
|
requestAnimationFrame(function () {
|
|
ticking = false;
|
|
update();
|
|
});
|
|
}
|
|
|
|
// Rola dentro do painel (desktop) e na janela (mobile)
|
|
if (body) body.addEventListener('scroll', onScroll, { passive: true });
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
window.addEventListener('resize', onScroll, { passive: true });
|
|
update();
|
|
})();
|
|
|
|
// ── Cabeçalho fixo de cada aba ─────────────────────────────────────────
|
|
function adminBarHeight() {
|
|
var bar = document.getElementById('wpadminbar');
|
|
if (!bar) return 0;
|
|
var r = bar.getBoundingClientRect();
|
|
return r.bottom > 0 ? Math.round(r.height) : 0; // 32px (desktop) / 46px (mobile)
|
|
}
|
|
|
|
// Altura total ocupada no topo = barra do WordPress (quando visível) + header
|
|
function aapHeadHeight() {
|
|
var head = document.querySelector('#aap-app .aap-sticky-head');
|
|
return head ? Math.round(head.getBoundingClientRect().height) + adminBarHeight() : 0;
|
|
}
|
|
|
|
(function stickyHead() {
|
|
var head = document.querySelector('#aap-app .aap-sticky-head');
|
|
var side = document.querySelector('#aap-app .aap-sidebar-inner');
|
|
if (!head && !side) return;
|
|
|
|
function update() {
|
|
var barH = adminBarHeight();
|
|
var foot = document.getElementById('wpfooter');
|
|
var footH = (foot && getComputedStyle(foot).display !== 'none')
|
|
? Math.round(foot.getBoundingClientRect().height) : 0;
|
|
|
|
// Altura do rodapé usada no layout do painel (desktop)
|
|
document.documentElement.style.setProperty('--aap-footer-h', footH + 'px');
|
|
|
|
// Cola exatamente abaixo da barra do WordPress; se ela sair da tela, cola no topo
|
|
if (head) {
|
|
head.style.top = barH + 'px';
|
|
head.classList.toggle('is-stuck', window.pageYOffset > 0);
|
|
document.documentElement.style.setProperty('--aap-head-h', aapHeadHeight() + 'px');
|
|
}
|
|
// A sidebar fica fixa: sai do fluxo da página e não é afetada pelo fim
|
|
// da coluna (era isso que fazia os itens "subirem" no fim da rolagem).
|
|
if (side) {
|
|
if (window.matchMedia('(max-width: 782px)').matches) {
|
|
// No mobile ela é empilhada no topo, sem posicionamento fixo
|
|
clearSide();
|
|
} else {
|
|
var colRect = side.parentElement.getBoundingClientRect();
|
|
|
|
// No topo da página o app começa abaixo da barra (e de avisos);
|
|
// ao rolar ele encosta na barra e ali permanece.
|
|
var top = Math.max(barH, Math.round(colRect.top));
|
|
var h = Math.max(200, Math.round(window.innerHeight - top - footH));
|
|
|
|
setSide('position', 'fixed');
|
|
setSide('left', Math.round(colRect.left) + 'px');
|
|
setSide('width', Math.round(colRect.width) + 'px');
|
|
setSide('top', top + 'px');
|
|
setSide('height', h + 'px');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Escreve no estilo só quando o valor muda, evitando recálculos por quadro
|
|
var last = {};
|
|
function setSide(prop, value) {
|
|
if (last[prop] === value) return;
|
|
last[prop] = value;
|
|
side.style[prop] = value;
|
|
}
|
|
|
|
function clearSide() {
|
|
['position', 'left', 'width', 'top', 'height'].forEach(function (p) {
|
|
if (last[p] !== '') { last[p] = ''; side.style[p] = ''; }
|
|
});
|
|
}
|
|
|
|
update();
|
|
window.addEventListener('scroll', update, { passive: true });
|
|
window.addEventListener('resize', update, { passive: true });
|
|
})();
|
|
|
|
// ── Preferências de log (compartilhadas entre configurações e aba log) ──
|
|
function pref(key, value) {
|
|
try {
|
|
if (value === undefined) return localStorage.getItem('aap_' + key) !== '0';
|
|
localStorage.setItem('aap_' + key, value ? '1' : '0');
|
|
} catch (err) {}
|
|
return value;
|
|
}
|
|
|
|
$doc.on('change', '.aap-pref-autoscroll', function () {
|
|
var on = this.checked;
|
|
$('.aap-pref-autoscroll').prop('checked', on);
|
|
pref('autoscroll', on);
|
|
});
|
|
|
|
$doc.on('change', '.aap-pref-live', function () {
|
|
var on = this.checked;
|
|
$('.aap-pref-live').prop('checked', on);
|
|
pref('live', on);
|
|
if (on) startLogPolling(); else stopLogPolling();
|
|
});
|
|
|
|
// ── Mensagem de "configurações salvas" ──────────────────────────────────
|
|
if (/[?&](aap-saved=1|settings-updated=true)/.test(window.location.search)) {
|
|
var $toast = $('#aap-save-toast');
|
|
var $saveFb = $('#aap-save-feedback');
|
|
if ($toast.length && $saveFb.length) {
|
|
$saveFb.text(s.settings_saved);
|
|
$toast.prop('hidden', false);
|
|
setTimeout(function () { $toast.prop('hidden', true); }, 5000);
|
|
// Limpa a marca da URL para não repetir o aviso ao recarregar
|
|
if (window.history && window.history.replaceState) {
|
|
var clean = window.location.href
|
|
.replace(/([?&])aap-saved=1&?/, '$1')
|
|
.replace(/([?&])settings-updated=true&?/, '$1')
|
|
.replace(/[?&]$/, '');
|
|
window.history.replaceState({}, '', clean);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Estado de execução ──────────────────────────────────────────────────
|
|
var $execSection = $('#rap-exec-section');
|
|
var $execText = $('#rap-exec-text');
|
|
var $execSub = $('#rap-exec-sub');
|
|
var $progress = $('#rap-progress');
|
|
var $runStatus = $('#rap-run-status');
|
|
var $abortBtn = $('#rap-abort-btn');
|
|
var $abortStatus = $('#rap-abort-status');
|
|
var $lastRun = $('#rap-last-run');
|
|
|
|
function setBadge(processing, abortRequested) {
|
|
if ($execSection.length) {
|
|
$execSection.removeClass('is-idle is-aborting');
|
|
if (processing) {
|
|
$execSection.toggleClass('is-aborting', !!abortRequested);
|
|
$progress.toggleClass('is-indeterminate', true);
|
|
$execText.text(abortRequested ? s.aborting : s.running);
|
|
if (aapData.started_label) $execSub.text(aapData.started_label);
|
|
} else {
|
|
$execSection.addClass('is-idle');
|
|
$progress.removeClass('is-indeterminate');
|
|
$execText.text(s.no_execution);
|
|
$execSub.text(idleDesc);
|
|
}
|
|
if (processing) {
|
|
$abortBtn.prop('disabled', false).show();
|
|
} else {
|
|
$abortBtn.hide();
|
|
$abortStatus.text('');
|
|
if (($runStatus.text() || '') === s.processing_feeds) $runStatus.text('');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Subtítulo do estado ocioso (vem pronto do servidor)
|
|
var idleDesc = aapData.idle_desc || $execSub.text();
|
|
|
|
// ── Botão "Executar agora" ──────────────────────────────────────────────
|
|
var $runBtn = $('#rap-run-now');
|
|
|
|
function setRunBtn(running) {
|
|
$runBtn.prop('disabled', running);
|
|
var label = running ? s.running : s.run_now;
|
|
var $lbl = $runBtn.find('span');
|
|
if ($lbl.length) $lbl.text(label); else $runBtn.text(label);
|
|
$runBtn.toggleClass('is-busy', running);
|
|
}
|
|
|
|
function showRunError(msg) {
|
|
feedback($runStatus, s.error_prefix + ' ' + msg, 'error');
|
|
}
|
|
|
|
// ── Polling permanente — o servidor é a fonte da verdade ────────────────
|
|
var serverProcessing = aapData.processing;
|
|
var consecutiveErrors = 0;
|
|
var wasProcessing = serverProcessing;
|
|
|
|
setBadge(serverProcessing, false);
|
|
setRunBtn(serverProcessing);
|
|
|
|
function handleAjaxError(xhr) {
|
|
if (xhr.status === 403 || xhr.status === 400) {
|
|
window.location.reload();
|
|
}
|
|
consecutiveErrors++;
|
|
}
|
|
|
|
function pollStatus() {
|
|
$.post(ajaxurl, { action: 'aap_check_status', nonce: aapData.nonce_status })
|
|
.done(function (res) {
|
|
consecutiveErrors = 0;
|
|
if (!res.success) return;
|
|
var was = serverProcessing;
|
|
serverProcessing = res.data.processing;
|
|
setBadge(serverProcessing, res.data.abort_requested);
|
|
setRunBtn(serverProcessing);
|
|
|
|
if (res.data.last_run && $lastRun.length) {
|
|
$lastRun.html(s.last_run_prefix + ' <strong>' + $('<span>').text(res.data.last_run).html() + '</strong>');
|
|
}
|
|
|
|
// Execução terminou → recarrega a lista de últimos posts uma única vez
|
|
if (was && !serverProcessing && aapData.tab === 'execucoes') {
|
|
setTimeout(function () {
|
|
if (window.location.hash !== '#no-reload') window.location.reload();
|
|
}, 1500);
|
|
}
|
|
})
|
|
.fail(handleAjaxError);
|
|
}
|
|
|
|
setInterval(pollStatus, 3000);
|
|
|
|
// ── Iniciar pipeline (retorna imediatamente — roda em processo separado) ─
|
|
$runBtn.on('click', function () {
|
|
setRunBtn(true);
|
|
setBadge(true, false);
|
|
feedback($runStatus, s.preparing, 'pending');
|
|
$abortStatus.text('');
|
|
|
|
saveSettings().always(function () {
|
|
$.post(ajaxurl, { action: 'aap_start_pipeline', nonce: aapData.nonce_start })
|
|
.done(function (res) {
|
|
if (!res.success) {
|
|
showRunError(res.data || s.error_start);
|
|
setRunBtn(false);
|
|
setBadge(false, false);
|
|
} else {
|
|
feedback($runStatus, s.processing_feeds, 'pending');
|
|
}
|
|
})
|
|
.fail(function (xhr) {
|
|
var detail = xhr.responseText ? xhr.responseText.substring(0, 300) : s.comm_error;
|
|
showRunError('HTTP ' + xhr.status + ' — ' + detail);
|
|
setRunBtn(false);
|
|
setBadge(false, false);
|
|
});
|
|
});
|
|
});
|
|
|
|
// ── Abortar — encerra o processo imediatamente via PID ──────────────────
|
|
function doAbort() {
|
|
$abortBtn.prop('disabled', true);
|
|
feedback($abortStatus, s.aborting, 'pending');
|
|
setBadge(true, true);
|
|
|
|
$.post(ajaxurl, { action: 'aap_abort_execution', nonce: aapData.nonce_abort })
|
|
.done(function (res) {
|
|
if (res.success) {
|
|
setBadge(false, false);
|
|
setRunBtn(false);
|
|
$abortStatus.text('');
|
|
feedback($runStatus, s.aborted, 'warn');
|
|
} else {
|
|
feedback($abortStatus, res.data || s.error_abort, 'error');
|
|
$abortBtn.prop('disabled', false);
|
|
setBadge(serverProcessing, false);
|
|
}
|
|
})
|
|
.fail(function () {
|
|
feedback($abortStatus, s.comm_error, 'error');
|
|
$abortBtn.prop('disabled', false);
|
|
});
|
|
}
|
|
|
|
$abortBtn.on('click', doAbort);
|
|
|
|
// ── Geração automática ──────────────────────────────────────────────────
|
|
var $toggleBtn = $('#rap-toggle-auto');
|
|
var $autoBadge = $('#rap-auto-badge');
|
|
var $autoBadgeDot = $('#rap-auto-badge-dot');
|
|
var $autoBadgeText = $('#rap-auto-badge-text');
|
|
var $nextRunEl = $('#rap-next-run');
|
|
var $autoModeWrap = $('#rap-auto-mode-wrap');
|
|
var $modeSchedule = $('#rap-mode-schedule');
|
|
var $scheduleWrap = $('#rap-schedule-wrap');
|
|
var $scheduledAt = $('#rap-scheduled-at');
|
|
var $autoStatus = $('#rap-auto-status');
|
|
var $interval = $('#rap-auto-interval');
|
|
var $unit = $('#rap-auto-unit');
|
|
var autoEnabled = aapData.auto_enabled;
|
|
|
|
// Mostra/esconde o campo de agendamento conforme o rádio selecionado
|
|
$('input[name="aap_auto_mode"]').on('change', function () {
|
|
if ($modeSchedule.is(':checked')) {
|
|
$scheduleWrap.show();
|
|
if (!$scheduledAt.val()) {
|
|
var d = new Date();
|
|
d.setHours(d.getHours() + 1, 0, 0, 0);
|
|
var pad = function (n) { return String(n).padStart(2, '0'); };
|
|
$scheduledAt.val(d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + 'T' + pad(d.getHours()) + ':00');
|
|
}
|
|
} else {
|
|
$scheduleWrap.hide();
|
|
}
|
|
});
|
|
|
|
// Intervalo do automático também é uma opção salva — o valor editável fica na aba
|
|
// Execuções, por isso o formulário de configurações é atualizado antes de qualquer save.
|
|
$interval.add($unit).on('change', mirrorToForm);
|
|
mirrorToForm();
|
|
|
|
function updateAutoUI(enabled, nextRun) {
|
|
autoEnabled = enabled;
|
|
$autoBadgeDot.toggleClass('is-on', !!enabled);
|
|
$autoBadgeText.text(enabled ? s.auto_active : s.auto_inactive);
|
|
$toggleBtn.text(enabled ? s.stop_auto : s.start_auto).toggleClass('aap-btn-outline', !!enabled).toggleClass('aap-btn-primary', !enabled);
|
|
|
|
if (enabled) {
|
|
$autoModeWrap.hide();
|
|
if (nextRun) $nextRunEl.html(s.next_run + ' <strong>' + nextRun + '</strong>').show();
|
|
} else {
|
|
$autoModeWrap.show();
|
|
$nextRunEl.hide();
|
|
}
|
|
}
|
|
|
|
if (aapData.auto_enabled) updateAutoUI(true, aapData.next_run);
|
|
|
|
$toggleBtn.on('click', function () {
|
|
var $btn = $(this).prop('disabled', true);
|
|
var enable = !autoEnabled;
|
|
|
|
if (!enable) {
|
|
// Parando o automático — salva configs antes
|
|
feedback($autoStatus, '<em>' + s.wait + '</em>', 'pending');
|
|
saveSettings().always(function () {
|
|
$.post(ajaxurl, { action: 'aap_toggle_auto', nonce: aapData.nonce_toggle_auto, enable: 0 })
|
|
.done(function (res) {
|
|
if (res.success) {
|
|
updateAutoUI(false, '');
|
|
feedback($autoStatus, $('<span>').text(s.auto_paused).html(), 'pending');
|
|
} else {
|
|
feedback($autoStatus, s.error_prefix + ' ' + (res.data || s.error_start), 'error');
|
|
}
|
|
})
|
|
.fail(function () { feedback($autoStatus, s.comm_error, 'error'); })
|
|
.always(function () { $btn.prop('disabled', false); });
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Iniciando o automático — valida o agendamento se necessário
|
|
var mode = $modeSchedule.is(':checked') ? 'schedule' : 'now';
|
|
var scheduledAt = 0;
|
|
|
|
if (mode === 'schedule') {
|
|
var val = $scheduledAt.val();
|
|
if (!val) {
|
|
feedback($autoStatus, s.schedule_required, 'error');
|
|
$btn.prop('disabled', false);
|
|
return;
|
|
}
|
|
scheduledAt = Math.floor(new Date(val).getTime() / 1000);
|
|
if (scheduledAt <= Math.floor(Date.now() / 1000)) {
|
|
feedback($autoStatus, s.schedule_past, 'error');
|
|
$btn.prop('disabled', false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
feedback($autoStatus, '<em>' + s.wait + '</em>', 'pending');
|
|
|
|
// Salva configs (incluindo o intervalo exibido aqui) antes de iniciar
|
|
saveSettings().always(function () {
|
|
$.post(ajaxurl, {
|
|
action: 'aap_toggle_auto',
|
|
nonce: aapData.nonce_toggle_auto,
|
|
enable: 1,
|
|
mode: mode,
|
|
scheduled_at: scheduledAt,
|
|
interval_value: $interval.val(),
|
|
interval_unit: $unit.val(),
|
|
})
|
|
.done(function (res) {
|
|
if (res.success) {
|
|
updateAutoUI(res.data.enabled, res.data.next_run);
|
|
feedback($autoStatus, res.data.enabled ? s.auto_started : s.auto_paused, res.data.enabled ? 'success' : 'pending');
|
|
} else {
|
|
feedback($autoStatus, s.error_prefix + ' ' + (res.data || s.error_start), 'error');
|
|
}
|
|
})
|
|
.fail(function () { feedback($autoStatus, s.comm_error, 'error'); })
|
|
.always(function () { $btn.prop('disabled', false); });
|
|
});
|
|
});
|
|
|
|
// ── Log (banco de dados, paginado) ─────────────────────────────────────
|
|
var $logBox = $('#rap-log-box');
|
|
var $logStatus = $('#rap-log-status');
|
|
var $logMore = $('#rap-log-more');
|
|
var $logLevel = $('#rap-log-level');
|
|
|
|
var logPage = 1;
|
|
var logFetching = false;
|
|
var logTimer = null;
|
|
var logIds = {};
|
|
|
|
if (pref('live', undefined) === false) {
|
|
$('.aap-pref-live').prop('checked', false);
|
|
}
|
|
|
|
function logRow(row) {
|
|
var cls = row.level === 'error' ? 'erro' : row.level === 'warn' ? 'aviso' : 'info';
|
|
return '<div class="aap-log-line" data-id="' + row.id + '">'
|
|
+ '<span class="aap-log-time">' + $('<span>').text(row.time).html() + '</span>'
|
|
+ '<span class="aap-log-level aap-log-level-' + cls + '">' + String(row.level).toUpperCase() + '</span>'
|
|
+ '<span class="aap-log-msg">' + $('<span>').text(row.message).html() + '</span>'
|
|
+ '</div>';
|
|
}
|
|
|
|
function appendRows(rows, prepend) {
|
|
var html = '';
|
|
|
|
$.each(rows, function (_, row) {
|
|
if (logIds[row.id]) return;
|
|
logIds[row.id] = true;
|
|
html += logRow(row);
|
|
});
|
|
|
|
if (!html) return;
|
|
|
|
if (prepend) {
|
|
$logBox.prepend(html);
|
|
} else {
|
|
$logBox.append(html);
|
|
}
|
|
}
|
|
|
|
function fetchLogs(page, mode) {
|
|
if (logFetching || !$logBox.length) return;
|
|
logFetching = true;
|
|
|
|
$.post(ajaxurl, {
|
|
action: 'aap_fetch_logs',
|
|
nonce: aapData.nonce_logs,
|
|
page: page,
|
|
per_page: aapData.log_per_page || 50,
|
|
level: $logLevel.length ? $logLevel.val() : ''
|
|
})
|
|
.done(function (res) {
|
|
if (!res.success) return;
|
|
|
|
var rows = res.data.rows || [];
|
|
|
|
if (mode === 'reset') {
|
|
$logBox.empty();
|
|
logIds = {};
|
|
}
|
|
|
|
appendRows(rows, mode === 'new');
|
|
|
|
if (!$logBox.children().length) {
|
|
$logBox.html('<span class="aap-log-empty">' + $('<span>').text(s.log_empty).html() + '</span>');
|
|
} else {
|
|
$logBox.find('.aap-log-empty').remove();
|
|
}
|
|
|
|
$logMore.prop('hidden', !res.data.has_more);
|
|
$logStatus.text(s.log_updated + ' ' + new Date().toLocaleTimeString());
|
|
})
|
|
.fail(function () {
|
|
$logStatus.text(s.comm_error);
|
|
})
|
|
.always(function () {
|
|
logFetching = false;
|
|
});
|
|
}
|
|
|
|
function reloadLogs() {
|
|
logPage = 1;
|
|
fetchLogs(1, 'reset');
|
|
}
|
|
|
|
function startLogPolling() {
|
|
if (!$logBox.length) return;
|
|
stopLogPolling();
|
|
reloadLogs();
|
|
if (pref('live', undefined)) {
|
|
logTimer = setInterval(function () { fetchLogs(1, 'new'); }, 4000);
|
|
}
|
|
}
|
|
|
|
function stopLogPolling() {
|
|
clearInterval(logTimer);
|
|
logTimer = null;
|
|
}
|
|
|
|
if ($logMore.length) {
|
|
$logMore.on('click', function () {
|
|
logPage += 1;
|
|
fetchLogs(logPage, 'more');
|
|
});
|
|
}
|
|
|
|
if ($logLevel.length) {
|
|
$logLevel.on('change', reloadLogs);
|
|
}
|
|
|
|
document.addEventListener('visibilitychange', function () {
|
|
if (!$logBox.length) return;
|
|
if (document.hidden) stopLogPolling(); else startLogPolling();
|
|
});
|
|
|
|
if ($logBox.length) startLogPolling();
|
|
});
|