Spaces:
Running on Zero
Running on Zero
| # admin_manager.py | |
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| مدير الإدارة الكامل لمنصة Divid Teacher. | |
| المسؤوليات: | |
| - إدارة تسجيل الطلاب وتغيير حالة الحساب. | |
| - تعديل ملف الطالب ومفتاح الدخول وكلمة مرور ولي الأمر. | |
| - إدارة الأجهزة الموثوقة للطالب. | |
| - عرض قوائم الطلاب والتقارير الإدارية الآمنة. | |
| - الاحتفاظ بسجل تدقيق لجميع العمليات الإدارية. | |
| - إدارة التوجيه العام والمنهج والمواد والمسارات والاستراتيجيات وPomodoro. | |
| - إدارة اللغات عند توفر LanguageManager. | |
| - توفير ملخص موحد لفحص النظام. | |
| ملاحظات أمنية: | |
| - لا يعيد كلمات المرور أو قيم Hash إلى واجهة الإدارة. | |
| - يمكن تفعيل التحقق الداخلي بمفتاح الإدارة من متغيرات البيئة. | |
| - عمليات الحذف تحتاج تأكيدًا صريحًا ولا تُنفذ بالخطأ. | |
| """ | |
| from __future__ import annotations | |
| import copy | |
| import hmac | |
| import json | |
| import os | |
| import threading | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple | |
| from curriculum_manager import CurriculumManager | |
| from strategy_manager import StrategyManager | |
| try: | |
| from language_manager import LanguageManager | |
| except Exception: | |
| LanguageManager = None | |
| try: | |
| from student_manager import StudentManager | |
| except Exception: | |
| StudentManager = None | |
| try: | |
| from device_identity_manager import DeviceIdentityManager | |
| except Exception: | |
| DeviceIdentityManager = None | |
| try: | |
| from badge_progress_manager import BadgeProgressManager | |
| except Exception: | |
| BadgeProgressManager = None | |
| try: | |
| from report_manager import ReportManager | |
| except Exception: | |
| ReportManager = None | |
| try: | |
| from ai_task_queue_manager import AITaskQueueManager | |
| except Exception: | |
| AITaskQueueManager = None | |
| try: | |
| from config import SYSTEM_FOLDER | |
| except Exception: | |
| SYSTEM_FOLDER = "system" | |
| VERSION = "admin_manager_v4_full_access_student_registration_control" | |
| SCHEMA_VERSION = "admin_manager_schema_v4" | |
| ACCOUNT_STATUSES = {"pending", "active", "suspended", "rejected"} | |
| STATUS_ALIASES = { | |
| "pending": "pending", | |
| "بانتظار": "pending", | |
| "قيد الانتظار": "pending", | |
| "انتظار": "pending", | |
| "اعادة للانتظار": "pending", | |
| "إعادة للانتظار": "pending", | |
| "active": "active", | |
| "تفعيل": "active", | |
| "مفعل": "active", | |
| "مفعّل": "active", | |
| "قبول": "active", | |
| "مقبول": "active", | |
| "suspended": "suspended", | |
| "ايقاف": "suspended", | |
| "إيقاف": "suspended", | |
| "ايقاف مؤقت": "suspended", | |
| "إيقاف مؤقت": "suspended", | |
| "موقوف": "suspended", | |
| "rejected": "rejected", | |
| "رفض": "rejected", | |
| "مرفوض": "rejected", | |
| } | |
| PRIVATE_STUDENT_FIELDS = { | |
| "parent_password", | |
| "parent_password_hash", | |
| "parent_pin", | |
| "parent_pin_hash", | |
| "password", | |
| "password_hash", | |
| "secret", | |
| "secret_hash", | |
| "token", | |
| "hf_token", | |
| "api_key", | |
| } | |
| FULL_PERMISSIONS = [ | |
| "students.read", | |
| "students.update", | |
| "students.activate", | |
| "students.suspend", | |
| "students.reject", | |
| "students.pending", | |
| "students.bulk_status", | |
| "students.reset_access_key", | |
| "students.reset_parent_password", | |
| "students.devices.read", | |
| "students.devices.remove", | |
| "students.delete", | |
| "curriculum.read", | |
| "curriculum.write", | |
| "strategies.read", | |
| "strategies.write", | |
| "languages.read", | |
| "languages.write", | |
| "reports.read", | |
| "system.read", | |
| "system.write", | |
| "audit.read", | |
| ] | |
| class AdminManager: | |
| """واجهة الإدارة المركزية.""" | |
| def __init__(self, *args: Any, **kwargs: Any) -> None: | |
| self.version = VERSION | |
| self.schema_version = SCHEMA_VERSION | |
| self._lock = threading.RLock() | |
| self.system_folder = Path(SYSTEM_FOLDER) | |
| self.system_folder.mkdir(parents=True, exist_ok=True) | |
| self.general_instruction_file = self.system_folder / "general_instruction.txt" | |
| self.admin_notes_file = self.system_folder / "admin_notes.json" | |
| self.audit_log_file = self.system_folder / "admin_audit_log.jsonl" | |
| self.require_admin_key = self._env_bool("ADMIN_MANAGER_REQUIRE_KEY", False) | |
| self.admin_key = str( | |
| os.getenv("ADMIN_PANEL_KEY", "") | |
| or os.getenv("ADMIN_MANAGER_KEY", "") | |
| or "" | |
| ).strip() | |
| self.curriculum_manager = CurriculumManager() | |
| self.strategy_manager = StrategyManager() | |
| self.language_manager = self._safe_manager(LanguageManager) | |
| self.student_manager = self._safe_manager(StudentManager) | |
| self.device_manager = self._safe_manager(DeviceIdentityManager) | |
| self.badge_progress_manager = self._safe_manager(BadgeProgressManager) | |
| self.report_manager = self._safe_manager(ReportManager) | |
| self.queue_manager = self._safe_manager(AITaskQueueManager) | |
| if not self.general_instruction_file.exists(): | |
| self.save_general_instruction( | |
| self.default_general_instruction(), | |
| changed_by="system", | |
| admin_key=self.admin_key if self.require_admin_key else "", | |
| _skip_guard=True, | |
| ) | |
| if not self.admin_notes_file.exists(): | |
| self.save_admin_notes([]) | |
| # ========================================================= | |
| # أدوات عامة وأمنية | |
| # ========================================================= | |
| def _env_bool(self, name: str, default: bool = False) -> bool: | |
| raw = str(os.getenv(name, "1" if default else "0")).strip().lower() | |
| return raw not in {"0", "false", "no", "off", ""} | |
| def _safe_manager(self, manager_class: Any) -> Any: | |
| if manager_class is None: | |
| return None | |
| try: | |
| return manager_class() | |
| except Exception: | |
| return None | |
| def now(self) -> str: | |
| return datetime.now(timezone.utc).isoformat() | |
| def _normalize_status(self, status: Any) -> str: | |
| raw = str(status or "").strip().lower() | |
| return STATUS_ALIASES.get(raw, raw) | |
| def verify_admin_key(self, admin_key: Any = "") -> bool: | |
| if not self.require_admin_key: | |
| return True | |
| supplied = str(admin_key or "").strip() | |
| if not self.admin_key or not supplied: | |
| return False | |
| return hmac.compare_digest(supplied, self.admin_key) | |
| def _permission_error(self, permission: str) -> Dict[str, Any]: | |
| return { | |
| "success": False, | |
| "authorized": False, | |
| "permission": permission, | |
| "message": "مفتاح الإدارة غير صحيح أو غير موجود.", | |
| "version": self.version, | |
| } | |
| def _guard(self, permission: str, admin_key: Any = "") -> Optional[Dict[str, Any]]: | |
| if self.verify_admin_key(admin_key): | |
| return None | |
| return self._permission_error(permission) | |
| def _require_student_manager(self) -> Optional[Dict[str, Any]]: | |
| if self.student_manager is not None: | |
| return None | |
| return { | |
| "success": False, | |
| "message": "StudentManager غير متوفر.", | |
| "version": self.version, | |
| } | |
| def _sanitize(self, value: Any) -> Any: | |
| if isinstance(value, dict): | |
| result: Dict[str, Any] = {} | |
| for key, item in value.items(): | |
| if str(key).lower() in PRIVATE_STUDENT_FIELDS: | |
| continue | |
| result[key] = self._sanitize(item) | |
| return result | |
| if isinstance(value, list): | |
| return [self._sanitize(item) for item in value] | |
| if isinstance(value, tuple): | |
| return [self._sanitize(item) for item in value] | |
| return value | |
| def _student_admin_summary(self, student: Any) -> Dict[str, Any]: | |
| if not isinstance(student, dict): | |
| return {} | |
| safe = self._sanitize(copy.deepcopy(student)) | |
| devices = safe.get("authorized_devices", []) | |
| if not isinstance(devices, list): | |
| devices = [] | |
| safe["authorized_devices_count"] = len( | |
| [ | |
| device | |
| for device in devices | |
| if isinstance(device, dict) and device.get("status", "active") == "active" | |
| ] | |
| ) | |
| return safe | |
| def _audit( | |
| self, | |
| action: str, | |
| target: str = "", | |
| actor: str = "admin", | |
| success: bool = True, | |
| details: Any = None, | |
| ) -> None: | |
| event = { | |
| "created_at": self.now(), | |
| "action": str(action or ""), | |
| "target": str(target or ""), | |
| "actor": str(actor or "admin"), | |
| "success": bool(success), | |
| "details": self._sanitize(details if details is not None else {}), | |
| "version": self.version, | |
| } | |
| try: | |
| with self._lock: | |
| with open(self.audit_log_file, "a", encoding="utf-8") as file: | |
| file.write(json.dumps(event, ensure_ascii=False) + "\n") | |
| except Exception: | |
| pass | |
| def load_audit_log( | |
| self, | |
| limit: int = 200, | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("audit.read", admin_key) | |
| if denied: | |
| return denied | |
| items: List[Dict[str, Any]] = [] | |
| try: | |
| if self.audit_log_file.exists(): | |
| with open(self.audit_log_file, "r", encoding="utf-8") as file: | |
| for line in file: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| try: | |
| item = json.loads(line) | |
| except Exception: | |
| continue | |
| if isinstance(item, dict): | |
| items.append(item) | |
| except Exception: | |
| items = [] | |
| limit = max(1, min(int(limit or 200), 2000)) | |
| return { | |
| "success": True, | |
| "count": min(len(items), limit), | |
| "events": items[-limit:], | |
| "version": self.version, | |
| } | |
| # ========================================================= | |
| # إدارة الطلاب والتسجيل | |
| # ========================================================= | |
| def refresh_students(self, use_cloud: bool = True) -> Dict[str, Any]: | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| try: | |
| students = self.student_manager.load_students(use_cloud=bool(use_cloud)) | |
| return { | |
| "success": True, | |
| "students_count": len(students) if isinstance(students, dict) else 0, | |
| "version": self.version, | |
| } | |
| except Exception as exc: | |
| return { | |
| "success": False, | |
| "message": f"فشل تحديث قائمة الطلاب: {exc}", | |
| "version": self.version, | |
| } | |
| def list_students( | |
| self, | |
| status: Any = "all", | |
| full_profiles: bool = False, | |
| search_text: Any = "", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("students.read", admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| try: | |
| self.student_manager.load_students(use_cloud=True) | |
| normalized_status = self._normalize_status(status) | |
| if normalized_status in ACCOUNT_STATUSES: | |
| students = self.student_manager.list_students_by_status(normalized_status) | |
| else: | |
| records = self.student_manager.all_students(full_profiles=bool(full_profiles)) | |
| students = list(records.values()) if isinstance(records, dict) else [] | |
| wanted = str(search_text or "").strip().lower() | |
| safe_students: List[Dict[str, Any]] = [] | |
| for student in students: | |
| if not isinstance(student, dict): | |
| continue | |
| safe = self._student_admin_summary(student) | |
| if wanted: | |
| haystack = " ".join( | |
| str(safe.get(field, "")) | |
| for field in ( | |
| "identity", | |
| "student_id", | |
| "full_name", | |
| "name", | |
| "access_key", | |
| "email", | |
| "phone", | |
| "country", | |
| "grade", | |
| ) | |
| ).lower() | |
| if wanted not in haystack: | |
| continue | |
| safe_students.append(safe) | |
| safe_students.sort( | |
| key=lambda item: str( | |
| item.get("registration_requested_at") | |
| or item.get("created_at") | |
| or "" | |
| ), | |
| reverse=True, | |
| ) | |
| return { | |
| "success": True, | |
| "status_filter": normalized_status if normalized_status else "all", | |
| "count": len(safe_students), | |
| "students": safe_students, | |
| "version": self.version, | |
| } | |
| except Exception as exc: | |
| return { | |
| "success": False, | |
| "message": f"فشل عرض الطلاب: {exc}", | |
| "version": self.version, | |
| } | |
| def list_pending_registrations(self, admin_key: Any = "") -> Dict[str, Any]: | |
| return self.list_students(status="pending", admin_key=admin_key) | |
| def get_student_record( | |
| self, | |
| identity: Any, | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("students.read", admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| student = self.student_manager.get_student(identity) | |
| if not isinstance(student, dict): | |
| return { | |
| "success": False, | |
| "message": "لم يتم العثور على الطالب.", | |
| "identity": str(identity or ""), | |
| "version": self.version, | |
| } | |
| return { | |
| "success": True, | |
| "student": self._student_admin_summary(student), | |
| "version": self.version, | |
| } | |
| def student_status_counts(self, admin_key: Any = "") -> Dict[str, Any]: | |
| denied = self._guard("students.read", admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| counts = {status: 0 for status in sorted(ACCOUNT_STATUSES)} | |
| try: | |
| records = self.student_manager.all_students(full_profiles=False) | |
| if isinstance(records, dict): | |
| for student in records.values(): | |
| if not isinstance(student, dict): | |
| continue | |
| status = self._normalize_status(student.get("account_status", "pending")) | |
| counts[status] = counts.get(status, 0) + 1 | |
| except Exception: | |
| pass | |
| return { | |
| "success": True, | |
| "counts": counts, | |
| "total": sum(counts.values()), | |
| "version": self.version, | |
| } | |
| def change_student_registration_status( | |
| self, | |
| identity: Any, | |
| new_status: Any, | |
| reason: Any = "", | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| normalized_status = self._normalize_status(new_status) | |
| permission_map = { | |
| "active": "students.activate", | |
| "suspended": "students.suspend", | |
| "rejected": "students.reject", | |
| "pending": "students.pending", | |
| } | |
| permission = permission_map.get(normalized_status, "students.update") | |
| denied = self._guard(permission, admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| if normalized_status not in ACCOUNT_STATUSES: | |
| return { | |
| "success": False, | |
| "message": "حالة تسجيل الطالب غير صحيحة.", | |
| "allowed_statuses": sorted(ACCOUNT_STATUSES), | |
| "version": self.version, | |
| } | |
| actor = str(changed_by or "admin") | |
| reason_text = str(reason or "").strip() | |
| try: | |
| result = self.student_manager.set_account_status( | |
| identity=identity, | |
| status=normalized_status, | |
| changed_by=actor, | |
| reason=reason_text, | |
| ) | |
| except TypeError: | |
| if normalized_status == "active": | |
| result = self.student_manager.activate_student( | |
| identity, | |
| activated_by=actor, | |
| reason=reason_text, | |
| ) | |
| elif normalized_status == "suspended": | |
| result = self.student_manager.suspend_student( | |
| identity, | |
| suspended_by=actor, | |
| reason=reason_text, | |
| ) | |
| elif normalized_status == "rejected": | |
| result = self.student_manager.reject_student( | |
| identity, | |
| rejected_by=actor, | |
| reason=reason_text, | |
| ) | |
| else: | |
| result = self.student_manager.set_student_pending( | |
| identity, | |
| changed_by=actor, | |
| reason=reason_text, | |
| ) | |
| success = bool(isinstance(result, dict) and result.get("success", False)) | |
| self._audit( | |
| action="student.registration_status_changed", | |
| target=str(identity or ""), | |
| actor=actor, | |
| success=success, | |
| details={ | |
| "new_status": normalized_status, | |
| "reason": reason_text, | |
| "result": result, | |
| }, | |
| ) | |
| if not isinstance(result, dict): | |
| return { | |
| "success": success, | |
| "account_status": normalized_status, | |
| "message": "تم تحديث حالة تسجيل الطالب." if success else "فشل تحديث حالة الطالب.", | |
| "version": self.version, | |
| } | |
| result = self._sanitize(result) | |
| result["version"] = self.version | |
| result["admin_action"] = "change_student_registration_status" | |
| return result | |
| def activate_student_registration( | |
| self, | |
| identity: Any, | |
| reason: Any = "تم قبول التسجيل وتفعيل الحساب.", | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| return self.change_student_registration_status( | |
| identity=identity, | |
| new_status="active", | |
| reason=reason, | |
| changed_by=changed_by, | |
| admin_key=admin_key, | |
| ) | |
| def suspend_student_registration( | |
| self, | |
| identity: Any, | |
| reason: Any = "تم إيقاف الحساب مؤقتًا.", | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| return self.change_student_registration_status( | |
| identity=identity, | |
| new_status="suspended", | |
| reason=reason, | |
| changed_by=changed_by, | |
| admin_key=admin_key, | |
| ) | |
| def reject_student_registration( | |
| self, | |
| identity: Any, | |
| reason: Any = "تم رفض طلب التسجيل.", | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| return self.change_student_registration_status( | |
| identity=identity, | |
| new_status="rejected", | |
| reason=reason, | |
| changed_by=changed_by, | |
| admin_key=admin_key, | |
| ) | |
| def return_student_to_pending( | |
| self, | |
| identity: Any, | |
| reason: Any = "أعيد الحساب إلى قائمة انتظار المراجعة.", | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| return self.change_student_registration_status( | |
| identity=identity, | |
| new_status="pending", | |
| reason=reason, | |
| changed_by=changed_by, | |
| admin_key=admin_key, | |
| ) | |
| # أسماء مختصرة متوافقة مع الواجهة | |
| def activate_student(self, identity: Any, reason: Any = "", changed_by: Any = "admin", admin_key: Any = "") -> Dict[str, Any]: | |
| return self.activate_student_registration(identity, reason, changed_by, admin_key) | |
| def suspend_student(self, identity: Any, reason: Any = "", changed_by: Any = "admin", admin_key: Any = "") -> Dict[str, Any]: | |
| return self.suspend_student_registration(identity, reason, changed_by, admin_key) | |
| def reject_student(self, identity: Any, reason: Any = "", changed_by: Any = "admin", admin_key: Any = "") -> Dict[str, Any]: | |
| return self.reject_student_registration(identity, reason, changed_by, admin_key) | |
| def set_student_pending(self, identity: Any, reason: Any = "", changed_by: Any = "admin", admin_key: Any = "") -> Dict[str, Any]: | |
| return self.return_student_to_pending(identity, reason, changed_by, admin_key) | |
| def bulk_change_student_status( | |
| self, | |
| identities: Sequence[Any], | |
| new_status: Any, | |
| reason: Any = "", | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("students.bulk_status", admin_key) | |
| if denied: | |
| return denied | |
| normalized_status = self._normalize_status(new_status) | |
| unique_identities: List[str] = [] | |
| for identity in identities or []: | |
| text = str(identity or "").strip() | |
| if text and text not in unique_identities: | |
| unique_identities.append(text) | |
| results: List[Dict[str, Any]] = [] | |
| for identity in unique_identities: | |
| result = self.change_student_registration_status( | |
| identity=identity, | |
| new_status=normalized_status, | |
| reason=reason, | |
| changed_by=changed_by, | |
| admin_key=admin_key, | |
| ) | |
| results.append({"identity": identity, "result": result}) | |
| success_count = sum( | |
| 1 | |
| for item in results | |
| if isinstance(item.get("result"), dict) and item["result"].get("success") | |
| ) | |
| return { | |
| "success": success_count == len(results) and bool(results), | |
| "requested_count": len(results), | |
| "success_count": success_count, | |
| "failed_count": len(results) - success_count, | |
| "new_status": normalized_status, | |
| "results": results, | |
| "version": self.version, | |
| } | |
| def update_student_profile( | |
| self, | |
| identity: Any, | |
| updates: Any = None, | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| **kwargs: Any, | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("students.update", admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| data = dict(updates) if isinstance(updates, dict) else {} | |
| data.update(kwargs) | |
| forbidden = { | |
| "identity", | |
| "student_id", | |
| "parent_password_hash", | |
| "parent_pin_hash", | |
| "account_status", | |
| "authorized_devices", | |
| "created_at", | |
| } | |
| cleaned = { | |
| key: value | |
| for key, value in data.items() | |
| if key not in forbidden and key not in PRIVATE_STUDENT_FIELDS | |
| } | |
| if not cleaned: | |
| return { | |
| "success": False, | |
| "message": "لا توجد بيانات مسموح بتحديثها.", | |
| "version": self.version, | |
| } | |
| try: | |
| student = self.student_manager.update_student(identity, cleaned) | |
| success = isinstance(student, dict) | |
| except Exception as exc: | |
| student = None | |
| success = False | |
| error_message = str(exc) | |
| else: | |
| error_message = "" | |
| self._audit( | |
| action="student.profile_updated", | |
| target=str(identity or ""), | |
| actor=str(changed_by or "admin"), | |
| success=success, | |
| details={"updated_fields": sorted(cleaned.keys())}, | |
| ) | |
| return { | |
| "success": success, | |
| "message": "تم تحديث ملف الطالب." if success else f"فشل تحديث ملف الطالب: {error_message}", | |
| "student": self._student_admin_summary(student), | |
| "updated_fields": sorted(cleaned.keys()), | |
| "version": self.version, | |
| } | |
| def reset_student_access_key( | |
| self, | |
| identity: Any, | |
| new_access_key: Any = "", | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("students.reset_access_key", admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| student = self.student_manager.get_student(identity) | |
| if not isinstance(student, dict): | |
| return { | |
| "success": False, | |
| "message": "لم يتم العثور على الطالب.", | |
| "version": self.version, | |
| } | |
| candidate = str(new_access_key or "").strip() | |
| if not candidate: | |
| candidate = self.student_manager.generate_access_key( | |
| full_name=student.get("full_name") or student.get("name") or "", | |
| identity=student.get("identity") or identity, | |
| ) | |
| updated = self.student_manager.update_student(identity, {"access_key": candidate}) | |
| actual_key = str(updated.get("access_key", "")) if isinstance(updated, dict) else "" | |
| success = bool(actual_key and actual_key == self.student_manager.normalize_access_key(candidate)) | |
| self._audit( | |
| action="student.access_key_reset", | |
| target=str(identity or ""), | |
| actor=str(changed_by or "admin"), | |
| success=success, | |
| details={"access_key_changed": success}, | |
| ) | |
| return { | |
| "success": success, | |
| "message": "تم تغيير مفتاح دخول الطالب." if success else "تعذر تغيير مفتاح الدخول؛ قد يكون مستخدمًا لطالب آخر.", | |
| "identity": str(identity or ""), | |
| "access_key": actual_key, | |
| "version": self.version, | |
| } | |
| def reset_parent_password( | |
| self, | |
| identity: Any, | |
| new_parent_password: Any, | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("students.reset_parent_password", admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| password = str(new_parent_password or "") | |
| if len(password) < 4: | |
| return { | |
| "success": False, | |
| "message": "كلمة مرور ولي الأمر يجب أن تتكون من 4 خانات على الأقل.", | |
| "version": self.version, | |
| } | |
| result = self.student_manager.set_parent_password(identity, password) | |
| success = bool(isinstance(result, dict) and result.get("success", False)) | |
| self._audit( | |
| action="student.parent_password_reset", | |
| target=str(identity or ""), | |
| actor=str(changed_by or "admin"), | |
| success=success, | |
| details={"password_hash_only": True}, | |
| ) | |
| result = result if isinstance(result, dict) else {"success": success} | |
| result = self._sanitize(result) | |
| result["version"] = self.version | |
| return result | |
| def list_student_devices( | |
| self, | |
| identity: Any, | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("students.devices.read", admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| devices = self.student_manager.get_authorized_devices(identity) | |
| safe_devices: List[Dict[str, Any]] = [] | |
| for device in devices if isinstance(devices, list) else []: | |
| if not isinstance(device, dict): | |
| continue | |
| safe_device = dict(device) | |
| fingerprint_hash = str(safe_device.get("fingerprint_hash", "")) | |
| safe_device["fingerprint_preview"] = ( | |
| fingerprint_hash[:8] + "…" if fingerprint_hash else "" | |
| ) | |
| safe_device.pop("raw_fingerprint", None) | |
| safe_devices.append(safe_device) | |
| return { | |
| "success": True, | |
| "identity": str(identity or ""), | |
| "count": len(safe_devices), | |
| "devices": safe_devices, | |
| "version": self.version, | |
| } | |
| def remove_student_device( | |
| self, | |
| identity: Any, | |
| fingerprint_hash: Any, | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("students.devices.remove", admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| result = self.student_manager.remove_authorized_device(identity, fingerprint_hash) | |
| success = bool(isinstance(result, dict) and result.get("success", False)) | |
| self._audit( | |
| action="student.device_removed", | |
| target=str(identity or ""), | |
| actor=str(changed_by or "admin"), | |
| success=success, | |
| details={"fingerprint_preview": str(fingerprint_hash or "")[:8]}, | |
| ) | |
| result = result if isinstance(result, dict) else {"success": success} | |
| result = self._sanitize(result) | |
| result["version"] = self.version | |
| return result | |
| def clear_student_devices( | |
| self, | |
| identity: Any, | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("students.devices.remove", admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| devices = self.student_manager.get_authorized_devices(identity) | |
| results: List[Dict[str, Any]] = [] | |
| for device in devices if isinstance(devices, list) else []: | |
| if not isinstance(device, dict): | |
| continue | |
| fingerprint_hash = device.get("fingerprint_hash", "") | |
| if not fingerprint_hash: | |
| continue | |
| result = self.student_manager.remove_authorized_device(identity, fingerprint_hash) | |
| results.append(self._sanitize(result)) | |
| self._audit( | |
| action="student.devices_cleared", | |
| target=str(identity or ""), | |
| actor=str(changed_by or "admin"), | |
| success=True, | |
| details={"devices_processed": len(results)}, | |
| ) | |
| return { | |
| "success": True, | |
| "identity": str(identity or ""), | |
| "removed_count": sum(1 for item in results if item.get("success")), | |
| "results": results, | |
| "version": self.version, | |
| } | |
| def delete_student_account( | |
| self, | |
| identity: Any, | |
| confirmation: Any, | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("students.delete", admin_key) | |
| if denied: | |
| return denied | |
| unavailable = self._require_student_manager() | |
| if unavailable: | |
| return unavailable | |
| if str(confirmation or "").strip() != "DELETE_STUDENT": | |
| return { | |
| "success": False, | |
| "message": "لحذف الطالب اكتب DELETE_STUDENT في خانة التأكيد.", | |
| "version": self.version, | |
| } | |
| existed = isinstance(self.student_manager.get_student(identity), dict) | |
| success = bool(self.student_manager.delete_student(identity)) if existed else False | |
| self._audit( | |
| action="student.deleted", | |
| target=str(identity or ""), | |
| actor=str(changed_by or "admin"), | |
| success=success, | |
| details={"explicit_confirmation": True}, | |
| ) | |
| return { | |
| "success": success, | |
| "message": "تم حذف النسخة المحلية وفهرس الطالب." if success else "لم يتم العثور على الطالب.", | |
| "warning": "قد تحتاج الملفات السحابية القديمة إلى حذف يدوي من خزنة الطلاب.", | |
| "version": self.version, | |
| } | |
| # ========================================================= | |
| # التوجيه العام والملاحظات | |
| # ========================================================= | |
| def default_general_instruction(self) -> str: | |
| return """ | |
| التوجيه العام لمنصة Divid Teacher: | |
| - لا تشرح للطالب دفعة واحدة. | |
| - قسّم الدرس إلى أجزاء قصيرة. | |
| - اسأل الطالب قبل بداية الحصة. | |
| - اسأل الطالب بعد كل جزء. | |
| - لا تنتقل إلى الجزء التالي حتى يجيب الطالب. | |
| - استخدم الخطة A للدرس الرئيسي. | |
| - استخدم الخطة B لتقليص الفجوة التعليمية عند الحاجة. | |
| - لا تخبر الطالب أنه ضعيف. | |
| - استخدم أسلوبًا مناسبًا لعمر الطالب. | |
| - إذا خرج الطالب عن الموضوع، أعده بهدوء. | |
| - إذا ظهرت فجوة في مهارة أساسية، اربطها بوسام إتقان مناسب. | |
| """.strip() | |
| def load_general_instruction(self) -> str: | |
| try: | |
| with open(self.general_instruction_file, "r", encoding="utf-8") as file: | |
| return file.read() | |
| except Exception: | |
| text = self.default_general_instruction() | |
| try: | |
| with open(self.general_instruction_file, "w", encoding="utf-8") as file: | |
| file.write(text) | |
| except Exception: | |
| pass | |
| return text | |
| def save_general_instruction( | |
| self, | |
| text: Any, | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| _skip_guard: bool = False, | |
| ) -> Dict[str, Any]: | |
| if not _skip_guard: | |
| denied = self._guard("system.write", admin_key) | |
| if denied: | |
| return denied | |
| text = str(text or "").strip() | |
| with self._lock: | |
| with open(self.general_instruction_file, "w", encoding="utf-8") as file: | |
| file.write(text) | |
| self._audit( | |
| action="system.general_instruction_saved", | |
| actor=str(changed_by or "admin"), | |
| success=True, | |
| details={"characters": len(text)}, | |
| ) | |
| return { | |
| "success": True, | |
| "message": "تم حفظ التوجيه العام.", | |
| "text": text, | |
| "version": self.version, | |
| } | |
| def append_general_instruction( | |
| self, | |
| extra_text: Any, | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("system.write", admin_key) | |
| if denied: | |
| return denied | |
| current = self.load_general_instruction() | |
| extra = str(extra_text or "").strip() | |
| if not extra: | |
| return { | |
| "success": False, | |
| "message": "لا يوجد نص لإضافته.", | |
| "text": current, | |
| "version": self.version, | |
| } | |
| return self.save_general_instruction( | |
| current + "\n\n" + extra, | |
| changed_by=changed_by, | |
| admin_key=admin_key, | |
| ) | |
| def load_admin_notes(self) -> List[Dict[str, Any]]: | |
| try: | |
| with open(self.admin_notes_file, "r", encoding="utf-8") as file: | |
| data = json.load(file) | |
| return data if isinstance(data, list) else [] | |
| except Exception: | |
| self.save_admin_notes([]) | |
| return [] | |
| def save_admin_notes(self, notes: Any) -> bool: | |
| values = notes if isinstance(notes, list) else [] | |
| with self._lock: | |
| with open(self.admin_notes_file, "w", encoding="utf-8") as file: | |
| json.dump(values, file, ensure_ascii=False, indent=4) | |
| return True | |
| def add_admin_note( | |
| self, | |
| title: Any, | |
| text: Any, | |
| category: Any = "general", | |
| changed_by: Any = "admin", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("system.write", admin_key) | |
| if denied: | |
| return denied | |
| notes = self.load_admin_notes() | |
| note = { | |
| "id": f"note_{int(datetime.now(timezone.utc).timestamp())}_{len(notes) + 1}", | |
| "title": str(title or "").strip(), | |
| "text": str(text or "").strip(), | |
| "category": str(category or "general").strip(), | |
| "created_at": self.now(), | |
| "created_by": str(changed_by or "admin"), | |
| } | |
| notes.append(note) | |
| self.save_admin_notes(notes) | |
| self._audit( | |
| action="system.admin_note_added", | |
| target=note["id"], | |
| actor=str(changed_by or "admin"), | |
| success=True, | |
| ) | |
| return { | |
| "success": True, | |
| "note": note, | |
| "message": "تمت إضافة الملاحظة الإدارية.", | |
| "version": self.version, | |
| } | |
| # ========================================================= | |
| # المنهج والمواد والمسارات | |
| # ========================================================= | |
| def curriculum_summary(self) -> Any: | |
| return self.curriculum_manager.summary_for_admin() | |
| def add_track(self, grade: Any, track_name: Any, admin_key: Any = "") -> Dict[str, Any]: | |
| denied = self._guard("curriculum.write", admin_key) | |
| if denied: | |
| return denied | |
| result = self.curriculum_manager.add_track(grade=grade, track_name=track_name) | |
| self._audit("curriculum.track_added", str(track_name or ""), success=bool(result), details={"grade": grade}) | |
| return { | |
| "success": bool(result), | |
| "message": "تمت إضافة المسار." if result else "فشل إضافة المسار.", | |
| "version": self.version, | |
| } | |
| def remove_track(self, grade: Any, track_name: Any, admin_key: Any = "") -> Dict[str, Any]: | |
| denied = self._guard("curriculum.write", admin_key) | |
| if denied: | |
| return denied | |
| result = self.curriculum_manager.remove_track(grade=grade, track_name=track_name) | |
| self._audit("curriculum.track_removed", str(track_name or ""), success=bool(result), details={"grade": grade}) | |
| return { | |
| "success": bool(result), | |
| "message": "تم حذف المسار." if result else "فشل حذف المسار.", | |
| "version": self.version, | |
| } | |
| def add_subject( | |
| self, | |
| grade: Any, | |
| subject: Any, | |
| subject_type: Any = "core", | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("curriculum.write", admin_key) | |
| if denied: | |
| return denied | |
| result = self.curriculum_manager.add_subject( | |
| grade=grade, | |
| subject=subject, | |
| subject_type=subject_type, | |
| ) | |
| message = "تمت إضافة المادة كمادة ثانوية." if subject_type == "secondary" else "تمت إضافة المادة كمادة أساسية." | |
| self._audit("curriculum.subject_added", str(subject or ""), success=bool(result), details={"grade": grade, "subject_type": subject_type}) | |
| return { | |
| "success": bool(result), | |
| "message": message if result else "فشل إضافة المادة.", | |
| "version": self.version, | |
| } | |
| def remove_subject(self, grade: Any, subject: Any, admin_key: Any = "") -> Dict[str, Any]: | |
| denied = self._guard("curriculum.write", admin_key) | |
| if denied: | |
| return denied | |
| result = self.curriculum_manager.remove_subject(grade=grade, subject=subject) | |
| self._audit("curriculum.subject_removed", str(subject or ""), success=bool(result), details={"grade": grade}) | |
| return { | |
| "success": bool(result), | |
| "message": "تم حذف المادة." if result else "فشل حذف المادة.", | |
| "version": self.version, | |
| } | |
| def change_subject_type( | |
| self, | |
| grade: Any, | |
| subject: Any, | |
| new_type: Any, | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("curriculum.write", admin_key) | |
| if denied: | |
| return denied | |
| result = self.curriculum_manager.change_subject_type( | |
| grade=grade, | |
| subject=subject, | |
| new_type=new_type, | |
| ) | |
| self._audit("curriculum.subject_type_changed", str(subject or ""), success=bool(result), details={"grade": grade, "new_type": new_type}) | |
| return { | |
| "success": bool(result), | |
| "message": "تم تغيير نوع المادة." if result else "فشل تغيير نوع المادة.", | |
| "version": self.version, | |
| } | |
| def get_subject_route(self, grade: Any, subject: Any) -> Any: | |
| return self.curriculum_manager.decide_subject_route(grade=grade, subject=subject) | |
| # ========================================================= | |
| # الاستراتيجيات وPomodoro | |
| # ========================================================= | |
| def strategy_summary(self) -> Any: | |
| return self.strategy_manager.summary() | |
| def add_subject_strategy( | |
| self, | |
| subject: Any, | |
| strategy_name: Any, | |
| steps_text: Any, | |
| methods_text: Any = "", | |
| pomodoro_recommended: bool = True, | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("strategies.write", admin_key) | |
| if denied: | |
| return denied | |
| steps = [line.strip() for line in str(steps_text or "").splitlines() if line.strip()] | |
| methods = [line.strip() for line in str(methods_text or "").splitlines() if line.strip()] | |
| if not steps: | |
| return { | |
| "success": False, | |
| "message": "يجب إدخال خطوات الاستراتيجية.", | |
| "version": self.version, | |
| } | |
| result = self.strategy_manager.add_subject_strategy( | |
| subject=subject, | |
| strategy_name=strategy_name, | |
| steps=steps, | |
| methods=methods, | |
| pomodoro_recommended=pomodoro_recommended, | |
| ) | |
| self._audit("strategy.subject_strategy_added", str(strategy_name or ""), success=bool(result), details={"subject": subject}) | |
| return { | |
| "success": bool(result), | |
| "message": "تم حفظ استراتيجية المادة." if result else "فشل حفظ الاستراتيجية.", | |
| "version": self.version, | |
| } | |
| def update_pomodoro_settings( | |
| self, | |
| study_minutes: Any = 25, | |
| short_break_minutes: Any = 5, | |
| long_break_minutes: Any = 15, | |
| long_break_after_sessions: Any = 4, | |
| enabled: bool = True, | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("strategies.write", admin_key) | |
| if denied: | |
| return denied | |
| settings = self.strategy_manager.update_pomodoro_settings( | |
| study_minutes=study_minutes, | |
| short_break_minutes=short_break_minutes, | |
| long_break_minutes=long_break_minutes, | |
| long_break_after_sessions=long_break_after_sessions, | |
| enabled=enabled, | |
| ) | |
| self._audit("strategy.pomodoro_updated", success=True, details=settings) | |
| return { | |
| "success": True, | |
| "message": "تم تحديث إعدادات Pomodoro.", | |
| "settings": settings, | |
| "version": self.version, | |
| } | |
| def choose_current_strategy( | |
| self, | |
| student: Any, | |
| subject: Any, | |
| plan_type: Any = "A", | |
| lesson_title: Any = "", | |
| gap_skill: Any = "", | |
| ) -> Any: | |
| return self.strategy_manager.choose_strategy( | |
| student=student, | |
| subject=subject, | |
| plan_type=plan_type, | |
| lesson_title=lesson_title, | |
| gap_skill=gap_skill, | |
| ) | |
| # ========================================================= | |
| # اللغات | |
| # ========================================================= | |
| def language_summary(self) -> Any: | |
| if self.language_manager is None: | |
| return "مدير اللغات غير متوفر حاليًا." | |
| return self.language_manager.summary() | |
| def add_language( | |
| self, | |
| code: Any, | |
| name: Any, | |
| direction: Any = "ltr", | |
| enabled: bool = True, | |
| admin_key: Any = "", | |
| ) -> Dict[str, Any]: | |
| denied = self._guard("languages.write", admin_key) | |
| if denied: | |
| return denied | |
| if self.language_manager is None: | |
| return { | |
| "success": False, | |
| "message": "مدير اللغات غير متوفر.", | |
| "version": self.version, | |
| } | |
| result = self.language_manager.add_supported_language( | |
| code=code, | |
| name=name, | |
| direction=direction, | |
| enabled=enabled, | |
| ) | |
| self._audit("language.added", str(code or ""), success=bool(result), details={"name": name, "direction": direction, "enabled": enabled}) | |
| return { | |
| "success": bool(result), | |
| "message": "تمت إضافة اللغة." if result else "فشل إضافة اللغة.", | |
| "version": self.version, | |
| } | |
| # ========================================================= | |
| # فحص الخدمات ولوحة الإدارة | |
| # ========================================================= | |
| def _manager_summary(self, manager: Any) -> Dict[str, Any]: | |
| if manager is None: | |
| return {"success": False, "available": False} | |
| try: | |
| if hasattr(manager, "summary"): | |
| result = manager.summary() | |
| return result if isinstance(result, dict) else {"success": True, "summary": result} | |
| return { | |
| "success": True, | |
| "available": True, | |
| "version": getattr(manager, "version", "unknown"), | |
| } | |
| except Exception as exc: | |
| return { | |
| "success": False, | |
| "available": True, | |
| "message": str(exc), | |
| } | |
| def services_summary(self) -> Dict[str, Any]: | |
| return { | |
| "student_manager": self._manager_summary(self.student_manager), | |
| "device_identity_manager": self._manager_summary(self.device_manager), | |
| "badge_progress_manager": self._manager_summary(self.badge_progress_manager), | |
| "report_manager": self._manager_summary(self.report_manager), | |
| "ai_task_queue_manager": self._manager_summary(self.queue_manager), | |
| "language_manager": self._manager_summary(self.language_manager), | |
| } | |
| def system_snapshot(self, admin_key: Any = "") -> Dict[str, Any]: | |
| denied = self._guard("system.read", admin_key) | |
| if denied: | |
| return denied | |
| try: | |
| curriculum = self.curriculum_manager.load_config() | |
| except Exception: | |
| curriculum = {} | |
| try: | |
| strategies = self.strategy_manager.load_general_strategies() | |
| except Exception: | |
| strategies = {} | |
| try: | |
| languages = self.language_manager.load_config() if self.language_manager is not None else {} | |
| except Exception: | |
| languages = {} | |
| return { | |
| "success": True, | |
| "created_at": self.now(), | |
| "general_instruction": self.load_general_instruction(), | |
| "curriculum": curriculum, | |
| "strategies": strategies, | |
| "admin_notes": self.load_admin_notes(), | |
| "languages": languages, | |
| "student_status_counts": self.student_status_counts(admin_key=admin_key), | |
| "services": self.services_summary(), | |
| "version": self.version, | |
| } | |
| def format_students_for_dashboard(self, status: Any = "pending", admin_key: Any = "") -> str: | |
| result = self.list_students(status=status, admin_key=admin_key) | |
| if not result.get("success"): | |
| return str(result.get("message", "تعذر عرض الطلاب.")) | |
| students = result.get("students", []) | |
| if not students: | |
| return f"لا يوجد طلاب بالحالة: {self._normalize_status(status)}." | |
| lines = [f"## الطلاب — {self._normalize_status(status)}", ""] | |
| for index, student in enumerate(students, 1): | |
| lines.extend( | |
| [ | |
| f"### {index}. {student.get('full_name') or student.get('name') or student.get('identity')}", | |
| f"- الهوية: {student.get('identity', '')}", | |
| f"- مفتاح الدخول: {student.get('access_key', '')}", | |
| f"- الحالة: {student.get('account_status', 'pending')}", | |
| f"- الصف: {student.get('grade', '')}", | |
| f"- البلد: {student.get('country', '')}", | |
| f"- البريد: {student.get('email', '')}", | |
| f"- الهاتف: {student.get('phone', '')}", | |
| f"- الأجهزة الموثوقة: {student.get('authorized_devices_count', 0)}", | |
| "", | |
| ] | |
| ) | |
| return "\n".join(lines).strip() | |
| def format_admin_dashboard(self, admin_key: Any = "") -> str: | |
| if not self.verify_admin_key(admin_key): | |
| return "مفتاح الإدارة غير صحيح أو غير موجود." | |
| counts_result = self.student_status_counts(admin_key=admin_key) | |
| counts = counts_result.get("counts", {}) if isinstance(counts_result, dict) else {} | |
| text = "# مكتب الإدارة\n\n" | |
| text += "## تسجيل الطلاب\n\n" | |
| text += f"- بانتظار المراجعة: {counts.get('pending', 0)}\n" | |
| text += f"- مفعّلون: {counts.get('active', 0)}\n" | |
| text += f"- موقوفون: {counts.get('suspended', 0)}\n" | |
| text += f"- مرفوضون: {counts.get('rejected', 0)}\n\n" | |
| text += "---\n\n" | |
| text += "## التوجيه العام\n\n" | |
| text += self.load_general_instruction() | |
| text += "\n\n---\n\n" | |
| text += "## المنهج والمواد\n\n" | |
| text += str(self.curriculum_summary()) | |
| text += "\n\n---\n\n" | |
| text += "## الاستراتيجيات\n\n" | |
| text += str(self.strategy_summary()) | |
| text += "\n\n---\n\n" | |
| text += "## اللغات\n\n" | |
| text += str(self.language_summary()) | |
| return text.strip() | |
| def summary(self) -> Dict[str, Any]: | |
| status_result = self.student_status_counts( | |
| admin_key=self.admin_key if self.require_admin_key else "" | |
| ) | |
| counts = status_result.get("counts", {}) if status_result.get("success") else {} | |
| return { | |
| "success": True, | |
| "ready": True, | |
| "version": self.version, | |
| "schema_version": self.schema_version, | |
| "role": "full_admin_control_plane", | |
| "admin_key_required": self.require_admin_key, | |
| "admin_key_configured": bool(self.admin_key), | |
| "permissions": list(FULL_PERMISSIONS), | |
| "student_registration_control": { | |
| "available": self.student_manager is not None, | |
| "allowed_statuses": sorted(ACCOUNT_STATUSES), | |
| "status_counts": counts, | |
| "supports_single_change": True, | |
| "supports_bulk_change": True, | |
| "supports_activate": True, | |
| "supports_suspend": True, | |
| "supports_reject": True, | |
| "supports_return_to_pending": True, | |
| }, | |
| "student_management": { | |
| "profile_update": True, | |
| "access_key_reset": True, | |
| "parent_password_reset": True, | |
| "device_management": True, | |
| "explicit_delete": True, | |
| }, | |
| "services": { | |
| "student_manager": self.student_manager is not None, | |
| "device_identity_manager": self.device_manager is not None, | |
| "badge_progress_manager": self.badge_progress_manager is not None, | |
| "report_manager": self.report_manager is not None, | |
| "ai_task_queue_manager": self.queue_manager is not None, | |
| "language_manager": self.language_manager is not None, | |
| }, | |
| "module_functions": [ | |
| "list_students", | |
| "list_pending_registrations", | |
| "get_student_record", | |
| "change_student_registration_status", | |
| "activate_student_registration", | |
| "suspend_student_registration", | |
| "reject_student_registration", | |
| "return_student_to_pending", | |
| "bulk_change_student_status", | |
| "update_student_profile", | |
| "reset_student_access_key", | |
| "reset_parent_password", | |
| "list_student_devices", | |
| "remove_student_device", | |
| "clear_student_devices", | |
| "delete_student_account", | |
| "load_audit_log", | |
| "system_snapshot", | |
| "summary", | |
| ], | |
| "message": "AdminManager جاهز بالصلاحيات الإدارية الكاملة، بما فيها تغيير حالة تسجيل الطلاب.", | |
| } | |
| def is_ready(self) -> bool: | |
| return True | |
| # ========================================================= | |
| # Singleton ودوال توافق مباشرة | |
| # ========================================================= | |
| _MANAGER: Optional[AdminManager] = None | |
| _MANAGER_LOCK = threading.Lock() | |
| def get_manager(*args: Any, **kwargs: Any) -> AdminManager: | |
| global _MANAGER | |
| if _MANAGER is None: | |
| with _MANAGER_LOCK: | |
| if _MANAGER is None: | |
| _MANAGER = AdminManager(*args, **kwargs) | |
| return _MANAGER | |
| def change_student_registration_status(*args: Any, **kwargs: Any) -> Dict[str, Any]: | |
| return get_manager().change_student_registration_status(*args, **kwargs) | |
| def activate_student_registration(*args: Any, **kwargs: Any) -> Dict[str, Any]: | |
| return get_manager().activate_student_registration(*args, **kwargs) | |
| def suspend_student_registration(*args: Any, **kwargs: Any) -> Dict[str, Any]: | |
| return get_manager().suspend_student_registration(*args, **kwargs) | |
| def reject_student_registration(*args: Any, **kwargs: Any) -> Dict[str, Any]: | |
| return get_manager().reject_student_registration(*args, **kwargs) | |
| def return_student_to_pending(*args: Any, **kwargs: Any) -> Dict[str, Any]: | |
| return get_manager().return_student_to_pending(*args, **kwargs) | |
| def list_students(*args: Any, **kwargs: Any) -> Dict[str, Any]: | |
| return get_manager().list_students(*args, **kwargs) | |
| def list_pending_registrations(*args: Any, **kwargs: Any) -> Dict[str, Any]: | |
| return get_manager().list_pending_registrations(*args, **kwargs) | |
| def summary() -> Dict[str, Any]: | |
| return get_manager().summary() | |
| AdminOfficeManager = AdminManager | |
| SystemAdminManager = AdminManager | |