Compare commits

...

71 Commits

Author SHA1 Message Date
wh1te909
9ab915a08b Release 0.2.23 2021-01-14 02:43:56 +00:00
wh1te909
e26fbf0328 bump versions 2021-01-14 02:29:14 +00:00
wh1te909
d9a52c4a2a update reqs 2021-01-14 02:27:40 +00:00
wh1te909
7b2ec90de9 feat: double-click agent action #232 2021-01-14 02:21:08 +00:00
wh1te909
d310bf8bbf add community scripts from dinger #242 2021-01-14 01:17:58 +00:00
wh1te909
2abc6cc939 partially fix sort 2021-01-14 00:01:08 +00:00
sadnub
56d4e694a2 fix annotations and error for the check chart 2021-01-13 18:43:09 -05:00
wh1te909
5f002c9cdc bump mesh 2021-01-13 23:35:14 +00:00
wh1te909
759daf4b4a add wording 2021-01-13 23:35:01 +00:00
wh1te909
3a8d9568e3 split some tasks into chunks to reduce load 2021-01-13 22:26:54 +00:00
wh1te909
ff22a9d94a fix deployments in docker 2021-01-13 22:19:09 +00:00
sadnub
a6e42d5374 fix removing pendingactions that are outstanding 2021-01-13 13:21:09 -05:00
wh1te909
a2f74e0488 add natsapi flags 2021-01-12 21:14:43 +00:00
wh1te909
ee44240569 black 2021-01-12 21:06:44 +00:00
wh1te909
d0828744a2 update nginx conf
(cherry picked from commit bf61e27f8a)
2021-01-12 06:38:52 +00:00
wh1te909
6e2e576b29 start natsapi 2021-01-12 06:32:00 +00:00
wh1te909
bf61e27f8a update nginx conf 2021-01-12 03:02:03 +00:00
Tragic Bronson
c441c30b46 Merge pull request #243 from sadnub/develop
Move Check Runs from Audit to its own table
2021-01-11 00:29:59 -08:00
Tragic Bronson
0e741230ea Merge pull request #242 from dinger1986/develop
Added some scripts checks etc
2021-01-11 00:29:47 -08:00
sadnub
1bfe9ac2db complete other pending actions with same task if task is deleted 2021-01-10 20:19:38 -05:00
sadnub
6812e72348 fix process sorting 2021-01-10 19:35:39 -05:00
sadnub
b6449d2f5b black 2021-01-10 16:33:10 -05:00
sadnub
7e3ea20dce add some tests and bug fixes 2021-01-10 16:27:48 -05:00
sadnub
c9d6fe9dcd allow returning all check data 2021-01-10 15:14:02 -05:00
sadnub
4a649a6b8b black 2021-01-10 14:47:34 -05:00
sadnub
8fef184963 add check history graph for cpu, memory, and diskspace 2021-01-10 14:15:05 -05:00
sadnub
69583ca3c0 docker dev fixes 2021-01-10 13:17:49 -05:00
dinger1986
6038a68e91 Win Defender exclusions for Tactical 2021-01-10 17:56:12 +00:00
dinger1986
fa8bd8db87 Manually reinstall Mesh just incase 2021-01-10 17:54:41 +00:00
dinger1986
18b4f0ed0f Runs DNS check on host as defined 2021-01-10 17:53:53 +00:00
dinger1986
461f9d66c9 Disable Faststartup on Windows 10 2021-01-10 17:51:33 +00:00
dinger1986
2155103c7a Check Win Defender for detections etc 2021-01-10 17:51:06 +00:00
dinger1986
c9a6839c45 Clears Win Defender log files 2021-01-10 17:50:13 +00:00
dinger1986
9fbe331a80 Allows the following Apps access by Win Defender 2021-01-10 17:49:36 +00:00
dinger1986
a56389c4ce Sync time with DC 2021-01-10 17:46:47 +00:00
dinger1986
64656784cb Powershell Speedtest 2021-01-10 17:46:00 +00:00
dinger1986
6eff2c181e Install RDP and change power config 2021-01-10 17:44:23 +00:00
dinger1986
1aa48c6d62 Install OpenSSH on PCs 2021-01-10 17:42:11 +00:00
dinger1986
c7ca1a346d Enable Windows Defender and set preferences 2021-01-10 17:40:06 +00:00
dinger1986
fa0ec7b502 check Duplicati Backup is running properly 2021-01-10 17:38:06 +00:00
dinger1986
768438c136 Checks disks for errors reported in event viewer 2021-01-10 17:36:42 +00:00
dinger1986
9badea0b3c Update DiskStatus.ps1
Checks local disks for errors reported in event viewer within the last 24 hours
2021-01-10 17:35:50 +00:00
dinger1986
43263a1650 Add files via upload 2021-01-10 17:33:48 +00:00
wh1te909
821e02dc75 update mesh docker conf 2021-01-10 00:20:44 +00:00
wh1te909
ed011ecf28 remove old mesh overrides #217 2021-01-10 00:15:11 +00:00
wh1te909
d861de4c2f update community scripts 2021-01-09 22:26:02 +00:00
Tragic Bronson
3a3b2449dc Merge pull request #241 from RVL-Solutions/develop
Create Windows10Upgrade.ps1
2021-01-09 14:12:05 -08:00
Ruben van Leusden
d2614406ca Create Windows10Upgrade.ps1
Shared by Kyt through Discord
2021-01-08 22:20:33 +01:00
Tragic Bronson
0798d098ae Merge pull request #238 from wh1te909/revert-235-master
Revert "Create Windows10Upgrade.ps1"
2021-01-08 10:38:33 -08:00
Tragic Bronson
dab7ddc2bb Revert "Create Windows10Upgrade.ps1" 2021-01-08 10:36:42 -08:00
Tragic Bronson
081a96e281 Merge pull request #235 from RVL-Solutions/master
Create Windows10Upgrade.ps1
2021-01-08 10:36:19 -08:00
wh1te909
a7dd881d79 Release 0.2.22 2021-01-08 18:16:17 +00:00
wh1te909
8134d5e24d remove threading 2021-01-08 18:15:55 +00:00
Ruben van Leusden
ba6756cd45 Create Windows10Upgrade.ps1 2021-01-06 23:19:14 +01:00
Tragic Bronson
5d8fce21ac Merge pull request #230 from wh1te909/dependabot/npm_and_yarn/web/axios-0.21.1
Bump axios from 0.21.0 to 0.21.1 in /web
2021-01-05 13:51:18 -08:00
dependabot[bot]
e7e4a5bcd4 Bump axios from 0.21.0 to 0.21.1 in /web
Bumps [axios](https://github.com/axios/axios) from 0.21.0 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.0...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-05 15:54:54 +00:00
wh1te909
55f33357ea Release 0.2.21 2021-01-05 08:55:54 +00:00
wh1te909
90568bba31 bump versions 2021-01-05 08:55:08 +00:00
wh1te909
5d6e2dc2e4 feat: add send script results by email #212 2021-01-05 08:52:17 +00:00
sadnub
6bb33f2559 fix unassigned scripts not show if not categories are present 2021-01-04 20:22:42 -05:00
wh1te909
ced92554ed update community scripts 2021-01-04 22:00:17 +00:00
Tragic Bronson
dff3383158 Merge pull request #228 from azulskyknight/patch-2
Create SetHighPerformancePowerProfile.ps1
2021-01-04 13:42:20 -08:00
Tragic Bronson
bf03c89cb2 Merge pull request #227 from azulskyknight/patch-1
Create ResetHighPerformancePowerProfiletoDefaults.ps1
2021-01-04 13:42:10 -08:00
azulskyknight
9f1484bbef Create SetHighPerformancePowerProfile.ps1
Script sets the High Performance Power profile to the active power profile.
Use this to keep machines from falling asleep.
2021-01-04 13:21:00 -07:00
azulskyknight
3899680e26 Create ResetHighPerformancePowerProfiletoDefaults.ps1
Script 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.
2021-01-04 13:19:03 -07:00
sadnub
6bb2eb25a1 sort script folders alphabetically and fix showing community scripts when no user scripts present 2021-01-03 21:01:50 -05:00
sadnub
f8dfd8edb3 Make pip copy the binaries versus symlink them in dev env 2021-01-03 20:15:40 -05:00
sadnub
042be624a3 Update .dockerignore 2021-01-03 15:16:13 -05:00
sadnub
6bafa4c79a fix mesh init on dev 2021-01-03 15:15:43 -05:00
wh1te909
58b42fac5c Release 0.2.20 2021-01-03 09:13:28 +00:00
wh1te909
3b47b9558a let python calculate default threadpool workers based on cpu count 2021-01-03 09:12:38 +00:00
74 changed files with 2015 additions and 112 deletions

View File

@@ -1,6 +1,6 @@
COMPOSE_PROJECT_NAME=trmm
IMAGE_REPO=tacticalrmm
IMAGE_REPO=tacticalrmm/
VERSION=latest
# tactical credentials (Used to login to dashboard)

View File

@@ -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:

View File

@@ -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

View File

@@ -21,4 +21,5 @@
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
**/env
README.md

19
.vscode/settings.json vendored
View File

@@ -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,
}
}

View File

@@ -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,
),
),
]

View File

@@ -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",

View File

@@ -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)

View File

@@ -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")

View File

@@ -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)

View File

@@ -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}")

View File

@@ -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)

View File

@@ -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"

View File

@@ -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)

View 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,
),
),
]

View 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",
),
),
],
),
]

View 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),
),
]

View 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),
),
]

View File

@@ -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 = []

View 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),
),
]

View File

@@ -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

View File

@@ -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")

View File

@@ -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"

View File

@@ -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)

View File

@@ -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()),
]

View File

@@ -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)

View File

@@ -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"

View File

@@ -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),
),
]

View File

@@ -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="")

View File

@@ -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)

View File

@@ -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,
}
)

View File

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class NatsapiConfig(AppConfig):
name = "natsapi"

View 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()),
]

View 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")

View File

@@ -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

View File

@@ -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"
}
]

View File

@@ -14,6 +14,7 @@ def get_debug_info():
EXCLUDE_PATHS = (
"/natsapi",
"/api/v3",
"/api/v2",
"/logs/auditlogs",

View File

@@ -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:

View File

@@ -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")),
]

View File

@@ -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
View 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
View 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=

View File

@@ -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
View 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
View 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
View File

@@ -0,0 +1,6 @@
package api
type NatsInfo struct {
User string `json:"user"`
Password string `json:"password"`
}

21
scripts/DiskStatus.ps1 Normal file
View 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

View 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
View 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

View 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
View 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

View 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'

View File

@@ -0,0 +1 @@
Start-Process -FilePath 'powercfg.exe' -ArgumentList '/setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'

49
scripts/Speedtest.ps1 Normal file
View 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
View File

@@ -0,0 +1,2 @@
REM Syncs time with domain controller
net time %logonserver% /set /y

View File

@@ -0,0 +1,2 @@
wevtutil cl "Microsoft-Windows-Windows Defender/Operational"
Write-Output "Logs are cleared and RMM status should be reset"

View 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

View 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

View File

@@ -0,0 +1,2 @@
REM Disable Faststartup on Windows 10
powercfg /h off

View 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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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;
},

View File

@@ -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"]),

View File

@@ -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",

View File

@@ -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;
}
},

View 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>

View File

@@ -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)

View File

@@ -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,

View 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>

View File

@@ -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: {

View File

@@ -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);
});
},