Spaces:
Running
Running
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +318 -38
src/streamlit_app.py
CHANGED
@@ -1,40 +1,320 @@
|
|
1 |
-
import altair as alt
|
2 |
-
import numpy as np
|
3 |
-
import pandas as pd
|
4 |
import streamlit as st
|
|
|
|
|
5 |
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
"
|
29 |
-
|
30 |
-
|
31 |
-
}
|
32 |
-
|
33 |
-
|
34 |
-
.
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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")
|