diff --git a/webapp/app.py b/webapp/app.py index f141f67..aabc8e8 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -40,6 +40,7 @@ from .corporate_memory_service import ( vote as memory_vote, is_km_admin, get_governance_mode, + get_groups as get_memory_groups, approve_item, reject_item, mandate_item, @@ -49,6 +50,7 @@ from .corporate_memory_service import ( get_pending_queue, get_audit_log, migrate_existing_items, + VALID_STATUSES, ) from .user_service import ( UserInfo, @@ -1393,9 +1395,11 @@ def register_routes(app: Flask) -> None: knowledge = get_knowledge(page=0, per_page=20) # Governance context for admin features + _is_admin = is_km_admin(email) if email else False governance = { "mode": get_governance_mode(), - "is_km_admin": is_km_admin(email) if email else False, + "is_km_admin": _is_admin, + "pending_count": get_memory_stats().get("pending_count", 0) if _is_admin else 0, } return render_template( @@ -1407,6 +1411,31 @@ def register_routes(app: Flask) -> None: governance=governance, ) + @app.route("/corporate-memory/admin") + @login_required + @km_admin_required + def corporate_memory_admin(): + """Corporate Memory admin review queue page.""" + user = session.get("user", {}) + email = user.get("email", "") + + stats = get_memory_stats() + groups = get_memory_groups() + + # Build groups list for audience dropdown (name + member count) + groups_list = [ + {"name": name, "members_count": len(g.get("members", []))} + for name, g in groups.items() + if isinstance(g, dict) + ] + + return render_template( + "corporate_memory_admin.html", + stats=stats, + groups=groups_list, + governance_mode=get_governance_mode(), + ) + # ───────────────────────────────────────────────────────────────── # Activity Center routes # ───────────────────────────────────────────────────────────────── @@ -1440,8 +1469,12 @@ def register_routes(app: Flask) -> None: # Admin status filter (only km_admins can filter by status) status = request.args.get("status") include_statuses = None - if status and is_km_admin(email): - include_statuses = {status} + if is_km_admin(email): + if status: + include_statuses = {status} + elif request.args.get("all_statuses", "").lower() == "true": + # Admin requesting all statuses (for admin "All Items" view) + include_statuses = set(VALID_STATUSES) result = get_knowledge( category=category, @@ -1723,9 +1756,16 @@ def register_routes(app: Flask) -> None: def corporate_memory_admin_config(): """Get current governance configuration.""" try: + groups = get_memory_groups() + groups_list = [ + {"name": name, "members_count": len(g.get("members", []))} + for name, g in groups.items() + if isinstance(g, dict) + ] return jsonify({ "ok": True, "governance_mode": get_governance_mode(), + "groups": groups_list, }) except Exception as e: logger.exception("Error fetching governance config") diff --git a/webapp/corporate_memory_service.py b/webapp/corporate_memory_service.py index eaa042e..05b91e9 100644 --- a/webapp/corporate_memory_service.py +++ b/webapp/corporate_memory_service.py @@ -104,6 +104,16 @@ def get_governance_mode() -> str | None: return gov.get("distribution_mode", "hybrid") +def get_groups() -> dict: + """Return the groups dict from instance config (public wrapper around _load_groups). + + Returns: + Dict mapping group name to group config (with 'members' list). + Empty dict if no groups configured. + """ + return _load_groups() + + def get_approval_mode() -> str | None: """Return the approval mode, or None if legacy (no config).""" gov = _load_governance_config() diff --git a/webapp/templates/corporate_memory.html b/webapp/templates/corporate_memory.html index ffea861..e534194 100644 --- a/webapp/templates/corporate_memory.html +++ b/webapp/templates/corporate_memory.html @@ -437,6 +437,40 @@ opacity: 0.5; } + /* Admin Link Button */ + .admin-link-btn { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + background: var(--surface); + border: 1px solid var(--primary); + border-radius: var(--radius-md); + color: var(--primary); + text-decoration: none; + font-size: var(--text-sm); + font-weight: var(--font-medium); + transition: all 0.15s; + } + + .admin-link-btn:hover { + background: var(--primary-light); + } + + .pending-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 5px; + border-radius: var(--radius-full); + background: var(--warning); + color: white; + font-size: 10px; + font-weight: var(--font-bold); + } + /* Responsive */ @media (max-width: 768px) { .page-header { @@ -499,15 +533,28 @@

Corporate Memory

-
- {% if session.user.picture %} - Profile - {% else %} -
- {{ session.user.email[:2].upper() }} -
+
+ {% if governance.is_km_admin %} + + + + + Admin Review + {% if governance.pending_count > 0 %} + {{ governance.pending_count }} + {% endif %} + {% endif %} - {{ session.user.email }} +
diff --git a/webapp/templates/corporate_memory_admin.html b/webapp/templates/corporate_memory_admin.html new file mode 100644 index 0000000..ff71d81 --- /dev/null +++ b/webapp/templates/corporate_memory_admin.html @@ -0,0 +1,1556 @@ + + + + + + Corporate Memory Admin - Data Analyst Portal + {% if not config.THEME_FONT_URL %} + + + + {% endif %} + + + {% include '_theme.html' %} + + +
+ + + + +
+
+
{{ stats.pending_count|default(0) }}
+
Pending
+
+
+
+
{{ stats.approved_count|default(0) }}
+
Approved
+
+
+
+
{{ stats.mandatory_count|default(0) }}
+
Mandatory
+
+
+
+
{{ stats.knowledge_count|default(0) }}
+
Total
+
+
+ + +
+ + + +
+ + +
+
+ + +
+ + +
+
+
+
Loading pending items...
+
+ +
+ + +
+
+ + +
+
+
Loading items...
+
+ +
+ + +
+
+ + +
+
+
Loading audit log...
+
+ +
+
+ + +
+ + + +