heartwood every commit a ring

Add lighthouse score checking

4d3f9f3b by Isaac Bythewood · 3 years ago

modified accounts/models.py
@@ -29,13 +29,3 @@ class User(AbstractUser):        for property in self.properties.all():            total_properties_down += property.current_status != 200        return total_properties_down    @property    def avg_response_time(self):        avg_response_times = []        for property in self.properties.all():            avg_response_times.append(property.avg_response_time)        try:            return int(sum(avg_response_times) / len(avg_response_times))        except ZeroDivisionError:            return 0
modified properties/management/commands/scheduler.py
@@ -19,19 +19,24 @@ class Command(BaseCommand):    def thread_target(self, property_id):        property = Property.objects.get(id=property_id)        self.stdout.write("[Scheduler] Checking status {}".format(property.url))        property.process_check()        self.stdout.write("[Scheduler] Checked {}".format(property.url))    def thread_target_lighthouse(self, property_id):        property = Property.objects.get(id=property_id)        self.stdout.write("[Scheduler] Checking lighthouse {}".format(property.url))        property.process_check_lighthouse()    def handle(self, *args, **options):        self.stdout.write("[Scheduler] Starting scheduler...")        while True:            # Do our standard checks            properties = [p for p in Property.objects.all() if p.should_check()]            for p in properties:                p.next_run_at = p.get_next_run_at()                p.last_run_at = timezone.now()                p.save()                p.save(update_fields=["next_run_at", "last_run_at"])            properties = [p.id for p in properties]            db.connections.close_all()
@@ -42,6 +47,22 @@ class Command(BaseCommand):            self.clean_checks()            # Do our daily lighthouse checks            # Only run 1 of these checks per loop to avoid overloading the server            properties = [p for p in Property.objects.all() if p.should_check_lighthouse()]            properties = properties[:1]            for p in properties:                p.next_lighthouse_run_at = p.get_next_run_at_lighthouse()                p.last_lighthouse_run_at = timezone.now()                p.save(update_fields=["next_lighthouse_run_at", "last_lighthouse_run_at"])            properties = [p.id for p in properties]            db.connections.close_all()            for p_id in properties:                t = threading.Thread(target=self.thread_target_lighthouse, args=(p_id,))                t.daemon = True                t.start()            self.stdout.write("[Scheduler] Sleeping scheduler for 30 seconds...")            try:                time.sleep(30)
added properties/migrations/0002_property_last_lighthouse_run_at_and_more.py
@@ -0,0 +1,28 @@# Generated by Django 4.0.6 on 2022-07-15 17:23from django.db import migrations, modelsclass Migration(migrations.Migration):    dependencies = [        ('properties', '0001_initial'),    ]    operations = [        migrations.AddField(            model_name='property',            name='last_lighthouse_run_at',            field=models.DateTimeField(blank=True, null=True),        ),        migrations.AddField(            model_name='property',            name='lighthouse_scores',            field=models.JSONField(blank=True, null=True),        ),        migrations.AddField(            model_name='property',            name='next_lighthouse_run_at',            field=models.DateTimeField(blank=True, null=True),        ),    ]
modified properties/models.py
@@ -9,6 +9,8 @@ from django.template.loader import render_to_stringfrom django.utils import timezonefrom django.utils.functional import cached_propertyfrom status.lighthouse import fetch_lighthouse_results, parse_lighthouse_resultsUser = get_user_model()
@@ -154,6 +156,10 @@ class Property(AlertsMixin, SecurityMixin, models.Model):    last_run_at = models.DateTimeField(blank=True, null=True)    next_run_at = models.DateTimeField(blank=True, null=True)    lighthouse_scores = models.JSONField(blank=True, null=True)    last_lighthouse_run_at = models.DateTimeField(blank=True, null=True)    next_lighthouse_run_at = models.DateTimeField(blank=True, null=True)    created_at = models.DateTimeField(auto_now_add=True)    updated_at = models.DateTimeField(auto_now=True)
@@ -236,6 +242,35 @@ class Property(AlertsMixin, SecurityMixin, models.Model):        if check.status_code != 200:            self.send_alerts()    def get_next_run_at_lighthouse(self):        """        Should check daily.        """        return timezone.now() + timezone.timedelta(days=1)    def should_check_lighthouse(self):        now = timezone.now()        if self.lighthouse_scores is None:            return True        if self.last_lighthouse_run_at is None:            return True        if self.next_lighthouse_run_at is None:            return True        return self.next_lighthouse_run_at <= now    def process_check_lighthouse(self):        self.run_check_lighthouse()    def run_check_lighthouse(self):        try:            results = fetch_lighthouse_results(self.url)            scores = parse_lighthouse_results(results)            if scores:                self.lighthouse_scores = scores                self.save(update_fields=["lighthouse_scores"])        except Exception:            pass    @property    def total_checks(self):        return self.statuses.count()
@@ -268,6 +303,11 @@ class Property(AlertsMixin, SecurityMixin, models.Model):        except Check.DoesNotExist:            return {}    @cached_property    def avg_lighthouse_score(self):        if self.lighthouse_scores:            scores = [score for score in self.lighthouse_scores.values()]            return round(sum(scores) / len(scores))class Check(models.Model):    property = models.ForeignKey(
modified properties/templates/properties/properties.html
@@ -94,12 +94,6 @@              <p class="card-text">Properties down</p>            </div>          </div>          <div class="col-6 col-md-2 d-flex align-items-center">            <div class="card-body py-1">              <div class="card-title h3 mb-0">{{ user.avg_response_time }}</div>              <p class="card-text">Avg. response time</p>            </div>          </div>          <div class="col-6 col-md-2 d-flex align-items-center">            <div class="card-body py-1">              <div class="card-title h3 mb-0">{{ user.total_checks }}</div>
@@ -121,7 +115,7 @@                    Delete                  </button>                </div>                <div class="modal fade" id="delete-modal-{{ property.id }}" tabindex="-1" aria-labelledby="delete-modal-{{ property.id }}-label" aria-hidden="true">                <div class="modal fade text-dark" id="delete-modal-{{ property.id }}" tabindex="-1" aria-labelledby="delete-modal-{{ property.id }}-label" aria-hidden="true">                  <div class="modal-dialog">                    <div class="modal-content">                      <div class="modal-header">
@@ -159,10 +153,16 @@                <div class="card-text text-truncate small">Security</div>              </div>            </div>            <div class="col-6 col-md-2 d-none d-md-flex d-flex align-items-center text-white {% if property.avg_response_time > 500 %}bg-danger{% else %}bg-success{% endif %}">            <div class="col-6 col-md-2 d-none d-md-flex d-flex align-items-center text-white {% if property.avg_lighthouse_score < 70 %}bg-warning{% else %}bg-success{% endif %}">              <div class="card-body">                <div class="card-title h4">{% if property.avg_response_time > 500 %}Unhealthy{% else %}Ok{% endif %}</div>                <div class="card-text text-truncate small">Response time</div>                <div class="card-title h4">                  {% if property.avg_lighthouse_score %}                    {{ property.avg_lighthouse_score }}%                  {% else %}                    Checking...                  {% endif %}                </div>                <div class="card-text text-truncate small">Avg. LH score</div>              </div>            </div>          </div>
modified properties/templates/properties/property.html
@@ -56,7 +56,7 @@          <button type="button" class="btn btn-sm btn-outline-danger ms-1 ms-lg-3 my-1" data-bs-toggle="modal" data-bs-target="#delete-modal-{{ property.id }}">            Delete          </button>          <div class="modal fade" id="delete-modal-{{ property.id }}" tabindex="-1" aria-labelledby="delete-modal-{{ property.id }}-label" aria-hidden="true">          <div class="modal fade text-dark" id="delete-modal-{{ property.id }}" tabindex="-1" aria-labelledby="delete-modal-{{ property.id }}-label" aria-hidden="true">            <div class="modal-dialog">              <div class="modal-content">                <div class="modal-header">
@@ -81,27 +81,27 @@</div><div class="container-fluid">  <div class="row mb-4">  <div class="row {% if not property.lighthouse_scores %}mb-4{% endif %}">    <div class="col-6 col-md-3 d-flex align-items-center text-white {% if property.current_status == 200 %}bg-success{% else %}bg-danger{% endif %}">      <div class="card-body">      <div class="card-body text-center">        <div class="card-title h4">{% if property.current_status == 200 %}Ok{% else %}Failed{% endif %}</div>        <div class="card-text text-truncate small">Current status</div>      </div>    </div>    <div class="col-6 col-md-3 d-flex align-items-center text-white {% if not property.invalid_cert %}bg-success{% else %}bg-danger{% endif %}">      <div class="card-body">      <div class="card-body text-center">        <div class="card-title h4">{% if not property.invalid_cert %}Ok{% else %}Unhealthy{% endif %}</div>        <div class="card-text text-truncate small">Certificate</div>      </div>    </div>    <div class="col-6 col-md-3 d-none d-md-flex d-flex align-items-center text-white {% if not property.has_security_issue %}bg-success{% else %}bg-danger{% endif %}">      <div class="card-body">      <div class="card-body text-center">        <div class="card-title h4">{% if not property.has_security_issue %}Ok{% else %}Failed{% endif %}</div>        <div class="card-text text-truncate small">Security</div>      </div>    </div>    <div class="col-6 col-md-3 d-none d-md-flex d-flex align-items-center text-white {% if property.avg_response_time > 500 %}bg-danger{% else %}bg-success{% endif %}">      <div class="card-body">      <div class="card-body text-center">        <div class="card-title h4">{% if property.avg_response_time > 500 %}Unhealthy{% else %}Ok{% endif %}</div>        <div class="card-text text-truncate small">Response time</div>      </div>
@@ -109,6 +109,21 @@  </div></div>{% if property.lighthouse_scores %}<div class="container-fluid">  <div class="row mb-4">    {% for category, score in property.lighthouse_scores.items %}    <div class="col-6 col-md-3 d-flex align-items-center text-white {% if score > 70 %}bg-success{% else %}bg-warning{% endif %}">      <div class="card-body text-center">        <div class="card-title h4">{{ score }}</div>        <div class="card-text text-truncate small">{{ category }}</div>      </div>    </div>    {% endfor %}  </div></div>{% endif %}<div class="container mt-4">  <div class="row">    <div class="col-12 col-md-8">
modified status/lighthouse.py
@@ -11,9 +11,10 @@ def fetch_lighthouse_results(url):    command = [        f"{settings.BASE_DIR}/node_modules/.bin/lighthouse",        url,        '--chrome-flags="--headless --no-sandbox"',        '--chrome-flags="--headless --no-sandbox --disable-dev-shm-usage"',        '--output="json"',        '--output-path="stdout"',        '--max-wait-for-load=5000',        "--quiet",    ]    try:
@@ -26,10 +27,10 @@ def fetch_lighthouse_results(url):def parse_lighthouse_results(results):    try:        scores = {            "performance": results["categories"]["performance"]["score"],            "accessibility": results["categories"]["accessibility"]["score"],            "best-practices": results["categories"]["best-practices"]["score"],            "seo": results["categories"]["seo"]["score"],            "Performance": results["categories"]["performance"]["score"],            "Accessibility": results["categories"]["accessibility"]["score"],            "Best practices": results["categories"]["best-practices"]["score"],            "SEO": results["categories"]["seo"]["score"],        }    except KeyError:        return None