This commit is contained in:
Slipstream 2025-05-04 14:09:24 -06:00
parent 427043f334
commit af57514549
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
4 changed files with 330 additions and 263 deletions

View File

@ -423,6 +423,22 @@ except ImportError as e:
log.error(f"Could not import command customization endpoints: {e}")
log.error("Command customization endpoints will not be available")
# Import cog management endpoints
try:
# Try relative import first
try:
from .cog_management_endpoints import router as cog_management_router
except ImportError:
# Fall back to absolute import
from cog_management_endpoints import router as cog_management_router
# Add the cog management router to the dashboard API app
dashboard_api_app.include_router(cog_management_router, tags=["Cog Management"])
log.info("Cog management endpoints loaded successfully")
except ImportError as e:
log.error(f"Could not import cog management endpoints: {e}")
log.error("Cog management endpoints will not be available")
# Mount the API apps at their respective paths
app.mount("/api", api_app)
app.mount("/discordapi", discordapi_app)

View File

@ -0,0 +1,244 @@
"""
Cog Management API endpoints for the bot dashboard.
These endpoints provide functionality for managing cogs and commands.
"""
import logging
from typing import List, Dict, Optional, Any
from fastapi import APIRouter, Depends, HTTPException, status, Body
from pydantic import BaseModel, Field
# Import the dependencies from api_server.py
try:
# Try relative import first
from .api_server import (
get_dashboard_user,
verify_dashboard_guild_admin
)
except ImportError:
# Fall back to absolute import
from api_server import (
get_dashboard_user,
verify_dashboard_guild_admin
)
# Import settings_manager for database access
try:
from discordbot import settings_manager
except ImportError:
# Try relative import
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
from discordbot import settings_manager
# Set up logging
log = logging.getLogger(__name__)
# Create a router for the cog management API endpoints
router = APIRouter(tags=["Cog Management"])
# --- Models ---
class CommandInfo(BaseModel):
name: str
description: Optional[str] = None
enabled: bool = True
class CogInfo(BaseModel):
name: str
description: Optional[str] = None
enabled: bool = True
commands: List[Dict[str, Any]] = []
# --- Endpoints ---
@router.get("/guilds/{guild_id}/cogs", response_model=List[CogInfo])
async def get_guild_cogs(
guild_id: int,
_user: dict = Depends(get_dashboard_user),
_admin: bool = Depends(verify_dashboard_guild_admin)
):
"""Get all cogs and their commands for a guild."""
try:
# Check if bot instance is available via discord_bot_sync_api
try:
from discordbot import discord_bot_sync_api
bot = discord_bot_sync_api.bot_instance
if not bot:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Bot instance not available"
)
except ImportError:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Bot sync API not available"
)
# Get all cogs from the bot
cogs_list = []
for cog_name, cog in bot.cogs.items():
# Get enabled status from settings_manager
is_enabled = await settings_manager.is_cog_enabled(guild_id, cog_name, default_enabled=True)
# Get commands for this cog
commands_list = []
for command in cog.get_commands():
# Get command enabled status
cmd_enabled = await settings_manager.is_command_enabled(guild_id, command.qualified_name, default_enabled=True)
commands_list.append({
"name": command.qualified_name,
"description": command.help or "No description available",
"enabled": cmd_enabled
})
# Add slash commands if any
app_commands = [cmd for cmd in bot.tree.get_commands() if hasattr(cmd, 'cog') and cmd.cog and cmd.cog.qualified_name == cog_name]
for cmd in app_commands:
# Get command enabled status
cmd_enabled = await settings_manager.is_command_enabled(guild_id, cmd.name, default_enabled=True)
if not any(c["name"] == cmd.name for c in commands_list): # Avoid duplicates
commands_list.append({
"name": cmd.name,
"description": cmd.description or "No description available",
"enabled": cmd_enabled
})
cogs_list.append(CogInfo(
name=cog_name,
description=cog.__doc__ or "No description available",
enabled=is_enabled,
commands=commands_list
))
return cogs_list
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
log.error(f"Error getting cogs for guild {guild_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error getting cogs: {str(e)}"
)
@router.patch("/guilds/{guild_id}/cogs/{cog_name}", status_code=status.HTTP_200_OK)
async def update_cog_status(
guild_id: int,
cog_name: str,
enabled: bool = Body(..., embed=True),
_user: dict = Depends(get_dashboard_user),
_admin: bool = Depends(verify_dashboard_guild_admin)
):
"""Enable or disable a cog for a guild."""
try:
# Check if settings_manager is available
if not settings_manager or not settings_manager.pg_pool:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Settings manager not available"
)
# Check if the cog exists
try:
from discordbot import discord_bot_sync_api
bot = discord_bot_sync_api.bot_instance
if not bot:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Bot instance not available"
)
if cog_name not in bot.cogs:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Cog '{cog_name}' not found"
)
# Check if it's a core cog
if cog_name in bot.core_cogs:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Core cog '{cog_name}' cannot be disabled"
)
except ImportError:
# If we can't import the bot, we'll just assume the cog exists
log.warning("Bot sync API not available, skipping cog existence check")
# Update the cog enabled status
success = await settings_manager.set_cog_enabled(guild_id, cog_name, enabled)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to update cog '{cog_name}' status"
)
return {"message": f"Cog '{cog_name}' {'enabled' if enabled else 'disabled'} successfully"}
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
log.error(f"Error updating cog status for guild {guild_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error updating cog status: {str(e)}"
)
@router.patch("/guilds/{guild_id}/commands/{command_name}", status_code=status.HTTP_200_OK)
async def update_command_status(
guild_id: int,
command_name: str,
enabled: bool = Body(..., embed=True),
_user: dict = Depends(get_dashboard_user),
_admin: bool = Depends(verify_dashboard_guild_admin)
):
"""Enable or disable a command for a guild."""
try:
# Check if settings_manager is available
if not settings_manager or not settings_manager.pg_pool:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Settings manager not available"
)
# Check if the command exists
try:
from discordbot import discord_bot_sync_api
bot = discord_bot_sync_api.bot_instance
if not bot:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Bot instance not available"
)
# Check if it's a prefix command
command = bot.get_command(command_name)
if not command:
# Check if it's an app command
app_commands = [cmd for cmd in bot.tree.get_commands() if cmd.name == command_name]
if not app_commands:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Command '{command_name}' not found"
)
except ImportError:
# If we can't import the bot, we'll just assume the command exists
log.warning("Bot sync API not available, skipping command existence check")
# Update the command enabled status
success = await settings_manager.set_command_enabled(guild_id, command_name, enabled)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to update command '{command_name}' status"
)
return {"message": f"Command '{command_name}' {'enabled' if enabled else 'disabled'} successfully"}
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
log.error(f"Error updating command status for guild {guild_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error updating command status: {str(e)}"
)

View File

@ -99,11 +99,7 @@ class GlobalSettings(BaseModel):
max_tokens: Optional[int] = None
theme: Optional[ThemeSettings] = None
class CogInfo(BaseModel):
name: str
description: Optional[str] = None
enabled: bool = True
commands: List[Dict[str, Any]] = []
# CogInfo model moved to cog_management_endpoints.py
class CommandInfo(BaseModel):
name: str
@ -826,198 +822,7 @@ async def update_global_settings(
)
# --- Cog and Command Management Endpoints ---
@router.get("/guilds/{guild_id}/cogs", response_model=List[CogInfo])
async def get_guild_cogs(
guild_id: int,
_user: dict = Depends(get_dashboard_user),
_admin: bool = Depends(verify_dashboard_guild_admin)
):
"""Get all cogs and their commands for a guild."""
try:
# Check if bot instance is available via discord_bot_sync_api
try:
from discordbot import discord_bot_sync_api
bot = discord_bot_sync_api.bot_instance
if not bot:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Bot instance not available"
)
except ImportError:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Bot sync API not available"
)
# Get all cogs from the bot
cogs_list = []
for cog_name, cog in bot.cogs.items():
# Get enabled status from settings_manager
is_enabled = await settings_manager.is_cog_enabled(guild_id, cog_name, default_enabled=True)
# Get commands for this cog
commands_list = []
for command in cog.get_commands():
# Get command enabled status
cmd_enabled = await settings_manager.is_command_enabled(guild_id, command.qualified_name, default_enabled=True)
commands_list.append({
"name": command.qualified_name,
"description": command.help or "No description available",
"enabled": cmd_enabled
})
# Add slash commands if any
app_commands = [cmd for cmd in bot.tree.get_commands() if hasattr(cmd, 'cog') and cmd.cog and cmd.cog.qualified_name == cog_name]
for cmd in app_commands:
# Get command enabled status
cmd_enabled = await settings_manager.is_command_enabled(guild_id, cmd.name, default_enabled=True)
if not any(c["name"] == cmd.name for c in commands_list): # Avoid duplicates
commands_list.append({
"name": cmd.name,
"description": cmd.description or "No description available",
"enabled": cmd_enabled
})
cogs_list.append(CogInfo(
name=cog_name,
description=cog.__doc__ or "No description available",
enabled=is_enabled,
commands=commands_list
))
return cogs_list
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
log.error(f"Error getting cogs for guild {guild_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error getting cogs: {str(e)}"
)
@router.patch("/guilds/{guild_id}/cogs/{cog_name}", status_code=status.HTTP_200_OK)
async def update_cog_status(
guild_id: int,
cog_name: str,
enabled: bool = Body(..., embed=True),
_user: dict = Depends(get_dashboard_user),
_admin: bool = Depends(verify_dashboard_guild_admin)
):
"""Enable or disable a cog for a guild."""
try:
# Check if settings_manager is available
if not settings_manager or not settings_manager.pg_pool:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Settings manager not available"
)
# Check if the cog exists
try:
from discordbot import discord_bot_sync_api
bot = discord_bot_sync_api.bot_instance
if not bot:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Bot instance not available"
)
if cog_name not in bot.cogs:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Cog '{cog_name}' not found"
)
# Check if it's a core cog
if cog_name in bot.core_cogs:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Core cog '{cog_name}' cannot be disabled"
)
except ImportError:
# If we can't import the bot, we'll just assume the cog exists
log.warning("Bot sync API not available, skipping cog existence check")
# Update the cog enabled status
success = await settings_manager.set_cog_enabled(guild_id, cog_name, enabled)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to update cog '{cog_name}' status"
)
return {"message": f"Cog '{cog_name}' {'enabled' if enabled else 'disabled'} successfully"}
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
log.error(f"Error updating cog status for guild {guild_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error updating cog status: {str(e)}"
)
@router.patch("/guilds/{guild_id}/commands/{command_name}", status_code=status.HTTP_200_OK)
async def update_command_status(
guild_id: int,
command_name: str,
enabled: bool = Body(..., embed=True),
_user: dict = Depends(get_dashboard_user),
_admin: bool = Depends(verify_dashboard_guild_admin)
):
"""Enable or disable a command for a guild."""
try:
# Check if settings_manager is available
if not settings_manager or not settings_manager.pg_pool:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Settings manager not available"
)
# Check if the command exists
try:
from discordbot import discord_bot_sync_api
bot = discord_bot_sync_api.bot_instance
if not bot:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Bot instance not available"
)
# Check if it's a prefix command
command = bot.get_command(command_name)
if not command:
# Check if it's an app command
app_commands = [cmd for cmd in bot.tree.get_commands() if cmd.name == command_name]
if not app_commands:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Command '{command_name}' not found"
)
except ImportError:
# If we can't import the bot, we'll just assume the command exists
log.warning("Bot sync API not available, skipping command existence check")
# Update the command enabled status
success = await settings_manager.set_command_enabled(guild_id, command_name, enabled)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to update command '{command_name}' status"
)
return {"message": f"Command '{command_name}' {'enabled' if enabled else 'disabled'} successfully"}
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
log.error(f"Error updating command status for guild {guild_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error updating command status: {str(e)}"
)
# Note: These endpoints have been moved to cog_management_endpoints.py
# --- Conversations Endpoints ---

View File

@ -30,7 +30,7 @@ function initCogManagement() {
navCogManagement.addEventListener('click', () => {
// Show cog management section
showSection('cog-management');
// Load guilds if not already loaded
if (!cogManagementLoaded) {
loadGuildsForCogManagement();
@ -79,17 +79,17 @@ function initCogManagement() {
*/
function loadGuildsForCogManagement() {
const cogGuildSelect = document.getElementById('cog-guild-select');
// Show loading state
cogGuildSelect.disabled = true;
cogGuildSelect.innerHTML = '<option value="">Loading servers...</option>';
// Fetch guilds from API
API.get('/dashboard/api/guilds')
.then(guilds => {
// Clear loading state
cogGuildSelect.innerHTML = '<option value="">--Please choose a server--</option>';
// Add guilds to select
guilds.forEach(guild => {
const option = document.createElement('option');
@ -97,7 +97,7 @@ function loadGuildsForCogManagement() {
option.textContent = guild.name;
cogGuildSelect.appendChild(option);
});
// Enable select
cogGuildSelect.disabled = false;
})
@ -117,19 +117,21 @@ function loadCogsAndCommands(guildId) {
// Show loading state
document.getElementById('cog-management-loading').style.display = 'flex';
document.getElementById('cog-management-content').style.display = 'none';
// Fetch cogs and commands from API
console.log(`Loading cogs and commands for guild ${guildId}...`);
API.get(`/dashboard/api/guilds/${guildId}/cogs`)
.then(data => {
console.log('Cogs and commands loaded successfully:', data);
// Store data
cogsData = data;
// Populate cogs list
populateCogsUI(data);
// Populate commands list
populateCommandsUI(data);
// Hide loading state
document.getElementById('cog-management-loading').style.display = 'none';
document.getElementById('cog-management-content').style.display = 'block';
@ -148,68 +150,68 @@ function loadCogsAndCommands(guildId) {
function populateCogsUI(cogs) {
const cogsList = document.getElementById('cogs-list');
const cogFilter = document.getElementById('cog-filter');
// Clear previous content
cogsList.innerHTML = '';
// Clear filter options except "All Cogs"
cogFilter.innerHTML = '<option value="all">All Cogs</option>';
// Add cogs to list
cogs.forEach(cog => {
// Create cog card
const cogCard = document.createElement('div');
cogCard.className = 'cog-card p-4 border rounded';
// Create cog header
const cogHeader = document.createElement('div');
cogHeader.className = 'cog-header flex items-center justify-between mb-2';
// Create cog checkbox
const cogCheckbox = document.createElement('div');
cogCheckbox.className = 'cog-checkbox flex items-center';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = `cog-${cog.name}`;
checkbox.className = 'mr-2';
checkbox.checked = cog.enabled;
checkbox.dataset.cogName = cog.name;
// Disable checkbox for core cogs
if (cog.name === 'SettingsCog' || cog.name === 'HelpCog') {
checkbox.disabled = true;
checkbox.title = 'Core cogs cannot be disabled';
}
const label = document.createElement('label');
label.htmlFor = `cog-${cog.name}`;
label.textContent = cog.name;
label.className = 'font-medium';
cogCheckbox.appendChild(checkbox);
cogCheckbox.appendChild(label);
// Create command count badge
const commandCount = document.createElement('span');
commandCount.className = 'command-count bg-gray-200 text-gray-800 px-2 py-1 rounded text-xs';
commandCount.textContent = `${cog.commands.length} commands`;
cogHeader.appendChild(cogCheckbox);
cogHeader.appendChild(commandCount);
// Create cog description
const cogDescription = document.createElement('p');
cogDescription.className = 'cog-description text-sm text-gray-600 mt-1';
cogDescription.textContent = cog.description || 'No description available';
// Add elements to cog card
cogCard.appendChild(cogHeader);
cogCard.appendChild(cogDescription);
// Add cog card to list
cogsList.appendChild(cogCard);
// Add cog to filter options
const option = document.createElement('option');
option.value = cog.name;
@ -224,13 +226,13 @@ function populateCogsUI(cogs) {
*/
function populateCommandsUI(cogs) {
const commandsList = document.getElementById('commands-list');
// Clear previous content
commandsList.innerHTML = '';
// Create a flat list of all commands with their cog
commandsData = {};
cogs.forEach(cog => {
cog.commands.forEach(command => {
// Store command data with cog name
@ -238,10 +240,10 @@ function populateCommandsUI(cogs) {
...command,
cog_name: cog.name
};
// Create command card
const commandCard = createCommandCard(command, cog.name);
// Add command card to list
commandsList.appendChild(commandCard);
});
@ -259,47 +261,47 @@ function createCommandCard(command, cogName) {
const commandCard = document.createElement('div');
commandCard.className = 'command-card p-4 border rounded';
commandCard.dataset.cogName = cogName;
// Create command header
const commandHeader = document.createElement('div');
commandHeader.className = 'command-header flex items-center justify-between mb-2';
// Create command checkbox
const commandCheckbox = document.createElement('div');
commandCheckbox.className = 'command-checkbox flex items-center';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = `command-${command.name}`;
checkbox.className = 'mr-2';
checkbox.checked = command.enabled;
checkbox.dataset.commandName = command.name;
const label = document.createElement('label');
label.htmlFor = `command-${command.name}`;
label.textContent = command.name;
label.className = 'font-medium';
commandCheckbox.appendChild(checkbox);
commandCheckbox.appendChild(label);
// Create cog badge
const cogBadge = document.createElement('span');
cogBadge.className = 'cog-badge bg-blue-100 text-blue-800 px-2 py-1 rounded text-xs';
cogBadge.textContent = cogName;
commandHeader.appendChild(commandCheckbox);
commandHeader.appendChild(cogBadge);
// Create command description
const commandDescription = document.createElement('p');
commandDescription.className = 'command-description text-sm text-gray-600 mt-1';
commandDescription.textContent = command.description || 'No description available';
// Add elements to command card
commandCard.appendChild(commandHeader);
commandCard.appendChild(commandDescription);
return commandCard;
}
@ -309,7 +311,7 @@ function createCommandCard(command, cogName) {
*/
function filterCommands(cogName) {
const commandCards = document.querySelectorAll('.command-card');
commandCards.forEach(card => {
if (cogName === 'all' || card.dataset.cogName === cogName) {
card.style.display = 'block';
@ -324,22 +326,22 @@ function filterCommands(cogName) {
*/
function saveCogsSettings() {
if (!selectedGuildId) return;
// Show loading state
const saveButton = document.getElementById('save-cogs-button');
saveButton.disabled = true;
saveButton.textContent = 'Saving...';
// Get cog settings
const cogsPayload = {};
const cogCheckboxes = document.querySelectorAll('#cogs-list input[type="checkbox"]');
cogCheckboxes.forEach(checkbox => {
if (!checkbox.disabled) {
cogsPayload[checkbox.dataset.cogName] = checkbox.checked;
}
});
// Send request to API
API.patch(`/dashboard/api/guilds/${selectedGuildId}/settings`, {
cogs: cogsPayload
@ -348,30 +350,30 @@ function saveCogsSettings() {
// Reset button state
saveButton.disabled = false;
saveButton.textContent = 'Save Cog Settings';
// Show success message
document.getElementById('cogs-feedback').textContent = 'Cog settings saved successfully!';
document.getElementById('cogs-feedback').className = 'mt-2 text-green-600';
// Clear message after 3 seconds
setTimeout(() => {
document.getElementById('cogs-feedback').textContent = '';
document.getElementById('cogs-feedback').className = 'mt-2';
}, 3000);
Toast.success('Cog settings saved successfully!');
})
.catch(error => {
console.error('Error saving cog settings:', error);
// Reset button state
saveButton.disabled = false;
saveButton.textContent = 'Save Cog Settings';
// Show error message
document.getElementById('cogs-feedback').textContent = 'Error saving cog settings. Please try again.';
document.getElementById('cogs-feedback').className = 'mt-2 text-red-600';
Toast.error('Failed to save cog settings. Please try again.');
});
}
@ -381,20 +383,20 @@ function saveCogsSettings() {
*/
function saveCommandsSettings() {
if (!selectedGuildId) return;
// Show loading state
const saveButton = document.getElementById('save-commands-button');
saveButton.disabled = true;
saveButton.textContent = 'Saving...';
// Get command settings
const commandsPayload = {};
const commandCheckboxes = document.querySelectorAll('#commands-list input[type="checkbox"]');
commandCheckboxes.forEach(checkbox => {
commandsPayload[checkbox.dataset.commandName] = checkbox.checked;
});
// Send request to API
API.patch(`/dashboard/api/guilds/${selectedGuildId}/settings`, {
commands: commandsPayload
@ -403,30 +405,30 @@ function saveCommandsSettings() {
// Reset button state
saveButton.disabled = false;
saveButton.textContent = 'Save Command Settings';
// Show success message
document.getElementById('commands-feedback').textContent = 'Command settings saved successfully!';
document.getElementById('commands-feedback').className = 'mt-2 text-green-600';
// Clear message after 3 seconds
setTimeout(() => {
document.getElementById('commands-feedback').textContent = '';
document.getElementById('commands-feedback').className = 'mt-2';
}, 3000);
Toast.success('Command settings saved successfully!');
})
.catch(error => {
console.error('Error saving command settings:', error);
// Reset button state
saveButton.disabled = false;
saveButton.textContent = 'Save Command Settings';
// Show error message
document.getElementById('commands-feedback').textContent = 'Error saving command settings. Please try again.';
document.getElementById('commands-feedback').className = 'mt-2 text-red-600';
Toast.error('Failed to save command settings. Please try again.');
});
}
@ -438,28 +440,28 @@ function saveCommandsSettings() {
function showSection(sectionId) {
// Get all sections
const sections = document.querySelectorAll('.dashboard-section');
// Hide all sections
sections.forEach(section => {
section.style.display = 'none';
});
// Get all nav buttons
const navButtons = document.querySelectorAll('.nav-button');
// Remove active class from all nav buttons
navButtons.forEach(button => {
button.classList.remove('active');
});
// Show the selected section and activate the corresponding nav button
const selectedSection = document.getElementById(`${sectionId}-section`);
const selectedNavButton = document.getElementById(`nav-${sectionId}`);
if (selectedSection) {
selectedSection.style.display = 'block';
}
if (selectedNavButton) {
selectedNavButton.classList.add('active');
}