Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
a9231446a8 | |||
cf6d8fc2c2 |
15
.gitignore
vendored
15
.gitignore
vendored
@ -1 +1,16 @@
|
|||||||
|
# Deployment Script
|
||||||
deploy_dangrubb.net.sh
|
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
|
103
index.html
103
index.html
@ -11,6 +11,109 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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 -->
|
<!-- partial:index.partial.html -->
|
||||||
<div class="bard">
|
<div class="bard">
|
||||||
<div class="strip" style="
|
<div class="strip" style="
|
||||||
|
45
php/get_state.php
Normal file
45
php/get_state.php
Normal 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
66
php/update_state.php
Normal 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';
|
||||||
|
?>
|
Reference in New Issue
Block a user