Compare commits
	
		
			71 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 9ab915a08b | ||
|  | e26fbf0328 | ||
|  | d9a52c4a2a | ||
|  | 7b2ec90de9 | ||
|  | d310bf8bbf | ||
|  | 2abc6cc939 | ||
|  | 56d4e694a2 | ||
|  | 5f002c9cdc | ||
|  | 759daf4b4a | ||
|  | 3a8d9568e3 | ||
|  | ff22a9d94a | ||
|  | a6e42d5374 | ||
|  | a2f74e0488 | ||
|  | ee44240569 | ||
|  | d0828744a2 | ||
|  | 6e2e576b29 | ||
|  | bf61e27f8a | ||
|  | c441c30b46 | ||
|  | 0e741230ea | ||
|  | 1bfe9ac2db | ||
|  | 6812e72348 | ||
|  | b6449d2f5b | ||
|  | 7e3ea20dce | ||
|  | c9d6fe9dcd | ||
|  | 4a649a6b8b | ||
|  | 8fef184963 | ||
|  | 69583ca3c0 | ||
|  | 6038a68e91 | ||
|  | fa8bd8db87 | ||
|  | 18b4f0ed0f | ||
|  | 461f9d66c9 | ||
|  | 2155103c7a | ||
|  | c9a6839c45 | ||
|  | 9fbe331a80 | ||
|  | a56389c4ce | ||
|  | 64656784cb | ||
|  | 6eff2c181e | ||
|  | 1aa48c6d62 | ||
|  | c7ca1a346d | ||
|  | fa0ec7b502 | ||
|  | 768438c136 | ||
|  | 9badea0b3c | ||
|  | 43263a1650 | ||
|  | 821e02dc75 | ||
|  | ed011ecf28 | ||
|  | d861de4c2f | ||
|  | 3a3b2449dc | ||
|  | d2614406ca | ||
|  | 0798d098ae | ||
|  | dab7ddc2bb | ||
|  | 081a96e281 | ||
|  | a7dd881d79 | ||
|  | 8134d5e24d | ||
|  | ba6756cd45 | ||
|  | 5d8fce21ac | ||
|  | e7e4a5bcd4 | ||
|  | 55f33357ea | ||
|  | 90568bba31 | ||
|  | 5d6e2dc2e4 | ||
|  | 6bb33f2559 | ||
|  | ced92554ed | ||
|  | dff3383158 | ||
|  | bf03c89cb2 | ||
|  | 9f1484bbef | ||
|  | 3899680e26 | ||
|  | 6bb2eb25a1 | ||
|  | f8dfd8edb3 | ||
|  | 042be624a3 | ||
|  | 6bafa4c79a | ||
|  | 58b42fac5c | ||
|  | 3b47b9558a | 
| @@ -1,6 +1,6 @@ | ||||
| COMPOSE_PROJECT_NAME=trmm | ||||
|  | ||||
| IMAGE_REPO=tacticalrmm | ||||
| IMAGE_REPO=tacticalrmm/ | ||||
| VERSION=latest | ||||
|  | ||||
| # tactical credentials (Used to login to dashboard) | ||||
|   | ||||
| @@ -133,6 +133,7 @@ services: | ||||
|       APP_HOST: ${APP_HOST} | ||||
|       API_HOST: ${API_HOST} | ||||
|       MESH_HOST: ${MESH_HOST} | ||||
|       MESH_USER: ${MESH_USER} | ||||
|       TRMM_USER: ${TRMM_USER} | ||||
|       TRMM_PASS: ${TRMM_PASS} | ||||
|     depends_on: | ||||
|   | ||||
| @@ -68,7 +68,7 @@ KEY_FILE = '/opt/tactical/certs/privkey.pem' | ||||
|  | ||||
| SCRIPTS_DIR = '${WORKSPACE_DIR}/scripts' | ||||
|  | ||||
| ALLOWED_HOSTS = ['${API_HOST}'] | ||||
| ALLOWED_HOSTS = ['${API_HOST}', 'localhost', '127.0.0.1'] | ||||
|  | ||||
| ADMIN_URL = 'admin/' | ||||
|  | ||||
| @@ -137,8 +137,8 @@ if [ "$1" = 'tactical-init-dev' ]; then | ||||
|   test -f "${TACTICAL_READY_FILE}" && rm "${TACTICAL_READY_FILE}" | ||||
|  | ||||
|   # setup Python virtual env and install dependencies | ||||
|   python -m venv ${VIRTUAL_ENV} | ||||
|   env/bin/pip install --no-cache-dir -r /requirements.txt | ||||
|   test -f ${VIRTUAL_ENV} && python -m venv --copies ${VIRTUAL_ENV} | ||||
|   pip install --no-cache-dir -r /requirements.txt | ||||
|  | ||||
|   django_setup | ||||
|  | ||||
|   | ||||
| @@ -21,4 +21,5 @@ | ||||
| **/obj | ||||
| **/secrets.dev.yaml | ||||
| **/values.dev.yaml | ||||
| **/env | ||||
| README.md | ||||
|   | ||||
							
								
								
									
										19
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -41,4 +41,23 @@ | ||||
|             "**/*.zip": true | ||||
|         }, | ||||
|     }, | ||||
|     "go.useLanguageServer": true, | ||||
|     "[go]": { | ||||
|         "editor.formatOnSave": true, | ||||
|         "editor.codeActionsOnSave": { | ||||
|             "source.organizeImports": false, | ||||
|         }, | ||||
|         "editor.snippetSuggestions": "none", | ||||
|     }, | ||||
|     "[go.mod]": { | ||||
|         "editor.formatOnSave": true, | ||||
|         "editor.codeActionsOnSave": { | ||||
|             "source.organizeImports": true, | ||||
|         }, | ||||
|     }, | ||||
|     "gopls": { | ||||
|         "usePlaceholders": true, | ||||
|         "completeUnimported": true, | ||||
|         "staticcheck": true, | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,26 @@ | ||||
| # Generated by Django 3.1.4 on 2021-01-14 01:23 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("accounts", "0009_user_show_community_scripts"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="user", | ||||
|             name="agent_dblclick_action", | ||||
|             field=models.CharField( | ||||
|                 choices=[ | ||||
|                     ("editagent", "Edit Agent"), | ||||
|                     ("takecontrol", "Take Control"), | ||||
|                     ("remotebg", "Remote Background"), | ||||
|                 ], | ||||
|                 default="editagent", | ||||
|                 max_length=50, | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @@ -3,12 +3,21 @@ from django.contrib.auth.models import AbstractUser | ||||
|  | ||||
| from logs.models import BaseAuditModel | ||||
|  | ||||
| AGENT_DBLCLICK_CHOICES = [ | ||||
|     ("editagent", "Edit Agent"), | ||||
|     ("takecontrol", "Take Control"), | ||||
|     ("remotebg", "Remote Background"), | ||||
| ] | ||||
|  | ||||
|  | ||||
| class User(AbstractUser, BaseAuditModel): | ||||
|     is_active = models.BooleanField(default=True) | ||||
|     totp_key = models.CharField(max_length=50, null=True, blank=True) | ||||
|     dark_mode = models.BooleanField(default=True) | ||||
|     show_community_scripts = models.BooleanField(default=True) | ||||
|     agent_dblclick_action = models.CharField( | ||||
|         max_length=50, choices=AGENT_DBLCLICK_CHOICES, default="editagent" | ||||
|     ) | ||||
|  | ||||
|     agent = models.OneToOneField( | ||||
|         "agents.Agent", | ||||
|   | ||||
| @@ -278,6 +278,18 @@ class TestUserAction(TacticalTestCase): | ||||
|         r = self.client.patch(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 200) | ||||
|  | ||||
|         data = {"agent_dblclick_action": "editagent"} | ||||
|         r = self.client.patch(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 200) | ||||
|  | ||||
|         data = {"agent_dblclick_action": "remotebg"} | ||||
|         r = self.client.patch(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 200) | ||||
|  | ||||
|         data = {"agent_dblclick_action": "takecontrol"} | ||||
|         r = self.client.patch(url, data, format="json") | ||||
|         self.assertEqual(r.status_code, 200) | ||||
|  | ||||
|         self.check_not_authenticated("patch", url) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -197,4 +197,8 @@ class UserUI(APIView): | ||||
|             user.show_community_scripts = request.data["show_community_scripts"] | ||||
|             user.save(update_fields=["show_community_scripts"]) | ||||
|  | ||||
|         if "agent_dblclick_action" in request.data: | ||||
|             user.agent_dblclick_action = request.data["agent_dblclick_action"] | ||||
|             user.save(update_fields=["agent_dblclick_action"]) | ||||
|  | ||||
|         return Response("ok") | ||||
|   | ||||
| @@ -3,11 +3,11 @@ from loguru import logger | ||||
| from time import sleep | ||||
| import random | ||||
| import requests | ||||
| from concurrent.futures import ThreadPoolExecutor | ||||
| from packaging import version as pyver | ||||
| from typing import List | ||||
|  | ||||
| from django.conf import settings | ||||
| from scripts.models import Script | ||||
|  | ||||
| from tacticalrmm.celery import app | ||||
| from agents.models import Agent, AgentOutage | ||||
| @@ -39,16 +39,21 @@ def check_in_task() -> None: | ||||
|     agents: List[int] = [ | ||||
|         i.pk for i in q if pyver.parse(i.version) >= pyver.parse("1.1.12") | ||||
|     ] | ||||
|     with ThreadPoolExecutor(max_workers=30) as executor: | ||||
|         executor.map(_check_in_full, agents) | ||||
|     chunks = (agents[i : i + 50] for i in range(0, len(agents), 50)) | ||||
|     for chunk in chunks: | ||||
|         for pk in chunk: | ||||
|             _check_in_full(pk) | ||||
|             sleep(0.1) | ||||
|         rand = random.randint(3, 7) | ||||
|         sleep(rand) | ||||
|  | ||||
|  | ||||
| @app.task | ||||
| def monitor_agents_task() -> None: | ||||
|     q = Agent.objects.all() | ||||
|     agents: List[int] = [i.pk for i in q if i.has_nats and i.status != "online"] | ||||
|     with ThreadPoolExecutor(max_workers=15) as executor: | ||||
|         executor.map(_check_agent_service, agents) | ||||
|     for agent in agents: | ||||
|         _check_agent_service(agent) | ||||
|  | ||||
|  | ||||
| def agent_update(pk: int) -> str: | ||||
| @@ -115,7 +120,6 @@ def send_agent_update_task(pks: List[int], version: str) -> None: | ||||
| def auto_self_agent_update_task() -> None: | ||||
|     core = CoreSettings.objects.first() | ||||
|     if not core.agent_auto_update: | ||||
|         logger.info("Agent auto update is disabled. Skipping.") | ||||
|         return | ||||
|  | ||||
|     q = Agent.objects.only("pk", "version") | ||||
| @@ -137,8 +141,14 @@ def sync_sysinfo_task(): | ||||
|         for i in agents | ||||
|         if pyver.parse(i.version) >= pyver.parse("1.1.3") and i.status == "online" | ||||
|     ] | ||||
|     for agent in online: | ||||
|         asyncio.run(agent.nats_cmd({"func": "sync"}, wait=False)) | ||||
|  | ||||
|     chunks = (online[i : i + 50] for i in range(0, len(online), 50)) | ||||
|     for chunk in chunks: | ||||
|         for agent in chunk: | ||||
|             asyncio.run(agent.nats_cmd({"func": "sync"}, wait=False)) | ||||
|             sleep(0.1) | ||||
|         rand = random.randint(3, 7) | ||||
|         sleep(rand) | ||||
|  | ||||
|  | ||||
| @app.task | ||||
| @@ -281,6 +291,10 @@ def agent_outages_task(): | ||||
|                 outage = AgentOutage(agent=agent) | ||||
|                 outage.save() | ||||
|  | ||||
|                 # add a null check history to allow gaps in graph | ||||
|                 for check in agent.agentchecks.all(): | ||||
|                     check.add_check_history(None) | ||||
|  | ||||
|                 if agent.overdue_email_alert and not agent.maintenance_mode: | ||||
|                     agent_outage_email_task.delay(pk=outage.pk) | ||||
|  | ||||
| @@ -293,3 +307,52 @@ def install_salt_task(pk: int) -> None: | ||||
|     sleep(20) | ||||
|     agent = Agent.objects.get(pk=pk) | ||||
|     asyncio.run(agent.nats_cmd({"func": "installsalt"}, wait=False)) | ||||
|  | ||||
|  | ||||
| @app.task | ||||
| def run_script_email_results_task( | ||||
|     agentpk: int, scriptpk: int, nats_timeout: int, nats_data: dict, emails: List[str] | ||||
| ): | ||||
|     agent = Agent.objects.get(pk=agentpk) | ||||
|     script = Script.objects.get(pk=scriptpk) | ||||
|     nats_data["func"] = "runscriptfull" | ||||
|     r = asyncio.run(agent.nats_cmd(nats_data, timeout=nats_timeout)) | ||||
|     if r == "timeout": | ||||
|         logger.error(f"{agent.hostname} timed out running script.") | ||||
|         return | ||||
|  | ||||
|     CORE = CoreSettings.objects.first() | ||||
|     subject = f"{agent.hostname} {script.name} Results" | ||||
|     exec_time = "{:.4f}".format(r["execution_time"]) | ||||
|     body = ( | ||||
|         subject | ||||
|         + f"\nReturn code: {r['retcode']}\nExecution time: {exec_time} seconds\nStdout: {r['stdout']}\nStderr: {r['stderr']}" | ||||
|     ) | ||||
|  | ||||
|     import smtplib | ||||
|     from email.message import EmailMessage | ||||
|  | ||||
|     msg = EmailMessage() | ||||
|     msg["Subject"] = subject | ||||
|     msg["From"] = CORE.smtp_from_email | ||||
|  | ||||
|     if emails: | ||||
|         msg["To"] = ", ".join(emails) | ||||
|     else: | ||||
|         msg["To"] = ", ".join(CORE.email_alert_recipients) | ||||
|  | ||||
|     msg.set_content(body) | ||||
|  | ||||
|     try: | ||||
|         with smtplib.SMTP(CORE.smtp_host, CORE.smtp_port, timeout=20) as server: | ||||
|             if CORE.smtp_requires_auth: | ||||
|                 server.ehlo() | ||||
|                 server.starttls() | ||||
|                 server.login(CORE.smtp_host_user, CORE.smtp_host_password) | ||||
|                 server.send_message(msg) | ||||
|                 server.quit() | ||||
|             else: | ||||
|                 server.send_message(msg) | ||||
|                 server.quit() | ||||
|     except Exception as e: | ||||
|         logger.error(e) | ||||
|   | ||||
| @@ -32,7 +32,11 @@ from .serializers import ( | ||||
| ) | ||||
| from winupdate.serializers import WinUpdatePolicySerializer | ||||
|  | ||||
| from .tasks import uninstall_agent_task, send_agent_update_task | ||||
| from .tasks import ( | ||||
|     uninstall_agent_task, | ||||
|     send_agent_update_task, | ||||
|     run_script_email_results_task, | ||||
| ) | ||||
| from winupdate.tasks import bulk_check_for_updates_task | ||||
| from scripts.tasks import handle_bulk_command_task, handle_bulk_script_task | ||||
|  | ||||
| @@ -738,6 +742,21 @@ def run_script(request): | ||||
|     if output == "wait": | ||||
|         r = asyncio.run(agent.nats_cmd(data, timeout=req_timeout)) | ||||
|         return Response(r) | ||||
|     elif output == "email": | ||||
|         if not pyver.parse(agent.version) >= pyver.parse("1.1.12"): | ||||
|             return notify_error("Requires agent version 1.1.12 or greater") | ||||
|  | ||||
|         emails = ( | ||||
|             [] if request.data["emailmode"] == "default" else request.data["emails"] | ||||
|         ) | ||||
|         run_script_email_results_task.delay( | ||||
|             agentpk=agent.pk, | ||||
|             scriptpk=script.pk, | ||||
|             nats_timeout=req_timeout, | ||||
|             nats_data=data, | ||||
|             emails=emails, | ||||
|         ) | ||||
|         return Response(f"{script.name} will now be run on {agent.hostname}") | ||||
|     else: | ||||
|         asyncio.run(agent.nats_cmd(data, wait=False)) | ||||
|         return Response(f"{script.name} will now be run on {agent.hostname}") | ||||
|   | ||||
| @@ -266,16 +266,6 @@ class CheckRunner(APIView): | ||||
|         check.save(update_fields=["last_run"]) | ||||
|         status = check.handle_checkv2(request.data) | ||||
|  | ||||
|         # create audit entry | ||||
|         AuditLog.objects.create( | ||||
|             username=check.agent.hostname, | ||||
|             agent=check.agent.hostname, | ||||
|             object_type="agent", | ||||
|             action="check_run", | ||||
|             message=f"{check.readable_desc} was run on {check.agent.hostname}. Status: {status}", | ||||
|             after_value=Check.serialize(check), | ||||
|         ) | ||||
|  | ||||
|         return Response(status) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -176,6 +176,12 @@ def delete_win_task_schedule(pk, pending_action=False): | ||||
|         pendingaction.status = "completed" | ||||
|         pendingaction.save(update_fields=["status"]) | ||||
|  | ||||
|     # complete any other pending actions on agent with same task_id | ||||
|     for action in task.agent.pendingactions.all(): | ||||
|         if action.details["task_id"] == task.id: | ||||
|             action.status = "completed" | ||||
|             action.save() | ||||
|  | ||||
|     task.delete() | ||||
|     return "ok" | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from django.contrib import admin | ||||
|  | ||||
| from .models import Check | ||||
| from .models import Check, CheckHistory | ||||
|  | ||||
| admin.site.register(Check) | ||||
| admin.site.register(CheckHistory) | ||||
|   | ||||
							
								
								
									
										30
									
								
								api/tacticalrmm/checks/migrations/0011_check_run_history.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								api/tacticalrmm/checks/migrations/0011_check_run_history.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| # Generated by Django 3.1.4 on 2021-01-09 02:56 | ||||
|  | ||||
| import django.contrib.postgres.fields | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("checks", "0010_auto_20200922_1344"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="check", | ||||
|             name="run_history", | ||||
|             field=django.contrib.postgres.fields.ArrayField( | ||||
|                 base_field=django.contrib.postgres.fields.ArrayField( | ||||
|                     base_field=models.PositiveIntegerField(), | ||||
|                     blank=True, | ||||
|                     null=True, | ||||
|                     size=None, | ||||
|                 ), | ||||
|                 blank=True, | ||||
|                 default=list, | ||||
|                 null=True, | ||||
|                 size=None, | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										39
									
								
								api/tacticalrmm/checks/migrations/0011_checkhistory.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								api/tacticalrmm/checks/migrations/0011_checkhistory.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| # Generated by Django 3.1.4 on 2021-01-09 21:36 | ||||
|  | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("checks", "0010_auto_20200922_1344"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name="CheckHistory", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "id", | ||||
|                     models.AutoField( | ||||
|                         auto_created=True, | ||||
|                         primary_key=True, | ||||
|                         serialize=False, | ||||
|                         verbose_name="ID", | ||||
|                     ), | ||||
|                 ), | ||||
|                 ("x", models.DateTimeField()), | ||||
|                 ("y", models.PositiveIntegerField()), | ||||
|                 ("results", models.JSONField(blank=True, null=True)), | ||||
|                 ( | ||||
|                     "check_history", | ||||
|                     models.ForeignKey( | ||||
|                         on_delete=django.db.models.deletion.CASCADE, | ||||
|                         related_name="check_history", | ||||
|                         to="checks.check", | ||||
|                     ), | ||||
|                 ), | ||||
|             ], | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										18
									
								
								api/tacticalrmm/checks/migrations/0012_auto_20210110_0503.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								api/tacticalrmm/checks/migrations/0012_auto_20210110_0503.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # Generated by Django 3.1.4 on 2021-01-10 05:03 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("checks", "0011_checkhistory"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name="checkhistory", | ||||
|             name="y", | ||||
|             field=models.PositiveIntegerField(blank=True, null=True), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										18
									
								
								api/tacticalrmm/checks/migrations/0013_auto_20210110_0505.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								api/tacticalrmm/checks/migrations/0013_auto_20210110_0505.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # Generated by Django 3.1.4 on 2021-01-10 05:05 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("checks", "0012_auto_20210110_0503"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name="checkhistory", | ||||
|             name="y", | ||||
|             field=models.PositiveIntegerField(null=True), | ||||
|         ), | ||||
|     ] | ||||
| @@ -0,0 +1,13 @@ | ||||
| # Generated by Django 3.1.4 on 2021-01-10 18:08 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("checks", "0013_auto_20210110_0505"), | ||||
|         ("checks", "0011_check_run_history"), | ||||
|     ] | ||||
|  | ||||
|     operations = [] | ||||
							
								
								
									
										27
									
								
								api/tacticalrmm/checks/migrations/0015_auto_20210110_1808.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								api/tacticalrmm/checks/migrations/0015_auto_20210110_1808.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| # Generated by Django 3.1.4 on 2021-01-10 18:08 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("checks", "0014_merge_20210110_1808"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.RemoveField( | ||||
|             model_name="check", | ||||
|             name="run_history", | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name="checkhistory", | ||||
|             name="x", | ||||
|             field=models.DateTimeField(auto_now_add=True), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name="checkhistory", | ||||
|             name="y", | ||||
|             field=models.PositiveIntegerField(blank=True, default=None, null=True), | ||||
|         ), | ||||
|     ] | ||||
| @@ -3,12 +3,13 @@ import string | ||||
| import os | ||||
| import json | ||||
| import pytz | ||||
| from statistics import mean | ||||
| from statistics import mean, mode | ||||
|  | ||||
| from django.db import models | ||||
| from django.conf import settings | ||||
| from django.contrib.postgres.fields import ArrayField | ||||
| from django.core.validators import MinValueValidator, MaxValueValidator | ||||
| from rest_framework.fields import JSONField | ||||
|  | ||||
| from core.models import CoreSettings | ||||
| from logs.models import BaseAuditModel | ||||
| @@ -214,6 +215,10 @@ class Check(BaseAuditModel): | ||||
|             "modified_time", | ||||
|         ] | ||||
|  | ||||
|     def add_check_history(self, value): | ||||
|         if self.check_type in ["memory", "cpuload", "diskspace"]: | ||||
|             CheckHistory.objects.create(check_history=self, y=value) | ||||
|  | ||||
|     def handle_checkv2(self, data): | ||||
|         # cpuload or mem checks | ||||
|         if self.check_type == "cpuload" or self.check_type == "memory": | ||||
| @@ -232,6 +237,9 @@ class Check(BaseAuditModel): | ||||
|             else: | ||||
|                 self.status = "passing" | ||||
|  | ||||
|             # add check history | ||||
|             self.add_check_history(data["percent"]) | ||||
|  | ||||
|         # diskspace checks | ||||
|         elif self.check_type == "diskspace": | ||||
|             if data["exists"]: | ||||
| @@ -245,6 +253,9 @@ class Check(BaseAuditModel): | ||||
|                     self.status = "passing" | ||||
|  | ||||
|                 self.more_info = f"Total: {total}B, Free: {free}B" | ||||
|  | ||||
|                 # add check history | ||||
|                 self.add_check_history(percent_used) | ||||
|             else: | ||||
|                 self.status = "failing" | ||||
|                 self.more_info = f"Disk {self.disk} does not exist" | ||||
| @@ -645,3 +656,17 @@ class Check(BaseAuditModel): | ||||
|             body = subject | ||||
|  | ||||
|         CORE.send_sms(body) | ||||
|  | ||||
|  | ||||
| class CheckHistory(models.Model): | ||||
|     check_history = models.ForeignKey( | ||||
|         Check, | ||||
|         related_name="check_history", | ||||
|         on_delete=models.CASCADE, | ||||
|     ) | ||||
|     x = models.DateTimeField(auto_now_add=True) | ||||
|     y = models.PositiveIntegerField(null=True, blank=True, default=None) | ||||
|     results = models.JSONField(null=True, blank=True) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.check_history.readable_desc | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import validators as _v | ||||
|  | ||||
| import pytz | ||||
| from rest_framework import serializers | ||||
|  | ||||
| from .models import Check | ||||
| from .models import Check, CheckHistory | ||||
| from autotasks.models import AutomatedTask | ||||
| from scripts.serializers import ScriptSerializer, ScriptCheckSerializer | ||||
|  | ||||
| @@ -237,3 +237,15 @@ class CheckResultsSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = Check | ||||
|         fields = "__all__" | ||||
|  | ||||
|  | ||||
| class CheckHistorySerializer(serializers.ModelSerializer): | ||||
|     x = serializers.SerializerMethodField() | ||||
|  | ||||
|     def get_x(self, obj): | ||||
|         return obj.x.astimezone(pytz.timezone(self.context["timezone"])).isoformat() | ||||
|  | ||||
|     # used for return large amounts of graph data | ||||
|     class Meta: | ||||
|         model = CheckHistory | ||||
|         fields = ("x", "y") | ||||
|   | ||||
| @@ -5,8 +5,6 @@ from time import sleep | ||||
| from tacticalrmm.celery import app | ||||
| from django.utils import timezone as djangotime | ||||
|  | ||||
| from agents.models import Agent | ||||
|  | ||||
|  | ||||
| @app.task | ||||
| def handle_check_email_alert_task(pk): | ||||
| @@ -56,3 +54,15 @@ def handle_check_sms_alert_task(pk): | ||||
|                 check.save(update_fields=["text_sent"]) | ||||
|  | ||||
|     return "ok" | ||||
|  | ||||
|  | ||||
| @app.task | ||||
| def prune_check_history(older_than_days: int) -> str: | ||||
|     from .models import CheckHistory | ||||
|  | ||||
|     CheckHistory.objects.filter( | ||||
|         x__lt=djangotime.make_aware(dt.datetime.today()) | ||||
|         - djangotime.timedelta(days=older_than_days) | ||||
|     ).delete() | ||||
|  | ||||
|     return "ok" | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| from checks.models import CheckHistory | ||||
| from tacticalrmm.test import TacticalTestCase | ||||
| from .serializers import CheckSerializer | ||||
| from django.utils import timezone as djangotime | ||||
|  | ||||
| from model_bakery import baker | ||||
| from itertools import cycle | ||||
| @@ -8,6 +10,7 @@ from itertools import cycle | ||||
| class TestCheckViews(TacticalTestCase): | ||||
|     def setUp(self): | ||||
|         self.authenticate() | ||||
|         self.setup_coresettings() | ||||
|  | ||||
|     def test_get_disk_check(self): | ||||
|         # setup data | ||||
| @@ -180,3 +183,69 @@ class TestCheckViews(TacticalTestCase): | ||||
|         self.assertEqual(resp.status_code, 200) | ||||
|  | ||||
|         self.check_not_authenticated("patch", url_a) | ||||
|  | ||||
|     def test_get_check_history(self): | ||||
|         # setup data | ||||
|         agent = baker.make_recipe("agents.agent") | ||||
|         check = baker.make_recipe("checks.diskspace_check", agent=agent) | ||||
|         baker.make("checks.CheckHistory", check_history=check, _quantity=30) | ||||
|         check_history_data = baker.make( | ||||
|             "checks.CheckHistory", | ||||
|             check_history=check, | ||||
|             _quantity=30, | ||||
|         ) | ||||
|  | ||||
|         # need to manually set the date back 35 days | ||||
|         for check_history in check_history_data: | ||||
|             check_history.x = djangotime.now() - djangotime.timedelta(days=35) | ||||
|             check_history.save() | ||||
|  | ||||
|         # test invalid check pk | ||||
|         resp = self.client.patch("/checks/history/500/", format="json") | ||||
|         self.assertEqual(resp.status_code, 404) | ||||
|  | ||||
|         url = f"/checks/history/{check.id}/" | ||||
|  | ||||
|         # test with timeFilter last 30 days | ||||
|         data = {"timeFilter": 30} | ||||
|         resp = self.client.patch(url, data, format="json") | ||||
|         self.assertEqual(resp.status_code, 200) | ||||
|         self.assertEqual(len(resp.data), 30) | ||||
|  | ||||
|         # test with timeFilter equal to 0 | ||||
|         data = {"timeFilter": 0} | ||||
|         resp = self.client.patch(url, data, format="json") | ||||
|         self.assertEqual(resp.status_code, 200) | ||||
|         self.assertEqual(len(resp.data), 60) | ||||
|  | ||||
|         self.check_not_authenticated("patch", url) | ||||
|  | ||||
|  | ||||
| class TestCheckTasks(TacticalTestCase): | ||||
|     def setUp(self): | ||||
|         self.setup_coresettings() | ||||
|  | ||||
|     def test_prune_check_history(self): | ||||
|         from .tasks import prune_check_history | ||||
|  | ||||
|         # setup data | ||||
|         check = baker.make_recipe("checks.diskspace_check") | ||||
|         baker.make("checks.CheckHistory", check_history=check, _quantity=30) | ||||
|         check_history_data = baker.make( | ||||
|             "checks.CheckHistory", | ||||
|             check_history=check, | ||||
|             _quantity=30, | ||||
|         ) | ||||
|  | ||||
|         # need to manually set the date back 35 days | ||||
|         for check_history in check_history_data: | ||||
|             check_history.x = djangotime.now() - djangotime.timedelta(days=35) | ||||
|             check_history.save() | ||||
|  | ||||
|         # prune data 30 days old | ||||
|         prune_check_history(30) | ||||
|         self.assertEqual(CheckHistory.objects.count(), 30) | ||||
|  | ||||
|         # prune all Check history Data | ||||
|         prune_check_history(0) | ||||
|         self.assertEqual(CheckHistory.objects.count(), 0) | ||||
|   | ||||
| @@ -7,4 +7,5 @@ urlpatterns = [ | ||||
|     path("<pk>/loadchecks/", views.load_checks), | ||||
|     path("getalldisks/", views.get_disks_for_policies), | ||||
|     path("runchecks/<pk>/", views.run_checks), | ||||
|     path("history/<int:checkpk>/", views.CheckHistory.as_view()), | ||||
| ] | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| import asyncio | ||||
|  | ||||
| from django.shortcuts import get_object_or_404 | ||||
| from django.db.models import Q | ||||
| from django.utils import timezone as djangotime | ||||
|  | ||||
| from datetime import datetime as dt | ||||
|  | ||||
| from rest_framework.views import APIView | ||||
| from rest_framework.response import Response | ||||
| @@ -13,7 +17,7 @@ from automation.models import Policy | ||||
| from .models import Check | ||||
| from scripts.models import Script | ||||
|  | ||||
| from .serializers import CheckSerializer | ||||
| from .serializers import CheckSerializer, CheckHistorySerializer | ||||
|  | ||||
|  | ||||
| from automation.tasks import ( | ||||
| @@ -135,6 +139,29 @@ class GetUpdateDeleteCheck(APIView): | ||||
|         return Response(f"{check.readable_desc} was deleted!") | ||||
|  | ||||
|  | ||||
| class CheckHistory(APIView): | ||||
|     def patch(self, request, checkpk): | ||||
|         check = get_object_or_404(Check, pk=checkpk) | ||||
|  | ||||
|         timeFilter = Q() | ||||
|  | ||||
|         if "timeFilter" in request.data: | ||||
|             if request.data["timeFilter"] != 0: | ||||
|                 timeFilter = Q( | ||||
|                     x__lte=djangotime.make_aware(dt.today()), | ||||
|                     x__gt=djangotime.make_aware(dt.today()) | ||||
|                     - djangotime.timedelta(days=request.data["timeFilter"]), | ||||
|                 ) | ||||
|  | ||||
|         check_history = check.check_history.filter(timeFilter).order_by("-x") | ||||
|  | ||||
|         return Response( | ||||
|             CheckHistorySerializer( | ||||
|                 check_history, context={"timezone": check.agent.timezone}, many=True | ||||
|             ).data | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @api_view() | ||||
| def run_checks(request, pk): | ||||
|     agent = get_object_or_404(Agent, pk=pk) | ||||
|   | ||||
| @@ -192,7 +192,7 @@ class GenerateAgent(APIView): | ||||
|         if not os.path.exists(go_bin): | ||||
|             return notify_error("Missing golang") | ||||
|  | ||||
|         api = f"{request.scheme}://{request.get_host()}" | ||||
|         api = f"https://{request.get_host()}" | ||||
|         inno = ( | ||||
|             f"winagent-v{settings.LATEST_AGENT_VER}.exe" | ||||
|             if d.arch == "64" | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| # Generated by Django 3.1.4 on 2021-01-10 18:08 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("core", "0011_auto_20201026_0719"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="coresettings", | ||||
|             name="check_history_prune_days", | ||||
|             field=models.PositiveIntegerField(default=30), | ||||
|         ), | ||||
|     ] | ||||
| @@ -49,6 +49,8 @@ class CoreSettings(BaseAuditModel): | ||||
|     default_time_zone = models.CharField( | ||||
|         max_length=255, choices=TZ_CHOICES, default="America/Los_Angeles" | ||||
|     ) | ||||
|     # removes check history older than days | ||||
|     check_history_prune_days = models.PositiveIntegerField(default=30) | ||||
|     mesh_token = models.CharField(max_length=255, null=True, blank=True, default="") | ||||
|     mesh_username = models.CharField(max_length=255, null=True, blank=True, default="") | ||||
|     mesh_site = models.CharField(max_length=255, null=True, blank=True, default="") | ||||
|   | ||||
| @@ -4,8 +4,10 @@ from loguru import logger | ||||
| from django.conf import settings | ||||
| from django.utils import timezone as djangotime | ||||
| from tacticalrmm.celery import app | ||||
| from core.models import CoreSettings | ||||
| from autotasks.models import AutomatedTask | ||||
| from autotasks.tasks import delete_win_task_schedule | ||||
| from checks.tasks import prune_check_history | ||||
|  | ||||
| logger.configure(**settings.LOG_CONFIG) | ||||
|  | ||||
| @@ -25,3 +27,7 @@ def core_maintenance_tasks(): | ||||
|  | ||||
|         if now > task_time_utc: | ||||
|             delete_win_task_schedule.delay(task.pk) | ||||
|  | ||||
|     # remove old CheckHistory data | ||||
|     older_than = CoreSettings.objects.first().check_history_prune_days | ||||
|     prune_check_history.delay(older_than) | ||||
|   | ||||
| @@ -75,6 +75,7 @@ def dashboard_info(request): | ||||
|             "trmm_version": settings.TRMM_VERSION, | ||||
|             "dark_mode": request.user.dark_mode, | ||||
|             "show_community_scripts": request.user.show_community_scripts, | ||||
|             "dbl_click_action": request.user.agent_dblclick_action, | ||||
|         } | ||||
|     ) | ||||
|  | ||||
|   | ||||
							
								
								
									
										0
									
								
								api/tacticalrmm/natsapi/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								api/tacticalrmm/natsapi/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										5
									
								
								api/tacticalrmm/natsapi/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								api/tacticalrmm/natsapi/apps.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| from django.apps import AppConfig | ||||
|  | ||||
|  | ||||
| class NatsapiConfig(AppConfig): | ||||
|     name = "natsapi" | ||||
							
								
								
									
										0
									
								
								api/tacticalrmm/natsapi/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								api/tacticalrmm/natsapi/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										8
									
								
								api/tacticalrmm/natsapi/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								api/tacticalrmm/natsapi/urls.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| from django.urls import path | ||||
| from . import views | ||||
|  | ||||
| urlpatterns = [ | ||||
|     path("natsinfo/", views.nats_info), | ||||
|     path("checkin/", views.NatsCheckIn.as_view()), | ||||
|     path("syncmesh/", views.SyncMeshNodeID.as_view()), | ||||
| ] | ||||
							
								
								
									
										99
									
								
								api/tacticalrmm/natsapi/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								api/tacticalrmm/natsapi/views.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| from django.utils import timezone as djangotime | ||||
|  | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.views import APIView | ||||
| from rest_framework.decorators import ( | ||||
|     api_view, | ||||
|     permission_classes, | ||||
|     authentication_classes, | ||||
| ) | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.shortcuts import get_object_or_404 | ||||
|  | ||||
| from agents.models import Agent | ||||
| from software.models import InstalledSoftware | ||||
| from checks.utils import bytes2human | ||||
| from agents.serializers import WinAgentSerializer | ||||
|  | ||||
| from tacticalrmm.utils import notify_error, filter_software, SoftwareList | ||||
|  | ||||
|  | ||||
| @api_view() | ||||
| @permission_classes([]) | ||||
| @authentication_classes([]) | ||||
| def nats_info(request): | ||||
|     return Response({"user": "tacticalrmm", "password": settings.SECRET_KEY}) | ||||
|  | ||||
|  | ||||
| class NatsCheckIn(APIView): | ||||
|  | ||||
|     authentication_classes = [] | ||||
|     permission_classes = [] | ||||
|  | ||||
|     def patch(self, request): | ||||
|         agent = get_object_or_404(Agent, agent_id=request.data["agent_id"]) | ||||
|         agent.version = request.data["version"] | ||||
|         agent.last_seen = djangotime.now() | ||||
|         agent.save(update_fields=["version", "last_seen"]) | ||||
|         return Response("ok") | ||||
|  | ||||
|     def put(self, request): | ||||
|         agent = get_object_or_404(Agent, agent_id=request.data["agent_id"]) | ||||
|         serializer = WinAgentSerializer(instance=agent, data=request.data, partial=True) | ||||
|  | ||||
|         if request.data["func"] == "disks": | ||||
|             disks = request.data["disks"] | ||||
|             new = [] | ||||
|             for disk in disks: | ||||
|                 tmp = {} | ||||
|                 for _, _ in disk.items(): | ||||
|                     tmp["device"] = disk["device"] | ||||
|                     tmp["fstype"] = disk["fstype"] | ||||
|                     tmp["total"] = bytes2human(disk["total"]) | ||||
|                     tmp["used"] = bytes2human(disk["used"]) | ||||
|                     tmp["free"] = bytes2human(disk["free"]) | ||||
|                     tmp["percent"] = int(disk["percent"]) | ||||
|                 new.append(tmp) | ||||
|  | ||||
|             serializer.is_valid(raise_exception=True) | ||||
|             serializer.save(disks=new) | ||||
|             return Response("ok") | ||||
|  | ||||
|         if request.data["func"] == "loggedonuser": | ||||
|             if request.data["logged_in_username"] != "None": | ||||
|                 serializer.is_valid(raise_exception=True) | ||||
|                 serializer.save(last_logged_in_user=request.data["logged_in_username"]) | ||||
|                 return Response("ok") | ||||
|  | ||||
|         if request.data["func"] == "software": | ||||
|             raw: SoftwareList = request.data["software"] | ||||
|             if not isinstance(raw, list): | ||||
|                 return notify_error("err") | ||||
|  | ||||
|             sw = filter_software(raw) | ||||
|             if not InstalledSoftware.objects.filter(agent=agent).exists(): | ||||
|                 InstalledSoftware(agent=agent, software=sw).save() | ||||
|             else: | ||||
|                 s = agent.installedsoftware_set.first() | ||||
|                 s.software = sw | ||||
|                 s.save(update_fields=["software"]) | ||||
|  | ||||
|             return Response("ok") | ||||
|  | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|         serializer.save() | ||||
|         return Response("ok") | ||||
|  | ||||
|  | ||||
| class SyncMeshNodeID(APIView): | ||||
|     authentication_classes = [] | ||||
|     permission_classes = [] | ||||
|  | ||||
|     def post(self, request): | ||||
|         agent = get_object_or_404(Agent, agent_id=request.data["agent_id"]) | ||||
|         if agent.mesh_node_id != request.data["nodeid"]: | ||||
|             agent.mesh_node_id = request.data["nodeid"] | ||||
|             agent.save(update_fields=["mesh_node_id"]) | ||||
|  | ||||
|         return Response("ok") | ||||
| @@ -8,7 +8,7 @@ cffi==1.14.4 | ||||
| chardet==4.0.0 | ||||
| cryptography==3.3.1 | ||||
| decorator==4.4.2 | ||||
| Django==3.1.4 | ||||
| Django==3.1.5 | ||||
| django-cors-headers==3.6.0 | ||||
| django-rest-knox==4.1.0 | ||||
| djangorestframework==3.12.2 | ||||
| @@ -23,13 +23,13 @@ pycparser==2.20 | ||||
| pycryptodome==3.9.9 | ||||
| pyotp==2.4.1 | ||||
| pyparsing==2.4.7 | ||||
| pytz==2020.4 | ||||
| pytz==2020.5 | ||||
| qrcode==6.1 | ||||
| redis==3.5.3 | ||||
| requests==2.25.1 | ||||
| six==1.15.0 | ||||
| sqlparse==0.4.1 | ||||
| twilio==6.50.1 | ||||
| twilio==6.51.0 | ||||
| urllib3==1.26.2 | ||||
| uWSGI==2.0.19.1 | ||||
| validators==0.18.2 | ||||
|   | ||||
| @@ -96,5 +96,103 @@ | ||||
|         "name": "Check BIOS Information", | ||||
|         "description": "Retreives and reports on BIOS make, version, and date   .", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "ResetHighPerformancePowerProfiletoDefaults.ps1", | ||||
|         "submittedBy": "https://github.com/azulskyknight", | ||||
|         "name": "Reset High Perf Power Profile", | ||||
|         "description": "Resets monitor, disk, standby, and hibernate timers in the default High Performance power profile to their default values. It also re-indexes the AC and DC power profiles into their default order.", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "SetHighPerformancePowerProfile.ps1", | ||||
|         "submittedBy": "https://github.com/azulskyknight", | ||||
|         "name": "Set High Perf Power Profile", | ||||
|         "description": "Sets the High Performance Power profile to the active power profile. Use this to keep machines from falling asleep.", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "Windows10Upgrade.ps1", | ||||
|         "submittedBy": "https://github.com/RVL-Solutions and https://github.com/darimm", | ||||
|         "name": "Windows 10 Upgrade", | ||||
|         "description": "Forces an upgrade to the latest release of Windows 10.", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "DiskStatus.ps1", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "Check Disks", | ||||
|         "description": "Checks local disks for errors reported in event viewer within the last 24 hours", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "DuplicatiStatus.ps1", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "Check Duplicati", | ||||
|         "description": "Checks Duplicati Backup is running properly over the last 24 hours", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "EnableDefender.ps1", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "Enable Windows Defender", | ||||
|         "description": "Enables Windows Defender and sets preferences", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "OpenSSHServerInstall.ps1", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "Install SSH", | ||||
|         "description": "Installs and enabled OpenSSH Server", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "RDP_enable.bat", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "Enable RDP", | ||||
|         "description": "Enables RDP", | ||||
|         "shell": "cmd" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "Speedtest.ps1", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "PS Speed Test", | ||||
|         "description": "Powershell speed test (win 10 or server2016+)", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "SyncTime.bat", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "Sync DC Time", | ||||
|         "description": "Syncs time with domain controller", | ||||
|         "shell": "cmd" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "WinDefenderClearLogs.ps1", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "Clear Defender Logs", | ||||
|         "description": "Clears Windows Defender Logs", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "WinDefenderStatus.ps1", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "Defender Status", | ||||
|         "description": "This will check for Malware, Antispyware, that Windows Defender is Healthy, last scan etc within the last 24 hours", | ||||
|         "shell": "powershell" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "disable_FastStartup.bat", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "Disable Fast Startup", | ||||
|         "description": "Disables Faststartup on Windows 10", | ||||
|         "shell": "cmd" | ||||
|     }, | ||||
|     { | ||||
|         "filename": "updatetacticalexclusion.ps1", | ||||
|         "submittedBy": "https://github.com/dinger1986", | ||||
|         "name": "TRMM Defender Exclusions", | ||||
|         "description": "Windows Defender Exclusions for Tactical RMM", | ||||
|         "shell": "cmd" | ||||
|     } | ||||
| ] | ||||
| @@ -14,6 +14,7 @@ def get_debug_info(): | ||||
|  | ||||
|  | ||||
| EXCLUDE_PATHS = ( | ||||
|     "/natsapi", | ||||
|     "/api/v3", | ||||
|     "/api/v2", | ||||
|     "/logs/auditlogs", | ||||
|   | ||||
| @@ -15,11 +15,11 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe") | ||||
| AUTH_USER_MODEL = "accounts.User" | ||||
|  | ||||
| # latest release | ||||
| TRMM_VERSION = "0.2.19" | ||||
| TRMM_VERSION = "0.2.23" | ||||
|  | ||||
| # bump this version everytime vue code is changed | ||||
| # to alert user they need to manually refresh their browser | ||||
| APP_VER = "0.0.101" | ||||
| APP_VER = "0.0.103" | ||||
|  | ||||
| # https://github.com/wh1te909/salt | ||||
| LATEST_SALT_VER = "1.1.0" | ||||
| @@ -27,13 +27,13 @@ LATEST_SALT_VER = "1.1.0" | ||||
| # https://github.com/wh1te909/rmmagent | ||||
| LATEST_AGENT_VER = "1.1.12" | ||||
|  | ||||
| MESH_VER = "0.7.37" | ||||
| MESH_VER = "0.7.45" | ||||
|  | ||||
| SALT_MASTER_VER = "3002.2" | ||||
|  | ||||
| # for the update script, bump when need to recreate venv or npm install | ||||
| PIP_VER = "5" | ||||
| NPM_VER = "5" | ||||
| PIP_VER = "6" | ||||
| NPM_VER = "6" | ||||
|  | ||||
| DL_64 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}.exe" | ||||
| DL_32 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}-x86.exe" | ||||
| @@ -72,6 +72,7 @@ INSTALLED_APPS = [ | ||||
|     "logs", | ||||
|     "scripts", | ||||
|     "alerts", | ||||
|     "natsapi", | ||||
| ] | ||||
|  | ||||
| if not "TRAVIS" in os.environ and not "AZPIPELINE" in os.environ: | ||||
|   | ||||
| @@ -25,4 +25,5 @@ urlpatterns = [ | ||||
|     path("scripts/", include("scripts.urls")), | ||||
|     path("alerts/", include("alerts.urls")), | ||||
|     path("accounts/", include("accounts.urls")), | ||||
|     path("natsapi/", include("natsapi.urls")), | ||||
| ] | ||||
|   | ||||
| @@ -41,12 +41,7 @@ mesh_config="$(cat << EOF | ||||
|       "NewAccounts": false, | ||||
|       "mstsc": true, | ||||
|       "GeoLocation": true, | ||||
|       "CertUrl": "https://${NGINX_HOST_IP}:443", | ||||
|       "httpheaders": { | ||||
|         "Strict-Transport-Security": "max-age=360000", | ||||
|         "_x-frame-options": "sameorigin", | ||||
|         "Content-Security-Policy": "default-src 'none'; script-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-src 'self'; media-src 'self'" | ||||
|       } | ||||
|       "CertUrl": "https://${NGINX_HOST_IP}:443" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										17
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| module github.com/wh1te909/tacticalrmm | ||||
|  | ||||
| go 1.15 | ||||
|  | ||||
| require ( | ||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||
| 	github.com/go-resty/resty/v2 v2.3.0 | ||||
| 	github.com/josephspurrier/goversioninfo v1.2.0 | ||||
| 	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.1.13-0.20210112033642-9b310c2c7f53 | ||||
| 	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 | ||||
| 	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect | ||||
| ) | ||||
							
								
								
									
										155
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | ||||
| github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= | ||||
| github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= | ||||
| github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= | ||||
| github.com/capnspacehook/taskmaster v0.0.0-20201022195506-c2d8b114cec0/go.mod h1:257CYs3Wd/CTlLQ3c72jKv+fFE2MV3WPNnV5jiroYUU= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/elastic/go-sysinfo v1.4.0/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= | ||||
| github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= | ||||
| github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||||
| github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= | ||||
| github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So= | ||||
| github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= | ||||
| github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= | ||||
| github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= | ||||
| github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= | ||||
| github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= | ||||
| github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= | ||||
| github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= | ||||
| github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||||
| github.com/gonutz/w32 v1.0.1-0.20201105145118-e88c649a9470/go.mod h1:Rc/YP5K9gv0FW4p6X9qL3E7Y56lfMflEol1fLElfMW4= | ||||
| github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||
| github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||
| github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= | ||||
| github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | ||||
| github.com/iamacarpet/go-win64api v0.0.0-20200715182619-8cbc936e1a5a/go.mod h1:oGJx9dz0Ny7HC7U55RZ0Smd6N9p3hXP/+hOFtuYrAxM= | ||||
| github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= | ||||
| github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= | ||||
| github.com/josephspurrier/goversioninfo v1.2.0 h1:tpLHXAxLHKHg/dCU2AAYx08A4m+v9/CWg6+WUvTF4uQ= | ||||
| github.com/josephspurrier/goversioninfo v1.2.0/go.mod h1:AGP2a+Y/OVJZ+s6XM4IwFUpkETwvn0orYurY8qpw1+0= | ||||
| github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | ||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= | ||||
| github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2BA= | ||||
| github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc= | ||||
| github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= | ||||
| github.com/nats-io/jwt v0.3.3-0.20200519195258-f2bf5ce574c7 h1:RnGotxlghqR5D2KDAu4TyuLqyjuylOsJiAFhXvMvQIc= | ||||
| github.com/nats-io/jwt v0.3.3-0.20200519195258-f2bf5ce574c7/go.mod h1:n3cvmLfBfnpV4JJRN7lRYCyZnw48ksGsbThGXEk4w9M= | ||||
| github.com/nats-io/jwt/v2 v2.0.0-20200916203241-1f8ce17dff02/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ= | ||||
| github.com/nats-io/jwt/v2 v2.0.0-20201015190852-e11ce317263c h1:Hc1D9ChlsCMVwCxJ6QT5xqfk2zJ4XNea+LtdfaYhd20= | ||||
| github.com/nats-io/jwt/v2 v2.0.0-20201015190852-e11ce317263c/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ= | ||||
| github.com/nats-io/nats-server/v2 v2.1.8-0.20200524125952-51ebd92a9093/go.mod h1:rQnBf2Rv4P9adtAs/Ti6LfFmVtFG6HLhl/H7cVshcJU= | ||||
| github.com/nats-io/nats-server/v2 v2.1.8-0.20200601203034-f8d6dd992b71/go.mod h1:Nan/1L5Sa1JRW+Thm4HNYcIDcVRFc5zK9OpSZeI2kk4= | ||||
| github.com/nats-io/nats-server/v2 v2.1.8-0.20200929001935-7f44d075f7ad/go.mod h1:TkHpUIDETmTI7mrHN40D1pzxfzHZuGmtMbtb83TGVQw= | ||||
| github.com/nats-io/nats-server/v2 v2.1.8-0.20201129161730-ebe63db3e3ed h1:/FdiqqED2Wy6pyVh7K61gN5G0WfbvFVQzGgpHTcAlHA= | ||||
| github.com/nats-io/nats-server/v2 v2.1.8-0.20201129161730-ebe63db3e3ed/go.mod h1:XD0zHR/jTXdZvWaQfS5mQgsXj6x12kMjKLyAk/cOGgY= | ||||
| github.com/nats-io/nats.go v1.10.0/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE= | ||||
| github.com/nats-io/nats.go v1.10.1-0.20200531124210-96f2130e4d55/go.mod h1:ARiFsjW9DVxk48WJbO3OSZ2DG8fjkMi7ecLmXoY/n9I= | ||||
| github.com/nats-io/nats.go v1.10.1-0.20200606002146-fc6fed82929a/go.mod h1:8eAIv96Mo9QW6Or40jUHejS7e4VwZ3VRYD6Sf0BTDp4= | ||||
| github.com/nats-io/nats.go v1.10.1-0.20201021145452-94be476ad6e0/go.mod h1:VU2zERjp8xmF+Lw2NH4u2t5qWZxwc7jB3+7HVMWQXPI= | ||||
| github.com/nats-io/nats.go v1.10.1-0.20210107160453-a133396829fc h1:bjYoZsMFpySzGUZCFrPk9+ncZ47kPCVHPoQS/QTXYVQ= | ||||
| github.com/nats-io/nats.go v1.10.1-0.20210107160453-a133396829fc/go.mod h1:Sa3kLIonafChP5IF0b55i9uvGR10I3hPETFbi4+9kOI= | ||||
| github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= | ||||
| github.com/nats-io/nkeys v0.1.4/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= | ||||
| github.com/nats-io/nkeys v0.2.0 h1:WXKF7diOaPU9cJdLD7nuzwasQy9vT1tBqzXZZf3AMJM= | ||||
| github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= | ||||
| github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= | ||||
| github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= | ||||
| github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= | ||||
| github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||
| github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= | ||||
| github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= | ||||
| github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= | ||||
| github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | ||||
| github.com/rickb777/date v1.14.2/go.mod h1:swmf05C+hN+m8/Xh7gEq3uB6QJDNc5pQBWojKdHetOs= | ||||
| github.com/rickb777/date v1.14.3/go.mod h1:mes+vf4wqTD6l4zgZh4Z5TQkrLA57dpuzEGVeTk/XSc= | ||||
| github.com/rickb777/plural v1.2.2/go.mod h1:xyHbelv4YvJE51gjMnHvk+U2e9zIysg6lTnSQK8XUYA= | ||||
| github.com/shirou/gopsutil/v3 v3.20.12/go.mod h1:igHnfak0qnw1biGeI2qKQvu0ZkwvEkUcCLlYhZzdr/4= | ||||
| github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||
| github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= | ||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/tc-hib/goversioninfo v0.0.0-20200813185747-90ffbaa484a7/go.mod h1:NaPIGx19A2KXQEoek0x88NbM0lNgRooZS0xmrETzcjI= | ||||
| github.com/tc-hib/rsrc v0.9.1/go.mod h1:JGDB/TLOdMTvEEvjv3yetUTFnjXWYLbZDDeH4BTXG/8= | ||||
| github.com/ugorji/go v1.2.0/go.mod h1:1ny++pKMXhLWrwWV5Nf+CbOuZJhMoaFD+0GMFfd8fEc= | ||||
| github.com/ugorji/go v1.2.2 h1:60ZHIOcsJlo3bJm9CbTVu7OSqT2mxaEmyQbK2NwCkn0= | ||||
| github.com/ugorji/go v1.2.2/go.mod h1:bitgyERdV7L7Db/Z5gfd5v2NQMNhhiFiZwpgMw2SP7k= | ||||
| github.com/ugorji/go/codec v1.2.0/go.mod h1:dXvG35r7zTX6QImXOSFhGMmKtX+wJ7VTWzGvYQGIjBs= | ||||
| github.com/ugorji/go/codec v1.2.2 h1:08Gah8d+dXj4cZNUHhtuD/S4PXD5WpVbj5B8/ClELAQ= | ||||
| github.com/ugorji/go/codec v1.2.2/go.mod h1:OM8g7OAy52uYl3Yk+RE/3AS1nXFn1Wh4PPLtupCxbuU= | ||||
| github.com/wh1te909/go-win64api v0.0.0-20201021040544-8fba2a0fc3d0/go.mod h1:cfD5/vNQFm5PD5Q32YYYBJ6VIs9etzp8CJ9dinUcpUA= | ||||
| github.com/wh1te909/rmmagent v1.1.13-0.20210111092134-7c83da579caa h1:ZV7qIUJ5M3HDFLi3bun6a2A5+g9DoThbLWI7egBYYkQ= | ||||
| github.com/wh1te909/rmmagent v1.1.13-0.20210111092134-7c83da579caa/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0= | ||||
| github.com/wh1te909/rmmagent v1.1.13-0.20210111205455-e6620a17aebe h1:xsutMbsAJL2xTvE119BVyK4RdBWx1IBvC7azoEpioEE= | ||||
| github.com/wh1te909/rmmagent v1.1.13-0.20210111205455-e6620a17aebe/go.mod h1:05MQOAiC/kGvJjDlCOjaTsMNpf6wZFqOTkHqK0ATfW0= | ||||
| github.com/wh1te909/rmmagent v1.1.13-0.20210112033642-9b310c2c7f53 h1:Q47sibbW09BWaQoPZQTzblGd+rnNIc3W8W/jOYbMe10= | ||||
| github.com/wh1te909/rmmagent v1.1.13-0.20210112033642-9b310c2c7f53/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= | ||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= | ||||
| golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= | ||||
| golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200622182413-4b0db7f3f76b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba h1:xmhUJGQGbxlod18iJGqVEp9cHIPLl7QiX2aA3to708s= | ||||
| golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= | ||||
| golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= | ||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | ||||
| google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | ||||
| google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | ||||
| google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= | ||||
| google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= | ||||
| google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= | ||||
| google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= | ||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | ||||
| gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= | ||||
| gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= | ||||
							
								
								
									
										17
									
								
								install.sh
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								install.sh
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| SCRIPT_VERSION="30" | ||||
| SCRIPT_VERSION="32" | ||||
| SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/install.sh' | ||||
|  | ||||
| sudo apt install -y curl wget | ||||
| @@ -304,12 +304,7 @@ meshcfg="$(cat << EOF | ||||
|       "CertUrl": "https://${meshdomain}:443/", | ||||
|       "GeoLocation": true, | ||||
|       "CookieIpCheck": false, | ||||
|       "mstsc": true, | ||||
|       "httpheaders": { | ||||
|         "Strict-Transport-Security": "max-age=360000", | ||||
|         "_x-frame-options": "sameorigin", | ||||
|         "Content-Security-Policy": "default-src 'none'; script-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-src 'self'; media-src 'self'" | ||||
|       } | ||||
|       "mstsc": true | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -520,6 +515,14 @@ server { | ||||
|         alias /srv/salt/scripts/; | ||||
|     } | ||||
|  | ||||
|     location ~ ^/(natsapi) { | ||||
|         allow 127.0.0.1; | ||||
|         deny all; | ||||
|         uwsgi_pass tacticalrmm; | ||||
|         include     /etc/nginx/uwsgi_params; | ||||
|         uwsgi_read_timeout 9999s; | ||||
|         uwsgi_ignore_client_abort on; | ||||
|     } | ||||
|  | ||||
|     location / { | ||||
|         uwsgi_pass  tacticalrmm; | ||||
|   | ||||
							
								
								
									
										15
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
|  | ||||
| 	"github.com/wh1te909/tacticalrmm/natsapi" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	apiHost := flag.String("api-host", "", "django api base url") | ||||
| 	debug := flag.Bool("debug", false, "Debug") | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	api.Listen(*apiHost, *debug) | ||||
| } | ||||
							
								
								
									
										151
									
								
								natsapi/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								natsapi/api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/go-resty/resty/v2" | ||||
| 	nats "github.com/nats-io/nats.go" | ||||
| 	"github.com/ugorji/go/codec" | ||||
| 	rmm "github.com/wh1te909/rmmagent/shared" | ||||
| ) | ||||
|  | ||||
| var rClient = resty.New() | ||||
|  | ||||
| func getAPI(apihost string) (string, error) { | ||||
| 	if apihost != "" { | ||||
| 		return apihost, nil | ||||
| 	} | ||||
|  | ||||
| 	f, err := os.Open(`/etc/nginx/sites-available/rmm.conf`) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
|  | ||||
| 	scanner := bufio.NewScanner(f) | ||||
| 	for scanner.Scan() { | ||||
| 		if strings.Contains(scanner.Text(), "server_name") && !strings.Contains(scanner.Text(), "301") { | ||||
| 			r := strings.NewReplacer("server_name", "", ";", "") | ||||
| 			return strings.ReplaceAll(r.Replace(scanner.Text()), " ", ""), nil | ||||
| 		} | ||||
| 	} | ||||
| 	return "", errors.New("unable to parse api from nginx conf") | ||||
| } | ||||
|  | ||||
| func Listen(apihost string, debug bool) { | ||||
| 	var baseURL string | ||||
| 	api, err := getAPI(apihost) | ||||
| 	if err != nil { | ||||
| 		log.Fatalln(err) | ||||
| 	} | ||||
|  | ||||
| 	if debug { | ||||
| 		baseURL = fmt.Sprintf("http://%s:8000/natsapi", api) | ||||
| 	} else { | ||||
| 		baseURL = fmt.Sprintf("https://%s/natsapi", api) | ||||
| 	} | ||||
|  | ||||
| 	rClient.SetHostURL(baseURL) | ||||
| 	rClient.SetTimeout(30 * time.Second) | ||||
| 	natsinfo, err := rClient.R().SetResult(&NatsInfo{}).Get("/natsinfo/") | ||||
| 	if err != nil { | ||||
| 		log.Fatalln(err) | ||||
| 	} | ||||
|  | ||||
| 	opts := []nats.Option{ | ||||
| 		nats.Name("TacticalRMM"), | ||||
| 		nats.UserInfo(natsinfo.Result().(*NatsInfo).User, | ||||
| 			natsinfo.Result().(*NatsInfo).Password), | ||||
| 		nats.ReconnectWait(time.Second * 5), | ||||
| 		nats.RetryOnFailedConnect(true), | ||||
| 		nats.MaxReconnects(-1), | ||||
| 		nats.ReconnectBufSize(-1), | ||||
| 	} | ||||
|  | ||||
| 	server := fmt.Sprintf("tls://%s:4222", api) | ||||
| 	nc, err := nats.Connect(server, opts...) | ||||
| 	if err != nil { | ||||
| 		log.Fatalln(err) | ||||
| 	} | ||||
|  | ||||
| 	nc.Subscribe("*", func(msg *nats.Msg) { | ||||
| 		var mh codec.MsgpackHandle | ||||
| 		mh.RawToString = true | ||||
| 		dec := codec.NewDecoderBytes(msg.Data, &mh) | ||||
|  | ||||
| 		switch msg.Reply { | ||||
| 		case "hello": | ||||
| 			go func() { | ||||
| 				var p *rmm.CheckIn | ||||
| 				if err := dec.Decode(&p); err == nil { | ||||
| 					rClient.R().SetBody(p).Patch("/checkin/") | ||||
| 				} | ||||
| 			}() | ||||
| 		case "osinfo": | ||||
| 			go func() { | ||||
| 				var p *rmm.CheckInOS | ||||
| 				if err := dec.Decode(&p); err == nil { | ||||
| 					rClient.R().SetBody(p).Put("/checkin/") | ||||
| 				} | ||||
| 			}() | ||||
| 		case "winservices": | ||||
| 			go func() { | ||||
| 				var p *rmm.CheckInWinServices | ||||
| 				if err := dec.Decode(&p); err == nil { | ||||
| 					rClient.R().SetBody(p).Put("/checkin/") | ||||
| 				} | ||||
| 			}() | ||||
| 		case "publicip": | ||||
| 			go func() { | ||||
| 				var p *rmm.CheckInPublicIP | ||||
| 				if err := dec.Decode(&p); err == nil { | ||||
| 					rClient.R().SetBody(p).Put("/checkin/") | ||||
| 				} | ||||
| 			}() | ||||
| 		case "disks": | ||||
| 			go func() { | ||||
| 				var p *rmm.CheckInDisk | ||||
| 				if err := dec.Decode(&p); err == nil { | ||||
| 					rClient.R().SetBody(p).Put("/checkin/") | ||||
| 				} | ||||
| 			}() | ||||
| 		case "loggedonuser": | ||||
| 			go func() { | ||||
| 				var p *rmm.CheckInLoggedUser | ||||
| 				if err := dec.Decode(&p); err == nil { | ||||
| 					rClient.R().SetBody(p).Put("/checkin/") | ||||
| 				} | ||||
| 			}() | ||||
| 		case "software": | ||||
| 			go func() { | ||||
| 				var p *rmm.CheckInSW | ||||
| 				if err := dec.Decode(&p); err == nil { | ||||
| 					rClient.R().SetBody(p).Put("/checkin/") | ||||
| 				} | ||||
| 			}() | ||||
| 		case "syncmesh": | ||||
| 			go func() { | ||||
| 				var p *rmm.MeshNodeID | ||||
| 				if err := dec.Decode(&p); err == nil { | ||||
| 					rClient.R().SetBody(p).Post("/syncmesh/") | ||||
| 				} | ||||
| 			}() | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	nc.Flush() | ||||
|  | ||||
| 	if err := nc.LastError(); err != nil { | ||||
| 		fmt.Println(err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	runtime.Goexit() | ||||
| } | ||||
							
								
								
									
										6
									
								
								natsapi/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								natsapi/types.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| package api | ||||
|  | ||||
| type NatsInfo struct { | ||||
| 	User     string `json:"user"` | ||||
| 	Password string `json:"password"` | ||||
| } | ||||
							
								
								
									
										21
									
								
								scripts/DiskStatus.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								scripts/DiskStatus.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| # Checks local disks for errors reported in event viewer within the last 24 hours | ||||
|  | ||||
| $ErrorActionPreference= 'silentlycontinue' | ||||
| $TimeSpan = (Get-Date) - (New-TimeSpan -Day 1) | ||||
| if (Get-WinEvent -FilterHashtable @{LogName='system';ID='11','9','15','52','129','7','98';Level=2,3;ProviderName='*disk*','*storsvc*','*ntfs*';StartTime=$TimeSpan}) | ||||
|  | ||||
| { | ||||
| Write-Output "Disk errors detected please investigate" | ||||
| Get-WinEvent -FilterHashtable @{LogName='system';ID='11','9','15','52','129','7','98';Level=2,3;ProviderName='*disk*','*storsvc*','*ntfs*';StartTime=$TimeSpan} | ||||
| exit 1 | ||||
| } | ||||
|  | ||||
|  | ||||
| else | ||||
| { | ||||
| Write-Output "Disks are Healthy" | ||||
| exit 0 | ||||
| } | ||||
|  | ||||
|  | ||||
| Exit $LASTEXITCODE | ||||
							
								
								
									
										55
									
								
								scripts/DuplicatiStatus.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								scripts/DuplicatiStatus.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| # This will check Duplicati Backup is running properly over the last 24 hours | ||||
| ################ | ||||
| # Please make sure you have created the 2 files Duplicati_Before.bat and Duplicati_After.bat and saved them in a folder | ||||
| ################  | ||||
| # Change the Duplicati backup advanced settings to run the before script and after script you will need their full path | ||||
| ################ | ||||
| # Duplicati_Before.bat should contain the below without the proceeding #: | ||||
| # | ||||
| # REM Create Running Status | ||||
| # EVENTCREATE /T INFORMATION /L APPLICATION /SO Duplicati2 /ID 205 /D "%DUPLICATI__BACKUP_NAME% - Starting Duplicati Backup Job" | ||||
| ################ | ||||
| # Duplicati_After.bat should contain the below without the proceeding #: | ||||
| # | ||||
| # REM Create Result Status from Parsed Results | ||||
| # SET DSTATUS=%DUPLICATI__PARSED_RESULT% | ||||
| # If %DSTATUS%==Fatal GOTO DSError | ||||
| # If %DSTATUS%==Error GOTO DSError | ||||
| # If %DSTATUS%==Unknown GOTO DSWarning | ||||
| # If %DSTATUS%==Warning GOTO DSWarning | ||||
| # If %DSTATUS%==Success GOTO DSSuccess | ||||
| # GOTO END | ||||
| # :DSError | ||||
| # EVENTCREATE /T ERROR /L APPLICATION /SO Duplicati2 /ID 202 /D "%DUPLICATI__BACKUP_NAME% - Error running Duplicati Backup Job" | ||||
| # GOTO END | ||||
| # :DSWarning | ||||
| # EVENTCREATE /T WARNING /L APPLICATION /SO Duplicati2 /ID 201 /D "%DUPLICATI__BACKUP_NAME% - Warning running Duplicati Backup Job" | ||||
| # GOTO END | ||||
| # :DSSuccess | ||||
| # EVENTCREATE /T SUCCESS /L APPLICATION /SO Duplicati2 /ID 200 /D "%DUPLICATI__BACKUP_NAME% - Success in running Duplicati Backup Job" | ||||
| # GOTO END | ||||
| # :END | ||||
| # SET DSTATUS= | ||||
|  | ||||
| $ErrorActionPreference= 'silentlycontinue' | ||||
| $TimeSpan = (Get-Date) - (New-TimeSpan -Day 1) | ||||
|  | ||||
| if (Get-WinEvent -FilterHashtable @{LogName='Application';ID='202';StartTime=$TimeSpan})  | ||||
|  | ||||
| { | ||||
| Write-Output "Duplicati Backup Ended with Errors" | ||||
| Get-WinEvent -FilterHashtable @{LogName='Application';ID='205','201','202';StartTime=$TimeSpan} | ||||
| exit 1 | ||||
| } | ||||
|  | ||||
|  | ||||
| else  | ||||
|  | ||||
| { | ||||
| Write-Output "Duplicati Backup Is Working Correctly" | ||||
| Get-WinEvent -FilterHashtable @{LogName='Application';ID='205','200','201'} | ||||
| exit 0 | ||||
| } | ||||
|  | ||||
|  | ||||
| Exit $LASTEXITCODE | ||||
							
								
								
									
										135
									
								
								scripts/EnableDefender.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								scripts/EnableDefender.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| # Verifies that script is running on Windows 10 or greater | ||||
| function Check-IsWindows10 | ||||
| { | ||||
|     if ([System.Environment]::OSVersion.Version.Major -ge "10")  | ||||
|     { | ||||
|         Write-Output $true | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         Write-Output $false | ||||
|     } | ||||
| } | ||||
|  | ||||
| # Verifies that script is running on Windows 10 1709 or greater | ||||
| function Check-IsWindows10-1709 | ||||
| { | ||||
|     if ([System.Environment]::OSVersion.Version.Minor -ge "16299")  | ||||
|     { | ||||
|         Write-Output $true | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         Write-Output $false | ||||
|     } | ||||
| } | ||||
|  | ||||
| function SetRegistryKey([string]$key, [int]$value) | ||||
| { | ||||
|     #Editing Windows Defender settings AV via registry is NOT supported. This is a scripting workaround instead of using Group Policy or SCCM for Windows 10 version 1703 | ||||
|     $amRegistryPath = "HKLM:\Software\Policies\Microsoft\Microsoft Antimalware\MpEngine" | ||||
|     $wdRegistryPath = "HKLM:\Software\Policies\Microsoft\Windows Defender\MpEngine" | ||||
|     $regPathToUse = $wdRegistryPath #Default to WD path | ||||
|     if (Test-Path $amRegistryPath) | ||||
|     { | ||||
|         $regPathToUse = $amRegistryPath | ||||
|     } | ||||
|     New-ItemProperty -Path $regPathToUse -Name $key -Value $value -PropertyType DWORD -Force | Out-Null | ||||
| }  | ||||
|  | ||||
| #### Setup Windows Defender Secure Settings | ||||
|  | ||||
| # Start Windows Defender Service | ||||
| Set-Service -Name "WinDefend" -Status running -StartupType automatic | ||||
| Set-Service -Name "WdNisSvc" -Status running -StartupType automatic | ||||
|  | ||||
| #  Enable real-time monitoring | ||||
| Set-MpPreference -DisableRealtimeMonitoring 0 | ||||
|  | ||||
| # Enable cloud-deliveredprotection#  | ||||
| Set-MpPreference -MAPSReporting Advanced | ||||
|  | ||||
| # Enable sample submission#  | ||||
| Set-MpPreference -SubmitSamplesConsent 1 | ||||
|  | ||||
| # Enable checking signatures before scanning#  | ||||
| Set-MpPreference -CheckForSignaturesBeforeRunningScan 1 | ||||
|  | ||||
| # Enable behavior monitoring#  | ||||
| Set-MpPreference -DisableBehaviorMonitoring 0 | ||||
|  | ||||
| # Enable IOAV protection#  | ||||
| Set-MpPreference -DisableIOAVProtection 0 | ||||
|  | ||||
| # Enable script scanning#  | ||||
| Set-MpPreference -DisableScriptScanning 0 | ||||
|  | ||||
| # Enable removable drive scanning#  | ||||
| Set-MpPreference -DisableRemovableDriveScanning 0 | ||||
|  | ||||
| # Enable Block at first sight#  | ||||
| Set-MpPreference -DisableBlockAtFirstSeen 0 | ||||
|  | ||||
| # Enable potentially unwanted apps#  | ||||
| Set-MpPreference -PUAProtection Enabled | ||||
|  | ||||
| # Schedule signature updates every 8 hours#  | ||||
| Set-MpPreference -SignatureUpdateInterval 8 | ||||
|  | ||||
| # Enable archive scanning#  | ||||
| Set-MpPreference -DisableArchiveScanning 0 | ||||
|  | ||||
| # Enable email scanning#  | ||||
| Set-MpPreference -DisableEmailScanning 0 | ||||
|  | ||||
| if (!(Check-IsWindows10-1709)) | ||||
| { | ||||
|     # Set cloud block level to 'High'#  | ||||
|     Set-MpPreference -CloudBlockLevel High | ||||
|  | ||||
|     # Set cloud block timeout to 1 minute#  | ||||
|     Set-MpPreference -CloudExtendedTimeout 50 | ||||
|  | ||||
|     Write-Host # `nUpdating Windows Defender Exploit Guard settings`n#  -ForegroundColor Green  | ||||
|  | ||||
|     Write-Host # Enabling Controlled Folder Access and setting to block mode#  | ||||
|     Set-MpPreference -EnableControlledFolderAccess Enabled  | ||||
|  | ||||
|     Write-Host # Enabling Network Protection and setting to block mode#  | ||||
|     Set-MpPreference -EnableNetworkProtection Enabled | ||||
|  | ||||
|     Write-Host # Enabling Exploit Guard ASR rules and setting to block mode#  | ||||
|     Add-MpPreference -AttackSurfaceReductionRules_Ids 75668C1F-73B5-4CF0-BB93-3ECF5CB7CC84 -AttackSurfaceReductionRules_Actions Enabled | ||||
|     Add-MpPreference -AttackSurfaceReductionRules_Ids 3B576869-A4EC-4529-8536-B80A7769E899 -AttackSurfaceReductionRules_Actions Enabled | ||||
|     Add-MpPreference -AttackSurfaceReductionRules_Ids D4F940AB-401B-4EfC-AADC-AD5F3C50688A -AttackSurfaceReductionRules_Actions Enabled | ||||
|     Add-MpPreference -AttackSurfaceReductionRules_Ids D3E037E1-3EB8-44C8-A917-57927947596D -AttackSurfaceReductionRules_Actions Enabled | ||||
|     Add-MpPreference -AttackSurfaceReductionRules_Ids 5BEB7EFE-FD9A-4556-801D-275E5FFC04CC -AttackSurfaceReductionRules_Actions Enabled | ||||
|     Add-MpPreference -AttackSurfaceReductionRules_Ids BE9BA2D9-53EA-4CDC-84E5-9B1EEEE46550 -AttackSurfaceReductionRules_Actions Enabled | ||||
|     Add-MpPreference -AttackSurfaceReductionRules_Ids 92E97FA1-2EDF-4476-BDD6-9DD0B4DDDC7B -AttackSurfaceReductionRules_Actions Enabled | ||||
|  | ||||
|     if ($false -eq (Test-Path ProcessMitigation.xml)) | ||||
|     { | ||||
|         Write-Host # Downloading Process Mitigation file from https://demo.wd.microsoft.com/Content/ProcessMitigation.xml#  | ||||
|         $url = 'https://demo.wd.microsoft.com/Content/ProcessMitigation.xml' | ||||
|         Invoke-WebRequest $url -OutFile ProcessMitigation.xml | ||||
|     } | ||||
|  | ||||
|     Write-Host # Enabling Exploit Protection#  | ||||
|     Set-ProcessMitigation -PolicyFilePath ProcessMitigation.xml | ||||
|  | ||||
| } | ||||
|  | ||||
| else | ||||
| { | ||||
|     # #  Workaround for Windows 10 version 1703 | ||||
|     # Set cloud block level to 'High'#  | ||||
|     SetRegistryKey -key MpCloudBlockLevel -value 2 | ||||
|  | ||||
|     # Set cloud block timeout to 1 minute#  | ||||
|     SetRegistryKey -key MpBafsExtendedTimeout -value 50 | ||||
| } | ||||
|  | ||||
| Write-Host # `nSettings update complete#   -ForegroundColor Green | ||||
|  | ||||
| Write-Host # `nOutput Windows Defender AV settings status#   -ForegroundColor Green | ||||
| Get-MpPreference | ||||
							
								
								
									
										23
									
								
								scripts/OpenSSHServerInstall.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								scripts/OpenSSHServerInstall.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| ##Check if openssh is installed | ||||
| if((Get-WindowsCapability -Online | ? Name -like OpenSSH*).State -eq "Installed") | ||||
| 	{ | ||||
| 	Write-Output "OpenSSH Server is installed." | ||||
| 	} | ||||
|  | ||||
| else  | ||||
|  | ||||
| 		{ | ||||
| 	Write-Output "OpenSSH Server is NOT installed."; | ||||
| 	## Install SSH | ||||
| 	Add-WindowsCapability -Online -Name "OpenSSH.Server~~~~0.0.1.0" | ||||
|  | ||||
| 	## Set SSH service to start automatically | ||||
| 	Set-Service -Name sshd -StartupType "Automatic" | ||||
|  | ||||
| 	## Allow SSH through firewall on all profiles | ||||
| 	Get-NetFirewallRule -Name *ssh* | ||||
|  | ||||
| 	## Start SSH service | ||||
| 	Start-Service sshd | ||||
| 	 | ||||
| 	}  | ||||
							
								
								
									
										14
									
								
								scripts/RDP_enable.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								scripts/RDP_enable.bat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| powercfg.exe /h off | ||||
| powercfg /x -hibernate-timeout-ac 0 | ||||
| powercfg /x -hibernate-timeout-dc 0 | ||||
| powercfg /x -disk-timeout-ac 0 | ||||
| powercfg /x -disk-timeout-dc 0 | ||||
| powercfg /x -monitor-timeout-ac 0 | ||||
| powercfg /x -monitor-timeout-dc 0 | ||||
| Powercfg /x -standby-timeout-ac 0 | ||||
| powercfg /x -standby-timeout-dc 0 | ||||
| reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f | ||||
| netsh advfirewall firewall set rule group="remote desktop" new enable=Yes | ||||
|  | ||||
|  | ||||
| REM net localgroup "Remote Desktop Users" "%UserName%" /add | ||||
							
								
								
									
										10
									
								
								scripts/ResetHighPerformancePowerProfiletoDefaults.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								scripts/ResetHighPerformancePowerProfiletoDefaults.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '-x -monitor-timeout-ac 15'    | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '-x -disk-timeout-ac 0' | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '-x -standby-timeout-ac 0' | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '-x -hibernate-timeout-ac 0' | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '-setacvalueindex SCHEME_CURRENT 4f971e89-eebd-4455-a8de-9e59040e7347 5ca83367-6e45-459f-a27b-476b1d01c936 0' | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '-x -monitor-timeout-dc 10' | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '-x -disk-timeout-dc 0' | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '-x -standby-timeout-dc 20' | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '-x -hibernate-timeout-dc 0' | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '-setdcvalueindex SCHEME_CURRENT 4f971e89-eebd-4455-a8de-9e59040e7347 5ca83367-6e45-459f-a27b-476b1d01c936 1' | ||||
							
								
								
									
										1
									
								
								scripts/SetHighPerformancePowerProfile.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								scripts/SetHighPerformancePowerProfile.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| Start-Process -FilePath 'powercfg.exe' -ArgumentList '/setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' | ||||
							
								
								
									
										49
									
								
								scripts/Speedtest.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								scripts/Speedtest.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| ## Measures the speed of the download, can only be ran on a PC running Windows 10 or a server running Server 2016+, plan is to add uploading also | ||||
| ## Majority of this script has been copied/butchered from https://www.ramblingtechie.co.uk/2020/07/13/internet-speed-test-in-powershell/ | ||||
| # MINIMUM ACCEPTED THRESHOLD IN mbps  | ||||
| $mindownloadspeed = 20 | ||||
| $minuploadspeed = 4 | ||||
|  | ||||
| # File to download you can find download links for other files here https://speedtest.flonix.net | ||||
| $downloadurl = "https://files.xlawgaming.com/10mb.bin" | ||||
| #$UploadURL = "http://ipv4.download.thinkbroadband.com/10MB.zip" | ||||
|  | ||||
| # SIZE OF SPECIFIED FILE IN MB (adjust this to match the size of your file in MB as above) | ||||
| $size = 10 | ||||
| # Name of Downloaded file | ||||
| $localfile = "SpeedTest.bin" | ||||
|  | ||||
| # WEB CLIENT VARIABLES | ||||
| $webclient = New-Object System.Net.WebClient | ||||
|  | ||||
| #RUN DOWNLOAD & CALCULATE DOWNLOAD SPEED | ||||
| $downloadstart_time = Get-Date | ||||
| $webclient.DownloadFile($downloadurl, $localfile) | ||||
| $downloadtimetaken = $((Get-Date).Subtract($downloadstart_time).Seconds) | ||||
| $downloadspeed = ($size / $downloadtimetaken)*8 | ||||
| Write-Output "Time taken: $downloadtimetaken second(s) | Download Speed: $downloadspeed mbps" | ||||
|  | ||||
| #RUN UPLOAD & CALCULATE UPLOAD SPEED | ||||
| #$uploadstart_time = Get-Date | ||||
| #$webclient.UploadFile($UploadURL, $localfile) > $null; | ||||
| #$uploadtimetaken = $((Get-Date).Subtract($uploadstart_time).Seconds) | ||||
| #$uploadspeed = ($size / $uploadtimetaken) * 8 | ||||
| #Write-Output "Time taken: $uploadtimetaken second(s) | Upload Speed: $uploadspeed mbps" | ||||
|  | ||||
| #DELETE TEST DOWNLOAD FILE | ||||
| Remove-Item -path $localfile | ||||
|  | ||||
| #SEND ALERTS IF BELOW MINIMUM THRESHOLD  | ||||
| if ($downloadspeed -ge $mindownloadspeed)  | ||||
| {  | ||||
| Write-Output "Speed is acceptable. Current download speed at is $downloadspeed mbps which is above the threshold of $mindownloadspeed mbps"  | ||||
| exit 0 | ||||
| } | ||||
|  | ||||
| else  | ||||
| {  | ||||
| Write-Output "Current download speed at is $downloadspeed mbps which is below the minimum threshold of $mindownloadspeed mbps"  | ||||
| exit 1 | ||||
| } | ||||
|  | ||||
| Exit $LASTEXITCODE | ||||
							
								
								
									
										2
									
								
								scripts/SyncTime.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								scripts/SyncTime.bat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| REM Syncs time with domain controller | ||||
| net time %logonserver% /set /y | ||||
							
								
								
									
										2
									
								
								scripts/WinDefenderClearLogs.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								scripts/WinDefenderClearLogs.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| wevtutil cl "Microsoft-Windows-Windows Defender/Operational"  | ||||
| Write-Output "Logs are cleared and RMM status should be reset" | ||||
							
								
								
									
										24
									
								
								scripts/WinDefenderStatus.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								scripts/WinDefenderStatus.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| # This will check for Malware, Antispyware, that Windows Defender is Healthy, last scan etc within the last 24 hours | ||||
|  | ||||
| $ErrorActionPreference= 'silentlycontinue' | ||||
| $TimeSpan = (Get-Date) - (New-TimeSpan -Day 1) | ||||
|  | ||||
| if (Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Windows Defender/Operational';ID='1116','1118','1015','1006','5010','5012','5001','1123';StartTime=$TimeSpan})  | ||||
|  | ||||
| { | ||||
| Write-Output "Virus Found or Issue with Defender" | ||||
| Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Windows Defender/Operational';ID='1116','1118','1015','1006','5010','5012','5001','1123';StartTime=$TimeSpan} | ||||
| exit 1 | ||||
| } | ||||
|  | ||||
|  | ||||
| else  | ||||
|  | ||||
| { | ||||
| Write-Output "No Virus Found, Defender is Healthy" | ||||
| Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Windows Defender/Operational';ID='1150','1001';StartTime=$TimeSpan} | ||||
| exit 0 | ||||
| } | ||||
|  | ||||
|  | ||||
| Exit $LASTEXITCODE | ||||
							
								
								
									
										134
									
								
								scripts/Windows10Upgrade.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								scripts/Windows10Upgrade.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| Function Write-LogMessage { | ||||
| 	param( | ||||
| 		[Parameter(Mandatory)] | ||||
| 		[ValidateNotNullOrEmpty()] | ||||
| 		[string]$Message, | ||||
| 		[switch]$IsError | ||||
| 	) | ||||
| 	if ($IsError.IsPresent) { | ||||
| 		Write-EventLog -LogName "Application" ` | ||||
| 			-Source "RMMWindows10Upgrade" ` | ||||
| 			-EntryType Error ` | ||||
| 			-EventId 42042 ` | ||||
| 			-Message "$(Get-Date -Format 'MM/dd/yyyy HH:mm K') [ERROR] $message" | ||||
| 		Write-Verbose "$(Get-Date -Format 'MM/dd/yyyy HH:mm K') [ERROR] $message" | ||||
| 	} | ||||
| 	else { | ||||
| 		Write-EventLog -LogName "Application" ` | ||||
| 			-Source "RMMWindows10Upgrade" ` | ||||
| 			-EntryType Information ` | ||||
| 			-EventId 42042 ` | ||||
| 			-Message "$(Get-Date -Format 'MM/dd/yyyy HH:mm K') [INFO] $message" | ||||
| 		Write-Verbose "$(Get-Date -Format 'MM/dd/yyyy HH:mm K') [INFO] $message" | ||||
| 	} | ||||
| } | ||||
| Function New-Windows10EventSource { | ||||
| 	try {  | ||||
| 		New-EventLog -LogName "Application" ` | ||||
| 			-Source "RMMWindows10Upgrade" ` | ||||
| 			-ErrorAction Stop  | ||||
| 	} | ||||
|  catch { Write-LogMessage -Message "Summary of Actions - Set Up Windows Event Log Source`r`nEvent Log Source already registered." } | ||||
| } | ||||
| Function Remove-BuildNotificationRestrictions { | ||||
| 	$returnVal = $true | ||||
| 	$logEntry = "" | ||||
| 	$basePath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\" | ||||
| 	$fileNames = ( | ||||
| 		"Windows10UpgraderApp.exe", | ||||
| 		"Windows10Upgrader.exe", | ||||
| 		"WindowsUpdateBox.exe", | ||||
| 		"SetupHost.exe", | ||||
| 		"setupprep.exe", | ||||
| 		"EOSNotify.exe", | ||||
| 		"MusNotifyIcon.exe", | ||||
| 		"MusNotification.exe" | ||||
| 	) | ||||
| 	foreach ($file in $fileNames) { | ||||
| 		if (Test-Path "$($basePath)$($file)") { | ||||
| 			$logEntry = $logEntry + "Found $($file) in IFEO, removing.`r`n" | ||||
| 			Remove-Item -Path "$($basePath)$($file)" | ||||
| 			if (Test-Path "$($basePath)$($file)") { | ||||
| 				$returnVal = $false | ||||
| 				Write-LogMessage -Message "Failed to remove existing Registry Key $($basePath)\$file " -IsError | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	Write-LogMessage -Message "Summary of Actions - Removing Build Restrictions`r`n$($logEntry)" | ||||
| 	return $returnVal | ||||
| } | ||||
| Function Remove-OldUpgrades { | ||||
| 	$logMessage = "" | ||||
| 	$returnVal = $true | ||||
| 	$folders = ("_Windows_FU", "Windows10Upgrade") | ||||
| 	foreach ($folder in $folders) { | ||||
| 		if (Test-Path -Path "$($env:SystemDrive)\$folder") { | ||||
| 			$logMessage = $logMessage + "Found $($env:SystemDrive)\$folder... Removing`r`n" | ||||
| 			Remove-Item -Recurse -Path "$($env:SystemDrive)\$folder" -Force | ||||
| 			if (Test-Path -Path "$($env:SystemDrive)\$folder") { | ||||
| 				Write-LogMessage -Message "Failed to remove existing folder $($env:SystemDrive)\$folder " -IsError | ||||
| 				$returnVal = $false | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	Write-LogMessage -Message "Summary of Actions - Removing Old Upgrades`r`n$($logMessage)" | ||||
| 	return $returnVal | ||||
| } | ||||
| Function Test-FreeSpace { | ||||
| 	$filter = "DeviceID='$($env:SystemDrive)'" | ||||
| 	If (!((Get-WmiObject Win32_LogicalDisk -Filter $filter | Select-Object -expand freespace) / 1GB -ge 23)) { | ||||
| 		Write-LogMessage -Message "Insufficient Free Space available to perform Upgrade, 23 GB is required" -IsError | ||||
| 		return $false | ||||
| 	} | ||||
| 	return $true | ||||
| } | ||||
| Function Test-License { | ||||
| 	#Not a big fan of Doing it this way, but it's a lot easier/faster than the alternatives | ||||
| 	$returnVal = $false | ||||
| 	if ((cscript "$($env:windir)\system32\\slmgr.vbs" /dli) -match "Licensed") { $returnVal = $true } | ||||
| 	return $returnVal | ||||
| } | ||||
| Function New-Windows10Install { | ||||
| 	$ErrorActionPreference = "SilentlyContinue" | ||||
| 	$dir = "$($env:SystemDrive)\_Windows_FU\packages" | ||||
| 	New-Item -ItemType directory -Path $dir | ||||
| 	$webClient = New-Object System.Net.WebClient | ||||
| 	$url = 'https://go.microsoft.com/fwlink/?LinkID=799445' | ||||
| 	$file = "$($dir)\Win10Upgrade.exe" | ||||
| 	$webClient.DownloadFile($url, $file) | ||||
| 	$install = Start-Process -FilePath $file -ArgumentList "/quietinstall /skipeula /auto upgrade /copylogs $dir /migratedrivers all" -Wait -PassThru | ||||
| 	$hex = "{0:x}" -f $install.ExitCode | ||||
| 	$exit_code = "0x$hex" | ||||
|  | ||||
| 	# Convert hex code to human readable | ||||
| 	$message = Switch ($exit_code) { | ||||
| 		"0xC1900210" { "SUCCESS: No compatibility issues detected"; break } | ||||
| 		"0xC1900101" { "ERROR: Driver compatibility issue detected. https://docs.microsoft.com/en-us/windows/deployment/upgrade/resolution-procedures"; break } | ||||
| 		"0xC1900208" { "ERROR: Compatibility issue detected, unsupported programs:`r`n$incompatible_programs`r`n"; break } | ||||
| 		"0xC1900204" { "ERROR: Migration choice not available." ; break } | ||||
| 		"0xC1900200" { "ERROR: System not compatible with upgrade." ; break } | ||||
| 		"0xC190020E" { "ERROR: Insufficient disk space." ; break } | ||||
| 		"0x80070490" { "ERROR: General Windows Update failure, try the following troubleshooting steps`r`n- Run update troubleshooter`r`n- sfc /scannow`r`n- DISM.exe /Online /Cleanup-image /Restorehealth`r`n - Reset windows update components.`r`n"; break } | ||||
| 		"0xC1800118" { "ERROR: WSUS has downloaded content that it cannot use due to a missing decryption key."; break } | ||||
| 		"0x80090011" { "ERROR: A device driver error occurred during user data migration."; break } | ||||
| 		"0xC7700112" { "ERROR: Failure to complete writing data to the system drive, possibly due to write access failure on the hard disk."; break } | ||||
| 		"0xC1900201" { "ERROR: The system did not pass the minimum requirements to install the update."; break } | ||||
| 		"0x80240017" { "ERROR: The upgrade is unavailable for this edition of Windows."; break } | ||||
| 		"0x80070020" { "ERROR: The existing process cannot access the file because it is being used by another process."; break } | ||||
| 		"0xC1900107" { "ERROR: A cleanup operation from a previous installation attempt is still pending and a system reboot is required in order to continue the upgrade."; break } | ||||
| 		"0x3" { "SUCCESS: The upgrade started, no compatibility issues."; break } | ||||
| 		"0x5" { "ERROR: The compatibility check detected issues that require resolution before the upgrade can continue."; break } | ||||
| 		"0x7" { "ERROR: The installation option (upgrade or data only) was not available."; break } | ||||
| 		"0x0" { "SUCCESS: Upgrade started."; break } | ||||
| 		default { "WARNING: Unknown exit code."; break } | ||||
| 	} | ||||
| 	if ($exit_code -eq "0xC1900210" -or $exit_code -eq "0x3" -or $exit_code -eq "0x0") { Write-LogMessage -Message $message } | ||||
| 	else { Write-LogMessage -Message $message -IsError } | ||||
| } | ||||
|  | ||||
| #The Magic happens here | ||||
| New-Windows10EventSource | ||||
| Remove-BuildNotificationRestrictions | Out-Null | ||||
| Remove-OldUpgrades | Out-Null | ||||
| if (!$(Test-FreeSpace) -or !$(Test-License)) { Exit 1 } | ||||
| New-Windows10Install | ||||
							
								
								
									
										2
									
								
								scripts/disable_FastStartup.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								scripts/disable_FastStartup.bat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| REM Disable Faststartup on Windows 10 | ||||
| powercfg /h off | ||||
							
								
								
									
										5
									
								
								scripts/updatetacticalexclusion.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								scripts/updatetacticalexclusion.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| #Windows Defender Exclusions for Tactical | ||||
| Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*' | ||||
| Add-MpPreference -ExclusionPath 'C:\Windows\Temp\winagent-v*.exe' | ||||
| Add-MpPreference -ExclusionPath 'C:\Program Files\Mesh Agent\*' | ||||
| Add-MpPreference -ExclusionPath 'C:\salt\*' | ||||
							
								
								
									
										104
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										104
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -3933,9 +3933,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "@quasar/extras": { | ||||
|       "version": "1.9.12", | ||||
|       "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.9.12.tgz", | ||||
|       "integrity": "sha512-RZxwvOBYfptUUFUz81fsgRCaifB/rdwRvb7+/+ng5OQyK+VqRoCJZK6GDWNZPj0wbNNyKprX28Mem3TNWTHCMg==" | ||||
|       "version": "1.9.13", | ||||
|       "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.9.13.tgz", | ||||
|       "integrity": "sha512-9ptRGMjBqfgxzLmH4MOlzSzmkwg7JAkWaB+EqDuZmjHyqkAGjGkuZj2LK4qbJdgveR94mlsJHb5fKojKzGkt0w==" | ||||
|     }, | ||||
|     "@quasar/fastclick": { | ||||
|       "version": "1.1.4", | ||||
| @@ -4902,6 +4902,19 @@ | ||||
|         "picomatch": "^2.0.4" | ||||
|       } | ||||
|     }, | ||||
|     "apexcharts": { | ||||
|       "version": "3.23.1", | ||||
|       "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.23.1.tgz", | ||||
|       "integrity": "sha512-7fRpquXp725BUew5OO1mJWk16/IJPCUl0l8SjhISnAhAtbTaM9PnXPSmN2BvKO4RcT457CzMM7MCG5UokiTwcA==", | ||||
|       "requires": { | ||||
|         "svg.draggable.js": "^2.2.2", | ||||
|         "svg.easing.js": "^2.0.0", | ||||
|         "svg.filter.js": "^2.0.2", | ||||
|         "svg.pathmorphing.js": "^0.1.3", | ||||
|         "svg.resize.js": "^1.4.3", | ||||
|         "svg.select.js": "^3.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "aproba": { | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", | ||||
| @@ -5198,18 +5211,11 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "axios": { | ||||
|       "version": "0.21.0", | ||||
|       "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", | ||||
|       "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", | ||||
|       "version": "0.21.1", | ||||
|       "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", | ||||
|       "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", | ||||
|       "requires": { | ||||
|         "follow-redirects": "^1.10.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "follow-redirects": { | ||||
|           "version": "1.13.0", | ||||
|           "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", | ||||
|           "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "babel-code-frame": { | ||||
| @@ -9626,8 +9632,7 @@ | ||||
|     "follow-redirects": { | ||||
|       "version": "1.13.1", | ||||
|       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", | ||||
|       "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==", | ||||
|       "dev": true | ||||
|       "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" | ||||
|     }, | ||||
|     "for-in": { | ||||
|       "version": "1.0.2", | ||||
| @@ -16995,6 +17000,70 @@ | ||||
|         "has-flag": "^3.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "svg.draggable.js": { | ||||
|       "version": "2.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", | ||||
|       "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", | ||||
|       "requires": { | ||||
|         "svg.js": "^2.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "svg.easing.js": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", | ||||
|       "integrity": "sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=", | ||||
|       "requires": { | ||||
|         "svg.js": ">=2.3.x" | ||||
|       } | ||||
|     }, | ||||
|     "svg.filter.js": { | ||||
|       "version": "2.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", | ||||
|       "integrity": "sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=", | ||||
|       "requires": { | ||||
|         "svg.js": "^2.2.5" | ||||
|       } | ||||
|     }, | ||||
|     "svg.js": { | ||||
|       "version": "2.7.1", | ||||
|       "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", | ||||
|       "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" | ||||
|     }, | ||||
|     "svg.pathmorphing.js": { | ||||
|       "version": "0.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", | ||||
|       "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", | ||||
|       "requires": { | ||||
|         "svg.js": "^2.4.0" | ||||
|       } | ||||
|     }, | ||||
|     "svg.resize.js": { | ||||
|       "version": "1.4.3", | ||||
|       "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", | ||||
|       "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", | ||||
|       "requires": { | ||||
|         "svg.js": "^2.6.5", | ||||
|         "svg.select.js": "^2.1.2" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "svg.select.js": { | ||||
|           "version": "2.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", | ||||
|           "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", | ||||
|           "requires": { | ||||
|             "svg.js": "^2.2.5" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "svg.select.js": { | ||||
|       "version": "3.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", | ||||
|       "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", | ||||
|       "requires": { | ||||
|         "svg.js": "^2.6.5" | ||||
|       } | ||||
|     }, | ||||
|     "svgo": { | ||||
|       "version": "1.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", | ||||
| @@ -18119,6 +18188,11 @@ | ||||
|       "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "vue-apexcharts": { | ||||
|       "version": "1.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.6.0.tgz", | ||||
|       "integrity": "sha512-sT6tuVTLBwfH3TA7azecDNS/W70bmz14ZJI7aE7QIqcG9I6OywyH7x3hcOeY1v1DxttI8Svc5RuYj4Dd+A5F4g==" | ||||
|     }, | ||||
|     "vue-hot-reload-api": { | ||||
|       "version": "2.3.4", | ||||
|       "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", | ||||
|   | ||||
| @@ -9,11 +9,13 @@ | ||||
|     "test:unit": "quasar test --unit jest" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@quasar/extras": "^1.9.12", | ||||
|     "axios": "^0.21.0", | ||||
|     "@quasar/extras": "^1.9.13", | ||||
|     "axios": "^0.21.1", | ||||
|     "apexcharts": "^3.23.1", | ||||
|     "dotenv": "^8.2.0", | ||||
|     "qrcode.vue": "^1.7.0", | ||||
|     "quasar": "^1.15.0" | ||||
|     "quasar": "^1.15.0", | ||||
|     "vue-apexcharts": "^1.6.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@quasar/app": "^2.1.14", | ||||
|   | ||||
| @@ -473,7 +473,17 @@ export default { | ||||
|       // give time for store to change active row | ||||
|       setTimeout(() => { | ||||
|         this.$q.loading.hide(); | ||||
|         this.showEditAgentModal = true; | ||||
|         switch (this.agentDblClickAction) { | ||||
|           case "editagent": | ||||
|             this.showEditAgentModal = true; | ||||
|             break; | ||||
|           case "takecontrol": | ||||
|             this.takeControl(pk); | ||||
|             break; | ||||
|           case "remotebg": | ||||
|             this.remoteBG(pk); | ||||
|             break; | ||||
|         } | ||||
|       }, 500); | ||||
|     }, | ||||
|     runFavScript(scriptpk, agentpk) { | ||||
| @@ -694,6 +704,9 @@ export default { | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters(["selectedAgentPk", "agentTableHeight"]), | ||||
|     agentDblClickAction() { | ||||
|       return this.$store.state.agentDblClickAction; | ||||
|     }, | ||||
|     selectedRow() { | ||||
|       return this.$store.state.selectedRow; | ||||
|     }, | ||||
|   | ||||
| @@ -196,9 +196,14 @@ | ||||
|                   >output</span | ||||
|                 > | ||||
|               </q-td> | ||||
|               <q-td v-else-if="props.row.check_type === 'cpuload' || props.row.check_type === 'memory'">{{ | ||||
|                 props.row.history_info | ||||
|               }}</q-td> | ||||
|               <q-td v-else-if="props.row.check_type === 'cpuload' || props.row.check_type === 'memory'"> | ||||
|                 <span | ||||
|                   style="cursor: pointer; text-decoration: underline" | ||||
|                   class="text-primary" | ||||
|                   @click="showCheckGraphModal(props.row)" | ||||
|                   >Show Run History</span | ||||
|                 > | ||||
|               </q-td> | ||||
|               <q-td v-else>{{ props.row.more_info }}</q-td> | ||||
|               <q-td>{{ props.row.last_run }}</q-td> | ||||
|               <q-td v-if="props.row.assigned_task !== null && props.row.assigned_task.length > 1" | ||||
| @@ -267,6 +272,7 @@ import EventLogCheck from "@/components/modals/checks/EventLogCheck"; | ||||
| import ScriptCheck from "@/components/modals/checks/ScriptCheck"; | ||||
| import ScriptOutput from "@/components/modals/checks/ScriptOutput"; | ||||
| import EventLogCheckOutput from "@/components/modals/checks/EventLogCheckOutput"; | ||||
| import CheckGraph from "@/components/graphs/CheckGraph"; | ||||
|  | ||||
| export default { | ||||
|   name: "ChecksTab", | ||||
| @@ -424,6 +430,13 @@ export default { | ||||
|             .catch(e => this.notifyError(e.response.data)); | ||||
|         }); | ||||
|     }, | ||||
|     showCheckGraphModal(check) { | ||||
|       this.$q.dialog({ | ||||
|         component: CheckGraph, | ||||
|         parent: this, | ||||
|         check: check, | ||||
|       }); | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters(["selectedAgentPk", "checks", "tabsTableHeight"]), | ||||
|   | ||||
| @@ -109,6 +109,11 @@ export default { | ||||
|           field: "memory_percent", | ||||
|           align: "left", | ||||
|           sortable: true, | ||||
|           sort: (a, b, rowA, rowB) => { | ||||
|             const newA = parseFloat(a.replace(/[a-z]+/i, "")); | ||||
|             const newB = parseFloat(b.replace(/[a-z]+/i, "")); | ||||
|             return newB < newA; | ||||
|           }, | ||||
|         }, | ||||
|         { | ||||
|           name: "username", | ||||
|   | ||||
| @@ -111,7 +111,7 @@ | ||||
|         <q-tree | ||||
|           ref="folderTree" | ||||
|           v-if="!tableView" | ||||
|           style="min-height: 30vh; max-height: 65vh" | ||||
|           style="min-height: 65vh; max-height: 65vh" | ||||
|           class="scroll" | ||||
|           :nodes="tree" | ||||
|           :filter="search" | ||||
| @@ -120,6 +120,8 @@ | ||||
|           :expanded.sync="expanded" | ||||
|           @update:selected="nodeSelected" | ||||
|           :selected.sync="selected" | ||||
|           no-results-label="No Scripts Found" | ||||
|           no-nodes-label="No Scripts Found" | ||||
|         > | ||||
|           <template v-slot:header-script="props"> | ||||
|             <div :class="props.node.id === props.tree.selected ? 'text-primary' : ''"> | ||||
| @@ -203,7 +205,7 @@ | ||||
|         </q-tree> | ||||
|         <q-table | ||||
|           v-if="tableView" | ||||
|           style="min-height: 30vw; max-height: 30vw" | ||||
|           style="min-height: 65vh; max-height: 65vh" | ||||
|           dense | ||||
|           :table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }" | ||||
|           class="settings-tbl-sticky scroll" | ||||
| @@ -218,6 +220,7 @@ | ||||
|           virtual-scroll | ||||
|           flat | ||||
|           :rows-per-page-options="[0]" | ||||
|           no-data-label="No Scripts Found" | ||||
|         > | ||||
|           <template v-slot:header-cell-favorite="props"> | ||||
|             <q-th :props="props" auto-width> | ||||
| @@ -547,12 +550,18 @@ export default { | ||||
|         return []; | ||||
|       } else { | ||||
|         let nodes = []; | ||||
|         let unassigned = []; | ||||
|         let community = []; | ||||
|  | ||||
|         // copy scripts and categories to new array | ||||
|         let scriptsTemp = Object.assign([], this.visibleScripts); | ||||
|         let categoriesTemp = Object.assign([], this.categories); | ||||
|  | ||||
|         this.categories.forEach(category => { | ||||
|         // add Community and Unassigned values and categories array | ||||
|         if (this.showCommunityScripts) categoriesTemp.push("Community"); | ||||
|         categoriesTemp.push("Unassigned"); | ||||
|  | ||||
|         const sorted = categoriesTemp.sort(); | ||||
|  | ||||
|         sorted.forEach(category => { | ||||
|           let temp = { | ||||
|             icon: "folder", | ||||
|             iconColor: "yellow-9", | ||||
| @@ -565,11 +574,8 @@ export default { | ||||
|             if (scriptsTemp[i].category === category) { | ||||
|               temp.children.push({ label: scriptsTemp[i].name, header: "script", ...scriptsTemp[i] }); | ||||
|               scriptsTemp.splice(i, 1); | ||||
|             } else if (scriptsTemp[i].category === "Community") { | ||||
|               community.push({ label: scriptsTemp[i].name, header: "script", ...scriptsTemp[i] }); | ||||
|               scriptsTemp.splice(i, 1); | ||||
|             } else if (!scriptsTemp[i].category) { | ||||
|               unassigned.push({ label: scriptsTemp[i].name, header: "script", ...scriptsTemp[i] }); | ||||
|             } else if (category === "Unassigned" && !scriptsTemp[i].category) { | ||||
|               temp.children.push({ label: scriptsTemp[i].name, header: "script", ...scriptsTemp[i] }); | ||||
|               scriptsTemp.splice(i, 1); | ||||
|             } | ||||
|           } | ||||
| @@ -577,29 +583,6 @@ export default { | ||||
|           nodes.push(temp); | ||||
|         }); | ||||
|  | ||||
|         if (unassigned.length > 0) { | ||||
|           let temp = { | ||||
|             icon: "folder", | ||||
|             iconColor: "yellow-9", | ||||
|             label: "Unassigned", | ||||
|             id: "Unassigned", | ||||
|             selectable: false, | ||||
|             children: unassigned, | ||||
|           }; | ||||
|           nodes.push(temp); | ||||
|         } | ||||
|  | ||||
|         if (community.length > 0) { | ||||
|           let temp = { | ||||
|             icon: "folder", | ||||
|             iconColor: "yellow-9", | ||||
|             label: "Community", | ||||
|             id: "Community", | ||||
|             selectable: false, | ||||
|             children: community, | ||||
|           }; | ||||
|           nodes.push(temp); | ||||
|         } | ||||
|         return nodes; | ||||
|       } | ||||
|     }, | ||||
|   | ||||
							
								
								
									
										169
									
								
								web/src/components/graphs/CheckGraph.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								web/src/components/graphs/CheckGraph.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| <template> | ||||
|   <q-dialog ref="dialog" @hide="onHide"> | ||||
|     <q-card class="q-dialog-plugin" style="min-width: 80vw; min-height: 65vh; overflow-x: hidden"> | ||||
|       <q-bar> | ||||
|         <q-btn @click="getChartData" class="q-mr-sm" dense flat push icon="refresh" /> | ||||
|         {{ title }} | ||||
|         <q-space /> | ||||
|         <q-btn dense flat icon="close" v-close-popup> | ||||
|           <q-tooltip content-class="bg-white text-primary">Close</q-tooltip> | ||||
|         </q-btn> | ||||
|       </q-bar> | ||||
|       <div class="row"> | ||||
|         <span v-if="!showChart" class="q-pa-md">No Data</span> | ||||
|         <q-space /> | ||||
|         <q-select | ||||
|           v-model="timeFilter" | ||||
|           emit-value | ||||
|           map-options | ||||
|           style="width: 200px" | ||||
|           :options="timeFilterOptions" | ||||
|           outlined | ||||
|           dense | ||||
|           class="q-pr-md q-pt-md" | ||||
|           @input="getChartData" | ||||
|         /> | ||||
|       </div> | ||||
|       <apexchart | ||||
|         v-if="showChart" | ||||
|         class="q-pt-md" | ||||
|         type="line" | ||||
|         height="70%" | ||||
|         :options="chartOptions" | ||||
|         :series="[{ name: 'Percentage', data: history }]" | ||||
|       /> | ||||
|     </q-card> | ||||
|   </q-dialog> | ||||
| </template> | ||||
| <script> | ||||
| import VueApexCharts from "vue-apexcharts"; | ||||
|  | ||||
| export default { | ||||
|   name: "CheckGraph", | ||||
|   components: { | ||||
|     apexchart: VueApexCharts, | ||||
|   }, | ||||
|   props: { | ||||
|     check: !Object, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       history: [], | ||||
|       timeFilter: 1, | ||||
|       timeFilterOptions: [ | ||||
|         { value: 1, label: "Last 24 Hours" }, | ||||
|         { value: 7, label: "Last 7 Days" }, | ||||
|         { value: 30, label: "Last 30 Days" }, | ||||
|         { value: 0, label: "Everything" }, | ||||
|       ], | ||||
|       chartOptions: { | ||||
|         tooltip: { | ||||
|           x: { | ||||
|             format: "dd MMM h:mm:sst", | ||||
|           }, | ||||
|         }, | ||||
|         chart: { | ||||
|           id: "chart2", | ||||
|           type: "line", | ||||
|           toolbar: { | ||||
|             show: true, | ||||
|           }, | ||||
|           animations: { | ||||
|             enabled: false, | ||||
|           }, | ||||
|         }, | ||||
|         colors: ["#027BE3"], | ||||
|         stroke: { | ||||
|           width: 3, | ||||
|         }, | ||||
|         dataLabels: { | ||||
|           enabled: false, | ||||
|         }, | ||||
|         fill: { | ||||
|           opacity: 1, | ||||
|         }, | ||||
|         markers: { | ||||
|           size: 1, | ||||
|         }, | ||||
|         xaxis: { | ||||
|           type: "datetime", | ||||
|           labels: { | ||||
|             datetimeUTC: false, | ||||
|           }, | ||||
|         }, | ||||
|         yaxis: { | ||||
|           min: 0, | ||||
|           max: 100, | ||||
|         }, | ||||
|         noData: { | ||||
|           text: "No Data", | ||||
|         }, | ||||
|         theme: { | ||||
|           mode: this.$q.dark.isActive ? "dark" : "light", | ||||
|         }, | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|     title() { | ||||
|       return this.check.readable_desc + " history"; | ||||
|     }, | ||||
|     showChart() { | ||||
|       return !this.$q.loading.isActive && this.history.length > 0; | ||||
|     }, | ||||
|   }, | ||||
|   methods: { | ||||
|     getChartData() { | ||||
|       this.$q.loading.show(); | ||||
|  | ||||
|       this.$axios | ||||
|         .patch(`/checks/history/${this.check.id}/`, { timeFilter: this.timeFilter }) | ||||
|         .then(r => { | ||||
|           this.history = r.data; | ||||
|           this.$q.loading.hide(); | ||||
|         }) | ||||
|         .catch(e => { | ||||
|           this.$q.loading.hide(); | ||||
|         }); | ||||
|     }, | ||||
|     show() { | ||||
|       this.$refs.dialog.show(); | ||||
|     }, | ||||
|     hide() { | ||||
|       this.$refs.dialog.hide(); | ||||
|     }, | ||||
|     onHide() { | ||||
|       this.$emit("hide"); | ||||
|     }, | ||||
|   }, | ||||
|   created() { | ||||
|     this.getChartData(); | ||||
|  | ||||
|     // add annotation depending on check type | ||||
|     if ( | ||||
|       this.check.check_type === "cpuload" || | ||||
|       this.check.check_type === "memory" || | ||||
|       this.check.check_type === "diskspace" | ||||
|     ) { | ||||
|       this.chartOptions["annotations"] = { | ||||
|         position: "front", | ||||
|         yaxis: [ | ||||
|           { | ||||
|             y: this.check.threshold, | ||||
|             strokeDashArray: 0, | ||||
|             borderColor: "#C10015", | ||||
|             label: { | ||||
|               borderColor: "#C10015", | ||||
|               style: { | ||||
|                 color: "#FFF", | ||||
|                 background: "#C10015", | ||||
|               }, | ||||
|               text: "Threshold", | ||||
|             }, | ||||
|           }, | ||||
|         ], | ||||
|       }; | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| @@ -34,10 +34,36 @@ | ||||
|       </q-card-section> | ||||
|       <q-card-section> | ||||
|         <div class="q-gutter-sm"> | ||||
|           <q-radio dense v-model="output" val="wait" label="Wait for Output" /> | ||||
|           <q-radio dense v-model="output" val="forget" label="Fire and Forget" /> | ||||
|           <q-radio dense v-model="output" val="wait" label="Wait for Output" @input="emails = []" /> | ||||
|           <q-radio dense v-model="output" val="forget" label="Fire and Forget" @input="emails = []" /> | ||||
|           <q-radio dense v-model="output" val="email" label="Email results" /> | ||||
|         </div> | ||||
|       </q-card-section> | ||||
|       <q-card-section v-if="output === 'email'"> | ||||
|         <div class="q-gutter-sm"> | ||||
|           <q-radio | ||||
|             dense | ||||
|             v-model="emailmode" | ||||
|             val="default" | ||||
|             label="Use email addresses from global settings" | ||||
|             @input="emails = []" | ||||
|           /> | ||||
|           <q-radio dense v-model="emailmode" val="custom" label="Custom emails" /> | ||||
|         </div> | ||||
|       </q-card-section> | ||||
|       <q-card-section v-if="emailmode === 'custom' && output === 'email'"> | ||||
|         <q-select | ||||
|           label="Email recipients (press Enter after typing each email)" | ||||
|           filled | ||||
|           v-model="emails" | ||||
|           use-input | ||||
|           use-chips | ||||
|           multiple | ||||
|           hide-dropdown-icon | ||||
|           input-debounce="0" | ||||
|           new-value-mode="add" | ||||
|         /> | ||||
|       </q-card-section> | ||||
|       <q-card-section> | ||||
|         <q-input | ||||
|           v-model.number="timeout" | ||||
| @@ -83,6 +109,8 @@ export default { | ||||
|       ret: null, | ||||
|       output: "wait", | ||||
|       args: [], | ||||
|       emails: [], | ||||
|       emailmode: "default", | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
| @@ -117,6 +145,8 @@ export default { | ||||
|         scriptPK: this.scriptPK, | ||||
|         output: this.output, | ||||
|         args: this.args, | ||||
|         emails: this.emails, | ||||
|         emailmode: this.emailmode, | ||||
|       }; | ||||
|       this.$axios | ||||
|         .post("/agents/runscript/", data) | ||||
|   | ||||
| @@ -67,6 +67,11 @@ | ||||
|                     class="col-6" | ||||
|                   /> | ||||
|                 </q-card-section> | ||||
|                 <q-card-section class="row"> | ||||
|                   <div class="col-4">Remove Check History older than (days):</div> | ||||
|                   <div class="col-2"></div> | ||||
|                   <q-input outlined dense v-model="settings.check_history_prune_days" class="col-6" /> | ||||
|                 </q-card-section> | ||||
|                 <q-card-section class="row"> | ||||
|                   <div class="col-4">Reset Patch Policy on Agents:</div> | ||||
|                   <div class="col-2"></div> | ||||
| @@ -309,7 +314,7 @@ export default { | ||||
|       settings: {}, | ||||
|       email: null, | ||||
|       tab: "general", | ||||
|       splitterModel: 15, | ||||
|       splitterModel: 20, | ||||
|       isPwd: true, | ||||
|       allTimezones: [], | ||||
|       emailTest: false, | ||||
|   | ||||
							
								
								
									
										94
									
								
								web/src/components/modals/coresettings/UserPreferences.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								web/src/components/modals/coresettings/UserPreferences.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| <template> | ||||
|   <q-card style="min-width: 85vh"> | ||||
|     <q-splitter v-model="splitterModel"> | ||||
|       <template v-slot:before> | ||||
|         <q-tabs dense v-model="tab" vertical class="text-primary"> | ||||
|           <q-tab name="ui" label="User Interface" /> | ||||
|         </q-tabs> | ||||
|       </template> | ||||
|       <template v-slot:after> | ||||
|         <q-form @submit.prevent="editUserPrefs"> | ||||
|           <q-card-section class="row items-center"> | ||||
|             <div class="text-h6">Preferences</div> | ||||
|             <q-space /> | ||||
|             <q-btn icon="close" flat round dense v-close-popup /> | ||||
|           </q-card-section> | ||||
|           <q-tab-panels v-model="tab" animated transition-prev="jump-up" transition-next="jump-up"> | ||||
|             <!-- UI --> | ||||
|             <q-tab-panel name="ui"> | ||||
|               <div class="text-subtitle2">User Interface</div> | ||||
|               <hr /> | ||||
|               <q-card-section class="row"> | ||||
|                 <div class="col-6">Agent table double-click action:</div> | ||||
|                 <div class="col-2"></div> | ||||
|                 <q-select | ||||
|                   map-options | ||||
|                   emit-value | ||||
|                   outlined | ||||
|                   dense | ||||
|                   options-dense | ||||
|                   v-model="agentDblClickAction" | ||||
|                   :options="agentDblClickOptions" | ||||
|                   class="col-4" | ||||
|                 /> | ||||
|               </q-card-section> | ||||
|             </q-tab-panel> | ||||
|           </q-tab-panels> | ||||
|  | ||||
|           <q-card-section class="row items-center"> | ||||
|             <q-btn label="Save" color="primary" type="submit" /> | ||||
|           </q-card-section> | ||||
|         </q-form> | ||||
|       </template> | ||||
|     </q-splitter> | ||||
|   </q-card> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import mixins from "@/mixins/mixins"; | ||||
| import { mapState } from "vuex"; | ||||
|  | ||||
| export default { | ||||
|   name: "UserPreferences", | ||||
|   mixins: [mixins], | ||||
|   data() { | ||||
|     return { | ||||
|       agentDblClickAction: "", | ||||
|       tab: "ui", | ||||
|       splitterModel: 20, | ||||
|       agentDblClickOptions: [ | ||||
|         { | ||||
|           label: "Edit Agent", | ||||
|           value: "editagent", | ||||
|         }, | ||||
|         { | ||||
|           label: "Take Control", | ||||
|           value: "takecontrol", | ||||
|         }, | ||||
|         { | ||||
|           label: "Remote Background", | ||||
|           value: "remotebg", | ||||
|         }, | ||||
|       ], | ||||
|     }; | ||||
|   }, | ||||
|   methods: { | ||||
|     getUserPrefs() { | ||||
|       this.$axios.get("/core/dashinfo/").then(r => { | ||||
|         this.agentDblClickAction = r.data.dbl_click_action; | ||||
|       }); | ||||
|     }, | ||||
|     editUserPrefs() { | ||||
|       const data = { agent_dblclick_action: this.agentDblClickAction }; | ||||
|       this.$axios.patch("/accounts/users/ui/", data).then(r => { | ||||
|         this.notifySuccess("Preferences were saved!"); | ||||
|         this.$emit("edited"); | ||||
|         this.$emit("close"); | ||||
|       }); | ||||
|     }, | ||||
|   }, | ||||
|   created() { | ||||
|     this.getUserPrefs(); | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| @@ -33,7 +33,8 @@ export default function () { | ||||
|       needrefresh: false, | ||||
|       tableHeight: "35vh", | ||||
|       tabHeight: "35vh", | ||||
|       showCommunityScripts: false | ||||
|       showCommunityScripts: false, | ||||
|       agentDblClickAction: "", | ||||
|     }, | ||||
|     getters: { | ||||
|       loggedIn(state) { | ||||
| @@ -135,6 +136,9 @@ export default function () { | ||||
|       }, | ||||
|       setShowCommunityScripts(state, show) { | ||||
|         state.showCommunityScripts = show | ||||
|       }, | ||||
|       SET_AGENT_DBLCLICK_ACTION(state, action) { | ||||
|         state.agentDblClickAction = action | ||||
|       } | ||||
|     }, | ||||
|     actions: { | ||||
|   | ||||
| @@ -73,6 +73,11 @@ | ||||
|  | ||||
|         <q-btn-dropdown flat no-caps stretch :label="user"> | ||||
|           <q-list> | ||||
|             <q-item clickable v-ripple @click="showUserPreferencesModal = true" v-close-popup> | ||||
|               <q-item-section> | ||||
|                 <q-item-label>Preferences</q-item-label> | ||||
|               </q-item-section> | ||||
|             </q-item> | ||||
|             <q-item to="/expired" exact> | ||||
|               <q-item-section> | ||||
|                 <q-item-label>Logout</q-item-label> | ||||
| @@ -354,6 +359,10 @@ | ||||
|     <q-dialog v-model="showInstallAgentModal" @hide="closeInstallAgent"> | ||||
|       <InstallAgent @close="closeInstallAgent" :sitepk="parseInt(sitePk)" /> | ||||
|     </q-dialog> | ||||
|     <!-- user preferences modal --> | ||||
|     <q-dialog v-model="showUserPreferencesModal"> | ||||
|       <UserPreferences @close="showUserPreferencesModal = false" @edited="getDashInfo" /> | ||||
|     </q-dialog> | ||||
|   </q-layout> | ||||
| </template> | ||||
|  | ||||
| @@ -369,6 +378,7 @@ import PolicyAdd from "@/components/automation/modals/PolicyAdd"; | ||||
| import ClientsForm from "@/components/modals/clients/ClientsForm"; | ||||
| import SitesForm from "@/components/modals/clients/SitesForm"; | ||||
| import InstallAgent from "@/components/modals/agents/InstallAgent"; | ||||
| import UserPreferences from "@/components/modals/coresettings/UserPreferences"; | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
| @@ -380,6 +390,7 @@ export default { | ||||
|     ClientsForm, | ||||
|     SitesForm, | ||||
|     InstallAgent, | ||||
|     UserPreferences, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
| @@ -413,6 +424,7 @@ export default { | ||||
|       filterChecksFailing: false, | ||||
|       filterRebootNeeded: false, | ||||
|       currentTRMMVersion: null, | ||||
|       showUserPreferencesModal: false, | ||||
|       columns: [ | ||||
|         { | ||||
|           name: "smsalert", | ||||
| @@ -673,7 +685,7 @@ export default { | ||||
|         this.darkMode = r.data.dark_mode; | ||||
|         this.$q.dark.set(this.darkMode); | ||||
|         this.currentTRMMVersion = r.data.trmm_version; | ||||
|  | ||||
|         this.$store.commit("SET_AGENT_DBLCLICK_ACTION", r.data.dbl_click_action); | ||||
|         this.$store.commit("setShowCommunityScripts", r.data.show_community_scripts); | ||||
|       }); | ||||
|     }, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user