SIDEL_ScriptsManager/app/templates/script_group.html

1121 lines
57 KiB
HTML

<!DOCTYPE html>
<html lang="{{ current_user.language if current_user.is_authenticated else 'en' }}"
dir="{{ 'rtl' if current_user.is_authenticated and current_user.language in ['ar', 'he', 'fa'] else 'ltr' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
{% if script_group %}
{{ script_group.display_name }} - ScriptsManager
{% else %}
Script Group - ScriptsManager
{% endif %}
</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="{{ url_for('static', filename='css/main.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/themes.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/responsive.css') }}" rel="stylesheet">
<!-- Socket.IO for real-time updates -->
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
</head>
<body data-authenticated="{{ 'true' if current_user.is_authenticated else 'false' }}"
data-user-id="{{ current_user.id if current_user.is_authenticated else '' }}"
data-current-language="{{ current_user.language if current_user.is_authenticated else 'en' }}">
<!-- Navigation -->
<nav class="navbar navbar-expand-lg" id="main-navbar">
<div class="container-fluid">
<a class="navbar-brand d-flex align-items-center" href="{{ url_for('dashboard') }}">
<img src="{{ url_for('static', filename='images/SIDEL.png') }}"
alt="SIDEL Logo"
class="sidel-logo me-2">
<span data-translate="app.title">ScriptsManager</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('dashboard') }}">
<i class="bi bi-house me-1"></i>
<span data-translate="nav.dashboard">Dashboard</span>
</a>
</li>
{% if script_group %}
<li class="nav-item">
<span class="nav-link active">
<i class="bi bi-folder me-1"></i>
{{ script_group.display_name }}
</span>
</li>
{% endif %}
</ul>
<ul class="navbar-nav">
<!-- Language Selector -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown">
<i class="bi bi-translate me-1"></i>
<span id="current-language-display">{{ current_user.language.upper() if current_user.is_authenticated and current_user.language else 'EN' }}</span>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item language-option" href="#" data-lang="en">🇺🇸 English</a></li>
<li><a class="dropdown-item language-option" href="#" data-lang="es">🇪🇸 Español</a></li>
<li><a class="dropdown-item language-option" href="#" data-lang="it">🇮🇹 Italiano</a></li>
<li><a class="dropdown-item language-option" href="#" data-lang="fr">🇫🇷 Français</a></li>
</ul>
</li>
<!-- Theme Toggle -->
<li class="nav-item">
<button class="btn nav-link" id="theme-toggle" type="button">
<i class="bi bi-moon-fill" id="theme-icon"></i>
</button>
</li>
<!-- Connection Status -->
<li class="nav-item">
<span class="nav-link">
<span id="connection-status" class="connection-status disconnected" title="Connection status"></span>
</span>
</li>
<!-- User Menu -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown">
<i class="bi bi-person-circle me-1"></i>
{{ current_user.username }}
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="#" id="user-settings">
<i class="bi bi-gear me-2"></i>
<span data-translate="nav.settings">Settings</span>
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="{{ url_for('logout') }}">
<i class="bi bi-box-arrow-right me-2"></i>
<span data-translate="nav.logout">Logout</span>
</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<div class="container-fluid py-4">
{% if script_group %}
<!-- Script Group Header -->
<div class="row mb-4">
<div class="col">
<div class="d-flex align-items-center justify-content-between">
<div>
<h1 class="h2 mb-1">{{ script_group.display_name }}</h1>
{% if script_group.get_description(user_language or 'en') %}
<p class="text-muted mb-0">{{ script_group.get_description(user_language or 'en') }}</p>
{% endif %}
</div>
<div class="d-flex gap-2">
<button class="btn btn-outline-primary" id="refresh-scripts">
<i class="bi bi-arrow-clockwise me-1"></i>
<span data-translate="actions.refresh">Refresh</span>
</button>
{% if script_group.conda_env %}
<span class="badge bg-info ms-2">
<i class="bi bi-terminal me-1"></i>
{{ script_group.conda_env }}
</span>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Active Project Section -->
<div class="row mb-4">
<div class="col">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i class="bi bi-folder-open me-2"></i>
Active Project
</h6>
<div>
<button class="btn btn-sm btn-outline-primary" id="change-project-btn">
<i class="bi bi-arrow-repeat"></i> Change Project
</button>
<button class="btn btn-sm btn-success" id="new-project-group-btn">
<i class="bi bi-plus"></i> New Project
</button>
</div>
</div>
<div class="card-body py-2">
<div id="active-project-display">
<div class="d-flex align-items-center">
<div class="spinner-border spinner-border-sm me-2"></div>
<span>Loading project...</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Scripts Grid -->
<div class="row" id="scripts-container">
{% for script in script_group.scripts %}
<div class="col-lg-6 col-xl-4 mb-4">
<div class="card script-card h-100" data-script-id="{{ script.id }}">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">{{ script.display_name }}</h5>
<span class="badge script-status status-{{ script.status or 'idle' }}">
{{ script.status or 'idle' }}
</span>
</div>
<div class="card-body">
{% if script.description %}
<p class="card-text">{{ script.description }}</p>
{% endif %}
<!-- Script Parameters Form -->
{% if script.parameters %}
<form class="script-form mb-3" data-script-id="{{ script.id }}">
{% for param in script.parameters %}
<div class="mb-2">
<label class="form-label small">{{ param.display_name or param.name }}</label>
{% if param.type == 'text' %}
<input type="text" class="form-control form-control-sm"
name="{{ param.name }}"
value="{{ param.default or '' }}"
{% if param.required %}required{% endif %}>
{% elif param.type == 'number' %}
<input type="number" class="form-control form-control-sm"
name="{{ param.name }}"
value="{{ param.default or '' }}"
{% if param.min is defined %}min="{{ param.min }}"{% endif %}
{% if param.max is defined %}max="{{ param.max }}"{% endif %}
{% if param.required %}required{% endif %}>
{% elif param.type == 'select' %}
<select class="form-select form-select-sm"
name="{{ param.name }}"
{% if param.required %}required{% endif %}>
{% for option in param.options %}
<option value="{{ option.value }}"
{% if option.value == param.default %}selected{% endif %}>
{{ option.label }}
</option>
{% endfor %}
</select>
{% elif param.type == 'checkbox' %}
<div class="form-check">
<input type="checkbox" class="form-check-input"
name="{{ param.name }}"
value="true"
{% if param.default %}checked{% endif %}>
<label class="form-check-label">
{{ param.description or param.display_name }}
</label>
</div>
{% endif %}
{% if param.description and param.type != 'checkbox' %}
<small class="form-text text-muted">{{ param.description }}</small>
{% endif %}
</div>
{% endfor %}
</form>
{% endif %}
</div>
<div class="card-footer">
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary btn-sm run-script"
data-script-id="{{ script.id }}">
<i class="bi bi-play-fill me-1"></i>
<span data-translate="actions.run">Run</span>
</button>
<button type="button" class="btn btn-outline-secondary btn-sm view-logs"
data-script-id="{{ script.id }}">
<i class="bi bi-journal-text me-1"></i>
<span data-translate="actions.logs">Logs</span>
</button>
{% if can_edit %}
<button type="button" class="btn btn-outline-warning btn-sm edit-script"
data-script-id="{{ script.id }}">
<i class="bi bi-pencil me-1"></i>
<span data-translate="actions.edit">Edit</span>
</button>
{% endif %}
</div>
{% if script.has_interface %}
<button type="button" class="btn btn-outline-info btn-sm open-interface"
data-script-id="{{ script.id }}">
<i class="bi bi-window me-1"></i>
<span data-translate="actions.interface">Interface</span>
</button>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
{% if not script_group.scripts %}
<div class="col-12">
<div class="alert alert-info text-center" role="alert">
<i class="bi bi-info-circle me-2"></i>
<span data-translate="messages.no_scripts">No scripts found in this group.</span>
</div>
</div>
{% endif %}
</div>
<!-- Log Modal -->
<div class="modal fade" id="logModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="bi bi-journal-text me-2"></i>
<span data-translate="logs.title">Script Logs</span>
<span id="log-script-name" class="text-muted"></span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="script-logs" class="log-container"></div>
</div>
</div>
</div>
</div>
<!-- Edit Script Modal -->
<div class="modal fade" id="editScriptModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="bi bi-pencil me-2"></i>
<span data-translate="actions.edit_script">Edit Script</span>
<span id="edit-script-name" class="text-muted"></span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="editScriptForm">
<input type="hidden" id="editScriptId" name="script_id">
<div class="mb-3">
<label for="editDisplayName" class="form-label">
<span data-translate="script.display_name">Display Name</span>
</label>
<input type="text" class="form-control" id="editDisplayName"
name="display_name" required maxlength="100">
<div class="form-text">
<span data-translate="script.display_name_help">Name shown in the interface</span>
</div>
</div>
<div class="mb-3">
<label for="editDescription" class="form-label">
<span data-translate="script.description">Short Description</span>
</label>
<textarea class="form-control" id="editDescription"
name="description" rows="3" maxlength="500"></textarea>
<div class="form-text">
<span data-translate="script.description_help">Brief description shown on the card</span>
</div>
</div>
<div class="mb-3">
<label for="editDescriptionLong" class="form-label">
<span data-translate="script.description_long">Long Description (Markdown)</span>
<span class="badge bg-secondary ms-2" id="editLanguageBadge">EN</span>
</label>
<textarea class="form-control" id="editDescriptionLong"
name="description_long" rows="8"></textarea>
<div class="form-text">
<span data-translate="script.description_long_help">
Detailed description in Markdown format. Language-specific versions supported.
</span>
</div>
</div>
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle me-2"></i>
<span data-translate="script.markdown_info">
You can use Markdown syntax for formatting. If a description doesn't exist
in the selected language, the English version will be shown as fallback.
</span>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<span data-translate="actions.cancel">Cancel</span>
</button>
<button type="button" class="btn btn-primary" id="saveScriptBtn">
<i class="bi bi-check-lg me-1"></i>
<span data-translate="actions.save">Save Changes</span>
</button>
</div>
</div>
</div>
</div>
{% else %}
<!-- No Script Group Selected -->
<div class="row">
<div class="col-12">
<div class="alert alert-warning text-center" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>
<span data-translate="errors.group_not_found">Script group not found.</span>
<div class="mt-2">
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">
<i class="bi bi-house me-1"></i>
<span data-translate="actions.back_to_dashboard">Back to Dashboard</span>
</a>
</div>
</div>
</div>
</div>
{% endif %}
</div>
<!-- Toast Container -->
<div class="toast-container position-fixed bottom-0 end-0 p-3" id="toast-container"></div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JS -->
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
<script src="{{ url_for('static', filename='js/theme-manager.js') }}"></script>
<script src="{{ url_for('static', filename='js/language-manager.js') }}"></script>
<script src="{{ url_for('static', filename='js/websocket.js') }}"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize script group functionality
const scriptGroup = new ScriptGroupManager();
// Initialize log display for modal
window.scriptLogDisplay = new LogDisplay('script-logs');
// Handle script execution
document.querySelectorAll('.run-script').forEach(button => {
button.addEventListener('click', function() {
const scriptId = this.dataset.scriptId;
const form = document.querySelector(`.script-form[data-script-id="${scriptId}"]`);
const parameters = {};
// Collect form parameters
if (form) {
const formData = new FormData(form);
for (const [key, value] of formData.entries()) {
parameters[key] = value;
}
}
scriptGroup.runScript(scriptId, parameters);
});
});
// Handle log viewing
document.querySelectorAll('.view-logs').forEach(button => {
button.addEventListener('click', function() {
const scriptId = this.dataset.scriptId;
const scriptName = this.closest('.script-card').querySelector('.card-title').textContent;
document.getElementById('log-script-name').textContent = ` - ${scriptName}`;
window.scriptLogDisplay.clear();
// Subscribe to script logs
if (window.webSocketManager) {
window.webSocketManager.subscribeToScript(scriptId);
}
// Load existing logs
scriptGroup.loadScriptLogs(scriptId);
// Show modal
new bootstrap.Modal(document.getElementById('logModal')).show();
});
});
// Handle script editing (admin only)
document.querySelectorAll('.edit-script').forEach(button => {
button.addEventListener('click', function() {
const scriptId = this.dataset.scriptId;
const scriptName = this.closest('.script-card').querySelector('.card-title').textContent;
// Show loading state
const modal = new bootstrap.Modal(document.getElementById('editScriptModal'));
document.getElementById('edit-script-name').textContent = ` - ${scriptName}`;
// Load script data
loadScriptForEditing(scriptId);
modal.show();
});
});
// Handle save script changes
document.getElementById('saveScriptBtn')?.addEventListener('click', function() {
saveScriptChanges();
});
// Handle interface opening
document.querySelectorAll('.open-interface').forEach(button => {
button.addEventListener('click', function() {
const scriptId = this.dataset.scriptId;
scriptGroup.openScriptInterface(scriptId);
});
});
// Handle refresh
document.getElementById('refresh-scripts')?.addEventListener('click', function() {
const button = this;
const originalHTML = button.innerHTML;
button.innerHTML = '<i class="bi bi-arrow-clockwise spin"></i> Refreshing...';
button.disabled = true;
// Get group ID from the current URL or page data
const groupId = {{ script_group.id }};
fetch(`/api/script-groups/${groupId}/refresh`, { method: 'POST' })
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.success) {
window.location.reload(); // Reload page to show updated scripts
} else {
alert('Error refreshing scripts: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error refreshing scripts:', error);
alert('Error refreshing scripts: ' + error.message);
})
.finally(() => {
button.innerHTML = originalHTML;
button.disabled = false;
});
});
});
// Project Management Functions for Script Group Page
function loadActiveProjectForGroup() {
const groupId = {{ script_group.id }};
fetch('/api/projects/active')
.then(response => response.json())
.then(data => {
const projectDisplay = document.getElementById('active-project-display');
if (data.active_project && data.active_project.group_id === groupId) {
const project = data.active_project;
projectDisplay.innerHTML = `
<div class="d-flex justify-content-between align-items-center">
<div>
<strong>${project.project_name}</strong>
${project.description ? `<br><small class="text-muted">${project.description}</small>` : ''}
</div>
<span class="badge bg-success">Active</span>
</div>
`;
} else {
// Load available projects for this group
fetch(`/api/projects?group_id=${groupId}`)
.then(response => response.json())
.then(projects => {
if (projects.length > 0) {
const defaultProject = projects.find(p => p.is_default) || projects[0];
setActiveProject(defaultProject.id, false);
} else {
projectDisplay.innerHTML = `
<div class="text-muted">
<i class="bi bi-info-circle me-2"></i>
No projects found for this group. Create a new project to get started.
</div>
`;
}
});
}
})
.catch(error => {
console.error('Error loading active project:', error);
document.getElementById('active-project-display').innerHTML = `
<div class="text-danger">
<i class="bi bi-exclamation-triangle me-2"></i>
Error loading project information
</div>
`;
});
}
function showChangeProjectModal() {
const groupId = {{ script_group.id }};
const modalHtml = `
<div class="modal fade" id="changeProjectModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Select Project</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="group-projects-list">
<div class="text-center">
<div class="spinner-border" role="status"></div>
<p class="mt-2">Loading projects...</p>
</div>
</div>
</div>
</div>
</div>
</div>
`;
const existingModal = document.getElementById('changeProjectModal');
if (existingModal) {
existingModal.remove();
}
document.body.insertAdjacentHTML('beforeend', modalHtml);
const modal = new bootstrap.Modal(document.getElementById('changeProjectModal'));
modal.show();
// Load projects for this group
fetch(`/api/projects?group_id=${groupId}`)
.then(response => response.json())
.then(projects => {
const projectsList = document.getElementById('group-projects-list');
if (projects.length === 0) {
projectsList.innerHTML = `
<div class="text-center text-muted">
<i class="bi bi-folder-x display-4"></i>
<p class="mt-2">No projects found for this script group</p>
</div>
`;
return;
}
const projectsHtml = projects.map(project => `
<div class="card mb-2">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-1">${project.project_name}</h6>
<small class="text-muted">${project.description || 'No description'}</small>
${project.is_default ? '<br><span class="badge bg-secondary">Default</span>' : ''}
</div>
<button class="btn btn-sm btn-primary"
onclick="setActiveProject(${project.id}, true)">
Select
</button>
</div>
</div>
</div>
`).join('');
projectsList.innerHTML = projectsHtml;
})
.catch(error => {
console.error('Error loading projects:', error);
document.getElementById('group-projects-list').innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i>
Error loading projects
</div>
`;
});
}
function setActiveProject(projectId, closeModal = true) {
fetch(`/api/projects/${projectId}/set-active`, {
method: 'POST'
})
.then(response => response.json())
.then(result => {
if (result.success) {
if (closeModal) {
const modal = bootstrap.Modal.getInstance(document.getElementById('changeProjectModal'));
if (modal) modal.hide();
}
loadActiveProjectForGroup();
ScriptsManager.showNotification('Project activated successfully!', 'success');
} else {
ScriptsManager.showNotification('Error activating project: ' + (result.error || 'Unknown error'), 'error');
}
})
.catch(error => {
console.error('Error setting active project:', error);
ScriptsManager.showNotification('Error activating project: ' + error.message, 'error');
});
}
function showNewProjectForGroupModal() {
const groupId = {{ script_group.id }};
const groupName = "{{ script_group.name }}";
const modalHtml = `
<div class="modal fade" id="newProjectGroupModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create New Project for ${groupName}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="newProjectGroupForm">
<div class="mb-3">
<label for="projectNameGroup" class="form-label">Project Name</label>
<input type="text" class="form-control" id="projectNameGroup" required>
</div>
<div class="mb-3">
<label for="projectDescriptionGroup" class="form-label">Description (Optional)</label>
<textarea class="form-control" id="projectDescriptionGroup" rows="3"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="createProjectForGroup()">Create Project</button>
</div>
</div>
</div>
</div>
`;
const existingModal = document.getElementById('newProjectGroupModal');
if (existingModal) {
existingModal.remove();
}
document.body.insertAdjacentHTML('beforeend', modalHtml);
const modal = new bootstrap.Modal(document.getElementById('newProjectGroupModal'));
modal.show();
}
function createProjectForGroup() {
const groupId = {{ script_group.id }};
const form = document.getElementById('newProjectGroupForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const projectData = {
project_name: document.getElementById('projectNameGroup').value,
group_id: groupId,
description: document.getElementById('projectDescriptionGroup').value
};
fetch('/api/projects', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(projectData)
})
.then(response => response.json())
.then(result => {
if (result.id) {
const modal = bootstrap.Modal.getInstance(document.getElementById('newProjectGroupModal'));
modal.hide();
// Set the new project as active
setActiveProject(result.id, false);
ScriptsManager.showNotification('Project created and activated successfully!', 'success');
} else {
ScriptsManager.showNotification('Error creating project: ' + (result.error || 'Unknown error'), 'error');
}
})
.catch(error => {
console.error('Error creating project:', error);
ScriptsManager.showNotification('Error creating project: ' + error.message, 'error');
});
}
// Initialize project functionality when page loads
document.addEventListener('DOMContentLoaded', function() {
loadActiveProjectForGroup();
// Event listeners for project management
document.getElementById('change-project-btn')?.addEventListener('click', showChangeProjectModal);
document.getElementById('new-project-group-btn')?.addEventListener('click', showNewProjectForGroupModal);
});
// Basic ScriptsManager object for compatibility
window.ScriptsManager = {
showNotification: function(message, type = 'info') {
// Create a simple notification system
const alertClass = type === 'success' ? 'alert-success' :
type === 'error' ? 'alert-danger' :
type === 'warning' ? 'alert-warning' : 'alert-info';
const notification = document.createElement('div');
notification.className = `alert ${alertClass} alert-dismissible fade show position-fixed`;
notification.style.cssText = 'top: 20px; right: 20px; z-index: 1050; min-width: 300px;';
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(notification);
// Auto remove after 5 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 5000);
}
};
// Script Group Manager Class
class ScriptGroupManager {
async runScript(scriptId, parameters = {}) {
try {
const response = await fetch(`/api/scripts/${scriptId}/execute`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
parameters: parameters
})
});
const result = await response.json();
if (result.success) {
ScriptsManager.showNotification('Script started successfully', 'success');
this.updateScriptStatus(scriptId, 'running');
// If the script has an interface URL, wait for it to be ready before opening
if (result.interface_url) {
this.waitForScriptAndOpen(result.interface_url, scriptId);
}
} else {
ScriptsManager.showNotification(result.message || 'Failed to start script', 'error');
}
} catch (error) {
console.error('Error running script:', error);
ScriptsManager.showNotification('Error running script', 'error');
}
}
async waitForScriptAndOpen(url, scriptId) {
const maxAttempts = 30; // Maximum 30 attempts (30 seconds)
const checkInterval = 1000; // Check every 1 second
let attempts = 0;
let cancelled = false;
// Show loading notification with open now button
const loadingNotification = this.showAdvancedNotification(
'Waiting for script interface to start...',
'info',
[{
text: 'Open Now',
action: () => {
cancelled = true;
this.hideNotification(loadingNotification);
window.open(url, '_blank');
}
}]
);
const checkServer = async () => {
if (cancelled) return;
attempts++;
try {
// Use our backend endpoint to check script availability
const response = await fetch('/api/scripts/check-interface', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ url: url })
});
const result = await response.json();
if (result.ready) {
// Server is ready!
this.hideNotification(loadingNotification);
ScriptsManager.showNotification('Script interface ready!', 'success');
window.open(url, '_blank');
return;
}
// Server not ready yet
if (attempts >= maxAttempts) {
this.hideNotification(loadingNotification);
ScriptsManager.showNotification(
'Script may take longer to start. You can try opening it manually.',
'warning'
);
// Still try to open, maybe it's ready now
window.open(url, '_blank');
return;
}
// Update notification with progress
this.updateNotificationText(
loadingNotification,
`Waiting for script interface... (${attempts}/${maxAttempts})`
);
// Wait and try again
setTimeout(checkServer, checkInterval);
} catch (error) {
console.error('Error checking script interface:', error);
if (attempts >= maxAttempts) {
this.hideNotification(loadingNotification);
ScriptsManager.showNotification(
'Error checking script status. Opening anyway...',
'warning'
);
window.open(url, '_blank');
return;
}
// Wait and try again
setTimeout(checkServer, checkInterval);
}
};
// Start checking
setTimeout(checkServer, 2000); // Wait 2 seconds before first check
}
showAdvancedNotification(message, type = 'info', buttons = []) {
const alertId = 'alert-' + Date.now();
let buttonsHTML = '';
if (buttons.length > 0) {
buttonsHTML = '<div class="mt-2">';
buttons.forEach((button, index) => {
buttonsHTML += `<button type="button" class="btn btn-sm btn-outline-${type} me-2" data-button-index="${index}">${button.text}</button>`;
});
buttonsHTML += '</div>';
}
const alertHTML = `
<div id="${alertId}" class="alert alert-${type} alert-dismissible fade show" role="alert">
<span class="alert-text">${message}</span>
${buttonsHTML}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
const container = document.querySelector('.container-fluid') || document.body;
container.insertAdjacentHTML('afterbegin', alertHTML);
const alert = document.getElementById(alertId);
// Attach button event listeners
buttons.forEach((button, index) => {
const btn = alert.querySelector(`[data-button-index="${index}"]`);
if (btn) {
btn.addEventListener('click', button.action);
}
});
return alert;
}
hideNotification(notification) {
if (notification && notification.parentElement) {
const bsAlert = new bootstrap.Alert(notification);
bsAlert.close();
}
}
updateNotificationText(notification, text) {
if (notification) {
const textElement = notification.querySelector('.alert-text');
if (textElement) {
textElement.textContent = text;
}
}
}
async loadScriptLogs(scriptId) {
try {
const response = await fetch(`/api/scripts/${scriptId}/logs`);
const result = await response.json();
if (result.success && result.logs) {
result.logs.forEach(log => {
window.scriptLogDisplay.appendLog(log.message, log.level, log.timestamp);
});
}
} catch (error) {
console.error('Error loading logs:', error);
}
}
async openScriptInterface(scriptId) {
try {
const response = await fetch(`/api/scripts/${scriptId}/interface`);
const result = await response.json();
if (result.success && result.url) {
window.open(result.url, '_blank');
} else {
ScriptsManager.showNotification(result.message || 'Interface not available', 'warning');
}
} catch (error) {
console.error('Error opening interface:', error);
ScriptsManager.showNotification('Error opening script interface', 'error');
}
}
updateScriptStatus(scriptId, status) {
const statusElement = document.querySelector(`[data-script-id="${scriptId}"] .script-status`);
if (statusElement) {
statusElement.textContent = status;
statusElement.className = `badge script-status status-${status}`;
}
}
}
// Script editing functions
async function loadScriptForEditing(scriptId) {
try {
const response = await fetch(`/api/scripts/${scriptId}`);
const script = await response.json();
if (response.ok) {
// Populate form fields
document.getElementById('editScriptId').value = script.id;
document.getElementById('editDisplayName').value = script.display_name || '';
document.getElementById('editDescription').value = script.description || '';
document.getElementById('editDescriptionLong').value = script.description_long || '';
// Update language badge
const languageBadge = document.getElementById('editLanguageBadge');
const userLanguage = script.user_language || 'en';
languageBadge.textContent = userLanguage.toUpperCase();
} else {
ScriptsManager.showNotification(script.error || 'Failed to load script data', 'error');
}
} catch (error) {
console.error('Error loading script:', error);
ScriptsManager.showNotification('Error loading script data', 'error');
}
}
async function saveScriptChanges() {
const form = document.getElementById('editScriptForm');
const formData = new FormData(form);
const scriptId = formData.get('script_id');
if (!scriptId) {
ScriptsManager.showNotification('Script ID not found', 'error');
return;
}
// Get current language for long description
const languageBadge = document.getElementById('editLanguageBadge');
const currentLanguage = languageBadge.textContent.toLowerCase();
const data = {
display_name: formData.get('display_name'),
description: formData.get('description'),
description_long: formData.get('description_long'),
language: currentLanguage
};
// Show loading state
const saveBtn = document.getElementById('saveScriptBtn');
const originalText = saveBtn.innerHTML;
saveBtn.innerHTML = '<i class="bi bi-arrow-clockwise spin me-1"></i>Saving...';
saveBtn.disabled = true;
try {
const response = await fetch(`/api/scripts/${scriptId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
const result = await response.json();
if (response.ok) {
// Update the UI
updateScriptCardUI(scriptId, data);
// Close modal
const modal = bootstrap.Modal.getInstance(document.getElementById('editScriptModal'));
modal.hide();
ScriptsManager.showNotification('Script updated successfully', 'success');
} else {
ScriptsManager.showNotification(result.error || 'Failed to update script', 'error');
}
} catch (error) {
console.error('Error saving script:', error);
ScriptsManager.showNotification('Error saving script changes', 'error');
} finally {
// Restore button state
saveBtn.innerHTML = originalText;
saveBtn.disabled = false;
}
}
function updateScriptCardUI(scriptId, data) {
const scriptCard = document.querySelector(`[data-script-id="${scriptId}"]`);
if (scriptCard) {
// Update display name
const titleElement = scriptCard.querySelector('.card-title');
if (titleElement && data.display_name) {
titleElement.textContent = data.display_name;
}
// Update description
const descElement = scriptCard.querySelector('.card-text');
if (descElement) {
if (data.description) {
descElement.textContent = data.description;
descElement.style.display = '';
} else {
descElement.style.display = 'none';
}
}
}
}
</script>
</body>
</html>