Testys commited on
Commit
1a8e744
·
1 Parent(s): 2847bfd

Pushing to see if Streamlit works well

Browse files
Dockerfile CHANGED
@@ -10,4 +10,7 @@ COPY --chown=user ./requirements.txt requirements.txt
10
  RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
 
12
  COPY --chown=user . /app
13
- CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
10
  RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
 
12
  COPY --chown=user . /app
13
+
14
+ EXPOSE 7860 8000
15
+
16
+ CMD ["./start.sh"]
README.md CHANGED
@@ -1,11 +1,35 @@
1
  ---
2
- title: Clearance Sys
3
- emoji: 🏢
4
- colorFrom: purple
5
  colorTo: green
6
  sdk: docker
 
 
7
  pinned: false
8
  license: mit
9
  ---
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Clearance System
3
+ emoji:
4
+ colorFrom: blue
5
  colorTo: green
6
  sdk: docker
7
+ sdk_version: python3.11-slim
8
+ app_file: streamlit_app.py
9
  pinned: false
10
  license: mit
11
  ---
12
 
13
+ # Clearance System
14
+
15
+ A full-stack application for managing student clearance with RFID integration.
16
+
17
+ ## Features
18
+ - Student and user management
19
+ - RFID tag linking and status checking
20
+ - Admin scanner activation and retrieval
21
+ - Streamlit frontend for easy interaction
22
+
23
+ ## Deployment
24
+ This app is deployed on Hugging Face Spaces using Docker.
25
+
26
+ - Frontend (Streamlit): Accessible at the Space URL
27
+ - Backend (FastAPI): Runs internally on port 8000
28
+
29
+ ## Usage
30
+ 1. Login with your credentials.
31
+ 2. Navigate through the sidebar to manage students, users, tags, devices, etc.
32
+ 3. Use the RFID section to check tag statuses.
33
+ 4. Activate scanners for secure scanning workflows.
34
+
35
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
src/models.py CHANGED
@@ -164,6 +164,7 @@ class RFIDStatusResponse(SQLModel):
164
 
165
  class TagScan(SQLModel):
166
  tag_id: str
 
167
 
168
  # Device Models
169
  class DeviceCreate(SQLModel):
 
164
 
165
  class TagScan(SQLModel):
166
  tag_id: str
167
+ api_key: str
168
 
169
  # Device Models
170
  class DeviceCreate(SQLModel):
src/routers/admin.py CHANGED
@@ -14,16 +14,6 @@ from src.crud import students as student_crud
14
  from src.crud import tag_linking as tag_crud
15
  from src.crud import devices as device_crud
16
 
17
- # --- New State Management for Secure Admin Scanning ---
18
-
19
- # Maps a device's API key to the admin user ID who activated it.
20
- # This "activates" a scanner for a specific admin.
21
- activated_scanners: Dict[str, int] = {}
22
-
23
- # Stores the last tag scanned by a device, keyed by the admin ID who was waiting.
24
- admin_scanned_tags: Dict[int, str] = {}
25
-
26
-
27
  # Define the main administrative router
28
  router = APIRouter(
29
  prefix="/admin",
@@ -31,64 +21,8 @@ router = APIRouter(
31
  dependencies=[Depends(get_current_active_user(required_roles=[Role.ADMIN, Role.STAFF]))],
32
  )
33
 
34
- # --- New Secure Scanning Workflow ---
35
-
36
- class ActivationRequest(SQLModel):
37
- device_id: int
38
-
39
- @router.post("/scanners/activate", status_code=status.HTTP_204_NO_CONTENT)
40
- def activate_admin_scanner(
41
- activation: ActivationRequest,
42
- db: Session = Depends(get_session),
43
- current_user: User = Depends(get_current_active_user(required_roles=[Role.ADMIN, Role.STAFF]))
44
- ):
45
- """
46
- STEP 1 (Browser): Admin clicks "Scan Card" in the UI.
47
- The browser calls this endpoint to 'arm' their designated desk scanner.
48
- """
49
- device = device_crud.get_device_by_id(db, device_id=activation.device_id)
50
- if not device:
51
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Device not found.")
52
-
53
- # Map the device's API key to the currently logged-in admin's ID.
54
- activated_scanners[device.api_key] = current_user.id
55
- return
56
-
57
-
58
- @router.post("/scanners/scan", status_code=status.HTTP_204_NO_CONTENT)
59
- def receive_scan_from_activated_device(
60
- scan_data: TagScan,
61
- api_key: str = Depends(get_api_key) # Device authenticates with its API Key
62
- ):
63
- """
64
- STEP 2 (Device): The ESP32 device sends the scanned tag to this endpoint.
65
- This endpoint is protected by the device's API Key.
66
- """
67
- # Check if this device was activated by an admin.
68
- admin_id = activated_scanners.pop(api_key, None)
69
- if admin_id is None:
70
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="This scanner has not been activated for a scan.")
71
-
72
- # Store the scanned tag against the admin who was waiting for it.
73
- admin_scanned_tags[admin_id] = scan_data.tag_id
74
- return
75
-
76
- @router.get("/scanners/retrieve", response_model=TagScan)
77
- def retrieve_scanned_tag_for_ui(
78
- current_user: User = Depends(get_current_active_user(required_roles=[Role.ADMIN, Role.STAFF]))
79
- ):
80
- """
81
- STEP 3 (Browser): The browser polls this endpoint to get the tag ID
82
- that the device reported in STEP 2.
83
- """
84
- tag_id = admin_scanned_tags.pop(current_user.id, None)
85
- if not tag_id:
86
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No tag has been scanned by the activated device yet.")
87
- return TagScan(tag_id=tag_id)
88
-
89
-
90
  # --- All other administrative endpoints remain the same ---
91
- # ... (Student Management, User Management, etc.) ...
92
  @router.post("/students/", response_model=StudentReadWithClearance, status_code=status.HTTP_201_CREATED)
93
  def create_student(student: StudentCreate, db: Session = Depends(get_session)):
94
  """(Admin & Staff) Creates a new student and initializes their clearance status."""
 
14
  from src.crud import tag_linking as tag_crud
15
  from src.crud import devices as device_crud
16
 
 
 
 
 
 
 
 
 
 
 
17
  # Define the main administrative router
18
  router = APIRouter(
19
  prefix="/admin",
 
21
  dependencies=[Depends(get_current_active_user(required_roles=[Role.ADMIN, Role.STAFF]))],
22
  )
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  # --- All other administrative endpoints remain the same ---
25
+ # ... (Stuent Management, User Management, etc.) ...
26
  @router.post("/students/", response_model=StudentReadWithClearance, status_code=status.HTTP_201_CREATED)
27
  def create_student(student: StudentCreate, db: Session = Depends(get_session)):
28
  """(Admin & Staff) Creates a new student and initializes their clearance status."""
src/routers/devices.py CHANGED
@@ -66,3 +66,4 @@ def delete_device(
66
  detail="Device not found."
67
  )
68
  return db_device
 
 
66
  detail="Device not found."
67
  )
68
  return db_device
69
+
src/routers/rfid.py CHANGED
@@ -1,17 +1,85 @@
1
  from fastapi import APIRouter, Depends, HTTPException, status, Security
2
  from fastapi.security import APIKeyHeader
3
- from sqlmodel import Session
 
4
 
5
  from src.database import get_session
6
- from src.auth import get_api_key
7
- from src.models import RFIDStatusResponse, RFIDScanRequest, ClearanceStatusEnum
 
 
 
8
  from src.crud import students as student_crud
9
  from src.crud import users as user_crud
 
10
 
11
  # Define the router and the API key security scheme
12
  router = APIRouter(prefix="/rfid", tags=["RFID"])
13
  api_key_header = APIKeyHeader(name="x-api-key", auto_error=False)
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  @router.post("/check-status", response_model=RFIDStatusResponse)
16
  def check_rfid_status(
17
  scan_data: RFIDScanRequest,
 
1
  from fastapi import APIRouter, Depends, HTTPException, status, Security
2
  from fastapi.security import APIKeyHeader
3
+ from sqlmodel import Session, SQLModel
4
+ from typing import Dict
5
 
6
  from src.database import get_session
7
+ from src.auth import get_current_active_user, get_api_key
8
+ from src.models import (
9
+ RFIDStatusResponse, RFIDScanRequest, ClearanceStatusEnum,
10
+ TagScan, Device, Role, User
11
+ )
12
  from src.crud import students as student_crud
13
  from src.crud import users as user_crud
14
+ from src.crud import devices as device_crud
15
 
16
  # Define the router and the API key security scheme
17
  router = APIRouter(prefix="/rfid", tags=["RFID"])
18
  api_key_header = APIKeyHeader(name="x-api-key", auto_error=False)
19
 
20
+ # --- Moved from admin.py: State for secure admin scanning ---
21
+ # Maps a device's API key to the admin user ID who activated it (changed to str key for api_key).
22
+ activated_scanners: Dict[str, int] = {}
23
+
24
+ # Stores the last tag scanned by a device, keyed by the admin ID who was waiting.
25
+ admin_scanned_tags: Dict[int, tuple[str, str]] = {}
26
+
27
+ # --- Moved from admin.py: Secure Scanning Workflow ---
28
+ class ActivationRequest(SQLModel):
29
+ api_key: str
30
+
31
+ @router.post("/scanners/activate", status_code=status.HTTP_204_NO_CONTENT)
32
+ def activate_admin_scanner(
33
+ activation: ActivationRequest,
34
+ db: Session = Depends(get_session),
35
+ current_user: User = Depends(get_current_active_user(required_roles=[Role.ADMIN, Role.STAFF]))
36
+ ):
37
+ """
38
+ STEP 1 (Browser): Admin clicks "Scan Card" in the UI.
39
+ The browser calls this endpoint to 'arm' their designated desk scanner.
40
+ """
41
+ device = device_crud.get_device_by_api_key(db, api_key=activation.api_key)
42
+ if not device:
43
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Device not found.")
44
+
45
+ # Map the device's API key to the currently logged-in admin's ID.
46
+ activated_scanners[device.api_key] = current_user.id
47
+ return
48
+
49
+ @router.post("/scanners/scan", status_code=status.HTTP_204_NO_CONTENT)
50
+ def receive_scan_from_activated_device(
51
+ scan_data: TagScan,
52
+ # Removed api_key dependency for "free use" (easier RFID data submission).
53
+ # Assumes api_key is in scan_data for association.
54
+ ):
55
+ """
56
+ STEP 2 (Device): The ESP32 device sends the scanned tag to this endpoint.
57
+ No authentication required for free use.
58
+ """
59
+ # Check if this device was activated by an admin (using api_key from scan_data).
60
+ admin_id = activated_scanners.pop(scan_data.api_key, None)
61
+ if admin_id is None:
62
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="This scanner has not been activated for a scan.")
63
+
64
+ # Store the scanned tag against the admin who was waiting for it.
65
+ admin_scanned_tags[admin_id] = (scan_data.tag_id, scan_data.api_key)
66
+ return
67
+
68
+ @router.get("/scanners/retrieve", response_model=TagScan)
69
+ def retrieve_scanned_tag_for_ui(
70
+ current_user: User = Depends(get_current_active_user(required_roles=[Role.ADMIN, Role.STAFF]))
71
+ ):
72
+ """
73
+ STEP 3 (Browser): The browser polls this endpoint to get the tag ID
74
+ that the device reported in STEP 2.
75
+ """
76
+ tag_data = admin_scanned_tags.pop(current_user.id, None)
77
+ if not tag_data:
78
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No tag has been scanned by the activated device yet.")
79
+ tag_id, api_key = tag_data
80
+ return TagScan(tag_id=tag_id, api_key=api_key)
81
+
82
+ # --- Existing /check-status endpoint remains ---
83
  @router.post("/check-status", response_model=RFIDStatusResponse)
84
  def check_rfid_status(
85
  scan_data: RFIDScanRequest,
start.sh ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Start FastAPI in background
3
+ uvicorn main:app --host 0.0.0.0 --port 8000 &
4
+ # Start Streamlit on port 7860
5
+ streamlit run streamlit_app.py --server.port 7860 --server.address 0.0.0.0
streamlit_app.py ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ import json
4
+
5
+ # Backend URL
6
+ BASE_URL = "https://testys-clearance-sys.hf.space"
7
+
8
+ # Session state for auth
9
+ if 'token' not in st.session_state:
10
+ st.session_state.token = None
11
+ if 'user' not in st.session_state:
12
+ st.session_state.user = None
13
+
14
+ def login(username, password):
15
+ response = requests.post(f"{BASE_URL}/token", data={"username": username, "password": password})
16
+ if response.status_code == 200:
17
+ data = response.json()
18
+ st.session_state.token = data['access_token']
19
+ st.session_state.user = username
20
+ st.success("Logged in successfully!")
21
+ else:
22
+ st.error("Login failed!")
23
+
24
+ def logout():
25
+ st.session_state.token = None
26
+ st.session_state.user = None
27
+ st.success("Logged out!")
28
+
29
+ def api_call(method, endpoint, data=None, headers=None):
30
+ url = f"{BASE_URL}{endpoint}"
31
+ if headers is None:
32
+ headers = {}
33
+ if st.session_state.token:
34
+ headers['Authorization'] = f"Bearer {st.session_state.token}"
35
+ if method == 'GET':
36
+ response = requests.get(url, headers=headers)
37
+ elif method == 'POST':
38
+ response = requests.post(url, json=data, headers=headers)
39
+ elif method == 'PUT':
40
+ response = requests.put(url, json=data, headers=headers)
41
+ elif method == 'DELETE':
42
+ response = requests.delete(url, headers=headers)
43
+ return response
44
+
45
+ st.title("Clearance System Frontend")
46
+
47
+ if st.session_state.token is None:
48
+ st.header("Login")
49
+ username = st.text_input("Username")
50
+ password = st.text_input("Password", type="password")
51
+ if st.button("Login"):
52
+ login(username, password)
53
+ else:
54
+ st.write(f"Logged in as: {st.session_state.user}")
55
+ if st.button("Logout"):
56
+ logout()
57
+
58
+ # Navigation
59
+ page = st.sidebar.selectbox("Select Page", [
60
+ "Students", "Users", "Tags", "Devices", "Clearance", "RFID", "Scanners", "Profile"
61
+ ])
62
+
63
+ if page == "Students":
64
+ st.header("Student Management")
65
+ action = st.selectbox("Action", ["List", "Create", "Lookup", "Read Single", "Update", "Delete"])
66
+ if action == "List":
67
+ skip = st.number_input("Skip", min_value=0, value=0)
68
+ limit = st.number_input("Limit", min_value=1, value=100)
69
+ if st.button("Get Students"):
70
+ response = api_call('GET', f'/admin/students/?skip={skip}&limit={limit}')
71
+ if response.status_code == 200:
72
+ students = response.json()
73
+ st.json(students)
74
+ else:
75
+ st.error("Failed to fetch students")
76
+ elif action == "Create":
77
+ full_name = st.text_input("Full Name")
78
+ matric_no = st.text_input("Matric No")
79
+ email = st.text_input("Email")
80
+ department = st.selectbox("Department", ["Computer Science", "Engineering", "Business Administration", "Law", "Medicine"])
81
+ password = st.text_input("Password", type="password")
82
+ if st.button("Create Student"):
83
+ data = {"full_name": full_name, "matric_no": matric_no, "email": email, "department": department, "password": password}
84
+ response = api_call('POST', '/admin/students/', data)
85
+ if response.status_code == 201:
86
+ st.success("Student created!")
87
+ st.json(response.json())
88
+ else:
89
+ st.error("Failed to create student")
90
+ elif action == "Lookup":
91
+ lookup_type = st.selectbox("Lookup by", ["Matric No", "Tag ID"])
92
+ if lookup_type == "Matric No":
93
+ matric_no = st.text_input("Matric No")
94
+ if st.button("Lookup"):
95
+ response = api_call('GET', f'/admin/students/lookup?matric_no={matric_no}')
96
+ if response.status_code == 200:
97
+ st.json(response.json())
98
+ else:
99
+ st.error("Student not found")
100
+ else:
101
+ tag_id = st.text_input("Tag ID")
102
+ if st.button("Lookup"):
103
+ response = api_call('GET', f'/admin/students/lookup?tag_id={tag_id}')
104
+ if response.status_code == 200:
105
+ st.json(response.json())
106
+ else:
107
+ st.error("Student not found")
108
+ elif action == "Read Single":
109
+ student_id = st.number_input("Student ID", min_value=1)
110
+ if st.button("Read"):
111
+ response = api_call('GET', f'/admin/students/{student_id}')
112
+ if response.status_code == 200:
113
+ st.json(response.json())
114
+ else:
115
+ st.error("Student not found")
116
+ elif action == "Update":
117
+ student_id = st.number_input("Student ID", min_value=1)
118
+ full_name = st.text_input("Full Name")
119
+ department = st.selectbox("Department", ["Computer Science", "Engineering", "Business Administration", "Law", "Medicine"])
120
+ email = st.text_input("Email")
121
+ if st.button("Update"):
122
+ data = {"full_name": full_name, "department": department, "email": email}
123
+ response = api_call('PUT', f'/admin/students/{student_id}', data)
124
+ if response.status_code == 200:
125
+ st.success("Student updated!")
126
+ st.json(response.json())
127
+ else:
128
+ st.error("Failed to update student")
129
+ elif action == "Delete":
130
+ student_id = st.number_input("Student ID", min_value=1)
131
+ if st.button("Delete"):
132
+ response = api_call('DELETE', f'/admin/students/{student_id}')
133
+ if response.status_code == 200:
134
+ st.success("Student deleted!")
135
+ st.json(response.json())
136
+ else:
137
+ st.error("Failed to delete student")
138
+
139
+ elif page == "Users":
140
+ st.header("User Management")
141
+ action = st.selectbox("Action", ["Create", "List", "Lookup", "Update", "Delete"])
142
+ if action == "Create":
143
+ username = st.text_input("Username")
144
+ password = st.text_input("Password", type="password")
145
+ email = st.text_input("Email")
146
+ full_name = st.text_input("Full Name")
147
+ role = st.selectbox("Role", ["admin", "staff"])
148
+ department = st.selectbox("Department", ["Computer Science", "Engineering", "Business Administration", "Law", "Medicine"])
149
+ if st.button("Create User"):
150
+ data = {"username": username, "password": password, "email": email, "full_name": full_name, "role": role, "department": department}
151
+ response = api_call('POST', '/admin/users/', data)
152
+ if response.status_code == 201:
153
+ st.success("User created!")
154
+ st.json(response.json())
155
+ else:
156
+ st.error("Failed to create user")
157
+ elif action == "List":
158
+ if st.button("Get Users"):
159
+ response = api_call('GET', '/admin/users/')
160
+ if response.status_code == 200:
161
+ users = response.json()
162
+ st.json(users)
163
+ else:
164
+ st.error("Failed to fetch users")
165
+ elif action == "Lookup":
166
+ lookup_type = st.selectbox("Lookup by", ["Username", "Tag ID"])
167
+ if lookup_type == "Username":
168
+ username = st.text_input("Username")
169
+ if st.button("Lookup"):
170
+ response = api_call('GET', f'/admin/users/lookup?username={username}')
171
+ if response.status_code == 200:
172
+ st.json(response.json())
173
+ else:
174
+ st.error("User not found")
175
+ else:
176
+ tag_id = st.text_input("Tag ID")
177
+ if st.button("Lookup"):
178
+ response = api_call('GET', f'/admin/users/lookup?tag_id={tag_id}')
179
+ if response.status_code == 200:
180
+ st.json(response.json())
181
+ else:
182
+ st.error("User not found")
183
+ elif action == "Update":
184
+ user_id = st.number_input("User ID", min_value=1)
185
+ username = st.text_input("Username")
186
+ email = st.text_input("Email")
187
+ full_name = st.text_input("Full Name")
188
+ role = st.selectbox("Role", ["admin", "staff"])
189
+ department = st.selectbox("Department", ["Computer Science", "Engineering", "Business Administration", "Law", "Medicine"])
190
+ password = st.text_input("Password", type="password")
191
+ if st.button("Update"):
192
+ data = {"username": username, "email": email, "full_name": full_name, "role": role, "department": department, "password": password}
193
+ response = api_call('PUT', f'/admin/users/{user_id}', data)
194
+ if response.status_code == 200:
195
+ st.success("User updated!")
196
+ st.json(response.json())
197
+ else:
198
+ st.error("Failed to update user")
199
+ elif action == "Delete":
200
+ user_id = st.number_input("User ID", min_value=1)
201
+ if st.button("Delete"):
202
+ response = api_call('DELETE', f'/admin/users/{user_id}')
203
+ if response.status_code == 200:
204
+ st.success("User deleted!")
205
+ st.json(response.json())
206
+ else:
207
+ st.error("Failed to delete user")
208
+
209
+ elif page == "Tags":
210
+ st.header("Tag Management")
211
+ action = st.selectbox("Action", ["Link", "Unlink"])
212
+ if action == "Link":
213
+ tag_id = st.text_input("Tag ID")
214
+ matric_no = st.text_input("Matric No")
215
+ username = st.text_input("Username")
216
+ if st.button("Link Tag"):
217
+ data = {"tag_id": tag_id, "matric_no": matric_no, "username": username}
218
+ response = api_call('POST', '/admin/tags/link', data)
219
+ if response.status_code == 200:
220
+ st.success("Tag linked!")
221
+ st.json(response.json())
222
+ else:
223
+ st.error("Failed to link tag")
224
+ elif action == "Unlink":
225
+ tag_id = st.text_input("Tag ID")
226
+ if st.button("Unlink Tag"):
227
+ response = api_call('DELETE', f'/admin/tags/{tag_id}/unlink')
228
+ if response.status_code == 200:
229
+ st.success("Tag unlinked!")
230
+ st.json(response.json())
231
+ else:
232
+ st.error("Failed to unlink tag")
233
+
234
+ elif page == "Devices":
235
+ st.header("Device Management")
236
+ action = st.selectbox("Action", ["Create", "List", "Delete"])
237
+ if action == "Create":
238
+ device_name = st.text_input("Device Name")
239
+ location = st.text_input("Location")
240
+ department = st.selectbox("Department", ["Computer Science", "Engineering", "Business Administration", "Law", "Medicine"])
241
+ if st.button("Create Device"):
242
+ data = {"device_name": device_name, "location": location, "department": department}
243
+ response = api_call('POST', '/admin/devices/', data)
244
+ if response.status_code == 201:
245
+ st.success("Device created!")
246
+ st.json(response.json())
247
+ else:
248
+ st.error("Failed to create device")
249
+ elif action == "List":
250
+ if st.button("Get Devices"):
251
+ response = api_call('GET', '/admin/devices/')
252
+ if response.status_code == 200:
253
+ devices = response.json()
254
+ st.json(devices)
255
+ else:
256
+ st.error("Failed to fetch devices")
257
+ elif action == "Delete":
258
+ device_id = st.number_input("Device ID", min_value=1)
259
+ if st.button("Delete"):
260
+ response = api_call('DELETE', f'/admin/devices/{device_id}')
261
+ if response.status_code == 200:
262
+ st.success("Device deleted!")
263
+ st.json(response.json())
264
+ else:
265
+ st.error("Failed to delete device")
266
+
267
+ elif page == "Clearance":
268
+ st.header("Clearance Management")
269
+ matric_no = st.text_input("Matric No")
270
+ department = st.selectbox("Department", ["Library", "Student Affairs", "Bursary", "Academic Affairs", "Health Center"])
271
+ status = st.selectbox("Status", ["pending", "approved", "rejected"])
272
+ remarks = st.text_area("Remarks")
273
+ if st.button("Update Clearance"):
274
+ data = {"matric_no": matric_no, "department": department, "status": status, "remarks": remarks}
275
+ response = api_call('PUT', '/clearance/update', data)
276
+ if response.status_code == 200:
277
+ st.success("Clearance updated!")
278
+ st.json(response.json())
279
+ else:
280
+ st.error("Failed to update clearance")
281
+
282
+ elif page == "RFID":
283
+ st.header("RFID Check Status")
284
+ tag_id = st.text_input("Tag ID")
285
+ if st.button("Check Status"):
286
+ data = {"tag_id": tag_id}
287
+ response = api_call('POST', '/rfid/check-status', data)
288
+ if response.status_code == 200:
289
+ st.json(response.json())
290
+ else:
291
+ st.error("Failed to check status")
292
+
293
+ elif page == "Scanners":
294
+ st.header("Scanner Management")
295
+ action = st.selectbox("Action", ["Activate", "Retrieve"])
296
+ if action == "Activate":
297
+ api_key = st.text_input("Device API Key")
298
+ if st.button("Activate Scanner"):
299
+ data = {"api_key": api_key}
300
+ response = api_call('POST', '/rfid/scanners/activate', data)
301
+ if response.status_code == 204:
302
+ st.success("Scanner activated!")
303
+ else:
304
+ st.error("Failed to activate")
305
+ elif action == "Retrieve":
306
+ if st.button("Retrieve Tag"):
307
+ response = api_call('GET', '/rfid/scanners/retrieve')
308
+ if response.status_code == 200:
309
+ st.json(response.json())
310
+ else:
311
+ st.error("No tag scanned yet")
312
+
313
+ elif page == "Profile":
314
+ st.header("My Profile")
315
+ if st.button("Get My Profile"):
316
+ response = api_call('GET', '/users/me')
317
+ if response.status_code == 200:
318
+ st.json(response.json())
319
+ else:
320
+ st.error("Failed to fetch profile")