diff --git a/api/tacticalrmm/natsapi/urls.py b/api/tacticalrmm/natsapi/urls.py index 06894001..e776ad98 100644 --- a/api/tacticalrmm/natsapi/urls.py +++ b/api/tacticalrmm/natsapi/urls.py @@ -5,4 +5,5 @@ urlpatterns = [ path("natsinfo/", views.nats_info), path("checkin/", views.NatsCheckIn.as_view()), path("syncmesh/", views.SyncMeshNodeID.as_view()), + path("winupdates/", views.NatsWinUpdates.as_view()), ] diff --git a/api/tacticalrmm/natsapi/views.py b/api/tacticalrmm/natsapi/views.py index 45a82a15..eb9abef2 100644 --- a/api/tacticalrmm/natsapi/views.py +++ b/api/tacticalrmm/natsapi/views.py @@ -12,6 +12,7 @@ from django.conf import settings from django.shortcuts import get_object_or_404 from agents.models import Agent +from winupdate.models import WinUpdate from software.models import InstalledSoftware from checks.utils import bytes2human from agents.serializers import WinAgentSerializer @@ -124,3 +125,42 @@ class SyncMeshNodeID(APIView): agent.save(update_fields=["mesh_node_id"]) return Response("ok") + + +class NatsWinUpdates(APIView): + authentication_classes = [] + permission_classes = [] + + def post(self, request): + agent = get_object_or_404(Agent, agent_id=request.data["agent_id"]) + updates = request.data["wua_updates"] + for update in updates: + if agent.winupdates.filter(guid=update["guid"]).exists(): + u = agent.winupdates.filter(guid=update["guid"]).last() + u.downloaded = update["downloaded"] + u.installed = update["installed"] + u.save(update_fields=["downloaded", "installed"]) + else: + try: + kb = "KB" + update["kb_article_ids"][0] + except: + continue + + WinUpdate( + agent=agent, + guid=update["guid"], + kb=kb, + title=update["title"], + installed=update["installed"], + downloaded=update["downloaded"], + description=update["description"], + severity=update["severity"], + categories=update["categories"], + category_ids=update["category_ids"], + kb_article_ids=update["kb_article_ids"], + more_info_urls=update["more_info_urls"], + support_url=update["support_url"], + revision_number=update["revision_number"], + ).save() + + return Response("ok") diff --git a/api/tacticalrmm/winupdate/migrations/0010_auto_20210119_0052.py b/api/tacticalrmm/winupdate/migrations/0010_auto_20210119_0052.py new file mode 100644 index 00000000..188e79b0 --- /dev/null +++ b/api/tacticalrmm/winupdate/migrations/0010_auto_20210119_0052.py @@ -0,0 +1,93 @@ +# Generated by Django 3.1.5 on 2021-01-19 00:52 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("winupdate", "0009_auto_20200922_1344"), + ] + + operations = [ + migrations.AddField( + model_name="winupdate", + name="categories", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(blank=True, max_length=255, null=True), + blank=True, + default=list, + null=True, + size=None, + ), + ), + migrations.AddField( + model_name="winupdate", + name="category_ids", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(blank=True, max_length=255, null=True), + blank=True, + default=list, + null=True, + size=None, + ), + ), + migrations.AddField( + model_name="winupdate", + name="kb_article_ids", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(blank=True, max_length=255, null=True), + blank=True, + default=list, + null=True, + size=None, + ), + ), + migrations.AddField( + model_name="winupdate", + name="more_info_urls", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.TextField(blank=True, null=True), + blank=True, + default=list, + null=True, + size=None, + ), + ), + migrations.AddField( + model_name="winupdate", + name="revision_number", + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name="winupdate", + name="support_url", + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name="winupdate", + name="date_installed", + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name="winupdate", + name="description", + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name="winupdate", + name="guid", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="winupdate", + name="kb", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AlterField( + model_name="winupdate", + name="title", + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/api/tacticalrmm/winupdate/models.py b/api/tacticalrmm/winupdate/models.py index 7418e9d9..ef803808 100644 --- a/api/tacticalrmm/winupdate/models.py +++ b/api/tacticalrmm/winupdate/models.py @@ -42,20 +42,46 @@ class WinUpdate(models.Model): agent = models.ForeignKey( Agent, related_name="winupdates", on_delete=models.CASCADE ) - guid = models.CharField(max_length=255, null=True) - kb = models.CharField(max_length=100, null=True) - mandatory = models.BooleanField(default=False) - title = models.TextField(null=True) - needs_reboot = models.BooleanField(default=False) + guid = models.CharField(max_length=255, null=True, blank=True) + kb = models.CharField(max_length=100, null=True, blank=True) + mandatory = models.BooleanField(default=False) # deprecated + title = models.TextField(null=True, blank=True) + needs_reboot = models.BooleanField(default=False) # deprecated installed = models.BooleanField(default=False) downloaded = models.BooleanField(default=False) - description = models.TextField(null=True) + description = models.TextField(null=True, blank=True) severity = models.CharField(max_length=255, null=True, blank=True) + categories = ArrayField( + models.CharField(max_length=255, null=True, blank=True), + null=True, + blank=True, + default=list, + ) + category_ids = ArrayField( + models.CharField(max_length=255, null=True, blank=True), + null=True, + blank=True, + default=list, + ) + kb_article_ids = ArrayField( + models.CharField(max_length=255, null=True, blank=True), + null=True, + blank=True, + default=list, + ) + more_info_urls = ArrayField( + models.TextField(null=True, blank=True), + null=True, + blank=True, + default=list, + ) + support_url = models.TextField(null=True, blank=True) + revision_number = models.IntegerField(null=True, blank=True) action = models.CharField( max_length=100, choices=PATCH_ACTION_CHOICES, default="nothing" ) result = models.CharField(max_length=255, default="n/a") - date_installed = models.DateTimeField(null=True) + date_installed = models.DateTimeField(null=True, blank=True) def __str__(self): return f"{self.agent.hostname} {self.kb}" diff --git a/go.mod b/go.mod index dd9f5cd4..77fa0840 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/kr/pretty v0.1.0 // indirect github.com/nats-io/nats.go v1.10.1-0.20210107160453-a133396829fc github.com/ugorji/go/codec v1.2.2 - github.com/wh1te909/rmmagent v1.2.0 + github.com/wh1te909/rmmagent v1.2.2-0.20210118235958-bd6606570a6f golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect diff --git a/go.sum b/go.sum index d31229de..d739b492 100644 --- a/go.sum +++ b/go.sum @@ -98,6 +98,8 @@ github.com/wh1te909/rmmagent v1.1.13-0.20210112033642-9b310c2c7f53 h1:Q47sibbW09 github.com/wh1te909/rmmagent v1.1.13-0.20210112033642-9b310c2c7f53/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0= github.com/wh1te909/rmmagent v1.2.0 h1:dM/juD7k6Oa0lEKsvbNPgjc1wVC6uQtNzQoIqVuuxSQ= github.com/wh1te909/rmmagent v1.2.0/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0= +github.com/wh1te909/rmmagent v1.2.2-0.20210118235958-bd6606570a6f h1:lhcD2yJauZ8TyYCxYvSv/CPnUhiTrxwydPTESfPkyuc= +github.com/wh1te909/rmmagent v1.2.2-0.20210118235958-bd6606570a6f/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/natsapi/api.go b/natsapi/api.go index e3bc6b05..3ecf7be2 100644 --- a/natsapi/api.go +++ b/natsapi/api.go @@ -138,6 +138,13 @@ func Listen(apihost, natshost string, debug bool) { rClient.R().SetBody(p).Post("/syncmesh/") } }() + case "getwinupdates": + go func() { + var p *rmm.WinUpdateResult + if err := dec.Decode(&p); err == nil { + rClient.R().SetBody(p).Post("/winupdates/") + } + }() } })