Add graceful shutdown button and improve shutdown status display
- Add refresh/shutdown button next to status indicator that gracefully stops the process without cleaning my-storage - Create /api/shutdown endpoint that triggers graceful shutdown via SIGTERM - Update status indicator to show "Gracefully Cleaning...." when system is shutting down (instead of "Searching for peers...") - Include isShuttingDown flag in /api/status endpoint response - Use built-in ConfirmationModal for shutdown confirmation with warning styling The shutdown button appears in the top-right corner next to the status indicator and uses the existing confirmation modal system for user confirmation before initiating shutdown.
This commit is contained in:
@@ -14,7 +14,8 @@ async function handleStatusRoutes(req, res) {
|
|||||||
isMaster: state.isMaster,
|
isMaster: state.isMaster,
|
||||||
isConnected: !!state.dnsPass,
|
isConnected: !!state.dnsPass,
|
||||||
peersCount: state.connectedPeers.size,
|
peersCount: state.connectedPeers.size,
|
||||||
pid: process.pid
|
pid: process.pid,
|
||||||
|
isShuttingDown: state.isShuttingDown || false
|
||||||
};
|
};
|
||||||
trackRequest('/api/status', true);
|
trackRequest('/api/status', true);
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
@@ -28,6 +29,30 @@ async function handleStatusRoutes(req, res) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (method === 'POST' && urlPath === '/api/shutdown') {
|
||||||
|
try {
|
||||||
|
const { logInfo } = require('../../../infrastructure/logger');
|
||||||
|
|
||||||
|
// Send success response before shutting down
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Graceful shutdown initiated. Process will exit after cleanup completes.'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Trigger graceful shutdown after sending response
|
||||||
|
setTimeout(() => {
|
||||||
|
logInfo('Admin', 'Initiating graceful shutdown (without cleaning storage)...');
|
||||||
|
process.emit('SIGTERM');
|
||||||
|
}, 500); // Small delay to ensure response is sent
|
||||||
|
} catch (err) {
|
||||||
|
const errorResponse = createErrorResponse(err, 500);
|
||||||
|
res.writeHead(errorResponse.statusCode, errorResponse.headers);
|
||||||
|
res.end(errorResponse.body);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (method === 'GET' && urlPath === '/api/health') {
|
if (method === 'GET' && urlPath === '/api/health') {
|
||||||
try {
|
try {
|
||||||
const query = url.parse(req.url, true).query;
|
const query = url.parse(req.url, true).query;
|
||||||
|
|||||||
@@ -32,7 +32,12 @@
|
|||||||
<div class="container mx-auto max-w-7xl">
|
<div class="container mx-auto max-w-7xl">
|
||||||
<div class="flex justify-between items-center mb-8 flex-wrap gap-4">
|
<div class="flex justify-between items-center mb-8 flex-wrap gap-4">
|
||||||
<h1 class="text-4xl font-extrabold" style="background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">P2NS Admin Panel</h1>
|
<h1 class="text-4xl font-extrabold" style="background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">P2NS Admin Panel</h1>
|
||||||
<div id="status-indicator" class="px-4 py-2 rounded-lg"></div>
|
<div class="flex items-center gap-2">
|
||||||
|
<div id="status-indicator" class="px-4 py-2 rounded-lg"></div>
|
||||||
|
<button id="refresh-button" onclick="handleRefresh()" class="px-3 py-2 bg-primary hover:bg-primary-hover text-white rounded-lg transition-colors" title="Gracefully stop the process">
|
||||||
|
<i class="fas fa-redo"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="flex justify-center mb-8 space-x-4 flex-wrap">
|
<nav class="flex justify-center mb-8 space-x-4 flex-wrap">
|
||||||
|
|||||||
@@ -301,7 +301,10 @@ async function updateStatus() {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
let text;
|
let text;
|
||||||
let color = 'bg-blue-600';
|
let color = 'bg-blue-600';
|
||||||
if (data.isMaster) {
|
if (data.isShuttingDown) {
|
||||||
|
text = 'Gracefully Cleaning....';
|
||||||
|
color = 'bg-orange-500';
|
||||||
|
} else if (data.isMaster) {
|
||||||
text = `This is Master • Peers: ${data.peersCount}`;
|
text = `This is Master • Peers: ${data.peersCount}`;
|
||||||
} else {
|
} else {
|
||||||
if (data.isConnected) {
|
if (data.isConnected) {
|
||||||
@@ -371,6 +374,63 @@ function stopDomainsUpdates() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleRefresh() {
|
||||||
|
if (window.ConfirmationModal) {
|
||||||
|
const confirmed = await window.ConfirmationModal.warning(
|
||||||
|
'Are you sure you want to gracefully stop the process? This will shut down the system without cleaning my-storage.',
|
||||||
|
{
|
||||||
|
title: 'Graceful Shutdown',
|
||||||
|
confirmText: 'Shutdown',
|
||||||
|
cancelText: 'Cancel'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await performShutdown();
|
||||||
|
} else if (window.showConfirm) {
|
||||||
|
// Fallback to showConfirm if ConfirmationModal not available
|
||||||
|
window.showConfirm(
|
||||||
|
'Are you sure you want to gracefully stop the process? This will shut down the system without cleaning my-storage.',
|
||||||
|
async () => {
|
||||||
|
await performShutdown();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Final fallback to native confirm
|
||||||
|
if (!confirm('Are you sure you want to gracefully stop the process? This will shut down the system without cleaning my-storage.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await performShutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function performShutdown() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/shutdown', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
if (window.showNotification) {
|
||||||
|
window.showNotification(data.message || 'Graceful shutdown initiated', 'info');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const errorText = await res.text();
|
||||||
|
if (window.showNotification) {
|
||||||
|
window.showNotification('Failed to initiate shutdown: ' + errorText, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to call shutdown endpoint:', err);
|
||||||
|
if (window.showNotification) {
|
||||||
|
window.showNotification('Failed to initiate shutdown: ' + err.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Make functions globally accessible
|
// Make functions globally accessible
|
||||||
window.connectWebSocket = connectWebSocket;
|
window.connectWebSocket = connectWebSocket;
|
||||||
window.startPollingFallback = startPollingFallback;
|
window.startPollingFallback = startPollingFallback;
|
||||||
@@ -381,6 +441,7 @@ window.startStatusUpdates = startStatusUpdates;
|
|||||||
window.stopStatusUpdates = stopStatusUpdates;
|
window.stopStatusUpdates = stopStatusUpdates;
|
||||||
window.startDomainsUpdates = startDomainsUpdates;
|
window.startDomainsUpdates = startDomainsUpdates;
|
||||||
window.stopDomainsUpdates = stopDomainsUpdates;
|
window.stopDomainsUpdates = stopDomainsUpdates;
|
||||||
|
window.handleRefresh = handleRefresh;
|
||||||
|
|
||||||
// Initialize WebSocket connection
|
// Initialize WebSocket connection
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
|
|||||||
@@ -781,7 +781,12 @@
|
|||||||
</dialog>
|
</dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="status-indicator" class="fixed top-4 right-4 px-4 py-2 bg-blue-500 text-white rounded-lg shadow-md"></div>
|
<div class="fixed top-4 right-4 flex items-center gap-2">
|
||||||
|
<div id="status-indicator" class="px-4 py-2 bg-blue-500 text-white rounded-lg shadow-md"></div>
|
||||||
|
<button id="refresh-button" onclick="handleRefresh()" class="px-3 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg shadow-md transition-colors" title="Gracefully stop the process">
|
||||||
|
<i class="fas fa-redo"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div id="notifications" class="fixed bottom-4 right-4 flex flex-col-reverse space-y-2" style="z-index: 99999;"></div>
|
<div id="notifications" class="fixed bottom-4 right-4 flex flex-col-reverse space-y-2" style="z-index: 99999;"></div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm.js"></script>
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ async function handleStatusRoutes(req, res) {
|
|||||||
const status = {
|
const status = {
|
||||||
isMaster: state.isMaster,
|
isMaster: state.isMaster,
|
||||||
isConnected: !!state.dnsPass,
|
isConnected: !!state.dnsPass,
|
||||||
peersCount: state.connectedPeers.size
|
peersCount: state.connectedPeers.size,
|
||||||
|
isShuttingDown: state.isShuttingDown || false
|
||||||
};
|
};
|
||||||
trackRequest('/api/status', true);
|
trackRequest('/api/status', true);
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
@@ -27,6 +28,30 @@ async function handleStatusRoutes(req, res) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (method === 'POST' && urlPath === '/api/shutdown') {
|
||||||
|
try {
|
||||||
|
const { logInfo } = require('../../infrastructure/logger');
|
||||||
|
|
||||||
|
// Send success response before shutting down
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Graceful shutdown initiated. Process will exit after cleanup completes.'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Trigger graceful shutdown after sending response
|
||||||
|
setTimeout(() => {
|
||||||
|
logInfo('Admin', 'Initiating graceful shutdown (without cleaning storage)...');
|
||||||
|
process.emit('SIGTERM');
|
||||||
|
}, 500); // Small delay to ensure response is sent
|
||||||
|
} catch (err) {
|
||||||
|
const errorResponse = createErrorResponse(err, 500);
|
||||||
|
res.writeHead(errorResponse.statusCode, errorResponse.headers);
|
||||||
|
res.end(errorResponse.body);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (method === 'GET' && urlPath === '/api/health') {
|
if (method === 'GET' && urlPath === '/api/health') {
|
||||||
try {
|
try {
|
||||||
const query = url.parse(req.url, true).query;
|
const query = url.parse(req.url, true).query;
|
||||||
|
|||||||
@@ -244,7 +244,10 @@ async function updateStatus() {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
let text;
|
let text;
|
||||||
let color = 'bg-blue-600';
|
let color = 'bg-blue-600';
|
||||||
if (data.isMaster) {
|
if (data.isShuttingDown) {
|
||||||
|
text = 'Gracefully Cleaning...';
|
||||||
|
color = 'bg-orange-500';
|
||||||
|
} else if (data.isMaster) {
|
||||||
text = `This is Master • Peers: ${data.peersCount}`;
|
text = `This is Master • Peers: ${data.peersCount}`;
|
||||||
} else {
|
} else {
|
||||||
if (data.isConnected) {
|
if (data.isConnected) {
|
||||||
@@ -312,6 +315,63 @@ function stopDomainsUpdates() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleRefresh() {
|
||||||
|
if (window.ConfirmationModal) {
|
||||||
|
const confirmed = await window.ConfirmationModal.warning(
|
||||||
|
'Are you sure you want to gracefully stop the process? This will shut down the system without cleaning my-storage.',
|
||||||
|
{
|
||||||
|
title: 'Graceful Shutdown',
|
||||||
|
confirmText: 'Shutdown',
|
||||||
|
cancelText: 'Cancel'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await performShutdown();
|
||||||
|
} else if (window.showConfirm) {
|
||||||
|
// Fallback to showConfirm if ConfirmationModal not available
|
||||||
|
window.showConfirm(
|
||||||
|
'Are you sure you want to gracefully stop the process? This will shut down the system without cleaning my-storage.',
|
||||||
|
async () => {
|
||||||
|
await performShutdown();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Final fallback to native confirm
|
||||||
|
if (!confirm('Are you sure you want to gracefully stop the process? This will shut down the system without cleaning my-storage.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await performShutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function performShutdown() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/shutdown', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
if (window.showNotification) {
|
||||||
|
window.showNotification(data.message || 'Graceful shutdown initiated', 'info');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const errorText = await res.text();
|
||||||
|
if (window.showNotification) {
|
||||||
|
window.showNotification('Failed to initiate shutdown: ' + errorText, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to call shutdown endpoint:', err);
|
||||||
|
if (window.showNotification) {
|
||||||
|
window.showNotification('Failed to initiate shutdown: ' + err.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Make functions globally accessible
|
// Make functions globally accessible
|
||||||
window.connectWebSocket = connectWebSocket;
|
window.connectWebSocket = connectWebSocket;
|
||||||
window.startPollingFallback = startPollingFallback;
|
window.startPollingFallback = startPollingFallback;
|
||||||
@@ -322,6 +382,7 @@ window.startStatusUpdates = startStatusUpdates;
|
|||||||
window.stopStatusUpdates = stopStatusUpdates;
|
window.stopStatusUpdates = stopStatusUpdates;
|
||||||
window.startDomainsUpdates = startDomainsUpdates;
|
window.startDomainsUpdates = startDomainsUpdates;
|
||||||
window.stopDomainsUpdates = stopDomainsUpdates;
|
window.stopDomainsUpdates = stopDomainsUpdates;
|
||||||
|
window.handleRefresh = handleRefresh;
|
||||||
|
|
||||||
// Initialize WebSocket connection
|
// Initialize WebSocket connection
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
|
|||||||
Reference in New Issue
Block a user