per user log management
Browse files- templates/logs.html +5 -5
- todo.md +4 -4
- web/routes.py +59 -12
templates/logs.html
CHANGED
|
@@ -20,7 +20,7 @@
|
|
| 20 |
</button>
|
| 21 |
<button class="btn btn-outline-danger" onclick="confirmDeleteAllLogs()">
|
| 22 |
<i data-feather="trash-2" class="me-1"></i>
|
| 23 |
-
Delete All
|
| 24 |
</button>
|
| 25 |
</div>
|
| 26 |
</div>
|
|
@@ -179,22 +179,22 @@
|
|
| 179 |
<div class="modal-header">
|
| 180 |
<h5 class="modal-title text-danger">
|
| 181 |
<i data-feather="alert-triangle" class="me-2"></i>
|
| 182 |
-
Delete All Logs
|
| 183 |
</h5>
|
| 184 |
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| 185 |
</div>
|
| 186 |
<div class="modal-body">
|
| 187 |
-
<p>Are you sure you want to delete <strong>all logs</strong>? This action cannot be undone.</p>
|
| 188 |
<div class="alert alert-danger">
|
| 189 |
<i data-feather="alert-circle" class="me-2"></i>
|
| 190 |
-
This will permanently delete all system logs from the database.
|
| 191 |
</div>
|
| 192 |
</div>
|
| 193 |
<div class="modal-footer">
|
| 194 |
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
| 195 |
<button type="button" class="btn btn-danger" onclick="deleteAllLogs()">
|
| 196 |
<i data-feather="trash-2" class="me-1"></i>
|
| 197 |
-
Delete All Logs
|
| 198 |
</button>
|
| 199 |
</div>
|
| 200 |
</div>
|
|
|
|
| 20 |
</button>
|
| 21 |
<button class="btn btn-outline-danger" onclick="confirmDeleteAllLogs()">
|
| 22 |
<i data-feather="trash-2" class="me-1"></i>
|
| 23 |
+
{% if current_user and current_user.username == 'admin' %}Delete All Logs{% else %}Delete My Logs{% endif %}
|
| 24 |
</button>
|
| 25 |
</div>
|
| 26 |
</div>
|
|
|
|
| 179 |
<div class="modal-header">
|
| 180 |
<h5 class="modal-title text-danger">
|
| 181 |
<i data-feather="alert-triangle" class="me-2"></i>
|
| 182 |
+
{% if current_user and current_user.username == 'admin' %}Delete All Logs{% else %}Delete My Logs{% endif %}
|
| 183 |
</h5>
|
| 184 |
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| 185 |
</div>
|
| 186 |
<div class="modal-body">
|
| 187 |
+
<p>Are you sure you want to delete {% if current_user and current_user.username == 'admin' %}<strong>all logs</strong>{% else %}<strong>all your logs</strong>{% endif %}? This action cannot be undone.</p>
|
| 188 |
<div class="alert alert-danger">
|
| 189 |
<i data-feather="alert-circle" class="me-2"></i>
|
| 190 |
+
This will permanently delete {% if current_user and current_user.username == 'admin' %}all system logs{% else %}all your logs{% endif %} from the database.
|
| 191 |
</div>
|
| 192 |
</div>
|
| 193 |
<div class="modal-footer">
|
| 194 |
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
| 195 |
<button type="button" class="btn btn-danger" onclick="deleteAllLogs()">
|
| 196 |
<i data-feather="trash-2" class="me-1"></i>
|
| 197 |
+
{% if current_user and current_user.username == 'admin' %}Delete All Logs{% else %}Delete My Logs{% endif %}
|
| 198 |
</button>
|
| 199 |
</div>
|
| 200 |
</div>
|
todo.md
CHANGED
|
@@ -2,9 +2,8 @@ Analyze use cases:
|
|
| 2 |
|
| 3 |
|
| 4 |
What is the value to be extracted from the UX?
|
| 5 |
-
- Just management and tracking of the attestation store, not a webapp
|
| 6 |
-
- Show policy results
|
| 7 |
-
- Show product table with policy result tags
|
| 8 |
- SBOM app: search, vulns, etc.
|
| 9 |
- Discovery app
|
| 10 |
- verify signature + support PKI
|
|
@@ -16,4 +15,5 @@ Tasks:
|
|
| 16 |
- Fix attestation table: let choose labels/context fields to use. Show the whole Json of the attestation in a JSON viewer
|
| 17 |
- Implement/Fix deduplication
|
| 18 |
- download attestation: preserve file name if possible (for UX generated)
|
| 19 |
-
- Webhooks/attestation datamodel: add link for downloading the attestation from the app (better --> in data model)
|
|
|
|
|
|
| 2 |
|
| 3 |
|
| 4 |
What is the value to be extracted from the UX?
|
| 5 |
+
- Just management and tracking of the attestation store, not a webapp: **Done**
|
| 6 |
+
- Show policy results, Show product table with policy result tags
|
|
|
|
| 7 |
- SBOM app: search, vulns, etc.
|
| 8 |
- Discovery app
|
| 9 |
- verify signature + support PKI
|
|
|
|
| 15 |
- Fix attestation table: let choose labels/context fields to use. Show the whole Json of the attestation in a JSON viewer
|
| 16 |
- Implement/Fix deduplication
|
| 17 |
- download attestation: preserve file name if possible (for UX generated)
|
| 18 |
+
- Webhooks/attestation datamodel: add link for downloading the attestation from the app (better --> in data model) - **No need, we have the ID and there is the evidence API**
|
| 19 |
+
- Remove filter automatically logs: don't show app logs in the UI (post and get logs)
|
web/routes.py
CHANGED
|
@@ -243,6 +243,11 @@ def logs():
|
|
| 243 |
|
| 244 |
# Build query
|
| 245 |
query = SystemLog.query
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
|
| 247 |
if severity:
|
| 248 |
try:
|
|
@@ -325,9 +330,18 @@ def log_detail(log_id):
|
|
| 325 |
"""Return a single log entry as JSON by ID"""
|
| 326 |
if 'user_id' not in session:
|
| 327 |
return jsonify({'error': 'Unauthorized'}), 401
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
log = SystemLog.query.get(log_id)
|
| 329 |
if not log:
|
| 330 |
return jsonify({'error': 'Log not found'}), 404
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
def log_to_dict(log):
|
| 332 |
return {
|
| 333 |
"id": log.id,
|
|
@@ -354,27 +368,44 @@ def log_stats():
|
|
| 354 |
if 'user_id' not in session:
|
| 355 |
return redirect(url_for('web.login'))
|
| 356 |
|
|
|
|
|
|
|
|
|
|
| 357 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
# Get log counts by severity
|
| 359 |
severity_stats = db.session.query(
|
| 360 |
SystemLog.severity,
|
| 361 |
db.func.count(SystemLog.id).label('count')
|
| 362 |
-
)
|
|
|
|
|
|
|
|
|
|
| 363 |
|
| 364 |
# Get log counts by event type
|
| 365 |
event_stats = db.session.query(
|
| 366 |
SystemLog.event_type,
|
| 367 |
db.func.count(SystemLog.id).label('count')
|
| 368 |
-
)
|
|
|
|
|
|
|
|
|
|
| 369 |
|
| 370 |
# Get log counts by app name
|
| 371 |
app_stats = db.session.query(
|
| 372 |
SystemLog.app_name,
|
| 373 |
db.func.count(SystemLog.id).label('count')
|
| 374 |
-
)
|
|
|
|
|
|
|
|
|
|
| 375 |
|
| 376 |
# Total logs
|
| 377 |
-
total_logs =
|
| 378 |
|
| 379 |
return jsonify({
|
| 380 |
'total_logs': total_logs,
|
|
@@ -395,20 +426,29 @@ def log_stats():
|
|
| 395 |
@web_bp.route('/ux/logs/delete', methods=['POST'])
|
| 396 |
@login_required
|
| 397 |
def delete_logs():
|
| 398 |
-
"""Delete
|
| 399 |
if 'user_id' not in session:
|
| 400 |
return jsonify({'error': 'Unauthorized'}), 401
|
| 401 |
|
|
|
|
|
|
|
|
|
|
| 402 |
try:
|
| 403 |
-
# Delete all logs
|
| 404 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
db.session.commit()
|
| 406 |
|
| 407 |
log_event(
|
| 408 |
severity=6,
|
| 409 |
-
message=
|
| 410 |
event_type="logs_deleted",
|
| 411 |
-
user_id=
|
| 412 |
source_ip=request.remote_addr
|
| 413 |
)
|
| 414 |
|
|
@@ -424,7 +464,7 @@ def delete_logs():
|
|
| 424 |
severity=3,
|
| 425 |
message=f"Failed to delete logs: {str(e)}",
|
| 426 |
event_type="delete_logs_error",
|
| 427 |
-
user_id=
|
| 428 |
)
|
| 429 |
return jsonify({'error': str(e)}), 500
|
| 430 |
|
|
@@ -438,6 +478,9 @@ def status():
|
|
| 438 |
# Get system information
|
| 439 |
config = current_app.config.get('STORAGE_CONFIG')
|
| 440 |
|
|
|
|
|
|
|
|
|
|
| 441 |
stats = {
|
| 442 |
'storage_type': config.STORAGE_TYPE if config else 'Unknown',
|
| 443 |
'total_users': User.query.count(),
|
|
@@ -446,8 +489,12 @@ def status():
|
|
| 446 |
'total_logs': SystemLog.query.count(),
|
| 447 |
}
|
| 448 |
|
| 449 |
-
# Get recent activity
|
| 450 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
SystemLog.timestamp.desc()
|
| 452 |
).limit(10).all()
|
| 453 |
|
|
|
|
| 243 |
|
| 244 |
# Build query
|
| 245 |
query = SystemLog.query
|
| 246 |
+
|
| 247 |
+
# Filter by user_id unless user is admin
|
| 248 |
+
user_id = session['user_id']
|
| 249 |
+
if not is_admin_user_web(user_id):
|
| 250 |
+
query = query.filter(SystemLog.user_id == user_id)
|
| 251 |
|
| 252 |
if severity:
|
| 253 |
try:
|
|
|
|
| 330 |
"""Return a single log entry as JSON by ID"""
|
| 331 |
if 'user_id' not in session:
|
| 332 |
return jsonify({'error': 'Unauthorized'}), 401
|
| 333 |
+
|
| 334 |
+
user_id = session['user_id']
|
| 335 |
+
is_admin = is_admin_user_web(user_id)
|
| 336 |
+
|
| 337 |
+
# Get the log
|
| 338 |
log = SystemLog.query.get(log_id)
|
| 339 |
if not log:
|
| 340 |
return jsonify({'error': 'Log not found'}), 404
|
| 341 |
+
|
| 342 |
+
# Check if user has permission to view this log
|
| 343 |
+
if not is_admin and log.user_id != user_id:
|
| 344 |
+
return jsonify({'error': 'You do not have permission to view this log'}), 403
|
| 345 |
def log_to_dict(log):
|
| 346 |
return {
|
| 347 |
"id": log.id,
|
|
|
|
| 368 |
if 'user_id' not in session:
|
| 369 |
return redirect(url_for('web.login'))
|
| 370 |
|
| 371 |
+
user_id = session['user_id']
|
| 372 |
+
is_admin = is_admin_user_web(user_id)
|
| 373 |
+
|
| 374 |
try:
|
| 375 |
+
# Base queries - filter by user_id unless admin
|
| 376 |
+
base_query = SystemLog.query
|
| 377 |
+
if not is_admin:
|
| 378 |
+
base_query = base_query.filter_by(user_id=user_id)
|
| 379 |
+
|
| 380 |
# Get log counts by severity
|
| 381 |
severity_stats = db.session.query(
|
| 382 |
SystemLog.severity,
|
| 383 |
db.func.count(SystemLog.id).label('count')
|
| 384 |
+
)
|
| 385 |
+
if not is_admin:
|
| 386 |
+
severity_stats = severity_stats.filter(SystemLog.user_id == user_id)
|
| 387 |
+
severity_stats = severity_stats.group_by(SystemLog.severity).all()
|
| 388 |
|
| 389 |
# Get log counts by event type
|
| 390 |
event_stats = db.session.query(
|
| 391 |
SystemLog.event_type,
|
| 392 |
db.func.count(SystemLog.id).label('count')
|
| 393 |
+
)
|
| 394 |
+
if not is_admin:
|
| 395 |
+
event_stats = event_stats.filter(SystemLog.user_id == user_id)
|
| 396 |
+
event_stats = event_stats.group_by(SystemLog.event_type).limit(10).all()
|
| 397 |
|
| 398 |
# Get log counts by app name
|
| 399 |
app_stats = db.session.query(
|
| 400 |
SystemLog.app_name,
|
| 401 |
db.func.count(SystemLog.id).label('count')
|
| 402 |
+
)
|
| 403 |
+
if not is_admin:
|
| 404 |
+
app_stats = app_stats.filter(SystemLog.user_id == user_id)
|
| 405 |
+
app_stats = app_stats.group_by(SystemLog.app_name).limit(10).all()
|
| 406 |
|
| 407 |
# Total logs
|
| 408 |
+
total_logs = base_query.count()
|
| 409 |
|
| 410 |
return jsonify({
|
| 411 |
'total_logs': total_logs,
|
|
|
|
| 426 |
@web_bp.route('/ux/logs/delete', methods=['POST'])
|
| 427 |
@login_required
|
| 428 |
def delete_logs():
|
| 429 |
+
"""Delete logs endpoint"""
|
| 430 |
if 'user_id' not in session:
|
| 431 |
return jsonify({'error': 'Unauthorized'}), 401
|
| 432 |
|
| 433 |
+
user_id = session['user_id']
|
| 434 |
+
is_admin = is_admin_user_web(user_id)
|
| 435 |
+
|
| 436 |
try:
|
| 437 |
+
# Delete logs - if admin delete all, otherwise only user's logs
|
| 438 |
+
if is_admin:
|
| 439 |
+
count = SystemLog.query.delete()
|
| 440 |
+
message = f"All logs deleted by admin (count: {count})"
|
| 441 |
+
else:
|
| 442 |
+
count = SystemLog.query.filter_by(user_id=user_id).delete()
|
| 443 |
+
message = f"User logs deleted by user (count: {count})"
|
| 444 |
+
|
| 445 |
db.session.commit()
|
| 446 |
|
| 447 |
log_event(
|
| 448 |
severity=6,
|
| 449 |
+
message=message,
|
| 450 |
event_type="logs_deleted",
|
| 451 |
+
user_id=user_id,
|
| 452 |
source_ip=request.remote_addr
|
| 453 |
)
|
| 454 |
|
|
|
|
| 464 |
severity=3,
|
| 465 |
message=f"Failed to delete logs: {str(e)}",
|
| 466 |
event_type="delete_logs_error",
|
| 467 |
+
user_id=user_id
|
| 468 |
)
|
| 469 |
return jsonify({'error': str(e)}), 500
|
| 470 |
|
|
|
|
| 478 |
# Get system information
|
| 479 |
config = current_app.config.get('STORAGE_CONFIG')
|
| 480 |
|
| 481 |
+
user_id = session['user_id']
|
| 482 |
+
is_admin = is_admin_user_web(user_id)
|
| 483 |
+
|
| 484 |
stats = {
|
| 485 |
'storage_type': config.STORAGE_TYPE if config else 'Unknown',
|
| 486 |
'total_users': User.query.count(),
|
|
|
|
| 489 |
'total_logs': SystemLog.query.count(),
|
| 490 |
}
|
| 491 |
|
| 492 |
+
# Get recent activity - filter by user unless admin
|
| 493 |
+
log_query = SystemLog.query
|
| 494 |
+
if not is_admin:
|
| 495 |
+
log_query = log_query.filter_by(user_id=user_id)
|
| 496 |
+
|
| 497 |
+
recent_logs = log_query.order_by(
|
| 498 |
SystemLog.timestamp.desc()
|
| 499 |
).limit(10).all()
|
| 500 |
|