feat(manager): add Nexus Instance Manager for demo orchestration

- Complete Flask-based control panel for multi-tenant POS instances
- Dashboard with global stats, system health, and recent demos
- Demo provisioning in 1 click with auto-expiration tracking
- Tenant management: activate/deactivate, reset data, delete
- Health monitoring: PostgreSQL, Redis, disk, memory, systemd services
- Migration orchestration UI for running schema updates across all tenants
- JWT authentication with manager_users table
- Dark theme SPA frontend with real-time search and actions
- systemd service file included
This commit is contained in:
2026-05-17 21:01:01 +00:00
parent da362e32a6
commit be4bb8d9ad
20 changed files with 2685 additions and 0 deletions

99
manager/app.py Normal file
View File

@@ -0,0 +1,99 @@
"""Nexus Instance Manager — Flask Application."""
import os
import sys
from datetime import datetime
from flask import Flask, jsonify, render_template, send_from_directory, request
# Ensure POS modules are importable for tenant_manager reuse
POS_DIR = os.environ.get("POS_DIR", "/home/Autopartes/pos")
if POS_DIR not in sys.path:
sys.path.insert(0, POS_DIR)
from config import APP_NAME, APP_VERSION
from blueprints.auth_bp import auth_bp, require_manager_auth
from blueprints.tenants_bp import tenants_bp
from blueprints.demos_bp import demos_bp
from blueprints.health_bp import health_bp
from blueprints.admin_bp import admin_bp
def create_app():
app = Flask(
__name__,
template_folder="templates",
static_folder="static"
)
app.secret_key = os.environ.get("MANAGER_JWT_SECRET", "dev-secret-change-me")
# Register blueprints
app.register_blueprint(auth_bp)
app.register_blueprint(tenants_bp)
app.register_blueprint(demos_bp)
app.register_blueprint(health_bp)
app.register_blueprint(admin_bp)
# ─── Frontend Routes ───────────────────────────────────────────────────
@app.route("/")
def index():
return render_template("index.html")
@app.route("/login")
def login_page():
return render_template("index.html")
@app.route("/dashboard")
def dashboard_page():
return render_template("index.html")
@app.route("/tenants")
def tenants_page():
return render_template("index.html")
@app.route("/demos")
def demos_page():
return render_template("index.html")
@app.route("/health")
def health_page():
return render_template("index.html")
@app.route("/migrations")
def migrations_page():
return render_template("index.html")
# ─── Static Asset Helpers ──────────────────────────────────────────────
@app.route("/static/<path:filename>")
def static_files(filename):
return send_from_directory("static", filename)
# ─── API Status ────────────────────────────────────────────────────────
@app.route("/api/status")
def api_status():
return jsonify({
"app": APP_NAME,
"version": APP_VERSION,
"timestamp": datetime.utcnow().isoformat(),
"pos_dir": POS_DIR
})
# ─── Error Handlers ────────────────────────────────────────────────────
@app.errorhandler(404)
def not_found(e):
if request.path.startswith("/api/"):
return jsonify({"error": "Not found"}), 404
return render_template("index.html")
@app.errorhandler(500)
def internal_error(e):
if request.path.startswith("/api/"):
return jsonify({"error": "Internal server error"}), 500
return render_template("index.html")
return app
# Entry point for Gunicorn: gunicorn -w 2 -b 0.0.0.0:5003 app:app
app = create_app()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5003, debug=True)