Files
jpd a201d5d7ac Update 34 files
- /nfc/README.md
- /nfc/clean_old_logs.sh
- /nfc/clockin.py
- /nfc/emptycards.py
- /nfc/sync_clocks.sh
- /piservices/Dockerfile
- /piservices/Portainer-lamp-stack.env
- /piservices/Portainer-lamp-stack.yaml
- /piservices/README.md
- /piservices/avahi-alias.service
- /piservices/clockpy.service
- /piservices/emptycards.service
- /piservices/nfc-sync.service
- /piservices/test.sh
- /www/404
- /www/get-table-data.php
- /www/habiwan copy.png
- /www/habiwan.png
- /www/index.php
- /www/log_swipe.php
- /www/manage_data.php
- /www/manifest.json
- /www/names.csv
- /www/network_audit.csv
- /www/network-tracker.html
- /www/nfc_debug.log
- /www/scanner.html
- /www/staff_names.php
- /www/times.csv
- /www/check_update.php
- /www/dev.php
- /www/favicon.png
- /www/JsBarcode.all.min.js
- /www/README.md
2026-07-24 14:37:45 +01:00

355 lines
18 KiB
PHP

<?php
// Define defaults so they are never "undefined"
$rows = [];
$columns = [];
$total_rows = 0;
$total_pages = 0;
$page = 1;
$sort_col = '';
$sort_dir = 'ASC';
// Start a secure session
session_start();
// 1. CHOOSE YOUR DEV PASSWORD HERE:
define('DEV_PASSWORD', '123456');
define('DEV_TIMEOUT_SECONDS', 1800); // 30 min Inactivity threshold
// Handle Explicit Logout
if (isset($_GET['logout'])) {
unset($_SESSION['DEV_PASSWORD_authenticated']);
unset($_SESSION['DEV_last_activity']);
header("Location: " . $_SERVER['PHP_SELF']);
exit;
}
// Handle Login Form Submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['x_secure_token'])) {
if ($_POST['x_secure_token'] === DEV_PASSWORD) {
$_SESSION['DEV_PASSWORD_authenticated'] = true;
$_SESSION['DEV_last_activity'] = time();
header("Location: " . $_SERVER['PHP_SELF']);
exit;
} else {
$login_error = "Incorrect password access denied.";
}
}
// Server-Side Inactivity Check
if (isset($_SESSION['DEV_PASSWORD_authenticated']) && $_SESSION['DEV_PASSWORD_authenticated'] === true) {
if (isset($_SESSION['DEV_last_activity']) && (time() - $_SESSION['DEV_last_activity'] > DEV_TIMEOUT_SECONDS)) {
unset($_SESSION['DEV_PASSWORD_authenticated']);
unset($_SESSION['DEV_last_activity']);
header("Location: " . $_SERVER['PHP_SELF'] . "?reason=timeout");
exit;
}
$_SESSION['DEV_last_activity'] = time();
}
if (isset($_GET['reason']) && $_GET['reason'] === 'timeout') {
unset($_SESSION['DEV_PASSWORD_authenticated']);
unset($_SESSION['DEV_last_activity']);
}
// Display Login Screen if not authenticated
if (!isset($_SESSION['DEV_PASSWORD_authenticated']) || $_SESSION['DEV_PASSWORD_authenticated'] !== true) {
$display_msg = "Developer Access";
if (isset($_GET['reason']) && $_GET['reason'] === 'timeout') {
$login_error = "Logged out due to 30 min of inactivity.";
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Developer Login</title>
<link rel="icon" href="favicon.png" type="image/png">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', sans-serif; }
body { background: linear-gradient(180deg, #1c3344, #102a33, #000000); min-height: 100vh; display: flex; flex-direction: column; align-items: center; }
.login-box { margin: auto; background: rgba(255, 255, 255, 0.95); padding: 40px 30px; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.5); width: 100%; max-width: 380px; text-align: center; }
h2 { margin-bottom: 25px; color: #00796b; font-size: 1.8rem; font-weight: 600; }
input[type="password"] { width: 100%; padding: 15px; margin-bottom: 20px; border: 1px solid #ccc; border-radius: 8px; font-size: 1rem; }
button { width: 100%; padding: 15px; background-color: #00897b; color: white; border: none; border-radius: 8px; font-size: 1.1rem; cursor: pointer; transition: 0.3s; }
button:hover { background-color: #00695c; }
.error { background: #ffebee; color: #c62828; padding: 12px; border-radius: 8px; margin-bottom: 20px; border: 1px solid #ffcdd2; }
.footer { text-align: center; padding-bottom: 20px; color: rgba(255, 255, 255, 0.7); font-size: 0.9rem; }
</style>
</head>
<body>
<img src="favicon.png" alt="PiClock">
<div class="login-box">
<h2><?= htmlspecialchars($display_msg) ?></h2>
<?php if (isset($login_error)): ?>
<div class="error"><?= htmlspecialchars($login_error) ?></div>
<?php endif; ?>
<form method="POST" autocomplete="off">
<input type="password" name="x_secure_token" placeholder="Enter Dev Password" required autofocus>
<button type="submit">Secure Log In</button>
</form>
</div>
<div class="footer">
'PiClock' DB Manager <span style="display: inline-block; transform: rotateY(180deg);">&copy;</span> 2026
<br>made with &hearts; by F.Javier "<a href="mailto:habiwan@me.com" style="color: #fff;">habiwan</a>" Puig Diaz
<br><img src="habiwan.png" alt="habiwan">
</div>
</body>
</html>
<?php exit;
}
/** Database Connection **/
$dbHost = 'db'; $dbName = 'PiClock'; $dbUser = 'root'; $dbPass = 'rootpassword';
try {
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
} catch (PDOException $e) { die("Database connection failed: " . $e->getMessage()); }
$message = "";
$current_table = (isset($_GET['table']) && $_GET['table'] === 'times') ? 'times' : 'names';
// Identify PK: 'UID' for times, 'id' for everything else
$pk = ($current_table === 'times') ? 'UID' : 'id';
// --- HANDLE POST ACTIONS ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
if (isset($_POST['backup_all'])) {
$pdo->exec("DROP TABLE IF EXISTS names_backup; CREATE TABLE names_backup AS SELECT * FROM names;");
$pdo->exec("DROP TABLE IF EXISTS times_backup; CREATE TABLE times_backup AS SELECT * FROM times;");
$message = "<div class='msg-success'>Full Database Backup created!</div>";
} elseif (isset($_POST['restore_names'])) {
try {
// Ensure table exists first
$pdo->exec("CREATE TABLE IF NOT EXISTS names (
id INT AUTO_INCREMENT PRIMARY KEY,
CardID VARCHAR(12) NOT NULL UNIQUE,
name VARCHAR(100) DEFAULT NULL
)");
// Now restore
$pdo->exec("TRUNCATE TABLE names; INSERT INTO names SELECT * FROM names_backup;");
$message = "<div class='msg-success'>'names' table restored and re-created!</div>";
} catch (Exception $e) {
$message = "<div class='msg-error'>Error restoring 'names': " . $e->getMessage() . "</div>";
}
} elseif (isset($_POST['restore_times'])) {
try {
// Ensure table exists first
$pdo->exec("CREATE TABLE IF NOT EXISTS times (
UID VARCHAR(12) NOT NULL,
temp FLOAT,
timestamp DATETIME
)");
// Now restore
$pdo->exec("TRUNCATE TABLE times; INSERT INTO times SELECT * FROM times_backup;");
$message = "<div class='msg-success'>'times' table restored and re-created!</div>";
} catch (Exception $e) {
$message = "<div class='msg-error'>Error restoring 'times': " . $e->getMessage() . "</div>";
}
} elseif (isset($_POST['action']) && $_POST['action'] === 'save_changes') {
// ONLY proceed if 'rows' exists and is an array
if (isset($_POST['rows']) && is_array($_POST['rows'])) {
$pdo->beginTransaction();
foreach ($_POST['rows'] as $pk_val => $columns) {
$setClauses = []; $params = [];
foreach ($columns as $col => $val) {
$setClauses[] = "`$col` = ?";
$params[] = trim($val);
}
$params[] = $pk_val;
$stmt = $pdo->prepare("UPDATE `$current_table` SET " . implode(", ", $setClauses) . " WHERE `$pk` = ?");
$stmt->execute($params);
}
$pdo->commit();
$message = "<div class='msg-success'>Changes saved successfully!</div>";
}
} elseif (isset($_POST['action']) && $_POST['action'] === 'delete_selected') {
if (!empty($_POST['selected_ids'])) {
$ids = $_POST['selected_ids'];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("DELETE FROM `$current_table` WHERE `$pk` IN ($placeholders)");
$stmt->execute($ids);
$message = "<div class='msg-success'>Rows deleted successfully!</div>";
}
}
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
$message = "<div class='msg-error'>Error: " . $e->getMessage() . "</div>";
}
}
// --- FETCH DATA ---
$rows = [];
$columns = [];
$total_rows = 0;
$total_pages = 0;
// Only attempt to fetch data if the table actually exists
try {
// Check if table exists by trying to select one row
$pdo->query("SELECT 1 FROM `$current_table` LIMIT 1");
// If we reach here, table exists, proceed with standard queries
$colStmt = $pdo->query("SHOW COLUMNS FROM `$current_table`");
$columns = $colStmt->fetchAll(PDO::FETCH_COLUMN);
$sort_col = (isset($_GET['sort']) && in_array($_GET['sort'], $columns)) ? $_GET['sort'] : $pk;
$sort_dir = (isset($_GET['dir']) && in_array($_GET['dir'], ['ASC', 'DESC'])) ? $_GET['dir'] : 'ASC';
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$limit = 50;
$offset = ($page - 1) * $limit;
$countStmt = $pdo->query("SELECT COUNT(*) FROM `$current_table`");
$total_rows = $countStmt->fetchColumn();
$total_pages = ceil($total_rows / $limit);
$stmt = $pdo->prepare("SELECT * FROM `$current_table` ORDER BY `$sort_col` $sort_dir LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
// Table doesn't exist, leave $rows, $columns, etc., as empty arrays
// The page will render, but the table section will be empty/blank
}
function buildUrl($params = []) { return "?" . http_build_query(array_merge($_GET, $params)); }
?>
<!DOCTYPE html>
<html>
<head>
<title>PiClock DB Management</title>
<link rel="icon" href="favicon.png" type="image/png">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', sans-serif; }
body { background: linear-gradient(180deg, #1c3344, #102a33, #000000); min-height: 100vh; padding: 40px 20px; color: #fff; }
.container { max-width: 1200px; margin: 0 auto; }
.header-container { display: flex; justify-content: space-between; align-items: center; padding-bottom: 20px; border-bottom: 1px solid rgba(255,255,255,0.2); margin-bottom: 20px; }
.header-container h2 { margin: 0; color: #00bfa5; }
.header-buttons { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.btn { padding: 10px 16px; border-radius: 8px; font-weight: 600; font-size: 0.95rem; border: none; cursor: pointer; text-decoration: none; display: inline-block; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(0,0,0,0.2); }
.btn-fs { background-color: #4caf50; color: white; }
.btn-logout { background-color: #e53935; color: white; }
.btn-backup { background-color: #9c27b0; color: white; }
.btn-restore { background-color: #ff9800; color: white; }
.btn:hover { transform: translateY(-2px); filter: brightness(1.1); }
.tabs { display: flex; margin-bottom: 20px; gap: 10px; }
.tabs a { padding: 12px 24px; background: rgba(255,255,255,0.1); color: #fff; text-decoration: none; border-radius: 8px; font-weight: bold; transition: 0.3s; }
.tabs a:hover { background: rgba(255,255,255,0.2); }
.tabs a.active { background: #00897b; color: white; box-shadow: 0 4px 10px rgba(0,137,123,0.4); }
.msg-success { color: #81c784; background: rgba(129, 199, 132, 0.1); border: 1px solid #81c784; padding: 15px; border-radius: 8px; font-weight: bold; margin-bottom: 20px; }
.msg-error { color: #e57373; border: 1px solid #e57373; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
.card-table { background: #fff; border-radius: 12px; color: #333; overflow-x: auto; box-shadow: 0 10px 30px rgba(0,0,0,0.5); margin-bottom: 20px;}
table { border-collapse: collapse; width: 100%; min-width: 800px; }
th { background-color: #00695c; color: white; padding: 16px; text-align: left; }
td { padding: 8px 12px; border-bottom: 1px solid #e0e0e0; vertical-align: middle; }
tr:hover { background-color: #f5f5f5; }
th a { color: #ffffff; text-decoration: none; display: block; }
input[type="text"] { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 6px; font-family: monospace; }
.bottom-actions { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; flex-wrap: wrap; gap: 15px; }
.btn-delete { background-color: #d32f2f; color: white; padding: 12px 20px; font-size: 1.1rem;}
.btn-save { background-color: #0288d1; color: white; padding: 12px 40px; font-size: 1.1rem; }
.pagination { display: flex; gap: 10px; align-items: center; background: rgba(255,255,255,0.1); padding: 10px 20px; border-radius: 8px; }
.pagination a { color: #4fc3f7; text-decoration: none; font-weight: bold; }
.footer { text-align: center; margin-top: 40px; color: rgba(255, 255, 255, 0.7); font-size: 0.9rem; }
</style>
</head>
<body>
<div class="container">
<div class="header-container">
<h2>🛠️ Developer DB Management</h2>
<div class="header-buttons">
<form method="POST" style="display:inline;" onsubmit="return confirm('Backup ALL tables now? This overwrites existing backups.');">
<button type="submit" name="backup_all" class="btn btn-backup">Backup All</button></form>
<form method="POST" style="display:inline;" onsubmit="return confirm('WARNING: This drops current NAMES and restores from backup!');">
<button type="submit" name="restore_names" class="btn btn-restore">Restore Names</button></form>
<form method="POST" style="display:inline;" onsubmit="return confirm('WARNING: This drops current TIMES and restores from backup!');">
<button type="submit" name="restore_times" class="btn btn-restore">Restore Times</button></form>
<a href="?logout=1" class="btn btn-logout">Log Out</a>
</div>
</div>
<?= $message ?>
<div class="tabs">
<a href="?table=names" class="<?= $current_table === 'names' ? 'active' : '' ?>">Names Table</a>
<a href="?table=times" class="<?= $current_table === 'times' ? 'active' : '' ?>">Times Table</a>
</div>
<form method="POST" id="dbForm">
<input type="hidden" name="action" id="formAction" value="save_changes">
<div class="card-table">
<table>
<tr>
<th style="width: 5%; text-align: center;"><input type="checkbox" id="selectAll" onclick="toggleSelectAll()"></th>
<?php if (!empty($columns)): ?>
<?php foreach ($columns as $col): ?>
<th>
<?php
$next_dir = ($sort_col === $col && $sort_dir === 'ASC') ? 'DESC' : 'ASC';
$sortUrl = buildUrl(['sort' => $col, 'dir' => $next_dir]);
?>
<a href="<?= htmlspecialchars($sortUrl) ?>"><?= htmlspecialchars($col) ?> <?= ($sort_col === $col) ? ($sort_dir === 'ASC' ? '↑' : '↓') : '' ?></a>
</th>
<?php endforeach; ?>
<?php else: ?>
<th>No columns found (Table empty)</th>
<?php endif; ?>
</tr>
<?php foreach ($rows as $row): ?>
<tr>
<td style="text-align: center;"><input type="checkbox" name="selected_ids[]" class="rowCheckbox" value="<?= htmlspecialchars($row[$pk]) ?>"></td>
<?php foreach ($columns as $col): ?>
<td>
<?php if ($col === $pk): ?> <strong><?= htmlspecialchars($row[$col]) ?></strong>
<?php else: ?> <input type="text" name="rows[<?= htmlspecialchars($row[$pk]) ?>][<?= $col ?>]" value="<?= htmlspecialchars($row[$col]) ?>">
<?php endif; ?>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</table>
</div>
<div class="bottom-actions">
<button type="button" class="btn btn-delete" onclick="submitForm('delete_selected')">Delete Selected</button>
<div class="pagination">
<span>Total: <?= $total_rows ?></span> |
<?php if ($page > 1): ?><a href="<?= buildUrl(['page' => $page - 1]) ?>">« Prev</a><?php endif; ?>
<span>Page <?= $page ?></span>
<?php if ($page < $total_pages): ?><a href="<?= buildUrl(['page' => $page + 1]) ?>">Next »</a><?php endif; ?>
</div>
<button type="button" class="btn btn-save" onclick="submitForm('save_changes')">Save All Edits</button>
</div>
</form>
<div class="footer">
'PiClock' DB Manager <span style="display: inline-block; transform: rotateY(180deg);">&copy;</span> 2026
<br>made with &hearts; by F.Javier "<a href="mailto:habiwan@me.com" style="color: #fff;">habiwan</a>" Puig Diaz
<br><img src="habiwan.png" alt="habiwan">
</div>
</div>
<script>
function toggleSelectAll() { const cb = document.getElementById('selectAll'); document.querySelectorAll('.rowCheckbox').forEach(i => i.checked = cb.checked); }
function submitForm(action) { document.getElementById('formAction').value = action; document.getElementById('dbForm').submit(); }
</script>
<script>
// Pass the PHP timeout value into JavaScript to log out after inactivity
const TIMEOUT_DURATION = <?php echo DEV_TIMEOUT_SECONDS; ?> * 1000;
let logoutTimer;
function resetTimer() {
clearTimeout(logoutTimer);
logoutTimer = setTimeout(logoutUser, TIMEOUT_DURATION);
}
function logoutUser() {
window.location.href = "?logout=1";
}
// Attach listeners
window.onload = resetTimer;
window.onmousemove = resetTimer;
window.onmousedown = resetTimer;
window.ontouchstart = resetTimer;
window.onclick = resetTimer;
window.onkeydown = resetTimer;
</script>
</body>
</html>