diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..563c194 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -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 diff --git a/api.php b/api.php index fd79212..60e8d36 100644 --- a/api.php +++ b/api.php @@ -21,24 +21,46 @@ $action = $_GET['action'] ?? 'domains'; if ($action === 'domains') { $q = trim($_GET['q'] ?? ''); $tld = trim($_GET['tld'] ?? ''); + $onlyNew = (($_GET['only_new'] ?? '0') === '1'); $runId = $db->querySingle("SELECT value FROM metadata WHERE key='latest_run_id'"); $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"; - if ($q !== '') $sql .= " AND domain LIKE :q"; - if ($tld !== '') $sql .= " AND tld = :tld"; - $sql .= " ORDER BY score DESC, domain ASC LIMIT 500"; + $stPrev = $db->prepare("SELECT run_id FROM domains WHERE run_id != :run GROUP BY run_id ORDER BY MAX(scanned_at) DESC LIMIT 1"); + $stPrev->bindValue(':run', $runId, SQLITE3_TEXT); + $prevRes = $stPrev->execute(); + $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->bindValue(':run', $runId, SQLITE3_TEXT); + $st->bindValue(':prev', $prevRunId, SQLITE3_TEXT); if ($q !== '') $st->bindValue(':q', '%' . $q . '%', SQLITE3_TEXT); if ($tld !== '') $st->bindValue(':tld', $tld, SQLITE3_TEXT); $res = $st->execute(); $rows = []; + $newCount = 0; while ($r = $res->fetchArray(SQLITE3_ASSOC)) { $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']); $rows[] = $r; } @@ -47,8 +69,10 @@ if ($action === 'domains') { 'ok' => true, 'meta' => [ 'runId' => $runId, + 'prevRunId' => $prevRunId, 'scannedAt' => $scannedAt, - 'count' => count($rows) + 'count' => count($rows), + 'newCount' => $newCount ], 'domains' => $rows, ], JSON_UNESCAPED_UNICODE); diff --git a/index.html b/index.html index c193026..467356f 100644 --- a/index.html +++ b/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 input{width:78px;padding:4px 6px} .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: 700px){ body{padding:10px} @@ -42,6 +44,8 @@ + +
@@ -112,13 +116,15 @@ function best2y(regs, tld){ async function loadDomains(){ const q = document.getElementById('q').value.trim(); const tld = document.getElementById('tld').value; + const onlyNew = document.getElementById('onlyNew').checked; const u = new URL('./api.php', location.href); u.searchParams.set('action','domains'); if(q) u.searchParams.set('q', q); if(tld) u.searchParams.set('tld', tld); + if(onlyNew) u.searchParams.set('only_new', '1'); const d = await (await fetch(u)).json(); 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(){ @@ -176,7 +182,7 @@ function renderRows(){ const renew = best(registrars,d.tld,'renew'); const two = best2y(registrars,d.tld); const tr = document.createElement('tr'); - tr.innerHTML = `