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