Skip to content

Commit

Permalink
Merge pull request #2 from rasulkireev/status-page
Browse files Browse the repository at this point in the history
Add Project Status Page
  • Loading branch information
rasulkireev authored Oct 20, 2024
2 parents d64265f + 7ab64e8 commit cc7ebaf
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 48 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ repos:
- id: isort
name: isort (python)

- repo: https://github.com/Riverside-Healthcare/djLint
rev: v1.19.16
- repo: https://github.com/djlint/djLint
rev: v1.35.2
hooks:
- id: djlint-django

Expand Down
55 changes: 29 additions & 26 deletions core/views.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
from datetime import timedelta
from urllib.parse import urlencode

from core.views_utils import StatusSummaryMixin
import stripe

from allauth.account.models import EmailAddress
from allauth.account.utils import send_email_confirmation
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import redirect
from django.conf import settings
from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.views.generic import TemplateView, UpdateView, ListView, DetailView, CreateView, FormView
from django.shortcuts import get_object_or_404

from django.views.generic import CreateView, DetailView, FormView, ListView, TemplateView, UpdateView
from djstripe import models as djstripe_models

from core.forms import ProfileUpdateForm, ServiceForm
from core.models import Profile, BlogPost, Project
from core.models import BlogPost, Profile, Project
from core.utils import check_if_profile_has_pro_subscription

from core.views_utils import StatusSummaryMixin
from statushen.utils import get_statushen_logger

stripe.api_key = settings.STRIPE_SECRET_KEY

logger = get_statushen_logger(__name__)


class HomeView(StatusSummaryMixin, TemplateView):
template_name = "pages/home.html"

Expand All @@ -38,7 +33,7 @@ def get_context_data(self, **kwargs):

if user.is_authenticated:
profile = self.request.user.profile
user_projects = Project.objects.filter(profile=profile).prefetch_related('services', 'services__statuses')
user_projects = Project.objects.filter(profile=profile).prefetch_related("services", "services__statuses")

for project in user_projects:
self.add_status_summary_to_services(project.services.all(), number_of_sticks=100)
Expand All @@ -52,7 +47,6 @@ def get_context_data(self, **kwargs):
elif payment_status == "failed":
messages.error(self.request, "Something went wrong with the payment.")


return context


Expand All @@ -77,7 +71,7 @@ def get_context_data(self, **kwargs):
context["resend_confirmation_url"] = reverse("resend_confirmation")
context["has_subscription"] = profile.subscription is not None

user_projects = profile.projects.all().prefetch_related('services', 'services__statuses')
user_projects = profile.projects.all().prefetch_related("services", "services__statuses")
for project in user_projects:
self.add_status_summary_to_services(project.services.all())

Expand Down Expand Up @@ -182,9 +176,9 @@ class BlogPostView(DetailView):

class CreateProjectView(LoginRequiredMixin, CreateView):
model = Project
template_name = 'projects/create_project.html'
fields = ['name', 'slug', 'icon', 'public']
success_url = reverse_lazy('home')
template_name = "projects/create_project.html"
fields = ["name", "slug", "icon", "public"]
success_url = reverse_lazy("home")

def form_valid(self, form):
form.instance.profile = self.request.user.profile
Expand All @@ -193,31 +187,40 @@ def form_valid(self, form):
return response


class ProjectStatusPageView(DetailView):
class ProjectStatusPageView(StatusSummaryMixin, DetailView):
model = Project
template_name = 'projects/project_status.html'
context_object_name = 'project'
template_name = "projects/project_status.html"
context_object_name = "project"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
services = self.object.services.all()

# Add status summary to services (24 hours, 24 sticks)
self.add_status_summary_to_services(services, days=1, number_of_sticks=45)

# Get overall project status (90 days, 90 sticks)
context["project_overall_status"] = self.get_overall_project_status(services, days=90, number_of_sticks=90)

context["services"] = services
return context


class ProjectSettingsView(StatusSummaryMixin, LoginRequiredMixin, FormView):
template_name = 'projects/project_settings.html'
template_name = "projects/project_settings.html"
form_class = ServiceForm

def dispatch(self, request, *args, **kwargs):
self.project = get_object_or_404(Project, slug=self.kwargs['slug'])
self.project = get_object_or_404(Project, slug=self.kwargs["slug"])
return super().dispatch(request, *args, **kwargs)

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['project'] = self.project
context["project"] = self.project
services = self.project.services.all()

self.add_status_summary_to_services(services)
context['services'] = services
context["services"] = services
return context

def form_valid(self, form):
Expand All @@ -228,4 +231,4 @@ def form_valid(self, form):
return super().form_valid(form)

def get_success_url(self):
return reverse('project-settings', kwargs={'slug': self.project.slug})
return reverse("project-settings", kwargs={"slug": self.project.slug})
60 changes: 43 additions & 17 deletions core/views_utils.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,55 @@
from django.utils import timezone
from datetime import timedelta

from django.utils import timezone


class StatusSummaryMixin:
@staticmethod
def get_status_summary(statuses, now, start_time, number_of_sticks):
def get_status_summary(statuses, end_time, start_time, number_of_sticks):
summary = []
duration = end_time - start_time
interval = duration / number_of_sticks

for i in range(number_of_sticks):
minutes = 60*24 / number_of_sticks # Each stick represents X minutes
stick_time = start_time + timedelta(minutes=i*minutes)
status = statuses.filter(checked_at__lte=stick_time).last()
if status:
if status.is_up:
summary.append('up')
elif status.is_down:
summary.append('down')
stick_end = start_time + interval * (i + 1)
stick_start = start_time + interval * i

stick_statuses = [s for s in statuses if stick_start <= s.checked_at < stick_end]

if stick_statuses:
# Prioritize 'down' status, then 'degraded', then 'up'
if any(s.status == "DOWN" for s in stick_statuses):
summary.append("down")
elif any(s.status == "DEGRADED" for s in stick_statuses):
summary.append("degraded")
elif any(s.status == "UP" for s in stick_statuses):
summary.append("up")
else:
summary.append('unknown')
summary.append("unknown")
else:
summary.append('unknown')
summary.append("unknown")

return summary

def add_status_summary_to_services(self, services, number_of_sticks=85):
now = timezone.now()
twenty_four_hours_ago = now - timedelta(hours=24)
def add_status_summary_to_services(self, services, days=1, number_of_sticks=24):
end_time = timezone.now()
start_time = end_time - timedelta(days=days)

for service in services:
statuses = list(service.statuses.filter(checked_at__gte=start_time).order_by("checked_at"))
service.status_summary = self.get_status_summary(statuses, end_time, start_time, number_of_sticks)

# Add current status
latest_status = statuses[-1] if statuses else None
service.current_status = latest_status.status.lower() if latest_status else "unknown"

def get_overall_project_status(self, services, days=90, number_of_sticks=90):
end_time = timezone.now()
start_time = end_time - timedelta(days=days)

all_statuses = []
for service in services:
statuses = service.statuses.filter(checked_at__gte=twenty_four_hours_ago).order_by('checked_at')
service.status_summary = self.get_status_summary(statuses, now, twenty_four_hours_ago, number_of_sticks)
service_statuses = list(service.statuses.filter(checked_at__gte=start_time))
all_statuses.extend(service_statuses)

return self.get_status_summary(all_statuses, end_time, start_time, number_of_sticks)
5 changes: 2 additions & 3 deletions frontend/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@

<script defer data-domain="statushen.com" src="https://plausible-v2.cr.lvtd.dev/js/script.js"></script>


{% stylesheet_pack 'index' %}
{% javascript_pack 'index' attrs='defer' %}
</head>
Expand Down Expand Up @@ -87,8 +86,8 @@
<div class="flow-root mt-6">
<div class="flex flex-col justify-start space-y-1">
<a href="{% url 'home' %}" class="flex flex-row justify-center items-center p-1.5 -m-1.5 mb-2 space-x-2">
<img class="w-auto h-8" src="{% static 'vendors/images/logo.png' %}" alt="OSIG Logo" />
<span class="text-base font-semibold text-gray-700">OSIG</span>
<img class="w-auto h-8" src="{% static 'vendors/images/logo.png' %}" alt="StatusHen Logo" />
<span class="text-base font-semibold text-gray-700">StatusHen</span>
</a>
<a href="{% url 'home' %}" class="px-3 py-2.5 -mx-3 text-base font-semibold leading-7 text-center text-gray-900 rounded-lg hover:bg-gray-50">
Home
Expand Down
Loading

0 comments on commit cc7ebaf

Please sign in to comment.