Spaces:
Running
Running
Add login required
Browse files- app.py +192 -3
- templates/admin.html +97 -0
- templates/login.html +29 -0
- templates/map.html +16 -0
- templates/register.html +33 -0
- users.csv +3 -0
app.py
CHANGED
@@ -1,17 +1,191 @@
|
|
1 |
-
from fastapi import FastAPI, HTTPException, Request, Form
|
2 |
-
from fastapi.responses import HTMLResponse, FileResponse
|
3 |
from fastapi.templating import Jinja2Templates
|
|
|
|
|
4 |
from datetime import datetime, timedelta
|
5 |
import random
|
6 |
import folium
|
7 |
from folium.plugins import MarkerCluster
|
8 |
import csv
|
9 |
import os
|
|
|
|
|
10 |
|
11 |
app = FastAPI()
|
12 |
templates = Jinja2Templates(directory="templates")
|
13 |
|
|
|
14 |
CSV_FILE = "wifi_signals.csv"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
@app.post("/api/generate-data")
|
17 |
def generate_data():
|
@@ -81,6 +255,10 @@ async def upload_data(
|
|
81 |
|
82 |
@app.get("/", response_class=HTMLResponse)
|
83 |
def show_map(request: Request, start_date: str = None, end_date: str = None):
|
|
|
|
|
|
|
|
|
84 |
signal_data = []
|
85 |
|
86 |
if os.path.exists(CSV_FILE):
|
@@ -122,8 +300,19 @@ def show_map(request: Request, start_date: str = None, end_date: str = None):
|
|
122 |
).add_to(marker_cluster)
|
123 |
|
124 |
map_html = m._repr_html_()
|
125 |
-
return templates.TemplateResponse("map.html", {
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
|
|
|
|
|
|
|
|
|
|
|
127 |
|
128 |
@app.get("/download-csv")
|
129 |
async def download_csv():
|
|
|
1 |
+
from fastapi import FastAPI, HTTPException, Request, Form, Depends, status
|
2 |
+
from fastapi.responses import HTMLResponse, FileResponse, RedirectResponse, JSONResponse
|
3 |
from fastapi.templating import Jinja2Templates
|
4 |
+
from fastapi.staticfiles import StaticFiles
|
5 |
+
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
6 |
from datetime import datetime, timedelta
|
7 |
import random
|
8 |
import folium
|
9 |
from folium.plugins import MarkerCluster
|
10 |
import csv
|
11 |
import os
|
12 |
+
from pydantic import BaseModel
|
13 |
+
from typing import List, Optional
|
14 |
|
15 |
app = FastAPI()
|
16 |
templates = Jinja2Templates(directory="templates")
|
17 |
|
18 |
+
|
19 |
CSV_FILE = "wifi_signals.csv"
|
20 |
+
USERS_FILE = "users.csv"
|
21 |
+
security = HTTPBasic()
|
22 |
+
|
23 |
+
class User(BaseModel):
|
24 |
+
username: str
|
25 |
+
email: str
|
26 |
+
password: str
|
27 |
+
is_admin: bool = False
|
28 |
+
last_login: Optional[datetime] = None
|
29 |
+
is_active: bool = True
|
30 |
+
|
31 |
+
users = []
|
32 |
+
|
33 |
+
def load_users():
|
34 |
+
global users
|
35 |
+
if os.path.exists(USERS_FILE):
|
36 |
+
with open(USERS_FILE, mode='r') as file:
|
37 |
+
reader = csv.reader(file)
|
38 |
+
next(reader) # Skip header
|
39 |
+
users = []
|
40 |
+
for row in reader:
|
41 |
+
user = User(
|
42 |
+
username=row[0],
|
43 |
+
email=row[1],
|
44 |
+
password=row[2],
|
45 |
+
is_admin=row[3] == 'True',
|
46 |
+
last_login=datetime.fromisoformat(row[4]) if row[4] else None,
|
47 |
+
is_active=row[5] == 'True'
|
48 |
+
)
|
49 |
+
users.append(user)
|
50 |
+
|
51 |
+
def save_users():
|
52 |
+
with open(USERS_FILE, mode='w', newline='') as file:
|
53 |
+
writer = csv.writer(file)
|
54 |
+
writer.writerow(['username', 'email', 'password', 'is_admin', 'last_login', 'is_active'])
|
55 |
+
for user in users:
|
56 |
+
writer.writerow([
|
57 |
+
user.username,
|
58 |
+
user.email,
|
59 |
+
user.password,
|
60 |
+
user.is_admin,
|
61 |
+
user.last_login.isoformat() if user.last_login else '',
|
62 |
+
user.is_active
|
63 |
+
])
|
64 |
+
|
65 |
+
load_users()
|
66 |
+
|
67 |
+
def get_current_user(request: Request) -> Optional[User]:
|
68 |
+
username = request.cookies.get("username")
|
69 |
+
if username:
|
70 |
+
return next((u for u in users if u.username == username), None)
|
71 |
+
return None
|
72 |
+
|
73 |
+
def login_required(request: Request):
|
74 |
+
username = request.cookies.get("username")
|
75 |
+
if not username:
|
76 |
+
return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
|
77 |
+
user = next((u for u in users if u.username == username), None)
|
78 |
+
if not user:
|
79 |
+
return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
|
80 |
+
return user
|
81 |
+
|
82 |
+
@app.get("/login", response_class=HTMLResponse)
|
83 |
+
async def login_page(request: Request):
|
84 |
+
return templates.TemplateResponse("login.html", {"request": request})
|
85 |
+
|
86 |
+
@app.post("/login")
|
87 |
+
async def login(request: Request, username: str = Form(...), password: str = Form(...)):
|
88 |
+
user = next((u for u in users if u.username == username and u.password == password), None)
|
89 |
+
if user and user.is_active:
|
90 |
+
user.last_login = datetime.now()
|
91 |
+
save_users()
|
92 |
+
response = RedirectResponse(url="/", status_code=status.HTTP_302_FOUND)
|
93 |
+
response.set_cookie(key="username", value=username, httponly=True)
|
94 |
+
return response
|
95 |
+
return templates.TemplateResponse("login.html", {"request": request, "error": "Invalid credentials or inactive account"})
|
96 |
+
|
97 |
+
@app.get("/logout")
|
98 |
+
async def logout(response: JSONResponse):
|
99 |
+
response = RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
|
100 |
+
response.delete_cookie("username")
|
101 |
+
return response
|
102 |
+
|
103 |
+
@app.get("/register", response_class=HTMLResponse)
|
104 |
+
async def register_page(request: Request):
|
105 |
+
return templates.TemplateResponse("register.html", {"request": request})
|
106 |
+
|
107 |
+
@app.post("/register")
|
108 |
+
async def register(username: str = Form(...), email: str = Form(...), password: str = Form(...)):
|
109 |
+
if any(u.username == username for u in users):
|
110 |
+
return RedirectResponse(url="/register?error=1", status_code=status.HTTP_302_FOUND)
|
111 |
+
new_user = User(username=username, email=email, password=password)
|
112 |
+
users.append(new_user)
|
113 |
+
save_users()
|
114 |
+
return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
|
115 |
+
|
116 |
+
@app.get("/logout")
|
117 |
+
async def logout(request: Request):
|
118 |
+
response = RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
|
119 |
+
response.delete_cookie("username")
|
120 |
+
return response
|
121 |
+
|
122 |
+
@app.get("/admin", response_class=HTMLResponse)
|
123 |
+
async def admin_page(request: Request):
|
124 |
+
current_user = login_required(request)
|
125 |
+
if isinstance(current_user, RedirectResponse):
|
126 |
+
return current_user
|
127 |
+
if not current_user.is_admin:
|
128 |
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
|
129 |
+
return templates.TemplateResponse("admin.html", {"request": request, "users": users})
|
130 |
+
|
131 |
+
@app.post("/admin/delete/{username}")
|
132 |
+
async def delete_user(request: Request, username: str):
|
133 |
+
current_user = login_required(request)
|
134 |
+
if isinstance(current_user, RedirectResponse):
|
135 |
+
return current_user
|
136 |
+
if not current_user.is_admin:
|
137 |
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
|
138 |
+
global users
|
139 |
+
users = [u for u in users if u.username != username]
|
140 |
+
save_users()
|
141 |
+
return RedirectResponse(url="/admin", status_code=status.HTTP_302_FOUND)
|
142 |
+
|
143 |
+
@app.post("/admin/edit/{username}")
|
144 |
+
async def edit_user(request: Request, username: str, new_username: str = Form(...), email: str = Form(...), is_admin: bool = Form(False), is_active: bool = Form(False)):
|
145 |
+
current_user = login_required(request)
|
146 |
+
if isinstance(current_user, RedirectResponse):
|
147 |
+
return current_user
|
148 |
+
if not current_user.is_admin:
|
149 |
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
|
150 |
+
user = next((u for u in users if u.username == username), None)
|
151 |
+
if user:
|
152 |
+
# Check if the new username already exists
|
153 |
+
if new_username != username and any(u.username == new_username for u in users):
|
154 |
+
raise HTTPException(status_code=400, detail="Username already exists")
|
155 |
+
user.username = new_username
|
156 |
+
user.email = email
|
157 |
+
user.is_admin = is_admin
|
158 |
+
user.is_active = is_active
|
159 |
+
save_users()
|
160 |
+
return RedirectResponse(url="/admin", status_code=status.HTTP_302_FOUND)
|
161 |
+
|
162 |
+
@app.post("/admin/delete/{username}")
|
163 |
+
async def delete_user(username: str, current_user: User = Depends(get_current_user)):
|
164 |
+
if not current_user.is_admin:
|
165 |
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
|
166 |
+
global users
|
167 |
+
users = [u for u in users if u.username != username]
|
168 |
+
save_users()
|
169 |
+
return RedirectResponse(url="/admin", status_code=status.HTTP_302_FOUND)
|
170 |
+
|
171 |
+
@app.post("/admin/edit/{username}")
|
172 |
+
async def edit_user(request: Request, username: str, new_username: str = Form(...), email: str = Form(...), is_admin: bool = Form(False), is_active: bool = Form(False)):
|
173 |
+
current_user = login_required(request)
|
174 |
+
if isinstance(current_user, RedirectResponse):
|
175 |
+
return current_user
|
176 |
+
if not current_user.is_admin:
|
177 |
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
|
178 |
+
user = next((u for u in users if u.username == username), None)
|
179 |
+
if user:
|
180 |
+
# Check if the new username already exists
|
181 |
+
if new_username != username and any(u.username == new_username for u in users):
|
182 |
+
raise HTTPException(status_code=400, detail="Username already exists")
|
183 |
+
user.username = new_username
|
184 |
+
user.email = email
|
185 |
+
user.is_admin = is_admin
|
186 |
+
user.is_active = is_active
|
187 |
+
save_users()
|
188 |
+
return RedirectResponse(url="/admin", status_code=status.HTTP_302_FOUND)
|
189 |
|
190 |
@app.post("/api/generate-data")
|
191 |
def generate_data():
|
|
|
255 |
|
256 |
@app.get("/", response_class=HTMLResponse)
|
257 |
def show_map(request: Request, start_date: str = None, end_date: str = None):
|
258 |
+
current_user = login_required(request)
|
259 |
+
if isinstance(current_user, RedirectResponse):
|
260 |
+
return current_user
|
261 |
+
|
262 |
signal_data = []
|
263 |
|
264 |
if os.path.exists(CSV_FILE):
|
|
|
300 |
).add_to(marker_cluster)
|
301 |
|
302 |
map_html = m._repr_html_()
|
303 |
+
return templates.TemplateResponse("map.html", {
|
304 |
+
"request": request,
|
305 |
+
"map_html": map_html,
|
306 |
+
"start_date": start_date,
|
307 |
+
"end_date": end_date,
|
308 |
+
"current_user": current_user
|
309 |
+
})
|
310 |
|
311 |
+
@app.exception_handler(HTTPException)
|
312 |
+
async def http_exception_handler(request: Request, exc: HTTPException):
|
313 |
+
if exc.status_code == status.HTTP_401_UNAUTHORIZED:
|
314 |
+
return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
|
315 |
+
return templates.TemplateResponse("error.html", {"request": request, "detail": exc.detail}, status_code=exc.status_code)
|
316 |
|
317 |
@app.get("/download-csv")
|
318 |
async def download_csv():
|
templates/admin.html
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Admin Panel - Signal Tracker</title>
|
7 |
+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
8 |
+
</head>
|
9 |
+
<body>
|
10 |
+
<div class="container mt-5">
|
11 |
+
<h1>Admin Panel</h1>
|
12 |
+
<table class="table">
|
13 |
+
<thead>
|
14 |
+
<tr>
|
15 |
+
<th>Username</th>
|
16 |
+
<th>Email</th>
|
17 |
+
<th>Is Admin</th>
|
18 |
+
<th>Last Login</th>
|
19 |
+
<th>Is Active</th>
|
20 |
+
<th>Actions</th>
|
21 |
+
</tr>
|
22 |
+
</thead>
|
23 |
+
<tbody>
|
24 |
+
{% for user in users %}
|
25 |
+
<tr>
|
26 |
+
<td>{{ user.username }}</td>
|
27 |
+
<td>{{ user.email }}</td>
|
28 |
+
<td>{{ user.is_admin }}</td>
|
29 |
+
<td>{{ user.last_login.strftime('%Y-%m-%d %H:%M:%S') if user.last_login else 'Never' }}</td>
|
30 |
+
<td>{{ user.is_active }}</td>
|
31 |
+
<td>
|
32 |
+
<button class="btn btn-sm btn-primary" onclick="editUser('{{ user.username }}', '{{ user.email }}', {{ user.is_admin | tojson }}, {{ user.is_active | tojson }})">Edit</button>
|
33 |
+
<form method="post" action="/admin/delete/{{ user.username }}" style="display: inline;">
|
34 |
+
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
35 |
+
</form>
|
36 |
+
</td>
|
37 |
+
</tr>
|
38 |
+
{% endfor %}
|
39 |
+
</tbody>
|
40 |
+
</table>
|
41 |
+
<a href="/" class="btn btn-secondary">Back to Map</a>
|
42 |
+
</div>
|
43 |
+
|
44 |
+
<!-- Edit User Modal -->
|
45 |
+
<div class="modal fade" id="editUserModal" tabindex="-1" aria-hidden="true">
|
46 |
+
<div class="modal-dialog">
|
47 |
+
<div class="modal-content">
|
48 |
+
<div class="modal-header">
|
49 |
+
<h5 class="modal-title">Edit User</h5>
|
50 |
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
51 |
+
</div>
|
52 |
+
<form id="editUserForm" method="post">
|
53 |
+
<div class="modal-body">
|
54 |
+
<div class="mb-3">
|
55 |
+
<label for="editUsername" class="form-label">Username</label>
|
56 |
+
<input type="text" class="form-control" id="editUsername" name="new_username" required>
|
57 |
+
</div>
|
58 |
+
<div class="mb-3">
|
59 |
+
<label for="editEmail" class="form-label">Email</label>
|
60 |
+
<input type="email" class="form-control" id="editEmail" name="email" required>
|
61 |
+
</div>
|
62 |
+
<div class="mb-3 form-check">
|
63 |
+
<input type="hidden" name="is_admin" value="false">
|
64 |
+
<input type="checkbox" class="form-check-input" id="editIsAdmin" name="is_admin" value="true">
|
65 |
+
<label class="form-check-label" for="editIsAdmin">Is Admin</label>
|
66 |
+
</div>
|
67 |
+
<div class="mb-3 form-check">
|
68 |
+
<input type="hidden" name="is_active" value="false">
|
69 |
+
<input type="checkbox" class="form-check-input" id="editIsActive" name="is_active" value="true">
|
70 |
+
<label class="form-check-label" for="editIsActive">Is Active</label>
|
71 |
+
</div>
|
72 |
+
</div>
|
73 |
+
<div class="modal-footer">
|
74 |
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
75 |
+
<button type="submit" class="btn btn-primary">Save changes</button>
|
76 |
+
</div>
|
77 |
+
</form>
|
78 |
+
</div>
|
79 |
+
</div>
|
80 |
+
</div>
|
81 |
+
|
82 |
+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
83 |
+
<script>
|
84 |
+
function editUser(username, email, isAdmin, isActive) {
|
85 |
+
document.getElementById('editUserForm').action = `/admin/edit/${username}`;
|
86 |
+
document.getElementById('editUsername').value = username;
|
87 |
+
document.getElementById('editEmail').value = email;
|
88 |
+
document.getElementById('editIsAdmin').checked = isAdmin;
|
89 |
+
document.getElementById('editIsActive').checked = isActive;
|
90 |
+
var editUserModal = new bootstrap.Modal(document.getElementById('editUserModal'));
|
91 |
+
editUserModal.show();
|
92 |
+
}
|
93 |
+
</script>
|
94 |
+
</body>
|
95 |
+
</html>
|
96 |
+
|
97 |
+
|
templates/login.html
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Login - Signal Tracker</title>
|
7 |
+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
8 |
+
</head>
|
9 |
+
<body>
|
10 |
+
<div class="container mt-5">
|
11 |
+
<h1>Login</h1>
|
12 |
+
{% if request.query_params.get("error") %}
|
13 |
+
<div class="alert alert-danger">Invalid credentials</div>
|
14 |
+
{% endif %}
|
15 |
+
<form method="post" action="/login">
|
16 |
+
<div class="mb-3">
|
17 |
+
<label for="username" class="form-label">Username</label>
|
18 |
+
<input type="text" class="form-control" id="username" name="username" required>
|
19 |
+
</div>
|
20 |
+
<div class="mb-3">
|
21 |
+
<label for="password" class="form-label">Password</label>
|
22 |
+
<input type="password" class="form-control" id="password" name="password" required>
|
23 |
+
</div>
|
24 |
+
<button type="submit" class="btn btn-primary">Login</button>
|
25 |
+
</form>
|
26 |
+
<p class="mt-3">Don't have an account? <a href="/register">Register here</a></p>
|
27 |
+
</div>
|
28 |
+
</body>
|
29 |
+
</html>
|
templates/map.html
CHANGED
@@ -59,10 +59,26 @@
|
|
59 |
.btn-download:hover {
|
60 |
background-color: #0056b3;
|
61 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
</style>
|
63 |
</head>
|
64 |
<body>
|
65 |
<div class="container">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
<h1 class="title">Signal Tracker Map</h1>
|
67 |
<form class="filter-form" id="date-form" method="GET" action="/">
|
68 |
<div class="row g-3 align-items-center">
|
|
|
59 |
.btn-download:hover {
|
60 |
background-color: #0056b3;
|
61 |
}
|
62 |
+
.user-info {
|
63 |
+
position: absolute;
|
64 |
+
top: 10px;
|
65 |
+
right: 10px;
|
66 |
+
background-color: white;
|
67 |
+
padding: 5px 10px;
|
68 |
+
border-radius: 5px;
|
69 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
70 |
+
}
|
71 |
</style>
|
72 |
</head>
|
73 |
<body>
|
74 |
<div class="container">
|
75 |
+
<div class="user-info">
|
76 |
+
{% if current_user %}
|
77 |
+
Welcome, {{ current_user.username }} | <a href="/logout">Logout</a>
|
78 |
+
{% else %}
|
79 |
+
<a href="/login">Login</a>
|
80 |
+
{% endif %}
|
81 |
+
</div>
|
82 |
<h1 class="title">Signal Tracker Map</h1>
|
83 |
<form class="filter-form" id="date-form" method="GET" action="/">
|
84 |
<div class="row g-3 align-items-center">
|
templates/register.html
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Register - Signal Tracker</title>
|
7 |
+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
8 |
+
</head>
|
9 |
+
<body>
|
10 |
+
<div class="container mt-5">
|
11 |
+
<h1>Register</h1>
|
12 |
+
{% if request.query_params.get("error") %}
|
13 |
+
<div class="alert alert-danger">Username already exists</div>
|
14 |
+
{% endif %}
|
15 |
+
<form method="post" action="/register">
|
16 |
+
<div class="mb-3">
|
17 |
+
<label for="username" class="form-label">Username</label>
|
18 |
+
<input type="text" class="form-control" id="username" name="username" required>
|
19 |
+
</div>
|
20 |
+
<div class="mb-3">
|
21 |
+
<label for="email" class="form-label">Email</label>
|
22 |
+
<input type="email" class="form-control" id="email" name="email" required>
|
23 |
+
</div>
|
24 |
+
<div class="mb-3">
|
25 |
+
<label for="password" class="form-label">Password</label>
|
26 |
+
<input type="password" class="form-control" id="password" name="password" required>
|
27 |
+
</div>
|
28 |
+
<button type="submit" class="btn btn-primary">Register</button>
|
29 |
+
</form>
|
30 |
+
<p class="mt-3">Already have an account? <a href="/login">Login here</a></p>
|
31 |
+
</div>
|
32 |
+
</body>
|
33 |
+
</html>
|
users.csv
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
username,email,password,is_admin,last_login,is_active
|
2 |
+
admin,[email protected],admin,True,2024-08-30T17:44:00.209768,True
|
3 |
+
vumichien,[email protected],123456,True,2024-08-30T17:43:53.120071,True
|