pending actions refactor
This commit is contained in:
parent
9ade78b703
commit
dbd1f0d4f9
|
@ -3,10 +3,9 @@ from unittest.mock import patch
|
|||
|
||||
from model_bakery import baker, seq
|
||||
|
||||
from logs.models import PendingAction
|
||||
from tacticalrmm.test import TacticalTestCase
|
||||
|
||||
from .serializers import PendingActionSerializer
|
||||
|
||||
|
||||
class TestAuditViews(TacticalTestCase):
|
||||
def setUp(self):
|
||||
|
@ -177,63 +176,94 @@ class TestAuditViews(TacticalTestCase):
|
|||
|
||||
self.check_not_authenticated("post", url)
|
||||
|
||||
def test_agent_pending_actions(self):
|
||||
agent = baker.make_recipe("agents.agent")
|
||||
pending_actions = baker.make(
|
||||
def test_get_pending_actions(self):
|
||||
url = "/logs/pendingactions/"
|
||||
agent1 = baker.make_recipe("agents.online_agent")
|
||||
agent2 = baker.make_recipe("agents.online_agent")
|
||||
|
||||
baker.make(
|
||||
"logs.PendingAction",
|
||||
agent=agent,
|
||||
_quantity=6,
|
||||
agent=agent1,
|
||||
action_type="chocoinstall",
|
||||
details={"name": "googlechrome", "output": None, "installed": False},
|
||||
_quantity=12,
|
||||
)
|
||||
baker.make(
|
||||
"logs.PendingAction",
|
||||
agent=agent2,
|
||||
action_type="chocoinstall",
|
||||
status="completed",
|
||||
details={"name": "adobereader", "output": None, "installed": False},
|
||||
_quantity=14,
|
||||
)
|
||||
url = f"/logs/{agent.pk}/pendingactions/"
|
||||
|
||||
resp = self.client.get(url, format="json")
|
||||
serializer = PendingActionSerializer(pending_actions, many=True)
|
||||
data = {"showCompleted": False}
|
||||
r = self.client.patch(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(r.data["actions"]), 12) # type: ignore
|
||||
self.assertEqual(r.data["completed_count"], 14) # type: ignore
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(len(resp.data), 6)
|
||||
self.assertEqual(resp.data, serializer.data)
|
||||
PendingAction.objects.filter(action_type="chocoinstall").update(
|
||||
status="completed"
|
||||
)
|
||||
data = {"showCompleted": True}
|
||||
r = self.client.patch(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(r.data["actions"]), 26) # type: ignore
|
||||
self.assertEqual(r.data["completed_count"], 26) # type: ignore
|
||||
|
||||
self.check_not_authenticated("get", url)
|
||||
data = {"showCompleted": True, "agentPK": agent1.pk}
|
||||
r = self.client.patch(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(r.data["actions"]), 12) # type: ignore
|
||||
self.assertEqual(r.data["completed_count"], 26) # type: ignore
|
||||
|
||||
def test_all_pending_actions(self):
|
||||
url = "/logs/allpendingactions/"
|
||||
agent = baker.make_recipe("agents.agent")
|
||||
pending_actions = baker.make("logs.PendingAction", agent=agent, _quantity=6)
|
||||
|
||||
resp = self.client.get(url, format="json")
|
||||
serializer = PendingActionSerializer(pending_actions, many=True)
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(len(resp.data), 6)
|
||||
self.assertEqual(resp.data, serializer.data)
|
||||
|
||||
self.check_not_authenticated("get", url)
|
||||
self.check_not_authenticated("patch", url)
|
||||
|
||||
@patch("agents.models.Agent.nats_cmd")
|
||||
def test_cancel_pending_action(self, nats_cmd):
|
||||
url = "/logs/cancelpendingaction/"
|
||||
# TODO fix this TypeError: Object of type coroutine is not JSON serializable
|
||||
""" agent = baker.make("agents.Agent", version="1.1.1")
|
||||
pending_action = baker.make(
|
||||
nats_cmd.return_value = "ok"
|
||||
url = "/logs/pendingactions/"
|
||||
agent = baker.make_recipe("agents.online_agent")
|
||||
action = baker.make(
|
||||
"logs.PendingAction",
|
||||
agent=agent,
|
||||
action_type="schedreboot",
|
||||
details={
|
||||
"time": "2021-01-13 18:20:00",
|
||||
"taskname": "TacticalRMM_SchedReboot_wYzCCDVXlc",
|
||||
},
|
||||
)
|
||||
|
||||
data = {"pk": pending_action.id}
|
||||
resp = self.client.delete(url, data, format="json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = {"pk": action.pk} # type: ignore
|
||||
r = self.client.delete(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
nats_data = {
|
||||
"func": "delschedtask",
|
||||
"schedtaskpayload": {"name": "TacticalRMM_SchedReboot_wYzCCDVXlc"},
|
||||
}
|
||||
nats_cmd.assert_called_with(nats_data, timeout=10)
|
||||
|
||||
# try request again and it should fail since pending action doesn't exist
|
||||
resp = self.client.delete(url, data, format="json")
|
||||
self.assertEqual(resp.status_code, 404) """
|
||||
# try request again and it should 404 since pending action doesn't exist
|
||||
r = self.client.delete(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
nats_cmd.reset_mock()
|
||||
|
||||
action2 = baker.make(
|
||||
"logs.PendingAction",
|
||||
agent=agent,
|
||||
action_type="schedreboot",
|
||||
details={
|
||||
"time": "2021-01-13 18:20:00",
|
||||
"taskname": "TacticalRMM_SchedReboot_wYzCCDVXlc",
|
||||
},
|
||||
)
|
||||
|
||||
data = {"pk": action2.pk} # type: ignore
|
||||
nats_cmd.return_value = "error deleting sched task"
|
||||
r = self.client.delete(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 400)
|
||||
self.assertEqual(r.data, "error deleting sched task") # type: ignore
|
||||
|
||||
self.check_not_authenticated("delete", url)
|
||||
|
|
|
@ -3,11 +3,9 @@ from django.urls import path
|
|||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("pendingactions/", views.PendingActions.as_view()),
|
||||
path("auditlogs/", views.GetAuditLogs.as_view()),
|
||||
path("auditlogs/optionsfilter/", views.FilterOptionsAuditLog.as_view()),
|
||||
path("<int:pk>/pendingactions/", views.agent_pending_actions),
|
||||
path("allpendingactions/", views.all_pending_actions),
|
||||
path("cancelpendingaction/", views.cancel_pending_action),
|
||||
path("debuglog/<mode>/<hostname>/<order>/", views.debug_log),
|
||||
path("downloadlog/", views.download_log),
|
||||
]
|
||||
|
|
|
@ -106,34 +106,38 @@ class FilterOptionsAuditLog(APIView):
|
|||
return Response("error", status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@api_view()
|
||||
def agent_pending_actions(request, pk):
|
||||
action = PendingAction.objects.filter(agent__pk=pk)
|
||||
return Response(PendingActionSerializer(action, many=True).data)
|
||||
class PendingActions(APIView):
|
||||
def patch(self, request):
|
||||
status_filter = "completed" if request.data["showCompleted"] else "pending"
|
||||
completed = PendingAction.objects.filter(status="completed").count()
|
||||
if "agentPK" in request.data.keys():
|
||||
actions = PendingAction.objects.filter(
|
||||
agent__pk=request.data["agentPK"], status=status_filter
|
||||
)
|
||||
|
||||
else:
|
||||
actions = PendingAction.objects.filter(status=status_filter).select_related(
|
||||
"agent"
|
||||
)
|
||||
|
||||
@api_view()
|
||||
def all_pending_actions(request):
|
||||
actions = PendingAction.objects.all().select_related("agent")
|
||||
return Response(PendingActionSerializer(actions, many=True).data)
|
||||
ret = {
|
||||
"actions": PendingActionSerializer(actions, many=True).data,
|
||||
"completed_count": completed,
|
||||
}
|
||||
return Response(ret)
|
||||
|
||||
def delete(self, request):
|
||||
action = get_object_or_404(PendingAction, pk=request.data["pk"])
|
||||
nats_data = {
|
||||
"func": "delschedtask",
|
||||
"schedtaskpayload": {"name": action.details["taskname"]},
|
||||
}
|
||||
r = asyncio.run(action.agent.nats_cmd(nats_data, timeout=10))
|
||||
if r != "ok":
|
||||
return notify_error(r)
|
||||
|
||||
@api_view(["DELETE"])
|
||||
def cancel_pending_action(request):
|
||||
action = get_object_or_404(PendingAction, pk=request.data["pk"])
|
||||
if not action.agent.has_gotasks:
|
||||
return notify_error("Requires agent version 1.1.1 or greater")
|
||||
|
||||
nats_data = {
|
||||
"func": "delschedtask",
|
||||
"schedtaskpayload": {"name": action.details["taskname"]},
|
||||
}
|
||||
r = asyncio.run(action.agent.nats_cmd(nats_data, timeout=10))
|
||||
if r != "ok":
|
||||
return notify_error(r)
|
||||
|
||||
action.delete()
|
||||
return Response(f"{action.agent.hostname}: {action.description} was cancelled")
|
||||
action.delete()
|
||||
return Response(f"{action.agent.hostname}: {action.description} was cancelled")
|
||||
|
||||
|
||||
@api_view()
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<q-btn
|
||||
:label="showCompleted ? `Hide ${completedCount} Completed` : `Show ${completedCount} Completed`"
|
||||
:icon="showCompleted ? 'visibility_off' : 'visibility'"
|
||||
@click="showCompleted = !showCompleted"
|
||||
@click="toggleShowCompleted"
|
||||
dense
|
||||
unelevated
|
||||
no-caps
|
||||
|
@ -39,7 +39,7 @@
|
|||
dense
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="remote-bg-tbl-sticky"
|
||||
:data="filter"
|
||||
:data="actions"
|
||||
:columns="columns"
|
||||
:visible-columns="visibleColumns"
|
||||
:pagination.sync="pagination"
|
||||
|
@ -69,7 +69,7 @@
|
|||
</q-td>
|
||||
<q-td>{{ props.row.due }}</q-td>
|
||||
<q-td>{{ props.row.description }}</q-td>
|
||||
<q-td v-if="props.row.action_type === 'chocoinstall'">
|
||||
<q-td v-if="props.row.action_type === 'chocoinstall' && props.row.status === 'completed'">
|
||||
<q-btn
|
||||
color="primary"
|
||||
icon="preview"
|
||||
|
@ -107,6 +107,7 @@ export default {
|
|||
selectedRow: null,
|
||||
showCompleted: false,
|
||||
selectedStatus: null,
|
||||
completedCount: 0,
|
||||
actionType: null,
|
||||
hostname: "",
|
||||
pagination: {
|
||||
|
@ -145,14 +146,21 @@ export default {
|
|||
html: true,
|
||||
});
|
||||
},
|
||||
toggleShowCompleted() {
|
||||
this.showCompleted = !this.showCompleted;
|
||||
this.getPendingActions();
|
||||
},
|
||||
getPendingActions() {
|
||||
let data = { showCompleted: this.showCompleted };
|
||||
if (!!this.agentpk) data.agentPK = this.agentpk;
|
||||
this.$q.loading.show();
|
||||
this.clearRow();
|
||||
this.$axios
|
||||
.get(this.url)
|
||||
.patch("/logs/pendingactions/", data)
|
||||
.then(r => {
|
||||
this.actions = Object.freeze(r.data);
|
||||
if (!!this.agentpk) this.hostname = r.data[0].hostname;
|
||||
this.completedCount = r.data.completed_count;
|
||||
this.actions = Object.freeze(r.data.actions);
|
||||
if (!!this.agentpk) this.hostname = r.data.actions[0].hostname;
|
||||
this.$q.loading.hide();
|
||||
})
|
||||
.catch(e => {
|
||||
|
@ -170,7 +178,7 @@ export default {
|
|||
this.$q.loading.show();
|
||||
const data = { pk: this.selectedRow };
|
||||
this.$axios
|
||||
.delete("/logs/cancelpendingaction/", { data: data })
|
||||
.delete("/logs/pendingactions/", { data: data })
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
this.getPendingActions();
|
||||
|
@ -202,12 +210,6 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
url() {
|
||||
return !!this.agentpk ? `/logs/${this.agentpk}/pendingactions/` : "/logs/allpendingactions/";
|
||||
},
|
||||
filter() {
|
||||
return this.showCompleted ? this.actions : this.actions.filter(k => k.status === "pending");
|
||||
},
|
||||
columns() {
|
||||
return !!this.agentpk ? this.agent_columns : this.all_columns;
|
||||
},
|
||||
|
@ -217,9 +219,6 @@ export default {
|
|||
title() {
|
||||
return !!this.agentpk ? `Pending Actions for ${this.hostname}` : "All Pending Actions";
|
||||
},
|
||||
completedCount() {
|
||||
return this.actions.filter(k => k.status === "completed").length;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getPendingActions();
|
||||
|
|
Loading…
Reference in New Issue