vumichien commited on
Commit
4e2a6d5
·
1 Parent(s): ad47647
Files changed (5) hide show
  1. __pycache__/models.cpython-310.pyc +0 -0
  2. app.py +343 -168
  3. database.db +0 -0
  4. models.py +44 -8
  5. templates/admin.html +116 -0
__pycache__/models.cpython-310.pyc ADDED
Binary file (1.98 kB). View file
 
app.py CHANGED
@@ -1,101 +1,112 @@
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
@@ -105,168 +116,313 @@ 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("/api/generate-data")
163
- def generate_data():
 
 
 
 
 
 
164
  base_latitude = 35.6837
165
  base_longitude = 139.6805
166
  start_date = datetime(2024, 8, 1)
167
  end_date = datetime(2024, 8, 7)
168
  delta = end_date - start_date
169
 
170
- with open(CSV_FILE, mode='w', newline='') as file:
171
- writer = csv.writer(file)
172
- writer.writerow(['latitude', 'longitude', 'timestamp', 'signal_strength'])
173
-
174
- for _ in range(100):
175
- random_days = random.randint(0, delta.days)
176
- random_seconds = random.randint(0, 86400)
177
- random_time = start_date + timedelta(days=random_days, seconds=random_seconds)
178
-
179
- random_latitude = base_latitude + random.uniform(-0.01, 0.01)
180
- random_longitude = base_longitude + random.uniform(-0.01, 0.01)
181
- random_signal_strength = random.randint(0, 4)
182
-
183
- writer.writerow([
184
- random_latitude,
185
- random_longitude,
186
- random_time.strftime('%Y-%m-%d %H:%M:%S'),
187
- random_signal_strength
188
- ])
189
-
190
  return {"message": "Demo data generated successfully"}
191
 
192
- @app.delete("/api/delete-data")
193
- def delete_data():
 
 
 
 
 
 
 
 
 
194
  try:
195
- if os.path.exists(CSV_FILE):
196
- os.remove(CSV_FILE)
197
-
198
- # Create a new empty CSV file with headers
199
- with open(CSV_FILE, mode='w', newline='') as file:
200
- writer = csv.writer(file)
201
- writer.writerow(['latitude', 'longitude', 'timestamp', 'signal_strength'])
202
-
203
- return {"message": "Data file deleted and reset successfully"}
204
  except Exception as e:
 
205
  raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
206
 
207
- @app.post("/api/upload")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  async def upload_data(
 
 
 
209
  latitude: float = Form(...),
210
  longitude: float = Form(...),
211
  timestamp: str = Form(...),
212
- signal_strength: int = Form(...)
 
213
  ):
 
 
 
 
 
 
 
 
 
 
 
 
214
  try:
215
- # Validate timestamp format
216
- datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S')
217
-
218
- with open(CSV_FILE, mode='a', newline='') as file:
219
- writer = csv.writer(file)
220
- writer.writerow([latitude, longitude, timestamp, signal_strength])
221
-
 
 
 
 
222
  return {"message": "Data uploaded successfully"}
 
 
 
223
  except ValueError:
224
  raise HTTPException(status_code=400, detail="Invalid timestamp format. Use 'YYYY-MM-DD HH:MM:SS'")
225
  except Exception as e:
 
226
  raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
227
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  @app.get("/", response_class=HTMLResponse)
229
- def show_map(request: Request, start_date: str = None, end_date: str = None):
230
- current_user = login_required(request)
231
  if isinstance(current_user, RedirectResponse):
232
  return current_user
233
 
234
- signal_data = []
235
-
236
- if os.path.exists(CSV_FILE):
237
- with open(CSV_FILE, mode='r') as file:
238
- reader = csv.reader(file)
239
- next(reader) # Skip header row
240
- for row in reader:
241
- lat, lon, timestamp, strength = row
242
- timestamp = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S')
243
- if start_date and end_date:
244
- start_datetime = datetime.strptime(start_date, '%Y-%m-%d')
245
- end_datetime = datetime.strptime(end_date, '%Y-%m-%d')
246
- if start_datetime <= timestamp <= end_datetime:
247
- signal_data.append((float(lat), float(lon), int(strength)))
248
- else:
249
- signal_data.append((float(lat), float(lon), int(strength)))
250
-
251
- if signal_data:
252
- min_signal = min(s[2] for s in signal_data)
253
- max_signal = max(s[2] for s in signal_data)
254
- else:
255
- min_signal = max_signal = 1
256
 
257
  m = folium.Map(location=[35.6837, 139.6805], zoom_start=12)
258
  marker_cluster = MarkerCluster().add_to(m)
259
 
260
- for lat, lon, signal_strength in signal_data:
261
- if max_signal - min_signal != 0:
262
- radius = 5 + 10 * ((signal_strength - min_signal) / (max_signal - min_signal))
263
- else:
264
- radius = 15
265
  folium.CircleMarker(
266
  location=[lat, lon],
267
- radius=radius,
268
- popup=f'Signal Strength: {signal_strength}',
269
- color='blue',
270
  fill=True,
271
  fill_opacity=0.6
272
  ).add_to(marker_cluster)
@@ -280,17 +436,36 @@ def show_map(request: Request, start_date: str = None, end_date: str = None):
280
  "current_user": current_user
281
  })
282
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  @app.exception_handler(HTTPException)
284
  async def http_exception_handler(request: Request, exc: HTTPException):
285
  if exc.status_code == status.HTTP_401_UNAUTHORIZED:
286
  return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
287
  return templates.TemplateResponse("error.html", {"request": request, "detail": exc.detail}, status_code=exc.status_code)
288
 
289
- @app.get("/download-csv")
290
- async def download_csv():
291
- if os.path.exists(CSV_FILE):
292
- return FileResponse(CSV_FILE, filename="wifi_signals.csv")
293
- raise HTTPException(status_code=404, detail="CSV file not found")
294
 
295
  if __name__ == "__main__":
296
  import uvicorn
 
1
+ from fastapi import FastAPI, HTTPException, Request, Form, Depends, status, APIRouter, Header
2
+ from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse, StreamingResponse
3
  from fastapi.templating import Jinja2Templates
4
  from fastapi.staticfiles import StaticFiles
5
+ from fastapi.security import HTTPBasic
6
  from datetime import datetime, timedelta
7
  import random
8
  import folium
9
+ import uuid as uuid_module
10
  from folium.plugins import MarkerCluster
11
+ from typing import Optional
12
+ from sqlalchemy import create_engine
13
+ from sqlalchemy.orm import sessionmaker, Session
14
+ from models import Base, User, StatusRecord, SystemSetting, Device
15
+ import io
16
  import csv
17
+ from typing import Dict
18
+
19
+ # Database setup
20
+ SQLALCHEMY_DATABASE_URL = "sqlite:///./database.db"
21
+ engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
22
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
23
+
24
+ Base.metadata.create_all(bind=engine)
25
+
26
+ # Create default admin user and system settings
27
+ def create_default_data():
28
+ db = SessionLocal()
29
+ try:
30
+ # Create default admin user if not exists
31
+ if not db.query(User).filter(User.username == "admin").first():
32
+ admin_user = User(
33
+ username="admin",
34
+ email="[email protected]",
35
+ password="admin",
36
+ is_admin=True,
37
+ is_active=True
38
+ )
39
+ db.add(admin_user)
40
+
41
+ # Create default system settings if not exists
42
+ if not db.query(SystemSetting).first():
43
+ default_settings = SystemSetting()
44
+ db.add(default_settings)
45
+
46
+ db.commit()
47
+ except Exception as e:
48
+ db.rollback()
49
+ print(f"Error creating default data: {str(e)}")
50
+ finally:
51
+ db.close()
52
+
53
+ create_default_data()
54
 
55
  app = FastAPI()
56
  templates = Jinja2Templates(directory="templates")
57
+ security = HTTPBasic()
58
 
59
+ # Create APIRouters for grouping
60
+ admin_router = APIRouter(prefix="/admin", tags=["admin"])
61
+ api_router = APIRouter(prefix="/api", tags=["api"])
62
 
63
+ # Dependency to get the database session
64
+ def get_db():
65
+ db = SessionLocal()
66
+ try:
67
+ yield db
68
+ finally:
69
+ db.close()
70
 
71
+ def get_current_user(request: Request, db: Session = Depends(get_db)) -> Optional[User]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  username = request.cookies.get("username")
73
  if username:
74
+ return db.query(User).filter(User.username == username).first()
75
  return None
76
 
77
+ def login_required(request: Request, db: Session = Depends(get_db)):
78
  username = request.cookies.get("username")
79
  if not username:
80
  return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
81
+ user = db.query(User).filter(User.username == username).first()
82
  if not user:
83
  return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
84
  return user
85
 
86
+ # Device authentication function
87
+ def authenticate_device(device_id: str, device_password: str, db: Session = Depends(get_db)):
88
+ device = db.query(Device).filter(Device.device_id == device_id).first()
89
+ if not device or device.password != device_password:
90
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid device credentials")
91
+ return device
92
+
93
  @app.get("/login", response_class=HTMLResponse)
94
  async def login_page(request: Request):
95
  return templates.TemplateResponse("login.html", {"request": request})
96
 
97
  @app.post("/login")
98
+ async def login(request: Request, username: str = Form(...), password: str = Form(...), db: Session = Depends(get_db)):
99
+ user = db.query(User).filter(User.username == username, User.password == password, User.is_active == True).first()
100
+ if user:
101
  user.last_login = datetime.now()
102
+ db.commit()
103
  response = RedirectResponse(url="/", status_code=status.HTTP_302_FOUND)
104
  response.set_cookie(key="username", value=username, httponly=True)
105
  return response
106
  return templates.TemplateResponse("login.html", {"request": request, "error": "Invalid credentials or inactive account"})
107
 
108
  @app.get("/logout")
109
+ async def logout():
110
  response = RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
111
  response.delete_cookie("username")
112
  return response
 
116
  return templates.TemplateResponse("register.html", {"request": request})
117
 
118
  @app.post("/register")
119
+ async def register(username: str = Form(...), email: str = Form(...), password: str = Form(...), db: Session = Depends(get_db)):
120
+ existing_user = db.query(User).filter(User.username == username).first()
121
+ if existing_user:
122
  return RedirectResponse(url="/register?error=1", status_code=status.HTTP_302_FOUND)
123
  new_user = User(username=username, email=email, password=password)
124
+ db.add(new_user)
125
+ db.commit()
126
  return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
127
 
128
+ # Admin routes
129
+ @admin_router.get("", response_class=HTMLResponse)
130
+ async def admin_page(request: Request, db: Session = Depends(get_db)):
131
+ current_user = login_required(request, db)
 
 
 
 
 
132
  if isinstance(current_user, RedirectResponse):
133
  return current_user
134
  if not current_user.is_admin:
135
  raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
136
+ users = db.query(User).all()
137
+ devices = db.query(Device).all()
138
+ return templates.TemplateResponse("admin.html", {"request": request, "users": users, "devices": devices})
139
 
140
+ @admin_router.post("/delete/{username}")
141
+ async def delete_user(request: Request, username: str, db: Session = Depends(get_db)):
142
+ current_user = login_required(request, db)
143
  if isinstance(current_user, RedirectResponse):
144
  return current_user
145
  if not current_user.is_admin:
146
  raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
147
+ user = db.query(User).filter(User.username == username).first()
148
+ if user:
149
+ db.delete(user)
150
+ db.commit()
151
  return RedirectResponse(url="/admin", status_code=status.HTTP_302_FOUND)
152
 
153
+ @admin_router.post("/edit/{username}")
154
+ 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), db: Session = Depends(get_db)):
155
+ current_user = login_required(request, db)
156
  if isinstance(current_user, RedirectResponse):
157
  return current_user
158
  if not current_user.is_admin:
159
  raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
160
+ user = db.query(User).filter(User.username == username).first()
161
  if user:
162
+ if new_username != username and db.query(User).filter(User.username == new_username).first():
 
163
  raise HTTPException(status_code=400, detail="Username already exists")
164
  user.username = new_username
165
  user.email = email
166
  user.is_admin = is_admin
167
  user.is_active = is_active
168
+ db.commit()
169
+ return RedirectResponse(url="/admin", status_code=status.HTTP_302_FOUND)
170
+
171
+ @admin_router.post("/add_device")
172
+ async def add_device(
173
+ request: Request,
174
+ name: str = Form(...),
175
+ description: str = Form(...),
176
+ device_id: str = Form(...),
177
+ password: str = Form(...),
178
+ db: Session = Depends(get_db)
179
+ ):
180
+ current_user = login_required(request, db)
181
+ if isinstance(current_user, RedirectResponse):
182
+ return current_user
183
+ if not current_user.is_admin:
184
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
185
+
186
+ existing_device = db.query(Device).filter(Device.device_id == device_id).first()
187
+ if existing_device:
188
+ raise HTTPException(status_code=400, detail="Device ID already exists")
189
+
190
+ new_device = Device(name=name, description=description, device_id=device_id, password=password)
191
+ db.add(new_device)
192
+ db.commit()
193
+ return RedirectResponse(url="/admin", status_code=status.HTTP_302_FOUND)
194
+
195
+ @admin_router.post("/edit_device/{device_id}")
196
+ async def edit_device(
197
+ request: Request,
198
+ device_id: str,
199
+ name: str = Form(...),
200
+ description: str = Form(...),
201
+ new_device_id: str = Form(...),
202
+ password: str = Form(...),
203
+ db: Session = Depends(get_db)
204
+ ):
205
+ current_user = login_required(request, db)
206
+ if isinstance(current_user, RedirectResponse):
207
+ return current_user
208
+ if not current_user.is_admin:
209
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
210
+
211
+ device = db.query(Device).filter(Device.device_id == device_id).first()
212
+ if not device:
213
+ raise HTTPException(status_code=404, detail="Device not found")
214
+
215
+ if new_device_id != device_id and db.query(Device).filter(Device.device_id == new_device_id).first():
216
+ raise HTTPException(status_code=400, detail="New Device ID already exists")
217
+
218
+ device.name = name
219
+ device.description = description
220
+ device.device_id = new_device_id
221
+ device.password = password
222
+ db.commit()
223
+ return RedirectResponse(url="/admin", status_code=status.HTTP_302_FOUND)
224
+
225
+ @admin_router.post("/delete_device/{device_id}")
226
+ async def delete_device(request: Request, device_id: str, db: Session = Depends(get_db)):
227
+ current_user = login_required(request, db)
228
+ if isinstance(current_user, RedirectResponse):
229
+ return current_user
230
+ if not current_user.is_admin:
231
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
232
+
233
+ device = db.query(Device).filter(Device.device_id == device_id).first()
234
+ if device:
235
+ db.delete(device)
236
+ db.commit()
237
  return RedirectResponse(url="/admin", status_code=status.HTTP_302_FOUND)
238
 
239
+ # API routes
240
+ @api_router.post("/generate-data")
241
+ def generate_data(
242
+ device_id: str = Header(...),
243
+ device_password: str = Header(...),
244
+ db: Session = Depends(get_db)
245
+ ):
246
+ authenticate_device(device_id, device_password, db)
247
  base_latitude = 35.6837
248
  base_longitude = 139.6805
249
  start_date = datetime(2024, 8, 1)
250
  end_date = datetime(2024, 8, 7)
251
  delta = end_date - start_date
252
 
253
+ for _ in range(100):
254
+ random_days = random.randint(0, delta.days)
255
+ random_seconds = random.randint(0, 86400)
256
+ random_time = start_date + timedelta(days=random_days, seconds=random_seconds)
257
+
258
+ random_latitude = base_latitude + random.uniform(-0.01, 0.01)
259
+ random_longitude = base_longitude + random.uniform(-0.01, 0.01)
260
+ random_connect_status = random.choice([0, 1])
261
+
262
+ status_record = StatusRecord(
263
+ device_id=device_id,
264
+ latitude=random_latitude,
265
+ longitude=random_longitude,
266
+ timestamp=random_time,
267
+ connect_status=random_connect_status
268
+ )
269
+ db.add(status_record)
270
+
271
+ db.commit()
 
272
  return {"message": "Demo data generated successfully"}
273
 
274
+ @api_router.delete("/delete-data", summary="Delete all status records")
275
+ def delete_all_data(
276
+ device_id: str = Header(...),
277
+ device_password: str = Header(...),
278
+ db: Session = Depends(get_db)
279
+ ):
280
+ """
281
+ Delete all status records from the database.
282
+ Requires device authentication.
283
+ """
284
+ authenticate_device(device_id, device_password, db)
285
  try:
286
+ db.query(StatusRecord).delete()
287
+ db.commit()
288
+ return {"message": "All data deleted successfully"}
 
 
 
 
 
 
289
  except Exception as e:
290
+ db.rollback()
291
  raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
292
 
293
+ @api_router.delete("/delete-data/{device_id}", summary="Delete status records for a specific device")
294
+ def delete_device_data(
295
+ device_id: str,
296
+ auth_device_id: str = Header(...),
297
+ device_password: str = Header(...),
298
+ db: Session = Depends(get_db)
299
+ ):
300
+ """
301
+ Delete status records for a specific device ID.
302
+ Requires device authentication.
303
+ """
304
+ authenticate_device(auth_device_id, device_password, db)
305
+ try:
306
+ deleted_count = db.query(StatusRecord).filter(StatusRecord.device_id == device_id).delete()
307
+ db.commit()
308
+ if deleted_count == 0:
309
+ return {"message": f"No data found for device ID: {device_id}"}
310
+ return {"message": f"Data for device ID {device_id} deleted successfully"}
311
+ except Exception as e:
312
+ db.rollback()
313
+ raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
314
+
315
+ @api_router.post("/upload")
316
  async def upload_data(
317
+ device_id: str = Header(...),
318
+ device_password: str = Header(...),
319
+ uuid_str: str = Form(...),
320
  latitude: float = Form(...),
321
  longitude: float = Form(...),
322
  timestamp: str = Form(...),
323
+ connect_status: int = Form(...),
324
+ db: Session = Depends(get_db)
325
  ):
326
+ """
327
+ Upload a new status record.
328
+ Requires device authentication and a unique UUID.
329
+ """
330
+ authenticate_device(device_id, device_password, db)
331
+
332
+ # Validate UUID
333
+ try:
334
+ uuid_obj = uuid_module.UUID(uuid_str)
335
+ except ValueError:
336
+ raise HTTPException(status_code=400, detail="Invalid UUID format")
337
+
338
  try:
339
+ timestamp_dt = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S')
340
+ status_record = StatusRecord(
341
+ uuid=str(uuid_obj),
342
+ device_id=device_id,
343
+ latitude=latitude,
344
+ longitude=longitude,
345
+ timestamp=timestamp_dt,
346
+ connect_status=connect_status
347
+ )
348
+ db.add(status_record)
349
+ db.commit()
350
  return {"message": "Data uploaded successfully"}
351
+ except IntegrityError:
352
+ db.rollback()
353
+ raise HTTPException(status_code=400, detail="UUID already exists")
354
  except ValueError:
355
  raise HTTPException(status_code=400, detail="Invalid timestamp format. Use 'YYYY-MM-DD HH:MM:SS'")
356
  except Exception as e:
357
+ db.rollback()
358
  raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
359
 
360
+ @api_router.get("/health_check", summary="Check if the API is functioning correctly")
361
+ def health_check(
362
+ device_id: str = Header(...),
363
+ device_password: str = Header(...),
364
+ db: Session = Depends(get_db)
365
+ ):
366
+ """
367
+ Perform a health check on the API.
368
+ Requires device authentication.
369
+ Returns a 200 status code if successful.
370
+ Returns a 401 Unauthorized error if authentication fails.
371
+ """
372
+ try:
373
+ authenticate_device(device_id, device_password, db)
374
+ return JSONResponse(content={"status": "ok"}, status_code=status.HTTP_200_OK)
375
+ except HTTPException as e:
376
+ if e.status_code == status.HTTP_401_UNAUTHORIZED:
377
+ return JSONResponse(content={"status": "error", "detail": "Unauthorized"}, status_code=status.HTTP_401_UNAUTHORIZED)
378
+ raise e
379
+
380
+ @api_router.get("/config", summary="Get system configuration", response_model=Dict[str, int])
381
+ def get_config(
382
+ device_id: str = Header(...),
383
+ device_password: str = Header(...),
384
+ db: Session = Depends(get_db)
385
+ ):
386
+ """
387
+ Retrieve the system configuration from SystemSetting.
388
+ Requires device authentication.
389
+ """
390
+ authenticate_device(device_id, device_password, db)
391
+ system_setting = db.query(SystemSetting).first()
392
+ if not system_setting:
393
+ raise HTTPException(status_code=404, detail="System settings not found")
394
+
395
+ return {
396
+ "check_connect_period": system_setting.check_connect_period,
397
+ "data_sync_period": system_setting.data_sync_period,
398
+ "get_config_period": system_setting.get_config_period,
399
+ "point_distance": system_setting.point_distance
400
+ }
401
+
402
  @app.get("/", response_class=HTMLResponse)
403
+ def show_map(request: Request, start_date: str = None, end_date: str = None, db: Session = Depends(get_db)):
404
+ current_user = login_required(request, db)
405
  if isinstance(current_user, RedirectResponse):
406
  return current_user
407
 
408
+ query = db.query(StatusRecord)
409
+ if start_date and end_date:
410
+ start_datetime = datetime.strptime(start_date, '%Y-%m-%d')
411
+ end_datetime = datetime.strptime(end_date, '%Y-%m-%d')
412
+ query = query.filter(StatusRecord.timestamp.between(start_datetime, end_datetime))
413
+
414
+ status_data = [(s.latitude, s.longitude, s.connect_status, s.device_id) for s in query.all()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
 
416
  m = folium.Map(location=[35.6837, 139.6805], zoom_start=12)
417
  marker_cluster = MarkerCluster().add_to(m)
418
 
419
+ for lat, lon, connect_status, device_id in status_data:
420
+ color = 'green' if connect_status == 1 else 'red'
 
 
 
421
  folium.CircleMarker(
422
  location=[lat, lon],
423
+ radius=10,
424
+ popup=f'{device_id}',
425
+ color=color,
426
  fill=True,
427
  fill_opacity=0.6
428
  ).add_to(marker_cluster)
 
436
  "current_user": current_user
437
  })
438
 
439
+ @app.get("/download-csv")
440
+ async def download_csv(request: Request, db: Session = Depends(get_db)):
441
+ current_user = login_required(request, db)
442
+ if isinstance(current_user, RedirectResponse):
443
+ return current_user
444
+
445
+ status_records = db.query(StatusRecord).all()
446
+
447
+ output = io.StringIO()
448
+ writer = csv.writer(output)
449
+
450
+ writer.writerow(["UUID", "Device ID", "Latitude", "Longitude", "Timestamp", "Connect Status"])
451
+
452
+ for record in status_records:
453
+ writer.writerow([record.uuid, record.device_id, record.latitude, record.longitude, record.timestamp, record.connect_status])
454
+
455
+ response = StreamingResponse(iter([output.getvalue()]), media_type="text/csv")
456
+ response.headers["Content-Disposition"] = "attachment; filename=status_records.csv"
457
+
458
+ return response
459
+
460
  @app.exception_handler(HTTPException)
461
  async def http_exception_handler(request: Request, exc: HTTPException):
462
  if exc.status_code == status.HTTP_401_UNAUTHORIZED:
463
  return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
464
  return templates.TemplateResponse("error.html", {"request": request, "detail": exc.detail}, status_code=exc.status_code)
465
 
466
+ # Include the routers
467
+ app.include_router(admin_router)
468
+ app.include_router(api_router)
 
 
469
 
470
  if __name__ == "__main__":
471
  import uvicorn
database.db CHANGED
Binary files a/database.db and b/database.db differ
 
models.py CHANGED
@@ -1,10 +1,46 @@
1
- from flask_sqlalchemy import SQLAlchemy
 
 
 
2
 
3
- db = SQLAlchemy()
4
 
5
- class WifiSignal(db.Model):
6
- id = db.Column(db.Integer, primary_key=True)
7
- latitude = db.Column(db.Float, nullable=False)
8
- longitude = db.Column(db.Float, nullable=False)
9
- timestamp = db.Column(db.DateTime, nullable=False)
10
- signal_strength = db.Column(db.Integer, nullable=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String, Float, DateTime, Boolean, Enum
2
+ from sqlalchemy.ext.declarative import declarative_base
3
+ from datetime import datetime
4
+ import uuid
5
 
6
+ Base = declarative_base()
7
 
8
+ class User(Base):
9
+ __tablename__ = "users"
10
+
11
+ id = Column(Integer, primary_key=True, index=True)
12
+ username = Column(String, unique=True, index=True)
13
+ email = Column(String, unique=True, index=True)
14
+ password = Column(String)
15
+ is_admin = Column(Boolean, default=False)
16
+ last_login = Column(DateTime, nullable=True)
17
+ is_active = Column(Boolean, default=True)
18
+
19
+ class StatusRecord(Base):
20
+ __tablename__ = "status_records"
21
+
22
+ id = Column(Integer, primary_key=True, index=True)
23
+ uuid = Column(String, unique=True, default=lambda: str(uuid.uuid4()))
24
+ device_id = Column(String, index=True)
25
+ latitude = Column(Float)
26
+ longitude = Column(Float)
27
+ timestamp = Column(DateTime)
28
+ connect_status = Column(Integer) # 0 or 1
29
+
30
+ class SystemSetting(Base):
31
+ __tablename__ = "system_settings"
32
+
33
+ id = Column(Integer, primary_key=True, index=True)
34
+ check_connect_period = Column(Integer, default=10)
35
+ data_sync_period = Column(Integer, default=3600)
36
+ get_config_period = Column(Integer, default=60)
37
+ point_distance = Column(Integer, default=5)
38
+
39
+ class Device(Base):
40
+ __tablename__ = "devices"
41
+
42
+ id = Column(Integer, primary_key=True, index=True)
43
+ name = Column(String)
44
+ description = Column(String)
45
+ device_id = Column(String, unique=True, index=True)
46
+ password = Column(String)
templates/admin.html CHANGED
@@ -38,6 +38,35 @@
38
  {% endfor %}
39
  </tbody>
40
  </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  <a href="/" class="btn btn-secondary">Back to Map</a>
42
  </div>
43
 
@@ -79,6 +108,78 @@
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) {
@@ -90,6 +191,21 @@
90
  var editUserModal = new bootstrap.Modal(document.getElementById('editUserModal'));
91
  editUserModal.show();
92
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  </script>
94
  </body>
95
  </html>
 
38
  {% endfor %}
39
  </tbody>
40
  </table>
41
+
42
+ <h2>Devices</h2>
43
+ <button class="btn btn-success mb-3" onclick="addDevice()">Add Device</button>
44
+ <table class="table">
45
+ <thead>
46
+ <tr>
47
+ <th>Name</th>
48
+ <th>Description</th>
49
+ <th>Device ID</th>
50
+ <th>Actions</th>
51
+ </tr>
52
+ </thead>
53
+ <tbody>
54
+ {% for device in devices %}
55
+ <tr>
56
+ <td>{{ device.name }}</td>
57
+ <td>{{ device.description }}</td>
58
+ <td>{{ device.device_id }}</td>
59
+ <td>
60
+ <button class="btn btn-sm btn-primary" onclick="editDevice('{{ device.name }}', '{{ device.description }}', '{{ device.device_id }}')">Edit</button>
61
+ <form method="post" action="/admin/delete_device/{{ device.device_id }}" style="display: inline;">
62
+ <button type="submit" class="btn btn-sm btn-danger">Delete</button>
63
+ </form>
64
+ </td>
65
+ </tr>
66
+ {% endfor %}
67
+ </tbody>
68
+ </table>
69
+
70
  <a href="/" class="btn btn-secondary">Back to Map</a>
71
  </div>
72
 
 
108
  </div>
109
  </div>
110
 
111
+ <!-- Add Device Modal -->
112
+ <div class="modal fade" id="addDeviceModal" tabindex="-1" aria-hidden="true">
113
+ <div class="modal-dialog">
114
+ <div class="modal-content">
115
+ <div class="modal-header">
116
+ <h5 class="modal-title">Add Device</h5>
117
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
118
+ </div>
119
+ <form id="addDeviceForm" method="post" action="/admin/add_device">
120
+ <div class="modal-body">
121
+ <div class="mb-3">
122
+ <label for="deviceName" class="form-label">Name</label>
123
+ <input type="text" class="form-control" id="deviceName" name="name" required>
124
+ </div>
125
+ <div class="mb-3">
126
+ <label for="deviceDescription" class="form-label">Description</label>
127
+ <input type="text" class="form-control" id="deviceDescription" name="description" required>
128
+ </div>
129
+ <div class="mb-3">
130
+ <label for="deviceId" class="form-label">Device ID</label>
131
+ <input type="text" class="form-control" id="deviceId" name="device_id" required>
132
+ </div>
133
+ <div class="mb-3">
134
+ <label for="devicePassword" class="form-label">Password</label>
135
+ <input type="password" class="form-control" id="devicePassword" name="password" required>
136
+ </div>
137
+ </div>
138
+ <div class="modal-footer">
139
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
140
+ <button type="submit" class="btn btn-primary">Add Device</button>
141
+ </div>
142
+ </form>
143
+ </div>
144
+ </div>
145
+ </div>
146
+
147
+ <!-- Edit Device Modal -->
148
+ <div class="modal fade" id="editDeviceModal" tabindex="-1" aria-hidden="true">
149
+ <div class="modal-dialog">
150
+ <div class="modal-content">
151
+ <div class="modal-header">
152
+ <h5 class="modal-title">Edit Device</h5>
153
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
154
+ </div>
155
+ <form id="editDeviceForm" method="post">
156
+ <div class="modal-body">
157
+ <div class="mb-3">
158
+ <label for="editDeviceName" class="form-label">Name</label>
159
+ <input type="text" class="form-control" id="editDeviceName" name="name" required>
160
+ </div>
161
+ <div class="mb-3">
162
+ <label for="editDeviceDescription" class="form-label">Description</label>
163
+ <input type="text" class="form-control" id="editDeviceDescription" name="description" required>
164
+ </div>
165
+ <div class="mb-3">
166
+ <label for="editDeviceId" class="form-label">Device ID</label>
167
+ <input type="text" class="form-control" id="editDeviceId" name="new_device_id" required>
168
+ </div>
169
+ <div class="mb-3">
170
+ <label for="editDevicePassword" class="form-label">Password</label>
171
+ <input type="password" class="form-control" id="editDevicePassword" name="password" placeholder="Leave blank to keep current password">
172
+ </div>
173
+ </div>
174
+ <div class="modal-footer">
175
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
176
+ <button type="submit" class="btn btn-primary">Save changes</button>
177
+ </div>
178
+ </form>
179
+ </div>
180
+ </div>
181
+ </div>
182
+
183
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
184
  <script>
185
  function editUser(username, email, isAdmin, isActive) {
 
191
  var editUserModal = new bootstrap.Modal(document.getElementById('editUserModal'));
192
  editUserModal.show();
193
  }
194
+
195
+ function addDevice() {
196
+ var addDeviceModal = new bootstrap.Modal(document.getElementById('addDeviceModal'));
197
+ addDeviceModal.show();
198
+ }
199
+
200
+ function editDevice(name, description, deviceId) {
201
+ document.getElementById('editDeviceForm').action = `/admin/edit_device/${deviceId}`;
202
+ document.getElementById('editDeviceName').value = name;
203
+ document.getElementById('editDeviceDescription').value = description;
204
+ document.getElementById('editDeviceId').value = deviceId;
205
+ document.getElementById('editDevicePassword').value = ''; // Clear password field for security
206
+ var editDeviceModal = new bootstrap.Modal(document.getElementById('editDeviceModal'));
207
+ editDeviceModal.show();
208
+ }
209
  </script>
210
  </body>
211
  </html>