Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added notification status for priotiy
  • Loading branch information
yogeshojha committed Aug 31, 2024
commit f18e55e8f14838af40dab56b524c99d7dc43bd93
2 changes: 1 addition & 1 deletion web/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
class InAppNotificationSerializer(serializers.ModelSerializer):
class Meta:
model = InAppNotification
fields = ['id', 'title', 'description', 'icon', 'is_read', 'created_at', 'notification_type', 'project']
fields = ['id', 'title', 'description', 'icon', 'is_read', 'created_at', 'notification_type', 'status', 'project']
read_only_fields = ['id', 'created_at']

def get_project_name(self, obj):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.23 on 2024-08-31 01:21

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dashboard', '0004_rename_notification_inappnotification'),
]

operations = [
migrations.AlterField(
model_name='inappnotification',
name='notification_type',
field=models.CharField(choices=[('system', 'system'), ('project', 'project')], default='system', max_length=10),
),
]
18 changes: 18 additions & 0 deletions web/dashboard/migrations/0006_inappnotification_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.23 on 2024-08-31 02:29

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dashboard', '0005_alter_inappnotification_notification_type'),
]

operations = [
migrations.AddField(
model_name='inappnotification',
name='status',
field=models.CharField(choices=[('success', 'Success'), ('info', 'Informational'), ('warning', 'Warning'), ('error', 'Error')], default='info', max_length=10),
),
]
7 changes: 2 additions & 5 deletions web/dashboard/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.db import models

from reNgine.definitions import *

class SearchHistory(models.Model):
query = models.CharField(max_length=1000)
Expand Down Expand Up @@ -44,12 +44,9 @@ def __str__(self):


class InAppNotification(models.Model):
NOTIFICATION_TYPES = (
('system', 'System-wide'),
('project', 'Project-specific'),
)
project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True, blank=True)
notification_type = models.CharField(max_length=10, choices=NOTIFICATION_TYPES, default='system')
status = models.CharField(max_length=10, choices=NOTIFICATION_STATUS_TYPES, default='info')
title = models.CharField(max_length=255)
description = models.TextField()
icon = models.CharField(max_length=50) # mdi icon class name
Expand Down
15 changes: 15 additions & 0 deletions web/reNgine/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,3 +548,18 @@

# OSINT GooFuzz Path
GOFUZZ_EXEC_PATH = '/usr/src/github/goofuzz/GooFuzz'


# In App Notification Definitions
SYSTEM_LEVEL_NOTIFICATION = 'system'
PROJECT_LEVEL_NOTIFICATION = 'project'
NOTIFICATION_TYPES = (
('system', SYSTEM_LEVEL_NOTIFICATION),
('project', PROJECT_LEVEL_NOTIFICATION),
)
NOTIFICATION_STATUS_TYPES = (
('success', 'Success'),
('info', 'Informational'),
('warning', 'Warning'),
('error', 'Error'),
)
78 changes: 67 additions & 11 deletions web/static/custom/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ mark{
}

.notification-panel-dropdown {
width: 380px;
width: 400px;
border: none;
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
Expand Down Expand Up @@ -452,47 +452,87 @@ mark{
}

.notification-panel-item {
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
transition: background-color 0.2s ease;
border-left: 4px solid transparent;
transition: all 0.3s ease;
margin-bottom: 2px;
}

.notification-panel-item:last-child {
border-bottom: none;
}

.notification-panel-item:hover {
background-color: #f8f9fa;
transform: translateY(-2px);
box-shadow: 0 2px 0px rgba(0, 0, 0, 0.10);
}

/* status */
.notification-panel-status-success {
border-left-color: #10B981;
}

.notification-panel-status-info {
border-left-color: #3B82F6;
}

.notification-panel-status-warning {
border-left-color: #F59E0B;
}

.notification-panel-status-error {
border-left-color: #EF4444;
}

.notification-panel-unread {
background-color: #fafbff;
border-left: 3px solid #4a90e2;
background-color: #F3F4F6;
}

.notification-panel-unread:hover {
background-color: #f0f4ff;
background-color: #E5E7EB;
}

.notification-panel-icon {
font-size: 1.25rem;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
background-color: #E5E7EB;
}

.notification-panel-title {
color: #212529;
font-size: 0.95rem;
font-weight: 600;
}

.notification-panel-status-success .notification-panel-title {
color: #10B981;
}

.notification-panel-status-info .notification-panel-title {
color: #3B82F6;
}

.notification-panel-status-warning .notification-panel-title {
color: #F59E0B;
}

.notification-panel-status-error .notification-panel-title {
color: #EF4444;
}

.notification-panel-description {
color: #6c757d;
color: #4B5563;
font-size: 0.875rem;
margin-bottom: 0.25rem;
line-height: 1.4;
}

.notification-panel-time {
color: #adb5bd;
font-size: 0.75rem;
color: #6B7280;
font-size: 0.8rem;
}

.notification-panel-footer {
Expand All @@ -512,6 +552,22 @@ mark{
color: #0056b3;
}

.notification-panel-status-success .notification-panel-icon {
color: #10B981;
}

.notification-panel-status-info .notification-panel-icon {
color: #3B82F6;
}

.notification-panel-status-warning .notification-panel-icon {
color: #F59E0B;
}

.notification-panel-status-error .notification-panel-icon {
color: #EF4444;
}

@keyframes notification-buzz {
0% { transform: translateX(0) rotate(0); }
15% { transform: translateX(-5px) rotate(-5deg); }
Expand Down
53 changes: 47 additions & 6 deletions web/static/custom/notification.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// notifications.js
// all the functions and event listeners for the notification panel

// notifications.js

function updateNotifications() {
let api_url = "/api/notifications/";
const currentProjectSlug = getCurrentProjectSlug();
Expand Down Expand Up @@ -34,7 +36,7 @@ function updateNotifications() {
const notificationItem = document.createElement("div");
notificationItem.className = `notification-panel-item d-flex align-items-start p-3 ${
notification.is_read ? "" : "notification-panel-unread"
}`;
} notification-panel-status-${notification.status}`;
notificationItem.innerHTML = `
<div class="notification-panel-content flex-grow-1">
<div class="d-flex justify-content-between align-items-center mb-2">
Expand All @@ -48,9 +50,9 @@ function updateNotifications() {
<p class="notification-panel-description mb-1">${
notification.description
}</p>
<small class="notification-panel-time">${new Date(
notification.created_at
).toLocaleString()}</small>
<small class="notification-panel-time">${timeago.format(
new Date(notification.created_at)
)}</small>
</div>
`;
notificationItem.addEventListener("click", () =>
Expand Down Expand Up @@ -123,13 +125,52 @@ document.addEventListener("DOMContentLoaded", () => {
const clearAllLink = document.querySelector("#clear-notif-btn");
clearAllLink.addEventListener("click", clearAllNotifications);

const markAllReadBtn = document.querySelector("#mark-all-read-btn");
markAllReadBtn.addEventListener("click", markAllAsRead);

// Update notifications every 30 seconds
updateNotifications();
setInterval(updateNotifications, 30000);
});

setInterval(updateTimes, 60000);
});

function getCurrentProjectSlug() {
const hiddenInput = document.querySelector('input[name="current_project"]');
return hiddenInput ? hiddenInput.value : null;
}
}

function markAllAsRead() {
fetch("/api/notifications/mark_all_read/", {
method: "POST",
credentials: "same-origin",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.ok) {
document
.querySelectorAll(".notification-panel-item")
.forEach((item) => {
item.classList.remove("notification-panel-unread");
});
updateUnreadCount();
}
})
.catch((error) =>
console.error("Error marking all notifications as read:", error)
);
}

function updateTimes() {
document
.querySelectorAll(".notification-panel-time")
.forEach((timeElement) => {
const datetime = timeElement.getAttribute("datetime");
if (datetime) {
timeElement.textContent = timeago.format(new Date(datetime));
}
});
}
11 changes: 6 additions & 5 deletions web/templates/base/_items/top_bar.html
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,15 @@ <h6 class="text-overflow m-0">Welcome {{user.get_username}}!</h6>
<div class="dropdown-menu dropdown-menu-end notification-panel-dropdown">
<div class="notification-panel-header d-flex justify-content-between align-items-center p-3">
<h6 class="m-0">Notifications</h6>
<a href="javascript: void(0);" class="notification-panel-clear-link" id="clear-notif-btn">Clear All</a>
<div>
<a href="javascript: void(0);" class="notification-panel-mark-read-link me-3" id="mark-all-read-btn">Mark all as read</a>
<a href="javascript: void(0);" class="notification-panel-clear-link" id="clear-notif-btn">Clear All</a>
</div>
</div>
<div class="px-1" style="max-height: 400px;" data-simplebar>
<div class="notification-panel-body">
{% comment %} populate the notifications here {% endcomment %}
</div>
<div class="notification-panel-footer text-center p-2">
<a href="javascript:void(0);" class="notification-panel-view-all">Mark all as read</a>
<!-- populate the notifications here -->
</div>
</div>
</div>
</li>
Expand Down
1 change: 1 addition & 0 deletions web/templates/base/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ <h4 class="page-title">{% block page_title %}{% endblock page_title %}</h4>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.datatables.net/rowgroup/1.4.0/js/dataTables.rowGroup.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/timeago.js/4.0.2/timeago.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
// for tabs with urls, we need to append the hash in the url
Expand Down