From dbd1003002e37bf5e02c192d19836b675643fa4e Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:26:09 +0000 Subject: [PATCH 1/5] back to dev --- api/tacticalrmm/tacticalrmm/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index f0a7cd890d..e5d12dc108 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -21,7 +21,7 @@ AUTH_USER_MODEL = "accounts.User" # latest release -TRMM_VERSION = "0.19.1" +TRMM_VERSION = "0.19.2-dev" # https://github.com/amidaware/tacticalrmm-web WEB_VERSION = "0.101.47" From acdf20f8003d13a31379bee30acdae5fca62d7e7 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:26:43 +0000 Subject: [PATCH 2/5] add webhook to readme [skip ci] --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 84cc354012..0b71b562df 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Tactical RMM is a remote monitoring & management tool, built with Django and Vue It uses an [agent](https://github.com/amidaware/rmmagent) written in golang and integrates with [MeshCentral](https://github.com/Ylianst/MeshCentral) # [LIVE DEMO](https://demo.tacticalrmm.com/) + Demo database resets every hour. A lot of features are disabled for obvious reasons due to the nature of this app. ### [Discord Chat](https://discord.gg/upGTkWp) @@ -23,7 +24,7 @@ Demo database resets every hour. A lot of features are disabled for obvious reas - Event log viewer - Services management - Windows patch management -- Automated checks with email/SMS alerting (cpu, disk, memory, services, scripts, event logs) +- Automated checks with email/SMS/Webhook alerting (cpu, disk, memory, services, scripts, event logs) - Automated task runner (run scripts on a schedule) - Remote software installation via chocolatey - Software and hardware inventory @@ -33,9 +34,11 @@ Demo database resets every hour. A lot of features are disabled for obvious reas - Windows 7, 8.1, 10, 11, Server 2008R2, 2012R2, 2016, 2019, 2022 ## Linux agent versions supported + - Any distro with systemd which includes but is not limited to: Debian (10, 11), Ubuntu x86_64 (18.04, 20.04, 22.04), Synology 7, centos, freepbx and more! ## Mac agent versions supported + - 64 bit Intel and Apple Silicon (M1, M2) ## Installation / Backup / Restore / Usage From 8a1f4972657c6d6da6897071c639680d4d589ca8 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:19:58 +0000 Subject: [PATCH 3/5] fix alert actions not honoring 'run only on' settings and fix availability webhook invalid escape --- api/tacticalrmm/agents/models.py | 4 +-- api/tacticalrmm/alerts/models.py | 43 ++++++++++++++------------ api/tacticalrmm/autotasks/models.py | 4 +-- api/tacticalrmm/checks/models.py | 4 +-- api/tacticalrmm/core/utils.py | 2 ++ api/tacticalrmm/logs/models.py | 2 +- api/tacticalrmm/tacticalrmm/helpers.py | 30 +++++++++++++++--- 7 files changed, 58 insertions(+), 31 deletions(-) diff --git a/api/tacticalrmm/agents/models.py b/api/tacticalrmm/agents/models.py index ee2973cbed..101795fc29 100644 --- a/api/tacticalrmm/agents/models.py +++ b/api/tacticalrmm/agents/models.py @@ -969,8 +969,8 @@ def should_create_alert( return bool( has_agent_notification or has_alert_template_notification - or has_webhook(alert_template) - or has_script_actions(alert_template) + or has_webhook(alert_template, "agent") + or has_script_actions(alert_template, "agent") ) def send_outage_email(self) -> None: diff --git a/api/tacticalrmm/alerts/models.py b/api/tacticalrmm/alerts/models.py index c28b6d7599..e5c4e6d0a2 100644 --- a/api/tacticalrmm/alerts/models.py +++ b/api/tacticalrmm/alerts/models.py @@ -133,7 +133,7 @@ def create_or_return_availability_alert( agent=agent, alert_type=AlertType.AVAILABILITY, severity=AlertSeverity.ERROR, - message=f"{agent.hostname} in {agent.client.name}\\{agent.site.name} is overdue.", + message=f"{agent.hostname} in {agent.client.name}, {agent.site.name} is overdue.", hidden=True, ), ) @@ -306,7 +306,7 @@ def handle_alert_failure( alert_interval = None email_task = None text_task = None - run_script_action = None + should_run_script_or_webhook = False # check what the instance passed is if isinstance(instance, Agent): @@ -332,7 +332,7 @@ def handle_alert_failure( always_email = alert_template.agent_always_email always_text = alert_template.agent_always_text alert_interval = alert_template.agent_periodic_alert_days - run_script_action = alert_template.agent_script_actions + should_run_script_or_webhook = alert_template.agent_script_actions elif isinstance(instance, CheckResult): from checks.tasks import ( @@ -383,7 +383,7 @@ def handle_alert_failure( always_email = alert_template.check_always_email always_text = alert_template.check_always_text alert_interval = alert_template.check_periodic_alert_days - run_script_action = alert_template.check_script_actions + should_run_script_or_webhook = alert_template.check_script_actions elif isinstance(instance, TaskResult): from autotasks.tasks import handle_task_email_alert, handle_task_sms_alert @@ -417,7 +417,7 @@ def handle_alert_failure( always_email = alert_template.task_always_email always_text = alert_template.task_always_text alert_interval = alert_template.task_periodic_alert_days - run_script_action = alert_template.task_script_actions + should_run_script_or_webhook = alert_template.task_script_actions else: return @@ -490,11 +490,10 @@ def handle_alert_failure( text_task.delay(pk=alert.pk, alert_interval=alert_interval) # check if any scripts/webhooks should be run - if alert_template and not alert.action_run: + if alert_template and not alert.action_run and should_run_script_or_webhook: if ( alert_template.action_type == AlertTemplateActionType.SCRIPT and alert_template.action - and run_script_action ): hist = AgentHistory.objects.create( agent=agent, @@ -516,7 +515,6 @@ def handle_alert_failure( elif ( alert_template.action_type == AlertTemplateActionType.SERVER and alert_template.action - and run_script_action ): stdout, stderr, execution_time, retcode = run_server_script( body=alert_template.action.script_body, @@ -595,7 +593,7 @@ def handle_alert_resolve( text_on_resolved = False resolved_email_task = None resolved_text_task = None - run_script_action = None + should_run_script_or_webhook = False # check what the instance passed is if isinstance(instance, Agent): @@ -611,7 +609,7 @@ def handle_alert_resolve( if alert_template: email_on_resolved = alert_template.agent_email_on_resolved text_on_resolved = alert_template.agent_text_on_resolved - run_script_action = alert_template.agent_script_actions + should_run_script_or_webhook = alert_template.agent_script_actions email_severities = [AlertSeverity.ERROR] text_severities = [AlertSeverity.ERROR] @@ -636,7 +634,7 @@ def handle_alert_resolve( if alert_template: email_on_resolved = alert_template.check_email_on_resolved text_on_resolved = alert_template.check_text_on_resolved - run_script_action = alert_template.check_script_actions + should_run_script_or_webhook = alert_template.check_script_actions email_severities = alert_template.check_email_alert_severity or [ AlertSeverity.ERROR, AlertSeverity.WARNING, @@ -662,7 +660,7 @@ def handle_alert_resolve( if alert_template: email_on_resolved = alert_template.task_email_on_resolved text_on_resolved = alert_template.task_text_on_resolved - run_script_action = alert_template.task_script_actions + should_run_script_or_webhook = alert_template.task_script_actions email_severities = alert_template.task_email_alert_severity or [ AlertSeverity.ERROR, AlertSeverity.WARNING, @@ -714,11 +712,14 @@ def handle_alert_resolve( resolved_text_task.delay(pk=alert.pk) # check if resolved script/webhook should be run - if alert_template and not alert.resolved_action_run: + if ( + alert_template + and not alert.resolved_action_run + and should_run_script_or_webhook + ): if ( alert_template.resolved_action_type == AlertTemplateActionType.SCRIPT and alert_template.resolved_action - and run_script_action ): hist = AgentHistory.objects.create( agent=agent, @@ -740,7 +741,6 @@ def handle_alert_resolve( elif ( alert_template.resolved_action_type == AlertTemplateActionType.SERVER and alert_template.resolved_action - and run_script_action ): stdout, stderr, execution_time, retcode = run_server_script( body=alert_template.resolved_action.script_body, @@ -863,7 +863,9 @@ class AlertTemplate(BaseAuditModel): ) action_timeout = models.PositiveIntegerField(default=15) resolved_action_type = models.CharField( - max_length=10, choices=AlertTemplateActionType.choices, default="script" + max_length=10, + choices=AlertTemplateActionType.choices, + default=AlertTemplateActionType.SCRIPT, ) resolved_action = models.ForeignKey( "scripts.Script", @@ -917,7 +919,8 @@ class AlertTemplate(BaseAuditModel): agent_always_text = BooleanField(null=True, blank=True, default=None) agent_always_alert = BooleanField(null=True, blank=True, default=None) agent_periodic_alert_days = PositiveIntegerField(blank=True, null=True, default=0) - agent_script_actions = BooleanField(null=True, blank=True, default=True) + # fmt: off + agent_script_actions = BooleanField(null=True, blank=True, default=True) # should be renamed because also deals with webhooks # check alert settings check_email_alert_severity = ArrayField( @@ -941,7 +944,8 @@ class AlertTemplate(BaseAuditModel): check_always_text = BooleanField(null=True, blank=True, default=None) check_always_alert = BooleanField(null=True, blank=True, default=None) check_periodic_alert_days = PositiveIntegerField(blank=True, null=True, default=0) - check_script_actions = BooleanField(null=True, blank=True, default=True) + # fmt: off + check_script_actions = BooleanField(null=True, blank=True, default=True) # should be renamed because also deals with webhooks # task alert settings task_email_alert_severity = ArrayField( @@ -965,7 +969,8 @@ class AlertTemplate(BaseAuditModel): task_always_text = BooleanField(null=True, blank=True, default=None) task_always_alert = BooleanField(null=True, blank=True, default=None) task_periodic_alert_days = PositiveIntegerField(blank=True, null=True, default=0) - task_script_actions = BooleanField(null=True, blank=True, default=True) + # fmt: off + task_script_actions = BooleanField(null=True, blank=True, default=True) # should be renamed because also deals with webhooks # exclusion settings exclude_workstations = BooleanField(null=True, blank=True, default=False) diff --git a/api/tacticalrmm/autotasks/models.py b/api/tacticalrmm/autotasks/models.py index 46a99063bb..21bb40f651 100644 --- a/api/tacticalrmm/autotasks/models.py +++ b/api/tacticalrmm/autotasks/models.py @@ -458,8 +458,8 @@ def should_create_alert(self, alert_template=None): return ( has_autotask_notification or has_alert_template_notification - or has_webhook(alert_template) - or has_script_actions(alert_template) + or has_webhook(alert_template, "task") + or has_script_actions(alert_template, "task") ) diff --git a/api/tacticalrmm/checks/models.py b/api/tacticalrmm/checks/models.py index 27cacfbab5..e6fe3bd6bd 100644 --- a/api/tacticalrmm/checks/models.py +++ b/api/tacticalrmm/checks/models.py @@ -242,8 +242,8 @@ def should_create_alert(self, alert_template=None): return ( has_check_notifications or has_alert_template_notification - or has_webhook(alert_template) - or has_script_actions(alert_template) + or has_webhook(alert_template, "check") + or has_script_actions(alert_template, "check") ) def add_check_history( diff --git a/api/tacticalrmm/core/utils.py b/api/tacticalrmm/core/utils.py index 7ce323d998..b4428991d2 100644 --- a/api/tacticalrmm/core/utils.py +++ b/api/tacticalrmm/core/utils.py @@ -24,6 +24,7 @@ AgentPlat, MeshAgentIdent, ) +from tacticalrmm.logger import logger if TYPE_CHECKING: from core.models import CoreSettings @@ -264,6 +265,7 @@ def run_url_rest_action(*, action_id: int, instance=None) -> tuple[str, int]: url=url, method=method, body=body, headers=headers, instance=instance ) except Exception as e: + logger.error(str(e)) return (str(e), 500) return (response.text, response.status_code) diff --git a/api/tacticalrmm/logs/models.py b/api/tacticalrmm/logs/models.py index 1fced9b6f8..14ecc6bb4b 100644 --- a/api/tacticalrmm/logs/models.py +++ b/api/tacticalrmm/logs/models.py @@ -297,7 +297,7 @@ def audit_bulk_action( target = f"on all agents within client: {client.name}" elif affected["target"] == "site": site = Site.objects.get(pk=affected["site"]) - target = f"on all agents within site: {site.client.name}\\{site.name}" + target = f"on all agents within site: {site.client.name} - {site.name}" elif affected["target"] == "agents": agents = Agent.objects.filter(agent_id__in=affected["agents"]).values_list( "hostname", flat=True diff --git a/api/tacticalrmm/tacticalrmm/helpers.py b/api/tacticalrmm/tacticalrmm/helpers.py index 50816c6ddc..fedb96b12a 100644 --- a/api/tacticalrmm/tacticalrmm/helpers.py +++ b/api/tacticalrmm/tacticalrmm/helpers.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import os import random import secrets import string from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal from urllib.parse import urlparse from zoneinfo import ZoneInfo @@ -140,11 +142,29 @@ def days_until_cert_expires() -> int: return delta.days -def has_webhook(alert_templ: "AlertTemplate") -> bool: +def has_webhook( + alert_templ: AlertTemplate | None, instance: Literal["agent", "check", "task"] +) -> bool: return bool( - alert_templ and (alert_templ.action_rest or alert_templ.resolved_action_rest) + alert_templ + and (alert_templ.action_rest or alert_templ.resolved_action_rest) + and ( + (instance == "agent" and alert_templ.agent_script_actions) + or (instance == "check" and alert_templ.check_script_actions) + or (instance == "task" and alert_templ.task_script_actions) + ) ) -def has_script_actions(alert_templ: "AlertTemplate") -> bool: - return bool(alert_templ and (alert_templ.action or alert_templ.resolved_action)) +def has_script_actions( + alert_templ: AlertTemplate | None, instance: Literal["agent", "check", "task"] +) -> bool: + return bool( + alert_templ + and (alert_templ.action or alert_templ.resolved_action) + and ( + (instance == "agent" and alert_templ.agent_script_actions) + or (instance == "check" and alert_templ.check_script_actions) + or (instance == "task" and alert_templ.task_script_actions) + ) + ) From 5cf618695f437dab41c6116deaf070e5fc172e5f Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:31:21 +0000 Subject: [PATCH 4/5] fix lint --- api/tacticalrmm/alerts/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tacticalrmm/alerts/models.py b/api/tacticalrmm/alerts/models.py index e5c4e6d0a2..0d2ae3e4ed 100644 --- a/api/tacticalrmm/alerts/models.py +++ b/api/tacticalrmm/alerts/models.py @@ -920,7 +920,7 @@ class AlertTemplate(BaseAuditModel): agent_always_alert = BooleanField(null=True, blank=True, default=None) agent_periodic_alert_days = PositiveIntegerField(blank=True, null=True, default=0) # fmt: off - agent_script_actions = BooleanField(null=True, blank=True, default=True) # should be renamed because also deals with webhooks + agent_script_actions = BooleanField(null=True, blank=True, default=True) # should be renamed because also deals with webhooks # check alert settings check_email_alert_severity = ArrayField( From c8d72ddd3bf80cd2d8387f4e2c1911ff2ea806a0 Mon Sep 17 00:00:00 2001 From: wh1te909 <7434746+wh1te909@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:40:35 +0000 Subject: [PATCH 5/5] bump version [skip ci] --- api/tacticalrmm/tacticalrmm/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index e5d12dc108..4ad1cab1e5 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -21,7 +21,7 @@ AUTH_USER_MODEL = "accounts.User" # latest release -TRMM_VERSION = "0.19.2-dev" +TRMM_VERSION = "0.19.2" # https://github.com/amidaware/tacticalrmm-web WEB_VERSION = "0.101.47"