diff --git a/api/tacticalrmm/agents/migrations/0002_auto_20200206_0554.py b/api/tacticalrmm/agents/migrations/0002_auto_20200206_0554.py new file mode 100644 index 00000000..55a0a4d0 --- /dev/null +++ b/api/tacticalrmm/agents/migrations/0002_auto_20200206_0554.py @@ -0,0 +1,22 @@ +# Generated by Django 3.0.3 on 2020-02-06 05:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('agents', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='agent', + name='ping_check_interval', + ), + migrations.AddField( + model_name='agent', + name='check_interval', + field=models.PositiveIntegerField(default=120), + ), + ] diff --git a/api/tacticalrmm/agents/models.py b/api/tacticalrmm/agents/models.py index 11a4bc15..80932143 100644 --- a/api/tacticalrmm/agents/models.py +++ b/api/tacticalrmm/agents/models.py @@ -45,7 +45,7 @@ class Agent(models.Model): overdue_time = models.PositiveIntegerField(default=30) uninstall_pending = models.BooleanField(default=False) uninstall_inprogress = models.BooleanField(default=False) - ping_check_interval = models.PositiveIntegerField(default=300) + check_interval = models.PositiveIntegerField(default=120) needs_reboot = models.BooleanField(default=False) managed_by_wsus = models.BooleanField(default=False) is_updating = models.BooleanField(default=False) @@ -67,7 +67,6 @@ class Agent(models.Model): else: return "online" - @property def has_patches_pending(self): from winupdate.models import WinUpdate @@ -80,7 +79,7 @@ class Agent(models.Model): @property def salt_id(self): return f"{self.hostname}-{self.pk}" - + @staticmethod def salt_api_cmd(**kwargs): try: @@ -177,4 +176,3 @@ class Agent(models.Model): }, timeout=100) return resp """ - diff --git a/api/tacticalrmm/agents/serializers.py b/api/tacticalrmm/agents/serializers.py index 0729e61f..b5377ba4 100644 --- a/api/tacticalrmm/agents/serializers.py +++ b/api/tacticalrmm/agents/serializers.py @@ -44,7 +44,7 @@ class AgentSerializer(serializers.ModelSerializer): "status", "uninstall_pending", "uninstall_inprogress", - "ping_check_interval", + "check_interval", "needs_reboot", "managed_by_wsus", "is_updating", @@ -64,4 +64,3 @@ class AgentHostnameSerializer(serializers.ModelSerializer): "client", "site", ) - diff --git a/api/tacticalrmm/agents/tests.py b/api/tacticalrmm/agents/tests.py index 5f993d17..84911480 100644 --- a/api/tacticalrmm/agents/tests.py +++ b/api/tacticalrmm/agents/tests.py @@ -35,7 +35,7 @@ class TestAgentViews(BaseTestCase): "montype": "workstation", "desc": "asjdk234andasd", "overduetime": 300, - "pinginterval": 60, + "checkinterval": 60, "emailalert": True, "textalert": False, "critical": "approve", diff --git a/api/tacticalrmm/agents/views.py b/api/tacticalrmm/agents/views.py index 289e3257..01706a24 100644 --- a/api/tacticalrmm/agents/views.py +++ b/api/tacticalrmm/agents/views.py @@ -102,7 +102,7 @@ def edit_agent(request): agent.monitoring_type = request.data["montype"] agent.description = request.data["desc"] agent.overdue_time = request.data["overduetime"] - agent.ping_check_interval = request.data["pinginterval"] + agent.check_interval = request.data["checkinterval"] agent.overdue_email_alert = request.data["emailalert"] agent.overdue_text_alert = request.data["textalert"] @@ -126,7 +126,7 @@ def edit_agent(request): "site", "monitoring_type", "description", - "ping_check_interval", + "check_interval", "overdue_time", "overdue_email_alert", "overdue_text_alert", @@ -199,9 +199,7 @@ def get_processes(request, pk): agent = get_object_or_404(Agent, pk=pk) try: resp = agent.salt_api_cmd( - hostname=agent.salt_id, - timeout=70, - func="process_manager.get_procs" + hostname=agent.salt_id, timeout=70, func="process_manager.get_procs" ) data = resp.json() except Exception: @@ -211,14 +209,12 @@ def get_processes(request, pk): return Response(data["return"][0][agent.salt_id]) + @api_view() def kill_proc(request, pk, pid): agent = get_object_or_404(Agent, pk=pk) resp = agent.salt_api_cmd( - hostname=agent.salt_id, - timeout=60, - func="ps.kill_pid", - arg=int(pid) + hostname=agent.salt_id, timeout=60, func="ps.kill_pid", arg=int(pid) ) data = resp.json() diff --git a/api/tacticalrmm/checks/serializers.py b/api/tacticalrmm/checks/serializers.py index 9d830a61..f6f04dbf 100644 --- a/api/tacticalrmm/checks/serializers.py +++ b/api/tacticalrmm/checks/serializers.py @@ -100,7 +100,7 @@ class CheckSerializer(serializers.ModelSerializer): "hostname", "pk", "disks", - "ping_check_interval", + "check_interval", "diskchecks", "cpuloadchecks", "memchecks", diff --git a/api/tacticalrmm/checks/urls.py b/api/tacticalrmm/checks/urls.py index 594aec57..0f3aa514 100644 --- a/api/tacticalrmm/checks/urls.py +++ b/api/tacticalrmm/checks/urls.py @@ -12,4 +12,5 @@ urlpatterns = [ path("checkalert/", views.check_alert), path("updatepingcheck/", views.update_ping_check), path("updatescriptcheck/", views.update_script_check), + path("getscripts/", views.get_scripts), ] diff --git a/api/tacticalrmm/checks/views.py b/api/tacticalrmm/checks/views.py index 51256f6e..50552ef7 100644 --- a/api/tacticalrmm/checks/views.py +++ b/api/tacticalrmm/checks/views.py @@ -23,6 +23,7 @@ from .models import ( CpuLoadCheck, MemCheck, WinServiceCheck, + Script, ScriptCheck, ScriptCheckEmail, validate_threshold, @@ -36,11 +37,18 @@ from .serializers import ( MemCheckSerializer, WinServiceCheckSerializer, ScriptCheckSerializer, + ScriptSerializer, ) from .tasks import handle_check_email_alert_task +@api_view() +def get_scripts(request): + scripts = Script.objects.all() + return Response(ScriptSerializer(scripts, many=True, read_only=True).data) + + @api_view(["PATCH"]) @authentication_classes((TokenAuthentication,)) @permission_classes((IsAuthenticated,)) @@ -62,12 +70,12 @@ def update_ping_check(request): if new_count >= check.failures: alert = True - if alert: - if check.email_alert: - handle_check_email_alert_task.delay("ping", check.pk) + if alert and check.email_alert: + handle_check_email_alert_task.delay("ping", check.pk) return Response("ok") + @api_view(["PATCH"]) @authentication_classes((TokenAuthentication,)) @permission_classes((IsAuthenticated,)) @@ -75,26 +83,12 @@ def update_script_check(request): check = get_object_or_404(ScriptCheck, pk=request.data["id"]) try: - retcode = request.data["output"]["local"]["retcode"] + output = request.data["output"] + status = request.data["status"] except Exception: - retcode = 1 - - if retcode == 0: - status = "passing" - else: + output = "something went wrong" status = "failing" - - try: - stdout = request.data["output"]["local"]["stdout"] - stderr = request.data["output"]["local"]["stderr"] - except Exception: - output = "error running script" - else: - if stdout: - output = stdout - else: - output = stderr - + check.status = status check.more_info = output check.save(update_fields=["status", "more_info", "last_run"]) @@ -111,9 +105,8 @@ def update_script_check(request): if new_count >= check.failures: alert = True - if alert: - if check.email_alert: - handle_check_email_alert_task.delay("script", check.pk) + if alert and check.email_alert: + handle_check_email_alert_task.delay("script", check.pk) return Response("ok") @@ -218,6 +211,28 @@ def add_standard_check(request): ).save() return Response("ok") + elif request.data["check_type"] == "script": + script_pk = request.data["scriptPk"] + timeout = request.data["timeout"] + failures = request.data["failures"] + + script = Script.objects.get(pk=script_pk) + + if ScriptCheck.objects.filter(agent=agent).filter(script=script).exists(): + return Response( + f"{script.name} already exists on {agent.hostname}", + status=status.HTTP_400_BAD_REQUEST, + ) + + ScriptCheck( + agent=agent, timeout=timeout, failures=failures, script=script + ).save() + + return Response(f"{script.name} was added on {agent.hostname}!") + + else: + return Response("something went wrong", status=status.HTTP_400_BAD_REQUEST) + @api_view(["PATCH"]) def edit_standard_check(request): @@ -269,6 +284,13 @@ def edit_standard_check(request): ) return Response("ok") + elif request.data["check_type"] == "script": + check = get_object_or_404(ScriptCheck, pk=request.data["pk"]) + check.failures = request.data["failures"] + check.timeout = request.data["timeout"] + check.save(update_fields=["failures", "timeout"]) + return Response(f"{check.script.name} was edited on {check.agent.hostname}") + @api_view() def get_standard_check(request, checktype, pk): @@ -287,6 +309,9 @@ def get_standard_check(request, checktype, pk): elif checktype == "winsvc": check = WinServiceCheck.objects.get(pk=pk) return Response(WinServiceCheckSerializer(check).data) + elif checktype == "script": + check = ScriptCheck.objects.get(pk=pk) + return Response(ScriptCheckSerializer(check).data) @api_view(["DELETE"]) @@ -302,6 +327,8 @@ def delete_standard_check(request): check = MemCheck.objects.get(pk=pk) elif request.data["checktype"] == "winsvc": check = WinServiceCheck.objects.get(pk=pk) + elif request.data["checktype"] == "script": + check = ScriptCheck.objects.get(pk=pk) check.delete() return Response("ok") @@ -323,6 +350,8 @@ def check_alert(request): check = PingCheck.objects.get(pk=checkid) elif category == "winsvc": check = WinServiceCheck.objects.get(pk=checkid) + elif category == "script": + check = ScriptCheck.objects.get(pk=checkid) else: return Response( {"error": "Something went wrong"}, status=status.HTTP_400_BAD_REQUEST diff --git a/web/src/components/ChecksTab.vue b/web/src/components/ChecksTab.vue index 8b89a525..471fa654 100644 --- a/web/src/components/ChecksTab.vue +++ b/web/src/components/ChecksTab.vue @@ -20,6 +20,9 @@ Windows Service Check + + Script Check + @@ -38,9 +41,8 @@ More Info Date / Time - + - - + + + Edit - + + + Delete @@ -87,11 +93,14 @@ v-if="check.check_type === 'diskspace'" >Disk Space Drive {{ check.disk }} > {{check.threshold }}% Avg CPU Load > {{ check.cpuload }}% + Script check: {{ check.script.name }} Ping {{ check.name }} ({{ check.ip }}) Avg memory usage > {{ check.threshold }}% - Service Check - {{ check.svc_display_name }} + Service Check - {{ check.svc_display_name }} Awaiting First Synchronization Passing @@ -100,19 +109,21 @@ Failing - - output - + @click="moreInfo('Ping', check.more_info)" + >output + + + output {{ check.more_info }} {{ check.last_run }} - - @@ -169,6 +180,17 @@ :agentpk="checks.pk" /> + + + + + + + @@ -186,6 +208,8 @@ import AddMemCheck from "@/components/modals/checks/AddMemCheck"; import EditMemCheck from "@/components/modals/checks/EditMemCheck"; import AddWinSvcCheck from "@/components/modals/checks/AddWinSvcCheck"; import EditWinSvcCheck from "@/components/modals/checks/EditWinSvcCheck"; +import AddScriptCheck from "@/components/modals/checks/AddScriptCheck"; +import EditScriptCheck from "@/components/modals/checks/EditScriptCheck"; export default { name: "ChecksTab", @@ -199,7 +223,9 @@ export default { AddMemCheck, EditMemCheck, AddWinSvcCheck, - EditWinSvcCheck + EditWinSvcCheck, + AddScriptCheck, + EditScriptCheck }, mixins: [mixins], data() { @@ -214,6 +240,8 @@ export default { showEditMemCheck: false, showAddWinSvcCheck: false, showEditWinSvcCheck: false, + showAddScriptCheck: false, + showEditScriptCheck: false, editCheckPK: null }; }, @@ -238,10 +266,10 @@ export default { onRefresh(id) { this.$store.dispatch("loadChecks", id); }, - pingMoreInfo(output) { + moreInfo(name, output) { this.$q.dialog({ - title: "Ping output", - style: "width: 600px; max-width: 90vw", + title: `${name} output`, + style: "width: 80vw; max-width: 90vw", message: `
${output}
`, html: true, dark: true @@ -264,6 +292,9 @@ export default { case "winsvc": this.showEditWinSvcCheck = true; break; + case "script": + this.showEditScriptCheck = true; + break; default: return false; } @@ -294,11 +325,12 @@ export default { }), allChecks() { return [ - ...this.checks.pingchecks, ...this.checks.diskchecks, ...this.checks.cpuloadchecks, ...this.checks.memchecks, - ...this.checks.winservicechecks + ...this.checks.scriptchecks, + ...this.checks.winservicechecks, + ...this.checks.pingchecks ]; } } diff --git a/web/src/components/modals/agents/EditAgent.vue b/web/src/components/modals/agents/EditAgent.vue index 65449105..c90334ce 100644 --- a/web/src/components/modals/agents/EditAgent.vue +++ b/web/src/components/modals/agents/EditAgent.vue @@ -52,8 +52,8 @@ + + +
Add Script Check
+ + +
+ +

You need to upload a script first

+

Settings -> Script Manager

+
+
+ + +
Add Script Check
+ + +
+ + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/web/src/components/modals/checks/EditScriptCheck.vue b/web/src/components/modals/checks/EditScriptCheck.vue new file mode 100644 index 00000000..5bfc4eea --- /dev/null +++ b/web/src/components/modals/checks/EditScriptCheck.vue @@ -0,0 +1,89 @@ + + + \ No newline at end of file diff --git a/web/src/store/store.js b/web/src/store/store.js index bf9d833b..a4ce2ec2 100644 --- a/web/src/store/store.js +++ b/web/src/store/store.js @@ -23,7 +23,8 @@ export const store = new Vuex.Store({ agentChecks: {}, agentTableLoading: false, treeLoading: false, - installedSoftware: [] + installedSoftware: [], + scripts: [], }, getters: { loggedIn(state) { @@ -50,6 +51,9 @@ export const store = new Vuex.Store({ }, agentHostname(state) { return state.agentSummary.hostname; + }, + scripts(state) { + return state.scripts; } }, mutations: { @@ -92,9 +96,17 @@ export const store = new Vuex.Store({ (state.winUpdates = {}); (state.installedSoftware = []); state.selectedRow = ""; + }, + SET_SCRIPTS(state, scripts) { + state.scripts = scripts; } }, actions: { + getScripts(context) { + axios.get("/checks/getscripts/").then(r => { + context.commit("SET_SCRIPTS", r.data); + }) + }, loadInstalledSoftware(context, pk) { axios.get(`/software/installed/${pk}`).then(r => { context.commit("SET_INSTALLED_SOFTWARE", r.data.software);