749 lines
30 KiB
HTML
749 lines
30 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ t.dashboard }} - {{ t.app_title }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1><i class="bi bi-house"></i> {{ t.dashboard }}</h1>
|
|
<button class="btn btn-outline-primary" id="refresh-scripts">
|
|
<i class="bi bi-arrow-clockwise"></i> Refresh Scripts
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Status Row -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card dashboard-card-blue">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h5 class="card-title">{{ t.script_groups }}</h5>
|
|
<h3 class="mb-0">{{ script_groups|length }}</h3>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="bi bi-folder2 display-6"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card dashboard-card-green">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h5 class="card-title">{{ t.projects }}</h5>
|
|
<h3 class="mb-0">{{ user_projects|length }}</h3>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="bi bi-kanban display-6"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card dashboard-card-purple">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h5 class="card-title">User Level</h5>
|
|
<h6 class="mb-0">{{ t.user_level[current_user.user_level] }}</h6>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="bi bi-person-badge display-6"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card dashboard-card-orange">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h5 class="card-title">{{ t.status }}</h5>
|
|
<h6 class="mb-0" id="system-status">Loading...</h6>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="bi bi-activity display-6"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Active Project Section -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="bi bi-folder-plus"></i> Active Project
|
|
</h5>
|
|
<div>
|
|
<button class="btn btn-sm btn-outline-primary" id="manage-projects-btn">
|
|
<i class="bi bi-gear"></i> Manage Projects
|
|
</button>
|
|
<button class="btn btn-sm btn-success" id="new-project-btn">
|
|
<i class="bi bi-plus"></i> New Project
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="active-project-info">
|
|
<div class="d-flex align-items-center">
|
|
<div class="spinner-border spinner-border-sm me-2" id="project-loading">
|
|
</div>
|
|
<span>Loading active project...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Script Groups -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h2><i class="bi bi-collection"></i> {{ t.script_groups }}</h2>
|
|
|
|
{% if script_groups %}
|
|
<div class="row">
|
|
{% for group in script_groups %}
|
|
<div class="col-lg-4 col-md-6 mb-4">
|
|
<div class="card h-100 script-group-card" data-group-id="{{ group.id }}">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="bi bi-folder2-open text-primary"></i>
|
|
{{ group.name }}
|
|
</h5>
|
|
<span class="badge bg-{{ 'success' if group.is_active else 'secondary' }}">
|
|
{{ 'Active' if group.is_active else 'Inactive' }}
|
|
</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="card-text">
|
|
{% if group.description %}
|
|
{% set desc_dict = group.description | fromjson %}
|
|
{% if desc_dict is mapping %}
|
|
{{ desc_dict.get(current_lang, desc_dict.get('en', 'No description')) }}
|
|
{% else %}
|
|
{{ group.description }}
|
|
{% endif %}
|
|
{% else %}
|
|
No description available
|
|
{% endif %}
|
|
</p>
|
|
|
|
<div class="mb-2">
|
|
<small class="text-muted">
|
|
<i class="bi bi-shield-check"></i>
|
|
Required Level: {{ t.user_level.get(group.required_level, group.required_level) }}
|
|
</small>
|
|
</div>
|
|
|
|
{% if group.conda_environment %}
|
|
<div class="mb-2">
|
|
<small class="text-muted">
|
|
<i class="bi bi-cpu"></i>
|
|
Environment: {{ group.conda_environment }}
|
|
</small>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="mb-2">
|
|
<small class="text-muted">
|
|
<i class="bi bi-file-code"></i>
|
|
Scripts: <span class="scripts-count" data-group-id="{{ group.id }}">Loading...</span>
|
|
</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer">
|
|
<a href="{{ url_for('script_group_view', group_id=group.id) }}" class="btn btn-primary btn-sm">
|
|
<i class="bi bi-play-circle"></i> {{ t.execute }} {{ t.scripts }}
|
|
</a>
|
|
{% if current_user.user_level in ['admin', 'superuser'] %}
|
|
<button class="btn btn-outline-secondary btn-sm" onclick="editGroup({{ group.id }})">
|
|
<i class="bi bi-pencil"></i> Edit
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-folder-x display-1 text-muted"></i>
|
|
<h3 class="text-muted mt-3">No Script Groups Found</h3>
|
|
<p class="text-muted">Add script directories to <code>app/backend/script_groups/</code> to get started.</p>
|
|
<button class="btn btn-primary" id="refresh-scripts">
|
|
<i class="bi bi-arrow-clockwise"></i> Refresh Scripts
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Active Processes (if any) -->
|
|
<div class="row mt-4" id="active-processes-section" style="display: none;">
|
|
<div class="col-12">
|
|
<h3><i class="bi bi-cpu"></i> Active Processes</h3>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div id="active-processes-list">
|
|
<!-- Will be populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Load script counts for each group
|
|
loadScriptCounts();
|
|
|
|
// Load system status
|
|
loadSystemStatus();
|
|
|
|
// Load active processes
|
|
loadActiveProcesses();
|
|
|
|
// Refresh scripts button
|
|
document.getElementById('refresh-scripts').addEventListener('click', function() {
|
|
refreshScripts();
|
|
});
|
|
});
|
|
|
|
function loadScriptCounts() {
|
|
document.querySelectorAll('.scripts-count').forEach(element => {
|
|
const groupId = element.dataset.groupId;
|
|
|
|
fetch(`/api/script-groups/${groupId}/scripts`)
|
|
.then(response => response.json())
|
|
.then(scripts => {
|
|
element.textContent = scripts.length;
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading script count:', error);
|
|
element.textContent = 'Error';
|
|
});
|
|
});
|
|
}
|
|
|
|
function loadSystemStatus() {
|
|
fetch('/health')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const statusElement = document.getElementById('system-status');
|
|
if (data.status === 'healthy') {
|
|
statusElement.textContent = 'Healthy';
|
|
statusElement.className = 'mb-0 text-success';
|
|
} else {
|
|
statusElement.textContent = 'Issues';
|
|
statusElement.className = 'mb-0 text-danger';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading system status:', error);
|
|
document.getElementById('system-status').textContent = 'Unknown';
|
|
});
|
|
}
|
|
|
|
function loadActiveProcesses() {
|
|
// This would load active processes for the current user
|
|
// Implementation depends on the API endpoint
|
|
}
|
|
|
|
function refreshScripts() {
|
|
const button = document.getElementById('refresh-scripts');
|
|
const originalHTML = button.innerHTML;
|
|
|
|
button.innerHTML = '<i class="bi bi-arrow-clockwise spin"></i> Refreshing...';
|
|
button.disabled = true;
|
|
|
|
// Trigger script discovery
|
|
fetch('/api/script-groups/refresh', { method: 'POST' })
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.success) {
|
|
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;
|
|
});
|
|
}
|
|
|
|
function editGroup(groupId) {
|
|
// Load group details and show editing modal
|
|
fetch(`/api/script-groups/${groupId}`)
|
|
.then(response => response.json())
|
|
.then(group => {
|
|
showGroupEditModal(group);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading group details:', error);
|
|
alert('Error loading group details: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function showGroupEditModal(group) {
|
|
// Create modal HTML
|
|
const modalHtml = `
|
|
<div class="modal fade" id="groupEditModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Edit Group: ${group.name}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="groupEditForm">
|
|
<div class="mb-3">
|
|
<label for="groupName" class="form-label">Group Name</label>
|
|
<input type="text" class="form-control" id="groupName"
|
|
value="${group.name}" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="condaEnvironment" class="form-label">Conda Environment</label>
|
|
<select class="form-control" id="condaEnvironment" required>
|
|
<option value="base" ${group.conda_environment === 'base' ? 'selected' : ''}>base</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="requiredLevel" class="form-label">Required Permission Level</label>
|
|
<select class="form-control" id="requiredLevel" required>
|
|
<option value="viewer" ${group.required_level === 'viewer' ? 'selected' : ''}>Viewer</option>
|
|
<option value="operator" ${group.required_level === 'operator' ? 'selected' : ''}>Operator</option>
|
|
<option value="admin" ${group.required_level === 'admin' ? 'selected' : ''}>Admin</option>
|
|
<option value="superuser" ${group.required_level === 'superuser' ? 'selected' : ''}>Superuser</option>
|
|
</select>
|
|
</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="saveGroupChanges(${group.id})">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Remove any existing modal
|
|
const existingModal = document.getElementById('groupEditModal');
|
|
if (existingModal) {
|
|
existingModal.remove();
|
|
}
|
|
|
|
// Add modal to body and show it
|
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
|
|
// Load conda environments
|
|
loadCondaEnvironments(group.conda_environment);
|
|
|
|
// Show modal using Bootstrap 5 API
|
|
const modal = new bootstrap.Modal(document.getElementById('groupEditModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function loadCondaEnvironments(currentEnv) {
|
|
fetch('/api/conda/environments')
|
|
.then(response => response.json())
|
|
.then(environments => {
|
|
const select = document.getElementById('condaEnvironment');
|
|
select.innerHTML = '';
|
|
|
|
// Add base environment
|
|
const baseOption = new Option('base', 'base', false, currentEnv === 'base');
|
|
select.add(baseOption);
|
|
|
|
// Add other environments
|
|
environments.forEach(env => {
|
|
if (env.name !== 'base') {
|
|
const option = new Option(env.name, env.name, false, currentEnv === env.name);
|
|
select.add(option);
|
|
}
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading conda environments:', error);
|
|
// Keep the default base option if error occurs
|
|
});
|
|
}
|
|
|
|
function saveGroupChanges(groupId) {
|
|
const updateData = {
|
|
name: document.getElementById('groupName').value,
|
|
conda_environment: document.getElementById('condaEnvironment').value,
|
|
required_level: document.getElementById('requiredLevel').value
|
|
};
|
|
|
|
fetch(`/api/script-groups/${groupId}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(updateData)
|
|
})
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('groupEditModal'));
|
|
modal.hide();
|
|
alert('Group updated successfully!');
|
|
// Refresh the page to show updated group info
|
|
location.reload();
|
|
} else {
|
|
alert('Error updating group: ' + (result.error || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error updating group:', error);
|
|
alert('Error updating group: ' + error.message);
|
|
});
|
|
}
|
|
|
|
// Project Management Functions
|
|
function loadActiveProject() {
|
|
fetch('/api/projects/active')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const projectInfo = document.getElementById('active-project-info');
|
|
const loading = document.getElementById('project-loading');
|
|
|
|
loading.style.display = 'none';
|
|
|
|
if (data.active_project) {
|
|
const project = data.active_project;
|
|
projectInfo.innerHTML = `
|
|
<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>
|
|
</div>
|
|
<div>
|
|
<span class="badge bg-success">Active</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
projectInfo.innerHTML = `
|
|
<div class="text-muted">
|
|
<i class="bi bi-info-circle me-2"></i>
|
|
No active project. Create or select a project to get started.
|
|
</div>
|
|
`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading active project:', error);
|
|
document.getElementById('project-loading').style.display = 'none';
|
|
document.getElementById('active-project-info').innerHTML = `
|
|
<div class="text-danger">
|
|
<i class="bi bi-exclamation-triangle me-2"></i>
|
|
Error loading active project
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
function showProjectManagementModal() {
|
|
const modalHtml = `
|
|
<div class="modal fade" id="projectManagementModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Project Management</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="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>
|
|
`;
|
|
|
|
// Remove existing modal
|
|
const existingModal = document.getElementById('projectManagementModal');
|
|
if (existingModal) {
|
|
existingModal.remove();
|
|
}
|
|
|
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
|
|
const modal = new bootstrap.Modal(document.getElementById('projectManagementModal'));
|
|
modal.show();
|
|
|
|
loadProjectsList();
|
|
}
|
|
|
|
function loadProjectsList() {
|
|
fetch('/api/projects')
|
|
.then(response => response.json())
|
|
.then(projects => {
|
|
const projectsList = document.getElementById('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</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>
|
|
<br>
|
|
<small class="text-muted">
|
|
Created: ${new Date(project.created_at).toLocaleDateString()}
|
|
${project.last_accessed ? ', Last used: ' + new Date(project.last_accessed).toLocaleDateString() : ''}
|
|
</small>
|
|
</div>
|
|
<div>
|
|
${project.is_default ? '<span class="badge bg-secondary me-2">Default</span>' : ''}
|
|
<button class="btn btn-sm btn-outline-primary me-1"
|
|
onclick="setActiveProject(${project.id})">
|
|
<i class="bi bi-check-circle"></i> Set Active
|
|
</button>
|
|
${!project.is_default ? `
|
|
<button class="btn btn-sm btn-outline-danger"
|
|
onclick="deleteProject(${project.id})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
projectsList.innerHTML = projectsHtml;
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading projects:', error);
|
|
document.getElementById('projects-list').innerHTML = `
|
|
<div class="alert alert-danger">
|
|
<i class="bi bi-exclamation-triangle me-2"></i>
|
|
Error loading projects
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
function setActiveProject(projectId) {
|
|
fetch(`/api/projects/${projectId}/set-active`, {
|
|
method: 'POST'
|
|
})
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('projectManagementModal'));
|
|
modal.hide();
|
|
loadActiveProject(); // Refresh active project display
|
|
alert('Project set as active successfully!');
|
|
} else {
|
|
alert('Error setting active project: ' + (result.error || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error setting active project:', error);
|
|
alert('Error setting active project: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function deleteProject(projectId) {
|
|
if (!confirm('Are you sure you want to delete this project? This action cannot be undone.')) {
|
|
return;
|
|
}
|
|
|
|
fetch(`/api/projects/${projectId}`, {
|
|
method: 'DELETE'
|
|
})
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
loadProjectsList(); // Refresh projects list
|
|
loadActiveProject(); // Refresh active project display
|
|
alert('Project deleted successfully!');
|
|
} else {
|
|
alert('Error deleting project: ' + (result.error || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error deleting project:', error);
|
|
alert('Error deleting project: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function showNewProjectModal() {
|
|
const modalHtml = `
|
|
<div class="modal fade" id="newProjectModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Create New Project</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="newProjectForm">
|
|
<div class="mb-3">
|
|
<label for="projectName" class="form-label">Project Name</label>
|
|
<input type="text" class="form-control" id="projectName" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="projectGroup" class="form-label">Script Group</label>
|
|
<select class="form-control" id="projectGroup" required>
|
|
<option value="">Select a script group...</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="projectDescription" class="form-label">Description (Optional)</label>
|
|
<textarea class="form-control" id="projectDescription" 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="createProject()">Create Project</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const existingModal = document.getElementById('newProjectModal');
|
|
if (existingModal) {
|
|
existingModal.remove();
|
|
}
|
|
|
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
|
|
// Load script groups for selection
|
|
loadScriptGroupsForProject();
|
|
|
|
const modal = new bootstrap.Modal(document.getElementById('newProjectModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function loadScriptGroupsForProject() {
|
|
fetch('/api/script-groups')
|
|
.then(response => response.json())
|
|
.then(groups => {
|
|
const select = document.getElementById('projectGroup');
|
|
select.innerHTML = '<option value="">Select a script group...</option>';
|
|
|
|
groups.forEach(group => {
|
|
const option = new Option(group.name, group.id);
|
|
select.add(option);
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading script groups:', error);
|
|
});
|
|
}
|
|
|
|
function createProject() {
|
|
const form = document.getElementById('newProjectForm');
|
|
if (!form.checkValidity()) {
|
|
form.reportValidity();
|
|
return;
|
|
}
|
|
|
|
const projectData = {
|
|
project_name: document.getElementById('projectName').value,
|
|
group_id: parseInt(document.getElementById('projectGroup').value),
|
|
description: document.getElementById('projectDescription').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('newProjectModal'));
|
|
modal.hide();
|
|
loadActiveProject(); // Refresh active project display
|
|
alert('Project created successfully!');
|
|
} else {
|
|
alert('Error creating project: ' + (result.error || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error creating project:', error);
|
|
alert('Error creating project: ' + error.message);
|
|
});
|
|
}
|
|
|
|
// Initialize project management when page loads
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadActiveProject();
|
|
|
|
// Event listeners for project management
|
|
document.getElementById('manage-projects-btn').addEventListener('click', showProjectManagementModal);
|
|
document.getElementById('new-project-btn').addEventListener('click', showNewProjectModal);
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.script-group-card {
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.script-group-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.spin {
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
</style>
|
|
{% endblock %} |