Ensimmäinen commit

This commit is contained in:
Tontze
2026-06-08 19:44:24 +03:00
commit 8b23166a51
11 changed files with 563 additions and 0 deletions
+177
View File
@@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="fi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Torrent Vahti Ohjauspaneeli</title>
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon.png') }}">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background-color: #f8f9fa; font-family: system-ui, -apple-system, sans-serif; }
.card { border: none; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
.nav-tabs .nav-link { color: #495057; font-weight: 500; }
.nav-tabs .nav-link.active { font-weight: 600; }
#loki-sisalto, #skripti-loki {
background-color: #212529;
color: #f8f9fa;
padding: 15px;
border-radius: 5px;
font-size: 0.85rem;
max-height: 400px;
overflow-y: auto;
white-space: pre-wrap;
}
</style>
</head>
<body>
<nav class="navbar navbar-dark bg-dark mb-4">
<div class="container">
<span class="navbar-brand mb-0 h1">🤖 Torrent Vahti Ohjauspaneeli</span>
<span id="tila-badge" class="badge bg-secondary">Ladataan...</span>
</div>
</nav>
<div class="container mb-5">
<div class="row">
<div class="col-100">
<ul class="nav nav-tabs mb-4" id="paneeliTabs" role="tablist">
<li class="nav-item"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#haut-pane" type="button">📋 Hakujen Hallinta</button></li>
<li class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#historia-pane" type="button">⏳ Lataushistoria</button></li>
<li class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#asetukset-pane" type="button">⚙️ Järjestelmäasetukset</button></li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="haut-pane">
<div class="card p-4 mb-4">
<h5> Lisää uusi haku</h5>
<form id="lisaa-form" class="row g-3">
<div class="col-md-6"><input type="text" id="haku-sana" class="form-control form-control-sm" placeholder="Haku" required></div>
<input type="hidden" id="haku-regex" value=".*">
<div class="col-md-5"><input type="text" id="haku-kansio" class="form-control form-control-sm" placeholder="Kansio" required></div>
<div class="col-md-1"><button type="submit" class="btn btn-success btn-sm w-100">Lisää</button></div>
</form>
</div>
<div class="card p-4">
<h5>Aktiiviset haut</h5>
<table class="table table-hover align-middle"><tbody id="hakukohteet-lista"></tbody></table>
</div>
</div>
<div class="tab-pane fade" id="historia-pane">
<div class="card p-4">
<h5>Ladatut Torrentit</h5>
<div class="d-flex gap-2 mb-3">
<input type="text" id="historia-haku" class="form-control form-control-sm" placeholder="Suodata nimen mukaan..." oninput="paivitaNakyma()">
<button id="tyhjenna-loki-btn" class="btn btn-outline-danger btn-sm text-nowrap">Tyhjennä historia</button>
</div>
<div id="loki-sisalto">Ladataan...</div>
</div>
</div>
<div class="tab-pane fade" id="asetukset-pane">
<div class="card p-4">
<form id="asetukset-form">
<h6 class="fw-bold mb-3 text-primary">📡 Jackett Asetukset</h6>
<div class="mb-3"><input type="url" id="cfg-url" class="form-control form-control-sm" placeholder="Jackett URL"></div>
<div class="mb-4"><input type="text" id="cfg-apikey" class="form-control form-control-sm" placeholder="Jackett API-avain"></div>
<hr class="my-4">
<h6 class="fw-bold mb-3 text-success">🐾 qBittorrent Asetukset</h6>
<div class="mb-2"><input type="url" id="cfg-qbiturl" class="form-control form-control-sm" placeholder="Web UI URL"></div>
<div class="row mb-2">
<div class="col-sm-6"><input type="text" id="cfg-qbituser" class="form-control form-control-sm" placeholder="Käyttäjätunnus"></div>
<div class="col-sm-6"><input type="text" id="cfg-qbitpass" class="form-control form-control-sm" placeholder="Salasana/API"></div>
</div>
<button type="submit" class="btn btn-primary btn-sm mt-3">Tallenna kaikki asetukset</button>
</form>
<hr class="mt-5">
<h6 class="fw-bold mb-3 text-warning">🔍 Jackett Indexerit</h6>
<div class="input-group mb-3" style="max-width: 400px;">
<input type="text" id="uusi-indexer" class="form-control form-control-sm" placeholder="Indexer ID">
<button type="button" onclick="lisaaIndexer()" class="btn btn-outline-secondary btn-sm">Lisää</button>
</div>
<ul id="indexer-lista" class="list-group mb-3" style="max-width: 400px;"></ul>
</div>
</div>
</div>
<div class="card p-4 mt-4 bg-dark text-light">
<div class="d-flex justify-content-between"><h5>⚡ Järjestelmäloki</h5><button id="suorita-haku-btn" class="btn btn-success btn-sm">Suorita haku</button></div>
<pre id="skripti-loki">Ladataan...</pre>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
function rullaaLokiAlas() {
const lokit = ['loki-sisalto', 'skripti-loki'];
lokit.forEach(id => {
const el = document.getElementById(id);
el.scrollTop = el.scrollHeight;
});
}
function paivitaNakyma() {
fetch('/status').then(res => res.json()).then(data => {
document.getElementById('tila-badge').innerText = "Valmis";
document.getElementById('tila-badge').className = "badge bg-success";
const lista = document.getElementById('hakukohteet-lista');
lista.innerHTML = data.hakukohteet.map((k, i) => `<tr><td>${k.jackett_haku}</td><td>${k.kohdekansio}</td><td class="text-end"><button onclick="poistaKohde(${i})" class="btn btn-link text-danger btn-sm p-0">Poista</button></td></tr>`).join('');
const hakuTermi = document.getElementById('historia-haku').value.toLowerCase();
const lokiDiv = document.getElementById('loki-sisalto');
const kaikkiRivit = (data.loki_torrentit || '').split('\n').filter(r => r.trim() !== '');
lokiDiv.innerHTML = kaikkiRivit
.filter(r => r.toLowerCase().includes(hakuTermi))
.map((r) => {
return `<div class="d-flex justify-content-between border-bottom py-1">
<span class="font-monospace small text-truncate" style="max-width: 80%;" title="${r}">${r}</span>
<button onclick="poistaHist('${r.replace(/'/g, "\\'")}')" class="btn btn-outline-danger btn-sm py-0">Poista</button>
</div>`;
}).join('');
const iList = document.getElementById('indexer-lista');
iList.innerHTML = (data.indeksorit || []).map((idx, i) => `<li class="list-group-item d-flex justify-content-between py-1">${idx}<button onclick="poistaIndexer(${i})" class="btn btn-danger btn-sm py-0">Poista</button></li>`).join('');
document.getElementById('skripti-loki').innerText = data.loki_skripti;
document.getElementById('cfg-url').value = data.asetukset.jackett_url || '';
document.getElementById('cfg-apikey').value = data.asetukset.jackett_api_key || '';
document.getElementById('cfg-qbiturl').value = data.asetukset.qbit_url || '';
document.getElementById('cfg-qbituser').value = data.asetukset.qbit_user || '';
document.getElementById('cfg-qbitpass').value = data.asetukset.qbit_pass || '';
rullaaLokiAlas();
});
}
function poistaHist(riviTeksti) {
if(confirm("Poistetaanko tämä rivi historiasta?")) {
fetch('/poista_historiarivi', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ rivi: riviTeksti })
}).then(() => paivitaNakyma());
}
}
function lisaaIndexer() { const val = document.getElementById('uusi-indexer').value; if(!val) return; fetch('/status').then(r => r.json()).then(d => { let l = d.indeksorit || []; l.push(val); fetch('/paivita_indeksorit', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({indeksorit:l})}).then(() => paivitaNakyma()); }); }
function poistaIndexer(i) { fetch('/status').then(r => r.json()).then(d => { let l = d.indeksorit; l.splice(i, 1); fetch('/paivita_indeksorit', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({indeksorit:l})}).then(() => paivitaNakyma()); }); }
function poistaKohde(i) { fetch(`/poista_kohde/${i}`, {method:'DELETE'}).then(() => paivitaNakyma()); }
document.getElementById('lisaa-form').addEventListener('submit', (e) => {
e.preventDefault();
fetch('/lisaa_kohde', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
jackett_haku: document.getElementById('haku-sana').value,
regex_suodatin: document.getElementById('haku-regex').value,
kohdekansio: document.getElementById('haku-kansio').value
})
}).then(() => { e.target.reset(); paivitaNakyma(); });
});
document.getElementById('asetukset-form').addEventListener('submit', (e) => { e.preventDefault(); fetch('/tallenna_asetukset', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({jackett_url:document.getElementById('cfg-url').value, jackett_api_key:document.getElementById('cfg-apikey').value, qbit_url:document.getElementById('cfg-qbiturl').value, qbit_user:document.getElementById('cfg-qbituser').value, qbit_pass:document.getElementById('cfg-qbitpass').value})}).then(() => paivitaNakyma()); });
document.getElementById('suorita-haku-btn').addEventListener('click', () => fetch('/suorita_haku', {method:'POST'}));
document.getElementById('tyhjenna-loki-btn').addEventListener('click', () => fetch('/tyhjenna_loki', {method:'POST'}).then(() => paivitaNakyma()));
paivitaNakyma(); setInterval(paivitaNakyma, 5000);
</script>
</body>
</html>