forked from enviPath/enviPy
[Feature] Modern UI roll out (#236)
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>
This commit is contained in:
201
scripts/dev_server.py
Executable file
201
scripts/dev_server.py
Executable file
@ -0,0 +1,201 @@
|
||||
#!/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()
|
||||
59
scripts/pnpm_wrapper.py
Executable file
59
scripts/pnpm_wrapper.py
Executable file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cross-platform pnpm command wrapper.
|
||||
Finds pnpm correctly on Windows (handles pnpm.cmd) and Unix systems.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point - execute pnpm with provided arguments."""
|
||||
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)
|
||||
|
||||
# Get all arguments passed to this script
|
||||
args = sys.argv[1:]
|
||||
|
||||
# Execute pnpm with the provided arguments
|
||||
try:
|
||||
sys.exit(subprocess.call([pnpm_path] + args))
|
||||
except KeyboardInterrupt:
|
||||
# Handle Ctrl+C gracefully
|
||||
sys.exit(130)
|
||||
except Exception as e:
|
||||
print(f"Error executing pnpm: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user