Mejorado con la edicion de los parametros dentro de la pagina principal
This commit is contained in:
parent
372e5c087e
commit
abe66bc1d9
|
@ -84,17 +84,6 @@ def get_script_groups():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
@app.route('/api/script-groups/<group_id>/scripts', methods=['GET'])
|
|
||||||
def get_group_scripts(group_id):
|
|
||||||
"""Get scripts for a specific group"""
|
|
||||||
try:
|
|
||||||
scripts = script_manager.get_group_scripts(group_id)
|
|
||||||
return jsonify(scripts)
|
|
||||||
except ValueError as e:
|
|
||||||
return jsonify({"error": str(e)}), 404
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({"error": str(e)}), 500
|
|
||||||
|
|
||||||
# Directory handling endpoints
|
# Directory handling endpoints
|
||||||
@app.route('/api/select-directory', methods=['GET'])
|
@app.route('/api/select-directory', methods=['GET'])
|
||||||
def handle_select_directory():
|
def handle_select_directory():
|
||||||
|
@ -162,5 +151,43 @@ def update_group_config(work_dir, group_id):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"error": str(e)}), 400
|
return jsonify({"error": str(e)}), 400
|
||||||
|
|
||||||
|
@app.route('/api/script-groups/<group_id>/config-schema', methods=['PUT'])
|
||||||
|
def update_group_config_schema(group_id):
|
||||||
|
"""Update configuration schema for a script group"""
|
||||||
|
try:
|
||||||
|
schema = request.json
|
||||||
|
config_file = Path(script_manager.script_groups_dir) / group_id / "config.json"
|
||||||
|
|
||||||
|
with open(config_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(schema, f, indent=4)
|
||||||
|
|
||||||
|
return jsonify({"status": "success"})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/api/script-groups/<group_id>/scripts', methods=['GET'])
|
||||||
|
def get_group_scripts(group_id):
|
||||||
|
"""Get scripts for a specific group"""
|
||||||
|
try:
|
||||||
|
print(f"Loading scripts for group: {group_id}") # Debug
|
||||||
|
scripts = script_manager.get_group_scripts(group_id)
|
||||||
|
print(f"Scripts found: {scripts}") # Debug
|
||||||
|
return jsonify(scripts)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading scripts: {str(e)}") # Debug
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/api/script-groups/<group_id>/config-schema', methods=['GET'])
|
||||||
|
def get_group_config_schema(group_id):
|
||||||
|
"""Get configuration schema for a script group"""
|
||||||
|
try:
|
||||||
|
print(f"Loading config schema for group: {group_id}") # Debug
|
||||||
|
schema = script_manager.get_group_config_schema(group_id)
|
||||||
|
print(f"Schema loaded: {schema}") # Debug
|
||||||
|
return jsonify(schema)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading schema: {str(e)}") # Debug
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True, port=5000)
|
app.run(debug=True, port=5000)
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -6,24 +6,18 @@ from tkinter import filedialog
|
||||||
from flask import jsonify
|
from flask import jsonify
|
||||||
|
|
||||||
def select_directory():
|
def select_directory():
|
||||||
"""
|
"""Show directory selection dialog and return selected path"""
|
||||||
Show directory selection dialog and return selected path
|
|
||||||
"""
|
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
root.withdraw() # Hide the main window
|
root.withdraw()
|
||||||
|
root.attributes('-topmost', True) # Hace que el diálogo siempre esté encima
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print("Opening directory dialog...") # Debug
|
|
||||||
directory = filedialog.askdirectory(
|
directory = filedialog.askdirectory(
|
||||||
title="Select Work Directory",
|
title="Select Work Directory",
|
||||||
initialdir=os.path.expanduser("~")
|
initialdir=os.path.expanduser("~")
|
||||||
)
|
)
|
||||||
print(f"Selected directory: {directory}") # Debug
|
return {"path": directory} if directory else {"error": "No directory selected"}
|
||||||
result = {"path": directory} if directory else {"error": "No directory selected"}
|
|
||||||
print(f"Returning result: {result}") # Debug
|
|
||||||
return result
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in select_directory: {str(e)}") # Debug
|
|
||||||
return {"error": str(e)}
|
return {"error": str(e)}
|
||||||
finally:
|
finally:
|
||||||
root.destroy()
|
root.destroy()
|
|
@ -6,6 +6,30 @@ from typing import Dict, List, Any, Optional
|
||||||
import json
|
import json
|
||||||
|
|
||||||
class ScriptManager:
|
class ScriptManager:
|
||||||
|
|
||||||
|
def get_group_config_schema(self, group_id: str) -> Dict[str, Any]:
|
||||||
|
"""Get configuration schema for a script group"""
|
||||||
|
config_file = self.script_groups_dir / group_id / "config.json"
|
||||||
|
print(f"Looking for config file: {config_file}") # Debug
|
||||||
|
|
||||||
|
if config_file.exists():
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r', encoding='utf-8') as f:
|
||||||
|
schema = json.load(f)
|
||||||
|
print(f"Loaded schema: {schema}") # Debug
|
||||||
|
return schema
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading group config schema: {e}") # Debug
|
||||||
|
else:
|
||||||
|
print(f"Config file not found: {config_file}") # Debug
|
||||||
|
|
||||||
|
# Retornar un schema vacío si no existe el archivo
|
||||||
|
return {
|
||||||
|
"group_name": group_id,
|
||||||
|
"description": "",
|
||||||
|
"config_schema": {}
|
||||||
|
}
|
||||||
|
|
||||||
def get_available_groups(self) -> List[Dict[str, Any]]:
|
def get_available_groups(self) -> List[Dict[str, Any]]:
|
||||||
"""Get list of available script groups"""
|
"""Get list of available script groups"""
|
||||||
groups = []
|
groups = []
|
||||||
|
@ -23,18 +47,20 @@ class ScriptManager:
|
||||||
def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
||||||
"""Get scripts for a specific group"""
|
"""Get scripts for a specific group"""
|
||||||
group_dir = self.script_groups_dir / group_id
|
group_dir = self.script_groups_dir / group_id
|
||||||
|
print(f"Looking for scripts in: {group_dir}") # Debug
|
||||||
|
|
||||||
if not group_dir.exists() or not group_dir.is_dir():
|
if not group_dir.exists() or not group_dir.is_dir():
|
||||||
|
print(f"Directory not found: {group_dir}") # Debug
|
||||||
raise ValueError(f"Script group '{group_id}' not found")
|
raise ValueError(f"Script group '{group_id}' not found")
|
||||||
|
|
||||||
scripts = []
|
scripts = []
|
||||||
for script_file in group_dir.glob('x[0-9].py'):
|
for script_file in group_dir.glob('x[0-9].py'):
|
||||||
|
print(f"Found script file: {script_file}") # Debug
|
||||||
script_info = self._analyze_script(script_file)
|
script_info = self._analyze_script(script_file)
|
||||||
if script_info:
|
if script_info:
|
||||||
scripts.append(script_info)
|
scripts.append(script_info)
|
||||||
|
|
||||||
return sorted(scripts, key=lambda x: x['id'])
|
return sorted(scripts, key=lambda x: x['id'])
|
||||||
"""Manages script discovery and execution"""
|
|
||||||
|
|
||||||
def __init__(self, script_groups_dir: Path):
|
def __init__(self, script_groups_dir: Path):
|
||||||
self.script_groups_dir = script_groups_dir
|
self.script_groups_dir = script_groups_dir
|
||||||
|
@ -92,10 +118,19 @@ class ScriptManager:
|
||||||
break
|
break
|
||||||
|
|
||||||
if script_class:
|
if script_class:
|
||||||
|
# Extraer la primera línea del docstring como nombre
|
||||||
|
docstring = inspect.getdoc(script_class)
|
||||||
|
if docstring:
|
||||||
|
name, *description = docstring.split('\n', 1)
|
||||||
|
description = description[0] if description else ''
|
||||||
|
else:
|
||||||
|
name = script_file.stem
|
||||||
|
description = ''
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": script_file.stem,
|
"id": script_file.stem,
|
||||||
"name": script_class.__doc__.split('\n')[0].strip() if script_class.__doc__ else script_file.stem,
|
"name": name.strip(),
|
||||||
"description": inspect.getdoc(script_class),
|
"description": description.strip(),
|
||||||
"file": str(script_file.relative_to(self.script_groups_dir))
|
"file": str(script_file.relative_to(self.script_groups_dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"group_name": "System Analysis",
|
||||||
|
"description": "Scripts for system analysis and file management",
|
||||||
|
"config_schema": {
|
||||||
|
"exclude_dirs": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Directories to exclude (comma separated)",
|
||||||
|
"default": "venv,__pycache__,.git"
|
||||||
|
},
|
||||||
|
"count_hidden": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Include hidden files in count",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"min_size": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Minimum file size to count (in bytes)",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"save_report": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Save results to file",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"report_format": {
|
||||||
|
"type": "select",
|
||||||
|
"options": ["txt", "json", "csv"],
|
||||||
|
"description": "Format for saved reports",
|
||||||
|
"default": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,22 +2,33 @@
|
||||||
from backend.script_groups.base_script import BaseScript
|
from backend.script_groups.base_script import BaseScript
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import csv
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
class FileCounter(BaseScript):
|
class FileCounter(BaseScript):
|
||||||
"""
|
"""
|
||||||
Count Files in Directory
|
File Analysis
|
||||||
Lists and counts files in the working directory by extension
|
Analyzes files in directory with configurable filters and reporting
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def run(self, work_dir: str, profile: dict) -> dict:
|
def run(self, work_dir: str, profile: dict) -> dict:
|
||||||
try:
|
try:
|
||||||
# Get configuration if any
|
# Get configuration
|
||||||
config = self.get_config(work_dir, "example_group")
|
config = self.get_config(work_dir, "example_group")
|
||||||
exclude_dirs = config.get("exclude_dirs", [])
|
|
||||||
|
# Process configuration values
|
||||||
|
exclude_dirs = [d.strip() for d in config.get("exclude_dirs", "").split(",") if d.strip()]
|
||||||
|
count_hidden = config.get("count_hidden", False)
|
||||||
|
min_size = config.get("min_size", 0)
|
||||||
|
save_report = config.get("save_report", True)
|
||||||
|
report_format = config.get("report_format", "json")
|
||||||
|
|
||||||
# Initialize counters
|
# Initialize counters
|
||||||
extension_counts = {}
|
extension_counts = {}
|
||||||
total_files = 0
|
total_files = 0
|
||||||
|
total_size = 0
|
||||||
|
skipped_files = 0
|
||||||
|
|
||||||
# Walk through directory
|
# Walk through directory
|
||||||
for root, dirs, files in os.walk(work_dir):
|
for root, dirs, files in os.walk(work_dir):
|
||||||
|
@ -25,18 +36,67 @@ class FileCounter(BaseScript):
|
||||||
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
|
file_path = Path(root) / file
|
||||||
|
|
||||||
|
# Skip hidden files if not counting them
|
||||||
|
if not count_hidden and file.startswith('.'):
|
||||||
|
skipped_files += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check file size
|
||||||
|
try:
|
||||||
|
file_size = file_path.stat().st_size
|
||||||
|
if file_size < min_size:
|
||||||
|
skipped_files += 1
|
||||||
|
continue
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Count file
|
||||||
total_files += 1
|
total_files += 1
|
||||||
ext = Path(file).suffix.lower() or 'no extension'
|
total_size += file_size
|
||||||
|
ext = file_path.suffix.lower() or 'no extension'
|
||||||
extension_counts[ext] = extension_counts.get(ext, 0) + 1
|
extension_counts[ext] = extension_counts.get(ext, 0) + 1
|
||||||
|
|
||||||
|
# Prepare results
|
||||||
|
results = {
|
||||||
|
"scan_time": datetime.now().isoformat(),
|
||||||
|
"total_files": total_files,
|
||||||
|
"total_size": total_size,
|
||||||
|
"skipped_files": skipped_files,
|
||||||
|
"extension_counts": extension_counts
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save report if configured
|
||||||
|
if save_report:
|
||||||
|
report_path = Path(work_dir) / f"file_analysis.{report_format}"
|
||||||
|
if report_format == "json":
|
||||||
|
with open(report_path, 'w') as f:
|
||||||
|
json.dump(results, f, indent=2)
|
||||||
|
elif report_format == "csv":
|
||||||
|
with open(report_path, 'w', newline='') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(["Extension", "Count"])
|
||||||
|
for ext, count in sorted(extension_counts.items()):
|
||||||
|
writer.writerow([ext, count])
|
||||||
|
else: # txt
|
||||||
|
with open(report_path, 'w') as f:
|
||||||
|
f.write(f"File Analysis Report\n")
|
||||||
|
f.write(f"Generated: {results['scan_time']}\n\n")
|
||||||
|
f.write(f"Total Files: {total_files}\n")
|
||||||
|
f.write(f"Total Size: {total_size:,} bytes\n")
|
||||||
|
f.write(f"Skipped Files: {skipped_files}\n\n")
|
||||||
|
f.write("Extension Counts:\n")
|
||||||
|
for ext, count in sorted(extension_counts.items()):
|
||||||
|
f.write(f"{ext}: {count}\n")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"data": {
|
"data": results,
|
||||||
"total_files": total_files,
|
"output": f"Found {total_files:,} files ({total_size:,} bytes)\n" +
|
||||||
"extension_counts": extension_counts
|
f"Skipped {skipped_files} files\n\n" +
|
||||||
},
|
"Extensions:\n" + "\n".join(
|
||||||
"output": f"Found {total_files} files\n" + "\n".join(
|
f"{ext}: {count:,} files"
|
||||||
f"{ext}: {count} files"
|
|
||||||
for ext, count in sorted(extension_counts.items())
|
for ext, count in sorted(extension_counts.items())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -45,5 +105,4 @@ class FileCounter(BaseScript):
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"error": str(e)
|
"error": str(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,46 +3,93 @@ from backend.script_groups.base_script import BaseScript
|
||||||
import psutil
|
import psutil
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
class SystemInfo(BaseScript):
|
class SystemInfo(BaseScript):
|
||||||
"""
|
"""
|
||||||
System Information
|
System Monitor
|
||||||
Collects and displays basic system information
|
Collects and analyzes system performance metrics
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def run(self, work_dir: str, profile: dict) -> dict:
|
def run(self, work_dir: str, profile: dict) -> dict:
|
||||||
try:
|
try:
|
||||||
|
# Get configuration from the same config.json
|
||||||
|
config = self.get_config(work_dir, "example_group")
|
||||||
|
save_report = config.get("save_report", True)
|
||||||
|
report_format = config.get("report_format", "json")
|
||||||
|
|
||||||
# Collect system information
|
# Collect system information
|
||||||
|
cpu_freq = psutil.cpu_freq()
|
||||||
|
memory = psutil.virtual_memory()
|
||||||
|
disk = psutil.disk_usage(work_dir)
|
||||||
|
|
||||||
info = {
|
info = {
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"cores": psutil.cpu_count(),
|
"cores": psutil.cpu_count(),
|
||||||
"usage": psutil.cpu_percent(interval=1),
|
"physical_cores": psutil.cpu_count(logical=False),
|
||||||
|
"frequency": {
|
||||||
|
"current": round(cpu_freq.current, 2) if cpu_freq else None,
|
||||||
|
"min": round(cpu_freq.min, 2) if cpu_freq else None,
|
||||||
|
"max": round(cpu_freq.max, 2) if cpu_freq else None
|
||||||
|
},
|
||||||
|
"usage_percent": psutil.cpu_percent(interval=1)
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"total": psutil.virtual_memory().total,
|
"total": memory.total,
|
||||||
"available": psutil.virtual_memory().available,
|
"available": memory.available,
|
||||||
"percent": psutil.virtual_memory().percent,
|
"used": memory.used,
|
||||||
|
"percent": memory.percent
|
||||||
},
|
},
|
||||||
"disk": {
|
"disk": {
|
||||||
"total": psutil.disk_usage(work_dir).total,
|
"total": disk.total,
|
||||||
"free": psutil.disk_usage(work_dir).free,
|
"used": disk.used,
|
||||||
"percent": psutil.disk_usage(work_dir).percent,
|
"free": disk.free,
|
||||||
|
"percent": disk.percent
|
||||||
},
|
},
|
||||||
"timestamp": datetime.now().isoformat()
|
"network": {
|
||||||
|
"interfaces": list(psutil.net_if_addrs().keys()),
|
||||||
|
"connections": len(psutil.net_connections())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Save to work directory if configured
|
# Save report if configured
|
||||||
config = self.get_config(work_dir, "example_group")
|
if save_report:
|
||||||
if config.get("save_system_info", False):
|
report_path = Path(work_dir) / f"system_info.{report_format}"
|
||||||
output_file = Path(work_dir) / "system_info.json"
|
if report_format == "json":
|
||||||
with open(output_file, 'w') as f:
|
with open(report_path, 'w') as f:
|
||||||
json.dump(info, f, indent=2)
|
json.dump(info, f, indent=2)
|
||||||
|
elif report_format == "csv":
|
||||||
|
with open(report_path, 'w', newline='') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(["Metric", "Value"])
|
||||||
|
writer.writerow(["CPU Cores", info["cpu"]["cores"]])
|
||||||
|
writer.writerow(["CPU Usage", f"{info['cpu']['usage_percent']}%"])
|
||||||
|
writer.writerow(["Memory Total", f"{info['memory']['total']:,} bytes"])
|
||||||
|
writer.writerow(["Memory Used", f"{info['memory']['percent']}%"])
|
||||||
|
writer.writerow(["Disk Total", f"{info['disk']['total']:,} bytes"])
|
||||||
|
writer.writerow(["Disk Used", f"{info['disk']['percent']}%"])
|
||||||
|
else: # txt
|
||||||
|
with open(report_path, 'w') as f:
|
||||||
|
f.write(f"System Information Report\n")
|
||||||
|
f.write(f"Generated: {info['timestamp']}\n\n")
|
||||||
|
f.write(f"CPU:\n")
|
||||||
|
f.write(f" Cores: {info['cpu']['cores']}\n")
|
||||||
|
f.write(f" Usage: {info['cpu']['usage_percent']}%\n\n")
|
||||||
|
f.write(f"Memory:\n")
|
||||||
|
f.write(f" Total: {info['memory']['total']:,} bytes\n")
|
||||||
|
f.write(f" Used: {info['memory']['percent']}%\n\n")
|
||||||
|
f.write(f"Disk:\n")
|
||||||
|
f.write(f" Total: {info['disk']['total']:,} bytes\n")
|
||||||
|
f.write(f" Used: {info['disk']['percent']}%\n")
|
||||||
|
|
||||||
# Format output
|
# Format output
|
||||||
output = f"""System Information:
|
output = f"""System Information:
|
||||||
CPU: {info['cpu']['cores']} cores ({info['cpu']['usage']}% usage)
|
CPU: {info['cpu']['cores']} cores ({info['cpu']['usage_percent']}% usage)
|
||||||
Memory: {info['memory']['percent']}% used
|
Memory: {info['memory']['percent']}% used ({info['memory']['available']:,} bytes available)
|
||||||
Disk: {info['disk']['percent']}% used"""
|
Disk: {info['disk']['percent']}% used ({info['disk']['free']:,} bytes free)
|
||||||
|
Network Interfaces: {', '.join(info['network']['interfaces'])}
|
||||||
|
Active Connections: {info['network']['connections']}"""
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
|
@ -54,4 +101,4 @@ Disk: {info['disk']['percent']}% used"""
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"error": str(e)
|
"error": str(e)
|
||||||
}
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
|
class ClaudeProjectOrganizer:
|
||||||
|
def __init__(self):
|
||||||
|
self.source_dir = Path.cwd()
|
||||||
|
self.claude_dir = self.source_dir / 'claude'
|
||||||
|
self.file_mapping = {}
|
||||||
|
|
||||||
|
def should_skip_directory(self, dir_name):
|
||||||
|
skip_dirs = {'.git', '__pycache__', 'venv', 'env', '.pytest_cache', '.vscode', 'claude'}
|
||||||
|
return dir_name in skip_dirs
|
||||||
|
|
||||||
|
def get_comment_prefix(self, file_extension):
|
||||||
|
"""Determina el prefijo de comentario según la extensión del archivo"""
|
||||||
|
comment_styles = {
|
||||||
|
'.py': '#',
|
||||||
|
'.js': '//',
|
||||||
|
'.css': '/*',
|
||||||
|
'.html': '<!--',
|
||||||
|
'.scss': '//',
|
||||||
|
'.less': '//',
|
||||||
|
'.tsx': '//',
|
||||||
|
'.ts': '//',
|
||||||
|
'.jsx': '//',
|
||||||
|
}
|
||||||
|
return comment_styles.get(file_extension.lower(), None)
|
||||||
|
|
||||||
|
def get_comment_suffix(self, file_extension):
|
||||||
|
"""Determina el sufijo de comentario si es necesario"""
|
||||||
|
comment_suffixes = {
|
||||||
|
'.css': ' */',
|
||||||
|
'.html': ' -->',
|
||||||
|
}
|
||||||
|
return comment_suffixes.get(file_extension.lower(), '')
|
||||||
|
|
||||||
|
def normalize_path(self, path_str: str) -> str:
|
||||||
|
"""Normaliza la ruta usando forward slashes"""
|
||||||
|
return str(path_str).replace('\\', '/')
|
||||||
|
|
||||||
|
def check_existing_path_comment(self, content: str, normalized_path: str, comment_prefix: str) -> bool:
|
||||||
|
"""Verifica si ya existe un comentario con la ruta en el archivo"""
|
||||||
|
# Escapar caracteres especiales en el prefijo de comentario para regex
|
||||||
|
escaped_prefix = re.escape(comment_prefix)
|
||||||
|
|
||||||
|
# Crear patrones para buscar tanto forward como backward slashes
|
||||||
|
forward_pattern = f"{escaped_prefix}\\s*{re.escape(normalized_path)}\\b"
|
||||||
|
backward_path = normalized_path.replace('/', '\\\\') # Doble backslash para el patrón
|
||||||
|
backward_pattern = f"{escaped_prefix}\\s*{re.escape(backward_path)}"
|
||||||
|
|
||||||
|
# Buscar en las primeras líneas del archivo
|
||||||
|
first_lines = content.split('\n')[:5]
|
||||||
|
for line in first_lines:
|
||||||
|
if (re.search(forward_pattern, line) or
|
||||||
|
re.search(backward_pattern, line)):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_path_comment(self, file_path: Path, content: str) -> str:
|
||||||
|
"""Agrega un comentario con la ruta al inicio del archivo si no existe"""
|
||||||
|
relative_path = file_path.relative_to(self.source_dir)
|
||||||
|
normalized_path = self.normalize_path(relative_path)
|
||||||
|
comment_prefix = self.get_comment_prefix(file_path.suffix)
|
||||||
|
|
||||||
|
if comment_prefix is None:
|
||||||
|
return content
|
||||||
|
|
||||||
|
comment_suffix = self.get_comment_suffix(file_path.suffix)
|
||||||
|
|
||||||
|
# Verificar si ya existe el comentario
|
||||||
|
if self.check_existing_path_comment(content, normalized_path, comment_prefix):
|
||||||
|
print(f" - Comentario de ruta ya existe en {file_path}")
|
||||||
|
return content
|
||||||
|
|
||||||
|
path_comment = f"{comment_prefix} {normalized_path}{comment_suffix}\n"
|
||||||
|
|
||||||
|
# Para archivos HTML, insertar después del doctype si existe
|
||||||
|
if file_path.suffix.lower() == '.html':
|
||||||
|
if content.lower().startswith('<!doctype'):
|
||||||
|
doctype_end = content.find('>') + 1
|
||||||
|
return content[:doctype_end] + '\n' + path_comment + content[doctype_end:]
|
||||||
|
|
||||||
|
return path_comment + content
|
||||||
|
|
||||||
|
def clean_claude_directory(self):
|
||||||
|
if self.claude_dir.exists():
|
||||||
|
shutil.rmtree(self.claude_dir)
|
||||||
|
self.claude_dir.mkdir()
|
||||||
|
print(f"Directorio claude limpiado: {self.claude_dir}")
|
||||||
|
|
||||||
|
def copy_files(self):
|
||||||
|
self.clean_claude_directory()
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(self.source_dir):
|
||||||
|
dirs[:] = [d for d in dirs if not self.should_skip_directory(d)]
|
||||||
|
current_path = Path(root)
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
file_path = current_path / file
|
||||||
|
|
||||||
|
if file.endswith(('.py', '.js', '.css', '.html', '.json', '.yml', '.yaml',
|
||||||
|
'.tsx', '.ts', '.jsx', '.scss', '.less')):
|
||||||
|
target_path = self.claude_dir / file
|
||||||
|
|
||||||
|
# Si el archivo ya existe en el directorio claude, agregar un sufijo numérico
|
||||||
|
if target_path.exists():
|
||||||
|
base = target_path.stem
|
||||||
|
ext = target_path.suffix
|
||||||
|
counter = 1
|
||||||
|
while target_path.exists():
|
||||||
|
target_path = self.claude_dir / f"{base}_{counter}{ext}"
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Leer el contenido del archivo
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Agregar el comentario con la ruta si no existe
|
||||||
|
modified_content = self.add_path_comment(file_path, content)
|
||||||
|
|
||||||
|
# Escribir el nuevo contenido
|
||||||
|
with open(target_path, 'w', encoding='utf-8', newline='\n') as f:
|
||||||
|
f.write(modified_content)
|
||||||
|
|
||||||
|
self.file_mapping[str(file_path)] = target_path.name
|
||||||
|
print(f"Copiado: {file_path} -> {target_path}")
|
||||||
|
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
print(f"Advertencia: No se pudo procesar {file_path} como texto. Copiando sin modificar...")
|
||||||
|
shutil.copy2(file_path, target_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error procesando {file_path}: {str(e)}")
|
||||||
|
|
||||||
|
def generate_tree_report(self):
|
||||||
|
"""Genera el reporte en formato árbol visual"""
|
||||||
|
report = ["Estructura del proyecto original:\n"]
|
||||||
|
|
||||||
|
def add_to_report(path, prefix="", is_last=True):
|
||||||
|
report.append(prefix + ("└── " if is_last else "├── ") + path.name)
|
||||||
|
|
||||||
|
if path.is_dir() and not self.should_skip_directory(path.name):
|
||||||
|
children = sorted(path.iterdir(), key=lambda x: (x.is_file(), x.name))
|
||||||
|
children = [c for c in children if not (c.is_dir() and self.should_skip_directory(c.name))]
|
||||||
|
|
||||||
|
for i, child in enumerate(children):
|
||||||
|
is_last_child = i == len(children) - 1
|
||||||
|
new_prefix = prefix + (" " if is_last else "│ ")
|
||||||
|
add_to_report(child, new_prefix, is_last_child)
|
||||||
|
|
||||||
|
add_to_report(self.source_dir)
|
||||||
|
|
||||||
|
report_path = self.claude_dir / "project_structure.txt"
|
||||||
|
with open(report_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join(report))
|
||||||
|
print(f"\nReporte generado en: {report_path}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
print("Iniciando organización de archivos para Claude...")
|
||||||
|
organizer = ClaudeProjectOrganizer()
|
||||||
|
organizer.copy_files()
|
||||||
|
organizer.generate_tree_report()
|
||||||
|
print("\n¡Proceso completado exitosamente!")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nError durante la ejecución: {str(e)}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -1,100 +1,12 @@
|
||||||
/* frontend/static/css/style.css - Add these styles */
|
/* frontend/static/css/style.css */
|
||||||
|
|
||||||
.script-group {
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1rem;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-group-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-group-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: start;
|
|
||||||
padding: 1rem;
|
|
||||||
background: var(--bg-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-info {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-info h4 {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
color: var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-info p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-btn {
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.run-btn {
|
|
||||||
background-color: #27ae60;
|
|
||||||
}
|
|
||||||
|
|
||||||
.run-btn:hover {
|
|
||||||
background-color: #219a52;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-editor {
|
|
||||||
width: 100%;
|
|
||||||
font-family: monospace;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Solo mantenemos estilos específicos que no podemos lograr fácilmente con Tailwind */
|
||||||
.output-area {
|
.output-area {
|
||||||
background: white;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
font-family: monospace;
|
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-scripts {
|
/* Estilos para modales */
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add to your existing style.css */
|
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -111,82 +23,9 @@
|
||||||
.modal-content {
|
.modal-content {
|
||||||
background: white;
|
background: white;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
border-radius: 8px;
|
border-radius: 0.5rem;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input,
|
|
||||||
.form-group select,
|
|
||||||
.form-group textarea {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-info {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-configs {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-config {
|
|
||||||
margin-top: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
background: var(--bg-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-config pre {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* frontend/static/css/style.css */
|
|
||||||
|
|
||||||
.script-group-selector {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 1rem;
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-group-selector label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-group-selector select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#scriptList {
|
|
||||||
display: none;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
}
|
|
@ -3,17 +3,86 @@
|
||||||
// Global state
|
// Global state
|
||||||
let currentProfile = null;
|
let currentProfile = null;
|
||||||
|
|
||||||
// Initialize when page loads
|
async function initializeApp() {
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
|
||||||
try {
|
try {
|
||||||
await loadProfiles();
|
console.log('Initializing app...');
|
||||||
await loadScriptGroups();
|
|
||||||
|
// Cargar perfiles
|
||||||
|
const profiles = await apiRequest('/profiles');
|
||||||
|
console.log('Profiles loaded:', profiles);
|
||||||
|
|
||||||
|
// Obtener último perfil usado
|
||||||
|
const lastProfileId = localStorage.getItem('lastProfileId') || 'default';
|
||||||
|
console.log('Last profile ID:', lastProfileId);
|
||||||
|
|
||||||
|
// Actualizar selector de perfiles
|
||||||
|
updateProfileSelector(profiles);
|
||||||
|
|
||||||
|
// Seleccionar el último perfil usado
|
||||||
|
const selectedProfile = profiles.find(p => p.id === lastProfileId) || profiles[0];
|
||||||
|
if (selectedProfile) {
|
||||||
|
console.log('Selecting profile:', selectedProfile.id);
|
||||||
|
await selectProfile(selectedProfile.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cargar grupos de scripts y restaurar la última selección
|
||||||
|
await restoreScriptGroup();
|
||||||
|
|
||||||
|
// Actualizar la interfaz
|
||||||
updateWorkDirDisplay();
|
updateWorkDirDisplay();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Initialization error:', error);
|
console.error('Error initializing app:', error);
|
||||||
showError('Failed to initialize application');
|
showError('Failed to initialize application');
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
async function restoreScriptGroup() {
|
||||||
|
try {
|
||||||
|
// Primero cargar los grupos disponibles
|
||||||
|
await loadScriptGroups();
|
||||||
|
|
||||||
|
// Luego intentar restaurar el último grupo seleccionado
|
||||||
|
const lastGroupId = localStorage.getItem('lastGroupId');
|
||||||
|
if (lastGroupId) {
|
||||||
|
console.log('Restoring last group:', lastGroupId);
|
||||||
|
const groupSelect = document.getElementById('groupSelect');
|
||||||
|
if (groupSelect) {
|
||||||
|
groupSelect.value = lastGroupId;
|
||||||
|
if (groupSelect.value) { // Verifica que el valor se haya establecido correctamente
|
||||||
|
await loadGroupScripts(lastGroupId);
|
||||||
|
} else {
|
||||||
|
console.log('Selected group no longer exists:', lastGroupId);
|
||||||
|
localStorage.removeItem('lastGroupId');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error restoring script group:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Función para restaurar el último estado
|
||||||
|
async function restoreLastState() {
|
||||||
|
const lastProfileId = localStorage.getItem('lastProfileId');
|
||||||
|
const lastGroupId = localStorage.getItem('lastGroupId');
|
||||||
|
|
||||||
|
console.log('Restoring last state:', { lastProfileId, lastGroupId });
|
||||||
|
|
||||||
|
if (lastProfileId) {
|
||||||
|
const profileSelect = document.getElementById('profileSelect');
|
||||||
|
profileSelect.value = lastProfileId;
|
||||||
|
await selectProfile(lastProfileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastGroupId) {
|
||||||
|
const groupSelect = document.getElementById('groupSelect');
|
||||||
|
if (groupSelect) {
|
||||||
|
groupSelect.value = lastGroupId;
|
||||||
|
await loadGroupScripts(lastGroupId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// API functions
|
// API functions
|
||||||
async function apiRequest(endpoint, options = {}) {
|
async function apiRequest(endpoint, options = {}) {
|
||||||
|
@ -39,40 +108,69 @@ async function apiRequest(endpoint, options = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile functions
|
|
||||||
async function loadProfiles() {
|
async function loadProfiles() {
|
||||||
try {
|
try {
|
||||||
const profiles = await apiRequest('/profiles');
|
const profiles = await apiRequest('/profiles');
|
||||||
updateProfileSelector(profiles);
|
updateProfileSelector(profiles);
|
||||||
|
|
||||||
// Select first profile if none selected
|
// Obtener último perfil usado
|
||||||
if (!currentProfile) {
|
const lastProfileId = localStorage.getItem('lastProfileId');
|
||||||
const defaultProfile = profiles.find(p => p.id === 'default') || profiles[0];
|
|
||||||
if (defaultProfile) {
|
// Seleccionar perfil guardado o el default
|
||||||
await selectProfile(defaultProfile.id);
|
const defaultProfile = profiles.find(p => p.id === (lastProfileId || 'default')) || profiles[0];
|
||||||
}
|
if (defaultProfile) {
|
||||||
|
await selectProfile(defaultProfile.id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError('Failed to load profiles');
|
showError('Failed to load profiles');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function selectProfile(profileId) {
|
||||||
|
try {
|
||||||
|
console.log('Selecting profile:', profileId);
|
||||||
|
currentProfile = await apiRequest(`/profiles/${profileId}`);
|
||||||
|
|
||||||
|
// Guardar en localStorage
|
||||||
|
localStorage.setItem('lastProfileId', profileId);
|
||||||
|
console.log('Profile ID saved to storage:', profileId);
|
||||||
|
|
||||||
|
// Actualizar explícitamente el valor del combo
|
||||||
|
const select = document.getElementById('profileSelect');
|
||||||
|
if (select) {
|
||||||
|
select.value = profileId;
|
||||||
|
console.log('Updated profileSelect value to:', profileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWorkDirDisplay();
|
||||||
|
|
||||||
|
// Recargar scripts con el último grupo seleccionado
|
||||||
|
await restoreScriptGroup();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in selectProfile:', error);
|
||||||
|
showError('Failed to load profile');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when page loads
|
||||||
|
document.addEventListener('DOMContentLoaded', initializeApp);
|
||||||
|
|
||||||
function updateProfileSelector(profiles) {
|
function updateProfileSelector(profiles) {
|
||||||
const select = document.getElementById('profileSelect');
|
const select = document.getElementById('profileSelect');
|
||||||
|
const lastProfileId = localStorage.getItem('lastProfileId') || 'default';
|
||||||
|
|
||||||
|
console.log('Updating profile selector. Last profile ID:', lastProfileId);
|
||||||
|
|
||||||
|
// Construir las opciones
|
||||||
select.innerHTML = profiles.map(profile => `
|
select.innerHTML = profiles.map(profile => `
|
||||||
<option value="${profile.id}" ${profile.id === currentProfile?.id ? 'selected' : ''}>
|
<option value="${profile.id}" ${profile.id === lastProfileId ? 'selected' : ''}>
|
||||||
${profile.name}
|
${profile.name}
|
||||||
</option>
|
</option>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
|
||||||
|
// Asegurar que el valor seleccionado sea correcto
|
||||||
async function selectProfile(profileId) {
|
select.value = lastProfileId;
|
||||||
try {
|
console.log('Set profileSelect value to:', lastProfileId);
|
||||||
currentProfile = await apiRequest(`/profiles/${profileId}`);
|
|
||||||
updateWorkDirDisplay();
|
|
||||||
} catch (error) {
|
|
||||||
showError('Failed to load profile');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeProfile() {
|
async function changeProfile() {
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
// static/js/modal.js
|
||||||
|
function createModal(title, content, onSave = null) {
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50';
|
||||||
|
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4">
|
||||||
|
<div class="px-6 py-4 border-b border-gray-200">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">${title}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 py-4">
|
||||||
|
${content}
|
||||||
|
</div>
|
||||||
|
<div class="px-6 py-4 bg-gray-50 rounded-b-lg flex justify-end gap-3">
|
||||||
|
<button onclick="closeModal(this)"
|
||||||
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
${onSave ? `
|
||||||
|
<button onclick="saveModal(this)"
|
||||||
|
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
return modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal(button) {
|
||||||
|
const modal = button.closest('.fixed');
|
||||||
|
if (modal) {
|
||||||
|
modal.remove();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// frontend/static/js/profile.js
|
let selectedProfileId = localStorage.getItem('selectedProfileId') || 'default';
|
||||||
|
|
||||||
// Profile functions
|
// Profile functions
|
||||||
async function loadProfiles() {
|
async function loadProfiles() {
|
||||||
|
@ -6,13 +6,19 @@ async function loadProfiles() {
|
||||||
const profiles = await apiRequest('/profiles');
|
const profiles = await apiRequest('/profiles');
|
||||||
updateProfileSelector(profiles);
|
updateProfileSelector(profiles);
|
||||||
|
|
||||||
// Select first profile if none selected
|
// Asegurarse de que se seleccione el perfil guardado
|
||||||
if (!currentProfile) {
|
const selectElement = document.getElementById('profileSelect');
|
||||||
const defaultProfile = profiles.find(p => p.id === 'default') || profiles[0];
|
if (profiles[selectedProfileId]) {
|
||||||
if (defaultProfile) {
|
selectElement.value = selectedProfileId;
|
||||||
await selectProfile(defaultProfile.id);
|
await selectProfile(selectedProfileId); // Cargar el perfil seleccionado
|
||||||
}
|
} else {
|
||||||
|
// Si el perfil guardado ya no existe, usar el default
|
||||||
|
selectedProfileId = 'default';
|
||||||
|
selectElement.value = 'default';
|
||||||
|
await selectProfile('default');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('selectedProfileId', selectedProfileId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError('Failed to load profiles');
|
showError('Failed to load profiles');
|
||||||
}
|
}
|
||||||
|
@ -156,14 +162,94 @@ async function saveProfile(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function editProfile() {
|
// static/js/profile.js
|
||||||
|
async function editProfile() {
|
||||||
if (!currentProfile) {
|
if (!currentProfile) {
|
||||||
showError('No profile selected');
|
showError('No profile selected');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showProfileEditor(currentProfile);
|
|
||||||
|
const content = `
|
||||||
|
<form id="profileForm" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Profile ID</label>
|
||||||
|
<input type="text" name="id" value="${currentProfile.id}"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||||
|
${currentProfile.id === 'default' ? 'readonly' : ''}>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Name</label>
|
||||||
|
<input type="text" name="name" value="${currentProfile.name}"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Work Directory</label>
|
||||||
|
<input type="text" name="work_dir" value="${currentProfile.work_dir}" readonly
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm bg-gray-50">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">LLM Model</label>
|
||||||
|
<select name="llm_model"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
<option value="gpt-4" ${currentProfile.llm_settings?.model === 'gpt-4' ? 'selected' : ''}>GPT-4</option>
|
||||||
|
<option value="gpt-3.5-turbo" ${currentProfile.llm_settings?.model === 'gpt-3.5-turbo' ? 'selected' : ''}>GPT-3.5 Turbo</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">API Key</label>
|
||||||
|
<input type="password" name="api_key" value="${currentProfile.llm_settings?.api_key || ''}"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Temperature</label>
|
||||||
|
<input type="number" name="temperature" value="${currentProfile.llm_settings?.temperature || 0.7}"
|
||||||
|
min="0" max="2" step="0.1"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const modal = createModal('Edit Profile', content, true);
|
||||||
|
modal.querySelector('[onclick="saveModal(this)"]').onclick = async () => {
|
||||||
|
await saveProfile(modal);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveProfile(modal) {
|
||||||
|
const form = modal.querySelector('#profileForm');
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
const profileData = {
|
||||||
|
id: formData.get('id'),
|
||||||
|
name: formData.get('name'),
|
||||||
|
work_dir: formData.get('work_dir'),
|
||||||
|
llm_settings: {
|
||||||
|
model: formData.get('llm_model'),
|
||||||
|
api_key: formData.get('api_key'),
|
||||||
|
temperature: parseFloat(formData.get('temperature'))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiRequest(`/profiles/${currentProfile.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(profileData)
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadProfiles();
|
||||||
|
closeModal(modal.querySelector('button'));
|
||||||
|
showSuccess('Profile updated successfully');
|
||||||
|
} catch (error) {
|
||||||
|
showError('Failed to update profile');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function newProfile() {
|
function newProfile() {
|
||||||
showProfileEditor();
|
showProfileEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onProfileChange(event) {
|
||||||
|
selectedProfileId = event.target.value;
|
||||||
|
localStorage.setItem('selectedProfileId', selectedProfileId);
|
||||||
|
await selectProfile(selectedProfileId);
|
||||||
}
|
}
|
|
@ -13,52 +13,228 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
await loadScriptGroups();
|
await loadScriptGroups();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load available script groups
|
|
||||||
async function loadScriptGroups() {
|
async function loadScriptGroups() {
|
||||||
try {
|
try {
|
||||||
|
// Obtener los grupos desde el servidor
|
||||||
const groups = await apiRequest('/script-groups');
|
const groups = await apiRequest('/script-groups');
|
||||||
|
console.log('Loaded script groups:', groups);
|
||||||
|
|
||||||
|
// Obtener el selector y el último grupo seleccionado
|
||||||
const select = document.getElementById('groupSelect');
|
const select = document.getElementById('groupSelect');
|
||||||
|
const lastGroupId = localStorage.getItem('lastGroupId');
|
||||||
|
console.log('Last group ID:', lastGroupId);
|
||||||
|
|
||||||
|
// Remover event listener anterior si existe
|
||||||
|
select.removeEventListener('change', handleGroupChange);
|
||||||
|
|
||||||
|
// Construir las opciones
|
||||||
select.innerHTML = `
|
select.innerHTML = `
|
||||||
<option value="">Select a group...</option>
|
<option value="">Select a group...</option>
|
||||||
${groups.map(group => `
|
${groups.map(group => `
|
||||||
<option value="${group.id}">${group.name}</option>
|
<option value="${group.id}" ${group.id === lastGroupId ? 'selected' : ''}>
|
||||||
|
${group.name}
|
||||||
|
</option>
|
||||||
`).join('')}
|
`).join('')}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Agregar event listener para cambios
|
||||||
|
select.addEventListener('change', handleGroupChange);
|
||||||
|
console.log('Added change event listener to groupSelect');
|
||||||
|
|
||||||
|
// Si hay un grupo guardado, cargarlo
|
||||||
|
if (lastGroupId) {
|
||||||
|
console.log('Loading last group scripts:', lastGroupId);
|
||||||
|
await loadGroupScripts(lastGroupId);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Failed to load script groups:', error);
|
||||||
showError('Failed to load script groups');
|
showError('Failed to load script groups');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load scripts for selected group
|
|
||||||
|
// Función para manejar el cambio de grupo
|
||||||
|
async function handleGroupChange(event) {
|
||||||
|
const groupId = event.target.value;
|
||||||
|
console.log('Group selection changed:', groupId);
|
||||||
|
|
||||||
|
if (groupId) {
|
||||||
|
localStorage.setItem('lastGroupId', groupId);
|
||||||
|
console.log('Saved lastGroupId:', groupId);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('lastGroupId');
|
||||||
|
console.log('Removed lastGroupId');
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadGroupScripts(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar función de cambio de perfil para mantener la persistencia
|
||||||
|
async function changeProfile() {
|
||||||
|
const select = document.getElementById('profileSelect');
|
||||||
|
if (select.value) {
|
||||||
|
await selectProfile(select.value);
|
||||||
|
localStorage.setItem('lastProfileId', select.value);
|
||||||
|
|
||||||
|
// Al cambiar de perfil, intentamos mantener el último grupo seleccionado
|
||||||
|
const lastGroupId = localStorage.getItem('lastGroupId');
|
||||||
|
if (lastGroupId) {
|
||||||
|
const groupSelect = document.getElementById('groupSelect');
|
||||||
|
if (groupSelect) {
|
||||||
|
groupSelect.value = lastGroupId;
|
||||||
|
await loadGroupScripts(lastGroupId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadGroupScripts(groupId) {
|
async function loadGroupScripts(groupId) {
|
||||||
const scriptList = document.getElementById('scriptList');
|
const scriptList = document.getElementById('scriptList');
|
||||||
|
|
||||||
if (!groupId) {
|
if (!groupId) {
|
||||||
scriptList.style.display = 'none';
|
scriptList.style.display = 'none';
|
||||||
|
localStorage.removeItem('lastGroupId'); // Limpiar selección
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardar grupo seleccionado
|
||||||
|
localStorage.setItem('lastGroupId', groupId);
|
||||||
|
console.log('Group saved:', groupId);
|
||||||
|
|
||||||
|
if (!currentProfile?.work_dir) {
|
||||||
|
scriptList.innerHTML = `
|
||||||
|
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-sm text-yellow-700">
|
||||||
|
Please select a work directory first
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
scriptList.style.display = 'block';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const scripts = await apiRequest(`/script-groups/${groupId}/scripts`);
|
console.log('Loading data for group:', groupId);
|
||||||
|
|
||||||
|
// Actualizar el selector para reflejar la selección actual
|
||||||
|
const groupSelect = document.getElementById('groupSelect');
|
||||||
|
if (groupSelect && groupSelect.value !== groupId) {
|
||||||
|
groupSelect.value = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
scriptList.innerHTML = scripts.map(script => `
|
// Cargar y loguear scripts
|
||||||
<div class="script-item">
|
let groupScripts, configSchema;
|
||||||
<div class="script-info">
|
try {
|
||||||
<h4>${script.name}</h4>
|
groupScripts = await apiRequest(`/script-groups/${groupId}/scripts`);
|
||||||
<p>${script.description || 'No description available'}</p>
|
console.log('Scripts loaded:', groupScripts);
|
||||||
</div>
|
} catch (e) {
|
||||||
<div class="script-actions">
|
console.error('Error loading scripts:', e);
|
||||||
<button onclick="runScript('${groupId}', '${script.id}')" class="run-btn">
|
throw e;
|
||||||
Run
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
configSchema = await apiRequest(`/script-groups/${groupId}/config-schema`);
|
||||||
|
console.log('Config schema loaded:', configSchema);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading config schema:', e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intentar cargar configuración actual
|
||||||
|
let currentConfig = {};
|
||||||
|
try {
|
||||||
|
currentConfig = await apiRequest(
|
||||||
|
`/workdir-config/${encodeURIComponent(currentProfile.work_dir)}/group/${groupId}`
|
||||||
|
);
|
||||||
|
console.log('Current config loaded:', currentConfig);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('No existing configuration found, using defaults');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar que tenemos los datos necesarios
|
||||||
|
if (!groupScripts || !configSchema) {
|
||||||
|
throw new Error('Failed to load required data');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Rendering UI with:', {
|
||||||
|
groupScripts,
|
||||||
|
configSchema,
|
||||||
|
currentConfig
|
||||||
|
});
|
||||||
|
|
||||||
|
scriptList.innerHTML = `
|
||||||
|
<!-- Sección de Configuración -->
|
||||||
|
<div class="mb-6 bg-white shadow sm:rounded-lg">
|
||||||
|
<div class="border-b border-gray-200 p-4 flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">
|
||||||
|
${configSchema.group_name || 'Configuration'}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">${configSchema.description || ''}</p>
|
||||||
|
</div>
|
||||||
|
<button onclick="editConfigSchema('${groupId}')"
|
||||||
|
class="rounded-md bg-gray-100 px-3 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-200 flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||||
|
</svg>
|
||||||
|
Edit Schema
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<form id="groupConfigForm" class="grid grid-cols-2 gap-4">
|
||||||
|
${Object.entries(configSchema.config_schema || {}).map(([key, field]) => `
|
||||||
|
<div class="space-y-2 col-span-2">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">
|
||||||
|
${field.description}
|
||||||
|
</label>
|
||||||
|
${generateFormField(key, field, currentConfig[key])}
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
<div class="col-span-2 flex justify-end pt-4">
|
||||||
|
<button type="submit"
|
||||||
|
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
||||||
|
Save Configuration
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
|
||||||
|
<!-- Lista de Scripts -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
${groupScripts.map(script => `
|
||||||
|
<div class="bg-white px-4 py-3 rounded-md border border-gray-200 hover:border-gray-300 shadow sm:rounded-lg">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-medium text-gray-900">${script.name || script.id}</h4>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">${script.description || 'No description available'}</p>
|
||||||
|
</div>
|
||||||
|
<button onclick="runScript('${groupId}', '${script.id}')"
|
||||||
|
class="ml-4 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
||||||
|
Run
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>`;
|
||||||
|
|
||||||
scriptList.style.display = 'block';
|
scriptList.style.display = 'block';
|
||||||
|
|
||||||
|
// Agregar evento para guardar configuración
|
||||||
|
const form = document.getElementById('groupConfigForm');
|
||||||
|
form.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await saveGroupConfig(groupId, form);
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError('Failed to load scripts for group');
|
console.error('Error in loadGroupScripts:', error);
|
||||||
|
showError('Failed to load scripts and configuration');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,18 +348,443 @@ function showGroupConfigEditor(groupId, config) {
|
||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save group configuration
|
function generateFormField(key, field, value) {
|
||||||
async function saveGroupConfig(event, groupId) {
|
const currentValue = value !== undefined ? value : field.default;
|
||||||
event.preventDefault();
|
|
||||||
|
switch (field.type) {
|
||||||
|
case 'string':
|
||||||
|
return `
|
||||||
|
<input type="text"
|
||||||
|
name="${key}"
|
||||||
|
value="${currentValue || ''}"
|
||||||
|
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
`;
|
||||||
|
case 'number':
|
||||||
|
return `
|
||||||
|
<input type="number"
|
||||||
|
name="${key}"
|
||||||
|
value="${currentValue || 0}"
|
||||||
|
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
`;
|
||||||
|
case 'boolean':
|
||||||
|
return `
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="${key}"
|
||||||
|
${currentValue ? 'checked' : ''}
|
||||||
|
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
||||||
|
<span class="ml-2 text-sm text-gray-500">Enable</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
case 'select':
|
||||||
|
return `
|
||||||
|
<select name="${key}"
|
||||||
|
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
${field.options.map(opt => `
|
||||||
|
<option value="${opt}" ${currentValue === opt ? 'selected' : ''}>
|
||||||
|
${opt}
|
||||||
|
</option>
|
||||||
|
`).join('')}
|
||||||
|
</select>
|
||||||
|
`;
|
||||||
|
default:
|
||||||
|
return `<input type="text" name="${key}" value="${currentValue || ''}" class="w-full">`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadGroupScripts(groupId) {
|
||||||
|
const scriptList = document.getElementById('scriptList');
|
||||||
|
|
||||||
|
if (!groupId) {
|
||||||
|
scriptList.style.display = 'none';
|
||||||
|
localStorage.removeItem('lastGroupId');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentProfile?.work_dir) {
|
||||||
|
scriptList.innerHTML = `
|
||||||
|
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-sm text-yellow-700">
|
||||||
|
Please select a work directory first
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
scriptList.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const configText = document.getElementById('configData').value;
|
console.log('Loading data for group:', groupId);
|
||||||
const config = JSON.parse(configText);
|
|
||||||
|
// Cargar y loguear scripts
|
||||||
|
let groupScripts, configSchema;
|
||||||
|
try {
|
||||||
|
groupScripts = await apiRequest(`/script-groups/${groupId}/scripts`);
|
||||||
|
console.log('Scripts loaded:', groupScripts);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading scripts:', e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
configSchema = await apiRequest(`/script-groups/${groupId}/config-schema`);
|
||||||
|
console.log('Config schema loaded:', configSchema);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading config schema:', e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intentar cargar configuración actual
|
||||||
|
let currentConfig = {};
|
||||||
|
try {
|
||||||
|
console.log('Loading current config for work_dir:', currentProfile.work_dir);
|
||||||
|
currentConfig = await apiRequest(
|
||||||
|
`/workdir-config/${encodeURIComponent(currentProfile.work_dir)}/group/${groupId}`
|
||||||
|
);
|
||||||
|
console.log('Current config loaded:', currentConfig);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('No existing configuration found, using defaults');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar que tenemos los datos necesarios
|
||||||
|
if (!groupScripts || !configSchema) {
|
||||||
|
throw new Error('Failed to load required data');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Rendering UI with:', {
|
||||||
|
groupScripts,
|
||||||
|
configSchema,
|
||||||
|
currentConfig
|
||||||
|
});
|
||||||
|
|
||||||
|
scriptList.innerHTML = `
|
||||||
|
<!-- Sección de Configuración -->
|
||||||
|
<div class="mb-6 bg-white shadow sm:rounded-lg">
|
||||||
|
<div class="border-b border-gray-200 p-4 flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">
|
||||||
|
${configSchema.group_name || 'Configuration'}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">${configSchema.description || ''}</p>
|
||||||
|
</div>
|
||||||
|
<button onclick="editConfigSchema('${groupId}')"
|
||||||
|
class="rounded-md bg-gray-100 px-3 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-200 flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||||
|
</svg>
|
||||||
|
Edit Schema
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<form id="groupConfigForm" class="grid grid-cols-2 gap-4">
|
||||||
|
${Object.entries(configSchema.config_schema || {}).map(([key, field]) => `
|
||||||
|
<div class="space-y-2 col-span-2">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">
|
||||||
|
${field.description}
|
||||||
|
</label>
|
||||||
|
${generateFormField(key, field, currentConfig[key])}
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
<div class="col-span-2 flex justify-end pt-4">
|
||||||
|
<button type="submit"
|
||||||
|
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
||||||
|
Save Configuration
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lista de Scripts -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
${groupScripts.map(script => `
|
||||||
|
<div class="bg-white px-4 py-3 rounded-md border border-gray-200 hover:border-gray-300 shadow sm:rounded-lg">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-medium text-gray-900">${script.name || script.id}</h4>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">${script.description || 'No description available'}</p>
|
||||||
|
</div>
|
||||||
|
<button onclick="runScript('${groupId}', '${script.id}')"
|
||||||
|
class="ml-4 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
||||||
|
Run
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
scriptList.style.display = 'block';
|
||||||
|
|
||||||
|
// Agregar evento para guardar configuración
|
||||||
|
const form = document.getElementById('groupConfigForm');
|
||||||
|
form.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await saveGroupConfig(groupId, form);
|
||||||
|
});
|
||||||
|
|
||||||
await updateGroupConfig(groupId, config);
|
|
||||||
closeModal(event.target);
|
|
||||||
showSuccess('Group configuration updated successfully');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(`Failed to save configuration: ${error.message}`);
|
console.error('Error in loadGroupScripts:', error);
|
||||||
|
showError('Failed to load scripts and configuration');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editConfigSchema(groupId) {
|
||||||
|
try {
|
||||||
|
const schema = await apiRequest(`/script-groups/${groupId}/config-schema`);
|
||||||
|
const configSection = document.createElement('div');
|
||||||
|
configSection.id = 'schemaEditor';
|
||||||
|
configSection.className = 'mb-6 bg-white shadow sm:rounded-lg';
|
||||||
|
|
||||||
|
configSection.innerHTML = `
|
||||||
|
<div class="border-b border-gray-200 p-4 flex justify-between items-center bg-gray-50">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">Edit Schema Configuration</h3>
|
||||||
|
<button onclick="this.closest('#schemaEditor').remove()"
|
||||||
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
||||||
|
Close Editor
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Group Name</label>
|
||||||
|
<input type="text" name="group_name" value="${schema.group_name}"
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<button onclick="addParameter(this)"
|
||||||
|
class="mt-6 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500">
|
||||||
|
Add Parameter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Description</label>
|
||||||
|
<input type="text" name="description" value="${schema.description}"
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
<div id="parameters" class="space-y-2">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h4 class="font-medium text-gray-900">Parameters</h4>
|
||||||
|
</div>
|
||||||
|
${Object.entries(schema.config_schema).map(([key, param]) => `
|
||||||
|
<div class="parameter-item bg-gray-50 p-4 rounded-md relative">
|
||||||
|
<button onclick="removeParameter(this)"
|
||||||
|
class="absolute top-2 right-2 text-red-600 hover:text-red-700">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Parameter Name</label>
|
||||||
|
<input type="text" name="param_name" value="${key}"
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Type</label>
|
||||||
|
<select name="param_type"
|
||||||
|
onchange="handleTypeChange(this)"
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
<option value="string" ${param.type === 'string' ? 'selected' : ''}>String</option>
|
||||||
|
<option value="number" ${param.type === 'number' ? 'selected' : ''}>Number</option>
|
||||||
|
<option value="boolean" ${param.type === 'boolean' ? 'selected' : ''}>Boolean</option>
|
||||||
|
<option value="select" ${param.type === 'select' ? 'selected' : ''}>Select</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Description</label>
|
||||||
|
<input type="text" name="param_description" value="${param.description}"
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Default Value</label>
|
||||||
|
<input type="text" name="param_default" value="${param.default}"
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
${param.type === 'select' ? `
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Options (comma-separated)</label>
|
||||||
|
<input type="text" name="param_options" value="${param.options.join(', ')}"
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end space-x-3 pt-4 border-t border-gray-200">
|
||||||
|
<button onclick="this.closest('#schemaEditor').remove()"
|
||||||
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button onclick="saveConfigSchema('${groupId}', this.closest('#schemaEditor'))"
|
||||||
|
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Insertamos el editor justo después del botón "Edit Schema"
|
||||||
|
const scriptList = document.getElementById('scriptList');
|
||||||
|
const existingEditor = document.getElementById('schemaEditor');
|
||||||
|
if (existingEditor) {
|
||||||
|
existingEditor.remove();
|
||||||
|
}
|
||||||
|
scriptList.insertBefore(configSection, scriptList.firstChild);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
showError('Failed to load configuration schema');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createModal(title, content, onSave = null) {
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50 p-4';
|
||||||
|
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] flex flex-col">
|
||||||
|
<div class="px-6 py-4 border-b border-gray-200">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">${title}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 py-4 overflow-y-auto flex-1">
|
||||||
|
${content}
|
||||||
|
</div>
|
||||||
|
<div class="px-6 py-4 bg-gray-50 rounded-b-lg flex justify-end gap-3 border-t border-gray-200">
|
||||||
|
<button onclick="closeModal(this)"
|
||||||
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
${onSave ? `
|
||||||
|
<button onclick="saveModal(this)"
|
||||||
|
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
return modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addParameter(button) {
|
||||||
|
const parametersDiv = button.closest('.space-y-4').querySelector('#parameters');
|
||||||
|
const newParam = document.createElement('div');
|
||||||
|
newParam.className = 'parameter-item bg-gray-50 p-4 rounded-md relative';
|
||||||
|
newParam.innerHTML = `
|
||||||
|
<button onclick="removeParameter(this)"
|
||||||
|
class="absolute top-2 right-2 text-red-600 hover:text-red-700">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Parameter Name</label>
|
||||||
|
<input type="text" name="param_name" value=""
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Type</label>
|
||||||
|
<select name="param_type"
|
||||||
|
onchange="handleTypeChange(this)"
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
<option value="string">String</option>
|
||||||
|
<option value="number">Number</option>
|
||||||
|
<option value="boolean">Boolean</option>
|
||||||
|
<option value="select">Select</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Description</label>
|
||||||
|
<input type="text" name="param_description" value=""
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Default Value</label>
|
||||||
|
<input type="text" name="param_default" value=""
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
parametersDiv.appendChild(newParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeParameter(button) {
|
||||||
|
button.closest('.parameter-item').remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTypeChange(select) {
|
||||||
|
const paramItem = select.closest('.parameter-item');
|
||||||
|
const optionsDiv = paramItem.querySelector('[name="param_options"]')?.closest('div');
|
||||||
|
|
||||||
|
if (select.value === 'select') {
|
||||||
|
if (!optionsDiv) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'col-span-2';
|
||||||
|
div.innerHTML = `
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Options (comma-separated)</label>
|
||||||
|
<input type="text" name="param_options" value=""
|
||||||
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
`;
|
||||||
|
paramItem.querySelector('.grid').appendChild(div);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
optionsDiv?.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveConfigSchema(groupId, modal) {
|
||||||
|
const form = modal.querySelector('div');
|
||||||
|
const schema = {
|
||||||
|
group_name: form.querySelector('[name="group_name"]').value,
|
||||||
|
description: form.querySelector('[name="description"]').value,
|
||||||
|
config_schema: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Recopilar parámetros
|
||||||
|
form.querySelectorAll('.parameter-item').forEach(item => {
|
||||||
|
const name = item.querySelector('[name="param_name"]').value;
|
||||||
|
const type = item.querySelector('[name="param_type"]').value;
|
||||||
|
const description = item.querySelector('[name="param_description"]').value;
|
||||||
|
const defaultValue = item.querySelector('[name="param_default"]').value;
|
||||||
|
|
||||||
|
const param = {
|
||||||
|
type,
|
||||||
|
description,
|
||||||
|
default: type === 'boolean' ? defaultValue === 'true' : defaultValue
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === 'select') {
|
||||||
|
const options = item.querySelector('[name="param_options"]').value
|
||||||
|
.split(',')
|
||||||
|
.map(opt => opt.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
param.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.config_schema[name] = param;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiRequest(`/script-groups/${groupId}/config-schema`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(schema)
|
||||||
|
});
|
||||||
|
|
||||||
|
closeModal(modal.querySelector('button'));
|
||||||
|
showSuccess('Configuration schema updated successfully');
|
||||||
|
// Recargar la página para mostrar los cambios
|
||||||
|
loadGroupScripts(groupId);
|
||||||
|
} catch (error) {
|
||||||
|
showError('Failed to update configuration schema');
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -52,42 +52,43 @@ async function updateGroupConfig(groupId, settings) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static/js/workdir_config.js
|
||||||
async function showWorkDirConfig() {
|
async function showWorkDirConfig() {
|
||||||
if (!currentProfile?.work_dir) {
|
if (!currentProfile?.work_dir) {
|
||||||
showError('No work directory selected');
|
showError('No work directory selected');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await getWorkDirConfig();
|
try {
|
||||||
if (config) {
|
const config = await getWorkDirConfig();
|
||||||
const modal = document.createElement('div');
|
|
||||||
modal.className = 'modal active';
|
|
||||||
|
|
||||||
modal.innerHTML = `
|
const content = `
|
||||||
<div class="modal-content">
|
<div class="space-y-4">
|
||||||
<h2>Work Directory Configuration</h2>
|
<div>
|
||||||
<div class="config-info">
|
<h4 class="text-sm font-medium text-gray-900">Directory</h4>
|
||||||
<p><strong>Directory:</strong> ${currentProfile.work_dir}</p>
|
<p class="mt-1 text-sm text-gray-500">${currentProfile.work_dir}</p>
|
||||||
<p><strong>Version:</strong> ${config.version}</p>
|
|
||||||
<p><strong>Created:</strong> ${new Date(config.created_at).toLocaleString()}</p>
|
|
||||||
<p><strong>Updated:</strong> ${new Date(config.updated_at).toLocaleString()}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="group-configs">
|
<div>
|
||||||
<h3>Group Configurations</h3>
|
<h4 class="text-sm font-medium text-gray-900">Version</h4>
|
||||||
${Object.entries(config.group_settings || {}).map(([groupId, settings]) => `
|
<p class="mt-1 text-sm text-gray-500">${config.version}</p>
|
||||||
<div class="group-config">
|
|
||||||
<h4>${groupId}</h4>
|
|
||||||
<pre>${JSON.stringify(settings, null, 2)}</pre>
|
|
||||||
</div>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="button-group">
|
<div>
|
||||||
<button onclick="closeModal(this)">Close</button>
|
<h4 class="text-sm font-medium text-gray-900">Group Configurations</h4>
|
||||||
|
<div class="mt-2 space-y-3">
|
||||||
|
${Object.entries(config.group_settings || {}).map(([groupId, settings]) => `
|
||||||
|
<div class="rounded-md bg-gray-50 p-3">
|
||||||
|
<h5 class="text-sm font-medium text-gray-900">${groupId}</h5>
|
||||||
|
<pre class="mt-2 text-xs text-gray-500">${JSON.stringify(settings, null, 2)}</pre>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
document.body.appendChild(modal);
|
createModal('Work Directory Configuration', content);
|
||||||
|
} catch (error) {
|
||||||
|
showError('Failed to load work directory configuration');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,66 +1,113 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="h-full bg-gray-50">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Local Scripts Web - Home</title>
|
<title>Local Scripts Web</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
<!-- Tailwind y Alpine.js desde CDN -->
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
<!-- HeroIcons -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/heroicons/2.0.18/solid/index.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="h-full">
|
||||||
<header>
|
<div class="min-h-full">
|
||||||
<nav>
|
<!-- Navbar -->
|
||||||
<div class="nav-brand">Local Scripts Web</div>
|
<nav class="bg-white shadow-sm">
|
||||||
<div class="profile-selector">
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
<select id="profileSelect" onchange="changeProfile()">
|
<div class="flex h-16 justify-between">
|
||||||
<option value="">Loading profiles...</option>
|
<div class="flex">
|
||||||
</select>
|
<div class="flex flex-shrink-0 items-center">
|
||||||
<button onclick="editProfile()">Edit Profile</button>
|
<h1 class="text-xl font-semibold text-gray-900">Local Scripts Web</h1>
|
||||||
<button onclick="newProfile()">New Profile</button>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<select id="profileSelect"
|
||||||
|
class="rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600"
|
||||||
|
onchange="changeProfile()">
|
||||||
|
<option value="">Select Profile</option>
|
||||||
|
</select>
|
||||||
|
<button onclick="editProfile()"
|
||||||
|
class="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
||||||
|
Edit Profile
|
||||||
|
</button>
|
||||||
|
<button onclick="newProfile()"
|
||||||
|
class="rounded-md bg-indigo-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
||||||
|
New Profile
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
<!-- Main content -->
|
||||||
<div class="container">
|
<main>
|
||||||
<div class="work-dir-section">
|
<div class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
|
||||||
<h2>Work Directory</h2>
|
<div class="space-y-6">
|
||||||
<div class="work-dir-controls">
|
<!-- Work Directory Section -->
|
||||||
<input type="text" id="workDirPath" readonly>
|
<div class="bg-white shadow sm:rounded-lg">
|
||||||
<button onclick="selectWorkDir()">Browse</button>
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<button onclick="showWorkDirConfig()">Config</button>
|
<h3 class="text-base font-semibold leading-6 text-gray-900">Work Directory</h3>
|
||||||
|
<div class="mt-4 flex gap-4">
|
||||||
|
<input type="text" id="workDirPath" readonly
|
||||||
|
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
|
||||||
|
<button onclick="selectWorkDir()"
|
||||||
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
||||||
|
Browse
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts Section -->
|
||||||
|
<div class="bg-white shadow sm:rounded-lg">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<h3 class="text-base font-semibold leading-6 text-gray-900">Scripts</h3>
|
||||||
|
<div class="mt-4 space-y-4">
|
||||||
|
<select id="groupSelect"
|
||||||
|
class="w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
|
||||||
|
<option value="">Select Script Group</option>
|
||||||
|
</select>
|
||||||
|
<div id="scriptList" class="hidden space-y-4">
|
||||||
|
<!-- Scripts will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Output Section -->
|
||||||
|
<div class="bg-white shadow sm:rounded-lg">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h3 class="text-base font-semibold leading-6 text-gray-900">Output</h3>
|
||||||
|
<button onclick="clearOutput()"
|
||||||
|
class="rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500">
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="outputArea"
|
||||||
|
class="mt-4 h-64 overflow-y-auto p-4 font-mono text-sm bg-gray-50 rounded-md border border-gray-200">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</main>
|
||||||
<div class="scripts-section">
|
</div>
|
||||||
<h2>Scripts</h2>
|
|
||||||
<div class="script-group-selector">
|
|
||||||
<label for="groupSelect">Select Script Group:</label>
|
|
||||||
<select id="groupSelect" onchange="loadGroupScripts(this.value)">
|
|
||||||
<option value="">Select a group...</option>
|
|
||||||
<!-- Groups will be loaded here -->
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div id="scriptList" class="script-list" style="display: none;">
|
|
||||||
<!-- Scripts will be loaded here after group selection -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="output-section">
|
|
||||||
<h2>Output</h2>
|
|
||||||
<div class="output-controls">
|
|
||||||
<button onclick="clearOutput()">Clear Output</button>
|
|
||||||
</div>
|
|
||||||
<div id="outputArea" class="output-area">
|
|
||||||
<!-- Script output will appear here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/workdir_config.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/workdir_config.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/profile.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/profile.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/scripts.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/scripts.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/modal.js') }}"></script>
|
||||||
|
<!-- Al final del body -->
|
||||||
|
<script>
|
||||||
|
// Initialización cuando la página carga
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
console.log('DOM loaded, initializing...');
|
||||||
|
await initializeApp();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Reference in New Issue