Emmanuel Frimpong Asante commited on
Commit
8e2341a
·
1 Parent(s): ae868d8

update space

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .idea/Generative_AI_with_poultry_disease_detection_system_v2.iml +1 -0
  2. .idea/misc.xml +1 -1
  3. .idea/workspace.xml +63 -5
  4. app.py +0 -89
  5. models/auth.h5 +0 -3
  6. models/diseases.h5 +0 -3
  7. models/schemas/group_schema.py +0 -40
  8. models/schemas/health_record_schema.py +0 -51
  9. models/schemas/inquiry_schema.py +0 -41
  10. models/schemas/inventory_schema.py +0 -58
  11. models/schemas/log_schema.py +0 -28
  12. models/schemas/todo_schema.py +0 -76
  13. models/schemas/user_schema.py +0 -19
  14. requirements.txt +10 -8
  15. routes/administration.py +0 -417
  16. routes/authentication.py +0 -126
  17. routes/chatbot.py +0 -97
  18. routes/disease_detection.py +0 -93
  19. scripts/populate_health_records.py +0 -57
  20. scripts/populate_inventory.py +0 -52
  21. services/auth_service.py +0 -171
  22. services/disease_detection_service.py +0 -289
  23. services/email_notification_service.py +0 -76
  24. services/health_alerts_service.py +0 -42
  25. services/health_monitoring_service.py +0 -91
  26. services/image_preprocessing.py +0 -43
  27. services/utils.py +0 -204
  28. static/css/adminlte.css +0 -0
  29. static/css/adminlte.css.map +0 -0
  30. static/css/adminlte.min.css +0 -0
  31. static/css/adminlte.min.css.map +0 -0
  32. static/css/adminlte.rtl.css +0 -0
  33. static/css/adminlte.rtl.css.map +0 -0
  34. static/css/adminlte.rtl.min.css +0 -0
  35. static/css/adminlte.rtl.min.css.map +0 -0
  36. static/images/landing-bg.jpg +0 -0
  37. static/images/logo.png +0 -0
  38. static/js/adminlte.js +0 -715
  39. static/js/adminlte.js.map +0 -1
  40. static/js/adminlte.min.js +0 -7
  41. static/js/adminlte.min.js.map +0 -1
  42. templates/admin/dashboard.html +0 -43
  43. templates/admin/group/add.html +0 -30
  44. templates/admin/group/add_member.html +0 -30
  45. templates/admin/group/delete.html +0 -25
  46. templates/admin/group/list.html +0 -45
  47. templates/admin/group/share_task.html +0 -30
  48. templates/admin/group/view.html +0 -52
  49. templates/admin/inventory/add.html +0 -45
  50. templates/admin/inventory/delete.html +0 -31
.idea/Generative_AI_with_poultry_disease_detection_system_v2.iml CHANGED
@@ -4,6 +4,7 @@
4
  <content url="file://$MODULE_DIR$">
5
  <excludeFolder url="file://$MODULE_DIR$/.venv" />
6
  </content>
 
7
  <orderEntry type="sourceFolder" forTests="false" />
8
  <orderEntry type="library" name="jquery-3.6.0" level="application" />
9
  </component>
 
4
  <content url="file://$MODULE_DIR$">
5
  <excludeFolder url="file://$MODULE_DIR$/.venv" />
6
  </content>
7
+ <orderEntry type="jdk" jdkName="poultry" jdkType="Python SDK" />
8
  <orderEntry type="sourceFolder" forTests="false" />
9
  <orderEntry type="library" name="jquery-3.6.0" level="application" />
10
  </component>
.idea/misc.xml CHANGED
@@ -3,5 +3,5 @@
3
  <component name="Black">
4
  <option name="sdkName" value="Python 3.12 (Generative_AI_with_poultry)" />
5
  </component>
6
- <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (Generative_AI_with_poultry_disease_detection_system_v2)" project-jdk-type="Python SDK" />
7
  </project>
 
3
  <component name="Black">
4
  <option name="sdkName" value="Python 3.12 (Generative_AI_with_poultry)" />
5
  </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="poultry" project-jdk-type="Python SDK" />
7
  </project>
.idea/workspace.xml CHANGED
@@ -5,10 +5,69 @@
5
  </component>
6
  <component name="ChangeListManager">
7
  <list default="true" id="27c9ae1a-a6fa-4472-8bcd-a7087620894b" name="Changes" comment="update space">
8
- <change beforePath="$PROJECT_DIR$/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/app.py" afterDir="false" />
9
- <change beforePath="$PROJECT_DIR$/populate_health_records.py" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/populate_health_records.py" afterDir="false" />
10
- <change beforePath="$PROJECT_DIR$/populate_inventory.py" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/populate_inventory.py" afterDir="false" />
11
- <change beforePath="$PROJECT_DIR$/templates/main/base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/main/base.html" afterDir="false" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  </list>
13
  <option name="SHOW_DIALOG" value="false" />
14
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -86,7 +145,6 @@
86
  <option name="INTERPRETER_OPTIONS" value="" />
87
  <option name="PARENT_ENVS" value="true" />
88
  <option name="SDK_HOME" value="$PROJECT_DIR$/.venv/Scripts/python.exe" />
89
- <option name="SDK_NAME" value="Python 3.12 (Generative_AI_with_poultry_disease_detection_system_v2)" />
90
  <option name="WORKING_DIRECTORY" value="" />
91
  <option name="IS_MODULE_SDK" value="false" />
92
  <option name="ADD_CONTENT_ROOTS" value="true" />
 
5
  </component>
6
  <component name="ChangeListManager">
7
  <list default="true" id="27c9ae1a-a6fa-4472-8bcd-a7087620894b" name="Changes" comment="update space">
8
+ <change beforePath="$PROJECT_DIR$/app.py" beforeDir="false" />
9
+ <change beforePath="$PROJECT_DIR$/models/auth.h5" beforeDir="false" />
10
+ <change beforePath="$PROJECT_DIR$/models/diseases.h5" beforeDir="false" />
11
+ <change beforePath="$PROJECT_DIR$/models/schemas/group_schema.py" beforeDir="false" />
12
+ <change beforePath="$PROJECT_DIR$/models/schemas/health_record_schema.py" beforeDir="false" />
13
+ <change beforePath="$PROJECT_DIR$/models/schemas/inquiry_schema.py" beforeDir="false" />
14
+ <change beforePath="$PROJECT_DIR$/models/schemas/inventory_schema.py" beforeDir="false" />
15
+ <change beforePath="$PROJECT_DIR$/models/schemas/log_schema.py" beforeDir="false" />
16
+ <change beforePath="$PROJECT_DIR$/models/schemas/todo_schema.py" beforeDir="false" />
17
+ <change beforePath="$PROJECT_DIR$/models/schemas/user_schema.py" beforeDir="false" />
18
+ <change beforePath="$PROJECT_DIR$/routes/administration.py" beforeDir="false" />
19
+ <change beforePath="$PROJECT_DIR$/routes/authentication.py" beforeDir="false" />
20
+ <change beforePath="$PROJECT_DIR$/routes/chatbot.py" beforeDir="false" />
21
+ <change beforePath="$PROJECT_DIR$/routes/disease_detection.py" beforeDir="false" />
22
+ <change beforePath="$PROJECT_DIR$/scripts/populate_health_records.py" beforeDir="false" />
23
+ <change beforePath="$PROJECT_DIR$/scripts/populate_inventory.py" beforeDir="false" />
24
+ <change beforePath="$PROJECT_DIR$/services/auth_service.py" beforeDir="false" />
25
+ <change beforePath="$PROJECT_DIR$/services/disease_detection_service.py" beforeDir="false" />
26
+ <change beforePath="$PROJECT_DIR$/services/email_notification_service.py" beforeDir="false" />
27
+ <change beforePath="$PROJECT_DIR$/services/health_alerts_service.py" beforeDir="false" />
28
+ <change beforePath="$PROJECT_DIR$/services/health_monitoring_service.py" beforeDir="false" />
29
+ <change beforePath="$PROJECT_DIR$/services/image_preprocessing.py" beforeDir="false" />
30
+ <change beforePath="$PROJECT_DIR$/services/utils.py" beforeDir="false" />
31
+ <change beforePath="$PROJECT_DIR$/static/css/adminlte.css" beforeDir="false" />
32
+ <change beforePath="$PROJECT_DIR$/static/css/adminlte.css.map" beforeDir="false" />
33
+ <change beforePath="$PROJECT_DIR$/static/css/adminlte.min.css" beforeDir="false" />
34
+ <change beforePath="$PROJECT_DIR$/static/css/adminlte.min.css.map" beforeDir="false" />
35
+ <change beforePath="$PROJECT_DIR$/static/css/adminlte.rtl.css" beforeDir="false" />
36
+ <change beforePath="$PROJECT_DIR$/static/css/adminlte.rtl.css.map" beforeDir="false" />
37
+ <change beforePath="$PROJECT_DIR$/static/css/adminlte.rtl.min.css" beforeDir="false" />
38
+ <change beforePath="$PROJECT_DIR$/static/css/adminlte.rtl.min.css.map" beforeDir="false" />
39
+ <change beforePath="$PROJECT_DIR$/static/images/landing-bg.jpg" beforeDir="false" />
40
+ <change beforePath="$PROJECT_DIR$/static/images/logo.png" beforeDir="false" />
41
+ <change beforePath="$PROJECT_DIR$/static/js/adminlte.js" beforeDir="false" />
42
+ <change beforePath="$PROJECT_DIR$/static/js/adminlte.js.map" beforeDir="false" />
43
+ <change beforePath="$PROJECT_DIR$/static/js/adminlte.min.js" beforeDir="false" />
44
+ <change beforePath="$PROJECT_DIR$/static/js/adminlte.min.js.map" beforeDir="false" />
45
+ <change beforePath="$PROJECT_DIR$/templates/admin/dashboard.html" beforeDir="false" />
46
+ <change beforePath="$PROJECT_DIR$/templates/admin/group/add.html" beforeDir="false" />
47
+ <change beforePath="$PROJECT_DIR$/templates/admin/group/add_member.html" beforeDir="false" />
48
+ <change beforePath="$PROJECT_DIR$/templates/admin/group/delete.html" beforeDir="false" />
49
+ <change beforePath="$PROJECT_DIR$/templates/admin/group/list.html" beforeDir="false" />
50
+ <change beforePath="$PROJECT_DIR$/templates/admin/group/share_task.html" beforeDir="false" />
51
+ <change beforePath="$PROJECT_DIR$/templates/admin/group/view.html" beforeDir="false" />
52
+ <change beforePath="$PROJECT_DIR$/templates/admin/inventory/add.html" beforeDir="false" />
53
+ <change beforePath="$PROJECT_DIR$/templates/admin/inventory/delete.html" beforeDir="false" />
54
+ <change beforePath="$PROJECT_DIR$/templates/admin/inventory/edit.html" beforeDir="false" />
55
+ <change beforePath="$PROJECT_DIR$/templates/admin/inventory/list.html" beforeDir="false" />
56
+ <change beforePath="$PROJECT_DIR$/templates/admin/tasks/add.html" beforeDir="false" />
57
+ <change beforePath="$PROJECT_DIR$/templates/admin/tasks/delete.html" beforeDir="false" />
58
+ <change beforePath="$PROJECT_DIR$/templates/admin/tasks/edit.html" beforeDir="false" />
59
+ <change beforePath="$PROJECT_DIR$/templates/admin/tasks/list.html" beforeDir="false" />
60
+ <change beforePath="$PROJECT_DIR$/templates/admin/todo/add.html" beforeDir="false" />
61
+ <change beforePath="$PROJECT_DIR$/templates/admin/todo/delete.html" beforeDir="false" />
62
+ <change beforePath="$PROJECT_DIR$/templates/admin/todo/edit.html" beforeDir="false" />
63
+ <change beforePath="$PROJECT_DIR$/templates/admin/todo/list.html" beforeDir="false" />
64
+ <change beforePath="$PROJECT_DIR$/templates/auth/login.html" beforeDir="false" />
65
+ <change beforePath="$PROJECT_DIR$/templates/auth/register.html" beforeDir="false" />
66
+ <change beforePath="$PROJECT_DIR$/templates/chatbot.html" beforeDir="false" />
67
+ <change beforePath="$PROJECT_DIR$/templates/favicon.ico" beforeDir="false" />
68
+ <change beforePath="$PROJECT_DIR$/templates/index.html" beforeDir="false" />
69
+ <change beforePath="$PROJECT_DIR$/templates/main/auth-base.html" beforeDir="false" />
70
+ <change beforePath="$PROJECT_DIR$/templates/main/base.html" beforeDir="false" />
71
  </list>
72
  <option name="SHOW_DIALOG" value="false" />
73
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
 
145
  <option name="INTERPRETER_OPTIONS" value="" />
146
  <option name="PARENT_ENVS" value="true" />
147
  <option name="SDK_HOME" value="$PROJECT_DIR$/.venv/Scripts/python.exe" />
 
148
  <option name="WORKING_DIRECTORY" value="" />
149
  <option name="IS_MODULE_SDK" value="false" />
150
  <option name="ADD_CONTENT_ROOTS" value="true" />
app.py DELETED
@@ -1,89 +0,0 @@
1
- # app.py
2
- from sys import prefix
3
- from typing import Optional
4
- from fastapi import FastAPI, Request, HTTPException, BackgroundTasks, Depends
5
- from fastapi.templating import Jinja2Templates
6
- from fastapi.responses import HTMLResponse, RedirectResponse
7
- from fastapi.staticfiles import StaticFiles
8
- # import tensorflow as tf
9
- import os
10
- import logging
11
- import time
12
- from routes.authentication import auth_router
13
- from routes.chatbot import chatbot_router
14
- from routes.disease_detection import disease_router
15
- from routes.administration import dashboard_router
16
- from services.health_monitoring_service import evaluate_health_data, send_alerts
17
- from huggingface_hub import login
18
-
19
- # Setup logging
20
- logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
21
- logger = logging.getLogger(__name__)
22
-
23
- # # Check GPU availability for TensorFlow
24
- # gpu_devices = tf.config.list_physical_devices('GPU')
25
- # logger.info(f"{'GPUs available' if gpu_devices else 'Using CPU'} for TensorFlow")
26
-
27
- # Initialize FastAPI app and templates
28
- app = FastAPI()
29
- templates = Jinja2Templates(directory="templates")
30
-
31
- # Mount static files if directory exists
32
- static_dir = "static"
33
- if os.path.isdir(static_dir):
34
- app.mount("/static", StaticFiles(directory=static_dir), name="static")
35
- logger.info("Mounted static directory at /static")
36
- else:
37
- logger.error("Static directory not found.")
38
- raise HTTPException(status_code=500, detail="Static directory not found.")
39
-
40
- # Load Hugging Face Token
41
- HF_TOKEN = os.environ.get("HF_Token")
42
- if HF_TOKEN:
43
- login(token=HF_TOKEN, add_to_git_credential=True)
44
- else:
45
- logger.warning("Hugging Face token not found in environment variables.")
46
-
47
- # Include routers for different modules
48
- app.include_router(auth_router, prefix="/auth", tags=["Authentication"])
49
- app.include_router(disease_router, prefix="/disease", tags=["Disease Detection"])
50
- app.include_router(dashboard_router, prefix="/admin", tags=["Admin Dashboard"])
51
- app.include_router(chatbot_router, prefix="/chatbot", tags=["Chat Bot"])
52
- @app.get("/", response_class=HTMLResponse)
53
- async def landing_page(request: Request): # current_user: Optional[str] = Depends(optional_get_current_user)):
54
- """
55
- Render the landing page if not logged in, otherwise redirect to the appropriate dashboard based on user role.
56
- """
57
- # if current_user:
58
- # user_data = db.get_collection("users").find_one({"username": current_user})
59
- # if user_data:
60
- # user_role = user_data.get("role", "farmer")
61
- # redirect_url = "/admin/"
62
- # logger.info(f"Redirecting {current_user} to {redirect_url}")
63
- # return RedirectResponse(url=redirect_url)
64
- return templates.TemplateResponse("index.html", {"request": request})
65
-
66
- # Health metrics for periodic monitoring
67
- health_metrics = {
68
- "weight_loss_percentage": 6,
69
- "mortality_rate": 1,
70
- "reduced_feed_intake_percentage": 12
71
- }
72
-
73
-
74
- def monitor_health():
75
- """Evaluate health data and log notifications if thresholds are crossed."""
76
- while True:
77
- notifications = evaluate_health_data(health_metrics)
78
- if notifications["notifications"]:
79
- logger.info(f"Health Notifications: {notifications['notifications']}")
80
- send_alerts(notifications["notifications"], os.getenv("FARMER_EMAIL"))
81
- time.sleep(3600) # Run every hour
82
-
83
-
84
- @app.on_event("startup")
85
- async def startup_event():
86
- """Initialize background health monitoring on startup."""
87
- logger.info("Starting background health monitoring.")
88
- BackgroundTasks().add_task(monitor_health)
89
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/auth.h5 DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:55ca5e07d8f4d45eab021364be749ced18402f85f5edb7425486ed76ea5c3093
3
- size 234256896
 
 
 
 
models/diseases.h5 DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:e6fbc0b00b8e4d86b50707fcb39a39f99eecccdb6f4732ea53cdfee793a052c4
3
- size 234256896
 
 
 
 
models/schemas/group_schema.py DELETED
@@ -1,40 +0,0 @@
1
- # models/schemas/group_schema.py
2
-
3
- from pydantic import BaseModel, Field
4
- from bson import ObjectId
5
- from typing import List, Optional
6
- from datetime import datetime
7
-
8
- class PyObjectId(ObjectId):
9
- """Custom ObjectId for Pydantic validation."""
10
- @classmethod
11
- def __get_validators__(cls):
12
- yield cls.validate
13
-
14
- @classmethod
15
- def validate(cls, v):
16
- if not ObjectId.is_valid(v):
17
- raise ValueError("Invalid ObjectId")
18
- return ObjectId(v)
19
-
20
- class Group(BaseModel):
21
- id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
22
- name: str
23
- description: Optional[str] = None
24
- created_by: str # Admin who created the group
25
- members: List[str] = [] # List of user IDs for farmers in the group
26
- tasks: List[PyObjectId] = [] # List of task IDs shared with the group
27
- created_at: datetime = Field(default_factory=datetime.utcnow)
28
-
29
- class Config:
30
- json_encoders = {ObjectId: str}
31
- schema_extra = {
32
- "example": {
33
- "name": "Morning Farm Crew",
34
- "description": "Group for morning shift farmers.",
35
- "created_by": "admin_id_123",
36
- "members": ["farmer_id_1", "farmer_id_2"],
37
- "tasks": ["task_id_1", "task_id_2"],
38
- "created_at": "2024-10-31T10:00:00Z"
39
- }
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/schemas/health_record_schema.py DELETED
@@ -1,51 +0,0 @@
1
- # models/schemas/health_record_schema.py
2
- # check model
3
- from pydantic import BaseModel, Field, validator
4
- from datetime import datetime
5
- from bson import ObjectId
6
- from typing import Optional
7
-
8
- class PyObjectId(ObjectId):
9
- """Custom ObjectId type for Pydantic to enable validation and proper MongoDB support."""
10
- @classmethod
11
- def __get_validators__(cls):
12
- yield cls.validate
13
-
14
- @classmethod
15
- def validate(cls, value):
16
- if not ObjectId.is_valid(value):
17
- raise ValueError("Invalid ObjectId format")
18
- return ObjectId(value)
19
-
20
- class HealthRecord(BaseModel):
21
- id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
22
- bird_id: str = Field(..., description="Unique identifier for a specific bird or batch of birds")
23
- date: datetime = Field(default_factory=datetime.utcnow, description="Timestamp of the health record entry")
24
- weight: float = Field(..., gt=0, description="Weight measurement in kilograms")
25
- mortality_rate: float = Field(..., ge=0, le=100, description="Mortality rate as a percentage")
26
- feed_intake: float = Field(..., ge=0, description="Daily feed intake in kilograms")
27
- disease_detected: Optional[str] = Field(None, description="Detected disease name, if any")
28
- status: str = Field(default="Healthy", description="Health status determined by metrics")
29
- treatment_recommendation: Optional[str] = Field(None, description="Suggested treatment based on diagnosis")
30
-
31
- @validator('status')
32
- def validate_status(cls, value):
33
- allowed_statuses = {"Healthy", "At Risk", "Critical"}
34
- if value not in allowed_statuses:
35
- raise ValueError(f"Status must be one of {allowed_statuses}")
36
- return value
37
-
38
- class Config:
39
- json_encoders = {ObjectId: str}
40
- schema_extra = {
41
- "example": {
42
- "bird_id": "batch_123",
43
- "date": "2024-11-01T14:30:00Z",
44
- "weight": 1.5,
45
- "mortality_rate": 2.0,
46
- "feed_intake": 0.4,
47
- "disease_detected": "Coccidiosis",
48
- "status": "Critical",
49
- "treatment_recommendation": "Administer anti-coccidial medication and improve hygiene"
50
- }
51
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/schemas/inquiry_schema.py DELETED
@@ -1,41 +0,0 @@
1
- # models/schemas/inquiry_schema.py
2
-
3
- from pydantic import BaseModel, Field
4
- from datetime import datetime
5
- from typing import Optional, List
6
- from bson import ObjectId
7
-
8
- class PyObjectId(ObjectId):
9
- @classmethod
10
- def __get_validators__(cls):
11
- yield cls.validate
12
-
13
- @classmethod
14
- def validate(cls, v):
15
- if not ObjectId.is_valid(v):
16
- raise ValueError("Invalid ObjectId")
17
- return ObjectId(v)
18
-
19
- class Inquiry(BaseModel):
20
- id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
21
- user_id: str
22
- inquiry_text: str
23
- response_text: str
24
- timestamp: datetime = Field(default_factory=datetime.utcnow)
25
- attached_image: Optional[str] = None # File path or image ID in storage
26
- detected_disease: Optional[str] = None
27
- disease_confidence: Optional[float] = None
28
-
29
- class Config:
30
- json_encoders = {ObjectId: str}
31
- schema_extra = {
32
- "example": {
33
- "user_id": "user123",
34
- "inquiry_text": "What is the treatment for coccidiosis?",
35
- "response_text": "The recommended treatment for coccidiosis is...",
36
- "timestamp": "2024-11-01T12:00:00Z",
37
- "attached_image": "image123.jpg",
38
- "detected_disease": "Coccidiosis",
39
- "disease_confidence": 85.5
40
- }
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/schemas/inventory_schema.py DELETED
@@ -1,58 +0,0 @@
1
- # models/schemas/inventory_schema.py
2
-
3
- from pydantic import BaseModel, Field
4
- from bson import ObjectId
5
- from typing import Optional
6
- from datetime import datetime
7
-
8
- class PyObjectId(ObjectId):
9
- """Custom ObjectId for Pydantic validation."""
10
- @classmethod
11
- def __get_validators__(cls):
12
- yield cls.validate
13
-
14
- @classmethod
15
- def validate(cls, v):
16
- if not ObjectId.is_valid(v):
17
- raise ValueError("Invalid ObjectId")
18
- return ObjectId(v)
19
-
20
- class InventoryItem(BaseModel):
21
- id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
22
- item_name: str
23
- category: Optional[str] = "General"
24
- quantity: int
25
- restock_level: int # Threshold to trigger restock alert
26
- supplier: Optional[str] = None
27
- last_updated: datetime = Field(default_factory=datetime.utcnow)
28
-
29
- class Config:
30
- json_encoders = {ObjectId: str}
31
- schema_extra = {
32
- "example": {
33
- "item_name": "Chicken Feed",
34
- "category": "Feed",
35
- "quantity": 200,
36
- "restock_level": 50,
37
- "supplier": "Farm Supplies Inc.",
38
- "last_updated": "2024-10-31T10:00:00Z"
39
- }
40
- }
41
-
42
- class InventoryUpdate(BaseModel):
43
- """
44
- Schema for updating inventory items.
45
- """
46
- quantity: Optional[int] = None
47
- restock_level: Optional[int] = None
48
- last_updated: datetime = Field(default_factory=datetime.utcnow)
49
-
50
- class Config:
51
- json_encoders = {ObjectId: str}
52
- schema_extra = {
53
- "example": {
54
- "quantity": 150,
55
- "restock_level": 30,
56
- "last_updated": "2024-11-01T15:00:00Z"
57
- }
58
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/schemas/log_schema.py DELETED
@@ -1,28 +0,0 @@
1
- # models/schemas/log_schema.py
2
-
3
- from pydantic import BaseModel, Field
4
- from datetime import datetime
5
- from typing import Optional
6
- from bson import ObjectId
7
-
8
- class PyObjectId(ObjectId):
9
- """Custom ObjectId class for Pydantic validation."""
10
- @classmethod
11
- def __get_validators__(cls):
12
- yield cls.validate
13
-
14
- @classmethod
15
- def validate(cls, v):
16
- if not ObjectId.is_valid(v):
17
- raise ValueError("Invalid ObjectId")
18
- return ObjectId(v)
19
-
20
- class ActivityLog(BaseModel):
21
- id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
22
- activity_type: str # e.g., "health_check", "feeding", "medication"
23
- description: str
24
- user_id: str
25
- timestamp: datetime = Field(default_factory=datetime.utcnow)
26
-
27
- class Config:
28
- json_encoders = {ObjectId: str}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/schemas/todo_schema.py DELETED
@@ -1,76 +0,0 @@
1
- from pydantic import BaseModel, Field
2
- from typing import Optional
3
- from datetime import datetime
4
- from bson import ObjectId
5
-
6
- class PyObjectId(ObjectId):
7
- """Custom ObjectId class for Pydantic validation."""
8
- @classmethod
9
- def __get_validators__(cls):
10
- yield cls.validate
11
-
12
- @classmethod
13
- def validate(cls, v):
14
- if not ObjectId.is_valid(v):
15
- raise ValueError("Invalid ObjectId")
16
- return ObjectId(v)
17
-
18
- class ToDoItem(BaseModel):
19
- id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
20
- title: str
21
- description: Optional[str] = None
22
- due_date: datetime
23
- is_completed: bool = False
24
- assigned_to: Optional[str] = None # Username or User ID of the farmer
25
- created_at: datetime = Field(default_factory=datetime.utcnow)
26
- updated_at: Optional[datetime] = None # Track the latest update timestamp
27
-
28
- class Config:
29
- json_encoders = {ObjectId: str}
30
- schema_extra = {
31
- "example": {
32
- "title": "Feed the chickens",
33
- "description": "Provide feed and check water levels for all chicken pens",
34
- "due_date": "2024-11-05T14:00:00Z",
35
- "is_completed": False,
36
- "assigned_to": "farmer_username",
37
- "created_at": "2024-10-31T10:00:00Z",
38
- "updated_at": "2024-10-31T12:00:00Z"
39
- }
40
- }
41
-
42
- class ToDoCreate(BaseModel):
43
- """Schema for creating a new to-do item."""
44
- title: str
45
- description: Optional[str] = None
46
- due_date: datetime
47
- assigned_to: Optional[str] = None # Optional assignment at creation
48
-
49
- class Config:
50
- schema_extra = {
51
- "example": {
52
- "title": "Inspect chicken feed storage",
53
- "description": "Check for any signs of spoilage or infestation",
54
- "due_date": "2024-11-02T10:00:00Z",
55
- "assigned_to": "farmer_username"
56
- }
57
- }
58
-
59
- class ToDoUpdate(BaseModel):
60
- """Schema for updating an existing to-do item."""
61
- title: Optional[str] = None
62
- description: Optional[str] = None
63
- due_date: Optional[datetime] = None
64
- is_completed: Optional[bool] = None # Allow marking as completed
65
- assigned_to: Optional[str] = None # Allow reassignment if needed
66
-
67
- class Config:
68
- schema_extra = {
69
- "example": {
70
- "title": "Check chicken feed storage",
71
- "description": "Ensure feed is properly stored to avoid spoilage",
72
- "due_date": "2024-11-03T08:00:00Z",
73
- "is_completed": True,
74
- "assigned_to": "another_farmer_username"
75
- }
76
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/schemas/user_schema.py DELETED
@@ -1,19 +0,0 @@
1
- # models/schemas/user_schema.py
2
-
3
- from pydantic import BaseModel, EmailStr, Field
4
- from enum import Enum
5
-
6
- class UserRole(str, Enum):
7
- FARMER = "farmer"
8
- ADMIN = "admin"
9
-
10
- class UserBase(BaseModel):
11
- username: str = Field(..., min_length=3, max_length=50)
12
- email: EmailStr
13
- role: UserRole
14
-
15
- class UserCreate(UserBase):
16
- password: str = Field(..., min_length=6)
17
-
18
- class UserResponse(UserBase):
19
- id: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,21 +1,23 @@
1
  tensorflow[and-cuda]==2.12.0
2
  opencv-python-headless
3
- fastapi
4
  passlib[bcrypt]
5
- pydantic[email]
6
- pymongo
7
- python-dotenv
8
  python-jose[cryptography]
9
  starlette
10
  uvicorn
11
  jinja2
12
  python-multipart
13
  numpy<2.0.0
14
- pillow
15
  huggingface_hub
16
- transformers
17
  keras
18
- torch
19
- bcrypt>=3.2.0
20
  reportlab
21
  pandas
 
 
 
1
  tensorflow[and-cuda]==2.12.0
2
  opencv-python-headless
3
+ fastapi~=0.112.4
4
  passlib[bcrypt]
5
+ pydantic[email]~=2.9.2
6
+ pymongo~=4.8.0
7
+ python-dotenv~=1.0.1
8
  python-jose[cryptography]
9
  starlette
10
  uvicorn
11
  jinja2
12
  python-multipart
13
  numpy<2.0.0
14
+ pillow~=10.4.0
15
  huggingface_hub
16
+ transformers~=4.46.2
17
  keras
18
+ torch~=2.5.1
19
+ bcrypt~=3.1.7
20
  reportlab
21
  pandas
22
+ pyjwt~=2.9.0
23
+ huggingface-hub~=0.26.2
routes/administration.py DELETED
@@ -1,417 +0,0 @@
1
- # routes/administration.py
2
-
3
- import io
4
- import logging
5
- from datetime import datetime
6
- import pandas as pd
7
- from bson import ObjectId
8
- from fastapi import APIRouter, Depends, Request, HTTPException, status
9
- from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse, FileResponse
10
- from fastapi.templating import Jinja2Templates
11
- from reportlab.lib.pagesizes import letter
12
- from reportlab.pdfgen import canvas
13
- from models.schemas.inventory_schema import InventoryItem, InventoryUpdate
14
- from models.schemas.todo_schema import ToDoItem, ToDoCreate, ToDoUpdate
15
- from services.auth_service import get_current_user, is_admin_user
16
- from services.health_monitoring_service import evaluate_health_data
17
- from services.utils import db, log_activity
18
- from models.schemas.group_schema import Group, PyObjectId
19
-
20
- # Initialize the router and template engine
21
- dashboard_router = APIRouter()
22
- templates = Jinja2Templates(directory="templates")
23
-
24
- # MongoDB Collections
25
- todo_collection = db.get_collection("todo_items")
26
- health_collection = db.get_collection("health_records")
27
- activity_collection = db.get_collection("activity_logs")
28
- user_collection = db.get_collection("users")
29
- inventory_collection = db.get_collection("inventory")
30
- group_collection = db.get_collection("groups")
31
-
32
- admin_email = "[email protected]" # Admin email for notifications
33
-
34
- # --- Dashboard and Health Monitoring Routes ---
35
-
36
- @dashboard_router.get("/", response_class=HTMLResponse)
37
- async def health_dashboard(request: Request, current_user: str = Depends(get_current_user)):
38
- """
39
- Display the admin dashboard with real-time health alerts and historical alerts.
40
- """
41
- current_health_metrics = {"weight_loss_percentage": 4, "mortality_rate": 1, "reduced_feed_intake_percentage": 8}
42
- real_time_alerts = evaluate_health_data(current_health_metrics).get("notifications", [])
43
- historical_alerts = list(health_collection.find().sort("timestamp", -1).limit(20))
44
-
45
- context = {
46
- "request": request,
47
- "user": current_user,
48
- "real_time_alerts": real_time_alerts,
49
- "historical_alerts": historical_alerts,
50
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
51
- }
52
- return templates.TemplateResponse("admin/dashboard.html", context)
53
-
54
-
55
- # --- To-Do Task Management Routes ---
56
-
57
- @dashboard_router.get("/todo", response_class=HTMLResponse)
58
- async def todo_list(request: Request, current_user: str = Depends(get_current_user)):
59
- """
60
- Display a list of all to-do items.
61
- """
62
- todos = list(todo_collection.find({}))
63
-
64
- context = {
65
- "request": request,
66
- "user": current_user,
67
- "todos": todos,
68
- "title": "Todo List",
69
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
70
- }
71
- return templates.TemplateResponse("admin/todo/list.html", context)
72
-
73
-
74
- @dashboard_router.get("/todo/add", response_class=HTMLResponse)
75
- async def add_todo_form(request: Request, current_user: str = Depends(get_current_user)):
76
- """
77
- Show form to add a new to-do item.
78
- """
79
- context = {
80
- "request": request,
81
- "user": current_user,
82
- "title": "Add Todo List",
83
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
84
- }
85
- return templates.TemplateResponse("admin/todo/add.html", context)
86
-
87
-
88
- @dashboard_router.post("/todo/add")
89
- async def create_todo(todo_data: ToDoCreate, current_user: str = Depends(get_current_user)):
90
- """
91
- Create a new to-do item.
92
- """
93
- if not is_admin_user(current_user):
94
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
95
-
96
- todo = todo_data.dict()
97
- todo.update({"created_by": current_user, "is_completed": False, "created_at": datetime.utcnow()})
98
- result = todo_collection.insert_one(todo)
99
-
100
- if result.inserted_id:
101
- log_activity("To-Do Created", f"To-Do '{todo['title']}' created by {current_user}.", current_user)
102
- return {"message": "To-Do item created successfully."}
103
- raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="To-Do creation failed.")
104
-
105
-
106
- @dashboard_router.get("/todo/{todo_id}/edit", response_class=HTMLResponse)
107
- async def edit_todo_form(todo_id: str, request: Request, current_user: str = Depends(get_current_user)):
108
- """
109
- Show form to edit an existing to-do item.
110
- """
111
- todo = todo_collection.find_one({"_id": ObjectId(todo_id)})
112
- if not todo:
113
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="To-Do item not found.")
114
- context = {
115
- "request": request,
116
- "user": current_user,
117
- "title": "Edit Todo List",
118
- "todo": todo,
119
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
120
- }
121
- return templates.TemplateResponse("admin/todo/edit.html", context)
122
-
123
-
124
- @dashboard_router.post("/todo/{todo_id}/edit")
125
- async def update_todo(todo_id: str, todo_data: ToDoUpdate, current_user: str = Depends(get_current_user)):
126
- """
127
- Update an existing to-do item.
128
- """
129
- if not is_admin_user(current_user):
130
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
131
-
132
- update_data = {k: v for k, v in todo_data.dict().items() if v is not None}
133
- result = todo_collection.update_one({"_id": ObjectId(todo_id)}, {"$set": update_data})
134
-
135
- if result.modified_count:
136
- log_activity("To-Do Updated", f"To-Do '{todo_id}' updated by {current_user}.", current_user)
137
- return {"message": "To-Do item updated successfully."}
138
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="To-Do item not found or no changes made.")
139
-
140
-
141
- @dashboard_router.get("/todo/{todo_id}/delete", response_class=HTMLResponse)
142
- async def delete_todo_confirm(todo_id: str, request: Request, current_user: str = Depends(get_current_user)):
143
- """
144
- Show confirmation page before deleting a to-do item.
145
- """
146
- todo = todo_collection.find_one({"_id": ObjectId(todo_id)})
147
- if not todo:
148
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="To-Do item not found.")
149
-
150
- context = {
151
- "request": request,
152
- "user": current_user,
153
- "title": "Delete Todo List",
154
- "todo": todo,
155
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
156
- }
157
-
158
- return templates.TemplateResponse("admin/todo/delete.html", context)
159
-
160
-
161
- @dashboard_router.post("/todo/{todo_id}/delete")
162
- async def delete_todo(todo_id: str, current_user: str = Depends(get_current_user)):
163
- """
164
- Delete a specific to-do item.
165
- """
166
- if not is_admin_user(current_user):
167
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
168
-
169
- result = todo_collection.delete_one({"_id": ObjectId(todo_id)})
170
- if result.deleted_count:
171
- log_activity("To-Do Deleted", f"To-Do '{todo_id}' deleted by {current_user}.", current_user)
172
- return {"message": "To-Do item deleted successfully."}
173
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="To-Do item not found.")
174
-
175
-
176
- # --- Inventory Management Routes ---
177
-
178
- @dashboard_router.get("/inventory", response_class=HTMLResponse)
179
- async def inventory_list(request: Request, current_user: str = Depends(get_current_user)):
180
- """
181
- Display a list of all inventory items.
182
- """
183
- items = list(inventory_collection.find({}))
184
-
185
- context = {
186
- "request": request,
187
- "user": current_user,
188
- "title": "Inventory ",
189
- "items": items,
190
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
191
- }
192
- return templates.TemplateResponse("admin/inventory/list.html", context)
193
-
194
-
195
- @dashboard_router.get("/inventory/add", response_class=HTMLResponse)
196
- async def add_inventory_form(request: Request, current_user: str = Depends(get_current_user)):
197
- """
198
- Show form to add a new inventory item.
199
- """
200
- context = {
201
- "request": request,
202
- "user": current_user,
203
- "title": "Add to Inventory ",
204
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
205
- }
206
- return templates.TemplateResponse("admin/inventory/add.html",context)
207
-
208
-
209
- @dashboard_router.post("/inventory/add")
210
- async def create_inventory(item: InventoryItem, current_user: str = Depends(get_current_user)):
211
- """
212
- Create a new inventory item.
213
- """
214
- if not is_admin_user(current_user):
215
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
216
-
217
- item_data = item.dict()
218
- item_data["last_updated"] = datetime.utcnow()
219
- result = inventory_collection.insert_one(item_data)
220
-
221
- if result.inserted_id:
222
- log_activity("Inventory Created", f"Inventory item '{item.item_name}' added by {current_user}.", current_user)
223
- return {"message": "Inventory item created successfully."}
224
- raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Inventory creation failed.")
225
-
226
-
227
- @dashboard_router.get("/inventory/{item_id}/edit", response_class=HTMLResponse)
228
- async def edit_inventory_form(item_id: str, request: Request, current_user: str = Depends(get_current_user)):
229
- """
230
- Show form to edit an existing inventory item.
231
- """
232
-
233
- item = inventory_collection.find_one({"_id": ObjectId(item_id)})
234
- if not item:
235
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Inventory item not found.")
236
-
237
- context = {
238
- "request": request,
239
- "user": current_user,
240
- "title": "Edit Inventory ",
241
- "item": item,
242
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
243
- }
244
- return templates.TemplateResponse("admin/inventory/edit.html", context)
245
-
246
-
247
- @dashboard_router.post("/inventory/{item_id}/edit")
248
- async def update_inventory(item_id: str, item_update: InventoryUpdate, current_user: str = Depends(get_current_user)):
249
- """
250
- Update an existing inventory item.
251
- """
252
- if not is_admin_user(current_user):
253
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
254
-
255
- update_data = item_update.dict(exclude_unset=True)
256
- update_data["last_updated"] = datetime.utcnow()
257
- result = inventory_collection.update_one({"_id": ObjectId(item_id)}, {"$set": update_data})
258
-
259
- if result.modified_count:
260
- log_activity("Inventory Updated", f"Inventory item '{item_id}' updated by {current_user}.", current_user)
261
- return {"message": "Inventory item updated successfully."}
262
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Inventory item not found or no changes made.")
263
-
264
-
265
- @dashboard_router.get("/inventory/{item_id}/delete", response_class=HTMLResponse)
266
- async def delete_inventory_confirm(item_id: str, request: Request, current_user: str = Depends(get_current_user)):
267
- """
268
- Show confirmation page before deleting an inventory item.
269
- """
270
- item = inventory_collection.find_one({"_id": ObjectId(item_id)})
271
- if not item:
272
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Inventory item not found.")
273
-
274
- context = {
275
- "request": request,
276
- "user": current_user,
277
- "title": "Delete Item",
278
- "item": item,
279
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
280
- }
281
- return templates.TemplateResponse("admin/inventory/delete.html", context)
282
-
283
-
284
- @dashboard_router.post("/inventory/{item_id}/delete")
285
- async def delete_inventory(item_id: str, current_user: str = Depends(get_current_user)):
286
- """
287
- Delete a specific inventory item.
288
- """
289
- if not is_admin_user(current_user):
290
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
291
-
292
- result = inventory_collection.delete_one({"_id": ObjectId(item_id)})
293
- if result.deleted_count:
294
- log_activity("Inventory Deleted", f"Inventory item '{item_id}' deleted by {current_user}.", current_user)
295
- return {"message": "Inventory item deleted successfully."}
296
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Inventory item not found.")
297
-
298
-
299
- # --- Group Management Routes ---
300
-
301
- @dashboard_router.get("/groups", response_class=HTMLResponse)
302
- async def group_list(request: Request, current_user: str = Depends(get_current_user)):
303
- """
304
- Display a list of all groups.
305
- """
306
- groups = list(group_collection.find({}))
307
- context = {
308
- "request": request,
309
- "user": current_user,
310
- "title": "Groups",
311
- "groups": groups,
312
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
313
- }
314
- return templates.TemplateResponse("admin/group/list.html", context)
315
-
316
-
317
- @dashboard_router.get("/groups/add", response_class=HTMLResponse)
318
- async def add_group_form(request: Request, current_user: str = Depends(get_current_user)):
319
- """
320
- Show form to create a new group.
321
- """
322
- context = {
323
- "request": request,
324
- "user": current_user,
325
- "title": "Groups",
326
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
327
- }
328
- return templates.TemplateResponse("admin/group/add.html", context)
329
-
330
-
331
- @dashboard_router.post("/groups/add")
332
- async def create_group(group: Group, current_user: str = Depends(get_current_user)):
333
- """
334
- Create a new group.
335
- """
336
- if not is_admin_user(current_user):
337
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
338
-
339
- group_data = group.dict(by_alias=True)
340
- group_data["created_by"] = current_user
341
- group_data["created_at"] = datetime.utcnow()
342
- result = group_collection.insert_one(group_data)
343
-
344
- if result.inserted_id:
345
- log_activity("Group Created", f"Group '{group.group_name}' created by {current_user}.", current_user)
346
- return {"message": "Group created successfully."}
347
- raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Group creation failed.")
348
-
349
-
350
- @dashboard_router.get("/groups/{group_id}/add_member", response_class=HTMLResponse)
351
- async def add_member_form(group_id: str, request: Request, current_user: str = Depends(get_current_user)):
352
- """
353
- Show form to add a member to a group.
354
- """
355
- group = group_collection.find_one({"_id": ObjectId(group_id)})
356
- if not group:
357
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Group not found.")
358
- users = list(user_collection.find({"role": "farmer"}))
359
- context = {
360
- "request": request,
361
- "user": current_user,
362
- "title": "Groups",
363
- "group": group,
364
- "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
365
- }
366
-
367
- return templates.TemplateResponse("admin/group/add_member.html", {"request": request, "group": group, "users": users})
368
-
369
-
370
- @dashboard_router.post("/groups/{group_id}/add_member")
371
- async def add_member_to_group(group_id: str, user_id: str, current_user: str = Depends(get_current_user)):
372
- """
373
- Add a farmer to a group.
374
- """
375
- if not is_admin_user(current_user):
376
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
377
-
378
- group = group_collection.find_one({"_id": ObjectId(group_id)})
379
- if not group:
380
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Group not found.")
381
-
382
- if user_id in group["members"]:
383
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="User already a member.")
384
-
385
- group_collection.update_one({"_id": ObjectId(group_id)}, {"$push": {"members": user_id}})
386
- return {"message": "User added to group successfully."}
387
-
388
-
389
- # --- Health Data Export Route ---
390
-
391
- @dashboard_router.get("/export/health/pdf")
392
- async def export_health_pdf(current_user: str = Depends(get_current_user)):
393
- """
394
- Export health records as a PDF.
395
- """
396
- buffer = io.BytesIO()
397
- pdf = canvas.Canvas(buffer, pagesize=letter)
398
- pdf.setTitle("Health Records Report")
399
-
400
- health_data = list(health_collection.find({}, {"_id": 0}))
401
- if not health_data:
402
- raise HTTPException(status_code=404, detail="No health data available for export.")
403
-
404
- pdf.drawString(100, 750, "Health Records Report")
405
- pdf.drawString(100, 730, f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
406
- y = 710
407
- for record in health_data:
408
- pdf.drawString(100, y, str(record))
409
- y -= 20
410
- if y < 100:
411
- pdf.showPage()
412
- y = 750
413
- pdf.save()
414
- buffer.seek(0)
415
- log_activity("Exported PDF", "Health data exported as PDF", current_user)
416
- return StreamingResponse(buffer, media_type="application/pdf",
417
- headers={"Content-Disposition": "attachment;filename=health_records.pdf"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
routes/authentication.py DELETED
@@ -1,126 +0,0 @@
1
- # routes/authentication.py
2
-
3
- from datetime import timedelta, datetime
4
- import logging
5
- from fastapi import APIRouter, Depends, status, Request, HTTPException, Form
6
- from fastapi.responses import RedirectResponse, JSONResponse
7
- from fastapi.security import OAuth2PasswordRequestForm
8
- from fastapi.templating import Jinja2Templates
9
- from starlette.responses import HTMLResponse
10
- from pydantic import ValidationError
11
- from models.schemas.user_schema import UserCreate, UserResponse
12
- from services.auth_service import (
13
- create_user,
14
- authenticate_user,
15
- create_access_token,
16
- ACCESS_TOKEN_EXPIRE_MINUTES,
17
- get_current_user,
18
- blacklist_token,
19
- Token,
20
- oauth2_scheme
21
- )
22
- from services.utils import log_to_db
23
-
24
- # Configure router and templates
25
- auth_router = APIRouter()
26
- templates = Jinja2Templates(directory="templates")
27
-
28
- # Set up advanced logging
29
- logger = logging.getLogger(__name__)
30
-
31
- @auth_router.get("/register", response_class=HTMLResponse)
32
- async def register_page(request: Request):
33
- """Render the registration form for new users."""
34
- logger.info("Accessed registration page.")
35
- return templates.TemplateResponse("auth/register.html", {"request": request})
36
-
37
- @auth_router.post("/register", response_model=UserResponse)
38
- async def register(
39
- request: Request,
40
- username: str = Form(...),
41
- email: str = Form(...),
42
- password: str = Form(...),
43
- role: str = Form("farmer") # Default role set to "farmer"
44
- ):
45
- """Handle user registration, defaulting to the 'farmer' role."""
46
- logger.info("Received registration request.")
47
-
48
- try:
49
- user = UserCreate(username=username, email=email, password=password, role=role)
50
- logger.info(f"Attempting to register new user: {user.username}")
51
- except ValidationError as e:
52
- logger.error(f"User registration validation failed: {e}")
53
- log_to_db("ERROR", f"User registration validation failed: {e}")
54
- return templates.TemplateResponse("auth/register.html", {
55
- "request": request,
56
- "error": "Validation error in registration details."
57
- })
58
-
59
- result = create_user(user)
60
- if result["status"] == "error":
61
- error_message = result["message"]
62
- logger.warning(f"Registration failed for user {user.username}: {error_message}")
63
- log_to_db("WARNING", f"Registration failed for user {user.username}: {error_message}")
64
- return templates.TemplateResponse("auth/register.html", {
65
- "request": request,
66
- "error": error_message
67
- })
68
-
69
- logger.info(f"User {user.username} registered successfully.")
70
- log_to_db("INFO", f"User {user.username} registered successfully.")
71
- return RedirectResponse(url="/auth/login", status_code=status.HTTP_302_FOUND)
72
-
73
- @auth_router.get("/login", response_class=HTMLResponse)
74
- async def login_page(request: Request):
75
- """Render the login form for users."""
76
- logger.info("Accessed login page.")
77
- return templates.TemplateResponse("auth/login.html", {"request": request})
78
-
79
- @auth_router.post("/login", response_model=Token)
80
- async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
81
- """Authenticate user, issue JWT, and set it in a secure cookie."""
82
- logger.info(f"User login attempt: {form_data.username}")
83
- user = authenticate_user(form_data.username, form_data.password)
84
-
85
- if not user:
86
- error_message = "Invalid username or password"
87
- logger.warning(f"Failed login attempt for user {form_data.username}: {error_message}")
88
- return templates.TemplateResponse("auth/login.html", {"request": request, "error": error_message})
89
-
90
- # Create the JWT token
91
- access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
92
- access_token = create_access_token(data={"sub": user["username"]}, expires_delta=access_token_expires)
93
-
94
- # Set the token in a secure cookie
95
- response = RedirectResponse(url="/admin/", status_code=status.HTTP_302_FOUND)
96
- response.set_cookie(
97
- key="access_token",
98
- value=access_token,
99
- httponly=True,
100
- max_age=ACCESS_TOKEN_EXPIRE_MINUTES * 60,
101
- secure=False,
102
- samesite="Lax"
103
- )
104
- logger.info(f"User {form_data.username} logged in successfully and redirected to dashboard.")
105
- return response
106
- @auth_router.get("/me", response_model=UserResponse)
107
- async def read_users_me(current_user: str = Depends(get_current_user)):
108
- """Retrieve information about the current authenticated user."""
109
- logger.info(f"Fetching data for current user: {current_user}")
110
- if not current_user:
111
- error_message = "User not authenticated"
112
- logger.warning(error_message)
113
- log_to_db("WARNING", error_message)
114
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
115
-
116
- logger.info(f"Data retrieved for user: {current_user}")
117
- log_to_db("INFO", f"Data retrieved for user {current_user}")
118
- return {"username": current_user}
119
-
120
- @auth_router.get("/logout")
121
- async def logout(request: Request):
122
- """Logout the user by clearing the token cookie."""
123
- response = RedirectResponse(url="/auth/login", status_code=status.HTTP_302_FOUND)
124
- response.delete_cookie("access_token")
125
- logger.info("User logged out successfully.")
126
- return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
routes/chatbot.py DELETED
@@ -1,97 +0,0 @@
1
- # routes/chatbot.py
2
-
3
- import cv2
4
- import numpy as np
5
- import logging
6
- from fastapi.templating import Jinja2Templates
7
- from services.auth_service import get_current_user
8
- from services.utils import db
9
- from fastapi import APIRouter, UploadFile, File, Depends, Request, Form
10
- from fastapi.responses import HTMLResponse, JSONResponse
11
- from datetime import datetime
12
- from services.disease_detection_service import bot
13
- from models.schemas.inquiry_schema import Inquiry
14
-
15
- # Initialize router and template engine
16
- chatbot_router = APIRouter()
17
- templates = Jinja2Templates(directory="templates")
18
-
19
- # MongoDB collection for inquiries
20
- inquiry_collection = db.get_collection("inquiries")
21
-
22
- # Configure logging
23
- logging.basicConfig(level=logging.INFO)
24
- logger = logging.getLogger(__name__)
25
-
26
- async def greeting_based_on_time():
27
- logger.info("Generating greeting based on current time.")
28
- current_hour = datetime.now().hour
29
- if 5 <= current_hour < 12:
30
- return "Good morning"
31
- elif 12 <= current_hour < 17:
32
- return "Good afternoon"
33
- else:
34
- return "Good evening"
35
-
36
- @chatbot_router.get("/", response_class=HTMLResponse)
37
- async def chatbot_page(request: Request, current_user: str = Depends(get_current_user)):
38
- logger.info("Loading chatbot page for the user.")
39
- greeting = await greeting_based_on_time()
40
- context = {"request": request, "greeting": f"{greeting}, {current_user}! How can I help you today?"}
41
- logger.info("Returning chatbot HTML page with greeting.")
42
- return templates.TemplateResponse("chatbot.html", context)
43
-
44
- @chatbot_router.post("/inquire")
45
- async def inquire(
46
- text: str = Form(...),
47
- current_user: str = Depends(get_current_user),
48
- file: UploadFile = None
49
- ):
50
- """Handle inquiries with text and optional image for disease detection."""
51
- logger.info("Received inquiry request.")
52
- response_text = ""
53
- detected_disease, disease_confidence = None, None
54
-
55
- # Handle image inquiries with disease detection
56
- if file:
57
- logger.info(f"Image file received: {file.filename}")
58
- image_data = await file.read()
59
- image_np = np.frombuffer(image_data, np.uint8)
60
- image = cv2.imdecode(image_np, cv2.IMREAD_COLOR)
61
- response, disease_name, status, recommendation, confidence = bot.diagnose_and_respond(image)
62
- response_text = response
63
- detected_disease, disease_confidence = disease_name, confidence
64
- logger.info(f"Disease detected: {detected_disease} with confidence {disease_confidence}%")
65
- else:
66
- logger.info("Processing text-only inquiry.")
67
- response_text = bot.llama_response(text)
68
- logger.info(f"Generated response: {response_text}")
69
-
70
- # Log inquiry into MongoDB
71
- try:
72
- inquiry = Inquiry(
73
- user_id=current_user,
74
- inquiry_text=text,
75
- response_text=response_text,
76
- attached_image=file.filename if file else None,
77
- detected_disease=detected_disease,
78
- disease_confidence=disease_confidence
79
- )
80
- inquiry_collection.insert_one(inquiry.dict(by_alias=True))
81
- logger.info("Inquiry logged successfully in MongoDB.")
82
- except Exception as e:
83
- logger.error("Error logging inquiry in MongoDB.", exc_info=True)
84
-
85
- return JSONResponse(content={"response_text": response_text, "detected_disease": detected_disease, "confidence": disease_confidence})
86
-
87
- @chatbot_router.get("/history")
88
- async def get_inquiry_history(current_user: str = Depends(get_current_user)):
89
- """Fetch recent inquiry history for the current user."""
90
- logger.info("Fetching inquiry history for the user.")
91
- try:
92
- history = list(inquiry_collection.find({"user_id": current_user}).sort("timestamp", -1).limit(10))
93
- logger.info("Inquiry history retrieved successfully.")
94
- return JSONResponse(content={"history": history})
95
- except Exception as e:
96
- logger.error("Error fetching inquiry history from MongoDB.", exc_info=True)
97
- return JSONResponse(content={"error": "Failed to fetch inquiry history."}, status_code=500)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
routes/disease_detection.py DELETED
@@ -1,93 +0,0 @@
1
- # routes/disease_detection.py
2
-
3
- import datetime
4
- import logging
5
- import io
6
- from fastapi import APIRouter, UploadFile, File, HTTPException
7
- from fastapi.responses import JSONResponse
8
- from PIL import Image
9
- import numpy as np
10
-
11
- from routes.administration import health_collection
12
- from services.disease_detection_service import bot
13
- from services.health_alerts_service import check_health_thresholds
14
-
15
- # Configure logging for this module
16
- logger = logging.getLogger(__name__)
17
-
18
- # Initialize the disease detection router
19
- disease_router = APIRouter()
20
-
21
-
22
- @disease_router.post("/detect_disease", response_class=JSONResponse)
23
- async def detect_disease(file: UploadFile = File(...)):
24
- """
25
- Detect disease from an uploaded poultry image and provide treatment recommendations.
26
-
27
- Parameters:
28
- file (UploadFile): The image file to analyze.
29
-
30
- Returns:
31
- JSONResponse: JSON with disease prediction, confidence, treatment recommendation,
32
- and other details.
33
- """
34
- logger.info("Received disease detection request.")
35
-
36
- # Step 1: Validate the uploaded file type
37
- if file.content_type not in ["image/jpeg", "image/png"]:
38
- logger.warning(f"Invalid file type: {file.content_type}")
39
- raise HTTPException(status_code=400, detail="Invalid file type. Please upload a JPEG or PNG image.")
40
-
41
- try:
42
- # Step 2: Read and process the image
43
- image_data = await file.read()
44
- image = Image.open(io.BytesIO(image_data)).convert("RGB")
45
- image_np = np.array(image)
46
- logger.info("Image successfully loaded and converted to RGB format.")
47
- except Exception as e:
48
- logger.error(f"Failed to load image: {e}")
49
- raise HTTPException(status_code=500, detail="Error processing the uploaded image.")
50
-
51
- # Step 3: Perform disease detection
52
- try:
53
- detailed_response, disease_name, status, recommendation, confidence = bot.diagnose_and_respond(image_np)
54
- logger.info(f"Disease detected: {disease_name} with {confidence:.2f}% confidence.")
55
- except Exception as e:
56
- logger.error(f"Error in disease detection: {e}")
57
- raise HTTPException(status_code=500, detail="Error processing the image for disease detection.")
58
-
59
- # Step 4: Check if a valid disease was detected
60
- if not disease_name:
61
- logger.warning("No disease detected in the image.")
62
- return JSONResponse(content={"error": "Unable to detect disease. Please try a different image."},
63
- status_code=500)
64
-
65
- # Step 5: Log the detected disease and recommended treatment in health records
66
- record = {
67
- "bird_id": "batch_123", # Placeholder, replace with actual bird/batch ID
68
- "date": datetime.datetime.utcnow(),
69
- "disease_detected": disease_name,
70
- "status": status,
71
- "confidence": confidence,
72
- "treatment_recommendation": recommendation,
73
- "weight": 1.4, # Placeholder, replace with actual weight measurement
74
- "mortality_rate": 0.5, # Placeholder, replace with actual mortality rate
75
- "feed_intake": 0.35 # Placeholder, replace with actual feed intake
76
- }
77
- health_collection.insert_one(record)
78
- logger.info("Disease and treatment recommendation logged in health records.")
79
-
80
- # Step 6: Check for health alerts based on recorded data
81
- check_health_thresholds(record)
82
- logger.info("Health thresholds checked for potential alerts.")
83
-
84
- # Step 7: Construct and return response
85
- response_content = {
86
- "disease": disease_name,
87
- "status": status,
88
- "confidence": f"{confidence:.2f}%",
89
- "recommendation": recommendation,
90
- "details": detailed_response
91
- }
92
- logger.info(f"Response content prepared: {response_content}")
93
- return JSONResponse(content=response_content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scripts/populate_health_records.py DELETED
@@ -1,57 +0,0 @@
1
- # scripts/populate_health_records.py
2
- import os
3
- import random
4
- from datetime import datetime, timedelta
5
- from pymongo import MongoClient
6
- from models.schemas.health_record_schema import HealthRecord
7
- from bson import ObjectId
8
-
9
- # Connect to MongoDB
10
- client = MongoClient(os.getenv("MONGO_URI")) # Replace with your MongoDB URI
11
- db = client["poultry_management"]
12
- health_collection = db["health_records"]
13
-
14
- # Sample data for generating health records
15
- statuses = ["Healthy", "At Risk", "Critical"]
16
- diseases = ["Coccidiosis", "New Castle Disease", "Salmonella", None]
17
- treatments = {
18
- "Coccidiosis": "Administer anti-coccidial medication and improve hygiene",
19
- "New Castle Disease": "Isolate affected birds and consult a veterinarian",
20
- "Salmonella": "Administer antibiotics as prescribed and ensure biosecurity",
21
- None: "No treatment necessary; maintain regular monitoring"
22
- }
23
-
24
- # Function to generate random health record data
25
- def generate_random_health_record():
26
- bird_id = f"batch_{random.randint(100, 999)}" # Random bird batch ID
27
- date = datetime.utcnow() - timedelta(days=random.randint(1, 365)) # Random date within last year
28
- weight = round(random.uniform(0.8, 2.5), 2) # Weight in kg, between 0.8 and 2.5
29
- mortality_rate = round(random.uniform(0, 10), 2) # Mortality rate as a percentage
30
- feed_intake = round(random.uniform(0.1, 0.5), 2) # Feed intake in kg
31
- disease_detected = random.choice(diseases) # Random disease or None
32
- status = random.choices(statuses, weights=[70, 20, 10], k=1)[0] # Weighted choice for realistic distribution
33
- treatment_recommendation = treatments[disease_detected] if disease_detected else "No treatment necessary"
34
-
35
- # Create HealthRecord instance
36
- health_record = HealthRecord(
37
- id=ObjectId(),
38
- bird_id=bird_id,
39
- date=date,
40
- weight=weight,
41
- mortality_rate=mortality_rate,
42
- feed_intake=feed_intake,
43
- disease_detected=disease_detected,
44
- status=status,
45
- treatment_recommendation=treatment_recommendation
46
- )
47
- return health_record.dict(by_alias=True) # Convert to dictionary for MongoDB insertion
48
-
49
- # Generate and insert a large dataset
50
- def populate_health_records(num_records=1000):
51
- health_data = [generate_random_health_record() for _ in range(num_records)]
52
- result = health_collection.insert_many(health_data)
53
- print(f"Inserted {len(result.inserted_ids)} health records into the database.")
54
-
55
- # Run the data population script
56
- if __name__ == "__main__":
57
- populate_health_records()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scripts/populate_inventory.py DELETED
@@ -1,52 +0,0 @@
1
- # scripts/populate_inventory.py
2
- import os
3
- import random
4
- from datetime import datetime, timedelta
5
- from pymongo import MongoClient
6
- from models.schemas.inventory_schema import InventoryItem
7
- from bson import ObjectId
8
-
9
- # Connect to MongoDB
10
- client = MongoClient(os.getenv("MONGO_URI")) # Replace with your MongoDB URI
11
- db = client["poultry_management"]
12
- inventory_collection = db["inventory"]
13
-
14
- # Sample data to generate inventory items
15
- categories = ["Feed", "Medicine", "Supplies"]
16
- suppliers = ["Farm Supplies Inc.", "Agri Products Co.", "Livestock Solutions", "Rural Provisions"]
17
- items = {
18
- "Feed": ["Chicken Feed", "Layer Feed", "Broiler Feed", "Starter Feed"],
19
- "Medicine": ["Antibiotic", "Vitamin Supplement", "Dewormer", "Probiotic"],
20
- "Supplies": ["Water Feeder", "Nesting Box", "Feeding Trough", "Heat Lamp"]
21
- }
22
-
23
- # Function to generate random inventory item
24
- def generate_random_inventory_item():
25
- category = random.choice(categories)
26
- item_name = random.choice(items[category])
27
- quantity = random.randint(10, 500) # Random quantity between 10 and 500
28
- restock_level = random.randint(10, 50) # Random restock level between 10 and 50
29
- supplier = random.choice(suppliers)
30
- last_updated = datetime.utcnow() - timedelta(days=random.randint(1, 30)) # Random recent date
31
-
32
- # Create the InventoryItem
33
- inventory_item = InventoryItem(
34
- id=ObjectId(),
35
- item_name=item_name,
36
- category=category,
37
- quantity=quantity,
38
- restock_level=restock_level,
39
- supplier=supplier,
40
- last_updated=last_updated
41
- )
42
- return inventory_item.dict(by_alias=True) # Convert to dictionary for MongoDB insertion
43
-
44
- # Generate and insert a large dataset
45
- def populate_inventory_data(num_items=1000):
46
- inventory_data = [generate_random_inventory_item() for _ in range(num_items)]
47
- result = inventory_collection.insert_many(inventory_data)
48
- print(f"Inserted {len(result.inserted_ids)} inventory items into the database.")
49
-
50
- # Run the data population script
51
- if __name__ == "__main__":
52
- populate_inventory_data()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/auth_service.py DELETED
@@ -1,171 +0,0 @@
1
- # services/auth_service.py
2
-
3
- import os
4
- import logging
5
- from datetime import datetime, timedelta
6
- from typing import Optional, Union
7
- from fastapi.security import OAuth2PasswordBearer
8
- from passlib.context import CryptContext
9
- from pymongo.errors import DuplicateKeyError
10
- from models.schemas.user_schema import UserCreate
11
- from fastapi import Depends, HTTPException, status
12
- from pydantic import BaseModel
13
- from jose import JWTError, jwt
14
- from fastapi import Cookie
15
- from services.utils import mongo_instance, log_to_db, db # MongoDB and logging utilities
16
-
17
- # Configure logging with a detailed format for better traceability
18
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
19
- logger = logging.getLogger(__name__)
20
-
21
- # Set up OAuth2 scheme and password hashing
22
- oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")
23
- pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
24
-
25
- # JWT settings
26
- SECRET_KEY = os.getenv("SECRET_KEY", "your-secure-secret-key") # Set a secure key in production
27
- ALGORITHM = "HS256"
28
- ACCESS_TOKEN_EXPIRE_MINUTES = 30
29
-
30
- # MongoDB collections
31
- user_collection = mongo_instance.get_collection("users")
32
- blacklist_collection = mongo_instance.get_collection("token_blacklist")
33
-
34
- # Token Pydantic model for response structure
35
- class Token(BaseModel):
36
- access_token: str
37
- token_type: str
38
-
39
- def blacklist_token(token: str, expiration: datetime):
40
- """
41
- Adds a token to the blacklist to prevent future use.
42
- """
43
- try:
44
- blacklist_collection.insert_one({"token": token, "expires": expiration})
45
- logger.info(f"Token blacklisted successfully. Expires at {expiration}.")
46
- log_to_db("INFO", f"Token blacklisted: Expires at {expiration}")
47
- except Exception as e:
48
- logger.error(f"Failed to blacklist token: {e}", exc_info=True)
49
- log_to_db("ERROR", f"Failed to blacklist token: {e}")
50
-
51
- def is_token_blacklisted(token: str) -> bool:
52
- """
53
- Checks if a token is blacklisted.
54
- """
55
- entry = blacklist_collection.find_one({"token": token})
56
- if entry:
57
- logger.warning("Attempted access with a blacklisted token.")
58
- return entry is not None
59
-
60
- def decode_access_token(token: str) -> Optional[str]:
61
- """
62
- Decodes and validates a JWT token, ensuring it's not blacklisted.
63
- """
64
- if is_token_blacklisted(token):
65
- logger.warning("Access attempted with blacklisted token.")
66
- return None
67
- try:
68
- payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
69
- logger.info("Token decoded successfully.")
70
- return payload.get("sub")
71
- except JWTError as e:
72
- logger.error(f"Token decoding failed: {e}", exc_info=True)
73
- return None
74
-
75
- def hash_password(password: str) -> str:
76
- """
77
- Hashes a plaintext password.
78
- """
79
- logger.debug("Hashing password.")
80
- return pwd_context.hash(password)
81
-
82
- def create_user(user_data: UserCreate) -> dict:
83
- """
84
- Registers a new user by hashing the password and storing it in MongoDB.
85
- """
86
- try:
87
- hashed_password = hash_password(user_data.password)
88
- user_document = {
89
- "username": user_data.username,
90
- "email": user_data.email,
91
- "role": user_data.role.value,
92
- "password": hashed_password
93
- }
94
- user_collection.insert_one(user_document)
95
- logger.info(f"User {user_data.username} registered successfully.")
96
- log_to_db("INFO", f"User {user_data.username} registered successfully.")
97
- return {"status": "success", "message": "User registered successfully!"}
98
- except DuplicateKeyError:
99
- logger.warning(f"Registration failed - username or email already exists: {user_data.username}.")
100
- log_to_db("WARNING", f"Registration failed for {user_data.username}: Username or email already exists.")
101
- return {"status": "error", "message": "Username or email already exists."}
102
- except Exception as e:
103
- logger.error(f"Error during user registration: {e}", exc_info=True)
104
- log_to_db("ERROR", f"Error registering user {user_data.username}: {e}")
105
- return {"status": "error", "message": f"Error registering user: {e}"}
106
-
107
- def verify_password(plain_password: str, hashed_password: str) -> bool:
108
- """
109
- Verifies a plaintext password against a hashed password.
110
- """
111
- return pwd_context.verify(plain_password, hashed_password)
112
-
113
- def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
114
- """
115
- Generates a JWT token with an expiration time.
116
- """
117
- to_encode = data.copy()
118
- expire = datetime.utcnow() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
119
- to_encode.update({"exp": expire})
120
- token = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
121
- logger.info(f"Access token created with expiration: {expire}")
122
- return token
123
-
124
-
125
- async def get_current_user(access_token: str = Cookie(None)):
126
- """
127
- Retrieves the current user by decoding the access token.
128
- """
129
- logger.info(f"Cookies received: {access_token}")
130
- if not access_token:
131
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token missing")
132
-
133
- username = decode_access_token(access_token)
134
- if not username:
135
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
136
- logger.info(f"Access granted to user: {username}")
137
- return username
138
-
139
- def is_admin_user(current_user: str) -> bool:
140
- """
141
- Checks if the current user has an admin role.
142
- """
143
- user = db.get_collection("users").find_one({"username": current_user})
144
- is_admin = user and user.get("role") == "admin"
145
- logger.info(f"Admin access check for user {current_user}: {'Granted' if is_admin else 'Denied'}")
146
- return is_admin
147
-
148
- def optional_get_current_user(token: str = Depends(oauth2_scheme)) -> Optional[str]:
149
- """
150
- Optionally retrieves the current user without raising an exception if unauthorized.
151
- """
152
- try:
153
- return get_current_user(token)
154
- except HTTPException as e:
155
- if e.status_code == status.HTTP_401_UNAUTHORIZED:
156
- logger.info("No authenticated user found, continuing as guest.")
157
- return None
158
- raise
159
-
160
- def authenticate_user(username: str, password: str) -> Optional[dict]:
161
- """
162
- Authenticates a user by checking username and password.
163
- """
164
- user = user_collection.find_one({"username": username})
165
- if user and verify_password(password, user["password"]):
166
- logger.info(f"User {username} authenticated successfully.")
167
- log_to_db("INFO", f"User {username} authenticated successfully.")
168
- return user
169
- logger.warning(f"Authentication failed for user {username}.")
170
- log_to_db("WARNING", f"Authentication failed for user {username}.")
171
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/disease_detection_service.py DELETED
@@ -1,289 +0,0 @@
1
- # services/disease_detection_service.py
2
-
3
- import os
4
- import logging
5
- import threading
6
- import tensorflow as tf
7
- import torch
8
- import cv2
9
- import numpy as np
10
- from keras.models import load_model
11
- from huggingface_hub import login
12
- from transformers import AutoTokenizer, AutoModelForCausalLM
13
- from services.utils import db # MongoDB instance from utils
14
- import configparser
15
-
16
- # Configure logging
17
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
- logger = logging.getLogger(__name__)
19
-
20
- # Load Hugging Face API token for secure model access
21
- HF_TOKEN = os.environ.get('HF_Token')
22
- if HF_TOKEN:
23
- try:
24
- # Login to Hugging Face using the token from environment variables
25
- login(token=HF_TOKEN, add_to_git_credential=True)
26
- logger.info("Hugging Face login successful.")
27
- except Exception as e:
28
- logger.error("Failed to login to Hugging Face", exc_info=True)
29
- raise RuntimeError("Failed to login to Hugging Face. Verify API token.")
30
- else:
31
- logger.warning("Hugging Face token missing. Please set HF_Token in environment variables.")
32
-
33
- # Load configuration for model paths
34
- config = configparser.ConfigParser()
35
- try:
36
- config.read('config.ini')
37
- logger.info("Configuration loaded from config.ini.")
38
- except Exception as e:
39
- logger.error("Failed to load configuration from config.ini", exc_info=True)
40
- raise RuntimeError("Failed to load configuration file.")
41
-
42
- # Configure TensorFlow for GPU and mixed precision if supported
43
- gpu_devices = tf.config.list_physical_devices('GPU')
44
- if gpu_devices:
45
- logger.info(f"Number of GPUs found: {len(gpu_devices)}")
46
- for gpu in gpu_devices:
47
- try:
48
- # Enable memory growth to prevent TensorFlow from allocating all GPU memory at once
49
- tf.config.experimental.set_memory_growth(gpu, True)
50
- logger.info(f"Enabled memory growth for GPU: {gpu.name}")
51
- except Exception as e:
52
- logger.error(f"Failed to set memory growth for GPU: {gpu.name}", exc_info=True)
53
- # Enable mixed precision if GPU has sufficient compute capability
54
- try:
55
- if tf.config.experimental.get_device_details(gpu_devices[0]).get('compute_capability', (0, 0))[0] >= 7:
56
- from tensorflow.keras import mixed_precision
57
- policy = mixed_precision.Policy('mixed_float16')
58
- mixed_precision.set_global_policy(policy)
59
- logger.info("Mixed precision enabled on supported GPU.")
60
- except Exception as e:
61
- logger.error("Failed to enable mixed precision", exc_info=True)
62
- else:
63
- logger.info("No GPU detected, using CPU without mixed precision.")
64
-
65
- # Check if GPU is available for PyTorch
66
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
67
- logger.info(f"Using device for PyTorch: {device}")
68
-
69
- # Global model variables and threading event for synchronization
70
- disease_model, auth_model = None, None
71
- llama_model, llama_tokenizer = None, None
72
- model_loading_event = threading.Event()
73
-
74
- # Utility function to load models
75
- def load_model_util(model_path, model_name):
76
- """Load and compile a Keras model from the provided path."""
77
- try:
78
- logger.info(f"Loading {model_name} from path: {model_path}")
79
- model = load_model(model_path, compile=True)
80
- logger.info(f"{model_name} loaded successfully.")
81
- return model
82
- except Exception as e:
83
- logger.error(f"Error loading {model_name}", exc_info=True)
84
- raise RuntimeError(f"Failed to load {model_name}") from e
85
-
86
- # Function to load the disease detection model
87
- def load_disease_model():
88
- # Load the disease detection model from the configured path
89
- model_path = config.get('MODELS', 'DISEASE_MODEL_PATH', fallback="models/diseases.h5")
90
- try:
91
- return load_model_util(model_path, "Disease detection model")
92
- except RuntimeError as e:
93
- logger.error("Failed to load disease detection model", exc_info=True)
94
- raise
95
-
96
- # Function to load the authentication model
97
- def load_auth_model():
98
- # Load the authentication model from the configured path
99
- auth_model_path = config.get('MODELS', 'AUTH_MODEL_PATH', fallback="models/auth.h5")
100
- try:
101
- return load_model_util(auth_model_path, "Authentication model")
102
- except RuntimeError as e:
103
- logger.error("Failed to load authentication model", exc_info=True)
104
- raise
105
-
106
- # Function to load the Llama 3.2 model for language generation
107
- def load_llama_model():
108
- """Load the Llama model and tokenizer for language generation."""
109
- global llama_model, llama_tokenizer
110
- try:
111
- logger.info("Loading Llama 3.2 model and tokenizer using PyTorch-compatible model.")
112
- model_name = "meta-llama/Llama-3.2-1B"
113
-
114
- # Load the tokenizer and model from Hugging Face
115
- llama_tokenizer = AutoTokenizer.from_pretrained(model_name)
116
- logger.info("Tokenizer loaded successfully.")
117
- llama_model = AutoModelForCausalLM.from_pretrained(model_name).to(device)
118
- logger.info("Model loaded successfully.")
119
-
120
- # Add a padding token if it's missing
121
- if llama_tokenizer.pad_token is None:
122
- logger.info("Adding padding token to tokenizer.")
123
- llama_tokenizer.add_special_tokens({'pad_token': '[PAD]'})
124
- llama_model.resize_token_embeddings(len(llama_tokenizer))
125
-
126
- logger.info("Llama model and tokenizer loaded successfully.")
127
- model_loading_event.set() # Signal that the model has been loaded
128
- return llama_model, llama_tokenizer
129
- except Exception as e:
130
- logger.error("Failed to load Llama model", exc_info=True)
131
- model_loading_event.set() # Signal that model loading failed
132
- raise RuntimeError("Failed to load the Llama 3.2 model. Verify compatibility.")
133
-
134
- # Disease mappings and recommended treatments
135
- name_disease = {0: 'Coccidiosis', 1: 'Healthy', 2: 'New Castle Disease', 3: 'Salmonella'}
136
- status_map = {0: 'Critical', 1: 'No issue', 2: 'Critical', 3: 'Critical'}
137
- recommendations = {
138
- 0: 'Administer anti-coccidial medication, maintain hygiene, and ensure proper litter management.',
139
- 1: 'No treatment necessary; maintain regular monitoring and hygiene.',
140
- 2: 'Isolate affected birds and seek veterinary consultation for targeted treatment.',
141
- 3: 'Administer antibiotics as prescribed by a veterinarian and ensure biosecurity.'
142
- }
143
-
144
- class PoultryFarmBot:
145
- def __init__(self, disease_model, auth_model):
146
- self.disease_model = disease_model
147
- self.auth_model = auth_model
148
- self.logger = logging.getLogger(__name__)
149
- self.logger.info("PoultryFarmBot initialized with provided models.")
150
-
151
- def preprocess_image(self, image: np.ndarray) -> np.ndarray:
152
- """Preprocess the input image for model prediction."""
153
- try:
154
- self.logger.info(f"Original image shape: {image.shape}")
155
- # Resize the image to the required input size for the model
156
- image = cv2.resize(image, (224, 224))
157
- self.logger.info(f"Image resized to: {image.shape}")
158
- # Normalize pixel values to be between 0 and 1
159
- image = image / 255.0
160
- self.logger.info("Image normalized for model input.")
161
- return image
162
- except Exception as e:
163
- self.logger.error("Error in image preprocessing", exc_info=True)
164
- raise ValueError("Invalid image format or empty image provided.")
165
-
166
- def generate_detailed_response(self, disease_name: str, status: str, recommendation: str) -> str:
167
- """Generate detailed response using Llama model based on prediction."""
168
- try:
169
- # Create a prompt with detailed information about the disease
170
- prompt = (
171
- f"The detected disease is {disease_name}, classified as {status}. "
172
- f"Suggested action: {recommendation}. "
173
- f"Here is additional information on {disease_name}: causes, symptoms, and effective management."
174
- )
175
- self.logger.info(f"Generated prompt for Llama model: {prompt}")
176
- # Use the Llama model to generate a more detailed response
177
- response = self.llama_response(prompt)
178
- self.logger.info("Generated detailed response from Llama model.")
179
- # Remove the original prompt from the response and return only the generated text
180
- return response.replace(prompt, "").strip()
181
- except Exception as e:
182
- self.logger.error("Error generating detailed response", exc_info=True)
183
- return "Error generating detailed response."
184
-
185
- def predict_disease(self, image: np.ndarray):
186
- """Predict disease from preprocessed image and provide detailed results."""
187
- try:
188
- # Preprocess the image for prediction
189
- self.logger.info("Starting image preprocessing for disease prediction.")
190
- preprocessed_image = self.preprocess_image(image)
191
-
192
- # Use auth_model to verify the image is poultry-related
193
- self.logger.info("Verifying if the image is poultry-related.")
194
- is_poultry = self.auth_model.predict(preprocessed_image.reshape(1, 224, 224, 3)).argmax()
195
- self.logger.info(f"Auth model prediction result: {is_poultry}")
196
- if is_poultry != 0:
197
- self.logger.info("Image not recognized as poultry.")
198
- return {
199
- "message": "Image not recognized as poultry.",
200
- "disease_name": "N/A",
201
- "status": "N/A",
202
- "recommendation": "N/A",
203
- "confidence": None
204
- }
205
-
206
- # Predict disease if image is verified as poultry
207
- self.logger.info("Predicting disease from the image.")
208
- prediction = self.disease_model.predict(preprocessed_image.reshape(1, 224, 224, 3))
209
- predicted_class = prediction.argmax()
210
- self.logger.info(f"Disease model prediction result: {predicted_class}")
211
- disease_name = name_disease.get(predicted_class, "Unknown disease")
212
- disease_status = status_map.get(predicted_class, "Unknown status")
213
- recommendation = recommendations.get(predicted_class, "No recommendation available")
214
- confidence = float(prediction[0][predicted_class] * 100)
215
- self.logger.info(f"Disease Prediction: {disease_name} with {confidence:.2f}% confidence.")
216
-
217
- # Generate a detailed response using Llama model
218
- detailed_response = self.generate_detailed_response(disease_name, disease_status, recommendation)
219
- self.logger.info("Generated detailed response for disease prediction.")
220
- return {
221
- "message": detailed_response,
222
- "disease_name": disease_name,
223
- "status": disease_status,
224
- "recommendation": recommendation,
225
- "confidence": confidence
226
- }
227
- except Exception as e:
228
- self.logger.error("Error in disease prediction", exc_info=True)
229
- return {
230
- "message": "Prediction failed.",
231
- "disease_name": None,
232
- "status": None,
233
- "recommendation": None,
234
- "confidence": None
235
- }
236
-
237
- def llama_response(self, prompt: str) -> str:
238
- """Generate a response from the Llama model based on a prompt."""
239
- try:
240
- # Limit the maximum length of the generated response
241
- max_length = min(len(prompt) + 50, 512)
242
- self.logger.info(f"Generating response with max length: {max_length}")
243
- # Tokenize the input prompt
244
- inputs = llama_tokenizer(prompt, return_tensors="pt", max_length=max_length, truncation=True, padding=True).to(device)
245
- self.logger.info("Input prompt tokenized successfully.")
246
- # Generate a response using the Llama model
247
- outputs = llama_model.generate(inputs["input_ids"], max_length=max_length, do_sample=True, temperature=0.7, top_p=0.9)
248
- self.logger.info("Response generated by Llama model.")
249
- # Decode the generated response into a readable string
250
- response = llama_tokenizer.decode(outputs[0], skip_special_tokens=True)
251
- self.logger.info("Response decoded successfully.")
252
- return response
253
- except Exception as e:
254
- logger.error("Error generating response from Llama model", exc_info=True)
255
- return "Error generating detailed response."
256
-
257
- def diagnose_and_respond(self, image: np.ndarray):
258
- """Diagnose disease and provide comprehensive response."""
259
- # Check if the provided image is valid
260
- self.logger.info("Starting diagnosis process.")
261
- if image is None or image.size == 0:
262
- self.logger.warning("Invalid image provided for diagnosis.")
263
- return {
264
- "message": "Provide a valid poultry fecal image.",
265
- "disease_name": None,
266
- "status": None,
267
- "recommendation": None,
268
- "confidence": None
269
- }
270
- # Predict disease from the provided image
271
- return self.predict_disease(image)
272
-
273
- # Load models upon service startup
274
- logger.info("Loading models for disease detection service.")
275
- try:
276
- auth_model = load_auth_model()
277
- disease_model = load_disease_model()
278
- llama_model, llama_tokenizer = load_llama_model()
279
- except RuntimeError as e:
280
- logger.error("Critical error during model loading. Service cannot start.", exc_info=True)
281
- raise
282
-
283
- # Initialize the bot instance with MongoDB
284
- try:
285
- bot = PoultryFarmBot(disease_model, auth_model)
286
- logger.info("Bot instance initialized with models loaded.")
287
- except Exception as e:
288
- logger.error("Failed to initialize PoultryFarmBot", exc_info=True)
289
- raise RuntimeError("Failed to initialize PoultryFarmBot.") from e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/email_notification_service.py DELETED
@@ -1,76 +0,0 @@
1
- # services/email_notification_service.py
2
-
3
- import os
4
- import smtplib
5
- import logging
6
- from email.mime.multipart import MIMEMultipart
7
- from email.mime.text import MIMEText
8
- from dotenv import load_dotenv
9
-
10
- # Load environment variables
11
- load_dotenv()
12
-
13
- # Set up logging for email service
14
- logging.basicConfig(level=logging.INFO)
15
- logger = logging.getLogger(__name__)
16
-
17
- # Email configuration
18
- SMTP_SERVER = os.getenv("SMTP_SERVER")
19
- SMTP_PORT = int(os.getenv("SMTP_PORT", 587))
20
- EMAIL_USER = os.getenv("EMAIL_USER")
21
- EMAIL_PASSWORD = os.getenv("EMAIL_PASSWORD")
22
- EMAIL_FROM_NAME = os.getenv("EMAIL_FROM_NAME", "Poultry Management System")
23
- DEFAULT_RECEIVER = os.getenv("EMAIL_RECEIVER")
24
-
25
- def send_email(subject: str, body: str, to_email: str = DEFAULT_RECEIVER):
26
- """
27
- Sends an email notification.
28
-
29
- Parameters:
30
- subject (str): Subject of the email.
31
- body (str): Email body content.
32
- to_email (str): Recipient's email address.
33
- """
34
- # Check SMTP configuration
35
- if not all([SMTP_SERVER, SMTP_PORT, EMAIL_USER, EMAIL_PASSWORD, EMAIL_FROM_NAME]):
36
- logger.error("SMTP configuration is incomplete. Check environment variables.")
37
- return
38
-
39
- try:
40
- # Set up the email headers
41
- msg = MIMEMultipart()
42
- msg["From"] = f"{EMAIL_FROM_NAME} <{EMAIL_USER}>"
43
- msg["To"] = to_email
44
- msg["Subject"] = subject
45
-
46
- # Attach the email body
47
- msg.attach(MIMEText(body, "html"))
48
-
49
- # Establish connection to the SMTP server and send the email
50
- with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
51
- server.starttls()
52
- server.login(EMAIL_USER, EMAIL_PASSWORD)
53
- server.sendmail(EMAIL_USER, to_email, msg.as_string())
54
- logger.info(f"Email sent to {to_email} with subject: '{subject}'")
55
- except Exception as e:
56
- logger.error(f"Failed to send email to {to_email}: {e}")
57
-
58
- def generate_notification_template(title: str, message: str) -> str:
59
- """
60
- Generates an HTML email template for notifications.
61
-
62
- Parameters:
63
- title (str): Title of the notification.
64
- message (str): Message content.
65
-
66
- Returns:
67
- str: HTML formatted email body.
68
- """
69
- return f"""
70
- <html>
71
- <body>
72
- <h2>{title}</h2>
73
- <p>{message}</p>
74
- </body>
75
- </html>
76
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/health_alerts_service.py DELETED
@@ -1,42 +0,0 @@
1
- # services/health_alerts_service.py
2
-
3
- import os
4
- from services.utils import db
5
- from datetime import datetime
6
-
7
- health_collection = db.get_collection("health_records")
8
- alert_collection = db.get_collection("alerts")
9
-
10
- # Define thresholds for automated alerts
11
- THRESHOLDS = {
12
- "weight": 1.0, # Example: Weight below 1.0 kg triggers alert
13
- "mortality_rate": 2.0, # Mortality rate over 2% triggers alert
14
- "feed_intake": 0.3 # Feed intake below 0.3 kg triggers alert
15
- }
16
-
17
- def check_health_thresholds(record):
18
- alerts = []
19
- if record["weight"] < THRESHOLDS["weight"]:
20
- alerts.append("Low weight detected")
21
-
22
- if record["mortality_rate"] > THRESHOLDS["mortality_rate"]:
23
- alerts.append("High mortality rate detected")
24
-
25
- if record["feed_intake"] < THRESHOLDS["feed_intake"]:
26
- alerts.append("Reduced feed intake detected")
27
-
28
- if alerts:
29
- alert_record = {
30
- "bird_id": record["bird_id"],
31
- "date": datetime.utcnow(),
32
- "alerts": alerts,
33
- "status": record["status"],
34
- "treatment_recommendation": record["treatment_recommendation"]
35
- }
36
- alert_collection.insert_one(alert_record)
37
- # Send alert notification (e.g., email or SMS)
38
- send_alert_notification(alert_record)
39
-
40
- def send_alert_notification(alert):
41
- farmer_email = os.getenv("FARMER_EMAIL")
42
- # Logic to send notification via email or SMS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/health_monitoring_service.py DELETED
@@ -1,91 +0,0 @@
1
- # services/health_monitoring_service.py
2
-
3
- from datetime import datetime, timedelta
4
- from typing import Dict, List
5
- from services.email_notification_service import send_email
6
-
7
- # Dictionary to map diseases to suggested treatments
8
- TREATMENT_GUIDELINES = {
9
- "Healthy": {
10
- "notification": "The flock is healthy.",
11
- "suggestion": "No treatment necessary; maintain regular monitoring and hygiene."
12
- },
13
- "Coccidiosis": {
14
- "notification": "Coccidiosis symptoms detected.",
15
- "suggestion": "Administer anti-coccidial medication, maintain hygiene, and ensure proper litter management."
16
- },
17
- "New Castle Disease": {
18
- "notification": "New Castle Disease suspected.",
19
- "suggestion": "Isolate affected birds and seek veterinary consultation for targeted treatment."
20
- },
21
- "Salmonella": {
22
- "notification": "Salmonella infection detected.",
23
- "suggestion": "Administer antibiotics as prescribed by a veterinarian and ensure biosecurity."
24
- }
25
- }
26
-
27
- # Notification thresholds for health metrics
28
- HEALTH_THRESHOLDS = {
29
- "weight_loss_percentage": 5, # Alert if weight loss > 5% in a week
30
- "mortality_rate": 2, # Alert if mortality rate > 2% in a week
31
- "reduced_feed_intake_percentage": 10 # Alert if feed intake drops > 10%
32
- }
33
-
34
- def get_health_alerts(health_metrics: dict) -> List[str]:
35
- """
36
- Check health metrics and return alerts if thresholds are crossed.
37
-
38
- Parameters:
39
- health_metrics (dict): Health metrics like weight loss, mortality rate, and feed intake.
40
-
41
- Returns:
42
- list: Notifications and suggestions if any health issues are detected.
43
- """
44
- alerts = []
45
- if health_metrics.get("weight_loss_percentage", 0) > HEALTH_THRESHOLDS["weight_loss_percentage"]:
46
- alerts.append("Alert: Significant weight loss detected. Check feed quality and health.")
47
-
48
- if health_metrics.get("mortality_rate", 0) > HEALTH_THRESHOLDS["mortality_rate"]:
49
- alerts.append("Alert: Increased mortality rate observed. Review conditions and consult a vet.")
50
-
51
- if health_metrics.get("reduced_feed_intake_percentage", 0) > HEALTH_THRESHOLDS["reduced_feed_intake_percentage"]:
52
- alerts.append("Alert: Feed intake has dropped. Check for signs of illness or environmental issues.")
53
-
54
- return alerts
55
-
56
- def send_alerts(alerts: List[str], farmer_email: str):
57
- """Send alert notifications to the farmer's email."""
58
- if alerts:
59
- message = "\n".join(alerts)
60
- subject = "Poultry Health Alert"
61
- send_email(farmer_email, subject, message)
62
-
63
- def get_treatment_recommendation(disease: str) -> Dict[str, str]:
64
- """Get notification and treatment suggestion based on disease detected."""
65
- return TREATMENT_GUIDELINES.get(disease, {
66
- "notification": "Unknown disease detected.",
67
- "suggestion": "Consult a veterinarian for diagnosis and treatment."
68
- })
69
-
70
- def evaluate_health_data(health_metrics: dict) -> Dict[str, List[str]]:
71
- """
72
- Evaluate health data and trigger alerts if thresholds are crossed.
73
-
74
- Parameters:
75
- health_metrics (dict): Health metrics like weight loss, mortality rate, and feed intake.
76
-
77
- Returns:
78
- dict: Notifications if any health issues are detected.
79
- """
80
- notifications = []
81
-
82
- if health_metrics.get("weight_loss_percentage", 0) > HEALTH_THRESHOLDS["weight_loss_percentage"]:
83
- notifications.append("Significant weight loss detected. Monitor feed quality and check for underlying issues.")
84
-
85
- if health_metrics.get("mortality_rate", 0) > HEALTH_THRESHOLDS["mortality_rate"]:
86
- notifications.append("Increased mortality rate. Review flock conditions and investigate potential health issues.")
87
-
88
- if health_metrics.get("reduced_feed_intake_percentage", 0) > HEALTH_THRESHOLDS["reduced_feed_intake_percentage"]:
89
- notifications.append("Feed intake has dropped. Check for signs of illness or environmental stress.")
90
-
91
- return {"notifications": notifications}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/image_preprocessing.py DELETED
@@ -1,43 +0,0 @@
1
- # services/image_preprocessing.py
2
-
3
- from PIL import Image
4
- import numpy as np
5
- import logging
6
-
7
- # Setup logging
8
- logging.basicConfig(level=logging.INFO)
9
- logger = logging.getLogger(__name__)
10
-
11
-
12
- def preprocess_image(image: Image.Image, target_size=(224, 224)):
13
- """
14
- Preprocesses the uploaded image for disease detection.
15
-
16
- Parameters:
17
- image (PIL.Image.Image): Input image to preprocess.
18
- target_size (tuple): Target size for resizing the image.
19
-
20
- Returns:
21
- np.ndarray: Preprocessed image ready for model input.
22
- """
23
- try:
24
- # Ensure the image has 3 color channels (RGB)
25
- image = image.convert("RGB")
26
- logger.info("Image converted to RGB.")
27
-
28
- # Resize image to the target size
29
- image = image.resize(target_size)
30
- logger.info(f"Image resized to {target_size}.")
31
-
32
- # Normalize the image pixels to [0, 1] range
33
- image_array = np.array(image) / 255.0
34
- logger.info("Image normalized to [0, 1] range.")
35
-
36
- # Add a batch dimension to the image array
37
- image_array = np.expand_dims(image_array, axis=0)
38
- logger.info("Batch dimension added to image array.")
39
-
40
- return image_array
41
- except Exception as e:
42
- logger.error(f"Error preprocessing image: {e}")
43
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/utils.py DELETED
@@ -1,204 +0,0 @@
1
- # services/utils.py
2
-
3
- import os
4
- import logging
5
- import time
6
- from datetime import datetime
7
- from typing import Optional
8
- from dotenv import load_dotenv
9
- from pymongo import MongoClient, errors
10
- import smtplib
11
- from email.mime.text import MIMEText
12
- from email.mime.multipart import MIMEMultipart
13
-
14
- # Load environment variables
15
- load_dotenv()
16
-
17
- # Configure logging
18
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
19
- logger = logging.getLogger(__name__)
20
-
21
- # MongoDB connection setup
22
- MONGO_URI = os.getenv("MONGO_URI")
23
- if not MONGO_URI:
24
- logger.error("MONGO_URI environment variable is not set.")
25
- raise ValueError("MONGO_URI environment variable is required.")
26
-
27
- # MongoDB connection retry settings
28
- MAX_RETRIES = 3
29
- RETRY_DELAY = 5 # in seconds
30
-
31
- # Email configuration
32
- SMTP_SERVER = os.getenv("SMTP_SERVER")
33
- SMTP_PORT = int(os.getenv("SMTP_PORT", 587))
34
- EMAIL_USER = os.getenv("EMAIL_USER")
35
- EMAIL_PASSWORD = os.getenv("EMAIL_PASSWORD")
36
- ADMIN_EMAIL = os.getenv("ADMIN_EMAIL")
37
-
38
-
39
- def connect_to_mongo(uri: str, retries: int, delay: int) -> MongoClient:
40
- """
41
- Establishes a MongoDB connection with retry logic.
42
- """
43
- for attempt in range(retries):
44
- try:
45
- logger.info(f"Attempting MongoDB connection (Attempt {attempt + 1}/{retries}).")
46
- client = MongoClient(uri, serverSelectionTimeoutMS=5000)
47
- client.server_info() # Check connection
48
- logger.info("MongoDB connection established successfully.")
49
- return client
50
- except errors.ServerSelectionTimeoutError as e:
51
- logger.error(f"MongoDB connection attempt {attempt + 1} failed: {e}")
52
- if attempt < retries - 1:
53
- time.sleep(delay)
54
- else:
55
- logger.critical("Failed to connect to MongoDB after multiple attempts.")
56
- raise ConnectionError("Could not connect to MongoDB. Verify MONGO_URI and database availability.")
57
-
58
-
59
- # Initialize MongoDB client and database
60
- client = connect_to_mongo(MONGO_URI, MAX_RETRIES, RETRY_DELAY)
61
- db = client.poultry_management # Default database
62
-
63
- # MongoDB Collections
64
- logs_collection = db.logs
65
- activity_logs_collection = db.get_collection("activity_logs")
66
-
67
-
68
- # Logging activity to MongoDB
69
- def log_to_db(level: str, message: str):
70
- """
71
- Logs a message to MongoDB.
72
- """
73
- log_entry = {"level": level, "message": message, "timestamp": datetime.utcnow()}
74
- try:
75
- logs_collection.insert_one(log_entry)
76
- logger.debug(f"Log entry added to MongoDB: {log_entry}")
77
- except Exception as e:
78
- logger.error(f"Failed to log to MongoDB: {e}")
79
-
80
-
81
- # Custom logging handler for MongoDB
82
- class MongoHandler(logging.Handler):
83
- def emit(self, record):
84
- log_to_db(record.levelname, self.format(record))
85
-
86
-
87
- mongo_handler = MongoHandler()
88
- mongo_handler.setLevel(logging.INFO)
89
- mongo_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
90
- logger.addHandler(mongo_handler)
91
-
92
-
93
- # MongoDB wrapper class
94
- class MongoDB:
95
- def __init__(self, uri: str, db_name: str):
96
- self.client = MongoClient(uri, serverSelectionTimeoutMS=5000)
97
- self.db = self.client[db_name]
98
- logger.info(f"Connected to MongoDB database: {db_name}")
99
-
100
- def test_connection(self) -> str:
101
- """
102
- Tests MongoDB connection.
103
- """
104
- try:
105
- test_doc = {"status": "connection_test"}
106
- self.db.test_collection.insert_one(test_doc)
107
- self.db.test_collection.delete_many({"status": "connection_test"})
108
- logger.info("MongoDB connection verified successfully.")
109
- return "Connection successful!"
110
- except (errors.ConnectionFailure, errors.OperationFailure) as e:
111
- logger.error(f"Database connection error: {e}")
112
- return f"Database connection error: {e}"
113
-
114
- def get_collection(self, collection_name: str):
115
- """
116
- Retrieves a specified MongoDB collection.
117
- """
118
- return self.db[collection_name]
119
-
120
- def insert_log(self, level: str, message: str):
121
- """
122
- Adds a log entry to the MongoDB logs collection.
123
- """
124
- log_entry = {"level": level, "message": message, "timestamp": datetime.utcnow()}
125
- try:
126
- self.db.logs.insert_one(log_entry)
127
- logger.debug(f"Database log entry added: {log_entry}")
128
- except Exception as e:
129
- logger.error(f"Failed to insert log into database: {e}")
130
-
131
-
132
- # Create a MongoDB instance for centralized database access
133
- mongo_instance = MongoDB(uri=MONGO_URI, db_name="poultry_management")
134
-
135
-
136
- # Email utility function for sending alerts
137
- def send_email(recipient: str, subject: str, body: str):
138
- """
139
- Sends an email alert.
140
- """
141
- if not SMTP_SERVER or not EMAIL_USER or not EMAIL_PASSWORD:
142
- logger.error("SMTP configuration is incomplete.")
143
- raise EnvironmentError("SMTP configuration is required.")
144
-
145
- msg = MIMEMultipart()
146
- msg["From"] = EMAIL_USER
147
- msg["To"] = recipient
148
- msg["Subject"] = subject
149
- msg.attach(MIMEText(body, "plain"))
150
-
151
- try:
152
- with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
153
- server.starttls()
154
- server.login(EMAIL_USER, EMAIL_PASSWORD)
155
- server.sendmail(EMAIL_USER, recipient, msg.as_string())
156
- logger.info(f"Email sent to {recipient}.")
157
- except Exception as e:
158
- logger.error(f"Failed to send email: {e}")
159
-
160
-
161
- # Logging activity for user actions or system events
162
- def log_activity(activity_type: str, description: str, user_id: Optional[str] = None):
163
- """
164
- Logs a user or system activity in MongoDB.
165
- """
166
- log_entry = {
167
- "activity_type": activity_type,
168
- "description": description,
169
- "user_id": user_id,
170
- "timestamp": datetime.utcnow()
171
- }
172
- try:
173
- activity_logs_collection.insert_one(log_entry)
174
- logger.info(f"Activity logged: {activity_type} - {description}")
175
- except Exception as e:
176
- logger.error(f"Failed to log activity: {e}")
177
-
178
-
179
- # Helper function for testing the database connection
180
- def test_db_connection():
181
- """
182
- Tests MongoDB connection and logs the result.
183
- """
184
- result = mongo_instance.test_connection()
185
- logger.info(result)
186
- return result
187
-
188
-
189
- # Inventory stock alert
190
- def check_inventory_levels():
191
- """
192
- Checks inventory levels and sends alerts for items below the restock level.
193
- """
194
- inventory_collection = db.get_collection("inventory")
195
- low_stock_items = inventory_collection.find({"$expr": {"$lt": ["$quantity", "$restock_level"]}})
196
-
197
- for item in low_stock_items:
198
- alert_message = (
199
- f"Inventory Alert: '{item['item_name']}' is below the restock level. "
200
- f"Current stock: {item['quantity']}, Restock level: {item['restock_level']}."
201
- )
202
- send_email(ADMIN_EMAIL, "Low Stock Alert", alert_message)
203
- logger.info(f"Low stock alert sent for '{item['item_name']}'.")
204
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/css/adminlte.css DELETED
The diff for this file is too large to render. See raw diff
 
static/css/adminlte.css.map DELETED
The diff for this file is too large to render. See raw diff
 
static/css/adminlte.min.css DELETED
The diff for this file is too large to render. See raw diff
 
static/css/adminlte.min.css.map DELETED
The diff for this file is too large to render. See raw diff
 
static/css/adminlte.rtl.css DELETED
The diff for this file is too large to render. See raw diff
 
static/css/adminlte.rtl.css.map DELETED
The diff for this file is too large to render. See raw diff
 
static/css/adminlte.rtl.min.css DELETED
The diff for this file is too large to render. See raw diff
 
static/css/adminlte.rtl.min.css.map DELETED
The diff for this file is too large to render. See raw diff
 
static/images/landing-bg.jpg DELETED
Binary file (694 kB)
 
static/images/logo.png DELETED
Binary file (854 kB)
 
static/js/adminlte.js DELETED
@@ -1,715 +0,0 @@
1
- /*!
2
- * AdminLTE v4.0.0-beta2 (https://adminlte.io)
3
- * Copyright 2014-2024 Colorlib <https://colorlib.com>
4
- * Licensed under MIT (https://github.com/ColorlibHQ/AdminLTE/blob/master/LICENSE)
5
- */
6
- (function (global, factory) {
7
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
9
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.adminlte = {}));
10
- })(this, (function (exports) { 'use strict';
11
-
12
- const domContentLoadedCallbacks = [];
13
- const onDOMContentLoaded = (callback) => {
14
- if (document.readyState === 'loading') {
15
- // add listener on the first call when the document is in loading state
16
- if (!domContentLoadedCallbacks.length) {
17
- document.addEventListener('DOMContentLoaded', () => {
18
- for (const callback of domContentLoadedCallbacks) {
19
- callback();
20
- }
21
- });
22
- }
23
- domContentLoadedCallbacks.push(callback);
24
- }
25
- else {
26
- callback();
27
- }
28
- };
29
- /* SLIDE UP */
30
- const slideUp = (target, duration = 500) => {
31
- target.style.transitionProperty = 'height, margin, padding';
32
- target.style.transitionDuration = `${duration}ms`;
33
- target.style.boxSizing = 'border-box';
34
- target.style.height = `${target.offsetHeight}px`;
35
- target.style.overflow = 'hidden';
36
- window.setTimeout(() => {
37
- target.style.height = '0';
38
- target.style.paddingTop = '0';
39
- target.style.paddingBottom = '0';
40
- target.style.marginTop = '0';
41
- target.style.marginBottom = '0';
42
- }, 1);
43
- window.setTimeout(() => {
44
- target.style.display = 'none';
45
- target.style.removeProperty('height');
46
- target.style.removeProperty('padding-top');
47
- target.style.removeProperty('padding-bottom');
48
- target.style.removeProperty('margin-top');
49
- target.style.removeProperty('margin-bottom');
50
- target.style.removeProperty('overflow');
51
- target.style.removeProperty('transition-duration');
52
- target.style.removeProperty('transition-property');
53
- }, duration);
54
- };
55
- /* SLIDE DOWN */
56
- const slideDown = (target, duration = 500) => {
57
- target.style.removeProperty('display');
58
- let { display } = window.getComputedStyle(target);
59
- if (display === 'none') {
60
- display = 'block';
61
- }
62
- target.style.display = display;
63
- const height = target.offsetHeight;
64
- target.style.overflow = 'hidden';
65
- target.style.height = '0';
66
- target.style.paddingTop = '0';
67
- target.style.paddingBottom = '0';
68
- target.style.marginTop = '0';
69
- target.style.marginBottom = '0';
70
- window.setTimeout(() => {
71
- target.style.boxSizing = 'border-box';
72
- target.style.transitionProperty = 'height, margin, padding';
73
- target.style.transitionDuration = `${duration}ms`;
74
- target.style.height = `${height}px`;
75
- target.style.removeProperty('padding-top');
76
- target.style.removeProperty('padding-bottom');
77
- target.style.removeProperty('margin-top');
78
- target.style.removeProperty('margin-bottom');
79
- }, 1);
80
- window.setTimeout(() => {
81
- target.style.removeProperty('height');
82
- target.style.removeProperty('overflow');
83
- target.style.removeProperty('transition-duration');
84
- target.style.removeProperty('transition-property');
85
- }, duration);
86
- };
87
-
88
- /**
89
- * --------------------------------------------
90
- * @file AdminLTE layout.ts
91
- * @description Layout for AdminLTE.
92
- * @license MIT
93
- * --------------------------------------------
94
- */
95
- /**
96
- * ------------------------------------------------------------------------
97
- * Constants
98
- * ------------------------------------------------------------------------
99
- */
100
- const CLASS_NAME_HOLD_TRANSITIONS = 'hold-transition';
101
- const CLASS_NAME_APP_LOADED = 'app-loaded';
102
- /**
103
- * Class Definition
104
- * ====================================================
105
- */
106
- class Layout {
107
- constructor(element) {
108
- this._element = element;
109
- }
110
- holdTransition() {
111
- let resizeTimer;
112
- window.addEventListener('resize', () => {
113
- document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS);
114
- clearTimeout(resizeTimer);
115
- resizeTimer = setTimeout(() => {
116
- document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS);
117
- }, 400);
118
- });
119
- }
120
- }
121
- onDOMContentLoaded(() => {
122
- const data = new Layout(document.body);
123
- data.holdTransition();
124
- setTimeout(() => {
125
- document.body.classList.add(CLASS_NAME_APP_LOADED);
126
- }, 400);
127
- });
128
-
129
- /**
130
- * --------------------------------------------
131
- * @file AdminLTE push-menu.ts
132
- * @description Push menu for AdminLTE.
133
- * @license MIT
134
- * --------------------------------------------
135
- */
136
- /**
137
- * ------------------------------------------------------------------------
138
- * Constants
139
- * ------------------------------------------------------------------------
140
- */
141
- const DATA_KEY$4 = 'lte.push-menu';
142
- const EVENT_KEY$4 = `.${DATA_KEY$4}`;
143
- const EVENT_OPEN = `open${EVENT_KEY$4}`;
144
- const EVENT_COLLAPSE = `collapse${EVENT_KEY$4}`;
145
- const CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini';
146
- const CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse';
147
- const CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open';
148
- const CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand';
149
- const CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay';
150
- const CLASS_NAME_MENU_OPEN$1 = 'menu-open';
151
- const SELECTOR_APP_SIDEBAR = '.app-sidebar';
152
- const SELECTOR_SIDEBAR_MENU = '.sidebar-menu';
153
- const SELECTOR_NAV_ITEM$1 = '.nav-item';
154
- const SELECTOR_NAV_TREEVIEW = '.nav-treeview';
155
- const SELECTOR_APP_WRAPPER = '.app-wrapper';
156
- const SELECTOR_SIDEBAR_EXPAND = `[class*="${CLASS_NAME_SIDEBAR_EXPAND}"]`;
157
- const SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle="sidebar"]';
158
- const Defaults = {
159
- sidebarBreakpoint: 992
160
- };
161
- /**
162
- * Class Definition
163
- * ====================================================
164
- */
165
- class PushMenu {
166
- constructor(element, config) {
167
- this._element = element;
168
- this._config = Object.assign(Object.assign({}, Defaults), config);
169
- }
170
- // TODO
171
- menusClose() {
172
- const navTreeview = document.querySelectorAll(SELECTOR_NAV_TREEVIEW);
173
- navTreeview.forEach(navTree => {
174
- navTree.style.removeProperty('display');
175
- navTree.style.removeProperty('height');
176
- });
177
- const navSidebar = document.querySelector(SELECTOR_SIDEBAR_MENU);
178
- const navItem = navSidebar === null || navSidebar === void 0 ? void 0 : navSidebar.querySelectorAll(SELECTOR_NAV_ITEM$1);
179
- if (navItem) {
180
- navItem.forEach(navI => {
181
- navI.classList.remove(CLASS_NAME_MENU_OPEN$1);
182
- });
183
- }
184
- }
185
- expand() {
186
- const event = new Event(EVENT_OPEN);
187
- document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE);
188
- document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN);
189
- this._element.dispatchEvent(event);
190
- }
191
- collapse() {
192
- const event = new Event(EVENT_COLLAPSE);
193
- document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN);
194
- document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE);
195
- this._element.dispatchEvent(event);
196
- }
197
- addSidebarBreakPoint() {
198
- var _a, _b, _c;
199
- const sidebarExpandList = (_b = (_a = document.querySelector(SELECTOR_SIDEBAR_EXPAND)) === null || _a === void 0 ? void 0 : _a.classList) !== null && _b !== void 0 ? _b : [];
200
- const sidebarExpand = (_c = Array.from(sidebarExpandList).find(className => className.startsWith(CLASS_NAME_SIDEBAR_EXPAND))) !== null && _c !== void 0 ? _c : '';
201
- const sidebar = document.getElementsByClassName(sidebarExpand)[0];
202
- const sidebarContent = window.getComputedStyle(sidebar, '::before').getPropertyValue('content');
203
- this._config = Object.assign(Object.assign({}, this._config), { sidebarBreakpoint: Number(sidebarContent.replace(/[^\d.-]/g, '')) });
204
- if (window.innerWidth <= this._config.sidebarBreakpoint) {
205
- this.collapse();
206
- }
207
- else {
208
- if (!document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI)) {
209
- this.expand();
210
- }
211
- if (document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI) && document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
212
- this.collapse();
213
- }
214
- }
215
- }
216
- toggle() {
217
- if (document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
218
- this.expand();
219
- }
220
- else {
221
- this.collapse();
222
- }
223
- }
224
- init() {
225
- this.addSidebarBreakPoint();
226
- }
227
- }
228
- /**
229
- * ------------------------------------------------------------------------
230
- * Data Api implementation
231
- * ------------------------------------------------------------------------
232
- */
233
- onDOMContentLoaded(() => {
234
- var _a;
235
- const sidebar = document === null || document === void 0 ? void 0 : document.querySelector(SELECTOR_APP_SIDEBAR);
236
- if (sidebar) {
237
- const data = new PushMenu(sidebar, Defaults);
238
- data.init();
239
- window.addEventListener('resize', () => {
240
- data.init();
241
- });
242
- }
243
- const sidebarOverlay = document.createElement('div');
244
- sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY;
245
- (_a = document.querySelector(SELECTOR_APP_WRAPPER)) === null || _a === void 0 ? void 0 : _a.append(sidebarOverlay);
246
- sidebarOverlay.addEventListener('touchstart', event => {
247
- event.preventDefault();
248
- const target = event.currentTarget;
249
- const data = new PushMenu(target, Defaults);
250
- data.collapse();
251
- }, { passive: true });
252
- sidebarOverlay.addEventListener('click', event => {
253
- event.preventDefault();
254
- const target = event.currentTarget;
255
- const data = new PushMenu(target, Defaults);
256
- data.collapse();
257
- });
258
- const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE);
259
- fullBtn.forEach(btn => {
260
- btn.addEventListener('click', event => {
261
- event.preventDefault();
262
- let button = event.currentTarget;
263
- if ((button === null || button === void 0 ? void 0 : button.dataset.lteToggle) !== 'sidebar') {
264
- button = button === null || button === void 0 ? void 0 : button.closest(SELECTOR_SIDEBAR_TOGGLE);
265
- }
266
- if (button) {
267
- event === null || event === void 0 ? void 0 : event.preventDefault();
268
- const data = new PushMenu(button, Defaults);
269
- data.toggle();
270
- }
271
- });
272
- });
273
- });
274
-
275
- /**
276
- * --------------------------------------------
277
- * @file AdminLTE treeview.ts
278
- * @description Treeview plugin for AdminLTE.
279
- * @license MIT
280
- * --------------------------------------------
281
- */
282
- /**
283
- * ------------------------------------------------------------------------
284
- * Constants
285
- * ------------------------------------------------------------------------
286
- */
287
- // const NAME = 'Treeview'
288
- const DATA_KEY$3 = 'lte.treeview';
289
- const EVENT_KEY$3 = `.${DATA_KEY$3}`;
290
- const EVENT_EXPANDED$2 = `expanded${EVENT_KEY$3}`;
291
- const EVENT_COLLAPSED$2 = `collapsed${EVENT_KEY$3}`;
292
- // const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`
293
- const CLASS_NAME_MENU_OPEN = 'menu-open';
294
- const SELECTOR_NAV_ITEM = '.nav-item';
295
- const SELECTOR_NAV_LINK = '.nav-link';
296
- const SELECTOR_TREEVIEW_MENU = '.nav-treeview';
297
- const SELECTOR_DATA_TOGGLE$1 = '[data-lte-toggle="treeview"]';
298
- const Default$1 = {
299
- animationSpeed: 300,
300
- accordion: true
301
- };
302
- /**
303
- * Class Definition
304
- * ====================================================
305
- */
306
- class Treeview {
307
- constructor(element, config) {
308
- this._element = element;
309
- this._config = Object.assign(Object.assign({}, Default$1), config);
310
- }
311
- open() {
312
- var _a, _b;
313
- const event = new Event(EVENT_EXPANDED$2);
314
- if (this._config.accordion) {
315
- const openMenuList = (_a = this._element.parentElement) === null || _a === void 0 ? void 0 : _a.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`);
316
- openMenuList === null || openMenuList === void 0 ? void 0 : openMenuList.forEach(openMenu => {
317
- if (openMenu !== this._element.parentElement) {
318
- openMenu.classList.remove(CLASS_NAME_MENU_OPEN);
319
- const childElement = openMenu === null || openMenu === void 0 ? void 0 : openMenu.querySelector(SELECTOR_TREEVIEW_MENU);
320
- if (childElement) {
321
- slideUp(childElement, this._config.animationSpeed);
322
- }
323
- }
324
- });
325
- }
326
- this._element.classList.add(CLASS_NAME_MENU_OPEN);
327
- const childElement = (_b = this._element) === null || _b === void 0 ? void 0 : _b.querySelector(SELECTOR_TREEVIEW_MENU);
328
- if (childElement) {
329
- slideDown(childElement, this._config.animationSpeed);
330
- }
331
- this._element.dispatchEvent(event);
332
- }
333
- close() {
334
- var _a;
335
- const event = new Event(EVENT_COLLAPSED$2);
336
- this._element.classList.remove(CLASS_NAME_MENU_OPEN);
337
- const childElement = (_a = this._element) === null || _a === void 0 ? void 0 : _a.querySelector(SELECTOR_TREEVIEW_MENU);
338
- if (childElement) {
339
- slideUp(childElement, this._config.animationSpeed);
340
- }
341
- this._element.dispatchEvent(event);
342
- }
343
- toggle() {
344
- if (this._element.classList.contains(CLASS_NAME_MENU_OPEN)) {
345
- this.close();
346
- }
347
- else {
348
- this.open();
349
- }
350
- }
351
- }
352
- /**
353
- * ------------------------------------------------------------------------
354
- * Data Api implementation
355
- * ------------------------------------------------------------------------
356
- */
357
- onDOMContentLoaded(() => {
358
- const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE$1);
359
- button.forEach(btn => {
360
- btn.addEventListener('click', event => {
361
- const target = event.target;
362
- const targetItem = target.closest(SELECTOR_NAV_ITEM);
363
- const targetLink = target.closest(SELECTOR_NAV_LINK);
364
- if ((target === null || target === void 0 ? void 0 : target.getAttribute('href')) === '#' || (targetLink === null || targetLink === void 0 ? void 0 : targetLink.getAttribute('href')) === '#') {
365
- event.preventDefault();
366
- }
367
- if (targetItem) {
368
- const data = new Treeview(targetItem, Default$1);
369
- data.toggle();
370
- }
371
- });
372
- });
373
- });
374
-
375
- /**
376
- * --------------------------------------------
377
- * @file AdminLTE direct-chat.ts
378
- * @description Direct chat for AdminLTE.
379
- * @license MIT
380
- * --------------------------------------------
381
- */
382
- /**
383
- * Constants
384
- * ====================================================
385
- */
386
- const DATA_KEY$2 = 'lte.direct-chat';
387
- const EVENT_KEY$2 = `.${DATA_KEY$2}`;
388
- const EVENT_EXPANDED$1 = `expanded${EVENT_KEY$2}`;
389
- const EVENT_COLLAPSED$1 = `collapsed${EVENT_KEY$2}`;
390
- const SELECTOR_DATA_TOGGLE = '[data-lte-toggle="chat-pane"]';
391
- const SELECTOR_DIRECT_CHAT = '.direct-chat';
392
- const CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open';
393
- /**
394
- * Class Definition
395
- * ====================================================
396
- */
397
- class DirectChat {
398
- constructor(element) {
399
- this._element = element;
400
- }
401
- toggle() {
402
- if (this._element.classList.contains(CLASS_NAME_DIRECT_CHAT_OPEN)) {
403
- const event = new Event(EVENT_COLLAPSED$1);
404
- this._element.classList.remove(CLASS_NAME_DIRECT_CHAT_OPEN);
405
- this._element.dispatchEvent(event);
406
- }
407
- else {
408
- const event = new Event(EVENT_EXPANDED$1);
409
- this._element.classList.add(CLASS_NAME_DIRECT_CHAT_OPEN);
410
- this._element.dispatchEvent(event);
411
- }
412
- }
413
- }
414
- /**
415
- *
416
- * Data Api implementation
417
- * ====================================================
418
- */
419
- onDOMContentLoaded(() => {
420
- const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE);
421
- button.forEach(btn => {
422
- btn.addEventListener('click', event => {
423
- event.preventDefault();
424
- const target = event.target;
425
- const chatPane = target.closest(SELECTOR_DIRECT_CHAT);
426
- if (chatPane) {
427
- const data = new DirectChat(chatPane);
428
- data.toggle();
429
- }
430
- });
431
- });
432
- });
433
-
434
- /**
435
- * --------------------------------------------
436
- * @file AdminLTE card-widget.ts
437
- * @description Card widget for AdminLTE.
438
- * @license MIT
439
- * --------------------------------------------
440
- */
441
- /**
442
- * Constants
443
- * ====================================================
444
- */
445
- const DATA_KEY$1 = 'lte.card-widget';
446
- const EVENT_KEY$1 = `.${DATA_KEY$1}`;
447
- const EVENT_COLLAPSED = `collapsed${EVENT_KEY$1}`;
448
- const EVENT_EXPANDED = `expanded${EVENT_KEY$1}`;
449
- const EVENT_REMOVE = `remove${EVENT_KEY$1}`;
450
- const EVENT_MAXIMIZED$1 = `maximized${EVENT_KEY$1}`;
451
- const EVENT_MINIMIZED$1 = `minimized${EVENT_KEY$1}`;
452
- const CLASS_NAME_CARD = 'card';
453
- const CLASS_NAME_COLLAPSED = 'collapsed-card';
454
- const CLASS_NAME_COLLAPSING = 'collapsing-card';
455
- const CLASS_NAME_EXPANDING = 'expanding-card';
456
- const CLASS_NAME_WAS_COLLAPSED = 'was-collapsed';
457
- const CLASS_NAME_MAXIMIZED = 'maximized-card';
458
- const SELECTOR_DATA_REMOVE = '[data-lte-toggle="card-remove"]';
459
- const SELECTOR_DATA_COLLAPSE = '[data-lte-toggle="card-collapse"]';
460
- const SELECTOR_DATA_MAXIMIZE = '[data-lte-toggle="card-maximize"]';
461
- const SELECTOR_CARD = `.${CLASS_NAME_CARD}`;
462
- const SELECTOR_CARD_BODY = '.card-body';
463
- const SELECTOR_CARD_FOOTER = '.card-footer';
464
- const Default = {
465
- animationSpeed: 500,
466
- collapseTrigger: SELECTOR_DATA_COLLAPSE,
467
- removeTrigger: SELECTOR_DATA_REMOVE,
468
- maximizeTrigger: SELECTOR_DATA_MAXIMIZE
469
- };
470
- class CardWidget {
471
- constructor(element, config) {
472
- this._element = element;
473
- this._parent = element.closest(SELECTOR_CARD);
474
- if (element.classList.contains(CLASS_NAME_CARD)) {
475
- this._parent = element;
476
- }
477
- this._config = Object.assign(Object.assign({}, Default), config);
478
- }
479
- collapse() {
480
- var _a, _b;
481
- const event = new Event(EVENT_COLLAPSED);
482
- if (this._parent) {
483
- this._parent.classList.add(CLASS_NAME_COLLAPSING);
484
- const elm = (_a = this._parent) === null || _a === void 0 ? void 0 : _a.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`);
485
- elm.forEach(el => {
486
- if (el instanceof HTMLElement) {
487
- slideUp(el, this._config.animationSpeed);
488
- }
489
- });
490
- setTimeout(() => {
491
- if (this._parent) {
492
- this._parent.classList.add(CLASS_NAME_COLLAPSED);
493
- this._parent.classList.remove(CLASS_NAME_COLLAPSING);
494
- }
495
- }, this._config.animationSpeed);
496
- }
497
- (_b = this._element) === null || _b === void 0 ? void 0 : _b.dispatchEvent(event);
498
- }
499
- expand() {
500
- var _a, _b;
501
- const event = new Event(EVENT_EXPANDED);
502
- if (this._parent) {
503
- this._parent.classList.add(CLASS_NAME_EXPANDING);
504
- const elm = (_a = this._parent) === null || _a === void 0 ? void 0 : _a.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`);
505
- elm.forEach(el => {
506
- if (el instanceof HTMLElement) {
507
- slideDown(el, this._config.animationSpeed);
508
- }
509
- });
510
- setTimeout(() => {
511
- if (this._parent) {
512
- this._parent.classList.remove(CLASS_NAME_COLLAPSED);
513
- this._parent.classList.remove(CLASS_NAME_EXPANDING);
514
- }
515
- }, this._config.animationSpeed);
516
- }
517
- (_b = this._element) === null || _b === void 0 ? void 0 : _b.dispatchEvent(event);
518
- }
519
- remove() {
520
- var _a;
521
- const event = new Event(EVENT_REMOVE);
522
- if (this._parent) {
523
- slideUp(this._parent, this._config.animationSpeed);
524
- }
525
- (_a = this._element) === null || _a === void 0 ? void 0 : _a.dispatchEvent(event);
526
- }
527
- toggle() {
528
- var _a;
529
- if ((_a = this._parent) === null || _a === void 0 ? void 0 : _a.classList.contains(CLASS_NAME_COLLAPSED)) {
530
- this.expand();
531
- return;
532
- }
533
- this.collapse();
534
- }
535
- maximize() {
536
- var _a;
537
- const event = new Event(EVENT_MAXIMIZED$1);
538
- if (this._parent) {
539
- this._parent.style.height = `${this._parent.offsetHeight}px`;
540
- this._parent.style.width = `${this._parent.offsetWidth}px`;
541
- this._parent.style.transition = 'all .15s';
542
- setTimeout(() => {
543
- const htmlTag = document.querySelector('html');
544
- if (htmlTag) {
545
- htmlTag.classList.add(CLASS_NAME_MAXIMIZED);
546
- }
547
- if (this._parent) {
548
- this._parent.classList.add(CLASS_NAME_MAXIMIZED);
549
- if (this._parent.classList.contains(CLASS_NAME_COLLAPSED)) {
550
- this._parent.classList.add(CLASS_NAME_WAS_COLLAPSED);
551
- }
552
- }
553
- }, 150);
554
- }
555
- (_a = this._element) === null || _a === void 0 ? void 0 : _a.dispatchEvent(event);
556
- }
557
- minimize() {
558
- var _a;
559
- const event = new Event(EVENT_MINIMIZED$1);
560
- if (this._parent) {
561
- this._parent.style.height = 'auto';
562
- this._parent.style.width = 'auto';
563
- this._parent.style.transition = 'all .15s';
564
- setTimeout(() => {
565
- var _a;
566
- const htmlTag = document.querySelector('html');
567
- if (htmlTag) {
568
- htmlTag.classList.remove(CLASS_NAME_MAXIMIZED);
569
- }
570
- if (this._parent) {
571
- this._parent.classList.remove(CLASS_NAME_MAXIMIZED);
572
- if ((_a = this._parent) === null || _a === void 0 ? void 0 : _a.classList.contains(CLASS_NAME_WAS_COLLAPSED)) {
573
- this._parent.classList.remove(CLASS_NAME_WAS_COLLAPSED);
574
- }
575
- }
576
- }, 10);
577
- }
578
- (_a = this._element) === null || _a === void 0 ? void 0 : _a.dispatchEvent(event);
579
- }
580
- toggleMaximize() {
581
- var _a;
582
- if ((_a = this._parent) === null || _a === void 0 ? void 0 : _a.classList.contains(CLASS_NAME_MAXIMIZED)) {
583
- this.minimize();
584
- return;
585
- }
586
- this.maximize();
587
- }
588
- }
589
- /**
590
- *
591
- * Data Api implementation
592
- * ====================================================
593
- */
594
- onDOMContentLoaded(() => {
595
- const collapseBtn = document.querySelectorAll(SELECTOR_DATA_COLLAPSE);
596
- collapseBtn.forEach(btn => {
597
- btn.addEventListener('click', event => {
598
- event.preventDefault();
599
- const target = event.target;
600
- const data = new CardWidget(target, Default);
601
- data.toggle();
602
- });
603
- });
604
- const removeBtn = document.querySelectorAll(SELECTOR_DATA_REMOVE);
605
- removeBtn.forEach(btn => {
606
- btn.addEventListener('click', event => {
607
- event.preventDefault();
608
- const target = event.target;
609
- const data = new CardWidget(target, Default);
610
- data.remove();
611
- });
612
- });
613
- const maxBtn = document.querySelectorAll(SELECTOR_DATA_MAXIMIZE);
614
- maxBtn.forEach(btn => {
615
- btn.addEventListener('click', event => {
616
- event.preventDefault();
617
- const target = event.target;
618
- const data = new CardWidget(target, Default);
619
- data.toggleMaximize();
620
- });
621
- });
622
- });
623
-
624
- /**
625
- * --------------------------------------------
626
- * @file AdminLTE fullscreen.ts
627
- * @description Fullscreen plugin for AdminLTE.
628
- * @license MIT
629
- * --------------------------------------------
630
- */
631
- /**
632
- * Constants
633
- * ============================================================================
634
- */
635
- const DATA_KEY = 'lte.fullscreen';
636
- const EVENT_KEY = `.${DATA_KEY}`;
637
- const EVENT_MAXIMIZED = `maximized${EVENT_KEY}`;
638
- const EVENT_MINIMIZED = `minimized${EVENT_KEY}`;
639
- const SELECTOR_FULLSCREEN_TOGGLE = '[data-lte-toggle="fullscreen"]';
640
- const SELECTOR_MAXIMIZE_ICON = '[data-lte-icon="maximize"]';
641
- const SELECTOR_MINIMIZE_ICON = '[data-lte-icon="minimize"]';
642
- /**
643
- * Class Definition.
644
- * ============================================================================
645
- */
646
- class FullScreen {
647
- constructor(element, config) {
648
- this._element = element;
649
- this._config = config;
650
- }
651
- inFullScreen() {
652
- const event = new Event(EVENT_MAXIMIZED);
653
- const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON);
654
- const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
655
- void document.documentElement.requestFullscreen();
656
- if (iconMaximize) {
657
- iconMaximize.style.display = 'none';
658
- }
659
- if (iconMinimize) {
660
- iconMinimize.style.display = 'block';
661
- }
662
- this._element.dispatchEvent(event);
663
- }
664
- outFullscreen() {
665
- const event = new Event(EVENT_MINIMIZED);
666
- const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON);
667
- const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
668
- void document.exitFullscreen();
669
- if (iconMaximize) {
670
- iconMaximize.style.display = 'block';
671
- }
672
- if (iconMinimize) {
673
- iconMinimize.style.display = 'none';
674
- }
675
- this._element.dispatchEvent(event);
676
- }
677
- toggleFullScreen() {
678
- if (document.fullscreenEnabled) {
679
- if (document.fullscreenElement) {
680
- this.outFullscreen();
681
- }
682
- else {
683
- this.inFullScreen();
684
- }
685
- }
686
- }
687
- }
688
- /**
689
- * Data Api implementation
690
- * ============================================================================
691
- */
692
- onDOMContentLoaded(() => {
693
- const buttons = document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE);
694
- buttons.forEach(btn => {
695
- btn.addEventListener('click', event => {
696
- event.preventDefault();
697
- const target = event.target;
698
- const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE);
699
- if (button) {
700
- const data = new FullScreen(button, undefined);
701
- data.toggleFullScreen();
702
- }
703
- });
704
- });
705
- });
706
-
707
- exports.CardWidget = CardWidget;
708
- exports.DirectChat = DirectChat;
709
- exports.FullScreen = FullScreen;
710
- exports.Layout = Layout;
711
- exports.PushMenu = PushMenu;
712
- exports.Treeview = Treeview;
713
-
714
- }));
715
- //# sourceMappingURL=adminlte.js.map
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/js/adminlte.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"adminlte.js","sources":["../../src/ts/util/index.ts","../../src/ts/layout.ts","../../src/ts/push-menu.ts","../../src/ts/treeview.ts","../../src/ts/direct-chat.ts","../../src/ts/card-widget.ts","../../src/ts/fullscreen.ts"],"sourcesContent":["const domContentLoadedCallbacks: Array<() => void> = []\n\nconst onDOMContentLoaded = (callback: () => void): void => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!domContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of domContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n domContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\n/* SLIDE UP */\nconst slideUp = (target: HTMLElement, duration = 500) => {\n target.style.transitionProperty = 'height, margin, padding'\n target.style.transitionDuration = `${duration}ms`\n target.style.boxSizing = 'border-box'\n target.style.height = `${target.offsetHeight}px`\n target.style.overflow = 'hidden'\n\n window.setTimeout(() => {\n target.style.height = '0'\n target.style.paddingTop = '0'\n target.style.paddingBottom = '0'\n target.style.marginTop = '0'\n target.style.marginBottom = '0'\n }, 1)\n\n window.setTimeout(() => {\n target.style.display = 'none'\n target.style.removeProperty('height')\n target.style.removeProperty('padding-top')\n target.style.removeProperty('padding-bottom')\n target.style.removeProperty('margin-top')\n target.style.removeProperty('margin-bottom')\n target.style.removeProperty('overflow')\n target.style.removeProperty('transition-duration')\n target.style.removeProperty('transition-property')\n }, duration)\n}\n\n/* SLIDE DOWN */\nconst slideDown = (target: HTMLElement, duration = 500) => {\n target.style.removeProperty('display')\n let { display } = window.getComputedStyle(target)\n\n if (display === 'none') {\n display = 'block'\n }\n\n target.style.display = display\n const height = target.offsetHeight\n target.style.overflow = 'hidden'\n target.style.height = '0'\n target.style.paddingTop = '0'\n target.style.paddingBottom = '0'\n target.style.marginTop = '0'\n target.style.marginBottom = '0'\n\n window.setTimeout(() => {\n target.style.boxSizing = 'border-box'\n target.style.transitionProperty = 'height, margin, padding'\n target.style.transitionDuration = `${duration}ms`\n target.style.height = `${height}px`\n target.style.removeProperty('padding-top')\n target.style.removeProperty('padding-bottom')\n target.style.removeProperty('margin-top')\n target.style.removeProperty('margin-bottom')\n }, 1)\n\n window.setTimeout(() => {\n target.style.removeProperty('height')\n target.style.removeProperty('overflow')\n target.style.removeProperty('transition-duration')\n target.style.removeProperty('transition-property')\n }, duration)\n}\n\n/* TOGGLE */\nconst slideToggle = (target: HTMLElement, duration = 500) => {\n if (window.getComputedStyle(target).display === 'none') {\n slideDown(target, duration)\n return\n }\n\n slideUp(target, duration)\n}\n\nexport {\n onDOMContentLoaded,\n slideUp,\n slideDown,\n slideToggle\n}\n","/**\n * --------------------------------------------\n * @file AdminLTE layout.ts\n * @description Layout for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded\n} from './util/index'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst CLASS_NAME_HOLD_TRANSITIONS = 'hold-transition'\nconst CLASS_NAME_APP_LOADED = 'app-loaded'\n\n/**\n * Class Definition\n * ====================================================\n */\n\nclass Layout {\n _element: HTMLElement\n\n constructor(element: HTMLElement) {\n this._element = element\n }\n\n holdTransition(): void {\n let resizeTimer: ReturnType<typeof setTimeout>\n window.addEventListener('resize', () => {\n document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS)\n clearTimeout(resizeTimer)\n resizeTimer = setTimeout(() => {\n document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS)\n }, 400)\n })\n }\n}\n\nonDOMContentLoaded(() => {\n const data = new Layout(document.body)\n data.holdTransition()\n setTimeout(() => {\n document.body.classList.add(CLASS_NAME_APP_LOADED)\n }, 400)\n})\n\nexport default Layout\n","/**\n * --------------------------------------------\n * @file AdminLTE push-menu.ts\n * @description Push menu for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded\n} from './util/index'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst DATA_KEY = 'lte.push-menu'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_OPEN = `open${EVENT_KEY}`\nconst EVENT_COLLAPSE = `collapse${EVENT_KEY}`\n\nconst CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini'\nconst CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse'\nconst CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open'\nconst CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand'\nconst CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay'\nconst CLASS_NAME_MENU_OPEN = 'menu-open'\n\nconst SELECTOR_APP_SIDEBAR = '.app-sidebar'\nconst SELECTOR_SIDEBAR_MENU = '.sidebar-menu'\nconst SELECTOR_NAV_ITEM = '.nav-item'\nconst SELECTOR_NAV_TREEVIEW = '.nav-treeview'\nconst SELECTOR_APP_WRAPPER = '.app-wrapper'\nconst SELECTOR_SIDEBAR_EXPAND = `[class*=\"${CLASS_NAME_SIDEBAR_EXPAND}\"]`\nconst SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle=\"sidebar\"]'\n\ntype Config = {\n sidebarBreakpoint: number;\n}\n\nconst Defaults = {\n sidebarBreakpoint: 992\n}\n\n/**\n * Class Definition\n * ====================================================\n */\n\nclass PushMenu {\n _element: HTMLElement\n _config: Config\n\n constructor(element: HTMLElement, config: Config) {\n this._element = element\n this._config = { ...Defaults, ...config }\n }\n\n // TODO\n menusClose() {\n const navTreeview = document.querySelectorAll<HTMLElement>(SELECTOR_NAV_TREEVIEW)\n\n navTreeview.forEach(navTree => {\n navTree.style.removeProperty('display')\n navTree.style.removeProperty('height')\n })\n\n const navSidebar = document.querySelector(SELECTOR_SIDEBAR_MENU)\n const navItem = navSidebar?.querySelectorAll(SELECTOR_NAV_ITEM)\n\n if (navItem) {\n navItem.forEach(navI => {\n navI.classList.remove(CLASS_NAME_MENU_OPEN)\n })\n }\n }\n\n expand() {\n const event = new Event(EVENT_OPEN)\n\n document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE)\n document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN)\n\n this._element.dispatchEvent(event)\n }\n\n collapse() {\n const event = new Event(EVENT_COLLAPSE)\n\n document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN)\n document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE)\n\n this._element.dispatchEvent(event)\n }\n\n addSidebarBreakPoint() {\n const sidebarExpandList = document.querySelector(SELECTOR_SIDEBAR_EXPAND)?.classList ?? []\n const sidebarExpand = Array.from(sidebarExpandList).find(className => className.startsWith(CLASS_NAME_SIDEBAR_EXPAND)) ?? ''\n const sidebar = document.getElementsByClassName(sidebarExpand)[0]\n const sidebarContent = window.getComputedStyle(sidebar, '::before').getPropertyValue('content')\n this._config = { ...this._config, sidebarBreakpoint: Number(sidebarContent.replace(/[^\\d.-]/g, '')) }\n\n if (window.innerWidth <= this._config.sidebarBreakpoint) {\n this.collapse()\n } else {\n if (!document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI)) {\n this.expand()\n }\n\n if (document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI) && document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {\n this.collapse()\n }\n }\n }\n\n toggle() {\n if (document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {\n this.expand()\n } else {\n this.collapse()\n }\n }\n\n init() {\n this.addSidebarBreakPoint()\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nonDOMContentLoaded(() => {\n const sidebar = document?.querySelector(SELECTOR_APP_SIDEBAR) as HTMLElement | undefined\n\n if (sidebar) {\n const data = new PushMenu(sidebar, Defaults)\n data.init()\n\n window.addEventListener('resize', () => {\n data.init()\n })\n }\n\n const sidebarOverlay = document.createElement('div')\n sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY\n document.querySelector(SELECTOR_APP_WRAPPER)?.append(sidebarOverlay)\n\n sidebarOverlay.addEventListener('touchstart', event => {\n event.preventDefault()\n const target = event.currentTarget as HTMLElement\n const data = new PushMenu(target, Defaults)\n data.collapse()\n }, { passive: true })\n sidebarOverlay.addEventListener('click', event => {\n event.preventDefault()\n const target = event.currentTarget as HTMLElement\n const data = new PushMenu(target, Defaults)\n data.collapse()\n })\n\n const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE)\n\n fullBtn.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n\n let button = event.currentTarget as HTMLElement | undefined\n\n if (button?.dataset.lteToggle !== 'sidebar') {\n button = button?.closest(SELECTOR_SIDEBAR_TOGGLE) as HTMLElement | undefined\n }\n\n if (button) {\n event?.preventDefault()\n const data = new PushMenu(button, Defaults)\n data.toggle()\n }\n })\n })\n})\n\nexport default PushMenu\n","/**\n * --------------------------------------------\n * @file AdminLTE treeview.ts\n * @description Treeview plugin for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded,\n slideDown,\n slideUp\n} from './util/index'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\n// const NAME = 'Treeview'\nconst DATA_KEY = 'lte.treeview'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_EXPANDED = `expanded${EVENT_KEY}`\nconst EVENT_COLLAPSED = `collapsed${EVENT_KEY}`\n// const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst CLASS_NAME_MENU_OPEN = 'menu-open'\nconst SELECTOR_NAV_ITEM = '.nav-item'\nconst SELECTOR_NAV_LINK = '.nav-link'\nconst SELECTOR_TREEVIEW_MENU = '.nav-treeview'\nconst SELECTOR_DATA_TOGGLE = '[data-lte-toggle=\"treeview\"]'\n\nconst Default = {\n animationSpeed: 300,\n accordion: true\n}\n\ntype Config = {\n animationSpeed: number;\n accordion: boolean;\n}\n\n/**\n * Class Definition\n * ====================================================\n */\n\nclass Treeview {\n _element: HTMLElement\n _config: Config\n\n constructor(element: HTMLElement, config: Config) {\n this._element = element\n this._config = { ...Default, ...config }\n }\n\n open(): void {\n const event = new Event(EVENT_EXPANDED)\n\n if (this._config.accordion) {\n const openMenuList = this._element.parentElement?.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`)\n\n openMenuList?.forEach(openMenu => {\n if (openMenu !== this._element.parentElement) {\n openMenu.classList.remove(CLASS_NAME_MENU_OPEN)\n const childElement = openMenu?.querySelector(SELECTOR_TREEVIEW_MENU) as HTMLElement | undefined\n if (childElement) {\n slideUp(childElement, this._config.animationSpeed)\n }\n }\n })\n }\n\n this._element.classList.add(CLASS_NAME_MENU_OPEN)\n\n const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU) as HTMLElement | undefined\n if (childElement) {\n slideDown(childElement, this._config.animationSpeed)\n }\n\n this._element.dispatchEvent(event)\n }\n\n close(): void {\n const event = new Event(EVENT_COLLAPSED)\n\n this._element.classList.remove(CLASS_NAME_MENU_OPEN)\n\n const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU) as HTMLElement | undefined\n if (childElement) {\n slideUp(childElement, this._config.animationSpeed)\n }\n\n this._element.dispatchEvent(event)\n }\n\n toggle(): void {\n if (this._element.classList.contains(CLASS_NAME_MENU_OPEN)) {\n this.close()\n } else {\n this.open()\n }\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nonDOMContentLoaded(() => {\n const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE)\n\n button.forEach(btn => {\n btn.addEventListener('click', event => {\n const target = event.target as HTMLElement\n const targetItem = target.closest(SELECTOR_NAV_ITEM) as HTMLElement | undefined\n const targetLink = target.closest(SELECTOR_NAV_LINK) as HTMLAnchorElement | undefined\n\n if (target?.getAttribute('href') === '#' || targetLink?.getAttribute('href') === '#') {\n event.preventDefault()\n }\n\n if (targetItem) {\n const data = new Treeview(targetItem, Default)\n data.toggle()\n }\n })\n })\n})\n\nexport default Treeview\n","/**\n * --------------------------------------------\n * @file AdminLTE direct-chat.ts\n * @description Direct chat for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded\n} from './util/index'\n\n/**\n * Constants\n * ====================================================\n */\n\nconst DATA_KEY = 'lte.direct-chat'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_EXPANDED = `expanded${EVENT_KEY}`\nconst EVENT_COLLAPSED = `collapsed${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-lte-toggle=\"chat-pane\"]'\nconst SELECTOR_DIRECT_CHAT = '.direct-chat'\n\nconst CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open'\n\n/**\n * Class Definition\n * ====================================================\n */\n\nclass DirectChat {\n _element: HTMLElement\n constructor(element: HTMLElement) {\n this._element = element\n }\n\n toggle(): void {\n if (this._element.classList.contains(CLASS_NAME_DIRECT_CHAT_OPEN)) {\n const event = new Event(EVENT_COLLAPSED)\n\n this._element.classList.remove(CLASS_NAME_DIRECT_CHAT_OPEN)\n\n this._element.dispatchEvent(event)\n } else {\n const event = new Event(EVENT_EXPANDED)\n\n this._element.classList.add(CLASS_NAME_DIRECT_CHAT_OPEN)\n\n this._element.dispatchEvent(event)\n }\n }\n}\n\n/**\n *\n * Data Api implementation\n * ====================================================\n */\n\nonDOMContentLoaded(() => {\n const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE)\n\n button.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n const target = event.target as HTMLElement\n const chatPane = target.closest(SELECTOR_DIRECT_CHAT) as HTMLElement | undefined\n\n if (chatPane) {\n const data = new DirectChat(chatPane)\n data.toggle()\n }\n })\n })\n})\n\nexport default DirectChat\n","/**\n * --------------------------------------------\n * @file AdminLTE card-widget.ts\n * @description Card widget for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded,\n slideUp,\n slideDown\n} from './util/index'\n\n/**\n * Constants\n * ====================================================\n */\n\nconst DATA_KEY = 'lte.card-widget'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_COLLAPSED = `collapsed${EVENT_KEY}`\nconst EVENT_EXPANDED = `expanded${EVENT_KEY}`\nconst EVENT_REMOVE = `remove${EVENT_KEY}`\nconst EVENT_MAXIMIZED = `maximized${EVENT_KEY}`\nconst EVENT_MINIMIZED = `minimized${EVENT_KEY}`\n\nconst CLASS_NAME_CARD = 'card'\nconst CLASS_NAME_COLLAPSED = 'collapsed-card'\nconst CLASS_NAME_COLLAPSING = 'collapsing-card'\nconst CLASS_NAME_EXPANDING = 'expanding-card'\nconst CLASS_NAME_WAS_COLLAPSED = 'was-collapsed'\nconst CLASS_NAME_MAXIMIZED = 'maximized-card'\n\nconst SELECTOR_DATA_REMOVE = '[data-lte-toggle=\"card-remove\"]'\nconst SELECTOR_DATA_COLLAPSE = '[data-lte-toggle=\"card-collapse\"]'\nconst SELECTOR_DATA_MAXIMIZE = '[data-lte-toggle=\"card-maximize\"]'\nconst SELECTOR_CARD = `.${CLASS_NAME_CARD}`\nconst SELECTOR_CARD_BODY = '.card-body'\nconst SELECTOR_CARD_FOOTER = '.card-footer'\n\ntype Config = {\n animationSpeed: number;\n collapseTrigger: string;\n removeTrigger: string;\n maximizeTrigger: string;\n}\n\nconst Default: Config = {\n animationSpeed: 500,\n collapseTrigger: SELECTOR_DATA_COLLAPSE,\n removeTrigger: SELECTOR_DATA_REMOVE,\n maximizeTrigger: SELECTOR_DATA_MAXIMIZE\n}\n\nclass CardWidget {\n _element: HTMLElement\n _parent: HTMLElement | undefined\n _clone: HTMLElement | undefined\n _config: Config\n\n constructor(element: HTMLElement, config: Config) {\n this._element = element\n this._parent = element.closest(SELECTOR_CARD) as HTMLElement | undefined\n\n if (element.classList.contains(CLASS_NAME_CARD)) {\n this._parent = element\n }\n\n this._config = { ...Default, ...config }\n }\n\n collapse() {\n const event = new Event(EVENT_COLLAPSED)\n\n if (this._parent) {\n this._parent.classList.add(CLASS_NAME_COLLAPSING)\n\n const elm = this._parent?.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`)\n\n elm.forEach(el => {\n if (el instanceof HTMLElement) {\n slideUp(el, this._config.animationSpeed)\n }\n })\n\n setTimeout(() => {\n if (this._parent) {\n this._parent.classList.add(CLASS_NAME_COLLAPSED)\n this._parent.classList.remove(CLASS_NAME_COLLAPSING)\n }\n }, this._config.animationSpeed)\n }\n\n this._element?.dispatchEvent(event)\n }\n\n expand() {\n const event = new Event(EVENT_EXPANDED)\n\n if (this._parent) {\n this._parent.classList.add(CLASS_NAME_EXPANDING)\n\n const elm = this._parent?.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`)\n\n elm.forEach(el => {\n if (el instanceof HTMLElement) {\n slideDown(el, this._config.animationSpeed)\n }\n })\n\n setTimeout(() => {\n if (this._parent) {\n this._parent.classList.remove(CLASS_NAME_COLLAPSED)\n this._parent.classList.remove(CLASS_NAME_EXPANDING)\n }\n }, this._config.animationSpeed)\n }\n\n this._element?.dispatchEvent(event)\n }\n\n remove() {\n const event = new Event(EVENT_REMOVE)\n\n if (this._parent) {\n slideUp(this._parent, this._config.animationSpeed)\n }\n\n this._element?.dispatchEvent(event)\n }\n\n toggle() {\n if (this._parent?.classList.contains(CLASS_NAME_COLLAPSED)) {\n this.expand()\n return\n }\n\n this.collapse()\n }\n\n maximize() {\n const event = new Event(EVENT_MAXIMIZED)\n\n if (this._parent) {\n this._parent.style.height = `${this._parent.offsetHeight}px`\n this._parent.style.width = `${this._parent.offsetWidth}px`\n this._parent.style.transition = 'all .15s'\n\n setTimeout(() => {\n const htmlTag = document.querySelector('html')\n\n if (htmlTag) {\n htmlTag.classList.add(CLASS_NAME_MAXIMIZED)\n }\n\n if (this._parent) {\n this._parent.classList.add(CLASS_NAME_MAXIMIZED)\n\n if (this._parent.classList.contains(CLASS_NAME_COLLAPSED)) {\n this._parent.classList.add(CLASS_NAME_WAS_COLLAPSED)\n }\n }\n }, 150)\n }\n\n this._element?.dispatchEvent(event)\n }\n\n minimize() {\n const event = new Event(EVENT_MINIMIZED)\n\n if (this._parent) {\n this._parent.style.height = 'auto'\n this._parent.style.width = 'auto'\n this._parent.style.transition = 'all .15s'\n\n setTimeout(() => {\n const htmlTag = document.querySelector('html')\n\n if (htmlTag) {\n htmlTag.classList.remove(CLASS_NAME_MAXIMIZED)\n }\n\n if (this._parent) {\n this._parent.classList.remove(CLASS_NAME_MAXIMIZED)\n\n if (this._parent?.classList.contains(CLASS_NAME_WAS_COLLAPSED)) {\n this._parent.classList.remove(CLASS_NAME_WAS_COLLAPSED)\n }\n }\n }, 10)\n }\n\n this._element?.dispatchEvent(event)\n }\n\n toggleMaximize() {\n if (this._parent?.classList.contains(CLASS_NAME_MAXIMIZED)) {\n this.minimize()\n return\n }\n\n this.maximize()\n }\n}\n\n/**\n *\n * Data Api implementation\n * ====================================================\n */\n\nonDOMContentLoaded(() => {\n const collapseBtn = document.querySelectorAll(SELECTOR_DATA_COLLAPSE)\n\n collapseBtn.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n const target = event.target as HTMLElement\n const data = new CardWidget(target, Default)\n data.toggle()\n })\n })\n\n const removeBtn = document.querySelectorAll(SELECTOR_DATA_REMOVE)\n\n removeBtn.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n const target = event.target as HTMLElement\n const data = new CardWidget(target, Default)\n data.remove()\n })\n })\n\n const maxBtn = document.querySelectorAll(SELECTOR_DATA_MAXIMIZE)\n\n maxBtn.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n const target = event.target as HTMLElement\n const data = new CardWidget(target, Default)\n data.toggleMaximize()\n })\n })\n})\n\nexport default CardWidget\n","/**\n * --------------------------------------------\n * @file AdminLTE fullscreen.ts\n * @description Fullscreen plugin for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded\n} from './util/index'\n\n/**\n * Constants\n * ============================================================================\n */\nconst DATA_KEY = 'lte.fullscreen'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_MAXIMIZED = `maximized${EVENT_KEY}`\nconst EVENT_MINIMIZED = `minimized${EVENT_KEY}`\n\nconst SELECTOR_FULLSCREEN_TOGGLE = '[data-lte-toggle=\"fullscreen\"]'\nconst SELECTOR_MAXIMIZE_ICON = '[data-lte-icon=\"maximize\"]'\nconst SELECTOR_MINIMIZE_ICON = '[data-lte-icon=\"minimize\"]'\n\n/**\n * Class Definition.\n * ============================================================================\n */\nclass FullScreen {\n _element: HTMLElement\n _config: undefined\n\n constructor(element: HTMLElement, config?: undefined) {\n this._element = element\n this._config = config\n }\n\n inFullScreen(): void {\n const event = new Event(EVENT_MAXIMIZED)\n\n const iconMaximize = document.querySelector<HTMLElement>(SELECTOR_MAXIMIZE_ICON)\n const iconMinimize = document.querySelector<HTMLElement>(SELECTOR_MINIMIZE_ICON)\n\n void document.documentElement.requestFullscreen()\n\n if (iconMaximize) {\n iconMaximize.style.display = 'none'\n }\n\n if (iconMinimize) {\n iconMinimize.style.display = 'block'\n }\n\n this._element.dispatchEvent(event)\n }\n\n outFullscreen(): void {\n const event = new Event(EVENT_MINIMIZED)\n\n const iconMaximize = document.querySelector<HTMLElement>(SELECTOR_MAXIMIZE_ICON)\n const iconMinimize = document.querySelector<HTMLElement>(SELECTOR_MINIMIZE_ICON)\n\n void document.exitFullscreen()\n\n if (iconMaximize) {\n iconMaximize.style.display = 'block'\n }\n\n if (iconMinimize) {\n iconMinimize.style.display = 'none'\n }\n\n this._element.dispatchEvent(event)\n }\n\n toggleFullScreen(): void {\n if (document.fullscreenEnabled) {\n if (document.fullscreenElement) {\n this.outFullscreen()\n } else {\n this.inFullScreen()\n }\n }\n }\n}\n\n/**\n * Data Api implementation\n * ============================================================================\n */\nonDOMContentLoaded(() => {\n const buttons = document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE)\n\n buttons.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n\n const target = event.target as HTMLElement\n const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE) as HTMLElement | undefined\n\n if (button) {\n const data = new FullScreen(button, undefined)\n data.toggleFullScreen()\n }\n })\n })\n})\n\nexport default FullScreen\n"],"names":["DATA_KEY","EVENT_KEY","CLASS_NAME_MENU_OPEN","SELECTOR_NAV_ITEM","EVENT_EXPANDED","EVENT_COLLAPSED","SELECTOR_DATA_TOGGLE","Default","EVENT_MAXIMIZED","EVENT_MINIMIZED"],"mappings":";;;;;;;;;;;IAAA,MAAM,yBAAyB,GAAsB,EAAE,CAAA;IAEvD,MAAM,kBAAkB,GAAG,CAAC,QAAoB,KAAU;IACxD,IAAA,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE;;IAErC,QAAA,IAAI,CAAC,yBAAyB,CAAC,MAAM,EAAE;IACrC,YAAA,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,MAAK;IACjD,gBAAA,KAAK,MAAM,QAAQ,IAAI,yBAAyB,EAAE;IAChD,oBAAA,QAAQ,EAAE,CAAA;qBACX;IACH,aAAC,CAAC,CAAA;aACH;IAED,QAAA,yBAAyB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;SACzC;aAAM;IACL,QAAA,QAAQ,EAAE,CAAA;SACX;IACH,CAAC,CAAA;IAED;IACA,MAAM,OAAO,GAAG,CAAC,MAAmB,EAAE,QAAQ,GAAG,GAAG,KAAI;IACtD,IAAA,MAAM,CAAC,KAAK,CAAC,kBAAkB,GAAG,yBAAyB,CAAA;QAC3D,MAAM,CAAC,KAAK,CAAC,kBAAkB,GAAG,CAAG,EAAA,QAAQ,IAAI,CAAA;IACjD,IAAA,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,YAAY,CAAA;QACrC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAA,EAAA,CAAI,CAAA;IAChD,IAAA,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAEhC,IAAA,MAAM,CAAC,UAAU,CAAC,MAAK;IACrB,QAAA,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAA;IACzB,QAAA,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,CAAA;IAC7B,QAAA,MAAM,CAAC,KAAK,CAAC,aAAa,GAAG,GAAG,CAAA;IAChC,QAAA,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,CAAA;IAC5B,QAAA,MAAM,CAAC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAA;SAChC,EAAE,CAAC,CAAC,CAAA;IAEL,IAAA,MAAM,CAAC,UAAU,CAAC,MAAK;IACrB,QAAA,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAA;IAC7B,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAA;IACrC,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC,CAAA;IAC1C,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAA;IAC7C,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,YAAY,CAAC,CAAA;IACzC,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,CAAA;IAC5C,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,CAAA;IACvC,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,qBAAqB,CAAC,CAAA;IAClD,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,qBAAqB,CAAC,CAAA;SACnD,EAAE,QAAQ,CAAC,CAAA;IACd,CAAC,CAAA;IAED;IACA,MAAM,SAAS,GAAG,CAAC,MAAmB,EAAE,QAAQ,GAAG,GAAG,KAAI;IACxD,IAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,CAAA;QACtC,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAA;IAEjD,IAAA,IAAI,OAAO,KAAK,MAAM,EAAE;YACtB,OAAO,GAAG,OAAO,CAAA;SAClB;IAED,IAAA,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAA;IAC9B,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAA;IAClC,IAAA,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAChC,IAAA,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAA;IACzB,IAAA,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,CAAA;IAC7B,IAAA,MAAM,CAAC,KAAK,CAAC,aAAa,GAAG,GAAG,CAAA;IAChC,IAAA,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,CAAA;IAC5B,IAAA,MAAM,CAAC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAA;IAE/B,IAAA,MAAM,CAAC,UAAU,CAAC,MAAK;IACrB,QAAA,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,YAAY,CAAA;IACrC,QAAA,MAAM,CAAC,KAAK,CAAC,kBAAkB,GAAG,yBAAyB,CAAA;YAC3D,MAAM,CAAC,KAAK,CAAC,kBAAkB,GAAG,CAAG,EAAA,QAAQ,IAAI,CAAA;YACjD,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAG,EAAA,MAAM,IAAI,CAAA;IACnC,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC,CAAA;IAC1C,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAA;IAC7C,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,YAAY,CAAC,CAAA;IACzC,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,CAAA;SAC7C,EAAE,CAAC,CAAC,CAAA;IAEL,IAAA,MAAM,CAAC,UAAU,CAAC,MAAK;IACrB,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAA;IACrC,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,CAAA;IACvC,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,qBAAqB,CAAC,CAAA;IAClD,QAAA,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,qBAAqB,CAAC,CAAA;SACnD,EAAE,QAAQ,CAAC,CAAA;IACd,CAAC;;ICnFD;;;;;;IAMG;IAMH;;;;IAIG;IAEH,MAAM,2BAA2B,GAAG,iBAAiB,CAAA;IACrD,MAAM,qBAAqB,GAAG,YAAY,CAAA;IAE1C;;;IAGG;IAEH,MAAM,MAAM,CAAA;IAGV,IAAA,WAAA,CAAY,OAAoB,EAAA;IAC9B,QAAA,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;SACxB;QAED,cAAc,GAAA;IACZ,QAAA,IAAI,WAA0C,CAAA;IAC9C,QAAA,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAK;gBACrC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAA;gBACxD,YAAY,CAAC,WAAW,CAAC,CAAA;IACzB,YAAA,WAAW,GAAG,UAAU,CAAC,MAAK;oBAC5B,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAA;iBAC5D,EAAE,GAAG,CAAC,CAAA;IACT,SAAC,CAAC,CAAA;SACH;IACF,CAAA;IAED,kBAAkB,CAAC,MAAK;QACtB,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QACtC,IAAI,CAAC,cAAc,EAAE,CAAA;QACrB,UAAU,CAAC,MAAK;YACd,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA;SACnD,EAAE,GAAG,CAAC,CAAA;IACT,CAAC,CAAC;;ICnDF;;;;;;IAMG;IAMH;;;;IAIG;IAEH,MAAMA,UAAQ,GAAG,eAAe,CAAA;IAChC,MAAMC,WAAS,GAAG,CAAI,CAAA,EAAAD,UAAQ,EAAE,CAAA;IAEhC,MAAM,UAAU,GAAG,CAAO,IAAA,EAAAC,WAAS,EAAE,CAAA;IACrC,MAAM,cAAc,GAAG,CAAW,QAAA,EAAAA,WAAS,EAAE,CAAA;IAE7C,MAAM,uBAAuB,GAAG,cAAc,CAAA;IAC9C,MAAM,2BAA2B,GAAG,kBAAkB,CAAA;IACtD,MAAM,uBAAuB,GAAG,cAAc,CAAA;IAC9C,MAAM,yBAAyB,GAAG,gBAAgB,CAAA;IAClD,MAAM,0BAA0B,GAAG,iBAAiB,CAAA;IACpD,MAAMC,sBAAoB,GAAG,WAAW,CAAA;IAExC,MAAM,oBAAoB,GAAG,cAAc,CAAA;IAC3C,MAAM,qBAAqB,GAAG,eAAe,CAAA;IAC7C,MAAMC,mBAAiB,GAAG,WAAW,CAAA;IACrC,MAAM,qBAAqB,GAAG,eAAe,CAAA;IAC7C,MAAM,oBAAoB,GAAG,cAAc,CAAA;IAC3C,MAAM,uBAAuB,GAAG,CAAY,SAAA,EAAA,yBAAyB,IAAI,CAAA;IACzE,MAAM,uBAAuB,GAAG,6BAA6B,CAAA;IAM7D,MAAM,QAAQ,GAAG;IACf,IAAA,iBAAiB,EAAE,GAAG;KACvB,CAAA;IAED;;;IAGG;IAEH,MAAM,QAAQ,CAAA;QAIZ,WAAY,CAAA,OAAoB,EAAE,MAAc,EAAA;IAC9C,QAAA,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACvB,QAAA,IAAI,CAAC,OAAO,GAAA,MAAA,CAAA,MAAA,CAAA,MAAA,CAAA,MAAA,CAAA,EAAA,EAAQ,QAAQ,CAAK,EAAA,MAAM,CAAE,CAAA;SAC1C;;QAGD,UAAU,GAAA;YACR,MAAM,WAAW,GAAG,QAAQ,CAAC,gBAAgB,CAAc,qBAAqB,CAAC,CAAA;IAEjF,QAAA,WAAW,CAAC,OAAO,CAAC,OAAO,IAAG;IAC5B,YAAA,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,CAAA;IACvC,YAAA,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAA;IACxC,SAAC,CAAC,CAAA;YAEF,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAA;IAChE,QAAA,MAAM,OAAO,GAAG,UAAU,KAAA,IAAA,IAAV,UAAU,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAV,UAAU,CAAE,gBAAgB,CAACA,mBAAiB,CAAC,CAAA;YAE/D,IAAI,OAAO,EAAE;IACX,YAAA,OAAO,CAAC,OAAO,CAAC,IAAI,IAAG;IACrB,gBAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAACD,sBAAoB,CAAC,CAAA;IAC7C,aAAC,CAAC,CAAA;aACH;SACF;QAED,MAAM,GAAA;IACJ,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,UAAU,CAAC,CAAA;YAEnC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAA;YAC3D,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAA;IAEpD,QAAA,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;SACnC;QAED,QAAQ,GAAA;IACN,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,cAAc,CAAC,CAAA;YAEvC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAA;YACvD,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAA;IAExD,QAAA,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;SACnC;QAED,oBAAoB,GAAA;;IAClB,QAAA,MAAM,iBAAiB,GAAG,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,SAAS,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,EAAA,GAAI,EAAE,CAAA;YAC1F,MAAM,aAAa,GAAG,CAAA,EAAA,GAAA,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAC,MAAI,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,EAAA,GAAA,EAAE,CAAA;YAC5H,MAAM,OAAO,GAAG,QAAQ,CAAC,sBAAsB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IACjE,QAAA,MAAM,cAAc,GAAG,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAA;YAC/F,IAAI,CAAC,OAAO,GAAQ,MAAA,CAAA,MAAA,CAAA,MAAA,CAAA,MAAA,CAAA,EAAA,EAAA,IAAI,CAAC,OAAO,CAAA,EAAA,EAAE,iBAAiB,EAAE,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAA,CAAE,CAAA;YAErG,IAAI,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE;gBACvD,IAAI,CAAC,QAAQ,EAAE,CAAA;aAChB;iBAAM;IACL,YAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE;oBAC9D,IAAI,CAAC,MAAM,EAAE,CAAA;iBACd;gBAED,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,uBAAuB,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,2BAA2B,CAAC,EAAE;oBAC9H,IAAI,CAAC,QAAQ,EAAE,CAAA;iBAChB;aACF;SACF;QAED,MAAM,GAAA;YACJ,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,2BAA2B,CAAC,EAAE;gBACjE,IAAI,CAAC,MAAM,EAAE,CAAA;aACd;iBAAM;gBACL,IAAI,CAAC,QAAQ,EAAE,CAAA;aAChB;SACF;QAED,IAAI,GAAA;YACF,IAAI,CAAC,oBAAoB,EAAE,CAAA;SAC5B;IACF,CAAA;IAED;;;;IAIG;IAEH,kBAAkB,CAAC,MAAK;;IACtB,IAAA,MAAM,OAAO,GAAG,QAAQ,KAAA,IAAA,IAAR,QAAQ,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAR,QAAQ,CAAE,aAAa,CAAC,oBAAoB,CAA4B,CAAA;QAExF,IAAI,OAAO,EAAE;YACX,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;YAC5C,IAAI,CAAC,IAAI,EAAE,CAAA;IAEX,QAAA,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAK;gBACrC,IAAI,CAAC,IAAI,EAAE,CAAA;IACb,SAAC,CAAC,CAAA;SACH;QAED,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IACpD,IAAA,cAAc,CAAC,SAAS,GAAG,0BAA0B,CAAA;QACrD,CAAA,EAAA,GAAA,QAAQ,CAAC,aAAa,CAAC,oBAAoB,CAAC,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,MAAM,CAAC,cAAc,CAAC,CAAA;IAEpE,IAAA,cAAc,CAAC,gBAAgB,CAAC,YAAY,EAAE,KAAK,IAAG;YACpD,KAAK,CAAC,cAAc,EAAE,CAAA;IACtB,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,aAA4B,CAAA;YACjD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;YAC3C,IAAI,CAAC,QAAQ,EAAE,CAAA;IACjB,KAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IACrB,IAAA,cAAc,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,IAAG;YAC/C,KAAK,CAAC,cAAc,EAAE,CAAA;IACtB,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,aAA4B,CAAA;YACjD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;YAC3C,IAAI,CAAC,QAAQ,EAAE,CAAA;IACjB,KAAC,CAAC,CAAA;QAEF,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,uBAAuB,CAAC,CAAA;IAElE,IAAA,OAAO,CAAC,OAAO,CAAC,GAAG,IAAG;IACpB,QAAA,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,IAAG;gBACpC,KAAK,CAAC,cAAc,EAAE,CAAA;IAEtB,YAAA,IAAI,MAAM,GAAG,KAAK,CAAC,aAAwC,CAAA;IAE3D,YAAA,IAAI,CAAA,MAAM,KAAN,IAAA,IAAA,MAAM,KAAN,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,MAAM,CAAE,OAAO,CAAC,SAAS,MAAK,SAAS,EAAE;oBAC3C,MAAM,GAAG,MAAM,KAAA,IAAA,IAAN,MAAM,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAN,MAAM,CAAE,OAAO,CAAC,uBAAuB,CAA4B,CAAA;iBAC7E;gBAED,IAAI,MAAM,EAAE;IACV,gBAAA,KAAK,aAAL,KAAK,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAL,KAAK,CAAE,cAAc,EAAE,CAAA;oBACvB,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;oBAC3C,IAAI,CAAC,MAAM,EAAE,CAAA;iBACd;IACH,SAAC,CAAC,CAAA;IACJ,KAAC,CAAC,CAAA;IACJ,CAAC,CAAC;;ICzLF;;;;;;IAMG;IAQH;;;;IAIG;IAEH;IACA,MAAMF,UAAQ,GAAG,cAAc,CAAA;IAC/B,MAAMC,WAAS,GAAG,CAAI,CAAA,EAAAD,UAAQ,EAAE,CAAA;IAEhC,MAAMI,gBAAc,GAAG,CAAW,QAAA,EAAAH,WAAS,EAAE,CAAA;IAC7C,MAAMI,iBAAe,GAAG,CAAY,SAAA,EAAAJ,WAAS,EAAE,CAAA;IAC/C;IAEA,MAAM,oBAAoB,GAAG,WAAW,CAAA;IACxC,MAAM,iBAAiB,GAAG,WAAW,CAAA;IACrC,MAAM,iBAAiB,GAAG,WAAW,CAAA;IACrC,MAAM,sBAAsB,GAAG,eAAe,CAAA;IAC9C,MAAMK,sBAAoB,GAAG,8BAA8B,CAAA;IAE3D,MAAMC,SAAO,GAAG;IACd,IAAA,cAAc,EAAE,GAAG;IACnB,IAAA,SAAS,EAAE,IAAI;KAChB,CAAA;IAOD;;;IAGG;IAEH,MAAM,QAAQ,CAAA;QAIZ,WAAY,CAAA,OAAoB,EAAE,MAAc,EAAA;IAC9C,QAAA,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACvB,QAAA,IAAI,CAAC,OAAO,GAAA,MAAA,CAAA,MAAA,CAAA,MAAA,CAAA,MAAA,CAAA,EAAA,EAAQA,SAAO,CAAK,EAAA,MAAM,CAAE,CAAA;SACzC;QAED,IAAI,GAAA;;IACF,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAACH,gBAAc,CAAC,CAAA;IAEvC,QAAA,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE;IAC1B,YAAA,MAAM,YAAY,GAAG,CAAA,EAAA,GAAA,IAAI,CAAC,QAAQ,CAAC,aAAa,MAAE,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,gBAAgB,CAAC,CAAG,EAAA,iBAAiB,IAAI,oBAAoB,CAAA,CAAE,CAAC,CAAA;gBAElH,YAAY,KAAA,IAAA,IAAZ,YAAY,KAAZ,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,YAAY,CAAE,OAAO,CAAC,QAAQ,IAAG;oBAC/B,IAAI,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE;IAC5C,oBAAA,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAA;IAC/C,oBAAA,MAAM,YAAY,GAAG,QAAQ,KAAA,IAAA,IAAR,QAAQ,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAR,QAAQ,CAAE,aAAa,CAAC,sBAAsB,CAA4B,CAAA;wBAC/F,IAAI,YAAY,EAAE;4BAChB,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;yBACnD;qBACF;IACH,aAAC,CAAC,CAAA;aACH;YAED,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;YAEjD,MAAM,YAAY,GAAG,CAAA,EAAA,GAAA,IAAI,CAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,aAAa,CAAC,sBAAsB,CAA4B,CAAA;YACpG,IAAI,YAAY,EAAE;gBAChB,SAAS,CAAC,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;aACrD;IAED,QAAA,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;SACnC;QAED,KAAK,GAAA;;IACH,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAACC,iBAAe,CAAC,CAAA;YAExC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAA;YAEpD,MAAM,YAAY,GAAG,CAAA,EAAA,GAAA,IAAI,CAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,aAAa,CAAC,sBAAsB,CAA4B,CAAA;YACpG,IAAI,YAAY,EAAE;gBAChB,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;aACnD;IAED,QAAA,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;SACnC;QAED,MAAM,GAAA;YACJ,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE;gBAC1D,IAAI,CAAC,KAAK,EAAE,CAAA;aACb;iBAAM;gBACL,IAAI,CAAC,IAAI,EAAE,CAAA;aACZ;SACF;IACF,CAAA;IAED;;;;IAIG;IAEH,kBAAkB,CAAC,MAAK;QACtB,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CAACC,sBAAoB,CAAC,CAAA;IAE9D,IAAA,MAAM,CAAC,OAAO,CAAC,GAAG,IAAG;IACnB,QAAA,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,IAAG;IACpC,YAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAA;gBAC1C,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAA4B,CAAA;gBAC/E,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAkC,CAAA;IAErF,YAAA,IAAI,CAAA,MAAM,KAAN,IAAA,IAAA,MAAM,KAAN,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,MAAM,CAAE,YAAY,CAAC,MAAM,CAAC,MAAK,GAAG,IAAI,CAAA,UAAU,KAAV,IAAA,IAAA,UAAU,KAAV,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,UAAU,CAAE,YAAY,CAAC,MAAM,CAAC,MAAK,GAAG,EAAE;oBACpF,KAAK,CAAC,cAAc,EAAE,CAAA;iBACvB;gBAED,IAAI,UAAU,EAAE;oBACd,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,UAAU,EAAEC,SAAO,CAAC,CAAA;oBAC9C,IAAI,CAAC,MAAM,EAAE,CAAA;iBACd;IACH,SAAC,CAAC,CAAA;IACJ,KAAC,CAAC,CAAA;IACJ,CAAC,CAAC;;ICpIF;;;;;;IAMG;IAMH;;;IAGG;IAEH,MAAMP,UAAQ,GAAG,iBAAiB,CAAA;IAClC,MAAMC,WAAS,GAAG,CAAI,CAAA,EAAAD,UAAQ,EAAE,CAAA;IAChC,MAAMI,gBAAc,GAAG,CAAW,QAAA,EAAAH,WAAS,EAAE,CAAA;IAC7C,MAAMI,iBAAe,GAAG,CAAY,SAAA,EAAAJ,WAAS,EAAE,CAAA;IAE/C,MAAM,oBAAoB,GAAG,+BAA+B,CAAA;IAC5D,MAAM,oBAAoB,GAAG,cAAc,CAAA;IAE3C,MAAM,2BAA2B,GAAG,2BAA2B,CAAA;IAE/D;;;IAGG;IAEH,MAAM,UAAU,CAAA;IAEd,IAAA,WAAA,CAAY,OAAoB,EAAA;IAC9B,QAAA,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;SACxB;QAED,MAAM,GAAA;YACJ,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,2BAA2B,CAAC,EAAE;IACjE,YAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAACI,iBAAe,CAAC,CAAA;gBAExC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAA;IAE3D,YAAA,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;aACnC;iBAAM;IACL,YAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAACD,gBAAc,CAAC,CAAA;gBAEvC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAA;IAExD,YAAA,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;aACnC;SACF;IACF,CAAA;IAED;;;;IAIG;IAEH,kBAAkB,CAAC,MAAK;QACtB,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;IAE9D,IAAA,MAAM,CAAC,OAAO,CAAC,GAAG,IAAG;IACnB,QAAA,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,IAAG;gBACpC,KAAK,CAAC,cAAc,EAAE,CAAA;IACtB,YAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAA;gBAC1C,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAA4B,CAAA;gBAEhF,IAAI,QAAQ,EAAE;IACZ,gBAAA,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAA;oBACrC,IAAI,CAAC,MAAM,EAAE,CAAA;iBACd;IACH,SAAC,CAAC,CAAA;IACJ,KAAC,CAAC,CAAA;IACJ,CAAC,CAAC;;IC5EF;;;;;;IAMG;IAQH;;;IAGG;IAEH,MAAMJ,UAAQ,GAAG,iBAAiB,CAAA;IAClC,MAAMC,WAAS,GAAG,CAAI,CAAA,EAAAD,UAAQ,EAAE,CAAA;IAChC,MAAM,eAAe,GAAG,CAAY,SAAA,EAAAC,WAAS,EAAE,CAAA;IAC/C,MAAM,cAAc,GAAG,CAAW,QAAA,EAAAA,WAAS,EAAE,CAAA;IAC7C,MAAM,YAAY,GAAG,CAAS,MAAA,EAAAA,WAAS,EAAE,CAAA;IACzC,MAAMO,iBAAe,GAAG,CAAY,SAAA,EAAAP,WAAS,EAAE,CAAA;IAC/C,MAAMQ,iBAAe,GAAG,CAAY,SAAA,EAAAR,WAAS,EAAE,CAAA;IAE/C,MAAM,eAAe,GAAG,MAAM,CAAA;IAC9B,MAAM,oBAAoB,GAAG,gBAAgB,CAAA;IAC7C,MAAM,qBAAqB,GAAG,iBAAiB,CAAA;IAC/C,MAAM,oBAAoB,GAAG,gBAAgB,CAAA;IAC7C,MAAM,wBAAwB,GAAG,eAAe,CAAA;IAChD,MAAM,oBAAoB,GAAG,gBAAgB,CAAA;IAE7C,MAAM,oBAAoB,GAAG,iCAAiC,CAAA;IAC9D,MAAM,sBAAsB,GAAG,mCAAmC,CAAA;IAClE,MAAM,sBAAsB,GAAG,mCAAmC,CAAA;IAClE,MAAM,aAAa,GAAG,CAAI,CAAA,EAAA,eAAe,EAAE,CAAA;IAC3C,MAAM,kBAAkB,GAAG,YAAY,CAAA;IACvC,MAAM,oBAAoB,GAAG,cAAc,CAAA;IAS3C,MAAM,OAAO,GAAW;IACtB,IAAA,cAAc,EAAE,GAAG;IACnB,IAAA,eAAe,EAAE,sBAAsB;IACvC,IAAA,aAAa,EAAE,oBAAoB;IACnC,IAAA,eAAe,EAAE,sBAAsB;KACxC,CAAA;IAED,MAAM,UAAU,CAAA;QAMd,WAAY,CAAA,OAAoB,EAAE,MAAc,EAAA;IAC9C,QAAA,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;YACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAA4B,CAAA;YAExE,IAAI,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE;IAC/C,YAAA,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;aACvB;IAED,QAAA,IAAI,CAAC,OAAO,GAAA,MAAA,CAAA,MAAA,CAAA,MAAA,CAAA,MAAA,CAAA,EAAA,EAAQ,OAAO,CAAK,EAAA,MAAM,CAAE,CAAA;SACzC;QAED,QAAQ,GAAA;;IACN,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,eAAe,CAAC,CAAA;IAExC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;gBAChB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA;IAEjD,YAAA,MAAM,GAAG,GAAG,CAAA,EAAA,GAAA,IAAI,CAAC,OAAO,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,gBAAgB,CAAC,GAAG,kBAAkB,CAAA,EAAA,EAAK,oBAAoB,CAAA,CAAE,CAAC,CAAA;IAE5F,YAAA,GAAG,CAAC,OAAO,CAAC,EAAE,IAAG;IACf,gBAAA,IAAI,EAAE,YAAY,WAAW,EAAE;wBAC7B,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;qBACzC;IACH,aAAC,CAAC,CAAA;gBAEF,UAAU,CAAC,MAAK;IACd,gBAAA,IAAI,IAAI,CAAC,OAAO,EAAE;wBAChB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;wBAChD,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAA;qBACrD;IACH,aAAC,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;aAChC;YAED,CAAA,EAAA,GAAA,IAAI,CAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,aAAa,CAAC,KAAK,CAAC,CAAA;SACpC;QAED,MAAM,GAAA;;IACJ,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,cAAc,CAAC,CAAA;IAEvC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;gBAChB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;IAEhD,YAAA,MAAM,GAAG,GAAG,CAAA,EAAA,GAAA,IAAI,CAAC,OAAO,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,gBAAgB,CAAC,GAAG,kBAAkB,CAAA,EAAA,EAAK,oBAAoB,CAAA,CAAE,CAAC,CAAA;IAE5F,YAAA,GAAG,CAAC,OAAO,CAAC,EAAE,IAAG;IACf,gBAAA,IAAI,EAAE,YAAY,WAAW,EAAE;wBAC7B,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;qBAC3C;IACH,aAAC,CAAC,CAAA;gBAEF,UAAU,CAAC,MAAK;IACd,gBAAA,IAAI,IAAI,CAAC,OAAO,EAAE;wBAChB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAA;wBACnD,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAA;qBACpD;IACH,aAAC,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;aAChC;YAED,CAAA,EAAA,GAAA,IAAI,CAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,aAAa,CAAC,KAAK,CAAC,CAAA;SACpC;QAED,MAAM,GAAA;;IACJ,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,YAAY,CAAC,CAAA;IAErC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;gBAChB,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;aACnD;YAED,CAAA,EAAA,GAAA,IAAI,CAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,aAAa,CAAC,KAAK,CAAC,CAAA;SACpC;QAED,MAAM,GAAA;;IACJ,QAAA,IAAI,CAAA,EAAA,GAAA,IAAI,CAAC,OAAO,MAAE,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,SAAS,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE;gBAC1D,IAAI,CAAC,MAAM,EAAE,CAAA;gBACb,OAAM;aACP;YAED,IAAI,CAAC,QAAQ,EAAE,CAAA;SAChB;QAED,QAAQ,GAAA;;IACN,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAACO,iBAAe,CAAC,CAAA;IAExC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;IAChB,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAG,EAAA,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,CAAA;IAC5D,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAG,EAAA,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAA;gBAC1D,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,UAAU,CAAA;gBAE1C,UAAU,CAAC,MAAK;oBACd,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;oBAE9C,IAAI,OAAO,EAAE;IACX,oBAAA,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;qBAC5C;IAED,gBAAA,IAAI,IAAI,CAAC,OAAO,EAAE;wBAChB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;wBAEhD,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE;4BACzD,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAA;yBACrD;qBACF;iBACF,EAAE,GAAG,CAAC,CAAA;aACR;YAED,CAAA,EAAA,GAAA,IAAI,CAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,aAAa,CAAC,KAAK,CAAC,CAAA;SACpC;QAED,QAAQ,GAAA;;IACN,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAACC,iBAAe,CAAC,CAAA;IAExC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;gBAChB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAA;gBAClC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAA;gBACjC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,UAAU,CAAA;gBAE1C,UAAU,CAAC,MAAK;;oBACd,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;oBAE9C,IAAI,OAAO,EAAE;IACX,oBAAA,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAA;qBAC/C;IAED,gBAAA,IAAI,IAAI,CAAC,OAAO,EAAE;wBAChB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAA;IAEnD,oBAAA,IAAI,CAAA,EAAA,GAAA,IAAI,CAAC,OAAO,MAAE,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,SAAS,CAAC,QAAQ,CAAC,wBAAwB,CAAC,EAAE;4BAC9D,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAA;yBACxD;qBACF;iBACF,EAAE,EAAE,CAAC,CAAA;aACP;YAED,CAAA,EAAA,GAAA,IAAI,CAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAE,aAAa,CAAC,KAAK,CAAC,CAAA;SACpC;QAED,cAAc,GAAA;;IACZ,QAAA,IAAI,CAAA,EAAA,GAAA,IAAI,CAAC,OAAO,MAAE,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,SAAS,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE;gBAC1D,IAAI,CAAC,QAAQ,EAAE,CAAA;gBACf,OAAM;aACP;YAED,IAAI,CAAC,QAAQ,EAAE,CAAA;SAChB;IACF,CAAA;IAED;;;;IAIG;IAEH,kBAAkB,CAAC,MAAK;QACtB,MAAM,WAAW,GAAG,QAAQ,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,CAAA;IAErE,IAAA,WAAW,CAAC,OAAO,CAAC,GAAG,IAAG;IACxB,QAAA,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,IAAG;gBACpC,KAAK,CAAC,cAAc,EAAE,CAAA;IACtB,YAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAA;gBAC1C,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;gBAC5C,IAAI,CAAC,MAAM,EAAE,CAAA;IACf,SAAC,CAAC,CAAA;IACJ,KAAC,CAAC,CAAA;QAEF,MAAM,SAAS,GAAG,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;IAEjE,IAAA,SAAS,CAAC,OAAO,CAAC,GAAG,IAAG;IACtB,QAAA,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,IAAG;gBACpC,KAAK,CAAC,cAAc,EAAE,CAAA;IACtB,YAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAA;gBAC1C,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;gBAC5C,IAAI,CAAC,MAAM,EAAE,CAAA;IACf,SAAC,CAAC,CAAA;IACJ,KAAC,CAAC,CAAA;QAEF,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,CAAA;IAEhE,IAAA,MAAM,CAAC,OAAO,CAAC,GAAG,IAAG;IACnB,QAAA,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,IAAG;gBACpC,KAAK,CAAC,cAAc,EAAE,CAAA;IACtB,YAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAA;gBAC1C,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;gBAC5C,IAAI,CAAC,cAAc,EAAE,CAAA;IACvB,SAAC,CAAC,CAAA;IACJ,KAAC,CAAC,CAAA;IACJ,CAAC,CAAC;;ICtPF;;;;;;IAMG;IAMH;;;IAGG;IACH,MAAM,QAAQ,GAAG,gBAAgB,CAAA;IACjC,MAAM,SAAS,GAAG,CAAI,CAAA,EAAA,QAAQ,EAAE,CAAA;IAChC,MAAM,eAAe,GAAG,CAAY,SAAA,EAAA,SAAS,EAAE,CAAA;IAC/C,MAAM,eAAe,GAAG,CAAY,SAAA,EAAA,SAAS,EAAE,CAAA;IAE/C,MAAM,0BAA0B,GAAG,gCAAgC,CAAA;IACnE,MAAM,sBAAsB,GAAG,4BAA4B,CAAA;IAC3D,MAAM,sBAAsB,GAAG,4BAA4B,CAAA;IAE3D;;;IAGG;IACH,MAAM,UAAU,CAAA;QAId,WAAY,CAAA,OAAoB,EAAE,MAAkB,EAAA;IAClD,QAAA,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACvB,QAAA,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;SACtB;QAED,YAAY,GAAA;IACV,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,eAAe,CAAC,CAAA;YAExC,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAc,sBAAsB,CAAC,CAAA;YAChF,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAc,sBAAsB,CAAC,CAAA;IAEhF,QAAA,KAAK,QAAQ,CAAC,eAAe,CAAC,iBAAiB,EAAE,CAAA;YAEjD,IAAI,YAAY,EAAE;IAChB,YAAA,YAAY,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAA;aACpC;YAED,IAAI,YAAY,EAAE;IAChB,YAAA,YAAY,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAA;aACrC;IAED,QAAA,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;SACnC;QAED,aAAa,GAAA;IACX,QAAA,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,eAAe,CAAC,CAAA;YAExC,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAc,sBAAsB,CAAC,CAAA;YAChF,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAc,sBAAsB,CAAC,CAAA;IAEhF,QAAA,KAAK,QAAQ,CAAC,cAAc,EAAE,CAAA;YAE9B,IAAI,YAAY,EAAE;IAChB,YAAA,YAAY,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAA;aACrC;YAED,IAAI,YAAY,EAAE;IAChB,YAAA,YAAY,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAA;aACpC;IAED,QAAA,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;SACnC;QAED,gBAAgB,GAAA;IACd,QAAA,IAAI,QAAQ,CAAC,iBAAiB,EAAE;IAC9B,YAAA,IAAI,QAAQ,CAAC,iBAAiB,EAAE;oBAC9B,IAAI,CAAC,aAAa,EAAE,CAAA;iBACrB;qBAAM;oBACL,IAAI,CAAC,YAAY,EAAE,CAAA;iBACpB;aACF;SACF;IACF,CAAA;IAED;;;IAGG;IACH,kBAAkB,CAAC,MAAK;QACtB,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,CAAA;IAErE,IAAA,OAAO,CAAC,OAAO,CAAC,GAAG,IAAG;IACpB,QAAA,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,IAAG;gBACpC,KAAK,CAAC,cAAc,EAAE,CAAA;IAEtB,YAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAA;gBAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,0BAA0B,CAA4B,CAAA;gBAEpF,IAAI,MAAM,EAAE;oBACV,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;oBAC9C,IAAI,CAAC,gBAAgB,EAAE,CAAA;iBACxB;IACH,SAAC,CAAC,CAAA;IACJ,KAAC,CAAC,CAAA;IACJ,CAAC,CAAC;;;;;;;;;;;;;"}
 
 
static/js/adminlte.min.js DELETED
@@ -1,7 +0,0 @@
1
- /*!
2
- * AdminLTE v4.0.0-beta2 (https://adminlte.io)
3
- * Copyright 2014-2024 Colorlib <https://colorlib.com>
4
- * Licensed under MIT (https://github.com/ColorlibHQ/AdminLTE/blob/master/LICENSE)
5
- */
6
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).adminlte={})}(this,(function(e){"use strict";const t=[],n=e=>{"loading"===document.readyState?(t.length||document.addEventListener("DOMContentLoaded",(()=>{for(const e of t)e()})),t.push(e)):e()},s=(e,t=500)=>{e.style.transitionProperty="height, margin, padding",e.style.transitionDuration=`${t}ms`,e.style.boxSizing="border-box",e.style.height=`${e.offsetHeight}px`,e.style.overflow="hidden",window.setTimeout((()=>{e.style.height="0",e.style.paddingTop="0",e.style.paddingBottom="0",e.style.marginTop="0",e.style.marginBottom="0"}),1),window.setTimeout((()=>{e.style.display="none",e.style.removeProperty("height"),e.style.removeProperty("padding-top"),e.style.removeProperty("padding-bottom"),e.style.removeProperty("margin-top"),e.style.removeProperty("margin-bottom"),e.style.removeProperty("overflow"),e.style.removeProperty("transition-duration"),e.style.removeProperty("transition-property")}),t)},i=(e,t=500)=>{e.style.removeProperty("display");let{display:n}=window.getComputedStyle(e);"none"===n&&(n="block"),e.style.display=n;const s=e.offsetHeight;e.style.overflow="hidden",e.style.height="0",e.style.paddingTop="0",e.style.paddingBottom="0",e.style.marginTop="0",e.style.marginBottom="0",window.setTimeout((()=>{e.style.boxSizing="border-box",e.style.transitionProperty="height, margin, padding",e.style.transitionDuration=`${t}ms`,e.style.height=`${s}px`,e.style.removeProperty("padding-top"),e.style.removeProperty("padding-bottom"),e.style.removeProperty("margin-top"),e.style.removeProperty("margin-bottom")}),1),window.setTimeout((()=>{e.style.removeProperty("height"),e.style.removeProperty("overflow"),e.style.removeProperty("transition-duration"),e.style.removeProperty("transition-property")}),t)},o="hold-transition";class l{constructor(e){this._element=e}holdTransition(){let e;window.addEventListener("resize",(()=>{document.body.classList.add(o),clearTimeout(e),e=setTimeout((()=>{document.body.classList.remove(o)}),400)}))}}n((()=>{new l(document.body).holdTransition(),setTimeout((()=>{document.body.classList.add("app-loaded")}),400)}));const a=".lte.push-menu",r=`open${a}`,c=`collapse${a}`,d="sidebar-mini",m="sidebar-collapse",p="sidebar-open",h="sidebar-expand",u=`[class*="${h}"]`,v='[data-lte-toggle="sidebar"]',y={sidebarBreakpoint:992};class g{constructor(e,t){this._element=e,this._config=Object.assign(Object.assign({},y),t)}menusClose(){document.querySelectorAll(".nav-treeview").forEach((e=>{e.style.removeProperty("display"),e.style.removeProperty("height")}));const e=document.querySelector(".sidebar-menu"),t=null==e?void 0:e.querySelectorAll(".nav-item");t&&t.forEach((e=>{e.classList.remove("menu-open")}))}expand(){const e=new Event(r);document.body.classList.remove(m),document.body.classList.add(p),this._element.dispatchEvent(e)}collapse(){const e=new Event(c);document.body.classList.remove(p),document.body.classList.add(m),this._element.dispatchEvent(e)}addSidebarBreakPoint(){var e,t,n;const s=null!==(t=null===(e=document.querySelector(u))||void 0===e?void 0:e.classList)&&void 0!==t?t:[],i=null!==(n=Array.from(s).find((e=>e.startsWith(h))))&&void 0!==n?n:"",o=document.getElementsByClassName(i)[0],l=window.getComputedStyle(o,"::before").getPropertyValue("content");this._config=Object.assign(Object.assign({},this._config),{sidebarBreakpoint:Number(l.replace(/[^\d.-]/g,""))}),window.innerWidth<=this._config.sidebarBreakpoint?this.collapse():(document.body.classList.contains(d)||this.expand(),document.body.classList.contains(d)&&document.body.classList.contains(m)&&this.collapse())}toggle(){document.body.classList.contains(m)?this.expand():this.collapse()}init(){this.addSidebarBreakPoint()}}n((()=>{var e;const t=null===document||void 0===document?void 0:document.querySelector(".app-sidebar");if(t){const e=new g(t,y);e.init(),window.addEventListener("resize",(()=>{e.init()}))}const n=document.createElement("div");n.className="sidebar-overlay",null===(e=document.querySelector(".app-wrapper"))||void 0===e||e.append(n),n.addEventListener("touchstart",(e=>{e.preventDefault();const t=e.currentTarget;new g(t,y).collapse()}),{passive:!0}),n.addEventListener("click",(e=>{e.preventDefault();const t=e.currentTarget;new g(t,y).collapse()})),document.querySelectorAll(v).forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault();let t=e.currentTarget;"sidebar"!==(null==t?void 0:t.dataset.lteToggle)&&(t=null==t?void 0:t.closest(v)),t&&(null==e||e.preventDefault(),new g(t,y).toggle())}))}))}));const f=".lte.treeview",_=`expanded${f}`,E=`collapsed${f}`,b="menu-open",w=".nav-item",L=".nav-treeview",S={animationSpeed:300,accordion:!0};class x{constructor(e,t){this._element=e,this._config=Object.assign(Object.assign({},S),t)}open(){var e,t;const n=new Event(_);if(this._config.accordion){const t=null===(e=this._element.parentElement)||void 0===e?void 0:e.querySelectorAll(`${w}.${b}`);null==t||t.forEach((e=>{if(e!==this._element.parentElement){e.classList.remove(b);const t=null==e?void 0:e.querySelector(L);t&&s(t,this._config.animationSpeed)}}))}this._element.classList.add(b);const o=null===(t=this._element)||void 0===t?void 0:t.querySelector(L);o&&i(o,this._config.animationSpeed),this._element.dispatchEvent(n)}close(){var e;const t=new Event(E);this._element.classList.remove(b);const n=null===(e=this._element)||void 0===e?void 0:e.querySelector(L);n&&s(n,this._config.animationSpeed),this._element.dispatchEvent(t)}toggle(){this._element.classList.contains(b)?this.close():this.open()}}n((()=>{document.querySelectorAll('[data-lte-toggle="treeview"]').forEach((e=>{e.addEventListener("click",(e=>{const t=e.target,n=t.closest(w),s=t.closest(".nav-link");"#"!==(null==t?void 0:t.getAttribute("href"))&&"#"!==(null==s?void 0:s.getAttribute("href"))||e.preventDefault(),n&&new x(n,S).toggle()}))}))}));const T=".lte.direct-chat",$=`expanded${T}`,q=`collapsed${T}`,P="direct-chat-contacts-open";class z{constructor(e){this._element=e}toggle(){if(this._element.classList.contains(P)){const e=new Event(q);this._element.classList.remove(P),this._element.dispatchEvent(e)}else{const e=new Event($);this._element.classList.add(P),this._element.dispatchEvent(e)}}}n((()=>{document.querySelectorAll('[data-lte-toggle="chat-pane"]').forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault();const t=e.target.closest(".direct-chat");t&&new z(t).toggle()}))}))}));const k=".lte.card-widget",A=`collapsed${k}`,D=`expanded${k}`,B=`remove${k}`,j=`maximized${k}`,F=`minimized${k}`,O="card",C="collapsed-card",M="collapsing-card",H="expanding-card",W="was-collapsed",N="maximized-card",V='[data-lte-toggle="card-remove"]',G='[data-lte-toggle="card-collapse"]',I='[data-lte-toggle="card-maximize"]',J=`.${O}`,K=".card-body",Q=".card-footer",R={animationSpeed:500,collapseTrigger:G,removeTrigger:V,maximizeTrigger:I};class U{constructor(e,t){this._element=e,this._parent=e.closest(J),e.classList.contains(O)&&(this._parent=e),this._config=Object.assign(Object.assign({},R),t)}collapse(){var e,t;const n=new Event(A);this._parent&&(this._parent.classList.add(M),(null===(e=this._parent)||void 0===e?void 0:e.querySelectorAll(`${K}, ${Q}`)).forEach((e=>{e instanceof HTMLElement&&s(e,this._config.animationSpeed)})),setTimeout((()=>{this._parent&&(this._parent.classList.add(C),this._parent.classList.remove(M))}),this._config.animationSpeed)),null===(t=this._element)||void 0===t||t.dispatchEvent(n)}expand(){var e,t;const n=new Event(D);this._parent&&(this._parent.classList.add(H),(null===(e=this._parent)||void 0===e?void 0:e.querySelectorAll(`${K}, ${Q}`)).forEach((e=>{e instanceof HTMLElement&&i(e,this._config.animationSpeed)})),setTimeout((()=>{this._parent&&(this._parent.classList.remove(C),this._parent.classList.remove(H))}),this._config.animationSpeed)),null===(t=this._element)||void 0===t||t.dispatchEvent(n)}remove(){var e;const t=new Event(B);this._parent&&s(this._parent,this._config.animationSpeed),null===(e=this._element)||void 0===e||e.dispatchEvent(t)}toggle(){var e;(null===(e=this._parent)||void 0===e?void 0:e.classList.contains(C))?this.expand():this.collapse()}maximize(){var e;const t=new Event(j);this._parent&&(this._parent.style.height=`${this._parent.offsetHeight}px`,this._parent.style.width=`${this._parent.offsetWidth}px`,this._parent.style.transition="all .15s",setTimeout((()=>{const e=document.querySelector("html");e&&e.classList.add(N),this._parent&&(this._parent.classList.add(N),this._parent.classList.contains(C)&&this._parent.classList.add(W))}),150)),null===(e=this._element)||void 0===e||e.dispatchEvent(t)}minimize(){var e;const t=new Event(F);this._parent&&(this._parent.style.height="auto",this._parent.style.width="auto",this._parent.style.transition="all .15s",setTimeout((()=>{var e;const t=document.querySelector("html");t&&t.classList.remove(N),this._parent&&(this._parent.classList.remove(N),(null===(e=this._parent)||void 0===e?void 0:e.classList.contains(W))&&this._parent.classList.remove(W))}),10)),null===(e=this._element)||void 0===e||e.dispatchEvent(t)}toggleMaximize(){var e;(null===(e=this._parent)||void 0===e?void 0:e.classList.contains(N))?this.minimize():this.maximize()}}n((()=>{document.querySelectorAll(G).forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault();const t=e.target;new U(t,R).toggle()}))})),document.querySelectorAll(V).forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault();const t=e.target;new U(t,R).remove()}))})),document.querySelectorAll(I).forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault();const t=e.target;new U(t,R).toggleMaximize()}))}))}));const X=".lte.fullscreen",Y=`maximized${X}`,Z=`minimized${X}`,ee='[data-lte-toggle="fullscreen"]',te='[data-lte-icon="maximize"]',ne='[data-lte-icon="minimize"]';class se{constructor(e,t){this._element=e,this._config=t}inFullScreen(){const e=new Event(Y),t=document.querySelector(te),n=document.querySelector(ne);document.documentElement.requestFullscreen(),t&&(t.style.display="none"),n&&(n.style.display="block"),this._element.dispatchEvent(e)}outFullscreen(){const e=new Event(Z),t=document.querySelector(te),n=document.querySelector(ne);document.exitFullscreen(),t&&(t.style.display="block"),n&&(n.style.display="none"),this._element.dispatchEvent(e)}toggleFullScreen(){document.fullscreenEnabled&&(document.fullscreenElement?this.outFullscreen():this.inFullScreen())}}n((()=>{document.querySelectorAll(ee).forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault();const t=e.target.closest(ee);t&&new se(t,void 0).toggleFullScreen()}))}))})),e.CardWidget=U,e.DirectChat=z,e.FullScreen=se,e.Layout=l,e.PushMenu=g,e.Treeview=x}));
7
- //# sourceMappingURL=adminlte.min.js.map
 
 
 
 
 
 
 
 
static/js/adminlte.min.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"names":["domContentLoadedCallbacks","onDOMContentLoaded","callback","document","readyState","length","addEventListener","push","slideUp","target","duration","style","transitionProperty","transitionDuration","boxSizing","height","offsetHeight","overflow","window","setTimeout","paddingTop","paddingBottom","marginTop","marginBottom","display","removeProperty","slideDown","getComputedStyle","CLASS_NAME_HOLD_TRANSITIONS","Layout","constructor","element","this","_element","holdTransition","resizeTimer","body","classList","add","clearTimeout","remove","EVENT_KEY","EVENT_OPEN","EVENT_COLLAPSE","CLASS_NAME_SIDEBAR_MINI","CLASS_NAME_SIDEBAR_COLLAPSE","CLASS_NAME_SIDEBAR_OPEN","CLASS_NAME_SIDEBAR_EXPAND","SELECTOR_SIDEBAR_EXPAND","SELECTOR_SIDEBAR_TOGGLE","Defaults","sidebarBreakpoint","PushMenu","config","_config","Object","assign","menusClose","querySelectorAll","forEach","navTree","navSidebar","querySelector","navItem","navI","expand","event","Event","dispatchEvent","collapse","addSidebarBreakPoint","sidebarExpandList","_b","_a","sidebarExpand","_c","Array","from","find","className","startsWith","sidebar","getElementsByClassName","sidebarContent","getPropertyValue","Number","replace","innerWidth","contains","toggle","init","data","sidebarOverlay","createElement","append","preventDefault","currentTarget","passive","btn","button","dataset","lteToggle","closest","EVENT_EXPANDED","EVENT_COLLAPSED","CLASS_NAME_MENU_OPEN","SELECTOR_NAV_ITEM","SELECTOR_TREEVIEW_MENU","Default","animationSpeed","accordion","Treeview","open","openMenuList","parentElement","openMenu","childElement","close","targetItem","targetLink","getAttribute","CLASS_NAME_DIRECT_CHAT_OPEN","DirectChat","chatPane","EVENT_REMOVE","EVENT_MAXIMIZED","EVENT_MINIMIZED","CLASS_NAME_CARD","CLASS_NAME_COLLAPSED","CLASS_NAME_COLLAPSING","CLASS_NAME_EXPANDING","CLASS_NAME_WAS_COLLAPSED","CLASS_NAME_MAXIMIZED","SELECTOR_DATA_REMOVE","SELECTOR_DATA_COLLAPSE","SELECTOR_DATA_MAXIMIZE","SELECTOR_CARD","SELECTOR_CARD_BODY","SELECTOR_CARD_FOOTER","collapseTrigger","removeTrigger","maximizeTrigger","CardWidget","_parent","el","HTMLElement","maximize","width","offsetWidth","transition","htmlTag","minimize","toggleMaximize","SELECTOR_FULLSCREEN_TOGGLE","SELECTOR_MAXIMIZE_ICON","SELECTOR_MINIMIZE_ICON","FullScreen","inFullScreen","iconMaximize","iconMinimize","documentElement","requestFullscreen","outFullscreen","exitFullscreen","toggleFullScreen","fullscreenEnabled","fullscreenElement","undefined"],"sources":["../../src/ts/util/index.ts","../../src/ts/layout.ts","../../src/ts/push-menu.ts","../../src/ts/treeview.ts","../../src/ts/direct-chat.ts","../../src/ts/card-widget.ts","../../src/ts/fullscreen.ts"],"sourcesContent":["const domContentLoadedCallbacks: Array<() => void> = []\n\nconst onDOMContentLoaded = (callback: () => void): void => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!domContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of domContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n domContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\n/* SLIDE UP */\nconst slideUp = (target: HTMLElement, duration = 500) => {\n target.style.transitionProperty = 'height, margin, padding'\n target.style.transitionDuration = `${duration}ms`\n target.style.boxSizing = 'border-box'\n target.style.height = `${target.offsetHeight}px`\n target.style.overflow = 'hidden'\n\n window.setTimeout(() => {\n target.style.height = '0'\n target.style.paddingTop = '0'\n target.style.paddingBottom = '0'\n target.style.marginTop = '0'\n target.style.marginBottom = '0'\n }, 1)\n\n window.setTimeout(() => {\n target.style.display = 'none'\n target.style.removeProperty('height')\n target.style.removeProperty('padding-top')\n target.style.removeProperty('padding-bottom')\n target.style.removeProperty('margin-top')\n target.style.removeProperty('margin-bottom')\n target.style.removeProperty('overflow')\n target.style.removeProperty('transition-duration')\n target.style.removeProperty('transition-property')\n }, duration)\n}\n\n/* SLIDE DOWN */\nconst slideDown = (target: HTMLElement, duration = 500) => {\n target.style.removeProperty('display')\n let { display } = window.getComputedStyle(target)\n\n if (display === 'none') {\n display = 'block'\n }\n\n target.style.display = display\n const height = target.offsetHeight\n target.style.overflow = 'hidden'\n target.style.height = '0'\n target.style.paddingTop = '0'\n target.style.paddingBottom = '0'\n target.style.marginTop = '0'\n target.style.marginBottom = '0'\n\n window.setTimeout(() => {\n target.style.boxSizing = 'border-box'\n target.style.transitionProperty = 'height, margin, padding'\n target.style.transitionDuration = `${duration}ms`\n target.style.height = `${height}px`\n target.style.removeProperty('padding-top')\n target.style.removeProperty('padding-bottom')\n target.style.removeProperty('margin-top')\n target.style.removeProperty('margin-bottom')\n }, 1)\n\n window.setTimeout(() => {\n target.style.removeProperty('height')\n target.style.removeProperty('overflow')\n target.style.removeProperty('transition-duration')\n target.style.removeProperty('transition-property')\n }, duration)\n}\n\n/* TOGGLE */\nconst slideToggle = (target: HTMLElement, duration = 500) => {\n if (window.getComputedStyle(target).display === 'none') {\n slideDown(target, duration)\n return\n }\n\n slideUp(target, duration)\n}\n\nexport {\n onDOMContentLoaded,\n slideUp,\n slideDown,\n slideToggle\n}\n","/**\n * --------------------------------------------\n * @file AdminLTE layout.ts\n * @description Layout for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded\n} from './util/index'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst CLASS_NAME_HOLD_TRANSITIONS = 'hold-transition'\nconst CLASS_NAME_APP_LOADED = 'app-loaded'\n\n/**\n * Class Definition\n * ====================================================\n */\n\nclass Layout {\n _element: HTMLElement\n\n constructor(element: HTMLElement) {\n this._element = element\n }\n\n holdTransition(): void {\n let resizeTimer: ReturnType<typeof setTimeout>\n window.addEventListener('resize', () => {\n document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS)\n clearTimeout(resizeTimer)\n resizeTimer = setTimeout(() => {\n document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS)\n }, 400)\n })\n }\n}\n\nonDOMContentLoaded(() => {\n const data = new Layout(document.body)\n data.holdTransition()\n setTimeout(() => {\n document.body.classList.add(CLASS_NAME_APP_LOADED)\n }, 400)\n})\n\nexport default Layout\n","/**\n * --------------------------------------------\n * @file AdminLTE push-menu.ts\n * @description Push menu for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded\n} from './util/index'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst DATA_KEY = 'lte.push-menu'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_OPEN = `open${EVENT_KEY}`\nconst EVENT_COLLAPSE = `collapse${EVENT_KEY}`\n\nconst CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini'\nconst CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse'\nconst CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open'\nconst CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand'\nconst CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay'\nconst CLASS_NAME_MENU_OPEN = 'menu-open'\n\nconst SELECTOR_APP_SIDEBAR = '.app-sidebar'\nconst SELECTOR_SIDEBAR_MENU = '.sidebar-menu'\nconst SELECTOR_NAV_ITEM = '.nav-item'\nconst SELECTOR_NAV_TREEVIEW = '.nav-treeview'\nconst SELECTOR_APP_WRAPPER = '.app-wrapper'\nconst SELECTOR_SIDEBAR_EXPAND = `[class*=\"${CLASS_NAME_SIDEBAR_EXPAND}\"]`\nconst SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle=\"sidebar\"]'\n\ntype Config = {\n sidebarBreakpoint: number;\n}\n\nconst Defaults = {\n sidebarBreakpoint: 992\n}\n\n/**\n * Class Definition\n * ====================================================\n */\n\nclass PushMenu {\n _element: HTMLElement\n _config: Config\n\n constructor(element: HTMLElement, config: Config) {\n this._element = element\n this._config = { ...Defaults, ...config }\n }\n\n // TODO\n menusClose() {\n const navTreeview = document.querySelectorAll<HTMLElement>(SELECTOR_NAV_TREEVIEW)\n\n navTreeview.forEach(navTree => {\n navTree.style.removeProperty('display')\n navTree.style.removeProperty('height')\n })\n\n const navSidebar = document.querySelector(SELECTOR_SIDEBAR_MENU)\n const navItem = navSidebar?.querySelectorAll(SELECTOR_NAV_ITEM)\n\n if (navItem) {\n navItem.forEach(navI => {\n navI.classList.remove(CLASS_NAME_MENU_OPEN)\n })\n }\n }\n\n expand() {\n const event = new Event(EVENT_OPEN)\n\n document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE)\n document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN)\n\n this._element.dispatchEvent(event)\n }\n\n collapse() {\n const event = new Event(EVENT_COLLAPSE)\n\n document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN)\n document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE)\n\n this._element.dispatchEvent(event)\n }\n\n addSidebarBreakPoint() {\n const sidebarExpandList = document.querySelector(SELECTOR_SIDEBAR_EXPAND)?.classList ?? []\n const sidebarExpand = Array.from(sidebarExpandList).find(className => className.startsWith(CLASS_NAME_SIDEBAR_EXPAND)) ?? ''\n const sidebar = document.getElementsByClassName(sidebarExpand)[0]\n const sidebarContent = window.getComputedStyle(sidebar, '::before').getPropertyValue('content')\n this._config = { ...this._config, sidebarBreakpoint: Number(sidebarContent.replace(/[^\\d.-]/g, '')) }\n\n if (window.innerWidth <= this._config.sidebarBreakpoint) {\n this.collapse()\n } else {\n if (!document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI)) {\n this.expand()\n }\n\n if (document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI) && document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {\n this.collapse()\n }\n }\n }\n\n toggle() {\n if (document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {\n this.expand()\n } else {\n this.collapse()\n }\n }\n\n init() {\n this.addSidebarBreakPoint()\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nonDOMContentLoaded(() => {\n const sidebar = document?.querySelector(SELECTOR_APP_SIDEBAR) as HTMLElement | undefined\n\n if (sidebar) {\n const data = new PushMenu(sidebar, Defaults)\n data.init()\n\n window.addEventListener('resize', () => {\n data.init()\n })\n }\n\n const sidebarOverlay = document.createElement('div')\n sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY\n document.querySelector(SELECTOR_APP_WRAPPER)?.append(sidebarOverlay)\n\n sidebarOverlay.addEventListener('touchstart', event => {\n event.preventDefault()\n const target = event.currentTarget as HTMLElement\n const data = new PushMenu(target, Defaults)\n data.collapse()\n }, { passive: true })\n sidebarOverlay.addEventListener('click', event => {\n event.preventDefault()\n const target = event.currentTarget as HTMLElement\n const data = new PushMenu(target, Defaults)\n data.collapse()\n })\n\n const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE)\n\n fullBtn.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n\n let button = event.currentTarget as HTMLElement | undefined\n\n if (button?.dataset.lteToggle !== 'sidebar') {\n button = button?.closest(SELECTOR_SIDEBAR_TOGGLE) as HTMLElement | undefined\n }\n\n if (button) {\n event?.preventDefault()\n const data = new PushMenu(button, Defaults)\n data.toggle()\n }\n })\n })\n})\n\nexport default PushMenu\n","/**\n * --------------------------------------------\n * @file AdminLTE treeview.ts\n * @description Treeview plugin for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded,\n slideDown,\n slideUp\n} from './util/index'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\n// const NAME = 'Treeview'\nconst DATA_KEY = 'lte.treeview'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_EXPANDED = `expanded${EVENT_KEY}`\nconst EVENT_COLLAPSED = `collapsed${EVENT_KEY}`\n// const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst CLASS_NAME_MENU_OPEN = 'menu-open'\nconst SELECTOR_NAV_ITEM = '.nav-item'\nconst SELECTOR_NAV_LINK = '.nav-link'\nconst SELECTOR_TREEVIEW_MENU = '.nav-treeview'\nconst SELECTOR_DATA_TOGGLE = '[data-lte-toggle=\"treeview\"]'\n\nconst Default = {\n animationSpeed: 300,\n accordion: true\n}\n\ntype Config = {\n animationSpeed: number;\n accordion: boolean;\n}\n\n/**\n * Class Definition\n * ====================================================\n */\n\nclass Treeview {\n _element: HTMLElement\n _config: Config\n\n constructor(element: HTMLElement, config: Config) {\n this._element = element\n this._config = { ...Default, ...config }\n }\n\n open(): void {\n const event = new Event(EVENT_EXPANDED)\n\n if (this._config.accordion) {\n const openMenuList = this._element.parentElement?.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`)\n\n openMenuList?.forEach(openMenu => {\n if (openMenu !== this._element.parentElement) {\n openMenu.classList.remove(CLASS_NAME_MENU_OPEN)\n const childElement = openMenu?.querySelector(SELECTOR_TREEVIEW_MENU) as HTMLElement | undefined\n if (childElement) {\n slideUp(childElement, this._config.animationSpeed)\n }\n }\n })\n }\n\n this._element.classList.add(CLASS_NAME_MENU_OPEN)\n\n const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU) as HTMLElement | undefined\n if (childElement) {\n slideDown(childElement, this._config.animationSpeed)\n }\n\n this._element.dispatchEvent(event)\n }\n\n close(): void {\n const event = new Event(EVENT_COLLAPSED)\n\n this._element.classList.remove(CLASS_NAME_MENU_OPEN)\n\n const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU) as HTMLElement | undefined\n if (childElement) {\n slideUp(childElement, this._config.animationSpeed)\n }\n\n this._element.dispatchEvent(event)\n }\n\n toggle(): void {\n if (this._element.classList.contains(CLASS_NAME_MENU_OPEN)) {\n this.close()\n } else {\n this.open()\n }\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\nonDOMContentLoaded(() => {\n const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE)\n\n button.forEach(btn => {\n btn.addEventListener('click', event => {\n const target = event.target as HTMLElement\n const targetItem = target.closest(SELECTOR_NAV_ITEM) as HTMLElement | undefined\n const targetLink = target.closest(SELECTOR_NAV_LINK) as HTMLAnchorElement | undefined\n\n if (target?.getAttribute('href') === '#' || targetLink?.getAttribute('href') === '#') {\n event.preventDefault()\n }\n\n if (targetItem) {\n const data = new Treeview(targetItem, Default)\n data.toggle()\n }\n })\n })\n})\n\nexport default Treeview\n","/**\n * --------------------------------------------\n * @file AdminLTE direct-chat.ts\n * @description Direct chat for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded\n} from './util/index'\n\n/**\n * Constants\n * ====================================================\n */\n\nconst DATA_KEY = 'lte.direct-chat'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_EXPANDED = `expanded${EVENT_KEY}`\nconst EVENT_COLLAPSED = `collapsed${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-lte-toggle=\"chat-pane\"]'\nconst SELECTOR_DIRECT_CHAT = '.direct-chat'\n\nconst CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open'\n\n/**\n * Class Definition\n * ====================================================\n */\n\nclass DirectChat {\n _element: HTMLElement\n constructor(element: HTMLElement) {\n this._element = element\n }\n\n toggle(): void {\n if (this._element.classList.contains(CLASS_NAME_DIRECT_CHAT_OPEN)) {\n const event = new Event(EVENT_COLLAPSED)\n\n this._element.classList.remove(CLASS_NAME_DIRECT_CHAT_OPEN)\n\n this._element.dispatchEvent(event)\n } else {\n const event = new Event(EVENT_EXPANDED)\n\n this._element.classList.add(CLASS_NAME_DIRECT_CHAT_OPEN)\n\n this._element.dispatchEvent(event)\n }\n }\n}\n\n/**\n *\n * Data Api implementation\n * ====================================================\n */\n\nonDOMContentLoaded(() => {\n const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE)\n\n button.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n const target = event.target as HTMLElement\n const chatPane = target.closest(SELECTOR_DIRECT_CHAT) as HTMLElement | undefined\n\n if (chatPane) {\n const data = new DirectChat(chatPane)\n data.toggle()\n }\n })\n })\n})\n\nexport default DirectChat\n","/**\n * --------------------------------------------\n * @file AdminLTE card-widget.ts\n * @description Card widget for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded,\n slideUp,\n slideDown\n} from './util/index'\n\n/**\n * Constants\n * ====================================================\n */\n\nconst DATA_KEY = 'lte.card-widget'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_COLLAPSED = `collapsed${EVENT_KEY}`\nconst EVENT_EXPANDED = `expanded${EVENT_KEY}`\nconst EVENT_REMOVE = `remove${EVENT_KEY}`\nconst EVENT_MAXIMIZED = `maximized${EVENT_KEY}`\nconst EVENT_MINIMIZED = `minimized${EVENT_KEY}`\n\nconst CLASS_NAME_CARD = 'card'\nconst CLASS_NAME_COLLAPSED = 'collapsed-card'\nconst CLASS_NAME_COLLAPSING = 'collapsing-card'\nconst CLASS_NAME_EXPANDING = 'expanding-card'\nconst CLASS_NAME_WAS_COLLAPSED = 'was-collapsed'\nconst CLASS_NAME_MAXIMIZED = 'maximized-card'\n\nconst SELECTOR_DATA_REMOVE = '[data-lte-toggle=\"card-remove\"]'\nconst SELECTOR_DATA_COLLAPSE = '[data-lte-toggle=\"card-collapse\"]'\nconst SELECTOR_DATA_MAXIMIZE = '[data-lte-toggle=\"card-maximize\"]'\nconst SELECTOR_CARD = `.${CLASS_NAME_CARD}`\nconst SELECTOR_CARD_BODY = '.card-body'\nconst SELECTOR_CARD_FOOTER = '.card-footer'\n\ntype Config = {\n animationSpeed: number;\n collapseTrigger: string;\n removeTrigger: string;\n maximizeTrigger: string;\n}\n\nconst Default: Config = {\n animationSpeed: 500,\n collapseTrigger: SELECTOR_DATA_COLLAPSE,\n removeTrigger: SELECTOR_DATA_REMOVE,\n maximizeTrigger: SELECTOR_DATA_MAXIMIZE\n}\n\nclass CardWidget {\n _element: HTMLElement\n _parent: HTMLElement | undefined\n _clone: HTMLElement | undefined\n _config: Config\n\n constructor(element: HTMLElement, config: Config) {\n this._element = element\n this._parent = element.closest(SELECTOR_CARD) as HTMLElement | undefined\n\n if (element.classList.contains(CLASS_NAME_CARD)) {\n this._parent = element\n }\n\n this._config = { ...Default, ...config }\n }\n\n collapse() {\n const event = new Event(EVENT_COLLAPSED)\n\n if (this._parent) {\n this._parent.classList.add(CLASS_NAME_COLLAPSING)\n\n const elm = this._parent?.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`)\n\n elm.forEach(el => {\n if (el instanceof HTMLElement) {\n slideUp(el, this._config.animationSpeed)\n }\n })\n\n setTimeout(() => {\n if (this._parent) {\n this._parent.classList.add(CLASS_NAME_COLLAPSED)\n this._parent.classList.remove(CLASS_NAME_COLLAPSING)\n }\n }, this._config.animationSpeed)\n }\n\n this._element?.dispatchEvent(event)\n }\n\n expand() {\n const event = new Event(EVENT_EXPANDED)\n\n if (this._parent) {\n this._parent.classList.add(CLASS_NAME_EXPANDING)\n\n const elm = this._parent?.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`)\n\n elm.forEach(el => {\n if (el instanceof HTMLElement) {\n slideDown(el, this._config.animationSpeed)\n }\n })\n\n setTimeout(() => {\n if (this._parent) {\n this._parent.classList.remove(CLASS_NAME_COLLAPSED)\n this._parent.classList.remove(CLASS_NAME_EXPANDING)\n }\n }, this._config.animationSpeed)\n }\n\n this._element?.dispatchEvent(event)\n }\n\n remove() {\n const event = new Event(EVENT_REMOVE)\n\n if (this._parent) {\n slideUp(this._parent, this._config.animationSpeed)\n }\n\n this._element?.dispatchEvent(event)\n }\n\n toggle() {\n if (this._parent?.classList.contains(CLASS_NAME_COLLAPSED)) {\n this.expand()\n return\n }\n\n this.collapse()\n }\n\n maximize() {\n const event = new Event(EVENT_MAXIMIZED)\n\n if (this._parent) {\n this._parent.style.height = `${this._parent.offsetHeight}px`\n this._parent.style.width = `${this._parent.offsetWidth}px`\n this._parent.style.transition = 'all .15s'\n\n setTimeout(() => {\n const htmlTag = document.querySelector('html')\n\n if (htmlTag) {\n htmlTag.classList.add(CLASS_NAME_MAXIMIZED)\n }\n\n if (this._parent) {\n this._parent.classList.add(CLASS_NAME_MAXIMIZED)\n\n if (this._parent.classList.contains(CLASS_NAME_COLLAPSED)) {\n this._parent.classList.add(CLASS_NAME_WAS_COLLAPSED)\n }\n }\n }, 150)\n }\n\n this._element?.dispatchEvent(event)\n }\n\n minimize() {\n const event = new Event(EVENT_MINIMIZED)\n\n if (this._parent) {\n this._parent.style.height = 'auto'\n this._parent.style.width = 'auto'\n this._parent.style.transition = 'all .15s'\n\n setTimeout(() => {\n const htmlTag = document.querySelector('html')\n\n if (htmlTag) {\n htmlTag.classList.remove(CLASS_NAME_MAXIMIZED)\n }\n\n if (this._parent) {\n this._parent.classList.remove(CLASS_NAME_MAXIMIZED)\n\n if (this._parent?.classList.contains(CLASS_NAME_WAS_COLLAPSED)) {\n this._parent.classList.remove(CLASS_NAME_WAS_COLLAPSED)\n }\n }\n }, 10)\n }\n\n this._element?.dispatchEvent(event)\n }\n\n toggleMaximize() {\n if (this._parent?.classList.contains(CLASS_NAME_MAXIMIZED)) {\n this.minimize()\n return\n }\n\n this.maximize()\n }\n}\n\n/**\n *\n * Data Api implementation\n * ====================================================\n */\n\nonDOMContentLoaded(() => {\n const collapseBtn = document.querySelectorAll(SELECTOR_DATA_COLLAPSE)\n\n collapseBtn.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n const target = event.target as HTMLElement\n const data = new CardWidget(target, Default)\n data.toggle()\n })\n })\n\n const removeBtn = document.querySelectorAll(SELECTOR_DATA_REMOVE)\n\n removeBtn.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n const target = event.target as HTMLElement\n const data = new CardWidget(target, Default)\n data.remove()\n })\n })\n\n const maxBtn = document.querySelectorAll(SELECTOR_DATA_MAXIMIZE)\n\n maxBtn.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n const target = event.target as HTMLElement\n const data = new CardWidget(target, Default)\n data.toggleMaximize()\n })\n })\n})\n\nexport default CardWidget\n","/**\n * --------------------------------------------\n * @file AdminLTE fullscreen.ts\n * @description Fullscreen plugin for AdminLTE.\n * @license MIT\n * --------------------------------------------\n */\n\nimport {\n onDOMContentLoaded\n} from './util/index'\n\n/**\n * Constants\n * ============================================================================\n */\nconst DATA_KEY = 'lte.fullscreen'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_MAXIMIZED = `maximized${EVENT_KEY}`\nconst EVENT_MINIMIZED = `minimized${EVENT_KEY}`\n\nconst SELECTOR_FULLSCREEN_TOGGLE = '[data-lte-toggle=\"fullscreen\"]'\nconst SELECTOR_MAXIMIZE_ICON = '[data-lte-icon=\"maximize\"]'\nconst SELECTOR_MINIMIZE_ICON = '[data-lte-icon=\"minimize\"]'\n\n/**\n * Class Definition.\n * ============================================================================\n */\nclass FullScreen {\n _element: HTMLElement\n _config: undefined\n\n constructor(element: HTMLElement, config?: undefined) {\n this._element = element\n this._config = config\n }\n\n inFullScreen(): void {\n const event = new Event(EVENT_MAXIMIZED)\n\n const iconMaximize = document.querySelector<HTMLElement>(SELECTOR_MAXIMIZE_ICON)\n const iconMinimize = document.querySelector<HTMLElement>(SELECTOR_MINIMIZE_ICON)\n\n void document.documentElement.requestFullscreen()\n\n if (iconMaximize) {\n iconMaximize.style.display = 'none'\n }\n\n if (iconMinimize) {\n iconMinimize.style.display = 'block'\n }\n\n this._element.dispatchEvent(event)\n }\n\n outFullscreen(): void {\n const event = new Event(EVENT_MINIMIZED)\n\n const iconMaximize = document.querySelector<HTMLElement>(SELECTOR_MAXIMIZE_ICON)\n const iconMinimize = document.querySelector<HTMLElement>(SELECTOR_MINIMIZE_ICON)\n\n void document.exitFullscreen()\n\n if (iconMaximize) {\n iconMaximize.style.display = 'block'\n }\n\n if (iconMinimize) {\n iconMinimize.style.display = 'none'\n }\n\n this._element.dispatchEvent(event)\n }\n\n toggleFullScreen(): void {\n if (document.fullscreenEnabled) {\n if (document.fullscreenElement) {\n this.outFullscreen()\n } else {\n this.inFullScreen()\n }\n }\n }\n}\n\n/**\n * Data Api implementation\n * ============================================================================\n */\nonDOMContentLoaded(() => {\n const buttons = document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE)\n\n buttons.forEach(btn => {\n btn.addEventListener('click', event => {\n event.preventDefault()\n\n const target = event.target as HTMLElement\n const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE) as HTMLElement | undefined\n\n if (button) {\n const data = new FullScreen(button, undefined)\n data.toggleFullScreen()\n }\n })\n })\n})\n\nexport default FullScreen\n"],"mappings":"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","ignoreList":[]}
 
 
templates/admin/dashboard.html DELETED
@@ -1,43 +0,0 @@
1
- <!-- templates/dashboard.html -->
2
-
3
- {% extends "main/base.html" %}
4
-
5
- {% block title %}Admin Dashboard{% endblock %}
6
-
7
- {% block content %}
8
- <!-- Dashboard Header -->
9
-
10
- <div class="row mb-4">
11
- <div class="col-sm-6 text-right"> <small>Last Updated: {{ last_updated }} </small> </div>
12
- </div> <!-- Real-Time Health Metrics and Inventory Summary -->
13
- <div class="row">
14
- <div class="col-md-6">
15
- <div class="card">
16
- <div class="card-header">
17
- <h3 class="card-title">Real-Time Health Metrics</h3>
18
- </div>
19
- <div class="card-body"> <canvas id="realTimeHealthChart"></canvas> </div>
20
- </div>
21
- </div>
22
- <div class="col-md-6">
23
- <div class="card">
24
- <div class="card-header">
25
- <h3 class="card-title">Inventory Status</h3>
26
- </div>
27
- <div class="card-body"> <canvas id="inventoryChart"></canvas> </div>
28
- </div>
29
- </div>
30
- </div> <!-- Historical Health Alerts -->
31
- <div class="row mt-4">
32
- <div class="col-md-12">
33
- <div class="card">
34
- <div class="card-header">
35
- <h3 class="card-title">Historical Health Alerts</h3>
36
- </div>
37
- <div class="card-body"> <canvas id="historicalAlertsChart"></canvas> </div>
38
- </div>
39
- </div>
40
- </div>
41
-
42
-
43
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin/group/add.html DELETED
@@ -1,30 +0,0 @@
1
- {% extends "main/base.html" %}
2
-
3
- {% block title %}Add New Group{% endblock %}
4
-
5
- {% block content %}
6
- <div class="row g-4">
7
- <div class="col-md-6">
8
- <div class="card card-primary">
9
- <div class="card-header">
10
- <h3 class="card-title">Create New Group</h3>
11
- </div>
12
- <form method="POST" action="/group/add">
13
- <div class="card-body">
14
- <div class="form-group">
15
- <label for="groupName">Group Name</label>
16
- <input type="text" class="form-control" id="groupName" name="name" required>
17
- </div>
18
- <div class="form-group">
19
- <label for="description">Description</label>
20
- <textarea class="form-control" id="description" name="description"></textarea>
21
- </div>
22
- </div>
23
- <div class="card-footer">
24
- <button type="submit" class="btn btn-secondary shadow">Create Group</button>
25
- </div>
26
- </form>
27
- </div>
28
- </div>
29
- </div>
30
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin/group/add_member.html DELETED
@@ -1,30 +0,0 @@
1
- {% extends "main/base.html" %}
2
-
3
- {% block title %}Add Member{% endblock %}
4
-
5
- {% block content %}
6
- <div class="row g-4">
7
- <div class="col-md-6">
8
- <div class="card card-success">
9
- <div class="card-header">
10
- <h3 class="card-title">Add Member to {{ group.name }}</h3>
11
- </div>
12
- <form method="POST" action="/group/{{ group._id }}/add_member">
13
- <div class="card-body">
14
- <div class="form-group">
15
- <label for="userId">Select Member</label>
16
- <select class="form-control" id="userId" name="user_id">
17
- {% for user in available_users %}
18
- <option value="{{ user.username }}">{{ user.username }}</option>
19
- {% endfor %}
20
- </select>
21
- </div>
22
- </div>
23
- <div class="card-footer">
24
- <button type="submit" class="btn btn-success">Add Member</button>
25
- </div>
26
- </form>
27
- </div>
28
- </div>
29
- </div>
30
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin/group/delete.html DELETED
@@ -1,25 +0,0 @@
1
- {% extends "main/base.html" %}
2
-
3
- {% block title %}Delete Group{% endblock %}
4
-
5
- {% block content %}
6
- <div class="row g-4">
7
- <div class="col-md-6">
8
- <div class="card card-danger">
9
- <div class="card-header">
10
- <h3 class="card-title">Delete Group</h3>
11
- </div>
12
- <div class="card-body">
13
- <p>Are you sure you want to delete the group <strong>{{ group.name }}</strong>?</p>
14
- <p>This action cannot be undone.</p>
15
- </div>
16
- <div class="card-footer">
17
- <form method="POST" action="/group/{{ group._id }}/delete" style="display:inline;">
18
- <button type="submit" class="btn btn-danger">Yes, Delete Group</button>
19
- </form>
20
- <a href="/group/list" class="btn btn-secondary">Cancel</a>
21
- </div>
22
- </div>
23
- </div>
24
- </div>
25
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin/group/list.html DELETED
@@ -1,45 +0,0 @@
1
- {% extends "main/base.html" %}
2
-
3
- {% block title %}Group List{% endblock %}
4
-
5
- {% block content %}
6
- <div class="row g-4">
7
- <div class="col-md-6">
8
- <div class="card">
9
- <div class="card-header">
10
- <h3 class="card-title">User Groups</h3>
11
- <div class="card-tools">
12
- <a href="/group/add" class="btn btn-secondary shadow btn-sm">Add New Group</a>
13
- </div>
14
- </div>
15
- <div class="card-body">
16
- <table class="table table-bordered table-hover">
17
- <thead>
18
- <tr>
19
- <th>Group Name</th>
20
- <th>Description</th>
21
- <th>Created By</th>
22
- <th>Members</th>
23
- <th>Actions</th>
24
- </tr>
25
- </thead>
26
- <tbody>
27
- {% for group in groups %}
28
- <tr>
29
- <td>{{ group.name }}</td>
30
- <td>{{ group.description }}</td>
31
- <td>{{ group.created_by }}</td>
32
- <td>{{ group.members | length }}</td>
33
- <td>
34
- <a href="/group/{{ group._id }}/view" class="btn btn-info btn-sm">View</a>
35
- <a href="/group/{{ group._id }}/delete" class="btn btn-danger btn-sm">Delete</a>
36
- </td>
37
- </tr>
38
- {% endfor %}
39
- </tbody>
40
- </table>
41
- </div>
42
- </div>
43
- </div>
44
- </div>
45
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin/group/share_task.html DELETED
@@ -1,30 +0,0 @@
1
- {% extends "main/base.html" %}
2
-
3
- {% block title %}Share Task with Group{% endblock %}
4
-
5
- {% block content %}
6
- <div class="row g-4">
7
- <div class="col-md-6">
8
- <div class="card card-info">
9
- <div class="card-header">
10
- <h3 class="card-title">Share Task with {{ group.name }}</h3>
11
- </div>
12
- <form method="POST" action="/group/{{ group._id }}/share_task">
13
- <div class="card-body">
14
- <div class="form-group">
15
- <label for="taskId">Select Task</label>
16
- <select class="form-control" id="taskId" name="task_id">
17
- {% for task in available_tasks %}
18
- <option value="{{ task._id }}">{{ task.title }}</option>
19
- {% endfor %}
20
- </select>
21
- </div>
22
- </div>
23
- <div class="card-footer">
24
- <button type="submit" class="btn btn-info">Share Task</button>
25
- </div>
26
- </form>
27
- </div>
28
- </div>
29
- </div>
30
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin/group/view.html DELETED
@@ -1,52 +0,0 @@
1
- {% extends "main/base.html" %}
2
-
3
- {% block title %}Group Details{% endblock %}
4
-
5
- {% block content %}
6
- <div class="row g-4">
7
- <div class="col-md-6">
8
- <div class="card">
9
- <div class="card-header">
10
- <h3 class="card-title">{{ group.name }}</h3>
11
- <div class="card-tools">
12
- <a href="/group/{{ group._id }}/add_member" class="btn btn-success btn-sm">Add Member</a>
13
- <a href="/group/{{ group._id }}/share_task" class="btn btn-info btn-sm">Share Task</a>
14
- </div>
15
- </div>
16
- <div class="card-body">
17
- <h5>Description</h5>
18
- <p>{{ group.description }}</p>
19
-
20
- <h5>Members</h5>
21
- <ul>
22
- {% for member in group.members %}
23
- <li>{{ member }}</li>
24
- {% endfor %}
25
- </ul>
26
-
27
- <h5>Shared Tasks</h5>
28
- <table class="table table-bordered table-hover">
29
- <thead>
30
- <tr>
31
- <th>Task Title</th>
32
- <th>Description</th>
33
- <th>Due Date</th>
34
- <th>Status</th>
35
- </tr>
36
- </thead>
37
- <tbody>
38
- {% for task in tasks %}
39
- <tr>
40
- <td>{{ task.title }}</td>
41
- <td>{{ task.description }}</td>
42
- <td>{{ task.due_date }}</td>
43
- <td>{{ 'Completed' if task.is_completed else 'Pending' }}</td>
44
- </tr>
45
- {% endfor %}
46
- </tbody>
47
- </table>
48
- </div>
49
- </div>
50
- </div>
51
- </div>
52
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin/inventory/add.html DELETED
@@ -1,45 +0,0 @@
1
- {% extends "main/base.html" %}
2
-
3
- {% block title %}Add Inventory Item{% endblock %}
4
-
5
- {% block content %}
6
- <div class="row mb-4">
7
- <div class="col-sm-6 text-right"> <small>Last Updated: {{ last_updated }} </small> </div>
8
- </div>
9
- <div class="row g-4">
10
- <div class="col-md-6">
11
- <div class="card card-secondary shadow">
12
- <div class="card-header">
13
- <div class="card-title">Add New Inventory Item</div>
14
- </div>
15
- <form method="POST" action="/inventory/add">
16
- <div class="card-body">
17
- <div class="form-group">
18
- <label for="itemName">Item Name</label>
19
- <input type="text" class="form-control" id="itemName" name="item_name" required>
20
- </div>
21
- <div class="form-group">
22
- <label for="category">Category</label>
23
- <input type="text" class="form-control" id="category" name="category" required>
24
- </div>
25
- <div class="form-group">
26
- <label for="quantity">Quantity</label>
27
- <input type="number" class="form-control" id="quantity" name="quantity" required>
28
- </div>
29
- <div class="form-group">
30
- <label for="restockLevel">Restock Level</label>
31
- <input type="number" class="form-control" id="restockLevel" name="restock_level" required>
32
- </div>
33
- <div class="form-group">
34
- <label for="supplier">Supplier</label>
35
- <input type="text" class="form-control" id="supplier" name="supplier">
36
- </div>
37
- </div>
38
- <div class="card-footer">
39
- <button type="submit" class="btn btn-secondary shadow">Add Item</button>
40
- </div>
41
- </form>
42
- </div>
43
- </div>
44
- </div>
45
- {% endblock %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin/inventory/delete.html DELETED
@@ -1,31 +0,0 @@
1
- {% extends "main/base.html" %}
2
-
3
- {% block title %}Delete Inventory Item{% endblock %}
4
-
5
- {% block content %}
6
- <div class="row g-4">
7
- <div class="col-md-6">
8
- <div class="card card-danger">
9
- <div class="card-header">
10
- <h3 class="card-title">Delete Inventory Item</h3>
11
- </div>
12
- <div class="card-body">
13
- <p>Are you sure you want to delete the following item?</p>
14
- <ul>
15
- <li><strong>Item Name:</strong> {{ item.item_name }}</li>
16
- <li><strong>Category:</strong> {{ item.category }}</li>
17
- <li><strong>Quantity:</strong> {{ item.quantity }}</li>
18
- <li><strong>Restock Level:</strong> {{ item.restock_level }}</li>
19
- <li><strong>Supplier:</strong> {{ item.supplier }}</li>
20
- </ul>
21
- </div>
22
- <div class="card-footer">
23
- <form method="POST" action="/admin/inventory/delete/{{ item._id }}" style="display:inline;">
24
- <button type="submit" class="btn btn-danger">Yes, Delete</button>
25
- </form>
26
- <a href="admin/inventory" class="btn btn-secondary">Cancel</a>
27
- </div>
28
- </div>
29
- </div>
30
- </div>
31
- {% endblock %}