main.js•30.9 kB
/**
* Main JavaScript for the Sectional MCP Panel UI
*/
// API base URL
const API_BASE_URL = '/api/v1';
// DOM elements
const dashboardLink = document.getElementById('dashboard-link');
const sectionsLink = document.getElementById('sections-link');
const serversLink = document.getElementById('servers-link');
const tasksLink = document.getElementById('tasks-link');
const settingsLink = document.getElementById('settings-link');
const pageTitle = document.getElementById('page-title');
const refreshBtn = document.getElementById('refresh-btn');
const addSectionBtn = document.getElementById('add-section-btn');
const dashboardContent = document.getElementById('dashboard-content');
const sectionsContent = document.getElementById('sections-content');
const serversContent = document.getElementById('servers-content');
const tasksContent = document.getElementById('tasks-content');
const settingsContent = document.getElementById('settings-content');
const sectionsContainer = document.getElementById('sections-container');
const sectionsTableBody = document.getElementById('sections-table-body');
const serversTableBody = document.getElementById('servers-table-body');
const tasksTableBody = document.getElementById('tasks-table-body');
// Modal elements
const addSectionModal = new bootstrap.Modal(document.getElementById('add-section-modal'));
const addServerModal = new bootstrap.Modal(document.getElementById('add-server-modal'));
const addSectionSubmit = document.getElementById('add-section-submit');
const addServerSubmit = document.getElementById('add-server-submit');
// Panel settings form
const panelSettingsForm = document.getElementById('panel-settings-form');
const panelNameInput = document.getElementById('panel-name');
const panelVersionInput = document.getElementById('panel-version');
const globalSettingsInput = document.getElementById('global-settings');
// Add server form
const serverSectionSelect = document.getElementById('server-section');
// Event listeners
document.addEventListener('DOMContentLoaded', () => {
// Initialize the dashboard
loadDashboard();
// Navigation event listeners
dashboardLink.addEventListener('click', (e) => {
e.preventDefault();
showContent('dashboard');
});
sectionsLink.addEventListener('click', (e) => {
e.preventDefault();
showContent('sections');
loadSections();
});
serversLink.addEventListener('click', (e) => {
e.preventDefault();
showContent('servers');
loadServers();
});
tasksLink.addEventListener('click', (e) => {
e.preventDefault();
showContent('tasks');
loadTasks();
});
settingsLink.addEventListener('click', (e) => {
e.preventDefault();
showContent('settings');
loadPanelSettings();
});
// Refresh button
refreshBtn.addEventListener('click', () => {
const currentContent = document.querySelector('main > div[style="display: block;"]') || dashboardContent;
if (currentContent === dashboardContent) {
loadDashboard();
} else if (currentContent === sectionsContent) {
loadSections();
} else if (currentContent === serversContent) {
loadServers();
} else if (currentContent === tasksContent) {
loadTasks();
} else if (currentContent === settingsContent) {
loadPanelSettings();
}
});
// Add section button
addSectionBtn.addEventListener('click', () => {
addSectionModal.show();
});
// Add section form submission
addSectionSubmit.addEventListener('click', () => {
const sectionName = document.getElementById('section-name').value;
const sectionDescription = document.getElementById('section-description').value;
let sectionSettings = document.getElementById('section-settings').value;
if (!sectionName) {
alert('Section name is required');
return;
}
try {
sectionSettings = sectionSettings ? JSON.parse(sectionSettings) : {};
} catch (e) {
alert('Invalid JSON in section settings');
return;
}
createSection({
name: sectionName,
description: sectionDescription,
settings: sectionSettings
});
});
// Add server form submission
addServerSubmit.addEventListener('click', () => {
const serverName = document.getElementById('server-name').value;
const sectionId = parseInt(document.getElementById('server-section').value);
const serverDescription = document.getElementById('server-description').value;
const runtimeType = document.getElementById('server-runtime-type').value;
const command = document.getElementById('server-command').value;
const args = document.getElementById('server-args').value.split(' ').filter(arg => arg.trim());
const workingDir = document.getElementById('server-working-dir').value;
const portsStr = document.getElementById('server-ports').value;
let serverSettings = document.getElementById('server-settings').value;
if (!serverName || !sectionId || !runtimeType || !command) {
alert('Server name, section, runtime type, and command are required');
return;
}
try {
serverSettings = serverSettings ? JSON.parse(serverSettings) : {};
} catch (e) {
alert('Invalid JSON in server settings');
return;
}
// Parse ports
const ports = [];
if (portsStr) {
const portPairs = portsStr.split(',');
for (const pair of portPairs) {
const [port, protocol = 'TCP'] = pair.split(':');
if (port && !isNaN(parseInt(port))) {
ports.push({
containerPort: parseInt(port),
protocol: protocol.toUpperCase()
});
}
}
}
// Create runtime definition
const runtimeDefinition = {
type: runtimeType,
command: command,
args: args,
workingDirectory: workingDir || null,
ports: ports
};
createServer({
name: serverName,
section_id: sectionId,
description: serverDescription,
runtime_definition: runtimeDefinition,
settings: serverSettings
});
});
// Panel settings form submission
panelSettingsForm.addEventListener('submit', (e) => {
e.preventDefault();
const panelName = panelNameInput.value;
let globalSettings;
try {
globalSettings = JSON.parse(globalSettingsInput.value);
} catch (e) {
alert('Invalid JSON in global settings');
return;
}
updatePanelSettings({
name: panelName,
global_settings: globalSettings
});
});
});
// Show the selected content and hide others
function showContent(contentType) {
// Hide all content
dashboardContent.style.display = 'none';
sectionsContent.style.display = 'none';
serversContent.style.display = 'none';
tasksContent.style.display = 'none';
settingsContent.style.display = 'none';
// Remove active class from all links
dashboardLink.classList.remove('active');
sectionsLink.classList.remove('active');
serversLink.classList.remove('active');
tasksLink.classList.remove('active');
settingsLink.classList.remove('active');
// Show selected content and set active link
switch (contentType) {
case 'dashboard':
dashboardContent.style.display = 'block';
dashboardLink.classList.add('active');
pageTitle.textContent = 'Dashboard';
break;
case 'sections':
sectionsContent.style.display = 'block';
sectionsLink.classList.add('active');
pageTitle.textContent = 'Sections';
break;
case 'servers':
serversContent.style.display = 'block';
serversLink.classList.add('active');
pageTitle.textContent = 'Servers';
break;
case 'tasks':
tasksContent.style.display = 'block';
tasksLink.classList.add('active');
pageTitle.textContent = 'Tasks';
break;
case 'settings':
settingsContent.style.display = 'block';
settingsLink.classList.add('active');
pageTitle.textContent = 'Settings';
break;
}
}
// Load dashboard data
async function loadDashboard() {
try {
// Show loading indicator
sectionsContainer.innerHTML = '<div class="alert alert-info">Loading sections and servers...</div>';
// Load sections
const sections = await fetchAPI('/sections');
if (sections.length === 0) {
sectionsContainer.innerHTML = `
<div class="alert alert-warning">
No sections found. Click "Add Section" to create your first section.
</div>
`;
return;
}
// Clear container
sectionsContainer.innerHTML = '';
// Process each section
for (const section of sections) {
// Get servers in this section
const sectionDetails = await fetchAPI(`/sections/${section.id}`);
const servers = sectionDetails.servers || [];
// Create section card
const sectionCard = document.createElement('div');
sectionCard.className = 'card section-card mb-4';
sectionCard.innerHTML = `
<div class="card-header d-flex justify-content-between align-items-center">
<h5>${section.name}</h5>
<div class="action-buttons">
<button class="btn btn-sm btn-success section-start-btn" data-section-id="${section.id}">Start All</button>
<button class="btn btn-sm btn-danger section-stop-btn" data-section-id="${section.id}">Stop All</button>
<button class="btn btn-sm btn-primary section-restart-btn" data-section-id="${section.id}">Restart All</button>
<button class="btn btn-sm btn-outline-primary add-server-btn" data-section-id="${section.id}">Add Server</button>
</div>
</div>
<div class="card-body">
<p>${section.description || 'No description'}</p>
<div class="servers-container" id="section-${section.id}-servers">
${servers.length === 0 ? '<div class="alert alert-warning">No servers in this section</div>' : ''}
</div>
</div>
`;
sectionsContainer.appendChild(sectionCard);
// Add servers to the section
const serversContainer = document.getElementById(`section-${section.id}-servers`);
if (servers.length > 0) {
for (const server of servers) {
// Get full server details
const serverDetails = await fetchAPI(`/servers/${server.id}`);
// Create server card
const serverCard = document.createElement('div');
serverCard.className = `card server-card server-${serverDetails.status.toLowerCase()}`;
serverCard.innerHTML = `
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6>${serverDetails.name}</h6>
<span class="badge status-badge-${serverDetails.status.toLowerCase()}">${serverDetails.status}</span>
<small class="text-muted">${serverDetails.description || ''}</small>
</div>
<div class="action-buttons">
<button class="btn btn-sm btn-success server-start-btn" data-server-id="${serverDetails.id}" ${serverDetails.status === 'Running' ? 'disabled' : ''}>Start</button>
<button class="btn btn-sm btn-danger server-stop-btn" data-server-id="${serverDetails.id}" ${serverDetails.status === 'Stopped' ? 'disabled' : ''}>Stop</button>
<button class="btn btn-sm btn-primary server-restart-btn" data-server-id="${serverDetails.id}">Restart</button>
</div>
</div>
`;
serversContainer.appendChild(serverCard);
// Add event listeners for server actions
const startBtn = serverCard.querySelector('.server-start-btn');
const stopBtn = serverCard.querySelector('.server-stop-btn');
const restartBtn = serverCard.querySelector('.server-restart-btn');
startBtn.addEventListener('click', () => startServer(serverDetails.id));
stopBtn.addEventListener('click', () => stopServer(serverDetails.id));
restartBtn.addEventListener('click', () => restartServer(serverDetails.id));
}
}
// Add event listeners for section actions
const startAllBtn = sectionCard.querySelector('.section-start-btn');
const stopAllBtn = sectionCard.querySelector('.section-stop-btn');
const restartAllBtn = sectionCard.querySelector('.section-restart-btn');
const addServerBtn = sectionCard.querySelector('.add-server-btn');
startAllBtn.addEventListener('click', () => startSection(section.id));
stopAllBtn.addEventListener('click', () => stopSection(section.id));
restartAllBtn.addEventListener('click', () => restartSection(section.id));
addServerBtn.addEventListener('click', () => showAddServerModal(section.id));
}
} catch (error) {
console.error('Error loading dashboard:', error);
sectionsContainer.innerHTML = `
<div class="alert alert-danger">
Error loading dashboard: ${error.message}
</div>
`;
}
}
// Load sections data for the sections table
async function loadSections() {
try {
// Show loading indicator
sectionsTableBody.innerHTML = '<tr><td colspan="5" class="text-center">Loading sections...</td></tr>';
// Load sections
const sections = await fetchAPI('/sections');
if (sections.length === 0) {
sectionsTableBody.innerHTML = '<tr><td colspan="5" class="text-center">No sections found</td></tr>';
return;
}
// Clear table
sectionsTableBody.innerHTML = '';
// Process each section
for (const section of sections) {
// Get servers count
const sectionDetails = await fetchAPI(`/sections/${section.id}`);
const serversCount = sectionDetails.servers ? sectionDetails.servers.length : 0;
// Create table row
const row = document.createElement('tr');
row.innerHTML = `
<td>${section.id}</td>
<td>${section.name}</td>
<td>${section.description || 'No description'}</td>
<td>${serversCount}</td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary section-edit-btn" data-section-id="${section.id}">Edit</button>
<button class="btn btn-outline-danger section-delete-btn" data-section-id="${section.id}" ${serversCount > 0 ? 'disabled' : ''}>Delete</button>
</div>
</td>
`;
sectionsTableBody.appendChild(row);
// Add event listeners
const editBtn = row.querySelector('.section-edit-btn');
const deleteBtn = row.querySelector('.section-delete-btn');
editBtn.addEventListener('click', () => editSection(section.id));
deleteBtn.addEventListener('click', () => deleteSection(section.id));
}
} catch (error) {
console.error('Error loading sections:', error);
sectionsTableBody.innerHTML = `
<tr><td colspan="5" class="text-center text-danger">Error loading sections: ${error.message}</td></tr>
`;
}
}
// Load servers data for the servers table
async function loadServers() {
try {
// Show loading indicator
serversTableBody.innerHTML = '<tr><td colspan="6" class="text-center">Loading servers...</td></tr>';
// Load servers
const servers = await fetchAPI('/servers');
if (servers.length === 0) {
serversTableBody.innerHTML = '<tr><td colspan="6" class="text-center">No servers found</td></tr>';
return;
}
// Clear table
serversTableBody.innerHTML = '';
// Process each server
for (const server of servers) {
// Get section name
const section = await fetchAPI(`/sections/${server.section_id}`);
// Create table row
const row = document.createElement('tr');
row.innerHTML = `
<td>${server.id}</td>
<td>${server.name}</td>
<td>${section.name || 'Unknown'}</td>
<td><span class="badge status-badge-${server.status.toLowerCase()}">${server.status}</span></td>
<td>${server.runtime_definition.type}</td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-success server-start-btn" data-server-id="${server.id}" ${server.status === 'Running' ? 'disabled' : ''}>Start</button>
<button class="btn btn-danger server-stop-btn" data-server-id="${server.id}" ${server.status === 'Stopped' ? 'disabled' : ''}>Stop</button>
<button class="btn btn-primary server-restart-btn" data-server-id="${server.id}">Restart</button>
<button class="btn btn-outline-primary server-edit-btn" data-server-id="${server.id}" ${server.status === 'Running' ? 'disabled' : ''}>Edit</button>
<button class="btn btn-outline-danger server-delete-btn" data-server-id="${server.id}" ${server.status === 'Running' ? 'disabled' : ''}>Delete</button>
</div>
</td>
`;
serversTableBody.appendChild(row);
// Add event listeners
const startBtn = row.querySelector('.server-start-btn');
const stopBtn = row.querySelector('.server-stop-btn');
const restartBtn = row.querySelector('.server-restart-btn');
const editBtn = row.querySelector('.server-edit-btn');
const deleteBtn = row.querySelector('.server-delete-btn');
startBtn.addEventListener('click', () => startServer(server.id));
stopBtn.addEventListener('click', () => stopServer(server.id));
restartBtn.addEventListener('click', () => restartServer(server.id));
editBtn.addEventListener('click', () => editServer(server.id));
deleteBtn.addEventListener('click', () => deleteServer(server.id));
}
} catch (error) {
console.error('Error loading servers:', error);
serversTableBody.innerHTML = `
<tr><td colspan="6" class="text-center text-danger">Error loading servers: ${error.message}</td></tr>
`;
}
}
// Load tasks data for the tasks table
async function loadTasks() {
try {
// Show loading indicator
tasksTableBody.innerHTML = '<tr><td colspan="6" class="text-center">Loading tasks...</td></tr>';
// Load tasks
const tasks = await fetchAPI('/tasks');
if (tasks.length === 0) {
tasksTableBody.innerHTML = '<tr><td colspan="6" class="text-center">No tasks found</td></tr>';
return;
}
// Clear table
tasksTableBody.innerHTML = '';
// Process each task
for (const task of tasks) {
// Format dates
const createdAt = new Date(task.created_at).toLocaleString();
const updatedAt = new Date(task.updated_at).toLocaleString();
// Create table row
const row = document.createElement('tr');
row.innerHTML = `
<td>${task.task_id}</td>
<td>${task.task_type}</td>
<td>${task.status}</td>
<td>${createdAt}</td>
<td>${updatedAt}</td>
<td>
<button class="btn btn-sm btn-outline-primary task-details-btn" data-task-id="${task.task_id}">Details</button>
</td>
`;
tasksTableBody.appendChild(row);
// Add event listeners
const detailsBtn = row.querySelector('.task-details-btn');
detailsBtn.addEventListener('click', () => showTaskDetails(task.task_id));
}
} catch (error) {
console.error('Error loading tasks:', error);
tasksTableBody.innerHTML = `
<tr><td colspan="6" class="text-center text-danger">Error loading tasks: ${error.message}</td></tr>
`;
}
}
// Load panel settings
async function loadPanelSettings() {
try {
// Load panel configuration
const panel = await fetchAPI('/panel');
// Populate form
panelNameInput.value = panel.name;
panelVersionInput.value = panel.version;
globalSettingsInput.value = JSON.stringify(panel.global_settings, null, 2);
} catch (error) {
console.error('Error loading panel settings:', error);
alert(`Error loading panel settings: ${error.message}`);
}
}
// Show add server modal
async function showAddServerModal(sectionId = null) {
try {
// Load sections for dropdown
const sections = await fetchAPI('/sections');
// Clear and populate dropdown
serverSectionSelect.innerHTML = '';
for (const section of sections) {
const option = document.createElement('option');
option.value = section.id;
option.textContent = section.name;
if (sectionId && section.id === sectionId) {
option.selected = true;
}
serverSectionSelect.appendChild(option);
}
// Clear form fields
document.getElementById('server-name').value = '';
document.getElementById('server-description').value = '';
document.getElementById('server-runtime-type').value = 'docker_image';
document.getElementById('server-command').value = '';
document.getElementById('server-args').value = '';
document.getElementById('server-working-dir').value = '';
document.getElementById('server-ports').value = '';
document.getElementById('server-settings').value = '';
// Show modal
addServerModal.show();
} catch (error) {
console.error('Error preparing add server modal:', error);
alert(`Error: ${error.message}`);
}
}
// API helper functions
async function fetchAPI(endpoint, options = {}) {
const url = `${API_BASE_URL}${endpoint}`;
const defaultOptions = {
headers: {
'Content-Type': 'application/json',
},
};
const response = await fetch(url, { ...defaultOptions, ...options });
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `API error: ${response.status}`);
}
return response.json();
}
// Section operations
async function createSection(sectionData) {
try {
await fetchAPI('/sections', {
method: 'POST',
body: JSON.stringify(sectionData),
});
// Close modal and refresh
addSectionModal.hide();
loadDashboard();
// Clear form
document.getElementById('section-name').value = '';
document.getElementById('section-description').value = '';
document.getElementById('section-settings').value = '';
} catch (error) {
console.error('Error creating section:', error);
alert(`Error creating section: ${error.message}`);
}
}
async function editSection(sectionId) {
alert('Edit section functionality not implemented yet');
}
async function deleteSection(sectionId) {
if (!confirm('Are you sure you want to delete this section?')) {
return;
}
try {
await fetchAPI(`/sections/${sectionId}`, {
method: 'DELETE',
});
// Refresh sections
loadSections();
} catch (error) {
console.error('Error deleting section:', error);
alert(`Error deleting section: ${error.message}`);
}
}
async function startSection(sectionId) {
try {
const result = await fetchAPI(`/sections/${sectionId}/start`, {
method: 'POST',
body: JSON.stringify({ concurrency: 5 }),
});
alert(`Section start operation initiated. Task ID: ${result.task_id}`);
// Refresh after a delay
setTimeout(() => loadDashboard(), 2000);
} catch (error) {
console.error('Error starting section:', error);
alert(`Error starting section: ${error.message}`);
}
}
async function stopSection(sectionId) {
try {
const result = await fetchAPI(`/sections/${sectionId}/stop`, {
method: 'POST',
body: JSON.stringify({ concurrency: 5 }),
});
alert(`Section stop operation initiated. Task ID: ${result.task_id}`);
// Refresh after a delay
setTimeout(() => loadDashboard(), 2000);
} catch (error) {
console.error('Error stopping section:', error);
alert(`Error stopping section: ${error.message}`);
}
}
async function restartSection(sectionId) {
try {
const result = await fetchAPI(`/sections/${sectionId}/restart`, {
method: 'POST',
body: JSON.stringify({ concurrency: 5 }),
});
alert(`Section restart operation initiated. Task ID: ${result.task_id}`);
// Refresh after a delay
setTimeout(() => loadDashboard(), 2000);
} catch (error) {
console.error('Error restarting section:', error);
alert(`Error restarting section: ${error.message}`);
}
}
// Server operations
async function createServer(serverData) {
try {
await fetchAPI('/servers', {
method: 'POST',
body: JSON.stringify(serverData),
});
// Close modal and refresh
addServerModal.hide();
loadDashboard();
} catch (error) {
console.error('Error creating server:', error);
alert(`Error creating server: ${error.message}`);
}
}
async function editServer(serverId) {
alert('Edit server functionality not implemented yet');
}
async function deleteServer(serverId) {
if (!confirm('Are you sure you want to delete this server?')) {
return;
}
try {
await fetchAPI(`/servers/${serverId}`, {
method: 'DELETE',
});
// Refresh servers
loadServers();
} catch (error) {
console.error('Error deleting server:', error);
alert(`Error deleting server: ${error.message}`);
}
}
async function startServer(serverId) {
try {
const result = await fetchAPI(`/servers/${serverId}/start`, {
method: 'POST',
});
// Refresh the current view
if (dashboardContent.style.display === 'block') {
loadDashboard();
} else if (serversContent.style.display === 'block') {
loadServers();
}
} catch (error) {
console.error('Error starting server:', error);
alert(`Error starting server: ${error.message}`);
}
}
async function stopServer(serverId) {
try {
const result = await fetchAPI(`/servers/${serverId}/stop`, {
method: 'POST',
});
// Refresh the current view
if (dashboardContent.style.display === 'block') {
loadDashboard();
} else if (serversContent.style.display === 'block') {
loadServers();
}
} catch (error) {
console.error('Error stopping server:', error);
alert(`Error stopping server: ${error.message}`);
}
}
async function restartServer(serverId) {
try {
const result = await fetchAPI(`/servers/${serverId}/restart`, {
method: 'POST',
});
// Refresh the current view
if (dashboardContent.style.display === 'block') {
loadDashboard();
} else if (serversContent.style.display === 'block') {
loadServers();
}
} catch (error) {
console.error('Error restarting server:', error);
alert(`Error restarting server: ${error.message}`);
}
}
// Task operations
function showTaskDetails(taskId) {
alert(`Task details for ${taskId} not implemented yet`);
}
// Panel settings operations
async function updatePanelSettings(settingsData) {
try {
await fetchAPI('/panel', {
method: 'PUT',
body: JSON.stringify(settingsData),
});
alert('Panel settings updated successfully');
loadPanelSettings();
} catch (error) {
console.error('Error updating panel settings:', error);
alert(`Error updating panel settings: ${error.message}`);
}
}