Refactor code structure for improved readability and maintainability

This commit is contained in:
Miguel 2025-09-14 12:10:06 +02:00
parent bed4fcde1f
commit a655e68b71
15 changed files with 2212 additions and 46 deletions

View File

@ -221,6 +221,45 @@ def register_routes(app):
user_language=user_language,
)
@app.route("/script/<int:script_id>/loading")
@login_required
def script_loading_page(script_id):
"""
Página de loading intermedia que se muestra mientras el script se inicia
"""
script = Script.query.get_or_404(script_id)
# Check if user can access this script
if not can_access_script(current_user.user_level, script.required_level):
flash("Insufficient permissions to access this script", "error")
return redirect(url_for("dashboard"))
# Get active project for this script's group
active_project_id = session.get("active_project_id")
project = None
if active_project_id:
project = UserProject.query.filter_by(
id=active_project_id,
user_id=current_user.id,
group_id=script.group_id,
).first()
if not project:
flash("No active project found for this script group", "error")
return redirect(url_for("script_group_view", group_id=script.group_id))
# Generate proxy URL
proxy_url = f"/project/{project.id}/script/{script.id}/user/{current_user.id}/"
return render_template(
"script_loading.html",
script=script,
script_group=script.script_group,
project=project,
proxy_url=proxy_url,
)
# Administration routes
@app.route("/admin")
@login_required
@ -568,7 +607,10 @@ def register_routes(app):
)
print(f"[API_EXEC] Script executor result: {result}")
return jsonify(result)
print(f"[API_EXEC] About to return JSON response...")
response = jsonify(result)
print(f"[API_EXEC] JSON response created successfully")
return response
@app.route("/api/scripts/<int:script_id>/stop", methods=["POST"])
@login_required
@ -1105,7 +1147,20 @@ def create_app(config_name="default"):
app = Flask(__name__)
# Load configuration
app.config.from_object(config[config_name])
config_obj = config[config_name]
app.config.from_object(config_obj)
# Apply security configuration if present
if hasattr(config_obj, 'SECURITY_CONFIG'):
security_config = config_obj.SECURITY_CONFIG
# Apply session cookie settings
if 'session_cookie_secure' in security_config:
app.config['SESSION_COOKIE_SECURE'] = security_config['session_cookie_secure']
if 'session_cookie_httponly' in security_config:
app.config['SESSION_COOKIE_HTTPONLY'] = security_config['session_cookie_httponly']
if 'session_cookie_samesite' in security_config:
app.config['SESSION_COOKIE_SAMESITE'] = security_config['session_cookie_samesite']
# Add custom Jinja2 filters
@app.template_filter("fromjson")

View File

@ -127,6 +127,14 @@ class DevelopmentConfig(Config):
'echo': True, # Log SQL queries in development
}
# Development security config - allow HTTP cookies
SECURITY_CONFIG = {
**Config.SECURITY_CONFIG,
"session_cookie_secure": False, # Allow HTTP cookies in development
"session_cookie_httponly": True,
"session_cookie_samesite": "Lax",
}
class ProductionConfig(Config):
"""Production configuration."""

View File

@ -79,8 +79,11 @@ class DataManager:
print(f"Error saving config file {config_file}: {e}")
raise
def list_user_projects(self, user_id: int, group_id: int) -> List[Dict]:
"""List all projects for user in specific group."""
def list_user_projects(self, user_id: int, group_id: int = None) -> List[Dict]:
"""List all projects for user in specific group or all groups if group_id is None."""
if group_id is None:
projects = UserProject.query.filter_by(user_id=user_id).all()
else:
projects = UserProject.query.filter_by(user_id=user_id, group_id=group_id).all()
result = []
@ -89,7 +92,7 @@ class DataManager:
# Add file system information
project_path = self.get_user_project_path(
user_id, group_id, project.project_name
user_id, project.group_id, project.project_name
)
if project_path.exists():

View File

@ -799,32 +799,11 @@
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');
}
// Redirect immediately to loading page - it will handle script execution
window.location.href = `/script/${scriptId}/loading`;
} catch (error) {
console.error('Error running script:', error);
ScriptsManager.showNotification('Error running script', 'error');
console.error('Error redirecting to loading page:', error);
ScriptsManager.showNotification('Error starting script', 'error');
}
}

View File

@ -0,0 +1,285 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loading Script - ScriptsManager</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css" rel="stylesheet">
<style>
.loading-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.loading-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 3rem;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 500px;
width: 90%;
}
.loading-spinner {
width: 80px;
height: 80px;
margin: 0 auto 2rem;
}
.progress-bar-container {
background-color: #e9ecef;
border-radius: 10px;
height: 8px;
margin: 2rem 0;
overflow: hidden;
}
.progress-bar {
background: linear-gradient(90deg, #667eea, #764ba2);
height: 100%;
border-radius: 10px;
transition: width 0.3s ease;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.status-text {
color: #6c757d;
font-size: 1.1rem;
margin-bottom: 1rem;
}
.script-info {
background-color: #f8f9fa;
border-radius: 10px;
padding: 1rem;
margin-top: 2rem;
}
.script-info h5 {
color: #495057;
margin-bottom: 0.5rem;
}
.script-info p {
color: #6c757d;
margin-bottom: 0;
}
.error-message {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
border-radius: 10px;
padding: 1rem;
margin-top: 1rem;
}
.open-anyway-btn {
margin-top: 1rem;
}
</style>
</head>
<body>
<div class="loading-container">
<div class="loading-card">
<!-- Loading Spinner -->
<div class="spinner-border text-primary loading-spinner" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<!-- Status Text -->
<h3 class="mb-3" id="loading-title">Starting Script...</h3>
<p class="status-text" id="status-text">Initializing script environment...</p>
<!-- Progress Bar -->
<div class="progress-bar-container">
<div class="progress-bar" id="progress-bar" style="width: 10%;"></div>
</div>
<!-- Script Information -->
<div class="script-info">
<h5>{{ script.display_name or script.filename }}</h5>
<p>{{ script.description or "Running script..." }}</p>
<small class="text-muted">
<i class="bi bi-folder me-1"></i>{{ script_group.name }} |
<i class="bi bi-person me-1"></i>{{ current_user.username }} |
<i class="bi bi-briefcase me-1"></i>{{ project.project_name }}
</small>
</div>
<!-- Error Container (hidden by default) -->
<div id="error-container" class="error-message" style="display: none;">
<i class="bi bi-exclamation-triangle me-2"></i>
<span id="error-text"></span>
<button type="button" class="btn btn-outline-danger btn-sm open-anyway-btn" onclick="openScriptAnyway()">
<i class="bi bi-box-arrow-up-right me-1"></i>
Open Script Anyway
</button>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Configuration from backend
const SCRIPT_CONFIG = {
scriptId: {{ script.id }},
projectId: {{ project.id }},
userId: {{ current_user.id }},
proxyUrl: "{{ proxy_url }}",
fullProxyUrl: "{{ request.url_root.rstrip('/') }}{{ proxy_url }}",
maxAttempts: 30,
checkInterval: 2000
};
// Loading states and messages
const LOADING_STATES = [
{ progress: 10, title: "Starting Script...", message: "Initializing script environment..." },
{ progress: 25, title: "Loading Dependencies...", message: "Setting up required libraries..." },
{ progress: 40, title: "Configuring Environment...", message: "Preparing script workspace..." },
{ progress: 55, title: "Starting Web Server...", message: "Launching script interface..." },
{ progress: 70, title: "Checking Connectivity...", message: "Verifying script availability..." },
{ progress: 85, title: "Finalizing Setup...", message: "Almost ready..." },
{ progress: 95, title: "Script Ready!", message: "Redirecting to script interface..." }
];
let currentStateIndex = 0;
let attempts = 0;
let checkingStarted = false;
// DOM elements
const progressBar = document.getElementById('progress-bar');
const loadingTitle = document.getElementById('loading-title');
const statusText = document.getElementById('status-text');
const errorContainer = document.getElementById('error-container');
const errorText = document.getElementById('error-text');
// Update loading state
function updateLoadingState(stateIndex) {
if (stateIndex < LOADING_STATES.length) {
const state = LOADING_STATES[stateIndex];
progressBar.style.width = state.progress + '%';
loadingTitle.textContent = state.title;
statusText.textContent = state.message;
}
}
// Show error message
function showError(message) {
errorText.textContent = message;
errorContainer.style.display = 'block';
loadingTitle.textContent = "Error Starting Script";
statusText.textContent = "There was an issue starting the script.";
progressBar.style.width = '100%';
progressBar.style.backgroundColor = '#dc3545';
}
// Open script anyway (fallback)
function openScriptAnyway() {
window.location.href = SCRIPT_CONFIG.fullProxyUrl;
}
// Check if script is ready
async function checkScriptStatus() {
attempts++;
try {
// Update UI state periodically
if (!checkingStarted) {
checkingStarted = true;
// Start advancing through loading states
const stateInterval = setInterval(() => {
if (currentStateIndex < LOADING_STATES.length - 1) {
currentStateIndex++;
updateLoadingState(currentStateIndex);
} else {
clearInterval(stateInterval);
}
}, 3000);
}
// Check if script is responding
const response = await fetch(SCRIPT_CONFIG.fullProxyUrl, {
method: 'GET',
cache: 'no-cache'
});
if (response.ok) {
// Script is ready!
updateLoadingState(LOADING_STATES.length - 1);
setTimeout(() => {
window.location.href = SCRIPT_CONFIG.fullProxyUrl;
}, 1000);
return;
}
} catch (error) {
console.log(`Attempt ${attempts}: Script not ready yet`, error.message);
}
// Check if we've exceeded max attempts
if (attempts >= SCRIPT_CONFIG.maxAttempts) {
showError("Script is taking longer than expected to start. You can try opening it manually.");
return;
}
// Try again after interval
setTimeout(checkScriptStatus, SCRIPT_CONFIG.checkInterval);
}
// Start checking when page loads
document.addEventListener('DOMContentLoaded', function() {
// Start with first loading state
updateLoadingState(0);
// Start script execution via AJAX first
startScriptExecution();
// Begin checking script status after a short delay
setTimeout(checkScriptStatus, 3000);
});
// Function to start script execution
async function startScriptExecution() {
try {
const response = await fetch(`/api/scripts/${SCRIPT_CONFIG.scriptId}/execute`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
parameters: {}
})
});
const result = await response.json();
if (result.success) {
console.log('Script execution started successfully:', result);
} else {
console.error('Failed to start script:', result.message);
showError(result.message || 'Failed to start script');
}
} catch (error) {
console.error('Error starting script execution:', error);
showError('Error starting script: ' + error.message);
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,6 @@
{
"created_at": "2025-09-14T08:09:16.779232",
"last_modified": "2025-09-14T08:09:16.779241",
"project_settings": {},
"user_preferences": {}
}

View File

@ -0,0 +1,6 @@
{
"created_at": "2025-09-14T08:01:20.986814",
"last_modified": "2025-09-14T08:01:20.986822",
"project_settings": {},
"user_preferences": {}
}

View File

@ -0,0 +1,6 @@
{
"created_at": "2025-09-14T07:57:47.252787",
"last_modified": "2025-09-14T07:57:47.252795",
"project_settings": {},
"user_preferences": {}
}

View File

@ -87,7 +87,6 @@ services:
- scriptsmanager_network
ports:
- "5003:5003"
- "5200-5400:5200-5400"
- "5678:5678" # Debug port
environment:
# Database Configuration (same as production for parity)
@ -115,20 +114,21 @@ services:
- SIDEL_LOGO_PATH=/app/app/static/images/SIDEL.png
- CORPORATE_BRANDING=true
volumes:
# Hot reload: mount entire codebase
# Hot reload: mount entire codebase excluding workspaces
- .:/app
- ./data:/app/data
- ./backup:/app/backup
- ./logs:/app/logs
# Workspaces for proxy scripts
- ./workspaces:/app/workspaces
# Workspaces for proxy scripts (mounted separately to avoid hot-reload)
- ./workspaces:/app/workspaces:delegated
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
command: >
bash -c "source activate scriptsmanager &&
echo '=== SIDEL ScriptsManager Development Environment ===' &&
echo 'Hot reload enabled - code changes will be reflected automatically' &&
echo 'Development mode with stable container (hot reload disabled for stability)' &&
echo 'Application will be available at: http://localhost:5003' &&
echo 'Debug port available at: 5678' &&
python scripts/init_db.py &&

View File

@ -30,11 +30,22 @@ def init_database():
with app.app_context():
try:
# Drop all tables (for fresh start)
print("Dropping existing tables...")
db.drop_all()
# Check if database is already initialized
from sqlalchemy import inspect
inspector = inspect(db.engine)
existing_tables = inspector.get_table_names()
# Create all tables
if existing_tables:
print(f"Database already initialized with {len(existing_tables)} tables.")
print("Skipping table creation and data initialization.")
# Still discover script groups in case new ones were added
print("Updating script group discovery...")
discover_script_groups()
print("Database update completed successfully!")
return
# Only create tables if database is empty
print("Creating database tables...")
db.create_all()

View File

@ -25,15 +25,29 @@ def run_app():
port = int(os.environ.get('PORT', 5003 if os.environ.get('DEBUG') == 'true' else 5002))
debug = os.environ.get('DEBUG', 'false').lower() == 'true'
# For development: disable auto-reloader to prevent container restarts when workspaces change
# This avoids the issue where script execution causes Flask to restart
use_reloader = False if debug else False
# Run with SocketIO support
socketio.run(app, host="0.0.0.0", port=port, debug=debug)
socketio.run(
app,
host="0.0.0.0",
port=port,
debug=debug,
use_reloader=use_reloader
)
if __name__ == "__main__":
port = int(os.environ.get('PORT', 5003 if os.environ.get('DEBUG') == 'true' else 5002))
print("Starting ScriptsManager...")
print(f"🚀 Application will be available at: http://localhost:{port}")
print("🔄 HOT-RELOAD ENABLED - Modify files and they will update automatically!")
if os.environ.get('DEBUG') == 'true':
print("<EFBFBD> DEVELOPMENT MODE - Auto-reloader disabled for container stability")
print(" Manual restart required for code changes")
else:
print("🏭 PRODUCTION MODE")
print("Press Ctrl+C to stop the server")
print("-" * 50)
run_app()

View File

@ -6,10 +6,10 @@
"CONDA_DEFAULT_ENV": "tsnet",
"USER_LEVEL": "admin",
"PROJECT_ID": "1",
"PROJECT_NAME": "test",
"PROJECT_NAME": "Hola",
"USER_THEME": "light",
"USER_LANGUAGE": "en",
"SCRIPT_GROUP_NAME": "Hydraulic Analysis Tools"
},
"created_at": "2025-09-14T07:35:04.965536"
"created_at": "2025-09-14T10:08:46.733111"
}

View File

@ -10,7 +10,7 @@ os.chdir(WORKSPACE_PATH)
# Variables disponibles para el script del usuario
PROJECT_WORKSPACE = WORKSPACE_PATH
PARAMETERS = {}
ENVIRONMENT = {'CONDA_DEFAULT_ENV': 'tsnet', 'USER_LEVEL': 'admin', 'PROJECT_ID': '1', 'PROJECT_NAME': 'test', 'USER_THEME': 'light', 'USER_LANGUAGE': 'en', 'SCRIPT_GROUP_NAME': 'Hydraulic Analysis Tools'}
ENVIRONMENT = {'CONDA_DEFAULT_ENV': 'tsnet', 'USER_LEVEL': 'admin', 'PROJECT_ID': '1', 'PROJECT_NAME': 'Hola', 'USER_THEME': 'light', 'USER_LANGUAGE': 'en', 'SCRIPT_GROUP_NAME': 'Hydraulic Analysis Tools'}
# Importar Flask y configurar aplicación
from flask import Flask, request, jsonify, render_template, send_file, redirect, url_for

View File

@ -0,0 +1,7 @@
{
"port": 5201,
"workspace": "/app/workspaces/user_1/project_1/script_8",
"parameters": null,
"environment": null,
"created_at": "2025-09-14T07:38:22.462360"
}

File diff suppressed because it is too large Load Diff