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