forked from enviPath/enviPy
This PR moves all the collection pages into the new UI in a rough push. I did not put the same amount of care into these as into search, index, and predict. ## Major changes - All modals are now migrated to a state based alpine.js implementation. - jQuery is no longer present in the base layout; ajax is replace by native fetch api - most of the pps.js is now obsolte (as I understand it; the code is not referenced any more @jebus please double check) - in-memory pagination for large result lists (set to 50; we can make that configurable later; performance degrades at around 1k) stukk a bit rough tracked in #235 ## Minor things - Sarch and index also use alpine now - The loading spinner is now CSS animated (not sure if it currently gets correctly called) ## Not done - Ihave not even cheked the admin pages. Not sure If these need migrations - The temporary migration pages still use the old template. Not sure what is supposed to happen with those? @jebus ## What I did to test - opend all pages in browse, and user ; plus all pages reachable from there. - Interacted and tested the functionality of each modal superfically with exception of the API key modal (no functional test). --- This PR is massive sorry for that; just did not want to push half-brokenn state. @jebus @liambrydon I would be glad if you could click around and try to break it :) Finally closes #133 Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#236 Co-authored-by: Tobias O <tobias.olenyi@envipath.com> Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
202 lines
6.2 KiB
Python
Executable File
202 lines
6.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Cross-platform development server script.
|
|
Starts pnpm CSS watcher and Django dev server, handling cleanup on exit.
|
|
Works on both Windows and Unix systems.
|
|
"""
|
|
|
|
import atexit
|
|
import shutil
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
|
|
def find_pnpm():
|
|
"""
|
|
Find pnpm executable on the system.
|
|
Returns the path to pnpm or None if not found.
|
|
"""
|
|
# Try to find pnpm using shutil.which
|
|
# On Windows, this will find pnpm.cmd if it's in PATH
|
|
pnpm_path = shutil.which("pnpm")
|
|
|
|
if pnpm_path:
|
|
return pnpm_path
|
|
|
|
# On Windows, also try pnpm.cmd explicitly
|
|
if sys.platform == "win32":
|
|
pnpm_cmd = shutil.which("pnpm.cmd")
|
|
if pnpm_cmd:
|
|
return pnpm_cmd
|
|
|
|
return None
|
|
|
|
|
|
class DevServerManager:
|
|
"""Manages background processes for development server."""
|
|
|
|
def __init__(self):
|
|
self.processes = []
|
|
self._cleanup_registered = False
|
|
|
|
def start_process(self, command, description, shell=False):
|
|
"""Start a background process and return the process object."""
|
|
print(f"Starting {description}...")
|
|
try:
|
|
if shell:
|
|
# Use shell=True for commands that need shell interpretation
|
|
process = subprocess.Popen(
|
|
command,
|
|
shell=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
)
|
|
else:
|
|
# Split command into list for subprocess
|
|
process = subprocess.Popen(
|
|
command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
)
|
|
self.processes.append((process, description))
|
|
print(f"✓ Started {description} (PID: {process.pid})")
|
|
return process
|
|
except Exception as e:
|
|
print(f"✗ Failed to start {description}: {e}", file=sys.stderr)
|
|
self.cleanup()
|
|
sys.exit(1)
|
|
|
|
def cleanup(self):
|
|
"""Terminate all running processes."""
|
|
if not self.processes:
|
|
return
|
|
|
|
print("\nShutting down...")
|
|
for process, description in self.processes:
|
|
if process.poll() is None: # Process is still running
|
|
try:
|
|
# Try graceful termination first
|
|
if sys.platform == "win32":
|
|
process.terminate()
|
|
else:
|
|
process.send_signal(signal.SIGTERM)
|
|
|
|
# Wait up to 5 seconds for graceful shutdown
|
|
try:
|
|
process.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
# Force kill if graceful shutdown failed
|
|
if sys.platform == "win32":
|
|
process.kill()
|
|
else:
|
|
process.send_signal(signal.SIGKILL)
|
|
process.wait()
|
|
|
|
print(f"✓ {description} stopped")
|
|
except Exception as e:
|
|
print(f"✗ Error stopping {description}: {e}", file=sys.stderr)
|
|
|
|
self.processes.clear()
|
|
|
|
def register_cleanup(self):
|
|
"""Register cleanup handlers for various exit scenarios."""
|
|
if self._cleanup_registered:
|
|
return
|
|
|
|
self._cleanup_registered = True
|
|
|
|
# Register atexit handler (works on all platforms)
|
|
atexit.register(self.cleanup)
|
|
|
|
# Register signal handlers (Unix only)
|
|
if sys.platform != "win32":
|
|
signal.signal(signal.SIGINT, self._signal_handler)
|
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
|
|
def _signal_handler(self, signum, frame):
|
|
"""Handle Unix signals."""
|
|
self.cleanup()
|
|
sys.exit(0)
|
|
|
|
def wait_for_process(self, process, description):
|
|
"""Wait for a process to finish and handle its output."""
|
|
try:
|
|
# Stream output from the process
|
|
for line in iter(process.stdout.readline, ""):
|
|
if line:
|
|
print(f"[{description}] {line.rstrip()}")
|
|
|
|
process.wait()
|
|
return process.returncode
|
|
except KeyboardInterrupt:
|
|
# Handle Ctrl+C
|
|
self.cleanup()
|
|
sys.exit(0)
|
|
except Exception as e:
|
|
print(f"Error waiting for {description}: {e}", file=sys.stderr)
|
|
self.cleanup()
|
|
sys.exit(1)
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
manager = DevServerManager()
|
|
manager.register_cleanup()
|
|
|
|
# Find pnpm executable
|
|
pnpm_path = find_pnpm()
|
|
if not pnpm_path:
|
|
print("Error: pnpm not found in PATH.", file=sys.stderr)
|
|
print("\nPlease install pnpm:", file=sys.stderr)
|
|
print(" Windows: https://pnpm.io/installation#on-windows", file=sys.stderr)
|
|
print(" Unix: https://pnpm.io/installation#on-posix-systems", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Determine shell usage based on platform
|
|
use_shell = sys.platform == "win32"
|
|
|
|
# Start pnpm CSS watcher
|
|
# Use the found pnpm path to ensure it works on Windows
|
|
pnpm_command = f'"{pnpm_path}" run dev' if use_shell else [pnpm_path, "run", "dev"]
|
|
manager.start_process(
|
|
pnpm_command,
|
|
"CSS watcher",
|
|
shell=use_shell,
|
|
)
|
|
|
|
# Give pnpm a moment to start
|
|
time.sleep(1)
|
|
|
|
# Start Django dev server
|
|
django_process = manager.start_process(
|
|
["uv", "run", "python", "manage.py", "runserver"],
|
|
"Django server",
|
|
shell=False,
|
|
)
|
|
|
|
print("\nDevelopment servers are running. Press Ctrl+C to stop.\n")
|
|
|
|
try:
|
|
# Wait for Django server (main process)
|
|
# If Django exits, we should clean up everything
|
|
return_code = manager.wait_for_process(django_process, "Django")
|
|
|
|
# If Django exited unexpectedly, clean up and exit
|
|
if return_code != 0:
|
|
manager.cleanup()
|
|
sys.exit(return_code)
|
|
except KeyboardInterrupt:
|
|
# Ctrl+C was pressed
|
|
manager.cleanup()
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|