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:
99
manager/app.py
Normal file
99
manager/app.py
Normal 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)
|
||||
Reference in New Issue
Block a user