Compare commits

2 Commits
main ... dev

Author SHA1 Message Date
a9231446a8 added two dots 2025-07-16 16:50:15 -04:00
cf6d8fc2c2 initial switch commit 2025-07-16 15:34:57 -04:00
4 changed files with 230 additions and 1 deletions

17
.gitignore vendored
View File

@ -1 +1,16 @@
deploy_dangrubb.net.sh
# Deployment Script
deploy_dangrubb.net.sh
# Data files (contain current state)
server_state.txt
php/server_state.log
# Temporary files
*.tmp
.DS_Store
# Logs
*.log
# Local configuration
config.local.php

View File

@ -11,6 +11,109 @@
</head>
<body>
<!-- Add this anywhere in your HTML -->
<div id="server-control">
<span id="ascii-switch">[||==] OFF</span>
</div>
<style>
/* Position the control in top right */
#server-control {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
font-family: 'Courier New', monospace;
font-size: 16px;
}
#ascii-switch {
cursor: pointer;
user-select: none;
}
/* Color states */
.state-off {
color: white;
}
.state-on {
color: #4CAF50;
}
.state-unavailable {
color: #ff6b6b;
cursor: not-allowed;
text-decoration: none;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const asciiSwitch = document.getElementById('ascii-switch');
let currentState = false; // false = off, true = on
// Load current state
fetch('/php/get_state.php')
.then(response => response.text())
.then(data => {
const state = data.trim();
currentState = (state === '1');
updateDisplay(currentState);
})
.catch(() => {
// If file doesn't exist or can't be reached, set to unavailable
updateDisplay('unavailable');
});
// Handle clicks
asciiSwitch.addEventListener('click', function() {
if (currentState === 'unavailable') return; // Don't allow clicking if unavailable
const newState = !currentState;
const stateValue = newState ? '1' : '0';
// Optimistically update display
updateDisplay(newState);
currentState = newState;
// Send state to server
fetch('/php/update_state.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'state=' + stateValue
})
.then(response => response.text())
.then(data => {
console.log('State updated:', stateValue);
})
.catch(error => {
console.error('Error updating state:', error);
// Revert on error
currentState = !newState;
updateDisplay(currentState);
});
});
function updateDisplay(state) {
asciiSwitch.className = ''; // Clear existing classes
if (state === 'unavailable') {
asciiSwitch.textContent = '[????] UNAVAILABLE';
asciiSwitch.classList.add('state-unavailable');
} else if (state === true || state === '1') {
asciiSwitch.textContent = '[==||] ON';
asciiSwitch.classList.add('state-on');
} else {
asciiSwitch.textContent = '[||==] OFF';
asciiSwitch.classList.add('state-off');
}
}
});
</script>
<!-- partial:index.partial.html -->
<div class="bard">
<div class="strip" style="

45
php/get_state.php Normal file
View File

@ -0,0 +1,45 @@
<?php
header('Content-Type: text/plain');
// Only allow GET requests
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
http_response_code(405);
die('Method not allowed');
}
$file_path = '../server_state.txt';
// Check if file exists
if (!file_exists($file_path)) {
// Return default state if file doesn't exist
echo '0';
exit;
}
// Check if file is readable
if (!is_readable($file_path)) {
http_response_code(500);
die('Cannot read state file');
}
$state = file_get_contents($file_path);
// Validate the stored state
if ($state === false) {
http_response_code(500);
die('Failed to read state');
}
// Clean the state (remove any whitespace)
$state = trim($state);
// Validate what we read from the file
if (!in_array($state, ['0', '1'], true)) {
// File was corrupted somehow, reset to default
file_put_contents($file_path, '0', LOCK_EX);
echo '0';
exit;
}
echo $state;
?>

66
php/update_state.php Normal file
View File

@ -0,0 +1,66 @@
<?php
// Set proper headers
header('Content-Type: text/plain');
// Only allow POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
die('Method not allowed');
}
// Strict input validation
if (!isset($_POST['state'])) {
http_response_code(400);
die('Missing state parameter');
}
// Only allow exact string matches
if (!in_array($_POST['state'], ['0', '1'], true)) {
http_response_code(400);
die('Invalid state - must be 0 or 1');
}
// Additional validation - check if it's exactly one character
if (strlen($_POST['state']) !== 1) {
http_response_code(400);
die('Invalid state length');
}
// Validate it's actually a digit
if (!ctype_digit($_POST['state'])) {
http_response_code(400);
die('State must be numeric');
}
$state = $_POST['state'];
// Use a safe file path outside web root
$file_path = '../server_state.txt';
// Ensure directory exists and is writable
if (!is_writable(dirname($file_path))) {
http_response_code(500);
die('Server configuration error');
}
// Write with file locking to prevent race conditions
$result = file_put_contents($file_path, $state, LOCK_EX);
if ($result === false) {
http_response_code(500);
die('Failed to write state');
}
// Verify the write was successful
$written_state = file_get_contents($file_path);
if ($written_state !== $state) {
http_response_code(500);
die('State verification failed');
}
// Optional: Log the change (helpful for debugging)
$log_entry = date('Y-m-d H:i:s') . " - State changed to: {$state} - IP: {$_SERVER['REMOTE_ADDR']}\n";
error_log($log_entry, 3, '../tmp/server_state.log');
echo 'success';
?>