add webtracker (also avalibal at stats.search.serverjonas.de
This commit is contained in:
+246
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import json
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Flask, jsonify, render_template_string
|
||||
|
||||
DB_DATA = Path("page_counts.json")
|
||||
INTERVAL = 15 * 60 # 15 Minuten
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
HTML = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>JonasSearch – Pages</title>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background: #111;
|
||||
color: #eee;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.current {
|
||||
font-size: 2.5rem;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background: #1b1b1b;
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>JonasSearch</h1>
|
||||
<div>Indexed Pages</div>
|
||||
|
||||
<div class="current">
|
||||
<span id="current">–</span> Pages
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<canvas id="chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let chart = null;
|
||||
|
||||
async function update() {
|
||||
const response = await fetch("/api/data");
|
||||
const data = await response.json();
|
||||
|
||||
document.getElementById("current").textContent =
|
||||
data.values.length
|
||||
? data.values[data.values.length - 1].count.toLocaleString("de-DE")
|
||||
: "–";
|
||||
|
||||
const labels = data.values.map(x =>
|
||||
new Date(x.timestamp).toLocaleString("de-DE")
|
||||
);
|
||||
|
||||
const values = data.values.map(x => x.count);
|
||||
|
||||
if (chart) {
|
||||
chart.data.labels = labels;
|
||||
chart.data.datasets[0].data = values;
|
||||
chart.update();
|
||||
return;
|
||||
}
|
||||
|
||||
chart = new Chart(document.getElementById("chart"), {
|
||||
type: "line",
|
||||
|
||||
data: {
|
||||
labels: labels,
|
||||
|
||||
datasets: [{
|
||||
label: "Pages",
|
||||
data: values,
|
||||
tension: 0.25,
|
||||
pointRadius: 2,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
|
||||
options: {
|
||||
responsive: true,
|
||||
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
color: "#aaa"
|
||||
}
|
||||
},
|
||||
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
ticks: {
|
||||
color: "#aaa"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: "#eee"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
// Browser alle 30 Sekunden aktualisieren
|
||||
setInterval(update, 30000);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
def load_data():
|
||||
if not DB_DATA.exists():
|
||||
return []
|
||||
|
||||
try:
|
||||
with DB_DATA.open("r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def save_data(data):
|
||||
tmp = DB_DATA.with_suffix(".tmp")
|
||||
|
||||
with tmp.open("w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
tmp.replace(DB_DATA)
|
||||
|
||||
|
||||
def get_page_count():
|
||||
command = [
|
||||
"sudo",
|
||||
"-u",
|
||||
"postgres",
|
||||
"psql",
|
||||
"-p",
|
||||
"25432",
|
||||
"-d",
|
||||
"jonassearch",
|
||||
"-t",
|
||||
"-A",
|
||||
"-c",
|
||||
"SELECT COUNT(*) FROM pages;"
|
||||
]
|
||||
|
||||
result = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
return int(result.stdout.strip())
|
||||
|
||||
|
||||
def collect():
|
||||
while True:
|
||||
try:
|
||||
count = get_page_count()
|
||||
|
||||
entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"count": count
|
||||
}
|
||||
|
||||
data = load_data()
|
||||
data.append(entry)
|
||||
|
||||
save_data(data)
|
||||
|
||||
print(
|
||||
f"[{entry['timestamp']}] "
|
||||
f"Pages: {count}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Abrufen der Seitenzahl: {e}")
|
||||
|
||||
time.sleep(INTERVAL)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template_string(HTML)
|
||||
|
||||
|
||||
@app.route("/api/data")
|
||||
def api_data():
|
||||
return jsonify({
|
||||
"values": load_data()
|
||||
})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Collector im Hintergrund starten
|
||||
thread = threading.Thread(target=collect, daemon=True)
|
||||
thread.start()
|
||||
|
||||
# Webserver
|
||||
app.run(
|
||||
host="0.0.0.0",
|
||||
port=8081,
|
||||
threaded=True
|
||||
)
|
||||
Reference in New Issue
Block a user