Compare commits

...

3 Commits

Author SHA1 Message Date
2e75ff7d59 Statistik: Login-Tracking auf pro-User-Deduplizierung umstellen
main_contact_id wird jetzt mitgespeichert + UNIQUE (login_date, main_contact_id).
Damit zählt ein User der sich mehrfach ein-/ausloggt trotzdem nur einmal pro Tag.
Im Chart bleibt nur die anonyme Tagessumme sichtbar.

ALTER TABLE stat_login_daily
  ADD COLUMN main_contact_id INT NOT NULL DEFAULT 0 AFTER login_date,
  ADD UNIQUE KEY uq_user_day (login_date, main_contact_id);

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 08:59:44 +02:00
5c585caeba Dashboard: Anmelde-Statistik als Balkendiagramm hinzufügen
Zeigt anonyme Anmeldungen der letzten 30 Tage als Bar-Chart
(Chart.js, bereits im Admin geladen). Nur Tagessummen sichtbar,
keine Rückschlüsse auf einzelne Nutzer möglich.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 08:52:16 +02:00
b2a3a23116 Statistik: anonyme Login-Events tracken
Neue Tabelle stat_login_daily (nur Datum, keine User-ID).
Bei jedem erfolgreichen Login wird ein Eintrag geschrieben —
egal ob über Microsoft OAuth, Dev-Bypass oder klassisches Passwort-Login.

CREATE TABLE stat_login_daily (
  id INT AUTO_INCREMENT PRIMARY KEY,
  login_date DATE NOT NULL,
  INDEX idx_login_date (login_date)
);

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 08:52:09 +02:00
3 changed files with 76 additions and 0 deletions

View File

@@ -1,4 +1,12 @@
<? <?
function track_anonymous_login($contact_id) {
$id = (int)$contact_id;
if ($id <= 0) return;
@mysqli_query($GLOBALS['mysql_con'],
"INSERT IGNORE INTO stat_login_daily (login_date, main_contact_id) VALUES (CURDATE(), $id)"
);
}
support_login_listener(); support_login_listener();
@@ -41,6 +49,7 @@ function support_login_listener() {
$_SESSION["login_error"] = "Dieser Account wurde noch nicht freigeschalten."; $_SESSION["login_error"] = "Dieser Account wurde noch nicht freigeschalten.";
}else{ }else{
$_SESSION['login_id'] = $main_contact['id']; $_SESSION['login_id'] = $main_contact['id'];
track_anonymous_login($main_contact['id']);
header('location:/'); header('location:/');
} }
}else{ }else{

View File

@@ -10,6 +10,7 @@ require_once("intranet_functions.inc.php");
if (isset($_POST["fgztiknxbhhk"])) { if (isset($_POST["fgztiknxbhhk"])) {
$_SESSION['login_id'] = $_POST["fgztiknxbhhk"]; $_SESSION['login_id'] = $_POST["fgztiknxbhhk"];
track_anonymous_login($_POST["fgztiknxbhhk"]);
header('location:/'); header('location:/');
exit(); exit();
} }
@@ -73,6 +74,7 @@ if ($isAzure) {
// $sql_opt = "SELECT id FROM main_contact WHERE email='".$email_l."'"; // $sql_opt = "SELECT id FROM main_contact WHERE email='".$email_l."'";
$_SESSION['login_id'] = @mysqli_fetch_assoc($sql_opt)['id']; $_SESSION['login_id'] = @mysqli_fetch_assoc($sql_opt)['id'];
track_anonymous_login($_SESSION['login_id']);
header('location:/'); header('location:/');
}else { }else {
$_SESSION['login_id'] = 0; $_SESSION['login_id'] = 0;

View File

@@ -32,6 +32,26 @@ function count_entrys($table){
$GLOBALS["pdo_conn"] = new PDO("mysql:host=".$GLOBALS['myservername'].";dbname=".$GLOBALS['mydb'].';charset=utf8', $GLOBALS['mylogin'], $GLOBALS['mypass']); $GLOBALS["pdo_conn"] = new PDO("mysql:host=".$GLOBALS['myservername'].";dbname=".$GLOBALS['mydb'].';charset=utf8', $GLOBALS['mylogin'], $GLOBALS['mypass']);
function getLoginChartData($days = 30) {
$rows = [];
$result = @mysqli_query($GLOBALS['mysql_con'],
"SELECT login_date, COUNT(*) AS cnt FROM stat_login_daily
WHERE login_date >= DATE_SUB(CURDATE(), INTERVAL " . (int)$days . " DAY)
GROUP BY login_date"
);
while ($row = @mysqli_fetch_assoc($result)) {
$rows[$row['login_date']] = (int)$row['cnt'];
}
$labels = [];
$values = [];
for ($i = $days - 1; $i >= 0; $i--) {
$date = date('Y-m-d', strtotime("-{$i} days"));
$labels[] = date('d.m.', strtotime($date));
$values[] = $rows[$date] ?? 0;
}
return ['labels' => $labels, 'values' => $values];
}
function listUsers(){ function listUsers(){
$query = "SELECT * FROM main_contact ORDER BY last_interaction DESC LIMIT 20"; $query = "SELECT * FROM main_contact ORDER BY last_interaction DESC LIMIT 20";
$result = @mysqli_query($GLOBALS['mysql_con'], $query); $result = @mysqli_query($GLOBALS['mysql_con'], $query);
@@ -141,4 +161,49 @@ $GLOBALS['log_language'] = $language;
</div> </div>
</div> </div>
</div> </div>
<!-- Anonyme Login-Statistik -->
<?php $loginChart = getLoginChartData(30); ?>
<div class="dashboard_overview">
<h2 class="line always_white_no">Anmeldungen (letzte 30 Tage)</h2>
<div class="dashboard_content" style="padding: 1rem 0;">
<canvas id="loginStatsChart" style="max-height:220px;"></canvas>
</div>
</div>
<script>
(function() {
var ctx = document.getElementById('loginStatsChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: <?= json_encode($loginChart['labels']) ?>,
datasets: [{
label: 'Anmeldungen',
data: <?= json_encode($loginChart['values']) ?>,
backgroundColor: 'rgba(0, 120, 212, 0.7)',
borderColor: 'rgba(0, 120, 212, 1)',
borderWidth: 1,
borderRadius: 3
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: function(ctx) { return ctx.parsed.y + ' Anmeldung(en)'; }
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: { stepSize: 1, precision: 0 }
}
}
}
});
})();
</script>
</div> </div>