Add system shutdown/restart functionality via web interface
This commit adds the ability to safely shutdown or restart the Raspberry Pi from the web interface, with proper sudo permissions and user confirmations. Features: - API endpoints for /api/system/shutdown and /api/system/restart - System Control card in UI with shutdown and restart buttons - JavaScript confirmation dialogs to prevent accidental shutdowns - Shutdown has 1-minute delay, restart is immediate - Auto-reload after restart (30 second delay) - Sudoers file for passwordless sudo commands Technical details: - Uses subprocess.Popen() for non-blocking command execution - Shutdown: 'sudo shutdown -h +1' (1 minute delay) - Restart: 'sudo reboot' (immediate) - Created wedding-phone-shutdown sudoers file - Template version updated to 1.9.0 Installation required: sudo cp wedding-phone-shutdown /etc/sudoers.d/wedding-phone-shutdown sudo chmod 0440 /etc/sudoers.d/wedding-phone-shutdown 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -151,7 +151,7 @@ BACKUP_ON_WRITE = BACKUP_CONFIG.get('backup_on_write', True)
|
||||
WEB_PORT = SYS_CONFIG['web']['port']
|
||||
|
||||
# Template version - increment this when HTML template changes
|
||||
TEMPLATE_VERSION = "1.8.0" # Updated: Added recording sorting by date, name, duration, size
|
||||
TEMPLATE_VERSION = "1.9.0" # Updated: Added system shutdown/restart controls
|
||||
|
||||
# Flask app
|
||||
app = Flask(__name__)
|
||||
@@ -975,6 +975,28 @@ def api_backup_test():
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@app.route('/api/system/shutdown', methods=['POST'])
|
||||
def api_system_shutdown():
|
||||
"""Shutdown the Raspberry Pi"""
|
||||
try:
|
||||
import subprocess
|
||||
# Schedule shutdown in 1 minute to allow response to be sent
|
||||
subprocess.Popen(['sudo', 'shutdown', '-h', '+1'])
|
||||
return jsonify({"success": True, "message": "System will shutdown in 1 minute"})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@app.route('/api/system/restart', methods=['POST'])
|
||||
def api_system_restart():
|
||||
"""Restart the Raspberry Pi"""
|
||||
try:
|
||||
import subprocess
|
||||
# Schedule restart to allow response to be sent
|
||||
subprocess.Popen(['sudo', 'reboot'])
|
||||
return jsonify({"success": True, "message": "System will restart shortly"})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@app.route('/upload_greeting', methods=['POST'])
|
||||
def upload_greeting():
|
||||
"""Upload a new greeting message"""
|
||||
@@ -1869,6 +1891,30 @@ def main():
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Control Card -->
|
||||
<div class="card">
|
||||
<h2>⚙️ System Control</h2>
|
||||
|
||||
<div style="padding: 20px;">
|
||||
<p style="margin: 0 0 20px 0; color: #6b7280; text-align: center;">
|
||||
Safely shutdown or restart the Raspberry Pi
|
||||
</p>
|
||||
|
||||
<div style="display: flex; gap: 15px; justify-content: center; flex-wrap: wrap;">
|
||||
<button class="btn btn-danger" onclick="shutdownSystem()" style="min-width: 150px;">
|
||||
🔌 Shutdown
|
||||
</button>
|
||||
<button class="btn btn-warning" onclick="restartSystem()" style="min-width: 150px; background: #f59e0b;">
|
||||
🔄 Restart
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 15px; color: #9ca3af; font-size: 0.8em; text-align: center;">
|
||||
⚠️ System will shutdown/restart after confirmation
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sound Assignment Card -->
|
||||
<div class="card">
|
||||
<h2>🎵 Sound Assignment</h2>
|
||||
@@ -2473,6 +2519,53 @@ def main():
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// System Control Functions
|
||||
function shutdownSystem() {
|
||||
if (confirm('⚠️ Are you sure you want to SHUTDOWN the system?\n\nThe Raspberry Pi will power off in 1 minute.')) {
|
||||
fetch('/api/system/shutdown', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('🔌 System shutting down in 1 minute...', 'warning');
|
||||
setTimeout(() => {
|
||||
showAlert('System is now shutting down. This page will be unavailable.', 'error');
|
||||
}, 3000);
|
||||
} else {
|
||||
showAlert('Error: ' + data.error, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => showAlert('Error: ' + error, 'error'));
|
||||
}
|
||||
}
|
||||
|
||||
function restartSystem() {
|
||||
if (confirm('⚠️ Are you sure you want to RESTART the system?\n\nThe Raspberry Pi will reboot shortly.')) {
|
||||
fetch('/api/system/restart', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('🔄 System restarting...', 'warning');
|
||||
setTimeout(() => {
|
||||
showAlert('System is rebooting. Page will reload automatically.', 'warning');
|
||||
// Try to reload page after 30 seconds
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 30000);
|
||||
}, 3000);
|
||||
} else {
|
||||
showAlert('Error: ' + data.error, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => showAlert('Error: ' + error, 'error'));
|
||||
}
|
||||
}
|
||||
|
||||
// USB Backup Functions
|
||||
function refreshBackupStatus() {
|
||||
fetch('/api/backup/status')
|
||||
|
||||
8
wedding-phone-shutdown
Normal file
8
wedding-phone-shutdown
Normal file
@@ -0,0 +1,8 @@
|
||||
# Allow wedding-phone user to shutdown and reboot without password
|
||||
# Copy this file to /etc/sudoers.d/wedding-phone-shutdown
|
||||
# Usage: sudo cp wedding-phone-shutdown /etc/sudoers.d/wedding-phone-shutdown
|
||||
# Then: sudo chmod 0440 /etc/sudoers.d/wedding-phone-shutdown
|
||||
|
||||
# Allow user to run shutdown and reboot commands without password
|
||||
berwn ALL=(ALL) NOPASSWD: /sbin/shutdown
|
||||
berwn ALL=(ALL) NOPASSWD: /sbin/reboot
|
||||
Reference in New Issue
Block a user