Compare commits

...

32 Commits

Author SHA1 Message Date
wh1te909
0999d98225 Release 0.13.2 2022-04-25 00:33:40 +00:00
wh1te909
d8dd3e133f bump version 2022-04-25 00:31:14 +00:00
wh1te909
528470c37f fix slow query 2022-04-25 00:17:34 +00:00
wh1te909
c03cd53853 fix deprecated function 2022-04-24 23:20:39 +00:00
wh1te909
b57fc8a29c testing num queries 2022-04-24 23:14:37 +00:00
wh1te909
a04ed5c3ca remove duplicate settings 2022-04-24 23:09:44 +00:00
Dan
3ad1df14f6 Merge pull request #1085 from dinger1986/patch-1
Update troubleshoot_server.sh
2022-04-24 15:26:47 -07:00
wh1te909
d8caf12fdc optimize policy query 2022-04-24 22:07:24 +00:00
wh1te909
5ca9d30d5f add a test and optimize some queries 2022-04-24 21:23:33 +00:00
sadnub
a7a71b4a46 fix default tab not working if 'servers' is selected 2022-04-24 16:35:42 -04:00
sadnub
638603ac6b fix variable substitution when running policy tasks 2022-04-24 16:35:18 -04:00
wh1te909
1d70c15027 wrong related 2022-04-24 01:45:03 +00:00
wh1te909
7a5f03d672 fix slow query 2022-04-23 23:24:36 +00:00
wh1te909
39e97c5589 Release 0.13.1 2022-04-23 22:59:35 +00:00
wh1te909
1943d8367e bump version 2022-04-23 22:57:09 +00:00
wh1te909
f91c5af9a1 optimize query 2022-04-23 22:51:45 +00:00
wh1te909
2be71fc877 increase chunk size 2022-04-23 22:50:06 +00:00
wh1te909
f5f5b4a8db fixed 'Save and test email' always returning success even if it failed 2022-04-23 18:50:23 +00:00
sadnub
ac9cfd09ea fix init container not having access to the redis service 2022-04-23 12:28:13 -04:00
sadnub
4cfc85dbfd fix agent sorting by last response 2022-04-23 12:21:18 -04:00
sadnub
1f3d2f47b1 increase max_length on customfield name field to 100 2022-04-23 10:26:34 -04:00
dinger1986
653c482ff7 Update troubleshoot_server.sh
add in output of afew log log files
2022-04-23 15:03:58 +01:00
wh1te909
4b069cc2b0 fix pending actions showing agent pending update even though it was already updated 2022-04-23 07:43:30 +00:00
wh1te909
c89349a43a update badge 2022-04-23 03:49:37 +00:00
wh1te909
6e92d6c62c testing codecov 2022-04-23 03:44:45 +00:00
wh1te909
5d3d3e9076 fix tests? 2022-04-23 02:07:00 +00:00
wh1te909
b440c772d6 forgot pytest.ini 2022-04-23 02:01:34 +00:00
wh1te909
2895560b30 testing pytest/codecov 2022-04-23 02:00:12 +00:00
wh1te909
bedcecb2e1 fix deprecations 2022-04-23 00:19:00 +00:00
wh1te909
656ac829a4 black 2022-04-22 23:22:41 +00:00
wh1te909
4d83debc0e also sort in db 2022-04-22 23:19:42 +00:00
wh1te909
4ff5d19979 fix sorting of clients tree 2022-04-22 22:48:54 +00:00
33 changed files with 235 additions and 174 deletions

View File

@@ -42,9 +42,7 @@ jobs:
cd api/tacticalrmm
source ../env/bin/activate
rm -f .coverage coverage.lcov
coverage run --concurrency=multiprocessing manage.py test -v 2 --parallel
coverage combine
coverage lcov
pytest
if [ $? -ne 0 ]; then
exit 1
fi
@@ -58,9 +56,8 @@ jobs:
exit 1
fi
- name: Coveralls
uses: coverallsapp/github-action@master
- uses: codecov/codecov-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./api/tacticalrmm/coverage.lcov
base-path: ./api/tacticalrmm
directory: ./api/tacticalrmm
files: ./api/tacticalrmm/coverage.xml
verbose: true

2
.gitignore vendored
View File

@@ -53,3 +53,5 @@ nats-api.conf
ignore/
coverage.lcov
daphne.sock.lock
.pytest_cache
coverage.xml

View File

@@ -1,7 +1,7 @@
# Tactical RMM
![CI Tests](https://github.com/amidaware/tacticalrmm/actions/workflows/ci-tests.yml/badge.svg?branch=develop)
[![Coverage Status](https://coveralls.io/repos/github/amidaware/tacticalrmm/badge.svg?branch=develop)](https://coveralls.io/github/amidaware/tacticalrmm?branch=develop)
[![codecov](https://codecov.io/gh/amidaware/tacticalrmm/branch/develop/graph/badge.svg?token=8ACUPVPTH6)](https://codecov.io/gh/amidaware/tacticalrmm)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
Tactical RMM is a remote monitoring & management tool, built with Django and Vue.\

View File

@@ -69,17 +69,17 @@ class TestAccounts(TacticalTestCase):
self.assertEqual(r.status_code, 400)
self.assertIn("non_field_errors", r.data.keys())
@override_settings(DEBUG=True)
@patch("pyotp.TOTP.verify")
def test_debug_login_view(self, mock_verify):
url = "/login/"
mock_verify.return_value = True
# @override_settings(DEBUG=True)
# @patch("pyotp.TOTP.verify")
# def test_debug_login_view(self, mock_verify):
# url = "/login/"
# mock_verify.return_value = True
data = {"username": "bob", "password": "hunter2", "twofactor": "sekret"}
r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200)
self.assertIn("expiry", r.data.keys())
self.assertIn("token", r.data.keys())
# data = {"username": "bob", "password": "hunter2", "twofactor": "sekret"}
# r = self.client.post(url, data, format="json")
# self.assertEqual(r.status_code, 200)
# self.assertIn("expiry", r.data.keys())
# self.assertIn("token", r.data.keys())
class TestGetAddUsers(TacticalTestCase):
@@ -338,7 +338,7 @@ class TestAPIKeyViews(TacticalTestCase):
resp = self.client.put(url, data, format="json")
self.assertEqual(resp.status_code, 200)
apikey = APIKey.objects.get(pk=apikey.pk)
self.assertEquals(apikey.name, "New Name")
self.assertEqual(apikey.name, "New Name")
self.check_not_authenticated("put", url)

View File

@@ -68,7 +68,7 @@ def agent_update(agent_id: str, force: bool = False) -> str:
@app.task
def force_code_sign(agent_ids: list[str]) -> None:
chunks = (agent_ids[i : i + 50] for i in range(0, len(agent_ids), 50))
chunks = (agent_ids[i : i + 70] for i in range(0, len(agent_ids), 70))
for chunk in chunks:
for agent_id in chunk:
agent_update(agent_id=agent_id, force=True)
@@ -77,7 +77,7 @@ def force_code_sign(agent_ids: list[str]) -> None:
@app.task
def send_agent_update_task(agent_ids: list[str]) -> None:
chunks = (agent_ids[i : i + 50] for i in range(0, len(agent_ids), 50))
chunks = (agent_ids[i : i + 70] for i in range(0, len(agent_ids), 70))
for chunk in chunks:
for agent_id in chunk:
agent_update(agent_id)
@@ -97,7 +97,7 @@ def auto_self_agent_update_task() -> None:
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
]
chunks = (agent_ids[i : i + 30] for i in range(0, len(agent_ids), 30))
chunks = (agent_ids[i : i + 70] for i in range(0, len(agent_ids), 70))
for chunk in chunks:
for agent_id in chunk:
agent_update(agent_id)

View File

@@ -245,7 +245,10 @@ class TestAgentViews(TacticalTestCase):
def test_get_agent_versions(self):
url = "/agents/versions/"
r = self.client.get(url)
with self.assertNumQueries(1):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
assert any(i["hostname"] == self.agent.hostname for i in r.json()["agents"])

View File

@@ -136,10 +136,10 @@ class GetAgents(APIView):
else:
agents = (
Agent.objects.filter_by_role(request.user) # type: ignore
.select_related("site")
.defer(*AGENT_DEFER)
.select_related("site__client")
.filter(monitoring_type_filter)
.filter(client_site_filter)
.only("agent_id", "hostname", "site")
)
serializer = AgentHostnameSerializer(agents, many=True)
@@ -297,9 +297,9 @@ class AgentMeshCentral(APIView):
@permission_classes([IsAuthenticated, AgentPerms])
def get_agent_versions(request):
agents = (
Agent.objects.filter_by_role(request.user) # type: ignore
.prefetch_related("site")
.only("pk", "hostname")
Agent.objects.defer(*AGENT_DEFER)
.filter_by_role(request.user) # type: ignore
.select_related("site__client")
)
return Response(
{

View File

@@ -70,8 +70,8 @@ class TestAlertsViews(TacticalTestCase):
data = {"top": 3}
resp = self.client.patch(url, data, format="json")
self.assertEqual(resp.status_code, 200)
self.assertEquals(resp.data["alerts"], AlertSerializer(alerts, many=True).data)
self.assertEquals(resp.data["alerts_count"], 10)
self.assertEqual(resp.data["alerts"], AlertSerializer(alerts, many=True).data)
self.assertEqual(resp.data["alerts_count"], 10)
# test filter data
# test data and result counts
@@ -409,15 +409,15 @@ class TestAlertTasks(TacticalTestCase):
core.server_policy = policy
core.save()
self.assertEquals(server.set_alert_template().pk, alert_templates[0].pk)
self.assertEquals(workstation.set_alert_template().pk, alert_templates[0].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[0].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[0].pk)
# assign second Alert Template to as default alert template
core.alert_template = alert_templates[1]
core.save()
self.assertEquals(workstation.set_alert_template().pk, alert_templates[1].pk)
self.assertEquals(server.set_alert_template().pk, alert_templates[1].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[1].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[1].pk)
# assign third Alert Template to client
workstation.client.alert_template = alert_templates[2]
@@ -425,8 +425,8 @@ class TestAlertTasks(TacticalTestCase):
workstation.client.save()
server.client.save()
self.assertEquals(workstation.set_alert_template().pk, alert_templates[2].pk)
self.assertEquals(server.set_alert_template().pk, alert_templates[2].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[2].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[2].pk)
# apply policy to client and should override
workstation.client.workstation_policy = policy
@@ -434,8 +434,8 @@ class TestAlertTasks(TacticalTestCase):
workstation.client.save()
server.client.save()
self.assertEquals(workstation.set_alert_template().pk, alert_templates[0].pk)
self.assertEquals(server.set_alert_template().pk, alert_templates[0].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[0].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[0].pk)
# assign fouth Alert Template to site
workstation.site.alert_template = alert_templates[3]
@@ -443,8 +443,8 @@ class TestAlertTasks(TacticalTestCase):
workstation.site.save()
server.site.save()
self.assertEquals(workstation.set_alert_template().pk, alert_templates[3].pk)
self.assertEquals(server.set_alert_template().pk, alert_templates[3].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[3].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[3].pk)
# apply policy to site
workstation.site.workstation_policy = policy
@@ -452,8 +452,8 @@ class TestAlertTasks(TacticalTestCase):
workstation.site.save()
server.site.save()
self.assertEquals(workstation.set_alert_template().pk, alert_templates[0].pk)
self.assertEquals(server.set_alert_template().pk, alert_templates[0].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[0].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[0].pk)
# apply policy to agents
workstation.policy = policy
@@ -461,35 +461,35 @@ class TestAlertTasks(TacticalTestCase):
workstation.save()
server.save()
self.assertEquals(workstation.set_alert_template().pk, alert_templates[0].pk)
self.assertEquals(server.set_alert_template().pk, alert_templates[0].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[0].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[0].pk)
# test disabling alert template
alert_templates[0].is_active = False
alert_templates[0].save()
self.assertEquals(workstation.set_alert_template().pk, alert_templates[3].pk)
self.assertEquals(server.set_alert_template().pk, alert_templates[3].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[3].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[3].pk)
# test policy exclusions
alert_templates[3].excluded_agents.set([workstation.pk])
self.assertEquals(workstation.set_alert_template().pk, alert_templates[2].pk)
self.assertEquals(server.set_alert_template().pk, alert_templates[3].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[2].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[3].pk)
# test workstation exclusions
alert_templates[2].exclude_workstations = True
alert_templates[2].save()
self.assertEquals(workstation.set_alert_template().pk, alert_templates[1].pk)
self.assertEquals(server.set_alert_template().pk, alert_templates[3].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[1].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[3].pk)
# test server exclusions
alert_templates[3].exclude_servers = True
alert_templates[3].save()
self.assertEquals(workstation.set_alert_template().pk, alert_templates[1].pk)
self.assertEquals(server.set_alert_template().pk, alert_templates[2].pk)
self.assertEqual(workstation.set_alert_template().pk, alert_templates[1].pk)
self.assertEqual(server.set_alert_template().pk, alert_templates[2].pk)
@patch("agents.tasks.sleep")
@patch("core.models.CoreSettings.send_mail")
@@ -523,7 +523,7 @@ class TestAlertTasks(TacticalTestCase):
# call outages task and no alert should be created
agent_outages_task()
self.assertEquals(Alert.objects.count(), 0)
self.assertEqual(Alert.objects.count(), 0)
# set overdue_dashboard_alert and alert should be created
agent_dashboard_alert.overdue_dashboard_alert = True
@@ -574,22 +574,22 @@ class TestAlertTasks(TacticalTestCase):
agent_outages_task()
# should have created 6 alerts
self.assertEquals(Alert.objects.count(), 6)
self.assertEqual(Alert.objects.count(), 6)
# other specific agents should have created alerts
self.assertEquals(Alert.objects.filter(agent=agent_dashboard_alert).count(), 1)
self.assertEquals(Alert.objects.filter(agent=agent_text_alert).count(), 1)
self.assertEquals(Alert.objects.filter(agent=agent_email_alert).count(), 1)
self.assertEquals(Alert.objects.filter(agent=agent_template_email).count(), 1)
self.assertEquals(
self.assertEqual(Alert.objects.filter(agent=agent_dashboard_alert).count(), 1)
self.assertEqual(Alert.objects.filter(agent=agent_text_alert).count(), 1)
self.assertEqual(Alert.objects.filter(agent=agent_email_alert).count(), 1)
self.assertEqual(Alert.objects.filter(agent=agent_template_email).count(), 1)
self.assertEqual(
Alert.objects.filter(agent=agent_template_dashboard).count(), 1
)
self.assertEquals(Alert.objects.filter(agent=agent_template_text).count(), 1)
self.assertEquals(Alert.objects.filter(agent=agent_template_blank).count(), 0)
self.assertEqual(Alert.objects.filter(agent=agent_template_text).count(), 1)
self.assertEqual(Alert.objects.filter(agent=agent_template_blank).count(), 0)
# check if email and text tasks were called
self.assertEquals(outage_email.call_count, 2)
self.assertEquals(outage_sms.call_count, 2)
self.assertEqual(outage_email.call_count, 2)
self.assertEqual(outage_sms.call_count, 2)
outage_sms.assert_any_call(
pk=Alert.objects.get(agent=agent_text_alert).pk, alert_interval=None
@@ -630,7 +630,7 @@ class TestAlertTasks(TacticalTestCase):
# calling agent outage task again shouldn't create duplicate alerts and won't send alerts
agent_outages_task()
self.assertEquals(Alert.objects.count(), 6)
self.assertEqual(Alert.objects.count(), 6)
# test periodic notification
# change email/text sent to sometime in the past
@@ -942,7 +942,7 @@ class TestAlertTasks(TacticalTestCase):
send_email.assert_not_called()
send_sms.assert_not_called()
self.assertEquals(
self.assertEqual(
Alert.objects.filter(assigned_check=check_template_email).count(), 1
)
@@ -1244,7 +1244,7 @@ class TestAlertTasks(TacticalTestCase):
send_email.assert_not_called()
send_sms.assert_not_called()
self.assertEquals(
self.assertEqual(
Alert.objects.filter(assigned_task=task_template_email).count(), 1
)

View File

@@ -62,7 +62,7 @@ class TestAPIv3(TacticalTestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data["check_interval"], 20)
self.assertEquals(len(r.data["checks"]), 2)
self.assertEqual(len(r.data["checks"]), 2)
url = "/api/v3/Maj34ACb324j234asdj2n34kASDjh34-DESKTOPTEST123/checkrunner/"
r = self.client.get(url)

View File

@@ -266,9 +266,9 @@ class TaskRunner(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk, agentid):
_ = get_object_or_404(Agent, agent_id=agentid)
agent = get_object_or_404(Agent, agent_id=agentid)
task = get_object_or_404(AutomatedTask, pk=pk)
return Response(TaskGOGetSerializer(task).data)
return Response(TaskGOGetSerializer(task, context={"agent": agent}).data)
def patch(self, request, pk, agentid):
from alerts.models import Alert

View File

@@ -410,11 +410,11 @@ class TestPolicyTasks(TacticalTestCase):
)
self.assertEqual(resp.status_code, 200)
self.assertEquals(len(resp.data["server_clients"]), 1)
self.assertEquals(len(resp.data["server_sites"]), 0)
self.assertEquals(len(resp.data["workstation_clients"]), 1)
self.assertEquals(len(resp.data["workstation_sites"]), 0)
self.assertEquals(len(resp.data["agents"]), 0)
self.assertEqual(len(resp.data["server_clients"]), 1)
self.assertEqual(len(resp.data["server_sites"]), 0)
self.assertEqual(len(resp.data["workstation_clients"]), 1)
self.assertEqual(len(resp.data["workstation_sites"]), 0)
self.assertEqual(len(resp.data["agents"]), 0)
# Add Site to Policy
policy.server_sites.add(server_agents[10].site)
@@ -422,9 +422,9 @@ class TestPolicyTasks(TacticalTestCase):
resp = self.client.get(
f"/automation/policies/{policy.pk}/related/", format="json"
)
self.assertEquals(len(resp.data["server_sites"]), 1)
self.assertEquals(len(resp.data["workstation_sites"]), 1)
self.assertEquals(len(resp.data["agents"]), 0)
self.assertEqual(len(resp.data["server_sites"]), 1)
self.assertEqual(len(resp.data["workstation_sites"]), 1)
self.assertEqual(len(resp.data["agents"]), 0)
# Add Agent to Policy
policy.agents.add(server_agents[2])
@@ -432,7 +432,7 @@ class TestPolicyTasks(TacticalTestCase):
resp = self.client.get(
f"/automation/policies/{policy.pk}/related/", format="json"
)
self.assertEquals(len(resp.data["agents"]), 2)
self.assertEqual(len(resp.data["agents"]), 2)
def test_getting_agent_policy_checks(self):
@@ -442,7 +442,7 @@ class TestPolicyTasks(TacticalTestCase):
agent = baker.make_recipe("agents.agent", policy=policy)
# test policy assigned to agent
self.assertEquals(len(agent.get_checks_from_policies()), 7)
self.assertEqual(len(agent.get_checks_from_policies()), 7)
def test_getting_agent_policy_checks_with_enforced(self):
# setup data

View File

@@ -28,7 +28,9 @@ class GetAddPolicies(APIView):
permission_classes = [IsAuthenticated, AutomationPolicyPerms]
def get(self, request):
policies = Policy.objects.all()
policies = Policy.objects.select_related("alert_template").prefetch_related(
"excluded_agents", "excluded_sites", "excluded_clients"
)
return Response(
PolicyTableSerializer(

View File

@@ -198,13 +198,14 @@ class TaskGOGetSerializer(serializers.ModelSerializer):
def get_task_actions(self, obj):
tmp = []
actions_to_remove = []
agent = self.context["agent"] if "agent" in self.context.keys() else obj.agent
for action in obj.actions:
if action["type"] == "cmd":
tmp.append(
{
"type": "cmd",
"command": Script.parse_script_args(
agent=obj.agent,
agent=agent,
shell=action["shell"],
args=[action["command"]],
)[0],
@@ -225,7 +226,7 @@ class TaskGOGetSerializer(serializers.ModelSerializer):
"script_name": script.name,
"code": script.code,
"script_args": Script.parse_script_args(
agent=obj.agent,
agent=agent,
shell=script.shell,
args=action["script_args"],
),

View File

@@ -842,8 +842,8 @@ class TestCheckTasks(TacticalTestCase):
check_result = CheckResult.objects.get(assigned_check=check, agent=self.agent)
self.assertEquals(check.alert_severity, "warning")
self.assertEquals(check_result.status, "failing")
self.assertEqual(check.alert_severity, "warning")
self.assertEqual(check_result.status, "failing")
# test passing when contains
resp = self.client.patch(url, no_logs_data, format="json")
@@ -851,7 +851,7 @@ class TestCheckTasks(TacticalTestCase):
check_result = CheckResult.objects.get(assigned_check=check, agent=self.agent)
self.assertEquals(check_result.status, "passing")
self.assertEqual(check_result.status, "passing")
# test failing when not contains and message and source
check.fail_when = "not_contains"
@@ -863,8 +863,8 @@ class TestCheckTasks(TacticalTestCase):
check_result = CheckResult.objects.get(assigned_check=check, agent=self.agent)
self.assertEquals(check_result.status, "failing")
self.assertEquals(check.alert_severity, "error")
self.assertEqual(check_result.status, "failing")
self.assertEqual(check.alert_severity, "error")
# test passing when contains with source and message
resp = self.client.patch(url, data, format="json")
@@ -872,7 +872,7 @@ class TestCheckTasks(TacticalTestCase):
check_result = CheckResult.objects.get(assigned_check=check, agent=self.agent)
self.assertEquals(check_result.status, "passing")
self.assertEqual(check_result.status, "passing")
class TestCheckPermissions(TacticalTestCase):

View File

@@ -32,9 +32,8 @@ class GetAddClients(APIView):
def get(self, request):
clients = (
Client.objects.select_related(
"workstation_policy", "server_policy", "alert_template"
)
Client.objects.order_by("name")
.select_related("workstation_policy", "server_policy", "alert_template")
.filter_by_role(request.user) # type: ignore
.prefetch_related(
Prefetch(

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2022-04-23 14:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0033_coresettings_mesh_disable_auto_login'),
]
operations = [
migrations.AlterField(
model_name='customfield',
name='name',
field=models.CharField(max_length=100),
),
]

View File

@@ -175,12 +175,12 @@ class CoreSettings(BaseAuditModel):
body: str,
alert_template: "Optional[AlertTemplate]" = None,
test: bool = False,
) -> Union[bool, str]:
) -> tuple[str, bool]:
if test and not self.email_is_configured:
return "There needs to be at least one email recipient configured"
return ("There needs to be at least one email recipient configured", False)
# return since email must be configured to continue
elif not self.email_is_configured:
return "SMTP messaging not configured."
return ("SMTP messaging not configured.", False)
# override email from if alert_template is passed and is set
if alert_template and alert_template.email_from:
@@ -194,7 +194,7 @@ class CoreSettings(BaseAuditModel):
elif self.email_alert_recipients:
email_recipients = ", ".join(cast(List[str], self.email_alert_recipients))
else:
return "There needs to be at least one email recipient configured"
return ("There needs to be at least one email recipient configured", False)
try:
msg = EmailMessage()
@@ -221,18 +221,21 @@ class CoreSettings(BaseAuditModel):
except Exception as e:
DebugLog.error(message=f"Sending email failed with error: {e}")
if test:
return str(e)
finally:
return True
return (str(e), False)
if test:
return ("Email test ok!", True)
return ("ok", True)
def send_sms(
self,
body: str,
alert_template: "Optional[AlertTemplate]" = None,
test: bool = False,
) -> Union[str, bool]:
) -> tuple[str, bool]:
if not self.sms_is_configured:
return "Sms alerting is not setup correctly."
return ("Sms alerting is not setup correctly.", False)
# override email recipients if alert_template is passed and is set
if alert_template and alert_template.text_recipients:
@@ -240,7 +243,7 @@ class CoreSettings(BaseAuditModel):
elif self.sms_alert_recipients:
text_recipients = cast(List[str], self.sms_alert_recipients)
else:
return "No sms recipients found"
return ("No sms recipients found", False)
tw_client = TwClient(self.twilio_account_sid, self.twilio_auth_token)
for num in text_recipients:
@@ -249,9 +252,12 @@ class CoreSettings(BaseAuditModel):
except TwilioRestException as e:
DebugLog.error(message=f"SMS failed to send: {e}")
if test:
return str(e)
return (str(e), False)
return True
if test:
return ("SMS Test sent successfully!", True)
return ("ok", True)
@staticmethod
def serialize(core):
@@ -284,7 +290,7 @@ class CustomField(BaseAuditModel):
blank=True,
default=list,
)
name = models.CharField(max_length=30)
name = models.CharField(max_length=100)
required = models.BooleanField(blank=True, default=False)
default_value_string = models.TextField(null=True, blank=True)
default_value_bool = models.BooleanField(default=False)

View File

@@ -10,7 +10,7 @@ from clients.models import Client, Site
from checks.models import Check, CheckResult
from core.utils import get_core_settings
from django.conf import settings
from django.db.models import Prefetch, Exists, OuterRef
from django.db.models import Prefetch
from logs.models import PendingAction
from logs.tasks import prune_audit_log, prune_debug_log
from packaging import version as pyver
@@ -53,6 +53,23 @@ def core_maintenance_tasks() -> None:
@app.task
def handle_resolved_stuff() -> None:
# change agent update pending status to completed if agent has just updated
actions = (
PendingAction.objects.select_related("agent")
.defer("agent__services", "agent__wmi_detail")
.filter(action_type="agentupdate", status="pending")
)
to_update = [
action.id
for action in actions
if pyver.parse(action.agent.version) == pyver.parse(settings.LATEST_AGENT_VER)
and action.agent.status == "online"
]
PendingAction.objects.filter(pk__in=to_update).update(status="completed")
agent_queryset = (
Agent.objects.defer(*AGENT_DEFER)
.select_related(
@@ -80,26 +97,11 @@ def handle_resolved_stuff() -> None:
)
)
for agent in agent_queryset.annotate(
has_pending_actions=Exists(
PendingAction.objects.filter(
pk=OuterRef("pk"), action_type="agent_update", status="pending"
)
)
):
for agent in agent_queryset:
if (
pyver.parse(agent.version) >= pyver.parse("1.6.0")
and agent.status == "online"
):
# change agent update pending status to completed if agent has just updated
if (
pyver.parse(agent.version) == pyver.parse(settings.LATEST_AGENT_VER)
and agent.has_pending_actions
):
agent.pendingactions.filter(
action_type="agent_update", status="pending"
).update(status="completed")
# sync scheduled tasks
for task in agent.get_tasks_with_policies():
if not task.task_result or task.task_result.sync_status == "initial":

View File

@@ -1,6 +1,7 @@
from unittest.mock import patch
import requests
from django.conf import settings
from channels.db import database_sync_to_async
from channels.testing import WebsocketCommunicator
from model_bakery import baker
@@ -11,7 +12,9 @@ from core.utils import get_core_settings
from .consumers import DashInfo
from .models import CustomField, GlobalKVStore, URLAction
from .serializers import CustomFieldSerializer, KeyStoreSerializer, URLActionSerializer
from .tasks import core_maintenance_tasks
from .tasks import core_maintenance_tasks, handle_resolved_stuff
from logs.models import PendingAction
from agents.models import Agent
class TestCodeSign(TacticalTestCase):
@@ -393,6 +396,29 @@ class TestCoreTasks(TacticalTestCase):
self.check_not_authenticated("get", url)
def test_resolved_pending_agentupdate_task(self):
online = baker.make_recipe("agents.online_agent", version="2.0.0", _quantity=20)
offline = baker.make_recipe(
"agents.offline_agent", version="2.0.0", _quantity=20
)
agents = online + offline
for agent in agents:
baker.make_recipe("logs.pending_agentupdate_action", agent=agent)
Agent.objects.update(version=settings.LATEST_AGENT_VER)
handle_resolved_stuff()
complete = PendingAction.objects.filter(
action_type="agentupdate", status="completed"
).count()
old = PendingAction.objects.filter(
action_type="agentupdate", status="pending"
).count()
self.assertEqual(complete, 20)
self.assertEqual(old, 20)
class TestCorePermissions(TacticalTestCase):
def setUp(self):

View File

@@ -93,14 +93,14 @@ def dashboard_info(request):
@permission_classes([IsAuthenticated, CoreSettingsPerms])
def email_test(request):
core = get_core_settings()
r = core.send_mail(
msg, ok = core.send_mail(
subject="Test from Tactical RMM", body="This is a test message", test=True
)
if not ok:
return notify_error(msg)
if not isinstance(r, bool) and isinstance(r, str):
return notify_error(r)
return Response("Email Test OK!")
return Response(msg)
@api_view(["POST"])
@@ -388,9 +388,8 @@ class TwilioSMSTest(APIView):
"All fields are required, including at least 1 recipient"
)
r = core.send_sms("TacticalRMM Test SMS", test=True)
msg, ok = core.send_sms("TacticalRMM Test SMS", test=True)
if not ok:
return notify_error(msg)
if not isinstance(r, bool) and isinstance(r, str):
return notify_error(r)
return Response("SMS Test sent successfully!")
return Response(msg)

View File

@@ -1 +0,0 @@
default_app_config = "logs.apps.LogsConfig"

View File

@@ -26,3 +26,7 @@ object_logs = Recipe(
)
login_logs = Recipe("logs.AuditLog", action=cycle(login_actions), object_type="user")
pending_agentupdate_action = Recipe(
"logs.PendingAction", action_type="agentupdate", status="pending"
)

View File

@@ -96,9 +96,14 @@ class PendingActions(APIView):
def get(self, request, agent_id=None):
if agent_id:
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=agent_id
Agent.objects.defer(*AGENT_DEFER).prefetch_related("pendingactions"),
agent_id=agent_id,
)
actions = (
PendingAction.objects.filter(agent=agent)
.select_related("agent__site", "agent__site__client")
.defer("agent__services", "agent__wmi_detail")
)
actions = PendingAction.objects.filter(agent=agent)
else:
actions = (
PendingAction.objects.filter_by_role(request.user) # type: ignore

View File

@@ -0,0 +1,7 @@
[pytest]
DJANGO_SETTINGS_MODULE = tacticalrmm.settings
python_files = tests.py
addopts = -vvv --cov --cov-config=.coveragerc --cov-report=xml -n auto
filterwarnings =
ignore::django.core.cache.CacheKeyWarning

View File

@@ -1,5 +1,8 @@
coverage
coveralls
model_bakery
black
tblib
pytest
pytest-django
pytest-xdist
pytest-cov
codecov

View File

@@ -80,7 +80,7 @@ class TestScriptViews(TacticalTestCase):
resp = self.client.put(url, data, format="json")
self.assertEqual(resp.status_code, 200)
script = Script.objects.get(pk=script.pk)
self.assertEquals(script.description, "Description Change")
self.assertEqual(script.description, "Description Change")
# correct_hash = hmac.new(
# settings.SECRET_KEY.encode(), data["script_body"].encode(), hashlib.sha256
@@ -467,7 +467,7 @@ class TestScriptSnippetViews(TacticalTestCase):
resp = self.client.put(url, data, format="json")
self.assertEqual(resp.status_code, 200)
snippet = ScriptSnippet.objects.get(pk=snippet.pk)
self.assertEquals(snippet.name, "New Name")
self.assertEqual(snippet.name, "New Name")
self.check_not_authenticated("put", url)

View File

@@ -63,7 +63,7 @@ class TestServiceViews(TacticalTestCase):
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 200)
nats_cmd.assert_called_with(data={"func": "winservices"}, timeout=10)
self.assertEquals(Agent.objects.get(pk=agent.pk).services, nats_return)
self.assertEqual(Agent.objects.get(pk=agent.pk).services, nats_return)
self.check_not_authenticated("get", url)
@@ -141,7 +141,7 @@ class TestServiceViews(TacticalTestCase):
nats_cmd.assert_called_with(
{"func": "winsvcdetail", "payload": {"name": "alg"}}, timeout=10
)
self.assertEquals(resp.data, nats_return)
self.assertEqual(resp.data, nats_return)
self.check_not_authenticated("get", url)

View File

@@ -42,7 +42,7 @@ class TestSoftwareViews(TacticalTestCase):
# test without agent software
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 200)
self.assertEquals(resp.data, [])
self.assertEqual(resp.data, [])
# make some software
software = baker.make(
@@ -60,7 +60,7 @@ class TestSoftwareViews(TacticalTestCase):
serializer = InstalledSoftwareSerializer([software], many=True)
resp = self.client.get(f"{base_url}/", format="json")
self.assertEqual(resp.status_code, 200)
self.assertEquals(resp.data, serializer.data)
self.assertEqual(resp.data, serializer.data)
self.check_not_authenticated("get", url)

View File

@@ -17,11 +17,11 @@ LINUX_AGENT_SCRIPT = BASE_DIR / "core" / "agent_linux.sh"
AUTH_USER_MODEL = "accounts.User"
# latest release
TRMM_VERSION = "0.13.0"
TRMM_VERSION = "0.13.2"
# bump this version everytime vue code is changed
# to alert user they need to manually refresh their browser
APP_VER = "0.0.161"
APP_VER = "0.0.163"
# https://github.com/amidaware/rmmagent
LATEST_AGENT_VER = "2.0.3"
@@ -49,7 +49,6 @@ ASGI_APPLICATION = "tacticalrmm.asgi.application"
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_URL = "/static/"
@@ -96,23 +95,6 @@ if not DEBUG:
{"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",)}
)
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"corsheaders.middleware.CorsMiddleware", ##
"tacticalrmm.middleware.LogIPMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"tacticalrmm.middleware.AuditMiddleware",
"tacticalrmm.middleware.LinuxMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
if DEMO:
MIDDLEWARE += ("tacticalrmm.middleware.DemoMiddleware",)
INSTALLED_APPS = [
"django.contrib.auth",
"django.contrib.contenttypes",
@@ -257,11 +239,9 @@ LOGGING = {
},
}
if "AZPIPELINE" in os.environ:
ADMIN_ENABLED = False
if "GHACTIONS" in os.environ:
print("-----------------------PIPELINE----------------------------")
print("-----------------------GHACTIONS----------------------------")
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",

View File

@@ -65,9 +65,11 @@ services:
depends_on:
- tactical-postgres
- tactical-meshcentral
- tactical-redis
networks:
- api-db
- proxy
- redis
volumes:
- tactical_data:/opt/tactical
- mesh_data:/meshcentral-data

View File

@@ -328,7 +328,12 @@ else
echo -ne ${RED} SSL Certificate has expired or doesnt exist for $domain | tee -a checklog.log
printf >&2 "\n\n"
fi
echo -ne ${YELLOW} Getting summary output of logs | tee -a checklog.log
tail /rmm/api/tacticalrmm/tacticalrmm/private/log/django_debug.log | tee -a checklog.log
printf >&2 "\n\n"
tail /rmm/api/tacticalrmm/tacticalrmm/private/log/error.log | tee -a checklog.log
printf >&2 "\n\n"
printf >&2 "\n\n"
echo -ne ${YELLOW}

View File

@@ -23,7 +23,7 @@ export default function () {
showCommunityScripts: false,
agentDblClickAction: "",
agentUrlAction: null,
defaultAgentTblTab: "server",
defaultAgentTblTab: null,
clientTreeSort: "alphafail",
clientTreeSplitter: 20,
noCodeSign: false,
@@ -253,14 +253,15 @@ export default function () {
}
const sorted = output.sort((a, b) => a.label.localeCompare(b.label));
if (state.clientTreeSort === "alphafail") {
// move failing clients to the top
const failing = output.filter(i => i.color === "negative" || i.color === "warning");
const ok = output.filter(i => i.color !== "negative" && i.color !== "warning");
const failing = sorted.filter(i => i.color === "negative" || i.color === "warning");
const ok = sorted.filter(i => i.color !== "negative" && i.color !== "warning");
const sortedByFailing = [...failing, ...ok];
commit("loadTree", sortedByFailing);
} else {
commit("loadTree", output);
commit("loadTree", sorted);
}
})

View File

@@ -453,7 +453,6 @@ export default {
field: "last_seen",
sortable: true,
align: "left",
sort: (a, b) => this.dateStringToUnix(a) - this.dateStringToUnix(b),
},
{
name: "boot_time",
@@ -491,10 +490,12 @@ export default {
selectedTree(newVal, oldVal) {
if (this.clearSearchWhenSwitching) this.clearFilter();
},
tab(newVal, oldVal) {
this.$store.dispatch("loadAgents");
},
},
methods: {
getTree() {
this.$store.dispatch("loadAgents");
this.$store.dispatch("loadTree");
},
clearTreeSelected() {
@@ -708,7 +709,6 @@ export default {
},
set(newVal) {
this.$store.commit("SET_DEFAULT_AGENT_TBL_TAB", newVal);
this.$store.dispatch("loadAgents");
this.$store.commit("destroySubTable");
},
},