feat: add auto-refresh and new-domain filter + deploy workflow
This commit is contained in:
parent
c37ab7581a
commit
c6a4bdfe30
17
.gitea/workflows/deploy.yml
Normal file
17
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Deploy panel to server path
|
||||||
|
run: |
|
||||||
|
chmod +x refresh_and_publish.sh
|
||||||
|
./refresh_and_publish.sh
|
||||||
34
api.php
34
api.php
@ -21,24 +21,46 @@ $action = $_GET['action'] ?? 'domains';
|
|||||||
if ($action === 'domains') {
|
if ($action === 'domains') {
|
||||||
$q = trim($_GET['q'] ?? '');
|
$q = trim($_GET['q'] ?? '');
|
||||||
$tld = trim($_GET['tld'] ?? '');
|
$tld = trim($_GET['tld'] ?? '');
|
||||||
|
$onlyNew = (($_GET['only_new'] ?? '0') === '1');
|
||||||
|
|
||||||
$runId = $db->querySingle("SELECT value FROM metadata WHERE key='latest_run_id'");
|
$runId = $db->querySingle("SELECT value FROM metadata WHERE key='latest_run_id'");
|
||||||
$scannedAt = $db->querySingle("SELECT value FROM metadata WHERE key='latest_scanned_at'");
|
$scannedAt = $db->querySingle("SELECT value FROM metadata WHERE key='latest_scanned_at'");
|
||||||
|
|
||||||
$sql = "SELECT domain, tld, score, status, keywords_json FROM domains WHERE run_id = :run";
|
$stPrev = $db->prepare("SELECT run_id FROM domains WHERE run_id != :run GROUP BY run_id ORDER BY MAX(scanned_at) DESC LIMIT 1");
|
||||||
if ($q !== '') $sql .= " AND domain LIKE :q";
|
$stPrev->bindValue(':run', $runId, SQLITE3_TEXT);
|
||||||
if ($tld !== '') $sql .= " AND tld = :tld";
|
$prevRes = $stPrev->execute();
|
||||||
$sql .= " ORDER BY score DESC, domain ASC LIMIT 500";
|
$prev = $prevRes ? $prevRes->fetchArray(SQLITE3_ASSOC) : null;
|
||||||
|
$prevRunId = $prev['run_id'] ?? '';
|
||||||
|
|
||||||
|
$sql = "SELECT d.domain, d.tld, d.score, d.status, d.keywords_json,
|
||||||
|
CASE
|
||||||
|
WHEN :prev = '' THEN 1
|
||||||
|
WHEN EXISTS (SELECT 1 FROM domains p WHERE p.run_id = :prev AND p.domain = d.domain) THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END AS is_new
|
||||||
|
FROM domains d WHERE d.run_id = :run";
|
||||||
|
|
||||||
|
if ($q !== '') $sql .= " AND d.domain LIKE :q";
|
||||||
|
if ($tld !== '') $sql .= " AND d.tld = :tld";
|
||||||
|
if ($onlyNew) {
|
||||||
|
$sql .= " AND (CASE WHEN :prev = '' THEN 1 WHEN EXISTS (SELECT 1 FROM domains p WHERE p.run_id = :prev AND p.domain = d.domain) THEN 0 ELSE 1 END) = 1";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY d.score DESC, d.domain ASC LIMIT 500";
|
||||||
|
|
||||||
$st = $db->prepare($sql);
|
$st = $db->prepare($sql);
|
||||||
$st->bindValue(':run', $runId, SQLITE3_TEXT);
|
$st->bindValue(':run', $runId, SQLITE3_TEXT);
|
||||||
|
$st->bindValue(':prev', $prevRunId, SQLITE3_TEXT);
|
||||||
if ($q !== '') $st->bindValue(':q', '%' . $q . '%', SQLITE3_TEXT);
|
if ($q !== '') $st->bindValue(':q', '%' . $q . '%', SQLITE3_TEXT);
|
||||||
if ($tld !== '') $st->bindValue(':tld', $tld, SQLITE3_TEXT);
|
if ($tld !== '') $st->bindValue(':tld', $tld, SQLITE3_TEXT);
|
||||||
|
|
||||||
$res = $st->execute();
|
$res = $st->execute();
|
||||||
$rows = [];
|
$rows = [];
|
||||||
|
$newCount = 0;
|
||||||
while ($r = $res->fetchArray(SQLITE3_ASSOC)) {
|
while ($r = $res->fetchArray(SQLITE3_ASSOC)) {
|
||||||
$r['keywords'] = json_decode($r['keywords_json'] ?: '[]', true) ?: [];
|
$r['keywords'] = json_decode($r['keywords_json'] ?: '[]', true) ?: [];
|
||||||
|
$r['is_new'] = intval($r['is_new']) === 1;
|
||||||
|
if ($r['is_new']) $newCount++;
|
||||||
unset($r['keywords_json']);
|
unset($r['keywords_json']);
|
||||||
$rows[] = $r;
|
$rows[] = $r;
|
||||||
}
|
}
|
||||||
@ -47,8 +69,10 @@ if ($action === 'domains') {
|
|||||||
'ok' => true,
|
'ok' => true,
|
||||||
'meta' => [
|
'meta' => [
|
||||||
'runId' => $runId,
|
'runId' => $runId,
|
||||||
|
'prevRunId' => $prevRunId,
|
||||||
'scannedAt' => $scannedAt,
|
'scannedAt' => $scannedAt,
|
||||||
'count' => count($rows)
|
'count' => count($rows),
|
||||||
|
'newCount' => $newCount
|
||||||
],
|
],
|
||||||
'domains' => $rows,
|
'domains' => $rows,
|
||||||
], JSON_UNESCAPED_UNICODE);
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
|||||||
29
index.html
29
index.html
@ -20,6 +20,8 @@
|
|||||||
.pill{display:inline-flex;align-items:center;gap:6px;background:#1f2b56;border:1px solid #3a4a87;border-radius:999px;padding:4px 8px;margin:4px 6px 4px 0;font-size:12px}
|
.pill{display:inline-flex;align-items:center;gap:6px;background:#1f2b56;border:1px solid #3a4a87;border-radius:999px;padding:4px 8px;margin:4px 6px 4px 0;font-size:12px}
|
||||||
.pill input{width:78px;padding:4px 6px}
|
.pill input{width:78px;padding:4px 6px}
|
||||||
.ok{color:#63db8f}.bad{color:#ff8b8b}
|
.ok{color:#63db8f}.bad{color:#ff8b8b}
|
||||||
|
.chip{display:inline-block;padding:2px 8px;border-radius:999px;font-size:11px;background:#264a32;color:#9df5bc;border:1px solid #3f8b5b}
|
||||||
|
.small{font-size:12px}
|
||||||
@media (max-width: 980px){.grid{grid-template-columns:1fr}}
|
@media (max-width: 980px){.grid{grid-template-columns:1fr}}
|
||||||
@media (max-width: 700px){
|
@media (max-width: 700px){
|
||||||
body{padding:10px}
|
body{padding:10px}
|
||||||
@ -42,6 +44,8 @@
|
|||||||
<label>TLD
|
<label>TLD
|
||||||
<select id="tld"><option value="">Wszystkie</option><option>pl</option><option>com</option><option>ai</option></select>
|
<select id="tld"><option value="">Wszystkie</option><option>pl</option><option>com</option><option>ai</option></select>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="small"><input type="checkbox" id="onlyNew" /> Tylko nowe domeny</label>
|
||||||
|
<label class="small"><input type="checkbox" id="autoRefresh" checked /> Auto-refresh 60s</label>
|
||||||
<button id="reload">Odśwież</button>
|
<button id="reload">Odśwież</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="muted" id="meta"></p>
|
<p class="muted" id="meta"></p>
|
||||||
@ -112,13 +116,15 @@ function best2y(regs, tld){
|
|||||||
async function loadDomains(){
|
async function loadDomains(){
|
||||||
const q = document.getElementById('q').value.trim();
|
const q = document.getElementById('q').value.trim();
|
||||||
const tld = document.getElementById('tld').value;
|
const tld = document.getElementById('tld').value;
|
||||||
|
const onlyNew = document.getElementById('onlyNew').checked;
|
||||||
const u = new URL('./api.php', location.href);
|
const u = new URL('./api.php', location.href);
|
||||||
u.searchParams.set('action','domains');
|
u.searchParams.set('action','domains');
|
||||||
if(q) u.searchParams.set('q', q);
|
if(q) u.searchParams.set('q', q);
|
||||||
if(tld) u.searchParams.set('tld', tld);
|
if(tld) u.searchParams.set('tld', tld);
|
||||||
|
if(onlyNew) u.searchParams.set('only_new', '1');
|
||||||
const d = await (await fetch(u)).json();
|
const d = await (await fetch(u)).json();
|
||||||
domains = d.domains || [];
|
domains = d.domains || [];
|
||||||
document.getElementById('meta').textContent = `runId: ${d.meta?.runId||'—'} | scannedAt: ${d.meta?.scannedAt||'—'} | widoczne: ${domains.length}`;
|
document.getElementById('meta').textContent = `runId: ${d.meta?.runId||'—'} | prev: ${d.meta?.prevRunId||'—'} | scannedAt: ${d.meta?.scannedAt||'—'} | nowe: ${d.meta?.newCount||0} | widoczne: ${domains.length}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPrices(){
|
async function loadPrices(){
|
||||||
@ -176,7 +182,7 @@ function renderRows(){
|
|||||||
const renew = best(registrars,d.tld,'renew');
|
const renew = best(registrars,d.tld,'renew');
|
||||||
const two = best2y(registrars,d.tld);
|
const two = best2y(registrars,d.tld);
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
tr.innerHTML = `<td><b>${d.domain}</b></td><td>${d.score}</td>
|
tr.innerHTML = `<td><b>${d.domain}</b> ${d.is_new ? `<span class='chip'>NEW</span>` : ''}</td><td>${d.score}</td>
|
||||||
<td>${buy?`<a href='${buy.url}' target='_blank'>${buy.name}</a> (${money(buy.value)})`:'—'}</td>
|
<td>${buy?`<a href='${buy.url}' target='_blank'>${buy.name}</a> (${money(buy.value)})`:'—'}</td>
|
||||||
<td>${renew?`<a href='${renew.url}' target='_blank'>${renew.name}</a> (${money(renew.value)})`:'—'}</td>
|
<td>${renew?`<a href='${renew.url}' target='_blank'>${renew.name}</a> (${money(renew.value)})`:'—'}</td>
|
||||||
<td>${two?`<a href='${two.url}' target='_blank'>${two.name}</a> (${money(two.value)})`:'—'}</td>`;
|
<td>${two?`<a href='${two.url}' target='_blank'>${two.name}</a> (${money(two.value)})`:'—'}</td>`;
|
||||||
@ -192,13 +198,32 @@ async function refreshAll(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
let qTimer;
|
let qTimer;
|
||||||
|
let autoTimer;
|
||||||
|
|
||||||
|
function setupAutoRefresh(){
|
||||||
|
if(autoTimer) clearInterval(autoTimer);
|
||||||
|
const enabled = document.getElementById('autoRefresh').checked;
|
||||||
|
if(enabled){
|
||||||
|
autoTimer = setInterval(async()=>{
|
||||||
|
await loadDomains();
|
||||||
|
await loadPrices();
|
||||||
|
renderPricing();
|
||||||
|
renderRows();
|
||||||
|
}, 60000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('reload').addEventListener('click', refreshAll);
|
document.getElementById('reload').addEventListener('click', refreshAll);
|
||||||
document.getElementById('q').addEventListener('input', async()=>{
|
document.getElementById('q').addEventListener('input', async()=>{
|
||||||
clearTimeout(qTimer);
|
clearTimeout(qTimer);
|
||||||
qTimer = setTimeout(async()=>{ await loadDomains(); renderRows(); }, 250);
|
qTimer = setTimeout(async()=>{ await loadDomains(); renderRows(); }, 250);
|
||||||
});
|
});
|
||||||
document.getElementById('tld').addEventListener('change', async()=>{await loadDomains(); renderRows();});
|
document.getElementById('tld').addEventListener('change', async()=>{await loadDomains(); renderRows();});
|
||||||
|
document.getElementById('onlyNew').addEventListener('change', async()=>{await loadDomains(); renderRows();});
|
||||||
|
document.getElementById('autoRefresh').addEventListener('change', setupAutoRefresh);
|
||||||
|
|
||||||
refreshAll();
|
refreshAll();
|
||||||
|
setupAutoRefresh();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user