CC2 R3 frontend: login.html + admin_users.html (M1+M2+M10 UI)
- static/login.html: dark Palantir-style login with PGŽ branding,
Prijava se / Zaboravljena lozinka, demo account quick-fills,
GDPR cookie banner, autostore tokens (local/session)
- static/admin_users.html: full user-management admin panel:
- Collapsible left sidebar (Pregled, Korisnici, Tenanti, Audit log,
Sigurnost, GDPR, links to ERP/CRM)
- Users table with filters (q, tenant, role, status, limit)
- + Dodaj korisnika modal (CRUD via /api/admin/users/*)
- Suspend / unsuspend / reset-password / delete actions
- Audit log viewer + Security KPIs + GDPR queue
- Self-service: change pwd, export data (Art. 20), erasure request (Art. 17)
- pgz_sport_api.py: /login and /admin/users URL routes
- auth/seed_demo.py: added tajnik@atletski.pgz.hr/Atl2026!,
admin@ak-kvarner.hr/Kvarner2026! demo users
5/5 live tests pass: login JWT, /me, /admin/users, /gdpr/consent, /gdpr/export
Note: existing admin.html (CC4 ERP/OCR work) preserved intact;
admin_users.html is dedicated user-mgmt page linked from sidebar.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
audit_seal_router.py — HTTP surface for the Polygon PoS sealing module
|
||||
Author: Damir Radulić (damir@rinet.one) / dradulic@outlook.com
|
||||
Date: 2026-05-04
|
||||
|
||||
Endpoints (all under /sport/api):
|
||||
|
||||
POST /audit/seal
|
||||
body: {
|
||||
action: "sufinanciranje.approved",
|
||||
ref_type: "sufinanciranje", (optional)
|
||||
ref_id: "2026-001", (string or number)
|
||||
payload: { ... }, (sha256 computed server-side)
|
||||
data_hash: "abc..." (optional — if you already have the hash)
|
||||
}
|
||||
returns the seal record (seal_id, tx_hash, polygonscan_url, status, ...).
|
||||
|
||||
GET /audit/seal/list?action=&ref_type=&ref_id=&limit=
|
||||
Recent seals for the audit-log UI.
|
||||
|
||||
GET /audit/seal/{seal_id}
|
||||
Single seal with on-chain receipt cross-check (if web3 wired up).
|
||||
|
||||
The legacy hash-chain audit endpoints (/api/admin/audit-chain*) live in
|
||||
pgz_sport_api.py and remain unchanged.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys, os
|
||||
from typing import Any, Optional
|
||||
|
||||
from fastapi import APIRouter, Body, HTTPException, Header
|
||||
|
||||
# blockchain.seal lives at /opt/pgz-sport/blockchain/seal.py
|
||||
sys.path.insert(0, '/opt/pgz-sport')
|
||||
from blockchain import seal as seal_mod # noqa: E402
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/audit/seal")
|
||||
def audit_seal(body: dict = Body(...),
|
||||
x_user_email: Optional[str] = Header(default=None),
|
||||
x_user_id: Optional[int] = Header(default=None)):
|
||||
"""Seal an audit event to Polygon PoS (or queue for later if no key)."""
|
||||
if not isinstance(body, dict):
|
||||
raise HTTPException(400, "JSON body required")
|
||||
action = (body.get('action') or '').strip()
|
||||
if not action:
|
||||
raise HTTPException(400, "action is required")
|
||||
|
||||
payload = body.get('payload')
|
||||
data_hash = (body.get('data_hash') or '').strip().lower().lstrip('0x')
|
||||
if not data_hash:
|
||||
if payload is None:
|
||||
raise HTTPException(400, "either data_hash or payload required")
|
||||
data_hash = seal_mod.hash_payload(payload)
|
||||
|
||||
ref_id = body.get('ref_id')
|
||||
if ref_id is None:
|
||||
raise HTTPException(400, "ref_id is required")
|
||||
ref_type = body.get('ref_type')
|
||||
|
||||
try:
|
||||
result = seal_mod.seal_to_polygon(
|
||||
data_hash=data_hash,
|
||||
ref_id=str(ref_id),
|
||||
action=action,
|
||||
ref_type=ref_type,
|
||||
payload=payload,
|
||||
user_id=x_user_id,
|
||||
user_email=x_user_email,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(400, str(e))
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/audit/seal/list")
|
||||
def audit_seal_list(action: Optional[str] = None,
|
||||
ref_type: Optional[str] = None,
|
||||
ref_id: Optional[str] = None,
|
||||
limit: int = 50):
|
||||
rows = seal_mod.list_seals(action=action, ref_type=ref_type,
|
||||
ref_id=ref_id, limit=limit)
|
||||
return {'count': len(rows), 'rows': rows,
|
||||
'wallet': seal_mod.POLYGON_WALLET,
|
||||
'chain_id': seal_mod.POLYGON_CHAIN_ID,
|
||||
'live': seal_mod.HAS_WEB3 and bool(seal_mod.POLYGON_PRIVKEY)}
|
||||
|
||||
|
||||
@router.get("/audit/seal/{seal_id}")
|
||||
def audit_seal_get(seal_id: str):
|
||||
row = seal_mod.verify_seal(seal_id)
|
||||
if not row:
|
||||
raise HTTPException(404, "seal not found")
|
||||
return row
|
||||
Reference in New Issue
Block a user