feat: multi-user UI login with admin and monitor roles

Add sac_users table, JWT roles, settings/users API guarded for admin, Users page in UI, and sac_manage_user.py CLI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-06-01 09:27:14 +10:00
parent bc77f82307
commit 56c469ddbd
20 changed files with 859 additions and 85 deletions
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Manage SAC UI users (create admin/monitor accounts).
Examples (from backend/ on prod server):
python scripts/sac_manage_user.py create ivanov 'SecretPass123' --role monitor
python scripts/sac_manage_user.py list
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
BACKEND_ROOT = Path(__file__).resolve().parents[1]
if str(BACKEND_ROOT) not in sys.path:
sys.path.insert(0, str(BACKEND_ROOT))
from sqlalchemy import select
from app.database import SessionLocal
from app.models.user import USER_ROLE_ADMIN, USER_ROLE_MONITOR, User
from app.services.user_auth import create_user
def cmd_list() -> int:
db = SessionLocal()
try:
rows = db.scalars(select(User).order_by(User.username)).all()
if not rows:
print("No users.")
return 0
for u in rows:
status = "active" if u.is_active else "inactive"
print(f"{u.id}\t{u.username}\t{u.role}\t{status}\t{u.created_at.isoformat()}")
return 0
finally:
db.close()
def cmd_create(username: str, password: str, role: str) -> int:
db = SessionLocal()
try:
user = create_user(db, username=username, password=password, role=role)
db.commit()
print(f"Created user id={user.id} username={user.username!r} role={user.role}")
return 0
except ValueError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
finally:
db.close()
def main() -> int:
parser = argparse.ArgumentParser(description="SAC UI user management")
sub = parser.add_subparsers(dest="command", required=True)
sub.add_parser("list", help="List UI users")
create_p = sub.add_parser("create", help="Create a user")
create_p.add_argument("username")
create_p.add_argument("password")
create_p.add_argument(
"--role",
choices=[USER_ROLE_ADMIN, USER_ROLE_MONITOR],
default=USER_ROLE_MONITOR,
)
args = parser.parse_args()
if args.command == "list":
return cmd_list()
if args.command == "create":
return cmd_create(args.username, args.password, args.role)
return 1
if __name__ == "__main__":
raise SystemExit(main())