- /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
535 lines
23 KiB
PHP
535 lines
23 KiB
PHP
<?php
|
|
// Start a secure session
|
|
session_start();
|
|
|
|
// 1. CHOOSE YOUR MANAGEMENT PASSWORD HERE:
|
|
define('STAFF_PASSWORD', '1111');
|
|
define('TIMEOUT_SECONDS', 86400); // Inactivity threshold
|
|
|
|
// Handle Explicit Logout
|
|
if (isset($_GET['logout'])) {
|
|
unset($_SESSION['authenticated']);
|
|
unset($_SESSION['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'] === STAFF_PASSWORD) {
|
|
$_SESSION['authenticated'] = true;
|
|
$_SESSION['last_activity'] = time();
|
|
header("Location: " . $_SERVER['PHP_SELF']);
|
|
exit;
|
|
} else {
|
|
$login_error = "Incorrect password access denied.";
|
|
}
|
|
}
|
|
|
|
// Server-Side Inactivity Check
|
|
if (isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true) {
|
|
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > TIMEOUT_SECONDS)) {
|
|
unset($_SESSION['authenticated']);
|
|
unset($_SESSION['last_activity']);
|
|
header("Location: " . $_SERVER['PHP_SELF'] . "?reason=timeout");
|
|
exit;
|
|
}
|
|
$_SESSION['last_activity'] = time();
|
|
}
|
|
|
|
if (isset($_GET['reason']) && $_GET['reason'] === 'timeout') {
|
|
unset($_SESSION['authenticated']);
|
|
unset($_SESSION['last_activity']);
|
|
}
|
|
|
|
// If user is not authenticated, show the login gate
|
|
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
|
$display_msg = "Staff Access";
|
|
if (isset($_GET['reason']) && $_GET['reason'] === 'timeout') {
|
|
$login_error = "Logged out due to inactivity.";
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Staff Login</title>
|
|
<link rel="icon" href="favicon.png" type="image/png">
|
|
<style>
|
|
html {
|
|
-webkit-text-size-adjust: 100%;
|
|
touch-action: manipulation; /* Improves touch response */
|
|
}
|
|
/* Prevent iOS zoom on input focus */
|
|
input[type="text"],
|
|
input[type="date"],
|
|
input[type="password"] {
|
|
font-size: 16px !important;
|
|
}
|
|
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', sans-serif; }
|
|
|
|
/* 1. Change body to flex-column */
|
|
body {
|
|
background: linear-gradient(180deg, #1c3344, #102a33, #000000);
|
|
min-height: 100dvh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center; /* Keeps it centered horizontally */
|
|
}
|
|
|
|
/* 2. Push login box to the center of available space */
|
|
.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; transition: all 0.3s; }
|
|
input[type="password"]:focus { outline: none; border-color: #1976d2; box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.15); }
|
|
button { width: 100%; padding: 15px; background-color: #1976d2; color: white; border: none; border-radius: 8px; font-size: 1.1rem; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
|
|
button:hover { background-color: #1565c0; transform: translateY(-2px); box-shadow: 0 6px 15px rgba(25, 118, 210, 0.3); }
|
|
.error { background: #ffebee; color: #c62828; padding: 12px; border-radius: 8px; font-weight: bold; margin-bottom: 20px; font-size: 0.9rem; border: 1px solid #ffcdd2; }
|
|
|
|
/* 3. Footer stays at the bottom */
|
|
.footer { text-align: center; padding-bottom: 20px; color: rgba(255, 255, 255, 0.7); font-size: 0.9rem; }
|
|
|
|
/* Responsive adjustments for screens smaller than 600px */
|
|
@media (max-width: 600px) {
|
|
header h1 {
|
|
/* Scales between 1.5rem and 2.8rem depending on screen width */
|
|
font-size: clamp(1.5rem, 5vw, 2.8rem);
|
|
margin-bottom: 15px;
|
|
text-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
}
|
|
header p {
|
|
font-size: 1rem;
|
|
}
|
|
.employee-name {
|
|
font-size: 0.95rem;
|
|
}
|
|
.time-badge {
|
|
font-size: 0.8rem;
|
|
padding: 4px 10px;
|
|
}
|
|
.search-box {
|
|
max-width: 100%;
|
|
}
|
|
.controls {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</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" autocomplete="new-password" required autofocus>
|
|
<button type="submit">Log In</button>
|
|
</form>
|
|
</div>
|
|
<div class="footer">
|
|
'PiClock' NFC Timestamp Viewer <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;
|
|
}
|
|
?>
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<title>PiClock Dashboard</title>
|
|
<link rel="icon" href="favicon.png" type="image/png">
|
|
<style>
|
|
html {
|
|
-webkit-text-size-adjust: 100%;
|
|
touch-action: manipulation; /* Improves touch response */
|
|
}
|
|
/* Prevent iOS zoom on input focus */
|
|
input[type="text"],
|
|
input[type="date"],
|
|
input[type="password"] {
|
|
font-size: 16px !important;
|
|
}
|
|
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', sans-serif; }
|
|
body { background: linear-gradient(180deg, #1c3344, #102a33, #000000); min-height: 100dvh; padding: 30px 20px; color: #fff; }
|
|
.container { max-width: 1200px; margin: 0 auto; }
|
|
header { text-align: center; padding: 5px 0; margin-bottom: 1px; }
|
|
header h1 {
|
|
/* Scales between 1.5rem and 2.8rem depending on screen width */
|
|
font-size: clamp(1.5rem, 5vw, 2.8rem);
|
|
margin-bottom: 15px;
|
|
text-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
}
|
|
header p { font-size: 1.2rem; opacity: 0.9; }
|
|
|
|
.controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; flex-wrap: wrap; gap: 20px; }
|
|
.search-box { flex: 1; min-width: 150px; max-width: 250px; }
|
|
.search-box input { width: 100%; padding: 14px 20px; border-radius: 50px; border: 1px solid rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.15); color: white; font-size: 1rem; }
|
|
.search-box input:focus { outline: none; background: rgba(255, 255, 255, 0.25); border-color: #64b5f6; }
|
|
|
|
.date-filters { display: flex; gap: 15px; align-items: center; flex-wrap: wrap; }
|
|
.date-group { display: flex; align-items: center; gap: 8px; }
|
|
.date-group label { font-size: 0.9rem; color: #bbdefb; font-weight: 500; }
|
|
.date-group input[type="date"] { padding: 11px 15px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.15); color: white; font-size: 0.95rem; font-family: inherit; color-scheme: dark; }
|
|
.date-group input[type="date"]:focus { outline: none; background: rgba(255, 255, 255, 0.25); border-color: #64b5f6; }
|
|
|
|
.timeline-container { display: flex; flex-direction: column; gap: 25px; margin-bottom: 20px; }
|
|
.day-card { background: rgba(255, 255, 255, 0.95); border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.3); }
|
|
.day-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 25px; background: #1976d2; color: white; }
|
|
.day-title { font-size: 1.2rem; font-weight: 600; text-shadow: 0 1px 2px rgba(0,0,0,0.2); }
|
|
.day-meta { display: flex; gap: 10px; align-items: center; }
|
|
.day-meta .year, .day-meta .week { padding: 4px 10px; border-radius: 4px; font-size: 0.85rem; background: rgba(255, 255, 255, 0.2); color: white; font-weight: 500; }
|
|
|
|
.day-content { display: flex; flex-direction: column; }
|
|
.employee-row { display: flex; justify-content: space-between; align-items: center; padding: 15px 25px; border-bottom: 1px solid #e0e0e0; gap: 20px; }
|
|
.employee-row:last-child { border-bottom: none; }
|
|
.employee-row:hover { background-color: #f5f9ff; }
|
|
|
|
.employee-info { display: flex; align-items: center; gap: 15px; }
|
|
.avatar { width: 40px; height: 40px; border-radius: 50%; background: linear-gradient(45deg, #2196f3, #64b5f6); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 1.1rem; }
|
|
.employee-name { font-weight: 600; font-size: 1.05rem; color: #263238; }
|
|
.card-id-sub { font-size: 0.85rem; color: #666; margin-top: 2px; }
|
|
.badge-unknown { background-color: #ffebee; color: #c62828; padding: 4px 10px; border-radius: 12px; font-size: 0.85rem; font-weight: bold; border: 1px solid #ffcdd2; display: inline-block; }
|
|
code { background-color: #f0f1f4; padding: 2px 6px; border-radius: 4px; font-family: monospace; color: #1976d2; font-size: 0.85rem; }
|
|
|
|
.punch-times { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; max-width: 60%; }
|
|
.time-badge { background: #e3f2fd; color: #0d47a1; padding: 6px 14px; border-radius: 30px; font-size: 0.9rem; font-weight: 600; border: 1px solid #bbdefb; box-shadow: 0 2px 4px rgba(0,0,0,0.04); }
|
|
|
|
.table-header { display: flex; justify-content: space-between; padding: 15px 25px; background: #0d47a1; color: white; font-weight: 500; border-radius: 12px; margin-bottom: 15px; position: relative; z-index: 2; }
|
|
.table-header a, .table-header a:link, .table-header a:visited { color: #ffffffff !important; text-decoration: none; cursor: default; }
|
|
.footer { text-align: center; margin-top: 40px; color: rgba(255, 255, 255, 0.7); font-size: 0.9rem; }
|
|
|
|
/* ADDED: Visual Cues for Interactive filtering elements */
|
|
.day-title, .year, .week, .employee-name, .badge-unknown {
|
|
cursor: pointer;
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
.day-title:hover, .year:hover, .week:hover, .employee-name:hover, .badge-unknown:hover {
|
|
opacity: 0.6;
|
|
}
|
|
/* Responsive adjustments for screens smaller than 600px */
|
|
@media (max-width: 600px) {
|
|
/* 1. Reclaim wasted screen space by reducing container padding */
|
|
body {
|
|
padding: 15px 10px;
|
|
}
|
|
|
|
/* 2. Header and Search adjustments */
|
|
header h1 {
|
|
font-size: clamp(1.5rem, 6vw, 2.2rem) !important;
|
|
margin-bottom: 10px;
|
|
}
|
|
header p { font-size: 1rem; }
|
|
.search-box { max-width: 100%; }
|
|
.controls { flex-direction: column; align-items: stretch; }
|
|
.date-filters { justify-content: space-between; }
|
|
|
|
/* 3. Stack the Table Header */
|
|
.table-header {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
padding: 12px;
|
|
gap: 10px;
|
|
}
|
|
|
|
/* 4. Stack the Day Header so dates don't wrap aggressively */
|
|
.day-header {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
padding: 12px 15px;
|
|
gap: 8px;
|
|
}
|
|
.day-title { font-size: 1.1rem; }
|
|
|
|
/* 5. Stack the Employee Rows so time badges drop to a new line */
|
|
.employee-row {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
padding: 15px;
|
|
gap: 12px;
|
|
}
|
|
.employee-info { width: 100%; }
|
|
|
|
/* 6. Allow time punches to take full width and align left under the name */
|
|
.punch-times {
|
|
max-width: 100%;
|
|
justify-content: flex-start;
|
|
padding-left: 55px; /* Indents them nicely under the avatar */
|
|
}
|
|
.time-badge {
|
|
font-size: 0.85rem;
|
|
padding: 5px 12px;
|
|
}
|
|
.employee-name { font-size: 1rem; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header>
|
|
<h1>'PiClock' NFC System</h1>
|
|
<p>custom made Punchcard-System replacement</p>
|
|
</header>
|
|
<div class="controls">
|
|
<div class="search-box">
|
|
<input type="text" id="searchInput" placeholder="🔎 Search">
|
|
</div>
|
|
<div class="date-filters">
|
|
<div class="date-group">
|
|
<label for="startDate">From:</label>
|
|
<input type="date" id="startDate">
|
|
</div>
|
|
<div class="date-group">
|
|
<label for="endDate">To:</label>
|
|
<input type="date" id="endDate">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-header">
|
|
<div><a href="/staff_names.php" target="_blank">Grouped Shift Logs</a></div>
|
|
<div id="recordCounter">Loading NFC Card swipes...</div>
|
|
</div>
|
|
|
|
<div class="timeline-container" id="live-data">
|
|
</div>
|
|
<div class="footer">
|
|
'PiClock' NFC Timestamp Viewer <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>
|
|
<script>
|
|
// Safety net to re-enable fullscreen if lost
|
|
window.addEventListener('load', function() {
|
|
document.body.addEventListener('click', function() {
|
|
if (!document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen().catch(err => {});
|
|
}
|
|
}, { once: true });
|
|
});
|
|
|
|
// --- INTERACTIVE CLICK LISTENERS ---
|
|
document.getElementById('live-data').addEventListener('click', function(e) {
|
|
const searchInput = document.getElementById('searchInput');
|
|
const startDateInput = document.getElementById('startDate');
|
|
const endDateInput = document.getElementById('endDate');
|
|
|
|
// 1. Employee Name or ID or Unknown Badge
|
|
const clickedName = e.target.closest('.employee-name') || e.target.closest('.badge-unknown');
|
|
if (clickedName) {
|
|
let textToSearch = clickedName.textContent.trim();
|
|
|
|
// Toggle off if already searching this exact text
|
|
if (searchInput.value === textToSearch) {
|
|
searchInput.value = '';
|
|
} else {
|
|
searchInput.value = textToSearch;
|
|
}
|
|
filterDashboard();
|
|
}
|
|
|
|
// 2. Day Title
|
|
const clickedDay = e.target.closest('.day-title');
|
|
if (clickedDay) {
|
|
const d = clickedDay.closest('.day-card').dataset.date;
|
|
|
|
// Toggle off if already filtered to this exact day
|
|
if (startDateInput.value === d && endDateInput.value === d) {
|
|
startDateInput.value = '';
|
|
endDateInput.value = '';
|
|
} else {
|
|
startDateInput.value = d;
|
|
endDateInput.value = d;
|
|
}
|
|
filterDashboard();
|
|
}
|
|
|
|
// 3. Year
|
|
const clickedYear = e.target.closest('.year');
|
|
if (clickedYear) {
|
|
const y = clickedYear.dataset.year;
|
|
const startY = y + '-01-01';
|
|
const endY = y + '-12-31';
|
|
|
|
// Toggle off if already filtered to this exact year
|
|
if (startDateInput.value === startY && endDateInput.value === endY) {
|
|
startDateInput.value = '';
|
|
endDateInput.value = '';
|
|
} else {
|
|
startDateInput.value = startY;
|
|
endDateInput.value = endY;
|
|
}
|
|
filterDashboard();
|
|
}
|
|
|
|
// 4. Week
|
|
const clickedWeek = e.target.closest('.week');
|
|
if (clickedWeek) {
|
|
const startWk = clickedWeek.dataset.start;
|
|
const endWk = clickedWeek.dataset.end;
|
|
|
|
// Toggle off if already filtered to this exact week
|
|
if (startDateInput.value === startWk && endDateInput.value === endWk) {
|
|
startDateInput.value = '';
|
|
endDateInput.value = '';
|
|
} else {
|
|
startDateInput.value = startWk;
|
|
endDateInput.value = endWk;
|
|
}
|
|
filterDashboard();
|
|
}
|
|
});
|
|
|
|
|
|
// --- FILTER LOGIC ---
|
|
function filterDashboard() {
|
|
const searchTerm = document.getElementById('searchInput').value.toLowerCase().trim();
|
|
const startDate = document.getElementById('startDate').value;
|
|
const endDate = document.getElementById('endDate').value;
|
|
|
|
const dayCards = document.querySelectorAll('.day-card');
|
|
let totalVisiblePunches = 0;
|
|
|
|
dayCards.forEach(card => {
|
|
const dayDate = card.getAttribute('data-date');
|
|
|
|
let dateMatches = true;
|
|
if (startDate && dayDate < startDate) dateMatches = false;
|
|
if (endDate && dayDate > endDate) dateMatches = false;
|
|
|
|
if (!dateMatches) {
|
|
card.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
let visibleEmployeesInDay = 0;
|
|
const rows = card.querySelectorAll('.employee-row');
|
|
|
|
rows.forEach(row => {
|
|
const empName = row.getAttribute('data-name');
|
|
|
|
// Updated search logic
|
|
const textMatches = empName.includes(searchTerm) ||
|
|
(searchTerm === 'unknown card' && row.querySelector('.badge-unknown'));
|
|
|
|
if (textMatches) {
|
|
row.style.display = '';
|
|
visibleEmployeesInDay++;
|
|
totalVisiblePunches += row.querySelectorAll('.time-badge').length;
|
|
} else {
|
|
row.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
if (visibleEmployeesInDay > 0) {
|
|
card.style.display = '';
|
|
} else {
|
|
card.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
document.getElementById('recordCounter').textContent = 'Total: ' + totalVisiblePunches + ' swipes';
|
|
}
|
|
|
|
document.getElementById('searchInput').addEventListener('input', filterDashboard);
|
|
document.getElementById('startDate').addEventListener('change', filterDashboard);
|
|
document.getElementById('endDate').addEventListener('change', filterDashboard);
|
|
|
|
|
|
// --- LIVE UPDATE LOGIC ---
|
|
let currentVersion = '';
|
|
|
|
// Fetches the HTML from the backend and updates the container
|
|
async function fetchTableData() {
|
|
try {
|
|
// Ensure this points to the new PHP script you created in the first step
|
|
const response = await fetch('get-table-data.php?t=' + Date.now());
|
|
if (!response.ok) return;
|
|
const htmlRows = await response.text();
|
|
|
|
// Inject the new HTML
|
|
document.getElementById('live-data').innerHTML = htmlRows;
|
|
|
|
// Re-run the filter immediately so new data obeys active search/dates
|
|
filterDashboard();
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching table data:', error);
|
|
}
|
|
}
|
|
|
|
// Checks the database for new row counts
|
|
async function checkServerForUpdates() {
|
|
try {
|
|
const response = await fetch('check_update.php?t=' + Date.now());
|
|
if (!response.ok) {
|
|
console.warn('Update background check failed with status:', response.status);
|
|
return;
|
|
}
|
|
|
|
const latestVersion = await response.text();
|
|
|
|
// Initial load scenario
|
|
if (!currentVersion) {
|
|
currentVersion = latestVersion;
|
|
fetchTableData(); // Fetch the data for the first time
|
|
return;
|
|
}
|
|
|
|
// If a change happened in the MySQL Database
|
|
if (latestVersion !== currentVersion) {
|
|
console.log('Database change detected! Fetching new data...');
|
|
currentVersion = latestVersion;
|
|
fetchTableData();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking for updates:', error);
|
|
}
|
|
}
|
|
|
|
// Check for updates every 3000 milliseconds (3 seconds)
|
|
checkServerForUpdates();
|
|
setInterval(checkServerForUpdates, 3000);
|
|
</script>
|
|
<script>
|
|
// Pass the PHP timeout value into JavaScript to log out after inactivity
|
|
const TIMEOUT_DURATION = <?php echo 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>
|