- /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
307 lines
13 KiB
PHP
307 lines
13 KiB
PHP
<?php
|
|
// Start a secure session
|
|
session_start();
|
|
|
|
// 1. CHOOSE YOUR MANAGEMENT PASSWORD HERE:
|
|
define('MANAGEMENT_PASSWORD', '1234');
|
|
define('MANAGEMENT_TIMEOUT_SECONDS', 1200); // 20 min Inactivity threshold
|
|
|
|
// Handle Explicit Logout
|
|
if (isset($_GET['logout'])) {
|
|
unset($_SESSION['MANAGEMENT_PASSWORD_authenticated']);
|
|
unset($_SESSION['MANAGEMENT_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'] === MANAGEMENT_PASSWORD) {
|
|
$_SESSION['MANAGEMENT_PASSWORD_authenticated'] = true;
|
|
$_SESSION['MANAGEMENT_last_activity'] = time();
|
|
header("Location: " . $_SERVER['PHP_SELF']);
|
|
exit;
|
|
} else {
|
|
$login_error = "Incorrect password access denied.";
|
|
}
|
|
}
|
|
|
|
// Server-Side Inactivity Check
|
|
if (isset($_SESSION['MANAGEMENT_PASSWORD_authenticated']) && $_SESSION['MANAGEMENT_PASSWORD_authenticated'] === true) {
|
|
if (isset($_SESSION['MANAGEMENT_last_activity']) && (time() - $_SESSION['MANAGEMENT_last_activity'] > MANAGEMENT_TIMEOUT_SECONDS)) {
|
|
unset($_SESSION['MANAGEMENT_PASSWORD_authenticated']);
|
|
unset($_SESSION['MANAGEMENT_last_activity']);
|
|
header("Location: " . $_SERVER['PHP_SELF'] . "?reason=timeout");
|
|
exit;
|
|
}
|
|
$_SESSION['MANAGEMENT_last_activity'] = time();
|
|
}
|
|
|
|
if (isset($_GET['reason']) && $_GET['reason'] === 'timeout') {
|
|
unset($_SESSION['MANAGEMENT_PASSWORD_authenticated']);
|
|
unset($_SESSION['MANAGEMENT_last_activity']);
|
|
}
|
|
|
|
if (!isset($_SESSION['MANAGEMENT_PASSWORD_authenticated']) || $_SESSION['MANAGEMENT_PASSWORD_authenticated'] !== true) {
|
|
$display_msg = "Management Access";
|
|
if (isset($_GET['reason']) && $_GET['reason'] === 'timeout') {
|
|
$login_error = "Logged out due to 20 min of inactivity.";
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Management 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: #0d47a1; 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: #1976d2; color: white; border: none; border-radius: 8px; font-size: 1.1rem; cursor: pointer; }
|
|
.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" onsubmit="requestFS()">
|
|
<input type="password" name="x_secure_token" placeholder="Enter Password" required autofocus>
|
|
<button type="submit">Secure Log In</button>
|
|
</form>
|
|
</div>
|
|
<div class="footer">
|
|
'PiClock' NFC Card Manager <span style="display: inline-block; transform: rotateY(180deg);">©</span> 2025-<?php echo date('Y');?>
|
|
<br>made with ♥ 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 = "";
|
|
|
|
// 1. Handle Form Submission
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['cards'])) {
|
|
try {
|
|
$pdo->beginTransaction();
|
|
$stmt = $pdo->prepare("UPDATE names SET CardID = ?, Name = ? WHERE id = ?");
|
|
|
|
foreach ($_POST['cards'] as $id => $data) {
|
|
$stmt->execute([trim($data['CardID']), trim($data['Name']), $id]);
|
|
}
|
|
$pdo->commit();
|
|
$message = "<div style='color: #81c784; background: rgba(129, 199, 132, 0.1); border: 1px solid #81c784; padding: 15px; border-radius: 8px; font-weight: bold; margin-bottom: 20px;'>Database updated successfully!</div>";
|
|
} catch (Exception $e) {
|
|
$pdo->rollBack();
|
|
$message = "<div style='color: #e57373; border: 1px solid #e57373; padding: 15px; border-radius: 8px; margin-bottom: 20px;'>Error: " . $e->getMessage() . "</div>";
|
|
}
|
|
}
|
|
|
|
// 2. Fetch data with dynamic sorting logic
|
|
$allowed_sorts = ['id', 'CardID', 'Name'];
|
|
$allowed_dirs = ['ASC', 'DESC'];
|
|
|
|
$sort_col = (isset($_GET['sort']) && in_array($_GET['sort'], $allowed_sorts)) ? $_GET['sort'] : 'id';
|
|
$sort_dir = (isset($_GET['dir']) && in_array($_GET['dir'], $allowed_dirs)) ? $_GET['dir'] : 'ASC';
|
|
|
|
// SAFELY check if the table exists before querying
|
|
$rows = []; // Initialize as empty array
|
|
try {
|
|
$check = $pdo->query("SELECT 1 FROM names LIMIT 1");
|
|
if ($check) {
|
|
$stmt = $pdo->query("SELECT id, CardID, Name FROM names ORDER BY $sort_col $sort_dir");
|
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
} catch (PDOException $e) {
|
|
// Table doesn't exist, $rows remains empty, no error thrown
|
|
}
|
|
|
|
// Helper function to build header links
|
|
function getSortUrl($column, $current_col, $current_dir) {
|
|
$next_dir = 'ASC';
|
|
if ($column === $current_col) {
|
|
$next_dir = ($current_dir === 'ASC') ? 'DESC' : 'ASC';
|
|
}
|
|
return "?sort=" . urlencode($column) . "&dir=" . urlencode($next_dir);
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>PiClock NFC Card 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: 1000px; margin: 0 auto; }
|
|
|
|
/* Updated header layout */
|
|
.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; }
|
|
|
|
/* Flex wrapper for the 3 top-right buttons */
|
|
.header-buttons { display: flex; gap: 15px; align-items: center; }
|
|
|
|
/* Base button styles */
|
|
.btn { padding: 10px 20px; border-radius: 8px; font-weight: 600; font-size: 1rem; 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); }
|
|
|
|
/* Individual button variants */
|
|
.btn-scanner { background-color: #ff9800; color: white; }
|
|
.btn-scanner:hover { background-color: #e65100; transform: translateY(-2px); }
|
|
|
|
.btn-logout { background-color: #e53935; color: white; }
|
|
.btn-logout:hover { background-color: #c62828; transform: translateY(-2px); }
|
|
|
|
.btn-save { background-color: #1976d2; color: white; width: 100%; padding: 16px; font-size: 1.2rem; border: none; margin-top: 10px; }
|
|
.btn-save:hover { background-color: #1565c0; transform: translateY(-2px); box-shadow: 0 6px 20px rgba(25, 118, 210, 0.4); }
|
|
|
|
/* Updated Card Table Container to cleanly crop the inner table corners */
|
|
.card-table { background: #fff; border-radius: 12px; color: #333; overflow: hidden; }
|
|
|
|
table { border-collapse: collapse; width: 100%; }
|
|
th { background-color: #0d47a1; color: white; padding: 16px; text-align: left; }
|
|
td { padding: 12px 16px; border-bottom: 1px solid #e0e0e0; }
|
|
|
|
/* Remove the border from the very last row so the bottom curves are smooth */
|
|
tr:last-child td { border-bottom: none; }
|
|
|
|
/* Table Header Sort Links */
|
|
th a { color: #ffffff; text-decoration: none; display: block; width: 100%; height: 100%; cursor: pointer; }
|
|
th a:hover, th a:visited, th a:active { color: #ffffff; text-decoration: none; }
|
|
|
|
input[type="text"] { width: 100%; padding: 12px; border: 1px solid #ccc; border-radius: 8px; }
|
|
.barcode { display: block; margin: auto; height: 35px; }
|
|
.instructions { background: rgba(255, 255, 255, 0.1); padding: 20px 25px; border-radius: 12px; margin-bottom: 30px; border-left: 5px solid #ffb300; backdrop-filter: blur(5px); }
|
|
.instructions p { margin-bottom: 8px; font-size: 1.05rem; line-height: 1.5; opacity: 0.9; }
|
|
.instructions p:last-child { margin-bottom: 0; }
|
|
.warning-text { color: #ffcc80; font-weight: bold; letter-spacing: 0.5px; }
|
|
.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>Assign Card Names</h2>
|
|
<div class="header-buttons">
|
|
<a href="/dev.php" target="_blank" class="btn btn-logout">⚠️ DB Dev ⚠️</a>
|
|
<a href="https://habipi5.swordfish-goanna.ts.net/scanner.html" target="_blank" class="btn btn-scanner">Scanner</a>
|
|
<a href="?logout=1" class="btn btn-logout">Log Out</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="instructions">
|
|
<p>To update the employee names use the fields below to rename each NFC Card.
|
|
<br>Then scroll to the bottom and click "Save Changes".</p>
|
|
<p class="warning-text">⚠️ WARNING: "Save Changes" writes ALL VALUES to the database!</p>
|
|
<p style="font-size: 0.85rem; margin-top: 10px; opacity: 0.7;">🔒 For security, this session will automatically time out after 20 minutes of inactivity.</p>
|
|
</div>
|
|
|
|
<?= $message ?>
|
|
|
|
<form method="POST">
|
|
<div class="card-table">
|
|
<table>
|
|
<tr>
|
|
<th style="width: 10%;"><a href="<?= getSortUrl('id', $sort_col, $sort_dir) ?>">ID</a></th>
|
|
<th style="width: 20%;"><a href="<?= getSortUrl('CardID', $sort_col, $sort_dir) ?>">Card ID</a></th>
|
|
<th style="width: 40%;"><a href="<?= getSortUrl('Name', $sort_col, $sort_dir) ?>">Employee Name</a></th>
|
|
<th style="text-align: center; width: 30%;">Barcode</th>
|
|
</tr>
|
|
<?php foreach ($rows as $row): ?>
|
|
<tr>
|
|
<td><code><?= htmlspecialchars($row['id']) ?></code></td>
|
|
<td><code><?= htmlspecialchars($row['CardID']) ?></code></td>
|
|
<td>
|
|
<input type="text" name="cards[<?= $row['id'] ?>][CardID]" value="<?= htmlspecialchars($row['CardID']) ?>" style="display:none;">
|
|
<input type="text" name="cards[<?= $row['id'] ?>][Name]" value="<?= htmlspecialchars($row['Name']) ?>">
|
|
</td>
|
|
<td style="text-align: center;"><img class="barcode" data-code="<?= htmlspecialchars($row['CardID']) ?>"></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</table>
|
|
</div>
|
|
<button type="submit" class="btn btn-save">Save Changes</button>
|
|
</form>
|
|
<div class="footer">
|
|
'PiClock' NFC Card Manager <span style="display: inline-block; transform: rotateY(180deg);">©</span> 2025-<?php echo date('Y');?>
|
|
<br>made with ♥ 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 src="JsBarcode.all.min.js" defer></script>
|
|
<script>
|
|
window.addEventListener('load', function() {
|
|
document.querySelectorAll('.barcode').forEach(function(element) {
|
|
const code = element.getAttribute('data-code');
|
|
try { JsBarcode(element, code, {format: "CODE128", width: 1.5, height: 35, displayValue: false, margin: 5}); }
|
|
catch (e) { console.error("Barcode failed", e); }
|
|
});
|
|
});
|
|
|
|
|
|
</script>
|
|
<script>
|
|
// Pass the PHP timeout value into JavaScript to log out after inactivity
|
|
const TIMEOUT_DURATION = <?php echo MANAGEMENT_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>
|