Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71e78bd0c5 | ||
|
|
4766477c58 | ||
|
|
d97e49ff2b | ||
|
|
6b9d775cb9 | ||
|
|
e521f580d7 | ||
|
|
bc6faf817f | ||
|
|
d46ae55863 | ||
|
|
bbd900ab25 | ||
|
|
129ae93e2b | ||
|
|
44dd59fa3f | ||
|
|
ec4e7559b0 | ||
|
|
dce40611cf | ||
|
|
e71b8546f9 | ||
|
|
f827348467 | ||
|
|
f3978343db | ||
|
|
2654a7ea70 | ||
|
|
1068bf4ef7 | ||
|
|
e7fccc97cc | ||
|
|
733e289852 | ||
|
|
29d71a104c | ||
|
|
05200420ad | ||
|
|
eb762d4bfd | ||
|
|
58ace9eda1 | ||
|
|
eeb2623be0 | ||
|
|
cfa242c2fe | ||
|
|
ec0441ccc2 | ||
|
|
ae2782a8fe | ||
|
|
58ff570251 | ||
|
|
7b554b12c7 | ||
|
|
58f7603d4f | ||
|
|
8895994c54 | ||
|
|
de8f7e36d5 | ||
|
|
88d7a50265 | ||
|
|
21e19fc7e5 | ||
|
|
faf4935a69 | ||
|
|
71a1f9d74a | ||
|
|
bd8d523e10 | ||
|
|
60cae0e3ac | ||
|
|
5a342ac012 | ||
|
|
bb8767dfc3 | ||
|
|
fcb2779c15 | ||
|
|
77dd6c1f61 | ||
|
|
8118eef300 | ||
|
|
802d1489fe | ||
|
|
443a029185 | ||
|
|
4ee508fdd0 | ||
|
|
aa5608f7e8 | ||
|
|
cc472b4613 | ||
|
|
764b945ddc | ||
|
|
fd2206ce4c | ||
|
|
48c0ac9f00 | ||
|
|
84eb4fe9ed | ||
|
|
4a5428812c | ||
|
|
023f98a89d | ||
|
|
66893dd0c1 | ||
|
|
25a6666e35 | ||
|
|
19d75309b5 | ||
|
|
11110d65c1 | ||
|
|
a348f58fe2 | ||
|
|
13851dd976 | ||
|
|
2ec37c5da9 | ||
|
|
8c127160de | ||
|
|
2af820de9a | ||
|
|
55fb0bb3a0 | ||
|
|
9f9ecc521f | ||
|
|
dfd01df5ba | ||
|
|
474090698c | ||
|
|
6b71cdeea4 | ||
|
|
581e974236 | ||
|
|
ba3c3a42ce | ||
|
|
c8bc5671c5 | ||
|
|
ff9401a040 | ||
|
|
5e1bc1989f | ||
|
|
a1dc91cd7d | ||
|
|
99f2772bb3 | ||
|
|
e5d0e42655 | ||
|
|
2c914cc374 | ||
|
|
9bceb62381 | ||
|
|
de7518a800 | ||
|
|
304fb63453 | ||
|
|
0f7ef60ca0 | ||
|
|
07c74e4641 | ||
|
|
de7f325cfb | ||
|
|
42cdf70cb4 | ||
|
|
6beb6be131 | ||
|
|
fa4fc2a708 | ||
|
|
2db9758260 | ||
|
|
715982e40a | ||
|
|
d00cd4453a | ||
|
|
429c08c24a | ||
|
|
6a71490e20 | ||
|
|
9bceda0646 | ||
|
|
a1027a6773 | ||
|
|
302d4b75f9 | ||
|
|
5f6ee0e883 | ||
|
|
27f9720de1 | ||
|
|
22aa3fdbbc | ||
|
|
069ecdd33f | ||
|
|
dd545ae933 | ||
|
|
6650b705c4 | ||
|
|
59b0350289 | ||
|
|
1ad159f820 | ||
|
|
0bf42190e9 | ||
|
|
d2fa836232 | ||
|
|
c387774093 | ||
|
|
e99736ba3c | ||
|
|
16cb54fcc9 |
@@ -26,3 +26,6 @@ POSTGRES_PASS=postgrespass
|
||||
APP_PORT=80
|
||||
API_PORT=80
|
||||
HTTP_PROTOCOL=https
|
||||
DOCKER_NETWORK="172.21.0.0/24"
|
||||
DOCKER_NGINX_IP="172.21.0.20"
|
||||
NATS_PORTS="4222:4222"
|
||||
|
||||
@@ -46,7 +46,7 @@ services:
|
||||
API_PORT: ${API_PORT}
|
||||
DEV: 1
|
||||
ports:
|
||||
- "4222:4222"
|
||||
- "${NATS_PORTS}"
|
||||
volumes:
|
||||
- tactical-data-dev:/opt/tactical
|
||||
- ..:/workspace:cached
|
||||
@@ -67,7 +67,7 @@ services:
|
||||
MESH_PASS: ${MESH_PASS}
|
||||
MONGODB_USER: ${MONGODB_USER}
|
||||
MONGODB_PASSWORD: ${MONGODB_PASSWORD}
|
||||
NGINX_HOST_IP: 172.21.0.20
|
||||
NGINX_HOST_IP: ${DOCKER_NGINX_IP}
|
||||
networks:
|
||||
dev:
|
||||
aliases:
|
||||
@@ -115,7 +115,10 @@ services:
|
||||
redis-dev:
|
||||
container_name: trmm-redis-dev
|
||||
restart: always
|
||||
command: redis-server --appendonly yes
|
||||
image: redis:6.0-alpine
|
||||
volumes:
|
||||
- redis-data-dev:/data
|
||||
networks:
|
||||
dev:
|
||||
aliases:
|
||||
@@ -220,7 +223,7 @@ services:
|
||||
API_PORT: ${API_PORT}
|
||||
networks:
|
||||
dev:
|
||||
ipv4_address: 172.21.0.20
|
||||
ipv4_address: ${DOCKER_NGINX_IP}
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
@@ -247,6 +250,7 @@ volumes:
|
||||
postgres-data-dev:
|
||||
mongo-dev-data:
|
||||
mesh-data-dev:
|
||||
redis-data-dev:
|
||||
|
||||
networks:
|
||||
dev:
|
||||
@@ -254,4 +258,4 @@ networks:
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.21.0.0/24
|
||||
- subnet: ${DOCKER_NETWORK}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.4 on 2021-06-17 04:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0020_role_can_manage_roles'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='role',
|
||||
name='can_view_core_settings',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.4 on 2021-06-28 05:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0021_role_can_view_core_settings'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='clear_search_when_switching',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
@@ -46,6 +46,7 @@ class User(AbstractUser, BaseAuditModel):
|
||||
)
|
||||
client_tree_splitter = models.PositiveIntegerField(default=11)
|
||||
loading_bar_color = models.CharField(max_length=255, default="red")
|
||||
clear_search_when_switching = models.BooleanField(default=True)
|
||||
|
||||
agent = models.OneToOneField(
|
||||
"agents.Agent",
|
||||
@@ -90,6 +91,7 @@ class Role(models.Model):
|
||||
|
||||
# core
|
||||
can_manage_notes = models.BooleanField(default=False)
|
||||
can_view_core_settings = models.BooleanField(default=False)
|
||||
can_edit_core_settings = models.BooleanField(default=False)
|
||||
can_do_server_maint = models.BooleanField(default=False)
|
||||
can_code_sign = models.BooleanField(default=False)
|
||||
@@ -153,6 +155,7 @@ class Role(models.Model):
|
||||
"can_run_scripts",
|
||||
"can_run_bulk",
|
||||
"can_manage_notes",
|
||||
"can_view_core_settings",
|
||||
"can_edit_core_settings",
|
||||
"can_do_server_maint",
|
||||
"can_code_sign",
|
||||
|
||||
@@ -16,6 +16,7 @@ class UserUISerializer(ModelSerializer):
|
||||
"client_tree_sort",
|
||||
"client_tree_splitter",
|
||||
"loading_bar_color",
|
||||
"clear_search_when_switching",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -280,6 +280,7 @@ class TestUserAction(TacticalTestCase):
|
||||
"client_tree_sort": "alpha",
|
||||
"client_tree_splitter": 14,
|
||||
"loading_bar_color": "green",
|
||||
"clear_search_when_switching": False,
|
||||
}
|
||||
r = self.client.patch(url, data, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
23
api/tacticalrmm/agents/migrations/0037_auto_20210627_0014.py
Normal file
23
api/tacticalrmm/agents/migrations/0037_auto_20210627_0014.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.4 on 2021-06-27 00:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0036_agent_block_policy_inheritance'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='agent',
|
||||
name='has_patches_pending',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='agent',
|
||||
name='pending_actions_count',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -64,6 +64,8 @@ class Agent(BaseAuditModel):
|
||||
)
|
||||
maintenance_mode = models.BooleanField(default=False)
|
||||
block_policy_inheritance = models.BooleanField(default=False)
|
||||
pending_actions_count = models.PositiveIntegerField(default=0)
|
||||
has_patches_pending = models.BooleanField(default=False)
|
||||
alert_template = models.ForeignKey(
|
||||
"alerts.AlertTemplate",
|
||||
related_name="agents",
|
||||
@@ -95,10 +97,12 @@ class Agent(BaseAuditModel):
|
||||
# check if new agent has been created
|
||||
# or check if policy have changed on agent
|
||||
# or if site has changed on agent and if so generate-policies
|
||||
# or if agent was changed from server or workstation
|
||||
if (
|
||||
not old_agent
|
||||
or (old_agent and old_agent.policy != self.policy)
|
||||
or (old_agent.site != self.site)
|
||||
or (old_agent.monitoring_type != self.monitoring_type)
|
||||
or (old_agent.block_policy_inheritance != self.block_policy_inheritance)
|
||||
):
|
||||
self.generate_checks_from_policies()
|
||||
@@ -161,10 +165,6 @@ class Agent(BaseAuditModel):
|
||||
else:
|
||||
return "offline"
|
||||
|
||||
@property
|
||||
def has_patches_pending(self):
|
||||
return self.winupdates.filter(action="approve").filter(installed=False).exists() # type: ignore
|
||||
|
||||
@property
|
||||
def checks(self):
|
||||
total, passing, failing, warning, info = 0, 0, 0, 0, 0
|
||||
|
||||
@@ -9,7 +9,6 @@ from .models import Agent, AgentCustomField, Note
|
||||
|
||||
class AgentSerializer(serializers.ModelSerializer):
|
||||
# for vue
|
||||
patches_pending = serializers.ReadOnlyField(source="has_patches_pending")
|
||||
winupdatepolicy = WinUpdatePolicySerializer(many=True, read_only=True)
|
||||
status = serializers.ReadOnlyField()
|
||||
cpu_model = serializers.ReadOnlyField()
|
||||
@@ -45,8 +44,6 @@ class AgentOverdueActionSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class AgentTableSerializer(serializers.ModelSerializer):
|
||||
patches_pending = serializers.ReadOnlyField(source="has_patches_pending")
|
||||
pending_actions = serializers.SerializerMethodField()
|
||||
status = serializers.ReadOnlyField()
|
||||
checks = serializers.ReadOnlyField()
|
||||
last_seen = serializers.SerializerMethodField()
|
||||
@@ -69,9 +66,6 @@ class AgentTableSerializer(serializers.ModelSerializer):
|
||||
"always_alert": obj.alert_template.agent_always_alert,
|
||||
}
|
||||
|
||||
def get_pending_actions(self, obj):
|
||||
return obj.pendingactions.filter(status="pending").count()
|
||||
|
||||
def get_last_seen(self, obj) -> str:
|
||||
if obj.time_zone is not None:
|
||||
agent_tz = pytz.timezone(obj.time_zone)
|
||||
@@ -103,8 +97,8 @@ class AgentTableSerializer(serializers.ModelSerializer):
|
||||
"monitoring_type",
|
||||
"description",
|
||||
"needs_reboot",
|
||||
"patches_pending",
|
||||
"pending_actions",
|
||||
"has_patches_pending",
|
||||
"pending_actions_count",
|
||||
"status",
|
||||
"overdue_text_alert",
|
||||
"overdue_email_alert",
|
||||
@@ -173,11 +167,6 @@ class AgentEditSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class WinAgentSerializer(serializers.ModelSerializer):
|
||||
# for the windows agent
|
||||
patches_pending = serializers.ReadOnlyField(source="has_patches_pending")
|
||||
winupdatepolicy = WinUpdatePolicySerializer(many=True, read_only=True)
|
||||
status = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = Agent
|
||||
fields = "__all__"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import asyncio
|
||||
import datetime as dt
|
||||
import random
|
||||
import tempfile
|
||||
import json
|
||||
import subprocess
|
||||
import urllib.parse
|
||||
from time import sleep
|
||||
from typing import Union
|
||||
@@ -322,4 +325,23 @@ def get_wmi_task() -> None:
|
||||
"pk", "agent_id", "last_seen", "overdue_time", "offline_time"
|
||||
)
|
||||
ids = [i.agent_id for i in agents if i.status == "online"]
|
||||
run_nats_api_cmd("wmi", ids)
|
||||
run_nats_api_cmd("wmi", ids, timeout=45)
|
||||
|
||||
|
||||
@app.task
|
||||
def agent_checkin_task() -> None:
|
||||
db = settings.DATABASES["default"]
|
||||
config = {
|
||||
"key": settings.SECRET_KEY,
|
||||
"natsurl": f"tls://{settings.ALLOWED_HOSTS[0]}:4222",
|
||||
"user": db["USER"],
|
||||
"pass": db["PASSWORD"],
|
||||
"host": db["HOST"],
|
||||
"port": int(db["PORT"]),
|
||||
"dbname": db["NAME"],
|
||||
}
|
||||
with tempfile.NamedTemporaryFile() as fp:
|
||||
with open(fp.name, "w") as f:
|
||||
json.dump(config, f)
|
||||
cmd = ["/usr/local/bin/nats-api", "-c", fp.name, "-m", "checkin"]
|
||||
subprocess.run(cmd, timeout=30)
|
||||
|
||||
@@ -302,6 +302,8 @@ class AgentsTableList(APIView):
|
||||
"last_logged_in_user",
|
||||
"time_zone",
|
||||
"maintenance_mode",
|
||||
"pending_actions_count",
|
||||
"has_patches_pending",
|
||||
)
|
||||
ctx = {"default_tz": get_default_timezone()}
|
||||
serializer = AgentTableSerializer(queryset, many=True, context=ctx)
|
||||
|
||||
@@ -83,6 +83,7 @@ class PolicyCheckSerializer(ModelSerializer):
|
||||
class AutoTasksFieldSerializer(ModelSerializer):
|
||||
assigned_check = PolicyCheckSerializer(read_only=True)
|
||||
script = ReadOnlyField(source="script.id")
|
||||
custom_field = ReadOnlyField(source="custom_field.id")
|
||||
|
||||
class Meta:
|
||||
model = AutomatedTask
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Any, Dict, List, Union
|
||||
from tacticalrmm.celery import app
|
||||
|
||||
|
||||
@app.task
|
||||
@app.task(retry_backoff=5, retry_jitter=True, retry_kwargs={"max_retries": 5})
|
||||
def generate_agent_checks_task(
|
||||
policy: int = None,
|
||||
site: int = None,
|
||||
@@ -57,7 +57,9 @@ def generate_agent_checks_task(
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.task
|
||||
@app.task(
|
||||
acks_late=True, retry_backoff=5, retry_jitter=True, retry_kwargs={"max_retries": 5}
|
||||
)
|
||||
# updates policy managed check fields on agents
|
||||
def update_policy_check_fields_task(check: int) -> str:
|
||||
from checks.models import Check
|
||||
@@ -73,7 +75,7 @@ def update_policy_check_fields_task(check: int) -> str:
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.task
|
||||
@app.task(retry_backoff=5, retry_jitter=True, retry_kwargs={"max_retries": 5})
|
||||
# generates policy tasks on agents affected by a policy
|
||||
def generate_agent_autotasks_task(policy: int = None) -> str:
|
||||
from agents.models import Agent
|
||||
@@ -100,7 +102,12 @@ def generate_agent_autotasks_task(policy: int = None) -> str:
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.task
|
||||
@app.task(
|
||||
acks_late=True,
|
||||
retry_backoff=5,
|
||||
retry_jitter=True,
|
||||
retry_kwargs={"max_retries": 5},
|
||||
)
|
||||
def delete_policy_autotasks_task(task: int) -> str:
|
||||
from autotasks.models import AutomatedTask
|
||||
|
||||
@@ -120,7 +127,12 @@ def run_win_policy_autotasks_task(task: int) -> str:
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.task
|
||||
@app.task(
|
||||
acks_late=True,
|
||||
retry_backoff=5,
|
||||
retry_jitter=True,
|
||||
retry_kwargs={"max_retries": 5},
|
||||
)
|
||||
def update_policy_autotasks_fields_task(task: int, update_agent: bool = False) -> str:
|
||||
from autotasks.models import AutomatedTask
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.conf import settings
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
from django.db.models.fields import DateTimeField
|
||||
from django.db.utils import DatabaseError
|
||||
from django.utils import timezone as djangotime
|
||||
from logs.models import BaseAuditModel
|
||||
from loguru import logger
|
||||
@@ -183,6 +184,7 @@ class AutomatedTask(BaseAuditModel):
|
||||
"remove_if_not_scheduled",
|
||||
"run_asap_after_missed",
|
||||
"custom_field",
|
||||
"collector_all_output",
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@@ -364,9 +366,14 @@ class AutomatedTask(BaseAuditModel):
|
||||
|
||||
if r != "ok" and "The system cannot find the file specified" not in r:
|
||||
self.sync_status = "pendingdeletion"
|
||||
self.save(update_fields=["sync_status"])
|
||||
|
||||
try:
|
||||
self.save(update_fields=["sync_status"])
|
||||
except DatabaseError:
|
||||
pass
|
||||
|
||||
logger.warning(
|
||||
f"{agent.hostname} task {self.name} was successfully modified"
|
||||
f"{agent.hostname} task {self.name} will be deleted on next checkin"
|
||||
)
|
||||
return "timeout"
|
||||
else:
|
||||
@@ -408,7 +415,7 @@ class AutomatedTask(BaseAuditModel):
|
||||
CORE = CoreSettings.objects.first()
|
||||
|
||||
if self.agent:
|
||||
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Failed"
|
||||
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self.agent.hostname} - {self} Failed"
|
||||
else:
|
||||
subject = f"{self} Failed"
|
||||
|
||||
@@ -426,7 +433,7 @@ class AutomatedTask(BaseAuditModel):
|
||||
CORE = CoreSettings.objects.first()
|
||||
|
||||
if self.agent:
|
||||
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Failed"
|
||||
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self.agent.hostname} - {self} Failed"
|
||||
else:
|
||||
subject = f"{self} Failed"
|
||||
|
||||
|
||||
22
api/tacticalrmm/checks/migrations/0024_auto_20210606_1632.py
Normal file
22
api/tacticalrmm/checks/migrations/0024_auto_20210606_1632.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 3.2.1 on 2021-06-06 16:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('checks', '0023_check_run_interval'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='checkhistory',
|
||||
name='check_history',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='checkhistory',
|
||||
name='check_id',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -313,7 +313,7 @@ class Check(BaseAuditModel):
|
||||
)
|
||||
|
||||
def add_check_history(self, value: int, more_info: Any = None) -> None:
|
||||
CheckHistory.objects.create(check_history=self, y=value, results=more_info)
|
||||
CheckHistory.objects.create(check_id=self.pk, y=value, results=more_info)
|
||||
|
||||
def handle_check(self, data):
|
||||
from alerts.models import Alert
|
||||
@@ -509,7 +509,12 @@ class Check(BaseAuditModel):
|
||||
)
|
||||
|
||||
for task in self.assignedtask.all(): # type: ignore
|
||||
task.create_policy_task(agent=agent, policy=policy, assigned_check=check)
|
||||
if policy or (
|
||||
agent and not agent.autotasks.filter(parent_task=task.pk).exists()
|
||||
):
|
||||
task.create_policy_task(
|
||||
agent=agent, policy=policy, assigned_check=check
|
||||
)
|
||||
|
||||
for field in self.policy_fields_to_copy:
|
||||
setattr(check, field, getattr(self, field))
|
||||
@@ -683,14 +688,10 @@ class Check(BaseAuditModel):
|
||||
|
||||
|
||||
class CheckHistory(models.Model):
|
||||
check_history = models.ForeignKey(
|
||||
Check,
|
||||
related_name="check_history",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
check_id = models.PositiveIntegerField(default=0)
|
||||
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
|
||||
return self.x
|
||||
|
||||
@@ -6,6 +6,7 @@ from autotasks.models import AutomatedTask
|
||||
from scripts.serializers import ScriptCheckSerializer, ScriptSerializer
|
||||
|
||||
from .models import Check, CheckHistory
|
||||
from scripts.models import Script
|
||||
|
||||
|
||||
class AssignedTaskField(serializers.ModelSerializer):
|
||||
@@ -159,6 +160,15 @@ class AssignedTaskCheckRunnerField(serializers.ModelSerializer):
|
||||
class CheckRunnerGetSerializer(serializers.ModelSerializer):
|
||||
# only send data needed for agent to run a check
|
||||
script = ScriptCheckSerializer(read_only=True)
|
||||
script_args = serializers.SerializerMethodField()
|
||||
|
||||
def get_script_args(self, obj):
|
||||
if obj.check_type != "script":
|
||||
return []
|
||||
|
||||
return Script.parse_script_args(
|
||||
agent=obj.agent, shell=obj.script.shell, args=obj.script_args
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Check
|
||||
|
||||
@@ -363,10 +363,10 @@ class TestCheckViews(TacticalTestCase):
|
||||
# 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)
|
||||
baker.make("checks.CheckHistory", check_id=check.id, _quantity=30)
|
||||
check_history_data = baker.make(
|
||||
"checks.CheckHistory",
|
||||
check_history=check,
|
||||
check_id=check.id,
|
||||
_quantity=30,
|
||||
)
|
||||
|
||||
@@ -407,10 +407,10 @@ class TestCheckTasks(TacticalTestCase):
|
||||
|
||||
# setup data
|
||||
check = baker.make_recipe("checks.diskspace_check")
|
||||
baker.make("checks.CheckHistory", check_history=check, _quantity=30)
|
||||
baker.make("checks.CheckHistory", check_id=check.id, _quantity=30)
|
||||
check_history_data = baker.make(
|
||||
"checks.CheckHistory",
|
||||
check_history=check,
|
||||
check_id=check.id,
|
||||
_quantity=30,
|
||||
)
|
||||
|
||||
|
||||
@@ -8,5 +8,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()),
|
||||
path("history/<int:checkpk>/", views.GetCheckHistory.as_view()),
|
||||
]
|
||||
|
||||
@@ -15,7 +15,7 @@ from automation.models import Policy
|
||||
from scripts.models import Script
|
||||
from tacticalrmm.utils import notify_error
|
||||
|
||||
from .models import Check
|
||||
from .models import Check, CheckHistory
|
||||
from .permissions import ManageChecksPerms, RunChecksPerms
|
||||
from .serializers import CheckHistorySerializer, CheckSerializer
|
||||
|
||||
@@ -146,7 +146,7 @@ class GetUpdateDeleteCheck(APIView):
|
||||
return Response(f"{check.readable_desc} was deleted!")
|
||||
|
||||
|
||||
class CheckHistory(APIView):
|
||||
class GetCheckHistory(APIView):
|
||||
def patch(self, request, checkpk):
|
||||
check = get_object_or_404(Check, pk=checkpk)
|
||||
|
||||
@@ -160,7 +160,7 @@ class CheckHistory(APIView):
|
||||
- djangotime.timedelta(days=request.data["timeFilter"]),
|
||||
)
|
||||
|
||||
check_history = check.check_history.filter(timeFilter).order_by("-x") # type: ignore
|
||||
check_history = CheckHistory.objects.filter(check_id=checkpk).filter(timeFilter).order_by("-x") # type: ignore
|
||||
|
||||
return Response(
|
||||
CheckHistorySerializer(
|
||||
|
||||
@@ -87,12 +87,20 @@ class Client(BaseAuditModel):
|
||||
"offline_time",
|
||||
)
|
||||
.filter(site__client=self)
|
||||
.prefetch_related("agentchecks")
|
||||
.prefetch_related("agentchecks", "autotasks")
|
||||
)
|
||||
|
||||
data = {"error": False, "warning": False}
|
||||
|
||||
for agent in agents:
|
||||
if agent.maintenance_mode:
|
||||
break
|
||||
|
||||
if agent.overdue_email_alert or agent.overdue_text_alert:
|
||||
if agent.status == "overdue":
|
||||
data["error"] = True
|
||||
break
|
||||
|
||||
if agent.checks["has_failing_checks"]:
|
||||
|
||||
if agent.checks["warning"]:
|
||||
@@ -102,10 +110,11 @@ class Client(BaseAuditModel):
|
||||
data["error"] = True
|
||||
break
|
||||
|
||||
if agent.overdue_email_alert or agent.overdue_text_alert:
|
||||
if agent.status == "overdue":
|
||||
data["error"] = True
|
||||
break
|
||||
if agent.autotasks.exists(): # type: ignore
|
||||
for i in agent.autotasks.all(): # type: ignore
|
||||
if i.status == "failing" and i.alert_severity == "error":
|
||||
data["error"] = True
|
||||
break
|
||||
|
||||
return data
|
||||
|
||||
@@ -192,12 +201,19 @@ class Site(BaseAuditModel):
|
||||
"offline_time",
|
||||
)
|
||||
.filter(site=self)
|
||||
.prefetch_related("agentchecks")
|
||||
.prefetch_related("agentchecks", "autotasks")
|
||||
)
|
||||
|
||||
data = {"error": False, "warning": False}
|
||||
|
||||
for agent in agents:
|
||||
if agent.maintenance_mode:
|
||||
break
|
||||
|
||||
if agent.overdue_email_alert or agent.overdue_text_alert:
|
||||
if agent.status == "overdue":
|
||||
data["error"] = True
|
||||
break
|
||||
|
||||
if agent.checks["has_failing_checks"]:
|
||||
if agent.checks["warning"]:
|
||||
@@ -207,10 +223,11 @@ class Site(BaseAuditModel):
|
||||
data["error"] = True
|
||||
break
|
||||
|
||||
if agent.overdue_email_alert or agent.overdue_text_alert:
|
||||
if agent.status == "overdue":
|
||||
data["error"] = True
|
||||
break
|
||||
if agent.autotasks.exists(): # type: ignore
|
||||
for i in agent.autotasks.all(): # type: ignore
|
||||
if i.status == "failing" and i.alert_severity == "error":
|
||||
data["error"] = True
|
||||
break
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -53,9 +53,9 @@ If (Get-Service $serviceName -ErrorAction SilentlyContinue) {
|
||||
Write-Output "Waiting for network"
|
||||
Start-Sleep -s 5
|
||||
$X += 1
|
||||
} until(($connectreult = Test-NetConnection $apilink[2] -Port 443 | ? { $_.TcpTestSucceeded }) -or $X -eq 3)
|
||||
} until(($connectresult = Test-NetConnection $apilink[2] -Port 443 | ? { $_.TcpTestSucceeded }) -or $X -eq 3)
|
||||
|
||||
if ($connectreult.TcpTestSucceeded -eq $true){
|
||||
if ($connectresult.TcpTestSucceeded -eq $true){
|
||||
Try
|
||||
{
|
||||
Invoke-WebRequest -Uri $downloadlink -OutFile $OutPath\$output
|
||||
|
||||
@@ -3,6 +3,11 @@ from rest_framework import permissions
|
||||
from tacticalrmm.permissions import _has_perm
|
||||
|
||||
|
||||
class ViewCoreSettingsPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
return _has_perm(r, "can_view_core_settings")
|
||||
|
||||
|
||||
class EditCoreSettingsPerms(permissions.BasePermission):
|
||||
def has_permission(self, r, view):
|
||||
return _has_perm(r, "can_edit_core_settings")
|
||||
|
||||
@@ -37,3 +37,17 @@ def core_maintenance_tasks():
|
||||
# clear faults
|
||||
if core.clear_faults_days > 0:
|
||||
clear_faults_task.delay(core.clear_faults_days)
|
||||
|
||||
|
||||
@app.task
|
||||
def cache_db_fields_task():
|
||||
from agents.models import Agent
|
||||
|
||||
for agent in Agent.objects.all():
|
||||
agent.pending_actions_count = agent.pendingactions.filter(
|
||||
status="pending"
|
||||
).count()
|
||||
agent.has_patches_pending = (
|
||||
agent.winupdates.filter(action="approve").filter(installed=False).exists()
|
||||
)
|
||||
agent.save(update_fields=["pending_actions_count", "has_patches_pending"])
|
||||
|
||||
@@ -18,4 +18,5 @@ urlpatterns = [
|
||||
path("urlaction/", views.GetAddURLAction.as_view()),
|
||||
path("urlaction/<int:pk>/", views.UpdateDeleteURLAction.as_view()),
|
||||
path("urlaction/run/", views.RunURLAction.as_view()),
|
||||
path("smstest/", views.TwilioSMSTest.as_view()),
|
||||
]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import pprint
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
@@ -15,7 +16,12 @@ from agents.permissions import MeshPerms
|
||||
from tacticalrmm.utils import notify_error
|
||||
|
||||
from .models import CodeSignToken, CoreSettings, CustomField, GlobalKVStore, URLAction
|
||||
from .permissions import CodeSignPerms, EditCoreSettingsPerms, ServerMaintPerms
|
||||
from .permissions import (
|
||||
CodeSignPerms,
|
||||
ViewCoreSettingsPerms,
|
||||
EditCoreSettingsPerms,
|
||||
ServerMaintPerms,
|
||||
)
|
||||
from .serializers import (
|
||||
CodeSignTokenSerializer,
|
||||
CoreSettingsSerializer,
|
||||
@@ -46,6 +52,7 @@ class UploadMeshAgent(APIView):
|
||||
|
||||
|
||||
@api_view()
|
||||
@permission_classes([IsAuthenticated, ViewCoreSettingsPerms])
|
||||
def get_core_settings(request):
|
||||
settings = CoreSettings.objects.first()
|
||||
return Response(CoreSettingsSerializer(settings).data)
|
||||
@@ -85,6 +92,7 @@ def dashboard_info(request):
|
||||
"client_tree_sort": request.user.client_tree_sort,
|
||||
"client_tree_splitter": request.user.client_tree_splitter,
|
||||
"loading_bar_color": request.user.loading_bar_color,
|
||||
"clear_search_when_switching": request.user.clear_search_when_switching,
|
||||
"hosted": hasattr(settings, "HOSTED") and settings.HOSTED,
|
||||
}
|
||||
)
|
||||
@@ -353,3 +361,26 @@ class RunURLAction(APIView):
|
||||
url_pattern = re.sub("\\{\\{" + string + "\\}\\}", str(value), url_pattern)
|
||||
|
||||
return Response(requote_uri(url_pattern))
|
||||
|
||||
|
||||
class TwilioSMSTest(APIView):
|
||||
def get(self, request):
|
||||
from twilio.rest import Client as TwClient
|
||||
|
||||
core = CoreSettings.objects.first()
|
||||
if not core.sms_is_configured:
|
||||
return notify_error(
|
||||
"All fields are required, including at least 1 recipient"
|
||||
)
|
||||
|
||||
try:
|
||||
tw_client = TwClient(core.twilio_account_sid, core.twilio_auth_token)
|
||||
tw_client.messages.create(
|
||||
body="TacticalRMM Test SMS",
|
||||
to=core.sms_alert_recipients[0],
|
||||
from_=core.twilio_number,
|
||||
)
|
||||
except Exception as e:
|
||||
return notify_error(pprint.pformat(e))
|
||||
|
||||
return Response("SMS Test OK!")
|
||||
|
||||
@@ -6,4 +6,6 @@ mkdocs-material
|
||||
pymdown-extensions
|
||||
Pygments
|
||||
isort
|
||||
mypy
|
||||
mypy
|
||||
types-pytz
|
||||
types-pytz
|
||||
@@ -1,14 +1,14 @@
|
||||
asgiref==3.3.4
|
||||
asyncio-nats-client==0.11.4
|
||||
celery==5.1.0
|
||||
certifi==2020.12.5
|
||||
celery==5.1.1
|
||||
certifi==2021.5.30
|
||||
cffi==1.14.5
|
||||
channels==3.0.3
|
||||
channels_redis==3.2.0
|
||||
chardet==4.0.0
|
||||
cryptography==3.4.7
|
||||
daphne==3.0.2
|
||||
Django==3.2.3
|
||||
Django==3.2.4
|
||||
django-cors-headers==3.7.0
|
||||
django-rest-knox==4.1.0
|
||||
djangorestframework==3.12.4
|
||||
@@ -16,7 +16,7 @@ future==0.18.2
|
||||
loguru==0.5.3
|
||||
msgpack==1.0.2
|
||||
packaging==20.9
|
||||
psycopg2-binary==2.8.6
|
||||
psycopg2-binary==2.9.1
|
||||
pycparser==2.20
|
||||
pycryptodome==3.10.1
|
||||
pyotp==2.6.0
|
||||
@@ -27,10 +27,10 @@ redis==3.5.3
|
||||
requests==2.25.1
|
||||
six==1.16.0
|
||||
sqlparse==0.4.1
|
||||
twilio==6.59.0
|
||||
twilio==6.60.0
|
||||
urllib3==1.26.5
|
||||
uWSGI==2.0.19.1
|
||||
validators==0.18.2
|
||||
vine==5.0.0
|
||||
websockets==8.1
|
||||
websockets==9.1
|
||||
zipp==3.4.1
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Browsers",
|
||||
"default_timeout": "300"
|
||||
|
||||
},
|
||||
{
|
||||
"guid": "3ff6a386-11d1-4f9d-8cca-1b0563bb6443",
|
||||
@@ -38,7 +37,7 @@
|
||||
"description": "This script installs Duplicati 2.0.5.1 as a service.",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):3rd Party Software",
|
||||
"default_timeout": "300"
|
||||
"default_timeout": "300"
|
||||
},
|
||||
{
|
||||
"guid": "81cc5bcb-01bf-4b0c-89b9-0ac0f3fe0c04",
|
||||
@@ -48,7 +47,7 @@
|
||||
"description": "This script will reset all of the Windows Updates components to DEFAULT SETTINGS.",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Updates",
|
||||
"default_timeout": "300"
|
||||
"default_timeout": "300"
|
||||
},
|
||||
{
|
||||
"guid": "8db87ff0-a9b4-4d9d-bc55-377bbcb85b6d",
|
||||
@@ -58,7 +57,7 @@
|
||||
"description": "Cleans the C: drive's Window Temperary files, Windows SoftwareDistribution folder, the local users Temperary folder, IIS logs (if applicable) and empties the recycling bin. All deleted files will go into a log transcript in $env:TEMP. By default this script leaves files that are newer than 7 days old however this variable can be edited.",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Maintenance",
|
||||
"default_timeout": "25000"
|
||||
"default_timeout": "25000"
|
||||
},
|
||||
{
|
||||
"guid": "2f28e8c1-ae0f-4b46-a826-f513974526a3",
|
||||
@@ -273,6 +272,20 @@
|
||||
"shell": "cmd",
|
||||
"category": "TRMM (Win):Active Directory"
|
||||
},
|
||||
{
|
||||
"guid": "5320dfc8-022a-41e7-9e39-11c493545ec9",
|
||||
"filename": "Win_AD_Hudu_ADDS_Documentation.ps1",
|
||||
"submittedBy": "https://github.com/unplugged216",
|
||||
"name": "ADDS - Direcotry documentation in Hudu",
|
||||
"description": "Auto generates ADDS documentation and submits it to your Hudu instance.",
|
||||
"args": [
|
||||
"-ClientName {{client.name}}",
|
||||
"-HuduBaseDomain {{global.HuduBaseDomain}}",
|
||||
"-HuduApiKey {{global.HuduApiKey}}"
|
||||
],
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Active Directory"
|
||||
},
|
||||
{
|
||||
"guid": "b6b9912f-4274-4162-99cc-9fd47fbcb292",
|
||||
"filename": "Win_ADDC_Sync_Start.bat",
|
||||
@@ -626,9 +639,18 @@
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Storage"
|
||||
},
|
||||
{
|
||||
"guid": "6a52f495-d43e-40f4-91a9-bbe4f578e6d1",
|
||||
"filename": "Win_User_Create.ps1",
|
||||
"submittedBy": "https://github.com/brodur",
|
||||
"name": "Create Local User",
|
||||
"description": "Create a local user. Parameters are: username, password and optional: description, fullname, group (adds to Users if not specified)",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Other"
|
||||
},
|
||||
{
|
||||
"guid": "57997ec7-b293-4fd5-9f90-a25426d0eb90",
|
||||
"filename": "Win_Get_Computer_Users.ps1",
|
||||
"filename": "Win_Users_List.ps1",
|
||||
"submittedBy": "https://github.com/tremor021",
|
||||
"name": "Get Computer Users",
|
||||
"description": "Get list of computer users and show which one is enabled",
|
||||
@@ -682,5 +704,25 @@
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Misc>Reference",
|
||||
"default_timeout": "1"
|
||||
},
|
||||
{
|
||||
"guid": "453c6d22-84b7-4767-8b5f-b825f233cf55",
|
||||
"filename": "Win_AD_Join_Computer.ps1",
|
||||
"submittedBy": "https://github.com/rfost52",
|
||||
"name": "AD - Join Computer to Domain",
|
||||
"description": "Join computer to a domain in Active Directory",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Active Directory",
|
||||
"default_timeout": "300"
|
||||
},
|
||||
{
|
||||
"guid": "962d3cce-49a2-4f3e-a790-36f62a6799a0",
|
||||
"filename": "Win_Collect_System_Report_And_Email.ps1",
|
||||
"submittedBy": "https://github.com/rfost52",
|
||||
"name": "Collect System Report and Email",
|
||||
"description": "Generates a system report in HTML format, then emails it",
|
||||
"shell": "powershell",
|
||||
"category": "TRMM (Win):Other",
|
||||
"default_timeout": "300"
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -22,14 +22,5 @@ def handle_bulk_command_task(agentpks, cmd, shell, timeout) -> None:
|
||||
@app.task
|
||||
def handle_bulk_script_task(scriptpk, agentpks, args, timeout) -> None:
|
||||
script = Script.objects.get(pk=scriptpk)
|
||||
nats_data = {
|
||||
"func": "runscript",
|
||||
"timeout": timeout,
|
||||
"script_args": args,
|
||||
"payload": {
|
||||
"code": script.code,
|
||||
"shell": script.shell,
|
||||
},
|
||||
}
|
||||
for agent in Agent.objects.filter(pk__in=agentpks):
|
||||
asyncio.run(agent.nats_cmd(nats_data, wait=False))
|
||||
agent.run_script(scriptpk=script.pk, args=args, timeout=timeout)
|
||||
|
||||
@@ -54,10 +54,12 @@ def debug_task(self):
|
||||
@app.on_after_finalize.connect
|
||||
def setup_periodic_tasks(sender, **kwargs):
|
||||
|
||||
from agents.tasks import agent_outages_task
|
||||
from agents.tasks import agent_outages_task, agent_checkin_task
|
||||
from alerts.tasks import unsnooze_alerts
|
||||
from core.tasks import core_maintenance_tasks
|
||||
from core.tasks import core_maintenance_tasks, cache_db_fields_task
|
||||
|
||||
sender.add_periodic_task(45.0, agent_checkin_task.s())
|
||||
sender.add_periodic_task(60.0, agent_outages_task.s())
|
||||
sender.add_periodic_task(60.0 * 30, core_maintenance_tasks.s())
|
||||
sender.add_periodic_task(60.0 * 60, unsnooze_alerts.s())
|
||||
sender.add_periodic_task(90.0, cache_db_fields_task.s())
|
||||
|
||||
@@ -15,20 +15,20 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe")
|
||||
AUTH_USER_MODEL = "accounts.User"
|
||||
|
||||
# latest release
|
||||
TRMM_VERSION = "0.6.13"
|
||||
TRMM_VERSION = "0.7.1"
|
||||
|
||||
# bump this version everytime vue code is changed
|
||||
# to alert user they need to manually refresh their browser
|
||||
APP_VER = "0.0.137"
|
||||
APP_VER = "0.0.140"
|
||||
|
||||
# https://github.com/wh1te909/rmmagent
|
||||
LATEST_AGENT_VER = "1.5.7"
|
||||
LATEST_AGENT_VER = "1.5.9"
|
||||
|
||||
MESH_VER = "0.8.49"
|
||||
MESH_VER = "0.8.60"
|
||||
|
||||
# for the update script, bump when need to recreate venv or npm install
|
||||
PIP_VER = "17"
|
||||
NPM_VER = "16"
|
||||
PIP_VER = "19"
|
||||
NPM_VER = "18"
|
||||
|
||||
SETUPTOOLS_VER = "57.0.0"
|
||||
WHEEL_VER = "0.36.2"
|
||||
|
||||
@@ -46,7 +46,13 @@ def auto_approve_updates_task():
|
||||
def check_agent_update_schedule_task():
|
||||
# scheduled task that installs updates on agents if enabled
|
||||
agents = Agent.objects.only(
|
||||
"pk", "agent_id", "version", "last_seen", "overdue_time", "offline_time"
|
||||
"pk",
|
||||
"agent_id",
|
||||
"version",
|
||||
"last_seen",
|
||||
"overdue_time",
|
||||
"offline_time",
|
||||
"has_patches_pending",
|
||||
)
|
||||
online = [
|
||||
i
|
||||
|
||||
@@ -20,15 +20,17 @@ jobs:
|
||||
sudo -u postgres psql -c 'DROP DATABASE IF EXISTS pipeline'
|
||||
sudo -u postgres psql -c 'DROP DATABASE IF EXISTS test_pipeline'
|
||||
sudo -u postgres psql -c 'CREATE DATABASE pipeline'
|
||||
|
||||
SETTINGS_FILE="/myagent/_work/1/s/api/tacticalrmm/tacticalrmm/settings.py"
|
||||
rm -rf /myagent/_work/1/s/api/env
|
||||
cd /myagent/_work/1/s/api
|
||||
python3.9 -m venv env
|
||||
source env/bin/activate
|
||||
cd /myagent/_work/1/s/api/tacticalrmm
|
||||
pip install --no-cache-dir --upgrade pip
|
||||
pip install --no-cache-dir setuptools==54.2.0 wheel==0.36.2
|
||||
pip install --no-cache-dir -r requirements.txt -r requirements-test.txt -r requirements-dev.txt
|
||||
pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --upgrade pip
|
||||
SETUPTOOLS_VER=$(grep "^SETUPTOOLS_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||
WHEEL_VER=$(grep "^WHEEL_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||
pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org setuptools==${SETUPTOOLS_VER} wheel==${WHEEL_VER}
|
||||
pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org -r requirements.txt -r requirements-test.txt -r requirements-dev.txt
|
||||
displayName: "Install Python Dependencies"
|
||||
|
||||
- script: |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_VERSION="12"
|
||||
SCRIPT_VERSION="14"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/backup.sh'
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
@@ -59,6 +59,7 @@ mkdir ${tmp_dir}/nginx
|
||||
mkdir ${tmp_dir}/systemd
|
||||
mkdir ${tmp_dir}/rmm
|
||||
mkdir ${tmp_dir}/confd
|
||||
mkdir ${tmp_dir}/redis
|
||||
|
||||
|
||||
pg_dump --dbname=postgresql://"${POSTGRES_USER}":"${POSTGRES_PW}"@127.0.0.1:5432/tacticalrmm | gzip -9 > ${tmp_dir}/postgres/db-${dt_now}.psql.gz
|
||||
@@ -72,6 +73,8 @@ sudo tar -czvf ${tmp_dir}/nginx/etc-nginx.tar.gz -C /etc/nginx .
|
||||
|
||||
sudo tar -czvf ${tmp_dir}/confd/etc-confd.tar.gz -C /etc/conf.d .
|
||||
|
||||
sudo gzip -9 -c /var/lib/redis/appendonly.aof > ${tmp_dir}/redis/appendonly.aof.gz
|
||||
|
||||
sudo cp ${sysd}/rmm.service ${sysd}/celery.service ${sysd}/celerybeat.service ${sysd}/meshcentral.service ${sysd}/nats.service ${tmp_dir}/systemd/
|
||||
if [ -f "${sysd}/daphne.service" ]; then
|
||||
sudo cp ${sysd}/daphne.service ${tmp_dir}/systemd/
|
||||
|
||||
@@ -18,6 +18,7 @@ volumes:
|
||||
postgres_data:
|
||||
mongo_data:
|
||||
mesh_data:
|
||||
redis_data:
|
||||
|
||||
services:
|
||||
# postgres database for api service
|
||||
@@ -38,7 +39,10 @@ services:
|
||||
tactical-redis:
|
||||
container_name: trmm-redis
|
||||
image: redis:6.0-alpine
|
||||
command: redis-server --appendonly yes
|
||||
restart: always
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- redis
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ Category or Function - What It Does
|
||||
|
||||

|
||||
|
||||
*****
|
||||
|
||||
## Making Script Files
|
||||
|
||||
### Good Habits
|
||||
@@ -117,6 +119,8 @@ c:\ProgramData\TacticalRMM\
|
||||
- Doesn't play well with other community scripts (reused names etc.)
|
||||
|
||||
|
||||
*****
|
||||
|
||||
## Useful Reference Script Examples
|
||||
|
||||
RunAsUser (since Tactical RMM runs as system)
|
||||
@@ -128,6 +132,8 @@ Command Paramater Ninja
|
||||
Optional Command Parameters and testing for errors
|
||||
[https://github.com/wh1te909/tacticalrmm/blob/develop/scripts/Win_Rename_Computer.ps1](https://github.com/wh1te909/tacticalrmm/blob/develop/scripts/Win_Rename_Computer.ps1)
|
||||
|
||||
*****
|
||||
|
||||
## Volunteers Needed
|
||||
|
||||
If you want to contribute back to the project there are a lot of scripts that need some TLC (Tender Loving Care) please paruse thru them here: [https://github.com/wh1te909/tacticalrmm/tree/develop/scripts_wip](https://github.com/wh1te909/tacticalrmm/tree/develop/scripts_wip)
|
||||
|
||||
@@ -105,6 +105,7 @@ Then you're `push`ing that updated local repo to your online Github fork
|
||||
|
||||
Check your Github fork in browser, should be up to date now with original. Repeat 6 or 7 as necessary
|
||||
|
||||
*****
|
||||
## Reference
|
||||
|
||||
### Customizing the Admin Web Interface
|
||||
|
||||
133
docs/docs/howitallworks.md
Normal file
133
docs/docs/howitallworks.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# How It All Works
|
||||
|
||||
INSERT WIREFRAME GRAPHIC HERE USING <https://www.yworks.com/yed-live/>
|
||||
|
||||
## Server
|
||||
|
||||
Has a postgres database located here:
|
||||
|
||||
[Django Admin](https://wh1te909.github.io/tacticalrmm/functions/django_admin.html)
|
||||
|
||||
!!!description
|
||||
A web interface for the postgres database
|
||||
|
||||
### Services
|
||||
|
||||
|
||||
nginx
|
||||
|
||||
!!!description
|
||||
Web server that handles https traffic
|
||||
|
||||
Log located at `/var/log/nginx`
|
||||
|
||||
```bash
|
||||
tail /var/log/nginx
|
||||
```
|
||||
|
||||
### Dependencies from [here](https://github.com/wh1te909/tacticalrmm/blob/develop/api/tacticalrmm/requirements.txt)
|
||||
|
||||
[nats](https://nats.io/)
|
||||
|
||||
How communication between client and server bride NAT (Network Address Translation)
|
||||
|
||||
[celery](https://github.com/celery/celery)
|
||||
|
||||
!!!description
|
||||
Used to schedule tasks to be sent to Agent
|
||||
|
||||
Log located at `/var/log/celery`
|
||||
|
||||
```bash
|
||||
tail /var/log/celery
|
||||
```
|
||||
|
||||
[Django](https://www.djangoproject.com/)
|
||||
|
||||
!!!description
|
||||
Framework to integrate the server to interact with browser
|
||||
|
||||
future==0.18.2
|
||||
loguru==0.5.3
|
||||
msgpack==1.0.2
|
||||
packaging==20.9
|
||||
psycopg2-binary==2.9.1
|
||||
pycparser==2.20
|
||||
pycryptodome==3.10.1
|
||||
pyotp==2.6.0
|
||||
pyparsing==2.4.7
|
||||
pytz==2021.1
|
||||
|
||||
[qrcode](https://pypi.org/project/qrcode/)
|
||||
|
||||
!!!description
|
||||
For creating QR codes for 2FA
|
||||
|
||||
redis==3.5.3
|
||||
requests==2.25.1
|
||||
six==1.16.0
|
||||
sqlparse==0.4.1
|
||||
|
||||
[twilio](https://www.twilio.com/)
|
||||
|
||||
!!!description
|
||||
Python SMS notification integration
|
||||
|
||||
urllib3==1.26.5
|
||||
uWSGI==2.0.19.1
|
||||
validators==0.18.2
|
||||
vine==5.0.0
|
||||
websockets==9.1
|
||||
zipp==3.4.1
|
||||
|
||||
## Windows Agent
|
||||
|
||||
Found in `%programfiles%\TacticalAgent`
|
||||
|
||||
### Services
|
||||
|
||||
3 services exist on all clients
|
||||
|
||||
* `Mesh Agent`
|
||||

|
||||

|
||||
|
||||
**AND**
|
||||
|
||||
* `TacticalAgent` and `Tactical RMM RPC Service`
|
||||

|
||||

|
||||
|
||||
The [MeshCentral](https://meshcentral.com/) system which is accessible from <https://mesh.example.com> and is used
|
||||
|
||||
|
||||
* It runs 2 goroutines
|
||||
* one is the checkrunner which runs all the checks and then just sleeps until it's time to run more checks
|
||||
* 2nd goroutine periodically sends info about the agent to the rmm and also handles agent recovery
|
||||
|
||||
!!!note
|
||||
In Task Manager you will see additional `Tactical RMM Agent` processes appear and disappear. These are your Checks and Tasks running at scheduled intervals
|
||||
|
||||
`Tactical RMM RPC Service`
|
||||
|
||||
* Uses the pub/sub model so anytime you do anything realtime from rmm (like a send command or run script)
|
||||
* It maintains a persistent connection to your to the api.example.com rmm server on `port:4222` and is listening for events (using [nats](https://nats.io/))
|
||||
* It handles your Agent updates (Auto triggers at 35mins past every hour or when run manually from server Agents | Update Agents menu)
|
||||
|
||||
***
|
||||
|
||||
### Agent Installation Process
|
||||
|
||||
* Adds Defender AV exclusions
|
||||
* Copies temp files to `c:\windows\temp\tacticalxxx` folder.
|
||||
* INNO setup installs app into `%ProgramData%\TacticalAgent\` folder
|
||||
|
||||
***
|
||||
|
||||
### Agent Update Process
|
||||
|
||||
Downloads latest `winagent-vx.x.x-x86/64.exe` to `%programfiles%`
|
||||
|
||||
Executes the file (INNO setup exe)
|
||||
|
||||
Files create `c:\Windows\temp\Tacticalxxxx\` folder for install (and log files)
|
||||
BIN
docs/docs/images/trmm_services.png
Normal file
BIN
docs/docs/images/trmm_services.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/docs/images/trmm_services__taskmanager_agent.png
Normal file
BIN
docs/docs/images/trmm_services__taskmanager_agent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
BIN
docs/docs/images/trmm_services__taskmanager_mesh.png
Normal file
BIN
docs/docs/images/trmm_services__taskmanager_mesh.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
docs/docs/images/trmm_services_mesh.png
Normal file
BIN
docs/docs/images/trmm_services_mesh.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/docs/images/trmm_user_preferences.png
Normal file
BIN
docs/docs/images/trmm_user_preferences.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
@@ -9,19 +9,34 @@ See below for the available options.
|
||||
- **{{agent.version}}** - Tactical RMM agent version
|
||||
- **{{agent.operating_system}}** - Agent operating system example: *Windows 10 Pro, 64 bit (build 19042.928)*
|
||||
- **{{agent.plat}}** - Will show the platform example: *windows*
|
||||
- **{{agent.plat_release}}** - Will show the platform release
|
||||
- **{{agent.hostname}}** - The hostname of the agent
|
||||
- **{{agent.local_ip}}** - Local IP address of agent
|
||||
- **{{agent.public_ip}}** - Public IP address of agent
|
||||
- **{{agent.agent_id}}** - agent ID in database
|
||||
- **{{agent.last_seen}}** - Date and Time Agent last seen
|
||||
- **{{agent.used_ram}}** - Used RAM on agent. Returns an integer - example: *16*
|
||||
- **{{agent.total_ram}}** - Total RAM on agent. Returns an integer - example: *16*
|
||||
- **{{agent.boot_time}}** - Uptime of agent. Returns unix timestamp. example: *1619439603.0*
|
||||
- **{{agent.logged_in_user}}** - Username of logged in user
|
||||
- **{{agent.logged_in_username}}** - Username of logged in user
|
||||
- **{{agent.last_logged_in_user}}** - Username of last logged in user
|
||||
- **{{agent.monitoring_type}}** - Returns a string of *workstation* or *server*
|
||||
- **{{agent.description}}** - Description of agent in dashboard
|
||||
- **{{agent.mesh_node_id}}** - The mesh node id used for linking the tactical agent to mesh.
|
||||
- **{{agent.choco_installed}}** - Boolean to see if Chocolatey is installed
|
||||
- **{{agent.overdue_email_alert}}** - Returns true if overdue email alerts is enabled in TRMM
|
||||
- **{{agent.overdue_text_alert}}** - Returns true if overdue SMS alerts is enabled in TRMM
|
||||
- **{{agent.overdue_dashboard_alert}}** - Returns true if overdue agent alerts is enabled in TRMM
|
||||
- **{{agent.offline_time}}** - Returns offline time setting for agent in TRMM
|
||||
- **{{agent.overdue_time}}** - Returns overdue time setting for agent in TRMM
|
||||
- **{{agent.check_interval}}** - Returns check interval time setting for agent in TRMM
|
||||
- **{{agent.needs_reboot}}** - Returns true if reboot is pending on agent
|
||||
- **{{agent.choco_installed}}** - Returns true if Chocolatey is installed
|
||||
- **{{agent.patches_last_installed}}** - The date that patches were last installed by Tactical RMM.
|
||||
- **{{agent.needs_reboot}}** - Returns true if the agent needs a reboot
|
||||
- **{{agent.time_zone}}** - Returns timezone configured on agent
|
||||
- **{{agent.maintenance_mode}}** - Returns true if agent is in maintenance mode
|
||||
- **{{agent.block_policy_inheritance}}** - Returns true if agent has block policy inheritance
|
||||
- **{{agent.alert_template}** - Returns true if agent has block policy inheritance
|
||||
|
||||
## Client
|
||||
- **{{client.name}}** - Returns name of client
|
||||
|
||||
52
docs/docs/tipsntricks.md
Normal file
52
docs/docs/tipsntricks.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Tips and Tricks
|
||||
|
||||
## Customize User Interface
|
||||
|
||||
At the top right of your web administration interface, click your Username > preferences. Set default tab: Servers|Workstations|Mixed
|
||||
|
||||

|
||||
|
||||
*****
|
||||
|
||||
## Screenconnect / Connectwise Control
|
||||
|
||||
### Install Tactical RMM via Screeconnect commands window
|
||||
|
||||
1. Create a Deplopment under Agents > Manage Deployments
|
||||
2. Replace `<deployment URL>` below with your Deployment Download Link.
|
||||
|
||||
**x64**
|
||||
|
||||
```cmd
|
||||
#!ps
|
||||
#maxlength=500000
|
||||
#timeout=600000
|
||||
|
||||
Invoke-WebRequest "<deployment URL>" -OutFile ( New-Item -Path "C:\temp\trmminstallx64.exe" -Force )
|
||||
$proc = Start-Process c:\temp\trmminstallx64.exe -ArgumentList '-silent' -PassThru
|
||||
Wait-Process -InputObject $proc
|
||||
|
||||
if ($proc.ExitCode -ne 0) {
|
||||
Write-Warning "$_ exited with status code $($proc.ExitCode)"
|
||||
}
|
||||
Remove-Item -Path "c:\temp\trmminstallx64.exe" -Force
|
||||
```
|
||||
|
||||
**x86**
|
||||
|
||||
```cmd
|
||||
#!ps
|
||||
#maxlength=500000
|
||||
#timeout=600000
|
||||
|
||||
Invoke-WebRequest "<deployment URL>" -OutFile ( New-Item -Path "C:\temp\trmminstallx86.exe" -Force )
|
||||
$proc = Start-Process c:\temp\trmminstallx86.exe -ArgumentList '-silent' -PassThru
|
||||
Wait-Process -InputObject $proc
|
||||
|
||||
if ($proc.ExitCode -ne 0) {
|
||||
Write-Warning "$_ exited with status code $($proc.ExitCode)"
|
||||
}
|
||||
Remove-Item -Path "c:\temp\trmminstallx86.exe" -Force
|
||||
```
|
||||
|
||||
###
|
||||
2
go.mod
2
go.mod
@@ -3,6 +3,8 @@ module github.com/wh1te909/tacticalrmm
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/jmoiron/sqlx v1.3.4
|
||||
github.com/lib/pq v1.10.2
|
||||
github.com/nats-io/nats-server/v2 v2.1.8-0.20201129161730-ebe63db3e3ed // indirect
|
||||
github.com/nats-io/nats.go v1.11.0
|
||||
github.com/ugorji/go/codec v1.2.6
|
||||
|
||||
9
go.sum
9
go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
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=
|
||||
@@ -8,6 +10,13 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||
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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
|
||||
github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
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=
|
||||
|
||||
14
install.sh
14
install.sh
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_VERSION="49"
|
||||
SCRIPT_VERSION="50"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/install.sh'
|
||||
|
||||
sudo apt install -y curl wget dirmngr gnupg lsb-release
|
||||
@@ -217,6 +217,10 @@ sudo rm -rf Python-3.9.2 Python-3.9.2.tgz
|
||||
print_green 'Installing redis and git'
|
||||
sudo apt install -y ca-certificates redis git
|
||||
|
||||
# apply redis configuration
|
||||
sudo redis-cli config set appendonly yes
|
||||
sudo redis-cli config rewrite
|
||||
|
||||
print_green 'Installing postgresql'
|
||||
|
||||
echo "$postgresql_repo" | sudo tee /etc/apt/sources.list.d/pgdg.list
|
||||
@@ -487,12 +491,14 @@ map \$http_user_agent \$ignore_ua {
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${rmmdomain};
|
||||
return 301 https://\$server_name\$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name ${rmmdomain};
|
||||
client_max_body_size 300M;
|
||||
access_log /rmm/api/tacticalrmm/tacticalrmm/private/log/access.log combined if=\$ignore_ua;
|
||||
@@ -549,6 +555,7 @@ echo "${nginxrmm}" | sudo tee /etc/nginx/sites-available/rmm.conf > /dev/null
|
||||
nginxmesh="$(cat << EOF
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${meshdomain};
|
||||
return 301 https://\$server_name\$request_uri;
|
||||
}
|
||||
@@ -556,6 +563,7 @@ server {
|
||||
server {
|
||||
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
proxy_send_timeout 330s;
|
||||
proxy_read_timeout 330s;
|
||||
server_name ${meshdomain};
|
||||
@@ -710,6 +718,7 @@ server {
|
||||
access_log /var/log/nginx/frontend-access.log;
|
||||
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
ssl_certificate ${CERT_PUB_KEY};
|
||||
ssl_certificate_key ${CERT_PRIV_KEY};
|
||||
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
|
||||
@@ -720,7 +729,8 @@ server {
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
|
||||
listen 80;
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${frontenddomain};
|
||||
return 404;
|
||||
}
|
||||
|
||||
7
main.go
7
main.go
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/wh1te909/tacticalrmm/natsapi"
|
||||
)
|
||||
|
||||
var version = "2.0.0"
|
||||
var version = "2.1.0"
|
||||
|
||||
func main() {
|
||||
ver := flag.Bool("version", false, "Prints version")
|
||||
@@ -27,6 +27,9 @@ func main() {
|
||||
api.MonitorAgents(*config)
|
||||
case "wmi":
|
||||
api.GetWMI(*config)
|
||||
case "checkin":
|
||||
api.CheckIn(*config)
|
||||
default:
|
||||
fmt.Println(version)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
104
natsapi/tasks.go
104
natsapi/tasks.go
@@ -2,12 +2,15 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
nats "github.com/nats-io/nats.go"
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
@@ -18,6 +21,21 @@ type JsonFile struct {
|
||||
NatsURL string `json:"natsurl"`
|
||||
}
|
||||
|
||||
type DjangoConfig struct {
|
||||
Key string `json:"key"`
|
||||
NatsURL string `json:"natsurl"`
|
||||
User string `json:"user"`
|
||||
Pass string `json:"pass"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
DBName string `json:"dbname"`
|
||||
}
|
||||
|
||||
type Agent struct {
|
||||
ID int `db:"id"`
|
||||
AgentID string `db:"agent_id"`
|
||||
}
|
||||
|
||||
type Recovery struct {
|
||||
Func string `json:"func"`
|
||||
Data map[string]string `json:"payload"`
|
||||
@@ -87,6 +105,90 @@ func MonitorAgents(file string) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func CheckIn(file string) {
|
||||
var r DjangoConfig
|
||||
|
||||
jret, _ := ioutil.ReadFile(file)
|
||||
err := json.Unmarshal(jret, &r)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
|
||||
"password=%s dbname=%s sslmode=disable",
|
||||
r.Host, r.Port, r.User, r.Pass, r.DBName)
|
||||
|
||||
db, err := sqlx.Connect("postgres", psqlInfo)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
db.SetMaxOpenConns(15)
|
||||
|
||||
agent := Agent{}
|
||||
agents := make([]Agent, 0)
|
||||
rows, err := db.Queryx("SELECT agents_agent.id, agents_agent.agent_id FROM agents_agent")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.StructScan(&agent)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
agents = append(agents, agent)
|
||||
}
|
||||
|
||||
var payload []byte
|
||||
ret := codec.NewEncoderBytes(&payload, new(codec.MsgpackHandle))
|
||||
ret.Encode(map[string]string{"func": "ping"})
|
||||
|
||||
opts := setupNatsOptions(r.Key)
|
||||
|
||||
nc, err := nats.Connect(r.NatsURL, opts...)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer nc.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(agents))
|
||||
|
||||
loc, _ := time.LoadLocation("UTC")
|
||||
now := time.Now().In(loc)
|
||||
|
||||
for _, a := range agents {
|
||||
go func(id string, pk int, nc *nats.Conn, wg *sync.WaitGroup, db *sqlx.DB, now time.Time) {
|
||||
defer wg.Done()
|
||||
|
||||
var resp string
|
||||
var mh codec.MsgpackHandle
|
||||
mh.RawToString = true
|
||||
|
||||
time.Sleep(time.Duration(randRange(100, 1500)) * time.Millisecond)
|
||||
out, err := nc.Request(id, payload, 1*time.Second)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dec := codec.NewDecoderBytes(out.Data, &mh)
|
||||
if err := dec.Decode(&resp); err == nil {
|
||||
if resp == "pong" {
|
||||
_, err = db.NamedExec(
|
||||
`UPDATE agents_agent SET last_seen=:lastSeen WHERE agents_agent.id=:pk`,
|
||||
map[string]interface{}{"lastSeen": now, "pk": pk},
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}(a.AgentID, a.ID, nc, &wg, db, now)
|
||||
}
|
||||
wg.Wait()
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func GetWMI(file string) {
|
||||
var result JsonFile
|
||||
var payload []byte
|
||||
@@ -115,7 +217,7 @@ func GetWMI(file string) {
|
||||
for _, id := range result.Agents {
|
||||
go func(id string, nc *nats.Conn, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
time.Sleep(time.Duration(randRange(0, 20)) * time.Second)
|
||||
time.Sleep(time.Duration(randRange(0, 28)) * time.Second)
|
||||
nc.Publish(id, payload)
|
||||
}(id, nc, &wg)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_VERSION="27"
|
||||
SCRIPT_VERSION="29"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/restore.sh'
|
||||
|
||||
sudo apt update
|
||||
@@ -189,6 +189,13 @@ sudo rm -rf Python-3.9.2 Python-3.9.2.tgz
|
||||
print_green 'Installing redis and git'
|
||||
sudo apt install -y ca-certificates redis git
|
||||
|
||||
# redis configuration
|
||||
sudo gzip -dkc ${tmp_dir}/redis/appendonly.aof.gz | sudo tee /var/lib/redis/appendonly.aof > /dev/null
|
||||
sudo redis-check-aof --fix /var/lib/redis/appendonly.aof
|
||||
|
||||
sudo redis-cli config set appendonly yes
|
||||
sudo redis-cli config rewrite
|
||||
|
||||
print_green 'Installing postgresql'
|
||||
|
||||
echo "$postgresql_repo" | sudo tee /etc/apt/sources.list.d/pgdg.list
|
||||
|
||||
327
scripts/Win_AD_Hudu_ADDS_Documentation.ps1
Normal file
327
scripts/Win_AD_Hudu_ADDS_Documentation.ps1
Normal file
@@ -0,0 +1,327 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Automatically document ADDS configuration
|
||||
.DESCRIPTION
|
||||
Automatically document ADDS configuration. Submits generated documentation to your Hudu instance and associates it with the Company provided by ClientName. Requires Global Keystore variables for HuduBaseDomain and HuduApiKey.
|
||||
.INPUTS
|
||||
-ClientName {{client.name}}
|
||||
-HuduBaseDomain {{global.HuduBaseDomain}}
|
||||
-HuduApiKey {{global.HuduApiKey}}
|
||||
.NOTES
|
||||
v1.0
|
||||
Based on https://github.com/lwhitelock/HuduAutomation/blob/main/CyberdrainRewrite/Hudu-ADDS-Documentation.ps1
|
||||
.COMPONENT
|
||||
Hudu Documentation
|
||||
.ROLE
|
||||
Documentation
|
||||
#>
|
||||
|
||||
param (
|
||||
[string] $ClientName,
|
||||
[string] $HuduBaseDomain,
|
||||
[string] $HuduApiKey
|
||||
)
|
||||
|
||||
if (!$ClientName) {
|
||||
write-output "Must provide -ClientName with a valid value that is identical to the name of a Company that exists in your Hudu instance. This should be the {{client.name}} value. `n"
|
||||
$ErrorCount += 1
|
||||
}
|
||||
if (!$HuduBaseDomain) {
|
||||
write-output "Must provide -HuduBaseUrl and it must a FQDN that maps to your Hudu instance without a trailing slash. `n"
|
||||
$ErrorCount += 1
|
||||
}
|
||||
if (!$HuduApiKey) {
|
||||
write-output "Must provide -HuduApiKey with a valid value from your Hudu instance. `n"
|
||||
$ErrorCount += 1
|
||||
}
|
||||
|
||||
if (!$ErrorCount -eq 0) {
|
||||
exit 1
|
||||
}
|
||||
#####################################################################
|
||||
#
|
||||
# Active Directory Details to Hudu
|
||||
#
|
||||
# Get a Hudu API Key from https://yourhududomain.com/admin/api_keys
|
||||
# Set the base domain of your Hudu instance without a trailing /
|
||||
# Client Name as it appears in Hudu
|
||||
$HuduAssetLayoutName = "Active Directory - AutoDoc"
|
||||
#####################################################################
|
||||
|
||||
Write-Host "Connecting to $HuduBaseDomain"
|
||||
|
||||
#Get the Hudu API Module if not installed
|
||||
if (Get-Module -ListAvailable -Name HuduAPI) {
|
||||
Import-Module HuduAPI
|
||||
} else {
|
||||
Install-Module HuduAPI -Force
|
||||
Import-Module HuduAPI
|
||||
}
|
||||
|
||||
#Set Hudu logon information
|
||||
New-HuduAPIKey $HuduAPIKey
|
||||
New-HuduBaseUrl $HuduBaseDomain
|
||||
|
||||
function Get-WinADForestInformation {
|
||||
$Data = @{ }
|
||||
$ForestInformation = $(Get-ADForest)
|
||||
$Data.Forest = $ForestInformation
|
||||
$Data.RootDSE = $(Get-ADRootDSE -Properties *)
|
||||
$Data.ForestName = $ForestInformation.Name
|
||||
$Data.ForestNameDN = $Data.RootDSE.defaultNamingContext
|
||||
$Data.Domains = $ForestInformation.Domains
|
||||
$Data.ForestInformation = @{
|
||||
'Name' = $ForestInformation.Name
|
||||
'Root Domain' = $ForestInformation.RootDomain
|
||||
'Forest Functional Level' = $ForestInformation.ForestMode
|
||||
'Domains Count' = ($ForestInformation.Domains).Count
|
||||
'Sites Count' = ($ForestInformation.Sites).Count
|
||||
'Domains' = ($ForestInformation.Domains) -join ", "
|
||||
'Sites' = ($ForestInformation.Sites) -join ", "
|
||||
}
|
||||
|
||||
$Data.UPNSuffixes = Invoke-Command -ScriptBlock {
|
||||
$UPNSuffixList = [PSCustomObject] @{
|
||||
"Primary UPN" = $ForestInformation.RootDomain
|
||||
"UPN Suffixes" = $ForestInformation.UPNSuffixes -join ","
|
||||
}
|
||||
return $UPNSuffixList
|
||||
}
|
||||
|
||||
$Data.GlobalCatalogs = $ForestInformation.GlobalCatalogs
|
||||
$Data.SPNSuffixes = $ForestInformation.SPNSuffixes
|
||||
|
||||
$Data.Sites = Invoke-Command -ScriptBlock {
|
||||
$Sites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites
|
||||
$SiteData = foreach ($Site in $Sites) {
|
||||
[PSCustomObject] @{
|
||||
"Site Name" = $site.Name
|
||||
"Subnets" = ($site.Subnets) -join ", "
|
||||
"Servers" = ($Site.Servers) -join ", "
|
||||
}
|
||||
}
|
||||
Return $SiteData
|
||||
}
|
||||
|
||||
|
||||
$Data.FSMO = Invoke-Command -ScriptBlock {
|
||||
[PSCustomObject] @{
|
||||
"Domain" = $ForestInformation.RootDomain
|
||||
"Role" = 'Domain Naming Master'
|
||||
"Holder" = $ForestInformation.DomainNamingMaster
|
||||
}
|
||||
|
||||
[PSCustomObject] @{
|
||||
"Domain" = $ForestInformation.RootDomain
|
||||
"Role" = 'Schema Master'
|
||||
"Holder" = $ForestInformation.SchemaMaster
|
||||
}
|
||||
|
||||
foreach ($Domain in $ForestInformation.Domains) {
|
||||
$DomainFSMO = Get-ADDomain $Domain | Select-Object PDCEmulator, RIDMaster, InfrastructureMaster
|
||||
|
||||
[PSCustomObject] @{
|
||||
"Domain" = $Domain
|
||||
"Role" = 'PDC Emulator'
|
||||
"Holder" = $DomainFSMO.PDCEmulator
|
||||
}
|
||||
|
||||
|
||||
[PSCustomObject] @{
|
||||
"Domain" = $Domain
|
||||
"Role" = 'Infrastructure Master'
|
||||
"Holder" = $DomainFSMO.InfrastructureMaster
|
||||
}
|
||||
|
||||
[PSCustomObject] @{
|
||||
"Domain" = $Domain
|
||||
"Role" = 'RID Master'
|
||||
"Holder" = $DomainFSMO.RIDMaster
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Return $FSMO
|
||||
}
|
||||
|
||||
$Data.OptionalFeatures = Invoke-Command -ScriptBlock {
|
||||
$OptionalFeatures = $(Get-ADOptionalFeature -Filter * )
|
||||
$Optional = @{
|
||||
'Recycle Bin Enabled' = ''
|
||||
'Privileged Access Management Feature Enabled' = ''
|
||||
}
|
||||
### Fix Optional Features
|
||||
foreach ($Feature in $OptionalFeatures) {
|
||||
if ($Feature.Name -eq 'Recycle Bin Feature') {
|
||||
if ("$($Feature.EnabledScopes)" -eq '') {
|
||||
$Optional.'Recycle Bin Enabled' = $False
|
||||
}
|
||||
else {
|
||||
$Optional.'Recycle Bin Enabled' = $True
|
||||
}
|
||||
}
|
||||
if ($Feature.Name -eq 'Privileged Access Management Feature') {
|
||||
if ("$($Feature.EnabledScopes)" -eq '') {
|
||||
$Optional.'Privileged Access Management Feature Enabled' = $False
|
||||
}
|
||||
else {
|
||||
$Optional.'Privileged Access Management Feature Enabled' = $True
|
||||
}
|
||||
}
|
||||
}
|
||||
return $Optional
|
||||
### Fix optional features
|
||||
}
|
||||
return $Data
|
||||
}
|
||||
|
||||
$TableHeader = "<table style=`"width: 100%; border-collapse: collapse; border: 1px solid black;`">"
|
||||
$Whitespace = "<br/>"
|
||||
$TableStyling = "<th>", "<th style=`"background-color:#00adef; border: 1px solid black;`">"
|
||||
|
||||
$RawAD = Get-WinADForestInformation
|
||||
|
||||
$ForestRawInfo = new-object PSCustomObject -property $RawAD.ForestInformation | convertto-html -Fragment | Select-Object -Skip 1
|
||||
$ForestNice = $TableHeader + ($ForestRawInfo -replace $TableStyling) + $Whitespace
|
||||
|
||||
$SiteRawInfo = $RawAD.Sites | Select-Object 'Site Name', Servers, Subnets | ConvertTo-Html -Fragment | Select-Object -Skip 1
|
||||
$SiteNice = $TableHeader + ($SiteRawInfo -replace $TableStyling) + $Whitespace
|
||||
|
||||
$OptionalRawFeatures = new-object PSCustomObject -property $RawAD.OptionalFeatures | convertto-html -Fragment | Select-Object -Skip 1
|
||||
$OptionalNice = $TableHeader + ($OptionalRawFeatures -replace $TableStyling) + $Whitespace
|
||||
|
||||
$UPNRawFeatures = $RawAD.UPNSuffixes | convertto-html -Fragment -as list| Select-Object -Skip 1
|
||||
$UPNNice = $TableHeader + ($UPNRawFeatures -replace $TableStyling) + $Whitespace
|
||||
|
||||
$DCRawFeatures = $RawAD.GlobalCatalogs | ForEach-Object { Add-Member -InputObject $_ -Type NoteProperty -Name "Domain Controller" -Value $_; $_ } | convertto-html -Fragment | Select-Object -Skip 1
|
||||
$DCNice = $TableHeader + ($DCRawFeatures -replace $TableStyling) + $Whitespace
|
||||
|
||||
$FSMORawFeatures = $RawAD.FSMO | convertto-html -Fragment | Select-Object -Skip 1
|
||||
$FSMONice = $TableHeader + ($FSMORawFeatures -replace $TableStyling) + $Whitespace
|
||||
|
||||
$ForestFunctionalLevel = $RawAD.RootDSE.forestFunctionality
|
||||
$DomainFunctionalLevel = $RawAD.RootDSE.domainFunctionality
|
||||
$domaincontrollerMaxLevel = $RawAD.RootDSE.domainControllerFunctionality
|
||||
|
||||
$passwordpolicyraw = Get-ADDefaultDomainPasswordPolicy | Select-Object ComplexityEnabled, PasswordHistoryCount, LockoutDuration, LockoutThreshold, MaxPasswordAge, MinPasswordAge | convertto-html -Fragment -As List | Select-Object -skip 1
|
||||
$passwordpolicyheader = "<tr><th><b>Policy</b></th><th><b>Setting</b></th></tr>"
|
||||
$passwordpolicyNice = $TableHeader + ($passwordpolicyheader -replace $TableStyling) + ($passwordpolicyraw -replace $TableStyling) + $Whitespace
|
||||
|
||||
$adminsraw = Get-ADGroupMember "Domain Admins" | Select-Object SamAccountName, Name | convertto-html -Fragment | Select-Object -Skip 1
|
||||
$adminsnice = $TableHeader + ($adminsraw -replace $TableStyling) + $Whitespace
|
||||
|
||||
$EnabledUsers = (Get-AdUser -filter * | Where-Object { $_.enabled -eq $true }).count
|
||||
$DisabledUSers = (Get-AdUser -filter * | Where-Object { $_.enabled -eq $false }).count
|
||||
$AdminUsers = (Get-ADGroupMember -Identity "Domain Admins").count
|
||||
$Users = @"
|
||||
There are <b> $EnabledUsers </b> users Enabled<br>
|
||||
There are <b> $DisabledUSers </b> users Disabled<br>
|
||||
There are <b> $AdminUsers </b> Domain Administrator users<br>
|
||||
"@
|
||||
|
||||
# Setup the fields for the Asset
|
||||
$AssetFields = @{
|
||||
'domain_name' = $RawAD.ForestName
|
||||
'forest_summary' = $ForestNice
|
||||
'site_summary' = $SiteNice
|
||||
'domain_controllers' = $DCNice
|
||||
'fsmo_roles' = $FSMONice
|
||||
'optional_features' = $OptionalNice
|
||||
'upn_suffixes' = $UPNNice
|
||||
'default_password_policies' = $passwordpolicyNice
|
||||
'domain_admins' = $adminsnice
|
||||
'user_count' = $Users
|
||||
}
|
||||
|
||||
# Checking if the FlexibleAsset exists. If not, create a new one.
|
||||
$Layout = Get-HuduAssetLayouts -name $HuduAssetLayoutName
|
||||
|
||||
if (!$Layout) {
|
||||
|
||||
$AssetLayoutFields = @(
|
||||
@{
|
||||
label = 'Domain Name'
|
||||
field_type = 'Text'
|
||||
show_in_list = 'true'
|
||||
position = 1
|
||||
},
|
||||
@{
|
||||
label = 'Forest Summary'
|
||||
field_type = 'RichText'
|
||||
show_in_list = 'false'
|
||||
position = 2
|
||||
},
|
||||
@{
|
||||
label = 'Site Summary'
|
||||
field_type = 'RichText'
|
||||
show_in_list = 'false'
|
||||
position = 3
|
||||
},
|
||||
@{
|
||||
label = 'Domain Controllers'
|
||||
field_type = 'RichText'
|
||||
show_in_list = 'false'
|
||||
position = 4
|
||||
},
|
||||
@{
|
||||
label = 'FSMO Roles'
|
||||
field_type = 'RichText'
|
||||
show_in_list = 'false'
|
||||
position = 5
|
||||
},
|
||||
@{
|
||||
label = 'Optional Features'
|
||||
field_type = 'RichText'
|
||||
show_in_list = 'false'
|
||||
position = 6
|
||||
},
|
||||
@{
|
||||
label = 'UPN Suffixes'
|
||||
field_type = 'RichText'
|
||||
show_in_list = 'false'
|
||||
position = 7
|
||||
},
|
||||
@{
|
||||
label = 'Default Password Policies'
|
||||
field_type = 'RichText'
|
||||
show_in_list = 'false'
|
||||
position = 8
|
||||
},
|
||||
@{
|
||||
label = 'Domain Admins'
|
||||
field_type = 'RichText'
|
||||
show_in_list = 'false'
|
||||
position = 9
|
||||
},
|
||||
@{
|
||||
label = 'User Count'
|
||||
field_type = 'RichText'
|
||||
show_in_list = 'false'
|
||||
position = 10
|
||||
}
|
||||
)
|
||||
|
||||
Write-Host "Creating New Asset Layout"
|
||||
$NewLayout = New-HuduAssetLayout -name $HuduAssetLayoutName -icon "fas fa-sitemap" -color "#00adef" -icon_color "#000000" -include_passwords $false -include_photos $false -include_comments $false -include_files $false -fields $AssetLayoutFields
|
||||
$Layout = Get-HuduAssetLayouts -name $HuduAssetLayoutName
|
||||
}
|
||||
|
||||
|
||||
$Company = Get-HuduCompanies -name $ClientName
|
||||
if ($company) {
|
||||
#Upload data to Hudu
|
||||
$Asset = Get-HuduAssets -name $RawAD.ForestName -companyid $company.id -assetlayoutid $layout.id
|
||||
|
||||
#If the Asset does not exist, we edit the body to be in the form of a new asset, if not, we just upload.
|
||||
if (!$Asset) {
|
||||
Write-Host "New Asset Created"
|
||||
$Asset = New-HuduAsset -name $RawAD.ForestName -company_id $company.id -asset_layout_id $layout.id -fields $AssetFields
|
||||
}
|
||||
else {
|
||||
Write-Host "Asset has been Updated"
|
||||
$Asset = Set-HuduAsset -asset_id $Asset.id -name $RawAD.ForestName -company_id $company.id -asset_layout_id $layout.id -fields $AssetFields
|
||||
}
|
||||
|
||||
} else {
|
||||
Write-Host "$ClientName was not found in Hudu"
|
||||
}
|
||||
78
scripts/Win_AD_Join_Computer.ps1
Normal file
78
scripts/Win_AD_Join_Computer.ps1
Normal file
@@ -0,0 +1,78 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Joins computer to Active Directory.
|
||||
|
||||
.DESCRIPTION
|
||||
Computer can be joined to AD in a specific OU specified in the parameters or it will join the default location.
|
||||
|
||||
.OUTPUTS
|
||||
Results are printed to the console and sent to a log file in C:\Temp
|
||||
|
||||
.EXAMPLE
|
||||
In parameter set desired items
|
||||
-domain DOMAIN -password ADMINpassword -UserAccount ADMINaccount -OUPath OU=testOU,DC=test,DC=local
|
||||
|
||||
|
||||
.NOTES
|
||||
Change Log
|
||||
V1.0 Initial release
|
||||
V1.1 Parameterization; Error Checking with conditionals and exit codes
|
||||
V1.2 Variable declarations cleaned up; minor syntax corrections; Output to file added (@jeevis)
|
||||
|
||||
Reference Links:
|
||||
www.google.com
|
||||
docs.microsoft.com
|
||||
#>
|
||||
|
||||
param(
|
||||
$domain,
|
||||
$password,
|
||||
$UserAccount,
|
||||
$OUPath
|
||||
)
|
||||
|
||||
|
||||
|
||||
if ([string]::IsNullOrEmpty($domain)) {
|
||||
Write-Output "Domain must be defined. Use -domain <value> to pass it."
|
||||
EXIT 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($UserAccount)) {
|
||||
Write-Output "User Account must be defined. Use -UserAccount <value> to pass it."
|
||||
EXIT 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($password)){
|
||||
Write-Output "Password must be defined. Use -password <value> to pass it."
|
||||
EXIT 1
|
||||
}
|
||||
|
||||
else{
|
||||
$username = "$domain\$UserAccount"
|
||||
$password = ConvertTo-SecureString -string $password -asPlainText -Force
|
||||
$credential = New-Object System.Management.Automation.PSCredential($username, $password)
|
||||
}
|
||||
|
||||
try{
|
||||
|
||||
if ([string]::IsNullOrEmpty($OUPath)){
|
||||
Write-Output "OU Path is not defined. Computer object will be created in the default OU."
|
||||
Add-Computer -DomainName $domain -Credential $credential -Restart
|
||||
echo "Add-Computer -DomainName $domain -Credential $credential -Restart" >> C:\Temp\ADJoinCommand.log
|
||||
EXIT 0
|
||||
}
|
||||
|
||||
else {
|
||||
Add-Computer -DomainName $domain -OUPath $OUPath -Credential $credential -Restart
|
||||
echo "Add-Computer -DomainName $domain -OUPath $OUPath -Credential $credential -Restart" >> C:\Temp\ADJoinCommand.log
|
||||
EXIT 0
|
||||
}
|
||||
}
|
||||
|
||||
catch{
|
||||
Write-Output "An error has occured."
|
||||
EXIT 1
|
||||
}
|
||||
|
||||
Exit $LASTEXITCODE
|
||||
@@ -4,6 +4,8 @@
|
||||
#antivirusName must match the "displayName" exactly
|
||||
#If no antivirusName parameter is specified, the tool returns success if there is any active up to date antivirus on the system
|
||||
|
||||
# OS Build must be greater than 14393 to support this script. If it's not it returns exit code 2
|
||||
|
||||
|
||||
param($antivirusName = "*")
|
||||
|
||||
@@ -93,6 +95,11 @@ function Add-ProductStates {
|
||||
}
|
||||
}
|
||||
|
||||
if ([environment]::OSVersion.Version.Build -le 14393) {
|
||||
write-host "Antivirus check not supported on this OS. Returning Exit Code 2."
|
||||
exit 2
|
||||
}
|
||||
|
||||
|
||||
$return = Get-CimInstance -Namespace root/SecurityCenter2 -className AntivirusProduct |
|
||||
Where-Object {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
## Copied from https://github.com/ThatsNASt/tacticalrmm to add to new pull request for https://github.com/wh1te909/tacticalrmm
|
||||
#
|
||||
# WARNING
|
||||
# 1. Only applies to drive C
|
||||
# 2. Assumes you're encrypting more than the used space. "Used Space Only Encrypted" is the default windows behavior which is not compatible here.
|
||||
|
||||
function Log-Message {
|
||||
Param
|
||||
(
|
||||
@@ -22,7 +27,7 @@ function Log-Message {
|
||||
$log = "BitlockerReport.txt"
|
||||
|
||||
#Find BL info
|
||||
$mbde = [string](manage-bde -status)
|
||||
$mbde = [string](manage-bde -status C:)
|
||||
$mbdeProt = (manage-bde -protectors -get c: | Select-Object -Skip 6)
|
||||
#Dig out the recovery password, check for PIN
|
||||
ForEach ($line in $mbdeProt) {
|
||||
@@ -94,4 +99,4 @@ if ($Encrypted -eq "Yes" -and $RecoveryPassword -and $PIN -eq $true) {
|
||||
Log-Message "SUCCESS: Encrypted, PIN enabled, password is set." $log e
|
||||
Write-Host "Script check passed"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
171
scripts/Win_Collect_System_Report_And_Email.ps1
Normal file
171
scripts/Win_Collect_System_Report_And_Email.ps1
Normal file
@@ -0,0 +1,171 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generates a system report in HTML format.
|
||||
|
||||
.DESCRIPTION
|
||||
A report comprised of stopped services, running processes, drive space, network adapter settings, and printers is stored locally with a copy sent via e-mail
|
||||
|
||||
.INPUTS
|
||||
Must provide ALL parameter arguments in the following manner (failing to do so will cause the script to exit out prior to creating and sending the report):
|
||||
|
||||
-agentname <enter directly or use the Script Variable {{agent.hostname}}>
|
||||
-file <enter file name with the extension .HTM or .HTML>
|
||||
-fromaddress <sender's email address>
|
||||
-toaddress <recipient's email address>
|
||||
-smtpserver <address of SMTP mail server to be used for sending the report>
|
||||
-password <password associated with a valid mail account to access the mail server via SMTP>
|
||||
-port <587 is the standard port for sending mail over TLS>
|
||||
|
||||
.OUTPUTS
|
||||
Results are sent as a HTML file to C:\Temp and e-mailed based on provided parameters
|
||||
|
||||
.NOTES
|
||||
Change Log
|
||||
V1.0 Initial release and parameterization
|
||||
V1.1 Check for C:\Temp path prior to generating report
|
||||
V1.2 Parameter checks with exit codes added
|
||||
|
||||
|
||||
Reference Links:
|
||||
www.google.com
|
||||
docs.microsoft.com
|
||||
#>
|
||||
|
||||
|
||||
param(
|
||||
$agentname,
|
||||
$fromaddress,
|
||||
$toaddress,
|
||||
$smtpserver,
|
||||
$password,
|
||||
$port,
|
||||
$file
|
||||
)
|
||||
|
||||
#Parameter Checks with exit codes
|
||||
|
||||
if ([string]::IsNullOrEmpty($agentname)){
|
||||
write-host "You must directly enter a hostname or use the TRMM Script Variable {{agent.hostname}} to pass the hostname from the dashboard."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($file)){
|
||||
Write-host "You must provide a file name with a .HTM extension."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($fromaddress)){
|
||||
Write-host "You must provide a sender's email address."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($toaddress)){
|
||||
write-host "You must provide a recipient's email address."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($smtpserver)){
|
||||
write-host "You must provide a SMTP server address."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($password)){
|
||||
write-host "You must provide a password for the SMTP server"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($port)){
|
||||
write-host "A valid port number is required to send the report."
|
||||
exit 1
|
||||
}
|
||||
|
||||
else{
|
||||
|
||||
$path = "C:\Temp"
|
||||
$destination = "$path\$file"
|
||||
|
||||
|
||||
if(!(Test-Path -Path $path)){
|
||||
write-host "Path does not exist. Creating path prior to generating report."
|
||||
New-Item -Path "C:\" -Name "Temp" -ItemType "directory"
|
||||
}
|
||||
|
||||
else{
|
||||
Write-host "Path alreaedy exists. Attempting to generate report."
|
||||
}
|
||||
|
||||
|
||||
#HTML Styling
|
||||
|
||||
$a = "<style>BODY{font-family: Calibri; font-size: 15pt;}"
|
||||
$a = $a + "TABLE{border: 1px solid black; border-collapse: collapse;}"
|
||||
$a = $a + "TH{border: 1px solid green; background: lightgreen; padding: 5px; }"
|
||||
$a = $a + "TD{border: 1px solid green; padding: 5px; }"
|
||||
$a = $a + "</style>"
|
||||
|
||||
#Heading
|
||||
|
||||
"<H1 style='color:green;'>System Report For $agentname</H1>" | Out-File $destination -Append
|
||||
|
||||
#Network Information
|
||||
|
||||
Get-WmiObject win32_networkadapterconfiguration -filter "ipenabled = 'True'"|
|
||||
Select PSComputername, DNSHostName, Description,
|
||||
@{Name = "IPAddress";Expression =
|
||||
{[regex]$rx = "(\d{1,3}(\.?)){4}"
|
||||
$rx.matches($_.IPAddress).Value}},MACAddress | ConvertTo-HTML -Head "<H2 style='color:green;'>Network Information</H2>" -body $a | Out-file $destination -Append
|
||||
|
||||
#Get Event logs
|
||||
|
||||
Get-EventLog -LogName Application -Newest 10 -EntryType Error | Select TimeGenerated, EventID, Source, Message | ConvertTo-HTML -Head "<H2 style='color:green;'>Application Error Event Logs</H2>" -body $a | Out-file $file -Append
|
||||
Get-EventLog -LogName Application -Newest 10 -EntryType Warning | Select TimeGenerated, EventID, Source, Message | ConvertTo-HTML -Head "<H2 style='color:green;'>Application Warning Event Logs</H2>" -body $a | Out-file $file -Append
|
||||
Get-EventLog -LogName System -Newest 10 -EntryType Error | Select TimeGenerated, EventID, Source, Message | ConvertTo-HTML -Head "<H2 style='color:green;'>System Error Event Logs</H2>" -body $a | Out-file $file -Append
|
||||
Get-EventLog -LogName System -Newest 10 -EntryType Warning | Select TimeGenerated, EventID, Source, Message | ConvertTo-HTML -Head "<H2 style='color:green;'>System Warning Event Logs</H2>" -body $a | Out-file $file -Append
|
||||
|
||||
#Get Stopped Services
|
||||
|
||||
Get-Service | Where {($_.Status) -eq "Stopped"} | Select Status, Name, DisplayName | ConvertTo-HTML -Head "<H2 style='color:green;'>Stopped Services</H2>" -body $a | Out-File $destination -Append
|
||||
|
||||
#Get Processes and CPU
|
||||
|
||||
Get-Process | Select Id, ProcessName, CPU | ConvertTo-HTML -Head "<H2 style='color:green;'>Processes & CPU</H2>" -body $a | Out-File $destination -Append
|
||||
|
||||
#Get Mapped Drives
|
||||
|
||||
Get-PSDrive | Where {$_.Used -ne $null} | Select Name, @{n='Used';e={[float]($_.Used/1GB)}}, @{n='Free';e={[float]($_.Free/1GB)}}, Root| ConvertTo-HTML -Head "<H2 style='color:green;'>Mapped Drives</H2>" -body $a | Out-File $destination -Append
|
||||
|
||||
#Get Printers
|
||||
|
||||
Get-Printer | Select Name, Type, PortName | ConvertTo-HTML -Head "<H2 style='color:green;'>Printers</H2>" -body $a | Out-file $destination -append
|
||||
|
||||
|
||||
|
||||
try {
|
||||
#Send Email
|
||||
|
||||
$Subject = "System Report for $agentname"
|
||||
$body = Get-Content $destination
|
||||
|
||||
$message = new-object System.Net.Mail.MailMessage
|
||||
$message.IsBodyHTML = $true
|
||||
$message.From = $fromaddress
|
||||
$message.To.Add($toaddress)
|
||||
$message.Subject = $Subject
|
||||
$message.body = $body
|
||||
$smtp = new-object Net.Mail.SmtpClient($smtpserver, $port)
|
||||
$smtp.EnableSsl = $true
|
||||
$smtp.Credentials = New-Object System.Net.NetworkCredential($fromaddress, $Password)
|
||||
$smtp.Send($message)
|
||||
|
||||
write-host "System Report successfully sent via email."
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
catch {
|
||||
write-host "An error occurrd. Please check your parameters, SMTP server name, or credentials and try again."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
exit $LASTEXITCODE
|
||||
64
scripts/Win_User_Create.ps1
Normal file
64
scripts/Win_User_Create.ps1
Normal file
@@ -0,0 +1,64 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Creates Local User on computer
|
||||
|
||||
.DESCRIPTION
|
||||
Creates a Local user with password and adds to Users group. If group specificed you can add to a different group
|
||||
|
||||
.OUTPUTS
|
||||
Results are printed to the console.
|
||||
|
||||
.EXAMPLE
|
||||
In parameter set desired items
|
||||
-username testuser -password password -description "Big Bozz" -group administrators
|
||||
|
||||
.NOTES
|
||||
Change Log
|
||||
6/17/2021 V1.0 Initial release
|
||||
6/17/2021 v1.1 Adding group support
|
||||
|
||||
Contributed by: https://github.com/brodur
|
||||
Tweaks by: https://github.com/silversword411
|
||||
#>
|
||||
|
||||
param(
|
||||
$username,
|
||||
$password,
|
||||
$description = "User added by TacticalRMM",
|
||||
$fullname = "",
|
||||
$group
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrEmpty($username)) {
|
||||
Write-Output "Username must be defined. Use -username <value> to pass it."
|
||||
EXIT 1
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($password)) {
|
||||
Write-Output "Password must be defined. Use -password <value> to pass it."
|
||||
EXIT 1
|
||||
}
|
||||
else {
|
||||
$password = ConvertTo-SecureString -String $password -AsPlainText -Force
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
New-LocalUser -Name $username -Password $password -Description $description -PasswordNeverExpires -FullName $fullname
|
||||
if ([string]::IsNullOrEmpty($group)) {
|
||||
Add-LocalGroupMember -Group "Users" -Member $username
|
||||
Write-Output "Adding user to the User Group"
|
||||
}
|
||||
else {
|
||||
Add-LocalGroupMember -Group $group -Member $username
|
||||
Write-Output "Adding user to the $group Group"
|
||||
}
|
||||
EXIT 0
|
||||
}
|
||||
catch {
|
||||
Write-Output "An error has occured."
|
||||
Write-Output $_
|
||||
EXIT 1
|
||||
}
|
||||
|
||||
EXIT $LASTEXITCODE
|
||||
@@ -1,22 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
I do this
|
||||
|
||||
.DESCRIPTION
|
||||
I really do a lot of this
|
||||
|
||||
.OUTPUTS
|
||||
Results are printed to the console. Future releases will support outputting to a log file.
|
||||
|
||||
.NOTES
|
||||
Change Log
|
||||
V1.0 Initial release
|
||||
|
||||
Reference Links: www.google.com
|
||||
#>
|
||||
|
||||
$domain = "myDomain"
|
||||
$password = "myPassword!" | ConvertTo-SecureString -asPlainText -Force
|
||||
$username = "$domain\myUserAccount"
|
||||
$credential = New-Object System.Management.Automation.PSCredential($username, $password)
|
||||
Add-Computer -DomainName $domain -OUPath "OU=testOU,DC=domain,DC=Domain,DC=com" -Credential $credential -Restart
|
||||
3
scripts_wip/Win_AD_List_Enabled_Users.ps1
Normal file
3
scripts_wip/Win_AD_List_Enabled_Users.ps1
Normal file
@@ -0,0 +1,3 @@
|
||||
# ACTIVE DIRECTORY AD LIST ENABLED USERS DOMAIN
|
||||
Get-ADUser -Filter {Enabled -eq $true} | select Name,Enabled | Export-Csv c:\temp\aduserlist.csv
|
||||
Get-ADUser -Filter {Enabled -eq $true} | select SamAccountName,Name | Export-Csv c:\temp\aduserlist.csv
|
||||
@@ -1,73 +0,0 @@
|
||||
#The following variables should be changed:
|
||||
#$file ? should be named with a .htm ending
|
||||
#$fromaddress
|
||||
#$toaddress
|
||||
#$smtpserver
|
||||
#$Password
|
||||
#$port
|
||||
|
||||
$file = "C:\Temp\Report.htm"
|
||||
|
||||
#HTML Styling
|
||||
|
||||
$a = "<style>BODY{font-family: Calibri; font-size: 15pt;}"
|
||||
$a = $a + "TABLE{border: 1px solid black; border-collapse: collapse;}"
|
||||
$a = $a + "TH{border: 1px solid green; background: lightgreen; padding: 5px; }"
|
||||
$a = $a + "TD{border: 1px solid green; padding: 5px; }"
|
||||
$a = $a + "</style>"
|
||||
|
||||
#Heading
|
||||
|
||||
"<H1 style='color:green;'>System Report For Agent</H1>" | Out-File $file -Append
|
||||
|
||||
#Network Information
|
||||
|
||||
Get-WmiObject win32_networkadapterconfiguration -filter "ipenabled = 'True'"|
|
||||
Select PSComputername, DNSHostName, Description,
|
||||
@{Name = "IPAddress";Expression =
|
||||
{[regex]$rx = "(\d{1,3}(\.?)){4}"
|
||||
$rx.matches($_.IPAddress).Value}},MACAddress | ConvertTo-HTML -Head "<H2 style='color:green;'>Network Information</H2>" -body $a | Out-file $file -Append
|
||||
|
||||
#Get Event logs
|
||||
|
||||
Get-EventLog -LogName Application -Newest 10 -EntryType Error | Select TimeGenerated, EventID, Source, Message | ConvertTo-HTML -Head "<H2 style='color:green;'>Application Error Event Logs</H2>" -body $a | Out-file $file -Append
|
||||
Get-EventLog -LogName Application -Newest 10 -EntryType Warning | Select TimeGenerated, EventID, Source, Message | ConvertTo-HTML -Head "<H2 style='color:green;'>Application Warning Event Logs</H2>" -body $a | Out-file $file -Append
|
||||
Get-EventLog -LogName System -Newest 10 -EntryType Error | Select TimeGenerated, EventID, Source, Message | ConvertTo-HTML -Head "<H2 style='color:green;'>System Error Event Logs</H2>" -body $a | Out-file $file -Append
|
||||
Get-EventLog -LogName System -Newest 10 -EntryType Warning | Select TimeGenerated, EventID, Source, Message | ConvertTo-HTML -Head "<H2 style='color:green;'>System Warning Event Logs</H2>" -body $a | Out-file $file -Append
|
||||
|
||||
#Get Stopped Services
|
||||
|
||||
Get-Service | Where {($_.Status) -eq "Stopped"} | Select Status, Name, DisplayName | ConvertTo-HTML -Head "<H2 style='color:green;'>Stopped Services</H2>" -body $a | Out-File $file -Append
|
||||
|
||||
#Get Processes and CPU
|
||||
|
||||
Get-Process | Select Id, ProcessName, CPU | ConvertTo-HTML -Head "<H2 style='color:green;'>Processes & CPU</H2>" -body $a | Out-File $file -Append
|
||||
|
||||
#Get Mapped Drives
|
||||
|
||||
Get-PSDrive | Where {$_.Used -ne $null} | Select Name, @{n='Used';e={[float]($_.Used/1GB)}}, @{n='Free';e={[float]($_.Free/1GB)}}, Root| ConvertTo-HTML -Head "<H2 style='color:green;'>Mapped Drives</H2>" -body $a | Out-File $file -Append
|
||||
|
||||
#Get Printers
|
||||
|
||||
Get-Printer | Select Name, Type, PortName | ConvertTo-HTML -Head "<H2 style='color:green;'>Printers</H2>" -body $a | Out-file $file -append
|
||||
|
||||
#Send Email
|
||||
|
||||
$fromaddress = "<insert your email address>"
|
||||
$toaddress = "<insert your email address>"
|
||||
$Subject = "System Report for Agent"
|
||||
$body = Get-Content $file
|
||||
$smtpserver = "<your smtp address>" #for example, smtp.office365.com
|
||||
$Password = "<insert your email password>"
|
||||
$port = <insert smtp port> #for example, 587
|
||||
|
||||
$message = new-object System.Net.Mail.MailMessage
|
||||
$message.IsBodyHTML = $true
|
||||
$message.From = $fromaddress
|
||||
$message.To.Add($toaddress)
|
||||
$message.Subject = $Subject
|
||||
$message.body = $body
|
||||
$smtp = new-object Net.Mail.SmtpClient($smtpserver, $port)
|
||||
$smtp.EnableSsl = $true
|
||||
$smtp.Credentials = New-Object System.Net.NetworkCredential($fromaddress, $Password)
|
||||
$smtp.Send($message)
|
||||
3
scripts_wip/Win_Network_AutoDiscovery_Disable.ps1
Normal file
3
scripts_wip/Win_Network_AutoDiscovery_Disable.ps1
Normal file
@@ -0,0 +1,3 @@
|
||||
# SET NETWORK DISCOVERY TO FALSE ON ALL CONNECTIONS
|
||||
|
||||
Get-NetFirewallRule -DisplayGroup 'Network Discovery'|Set-NetFirewallRule -Profile 'Private, Domain' -Enabled false -PassThru|select Name,DisplayName,Enabled,Profile|ft -a
|
||||
3
scripts_wip/Win_Network_PublicPrivate_Switch.ps1
Normal file
3
scripts_wip/Win_Network_PublicPrivate_Switch.ps1
Normal file
@@ -0,0 +1,3 @@
|
||||
# GET NETWORK STATUS PRIVATE/PUBLIC/HOME/WORK
|
||||
Get-NetConnectionProfile
|
||||
Set-NetConnectionProfile -InterfaceIndex <index number> -NetworkCategory Private
|
||||
9
scripts_wip/Win_SMB_version.ps1
Normal file
9
scripts_wip/Win_SMB_version.ps1
Normal file
@@ -0,0 +1,9 @@
|
||||
# GET SMBv2 SERVER STATUS
|
||||
Get-SmbServerConfiguration | Select EnableSMB2Protocol
|
||||
|
||||
# GET SMB Session versions
|
||||
Get-SmbSession | Select-Object -Property ClientComputerName,ClientUserName,Dialect,NumOpens
|
||||
|
||||
|
||||
#Install SMB1
|
||||
Get-WindowsOptionalFeature –Online –FeatureName SMB1Protocol
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_VERSION="122"
|
||||
SCRIPT_VERSION="123"
|
||||
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/update.sh'
|
||||
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
|
||||
YELLOW='\033[1;33m'
|
||||
@@ -307,5 +307,9 @@ if [[ "${CURRENT_MESH_VER}" != "${LATEST_MESH_VER}" ]] || [[ "$force" = true ]];
|
||||
sudo systemctl start meshcentral
|
||||
fi
|
||||
|
||||
# apply redis configuration
|
||||
sudo redis-cli config set appendonly yes
|
||||
sudo redis-cli config rewrite
|
||||
|
||||
rm -f $TMP_SETTINGS
|
||||
printf >&2 "${GREEN}Update finished!${NC}\n"
|
||||
21644
web/package-lock.json
generated
21644
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,23 +10,20 @@
|
||||
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.10.5",
|
||||
"apexcharts": "^3.23.1",
|
||||
"@quasar/extras": "^1.10.7",
|
||||
"apexcharts": "^3.27.1",
|
||||
"axios": "^0.21.1",
|
||||
"dotenv": "^8.2.0",
|
||||
"prismjs": "^1.22.0",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
"quasar": "^1.15.18",
|
||||
"vue-apexcharts": "^1.6.0",
|
||||
"vue-prism-editor": "^1.2.2"
|
||||
"dotenv": "^8.6.0",
|
||||
"prismjs": "^1.23.0",
|
||||
"qrcode.vue": "^3.2.2",
|
||||
"quasar": "^2.0.0",
|
||||
"vue-prism-editor": "^2.0.0-alpha.2",
|
||||
"vue3-apexcharts": "^1.4.0",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/app": "^2.2.7",
|
||||
"@quasar/cli": "^1.2.0",
|
||||
"core-js": "^3.13.0",
|
||||
"eslint-plugin-cypress": "^2.11.2",
|
||||
"flush-promises": "^1.0.2",
|
||||
"fs-extra": "^9.1.0"
|
||||
"@quasar/app": "^3.0.0",
|
||||
"@quasar/cli": "^1.2.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 3 Chrome versions",
|
||||
|
||||
@@ -69,15 +69,7 @@ module.exports = function () {
|
||||
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework
|
||||
framework: {
|
||||
iconSet: 'material-icons', // Quasar icon set
|
||||
lang: 'en-us', // Quasar language pack
|
||||
|
||||
// * 'auto' - Auto-import needed Quasar components & directives
|
||||
// (slightly higher compile time; next to minimum bundle size; most convenient)
|
||||
// * false - Manually specify what to import
|
||||
// (fastest compile time; minimum bundle size; most tedious)
|
||||
// * true - Import everything from Quasar
|
||||
// (not treeshaking Quasar; biggest bundle size; convenient)
|
||||
importStrategy: 'auto',
|
||||
lang: 'en-US', // Quasar language pack
|
||||
|
||||
// Quasar plugins
|
||||
plugins: [
|
||||
@@ -96,6 +88,9 @@ module.exports = function () {
|
||||
timeout: 2000,
|
||||
textColor: "white",
|
||||
actions: [{ icon: "close", color: "white" }]
|
||||
},
|
||||
loading: {
|
||||
delay: 50
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<div id="q-app">
|
||||
<router-view />
|
||||
</div>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -70,7 +68,7 @@ export default {
|
||||
background-color: #c9e6ff
|
||||
|
||||
.highlight-dark
|
||||
background-color: #343434
|
||||
background-color: #404040
|
||||
|
||||
.action-completed
|
||||
background-color: $positive
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Vue from 'vue';
|
||||
import axios from 'axios';
|
||||
import { Notify } from "quasar"
|
||||
|
||||
@@ -14,9 +13,9 @@ export const getBaseUrl = () => {
|
||||
}
|
||||
};
|
||||
|
||||
export default function ({ router, store }) {
|
||||
export default function ({ app, router, store }) {
|
||||
|
||||
Vue.prototype.$axios = axios;
|
||||
app.config.globalProperties.$axios = axios;
|
||||
|
||||
axios.interceptors.request.use(
|
||||
function (config) {
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<div style="width: 900px; max-width: 90vw">
|
||||
<q-card>
|
||||
<q-bar>
|
||||
<q-btn ref="refresh" @click="refresh" class="q-mr-sm" dense flat push icon="refresh" />User Administration
|
||||
<q-btn ref="refresh" @click="getUsers" class="q-mr-sm" dense flat push icon="refresh" />User Administration
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<div class="q-pa-md">
|
||||
@@ -14,55 +14,37 @@
|
||||
</div>
|
||||
<q-table
|
||||
dense
|
||||
:data="users"
|
||||
:rows="users"
|
||||
:columns="columns"
|
||||
:pagination.sync="pagination"
|
||||
:selected.sync="selected"
|
||||
selection="single"
|
||||
v-model:pagination="pagination"
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
hide-pagination
|
||||
:hide-bottom="!!selected"
|
||||
virtual-scroll
|
||||
>
|
||||
<!-- header slots -->
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<template v-for="col in props.cols">
|
||||
<q-th v-if="col.name === 'active'" auto-width :key="col.name">
|
||||
<q-icon name="power_settings_new" size="1.5em">
|
||||
<q-tooltip>Enable User</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
|
||||
<q-th v-else :key="col.name" :props="props">{{ col.label }}</q-th>
|
||||
</template>
|
||||
</q-tr>
|
||||
<template v-slot:header-cell-is_active="props">
|
||||
<q-th :props="props" auto-width>
|
||||
<q-icon name="power_settings_new" size="1.5em">
|
||||
<q-tooltip>Enable User</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<!-- No data Slot -->
|
||||
<template v-slot:no-data>
|
||||
<div class="full-width row flex-center q-gutter-sm">
|
||||
<span v-if="users.length === 0">No Users</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- body slots -->
|
||||
<template v-slot:body="props">
|
||||
<q-tr
|
||||
:props="props"
|
||||
class="cursor-pointer"
|
||||
:class="rowSelectedClass(props.row.id, selected)"
|
||||
@click="
|
||||
editUserId = props.row.id;
|
||||
props.selected = true;
|
||||
"
|
||||
@contextmenu="
|
||||
editUserId = props.row.id;
|
||||
props.selected = true;
|
||||
"
|
||||
>
|
||||
<q-tr :props="props" class="cursor-pointer" @dblclick="showEditUserModal(props.row)">
|
||||
<!-- context menu -->
|
||||
<q-menu context-menu>
|
||||
<q-list dense style="min-width: 200px">
|
||||
<q-item clickable v-close-popup @click="showEditUserModal(selected[0])" id="context-edit">
|
||||
<q-item clickable v-close-popup @click="showEditUserModal(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="edit" />
|
||||
</q-item-section>
|
||||
@@ -72,7 +54,6 @@
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="deleteUser(props.row)"
|
||||
id="context-delete"
|
||||
:disable="props.row.username === logged_in_user"
|
||||
>
|
||||
<q-item-section side>
|
||||
@@ -108,7 +89,7 @@
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="toggleEnabled(props.row)"
|
||||
@update:model-value="toggleEnabled(props.row)"
|
||||
v-model="props.row.is_active"
|
||||
:disable="props.row.username === logged_in_user"
|
||||
/>
|
||||
@@ -123,16 +104,6 @@
|
||||
</q-table>
|
||||
</div>
|
||||
</q-card>
|
||||
|
||||
<!-- user form modal -->
|
||||
<q-dialog v-model="showUserFormModal" @hide="closeUserFormModal">
|
||||
<UserForm :pk="editUserId" @close="closeUserFormModal" />
|
||||
</q-dialog>
|
||||
|
||||
<!-- user reset password form modal -->
|
||||
<q-dialog v-model="showResetPasswordModal" @hide="closeResetPasswordModal">
|
||||
<UserResetPasswordForm :pk="resetUserId" :username="resetUserName" @close="closeResetPasswordModal" />
|
||||
</q-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -144,111 +115,122 @@ import UserResetPasswordForm from "@/components/modals/admin/UserResetPasswordFo
|
||||
|
||||
export default {
|
||||
name: "AdminManager",
|
||||
components: { UserForm, UserResetPasswordForm },
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {
|
||||
showUserFormModal: false,
|
||||
showResetPasswordModal: false,
|
||||
editUserId: null,
|
||||
resetUserId: null,
|
||||
resetUserName: null,
|
||||
selected: [],
|
||||
users: [],
|
||||
columns: [
|
||||
{ name: "is_active", label: "Active", field: "is_active", align: "left" },
|
||||
{ name: "username", label: "Username", field: "username", align: "left" },
|
||||
{ name: "username", label: "Username", field: "username", align: "left", sortable: true },
|
||||
{
|
||||
name: "name",
|
||||
label: "Name",
|
||||
field: "name",
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
label: "Email",
|
||||
field: "email",
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: "last_login",
|
||||
label: "Last Login",
|
||||
field: "last_login",
|
||||
align: "left",
|
||||
sortable: true,
|
||||
},
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 9999,
|
||||
rowsPerPage: 0,
|
||||
sortBy: "username",
|
||||
descending: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getUsers() {
|
||||
this.$store.dispatch("admin/loadUsers");
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.get("/accounts/users/")
|
||||
.then(r => {
|
||||
this.users = r.data;
|
||||
this.$q.loading.hide();
|
||||
})
|
||||
.catch(() => {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
},
|
||||
clearRow() {
|
||||
this.selected = [];
|
||||
},
|
||||
refresh() {
|
||||
this.getUsers();
|
||||
this.clearRow();
|
||||
},
|
||||
deleteUser(data) {
|
||||
deleteUser(user) {
|
||||
this.$q
|
||||
.dialog({
|
||||
title: `Delete user ${data.username}?`,
|
||||
title: `Delete user ${user.username}?`,
|
||||
cancel: true,
|
||||
ok: { label: "Delete", color: "negative" },
|
||||
})
|
||||
.onOk(() => {
|
||||
this.$store
|
||||
.dispatch("admin/deleteUser", data.id)
|
||||
this.$axios
|
||||
.delete(`/accounts/${user.id}/users/`)
|
||||
.then(() => {
|
||||
this.notifySuccess(`User ${data.username} was deleted!`);
|
||||
this.getUsers();
|
||||
this.notifySuccess(`User ${user.username} was deleted!`);
|
||||
})
|
||||
.catch(e => {});
|
||||
});
|
||||
},
|
||||
showEditUserModal(data) {
|
||||
this.editUserId = data.id;
|
||||
this.showUserFormModal = true;
|
||||
},
|
||||
closeUserFormModal() {
|
||||
this.showUserFormModal = false;
|
||||
this.editUserId = null;
|
||||
this.refresh();
|
||||
showEditUserModal(user) {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: UserForm,
|
||||
componentProps: {
|
||||
user: user,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.getUsers();
|
||||
});
|
||||
},
|
||||
showAddUserModal() {
|
||||
this.editUserId = null;
|
||||
this.selected = [];
|
||||
this.showUserFormModal = true;
|
||||
this.$q
|
||||
.dialog({
|
||||
component: UserForm,
|
||||
})
|
||||
.onOk(() => {
|
||||
this.getUsers();
|
||||
});
|
||||
},
|
||||
toggleEnabled(user) {
|
||||
if (user.username === this.logged_in_user) {
|
||||
return;
|
||||
}
|
||||
let text = user.is_active ? "User enabled successfully" : "User disabled successfully";
|
||||
let text = !user.is_active ? "User enabled successfully" : "User disabled successfully";
|
||||
|
||||
const data = {
|
||||
id: user.id,
|
||||
is_active: user.is_active,
|
||||
is_active: !user.is_active,
|
||||
};
|
||||
|
||||
this.$store
|
||||
.dispatch("admin/editUser", data)
|
||||
.then(response => {
|
||||
this.$axios
|
||||
.put(`/accounts/${data.id}/users/`, data)
|
||||
.then(() => {
|
||||
this.notifySuccess(text);
|
||||
})
|
||||
.catch(e => {});
|
||||
},
|
||||
ResetPassword(user) {
|
||||
this.resetUserId = user.id;
|
||||
this.resetUserName = user.username;
|
||||
this.showResetPasswordModal = true;
|
||||
},
|
||||
closeResetPasswordModal(user) {
|
||||
this.resetUserId = null;
|
||||
this.resetUserName = null;
|
||||
this.showResetPasswordModal = false;
|
||||
this.$q
|
||||
.dialog({
|
||||
component: UserResetPasswordForm,
|
||||
componentProps: {
|
||||
user: user,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.getUsers();
|
||||
});
|
||||
},
|
||||
reset2FA(user) {
|
||||
const data = {
|
||||
@@ -262,23 +244,19 @@ export default {
|
||||
ok: { label: "Reset", color: "positive" },
|
||||
})
|
||||
.onOk(() => {
|
||||
this.$store.dispatch("admin/resetUserTOTP", data).then(response => {
|
||||
this.$axios.put("/accounts/users/reset_totp/", data).then(response => {
|
||||
this.notifySuccess(response.data, 4000);
|
||||
});
|
||||
});
|
||||
},
|
||||
rowSelectedClass(id, selected) {
|
||||
if (selected.length !== 0 && selected[0].id === id) return this.$q.dark.isActive ? "highlight-dark" : "highlight";
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
users: state => state.admin.users,
|
||||
logged_in_user: state => state.username,
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.refresh();
|
||||
this.getUsers();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -4,8 +4,8 @@
|
||||
dense
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="agents-tbl-sticky"
|
||||
:style="{ 'max-height': agentTableHeight }"
|
||||
:data="frame"
|
||||
:table-style="{ 'max-height': agentTableHeight }"
|
||||
:rows="frame"
|
||||
:filter="search"
|
||||
:filter-method="filterTable"
|
||||
:columns="columns"
|
||||
@@ -13,9 +13,10 @@
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
virtual-scroll
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
no-data-label="No Agents"
|
||||
:loading="agentTableLoading"
|
||||
>
|
||||
<!-- header slots -->
|
||||
<template v-slot:header-cell-smsalert="props">
|
||||
@@ -75,12 +76,12 @@
|
||||
</q-th>
|
||||
</template>
|
||||
<!-- body slots -->
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<template v-slot:body="props">
|
||||
<q-tr
|
||||
@contextmenu="agentRowSelected(props.row.id, props.row.agent_id)"
|
||||
@contextmenu="agentRowSelected(props.row.id)"
|
||||
:props="props"
|
||||
:class="rowSelectedClass(props.row.id)"
|
||||
@click="agentRowSelected(props.row.id, props.row.agent_id)"
|
||||
@click="agentRowSelected(props.row.id)"
|
||||
@dblclick="rowDoubleClicked(props.row.id)"
|
||||
>
|
||||
<!-- context menu -->
|
||||
@@ -288,7 +289,7 @@
|
||||
<q-checkbox
|
||||
v-else
|
||||
dense
|
||||
@input="overdueAlert('text', props.row.id, props.row.overdue_text_alert)"
|
||||
@update:model-value="overdueAlert('text', props.row.id, props.row.overdue_text_alert)"
|
||||
v-model="props.row.overdue_text_alert"
|
||||
/>
|
||||
</q-td>
|
||||
@@ -305,7 +306,7 @@
|
||||
<q-checkbox
|
||||
v-else
|
||||
dense
|
||||
@input="overdueAlert('email', props.row.id, props.row.overdue_email_alert)"
|
||||
@update:model-value="overdueAlert('email', props.row.id, props.row.overdue_email_alert)"
|
||||
v-model="props.row.overdue_email_alert"
|
||||
/>
|
||||
</q-td>
|
||||
@@ -322,7 +323,7 @@
|
||||
<q-checkbox
|
||||
v-else
|
||||
dense
|
||||
@input="overdueAlert('dashboard', props.row.id, props.row.overdue_dashboard_alert)"
|
||||
@update:model-value="overdueAlert('dashboard', props.row.id, props.row.overdue_dashboard_alert)"
|
||||
v-model="props.row.overdue_dashboard_alert"
|
||||
/>
|
||||
</q-td>
|
||||
@@ -353,20 +354,20 @@
|
||||
<span v-else>{{ props.row.logged_username }}</span>
|
||||
</q-td>
|
||||
<q-td :props="props" key="patchespending">
|
||||
<q-icon v-if="props.row.patches_pending" name="far fa-clock" color="primary">
|
||||
<q-icon v-if="props.row.has_patches_pending" name="far fa-clock" color="primary">
|
||||
<q-tooltip>Patches Pending</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
<q-td :props="props" key="pendingactions">
|
||||
<q-icon
|
||||
v-if="props.row.pending_actions !== 0"
|
||||
v-if="props.row.pending_actions_count !== 0"
|
||||
@click="showPendingActionsModal(props.row.id)"
|
||||
name="far fa-clock"
|
||||
size="1.4em"
|
||||
color="warning"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<q-tooltip>Pending Action Count: {{ props.row.pending_actions }}</q-tooltip>
|
||||
<q-tooltip>Pending Action Count: {{ props.row.pending_actions_count }}</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
<!-- needs reboot -->
|
||||
@@ -391,21 +392,18 @@
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
<q-inner-loading :showing="agentTableLoading">
|
||||
<q-spinner size="40px" color="primary" />
|
||||
</q-inner-loading>
|
||||
<!-- edit agent modal -->
|
||||
<q-dialog v-model="showEditAgentModal">
|
||||
<EditAgent @close="showEditAgentModal = false" @edited="agentEdited" />
|
||||
<EditAgent @close="showEditAgentModal = false" @edit="agentEdited" />
|
||||
</q-dialog>
|
||||
<!-- reboot later modal -->
|
||||
<q-dialog v-model="showRebootLaterModal">
|
||||
<RebootLater @close="showRebootLaterModal = false" @edited="agentEdited" />
|
||||
<RebootLater @close="showRebootLaterModal = false" @edit="agentEdited" />
|
||||
</q-dialog>
|
||||
<!-- pending actions modal -->
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-dialog v-model="showPendingActions" @hide="closePendingActionsModal">
|
||||
<PendingActions :agentpk="pendingActionAgentPk" @close="closePendingActionsModal" @edited="agentEdited" />
|
||||
<PendingActions :agentpk="pendingActionAgentPk" @close="closePendingActionsModal" @edit="agentEdited" />
|
||||
</q-dialog>
|
||||
</div>
|
||||
<!-- send command modal -->
|
||||
@@ -437,7 +435,8 @@ import RunScript from "@/components/modals/agents/RunScript";
|
||||
|
||||
export default {
|
||||
name: "AgentTable",
|
||||
props: ["frame", "columns", "tab", "userName", "search", "visibleColumns"],
|
||||
props: ["frame", "columns", "userName", "search", "visibleColumns"],
|
||||
emits: ["edit"],
|
||||
components: {
|
||||
EditAgent,
|
||||
RebootLater,
|
||||
@@ -495,8 +494,8 @@ export default {
|
||||
return rows.filter(row => {
|
||||
if (advancedFilter) {
|
||||
if (checks && !row.checks.has_failing_checks) return false;
|
||||
if (patches && !row.patches_pending) return false;
|
||||
if (actions && row.pending_actions === 0) return false;
|
||||
if (patches && !row.has_patches_pending) return false;
|
||||
if (actions && row.pending_actions_count === 0) return false;
|
||||
if (reboot && !row.needs_reboot) return false;
|
||||
if (availability === "online" && row.status !== "online") return false;
|
||||
else if (availability === "offline" && row.status !== "overdue") return false;
|
||||
@@ -594,7 +593,7 @@ export default {
|
||||
});
|
||||
},
|
||||
agentEdited() {
|
||||
this.$emit("refreshEdit");
|
||||
this.$emit("edit");
|
||||
},
|
||||
showPendingActionsModal(pk) {
|
||||
this.showPendingActions = true;
|
||||
@@ -718,12 +717,12 @@ export default {
|
||||
else if (category === "text") db_field = "overdue_text_alert";
|
||||
else if (category === "dashboard") db_field = "overdue_dashboard_alert";
|
||||
|
||||
const action = alert_action ? "enabled" : "disabled";
|
||||
const action = !alert_action ? "enabled" : "disabled";
|
||||
const data = {
|
||||
pk: pk,
|
||||
[db_field]: alert_action,
|
||||
[db_field]: !alert_action,
|
||||
};
|
||||
const alertColor = alert_action ? "positive" : "warning";
|
||||
const alertColor = !alert_action ? "positive" : "warning";
|
||||
this.$axios
|
||||
.post("/agents/overdueaction/", data)
|
||||
.then(r => {
|
||||
@@ -748,12 +747,13 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: PolicyAdd,
|
||||
parent: this,
|
||||
type: "agent",
|
||||
object: agent,
|
||||
componentProps: {
|
||||
type: "agent",
|
||||
object: agent,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.$emit("refreshEdit");
|
||||
this.$emit("edit");
|
||||
});
|
||||
},
|
||||
toggleMaintenance(agent) {
|
||||
@@ -766,14 +766,18 @@ export default {
|
||||
const text = agent.maintenance_mode ? "Maintenance mode was disabled" : "Maintenance mode was enabled";
|
||||
this.$store.dispatch("toggleMaintenanceMode", data).then(response => {
|
||||
this.notifySuccess(text);
|
||||
this.$emit("refreshEdit");
|
||||
this.$emit("edit");
|
||||
});
|
||||
},
|
||||
menuMaintenanceText(mode) {
|
||||
return mode ? "Disable Maintenance Mode" : "Enable Maintenance Mode";
|
||||
},
|
||||
rowSelectedClass(id) {
|
||||
if (this.selectedRow === id) return this.$q.dark.isActive ? "highlight-dark" : "highlight";
|
||||
if (id === this.selectedRow) {
|
||||
return this.$q.dark.isActive ? "highlight-dark" : "highlight";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
getURLActions() {
|
||||
this.$axios
|
||||
|
||||
@@ -58,24 +58,19 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getAlerts(showLoading = true) {
|
||||
if (showLoading) this.$q.loading.show();
|
||||
getAlerts() {
|
||||
this.$axios
|
||||
.patch("alerts/alerts/", { top: 10 })
|
||||
.then(r => {
|
||||
this.alertsCount = r.data.alerts_count;
|
||||
this.topAlerts = r.data.alerts;
|
||||
this.$q.loading.hide();
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
.catch(e => {});
|
||||
},
|
||||
showOverview() {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: AlertsOverview,
|
||||
parent: this,
|
||||
})
|
||||
.onDismiss(() => {
|
||||
this.getAlerts();
|
||||
@@ -144,15 +139,15 @@ export default {
|
||||
},
|
||||
pollAlerts() {
|
||||
setInterval(() => {
|
||||
this.getAlerts(false);
|
||||
this.getAlerts();
|
||||
}, 60 * 1 * 1000);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getAlerts(false);
|
||||
this.getAlerts();
|
||||
this.pollAlerts();
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
clearInterval(this.poll);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<q-btn ref="refresh" @click="refresh" class="q-mr-sm" dense flat push icon="refresh" />Alerts Manager
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<div class="q-pa-sm" style="min-height: 65vh; max-height: 65vh">
|
||||
@@ -15,9 +15,9 @@
|
||||
</div>
|
||||
<q-table
|
||||
dense
|
||||
:data="templates"
|
||||
:rows="templates"
|
||||
:columns="columns"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
hide-pagination
|
||||
@@ -98,7 +98,7 @@
|
||||
</q-menu>
|
||||
<!-- enabled checkbox -->
|
||||
<q-td>
|
||||
<q-checkbox dense @input="toggleEnabled(props.row)" v-model="props.row.is_active" />
|
||||
<q-checkbox dense @update:model-value="toggleEnabled(props.row)" v-model="props.row.is_active" />
|
||||
</q-td>
|
||||
<!-- agent settings -->
|
||||
<q-td>
|
||||
@@ -169,6 +169,7 @@ import AlertTemplateRelated from "@/components/modals/alerts/AlertTemplateRelate
|
||||
export default {
|
||||
name: "AlertsManager",
|
||||
mixins: [mixins],
|
||||
emits: ["hide", "ok", "cancel"],
|
||||
data() {
|
||||
return {
|
||||
selectedTemplate: null,
|
||||
@@ -256,8 +257,9 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: AlertTemplateForm,
|
||||
parent: this,
|
||||
alertTemplate: template,
|
||||
componentProps: {
|
||||
alertTemplate: template,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.refresh();
|
||||
@@ -268,7 +270,6 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: AlertTemplateForm,
|
||||
parent: this,
|
||||
})
|
||||
.onOk(() => {
|
||||
this.refresh();
|
||||
@@ -278,8 +279,9 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: AlertExclusions,
|
||||
parent: this,
|
||||
template: template,
|
||||
componentProps: {
|
||||
template: template,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.refresh();
|
||||
@@ -288,16 +290,17 @@ export default {
|
||||
showTemplateApplied(template) {
|
||||
this.$q.dialog({
|
||||
component: AlertTemplateRelated,
|
||||
parent: this,
|
||||
template: template,
|
||||
componentProps: {
|
||||
template: template,
|
||||
},
|
||||
});
|
||||
},
|
||||
toggleEnabled(template) {
|
||||
let text = template.is_active ? "Template enabled successfully" : "Template disabled successfully";
|
||||
let text = !template.is_active ? "Template enabled successfully" : "Template disabled successfully";
|
||||
|
||||
const data = {
|
||||
id: template.id,
|
||||
is_active: template.is_active,
|
||||
is_active: !template.is_active,
|
||||
};
|
||||
|
||||
this.$axios
|
||||
|
||||
@@ -91,7 +91,7 @@ export default {
|
||||
computed: {
|
||||
...mapGetters(["selectedAgentPk"]),
|
||||
assets() {
|
||||
return Object.freeze(this.$store.state.agentSummary.wmi_detail);
|
||||
return this.$store.state.agentSummary.wmi_detail;
|
||||
},
|
||||
os() {
|
||||
return this.assets.os;
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
<q-space />Audit Manager
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<div class="text-h6 q-pl-sm q-pt-sm">Filter</div>
|
||||
<div class="row">
|
||||
<div class="q-pa-sm col-1">
|
||||
<q-option-group v-model="filterType" :options="filterTypeOptions" color="primary" @input="clear" />
|
||||
<q-option-group v-model="filterType" :options="filterTypeOptions" color="primary" @update:model-value="clear" />
|
||||
</div>
|
||||
<div class="q-pa-sm col-2" v-if="filterType === 'agents'">
|
||||
<q-select
|
||||
@@ -35,6 +35,16 @@
|
||||
<q-item-section class="text-grey">No results</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
<template v-slot:option="scope">
|
||||
<q-item v-if="!scope.opt.category" v-bind="scope.itemProps" class="q-pl-lg">
|
||||
<q-item-section>
|
||||
<q-item-label v-html="scope.opt.label"></q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item-label v-if="scope.opt.category" v-bind="scope.itemProps" header class="q-pa-sm">{{
|
||||
scope.opt.category
|
||||
}}</q-item-label>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="q-pa-sm col-2" v-if="filterType === 'clients'">
|
||||
@@ -125,10 +135,10 @@
|
||||
class="audit-mgr-tbl-sticky"
|
||||
binary-state-sort
|
||||
title="Audit Logs"
|
||||
:data="auditLogs"
|
||||
:rows="auditLogs"
|
||||
:columns="columns"
|
||||
row-key="id"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
:rows-per-page-options="[25, 50, 100, 500, 1000]"
|
||||
:no-data-label="noDataText"
|
||||
@row-click="showDetails"
|
||||
@@ -158,6 +168,7 @@
|
||||
import AuditLogDetail from "@/components/modals/logs/AuditLogDetail";
|
||||
import mixins from "@/mixins/mixins";
|
||||
import { exportFile } from "quasar";
|
||||
import { formatAgentOptions } from "@/utils/format";
|
||||
|
||||
function wrapCsvValue(val, formatFn) {
|
||||
let formatted = formatFn !== void 0 ? formatFn(val) : val;
|
||||
@@ -327,7 +338,7 @@ export default {
|
||||
this.$axios
|
||||
.post(`logs/auditlogs/optionsfilter/`, data)
|
||||
.then(r => {
|
||||
this.agentOptions = Object.freeze(r.data.map(agent => agent.hostname));
|
||||
this.agentOptions = Object.freeze(formatAgentOptions(r.data));
|
||||
this.$q.loading.hide();
|
||||
})
|
||||
.catch(e => {
|
||||
@@ -438,7 +449,7 @@ export default {
|
||||
return this.searched ? "No data found. Try to refine you search" : "Click search to find audit logs";
|
||||
},
|
||||
},
|
||||
created() {
|
||||
mounted() {
|
||||
this.getClients();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="tabs-tbl-sticky"
|
||||
:style="{ 'max-height': tabsTableHeight }"
|
||||
:data="tasks"
|
||||
:rows="tasks"
|
||||
:columns="columns"
|
||||
:row-key="row => row.id"
|
||||
binary-state-sort
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
hide-bottom
|
||||
>
|
||||
<!-- header slots -->
|
||||
@@ -72,7 +72,7 @@
|
||||
<q-th auto-width :props="props"></q-th>
|
||||
</template>
|
||||
<!-- body slots -->
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<template v-slot:body="props">
|
||||
<q-tr @contextmenu="editTaskPk = props.row.id">
|
||||
<!-- context menu -->
|
||||
<q-menu context-menu>
|
||||
@@ -110,7 +110,9 @@
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="taskEnableorDisable(props.row.id, props.row.enabled, props.row.managed_by_policy)"
|
||||
@update:model-value="
|
||||
taskEnableorDisable(props.row.id, props.row.enabled, props.row.managed_by_policy)
|
||||
"
|
||||
v-model="props.row.enabled"
|
||||
:disable="props.row.managed_by_policy"
|
||||
/>
|
||||
@@ -129,7 +131,9 @@
|
||||
<q-checkbox
|
||||
v-else
|
||||
dense
|
||||
@input="taskAlert(props.row.id, 'Text', props.row.text_alert, props.row.managed_by_policy)"
|
||||
@update:model-value="
|
||||
taskAlert(props.row.id, 'Text', props.row.text_alert, props.row.managed_by_policy)
|
||||
"
|
||||
v-model="props.row.text_alert"
|
||||
:disable="props.row.managed_by_policy"
|
||||
/>
|
||||
@@ -148,7 +152,9 @@
|
||||
<q-checkbox
|
||||
v-else
|
||||
dense
|
||||
@input="taskAlert(props.row.id, 'Email', props.row.email_alert, props.row.managed_by_policy)"
|
||||
@update:model-value="
|
||||
taskAlert(props.row.id, 'Email', props.row.email_alert, props.row.managed_by_policy)
|
||||
"
|
||||
v-model="props.row.email_alert"
|
||||
:disable="props.row.managed_by_policy"
|
||||
/>
|
||||
@@ -167,7 +173,9 @@
|
||||
<q-checkbox
|
||||
v-else
|
||||
dense
|
||||
@input="taskAlert(props.row.id, 'Dashboard', props.row.dashboard_alert, props.row.managed_by_policy)"
|
||||
@update:model-value="
|
||||
taskAlert(props.row.id, 'Dashboard', props.row.dashboard_alert, props.row.managed_by_policy)
|
||||
"
|
||||
v-model="props.row.dashboard_alert"
|
||||
:disable="props.row.managed_by_policy"
|
||||
/>
|
||||
@@ -330,14 +338,14 @@ export default {
|
||||
};
|
||||
|
||||
if (alert_type === "Email") {
|
||||
data.email_alert = action;
|
||||
data.email_alert = !action;
|
||||
} else if (alert_type === "Text") {
|
||||
data.text_alert = action;
|
||||
data.text_alert = !action;
|
||||
} else {
|
||||
data.dashboard_alert = action;
|
||||
data.dashboard_alert = !action;
|
||||
}
|
||||
|
||||
const act = action ? "enabled" : "disabled";
|
||||
const act = !action ? "enabled" : "disabled";
|
||||
this.$axios
|
||||
.put(`/tasks/${pk}/automatedtasks/`, data)
|
||||
.then(r => {
|
||||
@@ -354,16 +362,18 @@ export default {
|
||||
showScriptOutput(script) {
|
||||
this.$q.dialog({
|
||||
component: ScriptOutput,
|
||||
parent: this,
|
||||
scriptInfo: script,
|
||||
componentProps: {
|
||||
scriptInfo: script,
|
||||
},
|
||||
});
|
||||
},
|
||||
showEditTask(task) {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: EditAutomatedTask,
|
||||
parent: this,
|
||||
task: task,
|
||||
componentProps: {
|
||||
task: task,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.refreshTasks(this.automatedTasks.pk);
|
||||
|
||||
@@ -60,11 +60,11 @@
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="tabs-tbl-sticky"
|
||||
:style="{ 'max-height': tabsTableHeight }"
|
||||
:data="checks"
|
||||
:rows="checks"
|
||||
:columns="columns"
|
||||
:row-key="row => row.id + row.check_type"
|
||||
binary-state-sort
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
hide-bottom
|
||||
>
|
||||
<!-- header slots -->
|
||||
@@ -96,7 +96,7 @@
|
||||
<q-th auto-width :props="props"></q-th>
|
||||
</template>
|
||||
<!-- body slots -->
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<template v-slot:body="props">
|
||||
<q-tr @contextmenu="checkpk = props.row.id">
|
||||
<!-- context menu -->
|
||||
<q-menu context-menu>
|
||||
@@ -151,7 +151,9 @@
|
||||
<q-checkbox
|
||||
v-else
|
||||
dense
|
||||
@input="checkAlert(props.row.id, 'Text', props.row.text_alert, props.row.managed_by_policy)"
|
||||
@update:model-value="
|
||||
checkAlert(props.row.id, 'Text', props.row.text_alert, props.row.managed_by_policy)
|
||||
"
|
||||
v-model="props.row.text_alert"
|
||||
:disable="props.row.managed_by_policy"
|
||||
/>
|
||||
@@ -170,7 +172,9 @@
|
||||
<q-checkbox
|
||||
v-else
|
||||
dense
|
||||
@input="checkAlert(props.row.id, 'Email', props.row.email_alert, props.row.managed_by_policy)"
|
||||
@update:model-value="
|
||||
checkAlert(props.row.id, 'Email', props.row.email_alert, props.row.managed_by_policy)
|
||||
"
|
||||
v-model="props.row.email_alert"
|
||||
:disable="props.row.managed_by_policy"
|
||||
/>
|
||||
@@ -189,7 +193,9 @@
|
||||
<q-checkbox
|
||||
v-else
|
||||
dense
|
||||
@input="checkAlert(props.row.id, 'Dashboard', props.row.dashboard_alert, props.row.managed_by_policy)"
|
||||
@update:model-value="
|
||||
checkAlert(props.row.id, 'Dashboard', props.row.dashboard_alert, props.row.managed_by_policy)
|
||||
"
|
||||
v-model="props.row.dashboard_alert"
|
||||
:disable="props.row.managed_by_policy"
|
||||
/>
|
||||
@@ -326,6 +332,7 @@ import CheckGraph from "@/components/graphs/CheckGraph";
|
||||
|
||||
export default {
|
||||
name: "ChecksTab",
|
||||
emits: ["edit"],
|
||||
components: {
|
||||
DiskSpaceCheck,
|
||||
MemCheck,
|
||||
@@ -422,16 +429,16 @@ export default {
|
||||
|
||||
const data = {};
|
||||
if (alert_type === "Email") {
|
||||
data.email_alert = action;
|
||||
data.email_alert = !action;
|
||||
} else if (alert_type === "Text") {
|
||||
data.text_alert = action;
|
||||
data.text_alert = !action;
|
||||
} else {
|
||||
data.dashboard_alert = action;
|
||||
data.dashboard_alert = !action;
|
||||
}
|
||||
|
||||
data.check_alert = true;
|
||||
const act = action ? "enabled" : "disabled";
|
||||
const color = action ? "positive" : "warning";
|
||||
const act = !action ? "enabled" : "disabled";
|
||||
const color = !action ? "positive" : "warning";
|
||||
this.$axios
|
||||
.patch(`/checks/${id}/check/`, data)
|
||||
.then(r => {
|
||||
@@ -452,14 +459,14 @@ export default {
|
||||
this.$axios
|
||||
.patch(`/checks/${check}/check/`, data)
|
||||
.then(r => {
|
||||
this.$emit("refreshEdit");
|
||||
this.$emit("edit");
|
||||
this.$store.dispatch("loadChecks", this.selectedAgentPk);
|
||||
this.notifySuccess("The check was reset");
|
||||
})
|
||||
.catch(e => {});
|
||||
},
|
||||
onRefresh(id) {
|
||||
this.$emit("refreshEdit");
|
||||
this.$emit("edit");
|
||||
this.$store.dispatch("loadChecks", id);
|
||||
this.$store.dispatch("loadAutomatedTasks", id);
|
||||
},
|
||||
@@ -498,15 +505,17 @@ export default {
|
||||
showCheckGraphModal(check) {
|
||||
this.$q.dialog({
|
||||
component: CheckGraph,
|
||||
parent: this,
|
||||
check: check,
|
||||
componentProps: {
|
||||
check: check,
|
||||
},
|
||||
});
|
||||
},
|
||||
showScriptOutput(script) {
|
||||
this.$q.dialog({
|
||||
component: ScriptOutput,
|
||||
parent: this,
|
||||
scriptInfo: script,
|
||||
componentProps: {
|
||||
scriptInfo: script,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<q-btn @click="getClients" class="q-mr-sm" dense flat push icon="refresh" />Clients Manager
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<div class="q-pa-sm" style="min-height: 65vh; max-height: 65vh">
|
||||
@@ -15,9 +15,9 @@
|
||||
</div>
|
||||
<q-table
|
||||
dense
|
||||
:data="clients"
|
||||
:rows="clients"
|
||||
:columns="columns"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
hide-pagination
|
||||
@@ -92,6 +92,7 @@ import SitesTable from "@/components/modals/clients/SitesTable";
|
||||
|
||||
export default {
|
||||
name: "ClientsManager",
|
||||
emits: ["hide", "ok", "cancel"],
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {
|
||||
@@ -124,9 +125,10 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: DeleteClient,
|
||||
parent: this,
|
||||
object: client,
|
||||
type: "client",
|
||||
componentProps: {
|
||||
object: client,
|
||||
type: "client",
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.getClients();
|
||||
@@ -136,8 +138,9 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: ClientsForm,
|
||||
parent: this,
|
||||
client: client,
|
||||
componentProps: {
|
||||
client: client,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.getClients();
|
||||
@@ -147,7 +150,6 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: ClientsForm,
|
||||
parent: this,
|
||||
})
|
||||
.onOk(() => {
|
||||
this.getClients();
|
||||
@@ -157,8 +159,9 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: SitesForm,
|
||||
parent: this,
|
||||
client: client.id,
|
||||
componentProps: {
|
||||
client: client.id,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.getClients();
|
||||
@@ -167,8 +170,9 @@ export default {
|
||||
showSitesTable(client) {
|
||||
this.$q.dialog({
|
||||
component: SitesTable,
|
||||
parent: this,
|
||||
client: client,
|
||||
componentProps: {
|
||||
client: client,
|
||||
},
|
||||
});
|
||||
},
|
||||
show() {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
:label="field.name"
|
||||
:type="field.type === 'text' ? 'text' : 'number'"
|
||||
:hint="hintText(field)"
|
||||
:value="value"
|
||||
@input="value => $emit('input', value)"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="value => $emit('update:modelValue', value)"
|
||||
:rules="[...validationRules]"
|
||||
reactive-rules
|
||||
autogrow
|
||||
@@ -20,8 +20,8 @@
|
||||
ref="input"
|
||||
:label="field.name"
|
||||
:hint="hintText(field)"
|
||||
:value="value"
|
||||
@input="value => $emit('input', value)"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="value => $emit('update:modelValue', value)"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
@@ -31,15 +31,19 @@
|
||||
:hint="hintText(field)"
|
||||
outlined
|
||||
dense
|
||||
:value="value"
|
||||
@input="value => $emit('input', value)"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="value => $emit('update:modelValue', value)"
|
||||
:rules="[...validationRules]"
|
||||
reactive-rules
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon name="event" class="cursor-pointer">
|
||||
<q-popup-proxy transition-show="scale" transition-hide="scale">
|
||||
<q-date :value="value" @input="value => $emit('input', value)" mask="YYYY-MM-DD HH:mm">
|
||||
<q-date
|
||||
:model-value="modelValue"
|
||||
@update:model-value="value => $emit('update:modelValue', value)"
|
||||
mask="YYYY-MM-DD HH:mm"
|
||||
>
|
||||
<div class="row items-center justify-end">
|
||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
||||
</div>
|
||||
@@ -48,7 +52,11 @@
|
||||
</q-icon>
|
||||
<q-icon name="access_time" class="cursor-pointer">
|
||||
<q-popup-proxy transition-show="scale" transition-hide="scale">
|
||||
<q-time :value="value" @input="value => $emit('input', value)" mask="YYYY-MM-DD HH:mm">
|
||||
<q-time
|
||||
:model-value="modelValue"
|
||||
@update:model-value="value => $emit('update:modelValue', value)"
|
||||
mask="YYYY-MM-DD HH:mm"
|
||||
>
|
||||
<div class="row items-center justify-end">
|
||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
||||
</div>
|
||||
@@ -61,8 +69,8 @@
|
||||
<q-select
|
||||
v-else-if="field.type === 'single' || field.type === 'multiple'"
|
||||
ref="input"
|
||||
:value="value"
|
||||
@input="value => $emit('input', value)"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="value => $emit('update:modelValue', value)"
|
||||
outlined
|
||||
dense
|
||||
:hint="hintText(field)"
|
||||
@@ -78,7 +86,7 @@
|
||||
<script>
|
||||
export default {
|
||||
name: "CustomField",
|
||||
props: ["field", "value"],
|
||||
props: ["field", "modelValue"],
|
||||
methods: {
|
||||
validate(...args) {
|
||||
return this.$refs.input.validate(...args);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<q-space />Manage Deployments
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary" />
|
||||
<q-tooltip class="bg-white text-primary" />
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<div class="row">
|
||||
@@ -21,14 +21,14 @@
|
||||
class="audit-mgr-tbl-sticky"
|
||||
binary-state-sort
|
||||
virtual-scroll
|
||||
:data="deployments"
|
||||
:rows="deployments"
|
||||
:columns="columns"
|
||||
:visible-columns="visibleColumns"
|
||||
row-key="id"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
no-data-label="No Deployments"
|
||||
>
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<template v-slot:body="props">
|
||||
<q-tr>
|
||||
<q-td key="client" :props="props">{{ props.row.client_name }}</q-td>
|
||||
<q-td key="site" :props="props">{{ props.row.site_name }}</q-td>
|
||||
@@ -40,7 +40,7 @@
|
||||
<q-td key="created" :props="props">{{ props.row.created }}</q-td>
|
||||
<q-td key="flags" :props="props"
|
||||
><q-badge color="grey-8" label="View Flags" />
|
||||
<q-tooltip content-style="font-size: 12px">{{ props.row.install_flags }}</q-tooltip>
|
||||
<q-tooltip style="font-size: 12px">{{ props.row.install_flags }}</q-tooltip>
|
||||
</q-td>
|
||||
<q-td key="link" :props="props"
|
||||
><q-btn size="sm" color="primary" icon="content_copy" label="Copy" @click="copyLink(props)"
|
||||
@@ -53,7 +53,7 @@
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
<q-dialog v-model="showNewDeployment">
|
||||
<NewDeployment @close="showNewDeployment = false" @added="getDeployments" />
|
||||
<NewDeployment @close="showNewDeployment = false" @add="getDeployments" />
|
||||
</q-dialog>
|
||||
</q-card>
|
||||
</template>
|
||||
@@ -129,7 +129,7 @@ export default {
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
mounted() {
|
||||
this.getDeployments();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
v-model="days"
|
||||
:options="lastDays"
|
||||
:label="showDays"
|
||||
@input="getEventLog"
|
||||
@update:model-value="getEventLog"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-7"></div>
|
||||
@@ -21,9 +21,9 @@
|
||||
dense
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="remote-bg-tbl-sticky"
|
||||
:data="events"
|
||||
:rows="events"
|
||||
:columns="columns"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
:filter="filter"
|
||||
row-key="uid"
|
||||
binary-state-sort
|
||||
@@ -33,9 +33,15 @@
|
||||
<template v-slot:top>
|
||||
<q-btn dense flat push @click="getEventLog" icon="refresh" />
|
||||
<q-space />
|
||||
<q-radio v-model="logType" color="cyan" val="Application" label="Application" @input="getEventLog" />
|
||||
<q-radio v-model="logType" color="cyan" val="System" label="System" @input="getEventLog" />
|
||||
<q-radio v-model="logType" color="cyan" val="Security" label="Security" @input="getEventLog" />
|
||||
<q-radio
|
||||
v-model="logType"
|
||||
color="cyan"
|
||||
val="Application"
|
||||
label="Application"
|
||||
@update:model-value="getEventLog"
|
||||
/>
|
||||
<q-radio v-model="logType" color="cyan" val="System" label="System" @update:model-value="getEventLog" />
|
||||
<q-radio v-model="logType" color="cyan" val="Security" label="Security" @update:model-value="getEventLog" />
|
||||
<q-space />
|
||||
<q-input v-model="filter" outlined label="Search" dense clearable>
|
||||
<template v-slot:prepend>
|
||||
@@ -43,13 +49,13 @@
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td>{{ props.row.eventType }}</q-td>
|
||||
<q-td>{{ props.row.source }}</q-td>
|
||||
<q-td>{{ props.row.eventID }}</q-td>
|
||||
<q-td>{{ props.row.time }}</q-td>
|
||||
<q-td @click.native="showFullMsg(props.row.message)">
|
||||
<q-td @click="showFullMsg(props.row.message)">
|
||||
<span style="cursor: pointer; text-decoration: underline" class="text-primary">{{
|
||||
formatMessage(props.row.message)
|
||||
}}</span>
|
||||
@@ -121,7 +127,7 @@ export default {
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
mounted() {
|
||||
this.getEventLog();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
<!-- Update Agents Modal -->
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-dialog v-model="showUpdateAgentsModal" maximized transition-show="slide-up" transition-hide="slide-down">
|
||||
<UpdateAgents @close="showUpdateAgentsModal = false" @edited="edited" />
|
||||
<UpdateAgents @close="showUpdateAgentsModal = false" @edit="edited" />
|
||||
</q-dialog>
|
||||
</div>
|
||||
<!-- Script Manager -->
|
||||
@@ -237,6 +237,7 @@ import PermissionsManager from "@/components/PermissionsManager";
|
||||
|
||||
export default {
|
||||
name: "FileBar",
|
||||
emits: ["edit"],
|
||||
components: {
|
||||
LogModal,
|
||||
PendingActions,
|
||||
@@ -306,41 +307,35 @@ export default {
|
||||
showAutomationManager() {
|
||||
this.$q.dialog({
|
||||
component: AutomationManager,
|
||||
parent: this,
|
||||
});
|
||||
},
|
||||
showAlertsManager() {
|
||||
this.$q.dialog({
|
||||
component: AlertsManager,
|
||||
parent: this,
|
||||
});
|
||||
},
|
||||
showClientsManager() {
|
||||
this.$q.dialog({
|
||||
component: ClientsManager,
|
||||
parent: this,
|
||||
});
|
||||
},
|
||||
showAddClientModal() {
|
||||
this.$q.dialog({
|
||||
component: ClientsForm,
|
||||
parent: this,
|
||||
});
|
||||
},
|
||||
showAddSiteModal() {
|
||||
this.$q.dialog({
|
||||
component: SitesForm,
|
||||
parent: this,
|
||||
});
|
||||
},
|
||||
showPermissionsManager() {
|
||||
this.$q.dialog({
|
||||
component: PermissionsManager,
|
||||
parent: this,
|
||||
});
|
||||
},
|
||||
edited() {
|
||||
this.$emit("edited");
|
||||
this.$emit("edit");
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
grid
|
||||
class="tabs-tbl-sticky"
|
||||
:style="{ 'max-height': tabsTableHeight }"
|
||||
:data="notes"
|
||||
:rows="notes"
|
||||
:columns="columns"
|
||||
:visible-columns="visibleColumns"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
row-key="id"
|
||||
:rows-per-page-options="[0]"
|
||||
hide-bottom
|
||||
|
||||
@@ -20,14 +20,14 @@
|
||||
class="audit-mgr-tbl-sticky"
|
||||
binary-state-sort
|
||||
virtual-scroll
|
||||
:data="roles"
|
||||
:rows="roles"
|
||||
:columns="columns"
|
||||
:visible-columns="visibleColumns"
|
||||
row-key="id"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
no-data-label="No Roles"
|
||||
>
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<template v-slot:body="props">
|
||||
<q-tr>
|
||||
<q-td key="name" :props="props">{{ props.row.name }}</q-td>
|
||||
<q-td class="q-pa-md q-gutter-sm" key="actions" :props="props"
|
||||
@@ -53,6 +53,7 @@ import RolesForm from "@/components/modals/admin/RolesForm";
|
||||
|
||||
export default {
|
||||
name: "PermissionsManager",
|
||||
emits: ["hide", "ok", "cancel"],
|
||||
mixins: [mixins],
|
||||
components: { RolesForm },
|
||||
data() {
|
||||
@@ -119,7 +120,7 @@ export default {
|
||||
this.$emit("hide");
|
||||
},
|
||||
},
|
||||
created() {
|
||||
mounted() {
|
||||
this.getRoles();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
dense
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="remote-bg-tbl-sticky"
|
||||
:data="procs"
|
||||
:rows="procs"
|
||||
:columns="columns"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
:filter="filter"
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
@@ -41,7 +41,7 @@
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-menu context-menu>
|
||||
<q-list dense style="min-width: 200px">
|
||||
@@ -204,17 +204,16 @@ export default {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
clearInterval(this.polling);
|
||||
},
|
||||
created() {
|
||||
mounted() {
|
||||
this.getAgent();
|
||||
// disable loading bar
|
||||
this.$q.loadingBar.setDefaults({ size: "0px" });
|
||||
|
||||
this.getProcesses();
|
||||
},
|
||||
mounted() {
|
||||
|
||||
this.refreshProcs();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<q-btn @click="getScripts" class="q-mr-sm" dense flat push icon="refresh" />Script Manager
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<div class="q-pa-md">
|
||||
@@ -94,9 +94,9 @@
|
||||
:filter="search"
|
||||
no-connectors
|
||||
node-key="id"
|
||||
:expanded.sync="expanded"
|
||||
v-model:expanded="expanded"
|
||||
@update:selected="nodeSelected"
|
||||
:selected.sync="selected"
|
||||
v-model:selected="selected"
|
||||
no-results-label="No Scripts Found"
|
||||
no-nodes-label="No Scripts Found"
|
||||
>
|
||||
@@ -183,10 +183,10 @@
|
||||
dense
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="settings-tbl-sticky"
|
||||
:data="visibleScripts"
|
||||
:rows="visibleScripts"
|
||||
:columns="columns"
|
||||
:visible-columns="visibleColumns"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
:filter="search"
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
@@ -205,7 +205,7 @@
|
||||
</template>
|
||||
|
||||
<template v-slot:no-data> No Scripts Found </template>
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<template v-slot:body="props">
|
||||
<!-- Table View -->
|
||||
<q-tr
|
||||
:class="`${rowSelectedClass(props.row.id)} cursor-pointer`"
|
||||
@@ -289,7 +289,7 @@
|
||||
<q-td>
|
||||
<span v-if="props.row.args.length > 0">
|
||||
{{ truncateText(props.row.args.toString()) }}
|
||||
<q-tooltip v-if="props.row.args.toString().length >= 60" content-style="font-size: 12px">
|
||||
<q-tooltip v-if="props.row.args.toString().length >= 60" style="font-size: 12px">
|
||||
{{ props.row.args }}
|
||||
</q-tooltip>
|
||||
</span>
|
||||
@@ -298,7 +298,7 @@
|
||||
<q-td>{{ props.row.category }}</q-td>
|
||||
<q-td>
|
||||
{{ truncateText(props.row.description) }}
|
||||
<q-tooltip v-if="props.row.description.length >= 60" content-style="font-size: 12px">{{
|
||||
<q-tooltip v-if="props.row.description.length >= 60" style="font-size: 12px">{{
|
||||
props.row.description
|
||||
}}</q-tooltip>
|
||||
</q-td>
|
||||
@@ -314,7 +314,7 @@
|
||||
:script="selectedScript"
|
||||
:categories="categories"
|
||||
@close="showScriptUploadModal = false"
|
||||
@added="getScripts"
|
||||
@add="getScripts"
|
||||
/>
|
||||
</q-dialog>
|
||||
</div>
|
||||
@@ -417,9 +417,10 @@ export default {
|
||||
viewCode(script) {
|
||||
this.$q.dialog({
|
||||
component: ScriptFormModal,
|
||||
parent: this,
|
||||
script: script,
|
||||
readonly: true,
|
||||
componentProps: {
|
||||
script: script,
|
||||
readonly: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
favoriteScript(script) {
|
||||
@@ -482,9 +483,10 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: ScriptFormModal,
|
||||
parent: this,
|
||||
categories: this.categories,
|
||||
readonly: false,
|
||||
componentProps: {
|
||||
categories: this.categories,
|
||||
readonly: false,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.getScripts();
|
||||
@@ -494,10 +496,11 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: ScriptFormModal,
|
||||
parent: this,
|
||||
script: script,
|
||||
categories: this.categories,
|
||||
readonly: false,
|
||||
componentProps: {
|
||||
script: script,
|
||||
categories: this.categories,
|
||||
readonly: false,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.getScripts();
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
dense
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="remote-bg-tbl-sticky"
|
||||
:data="servicesData"
|
||||
:rows="servicesData"
|
||||
:columns="columns"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
:filter="filter"
|
||||
row-key="display_name"
|
||||
binary-state-sort
|
||||
@@ -21,7 +21,7 @@
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-menu context-menu>
|
||||
<q-list dense style="min-width: 200px">
|
||||
@@ -88,7 +88,7 @@
|
||||
<div class="col-3">Startup type:</div>
|
||||
<div class="col-5">
|
||||
<q-select
|
||||
@input="startupTypeChanged"
|
||||
@update:model-value="startupTypeChanged"
|
||||
dense
|
||||
options-dense
|
||||
outlined
|
||||
@@ -322,7 +322,7 @@ export default {
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
mounted() {
|
||||
this.getServices();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
class="tabs-tbl-sticky"
|
||||
:style="{ 'max-height': tabsTableHeight }"
|
||||
dense
|
||||
:data="software"
|
||||
:rows="software"
|
||||
:columns="columns"
|
||||
:filter="filter"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
binary-state-sort
|
||||
hide-bottom
|
||||
row-key="id"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<SummaryTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="checks" class="q-pb-xs q-pt-none">
|
||||
<ChecksTab @refreshEdit="$emit('refreshEdit')" />
|
||||
<ChecksTab @edit="$emit('edit')" />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="tasks" class="q-pb-xs q-pt-none">
|
||||
<AutomatedTasksTab />
|
||||
@@ -56,6 +56,7 @@ import AssetsTab from "@/components/AssetsTab";
|
||||
import NotesTab from "@/components/NotesTab";
|
||||
export default {
|
||||
name: "SubTableTabs",
|
||||
emits: ["edit"],
|
||||
components: {
|
||||
SummaryTab,
|
||||
ChecksTab,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div v-if="!selectedAgentPk">No agent selected</div>
|
||||
<div v-else-if="Object.keys(sortedUpdates).length === 0">No Patches</div>
|
||||
<div v-else class="q-pa-xs">
|
||||
<q-btn dense flat push @click="refreshUpdates(updates.pk)" icon="refresh" class="q-mr-sm"/>
|
||||
<q-btn dense flat push @click="refreshUpdates(updates.pk)" icon="refresh" class="q-mr-sm" />
|
||||
<span v-if="summary.patches_last_installed" class="text-bold">
|
||||
Patches last installed: {{ summary.patches_last_installed }}
|
||||
</span>
|
||||
@@ -12,10 +12,10 @@
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="tabs-tbl-sticky"
|
||||
:style="{ 'max-height': tabsTableHeight }"
|
||||
:data="sortedUpdates"
|
||||
:rows="sortedUpdates"
|
||||
:columns="columns"
|
||||
:visible-columns="visibleColumns"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
:filter="filter"
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
@@ -23,7 +23,7 @@
|
||||
virtual-scroll
|
||||
:rows-per-page-options="[0]"
|
||||
>
|
||||
<template slot="body" slot-scope="props" :props="props">
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-menu context-menu>
|
||||
<q-list dense style="min-width: 100px">
|
||||
@@ -73,9 +73,7 @@
|
||||
<q-td>{{ formatSeverity(props.row.severity) }}</q-td>
|
||||
<q-td>{{ formatMessage(props.row.title) }}</q-td>
|
||||
<q-td
|
||||
@click.native="
|
||||
showFullMsg(props.row.title, props.row.description, props.row.more_info_urls, props.row.categories)
|
||||
"
|
||||
@click="showFullMsg(props.row.title, props.row.description, props.row.more_info_urls, props.row.categories)"
|
||||
>
|
||||
<span style="cursor: pointer; text-decoration: underline" class="text-primary">{{
|
||||
formatMessage(props.row.description)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="scroll" :style="{'max-height': tabsTableHeight}">
|
||||
<div class="scroll" :style="{ 'max-height': tabsTableHeight }">
|
||||
<div v-for="i in info" :key="i + randomID()">
|
||||
<div v-for="j in i" :key="j + randomID()">
|
||||
<div v-for="(v, k) in j" :key="v + randomID()">
|
||||
@@ -13,8 +13,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uid } from "quasar";
|
||||
import { mapGetters } from "vuex";
|
||||
import { uid } from "quasar";
|
||||
|
||||
export default {
|
||||
name: "WmiDetail",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<q-btn ref="refresh" @click="refresh" class="q-mr-sm" dense flat push icon="refresh" />Automation Manager
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<q-card-section>
|
||||
@@ -27,9 +27,9 @@
|
||||
<q-table
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="tabs-tbl-sticky"
|
||||
:data="policies"
|
||||
:rows="policies"
|
||||
:columns="columns"
|
||||
:pagination.sync="pagination"
|
||||
v-model:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
dense
|
||||
row-key="id"
|
||||
@@ -135,11 +135,19 @@
|
||||
</q-menu>
|
||||
<!-- enabled checkbox -->
|
||||
<q-td>
|
||||
<q-checkbox dense @input="toggleCheckbox(props.row, 'Active')" v-model="props.row.active" />
|
||||
<q-checkbox
|
||||
dense
|
||||
@update:model-value="toggleCheckbox(props.row, 'Active')"
|
||||
v-model="props.row.active"
|
||||
/>
|
||||
</q-td>
|
||||
<!-- enforced checkbox -->
|
||||
<q-td>
|
||||
<q-checkbox dense @input="toggleCheckbox(props.row, 'Enforced')" v-model="props.row.enforced" />
|
||||
<q-checkbox
|
||||
dense
|
||||
@update:model-value="toggleCheckbox(props.row, 'Enforced')"
|
||||
v-model="props.row.enforced"
|
||||
/>
|
||||
</q-td>
|
||||
<q-td>
|
||||
{{ props.row.name }}
|
||||
@@ -248,6 +256,7 @@ import PolicyAutomatedTasksTab from "@/components/automation/PolicyAutomatedTask
|
||||
|
||||
export default {
|
||||
name: "AutomationManager",
|
||||
emits: ["hide", "ok", "cancel"],
|
||||
components: { PolicyChecksTab, PolicyAutomatedTasksTab },
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
@@ -353,21 +362,20 @@ export default {
|
||||
showRelations(policy) {
|
||||
this.$q.dialog({
|
||||
component: RelationsView,
|
||||
parent: this,
|
||||
policy: policy,
|
||||
componentProps: {
|
||||
policy: policy,
|
||||
},
|
||||
});
|
||||
},
|
||||
showPolicyOverview() {
|
||||
this.$q.dialog({
|
||||
component: PolicyOverview,
|
||||
parent: this,
|
||||
});
|
||||
},
|
||||
showAddPolicyForm(policy = undefined) {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: PolicyForm,
|
||||
parent: this,
|
||||
})
|
||||
.onOk(() => {
|
||||
this.refresh();
|
||||
@@ -377,8 +385,9 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: PolicyForm,
|
||||
parent: this,
|
||||
copyPolicy: policy,
|
||||
componentProps: {
|
||||
copyPolicy: policy,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.refresh();
|
||||
@@ -388,8 +397,9 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: PolicyForm,
|
||||
parent: this,
|
||||
policy: policy,
|
||||
componentProps: {
|
||||
policy: policy,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.refresh();
|
||||
@@ -399,9 +409,10 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: AlertTemplateAdd,
|
||||
parent: this,
|
||||
type: "policy",
|
||||
object: policy,
|
||||
componentProps: {
|
||||
type: "policy",
|
||||
object: policy,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.refresh();
|
||||
@@ -411,11 +422,12 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: DialogWrapper,
|
||||
parent: this,
|
||||
title: policy.winupdatepolicy.length > 0 ? "Edit Patch Policy" : "Add Patch Policy",
|
||||
vuecomponent: PatchPolicyForm,
|
||||
componentProps: {
|
||||
policy: policy,
|
||||
title: policy.winupdatepolicy.length > 0 ? "Edit Patch Policy" : "Add Patch Policy",
|
||||
vuecomponent: PatchPolicyForm,
|
||||
componentProps: {
|
||||
policy: policy,
|
||||
},
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
@@ -426,8 +438,9 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: PolicyExclusions,
|
||||
parent: this,
|
||||
policy: policy,
|
||||
componentProps: {
|
||||
policy: policy,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.refresh();
|
||||
@@ -454,20 +467,20 @@ export default {
|
||||
this.$q.loading.show();
|
||||
let text = "";
|
||||
|
||||
if (type === "Active") {
|
||||
text = policy.active ? "Policy enabled successfully" : "Policy disabled successfully";
|
||||
} else if (type === "Enforced") {
|
||||
text = policy.enforced ? "Policy enforced successfully" : "Policy enforcement disabled";
|
||||
}
|
||||
|
||||
const data = {
|
||||
id: policy.id,
|
||||
name: policy.name,
|
||||
desc: policy.desc,
|
||||
active: policy.active,
|
||||
enforced: policy.enforced,
|
||||
};
|
||||
|
||||
if (type === "Active") {
|
||||
text = !policy.active ? "Policy enabled successfully" : "Policy disabled successfully";
|
||||
data["active"] = !policy.active;
|
||||
} else if (type === "Enforced") {
|
||||
text = !policy.enforced ? "Policy enforced successfully" : "Policy enforcement disabled";
|
||||
data["enforced"] = !policy.enforced;
|
||||
}
|
||||
|
||||
this.$axios
|
||||
.put(`/automation/policies/${data.id}/`, data)
|
||||
.then(r => {
|
||||
|
||||
@@ -11,156 +11,154 @@
|
||||
@click="showAddTask = true"
|
||||
/>
|
||||
<q-btn v-if="!!selectedPolicy" dense flat push @click="getTasks" icon="refresh" />
|
||||
<template>
|
||||
<q-table
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="tabs-tbl-sticky"
|
||||
:data="tasks"
|
||||
:columns="columns"
|
||||
:rows-per-page-options="[0]"
|
||||
:pagination.sync="pagination"
|
||||
dense
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
hide-pagination
|
||||
virtual-scroll
|
||||
>
|
||||
<!-- No data Slot -->
|
||||
<template v-slot:no-data>
|
||||
<div class="full-width row flex-center q-gutter-sm">
|
||||
<span v-if="!selectedPolicy">Click on a policy to see the tasks</span>
|
||||
<span v-else>There are no tasks added to this policy</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- header slots -->
|
||||
<template v-slot:header-cell-enabled="props">
|
||||
<q-th auto-width :props="props">
|
||||
<small>Enabled</small>
|
||||
</q-th>
|
||||
</template>
|
||||
<q-table
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="tabs-tbl-sticky"
|
||||
:rows="tasks"
|
||||
:columns="columns"
|
||||
:rows-per-page-options="[0]"
|
||||
v-model:pagination="pagination"
|
||||
dense
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
hide-pagination
|
||||
virtual-scroll
|
||||
>
|
||||
<!-- No data Slot -->
|
||||
<template v-slot:no-data>
|
||||
<div class="full-width row flex-center q-gutter-sm">
|
||||
<span v-if="!selectedPolicy">Click on a policy to see the tasks</span>
|
||||
<span v-else>There are no tasks added to this policy</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- header slots -->
|
||||
<template v-slot:header-cell-enabled="props">
|
||||
<q-th auto-width :props="props">
|
||||
<small>Enabled</small>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<template v-slot:header-cell-smsalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="phone_android" size="1.5em">
|
||||
<q-tooltip>SMS Alert</q-tooltip>
|
||||
<template v-slot:header-cell-smsalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="phone_android" size="1.5em">
|
||||
<q-tooltip>SMS Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<template v-slot:header-cell-emailalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="email" size="1.5em">
|
||||
<q-tooltip>Email Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
<template v-slot:header-cell-dashboardalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="notifications" size="1.5em">
|
||||
<q-tooltip>Dashboard Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<template v-slot:header-cell-collector="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="mdi-database-arrow-up" size="1.5em">
|
||||
<q-tooltip>Collector Task</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<!-- body slots -->
|
||||
<template v-slot:body="props" :props="props">
|
||||
<q-tr class="cursor-pointer" @dblclick="showEditTask(props.row)">
|
||||
<!-- context menu -->
|
||||
<q-menu context-menu>
|
||||
<q-list dense style="min-width: 200px">
|
||||
<q-item clickable v-close-popup @click="runTask(props.row.id, props.row.enabled)">
|
||||
<q-item-section side>
|
||||
<q-icon name="play_arrow" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run task now</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="showEditTask(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="edit" />
|
||||
</q-item-section>
|
||||
<q-item-section>Edit</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="deleteTask(props.row.name, props.row.id)">
|
||||
<q-item-section side>
|
||||
<q-icon name="delete" />
|
||||
</q-item-section>
|
||||
<q-item-section>Delete</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
<q-item clickable v-close-popup @click="showStatus(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="sync" />
|
||||
</q-item-section>
|
||||
<q-item-section>Policy Status</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section>Close</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
<!-- tds -->
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@update:model-value="taskEnableorDisable(props.row.id, props.row.enabled)"
|
||||
v-model="props.row.enabled"
|
||||
/>
|
||||
</q-td>
|
||||
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@update:model-value="taskAlert(props.row.id, 'Text', props.row.text_alert)"
|
||||
v-model="props.row.text_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<!-- email alert -->
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@update:model-value="taskAlert(props.row.id, 'Email', props.row.email_alert)"
|
||||
v-model="props.row.email_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<!-- dashboard alert -->
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@update:model-value="taskAlert(props.row.id, 'Dashboard', props.row.dashboard_alert)"
|
||||
v-model="props.row.dashboard_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<!-- is collector task -->
|
||||
<q-td>
|
||||
<q-icon v-if="!!props.row.custom_field" style="font-size: 1.3rem" name="check">
|
||||
<q-tooltip>The task updates a custom field on the agent</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<template v-slot:header-cell-emailalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="email" size="1.5em">
|
||||
<q-tooltip>Email Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
<template v-slot:header-cell-dashboardalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="notifications" size="1.5em">
|
||||
<q-tooltip>Dashboard Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<template v-slot:header-cell-collector="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="mdi-database-arrow-up" size="1.5em">
|
||||
<q-tooltip>Collector Task</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<!-- body slots -->
|
||||
<template v-slot:body="props" :props="props">
|
||||
<q-tr class="cursor-pointer" @dblclick="showEditTask(props.row)">
|
||||
<!-- context menu -->
|
||||
<q-menu context-menu>
|
||||
<q-list dense style="min-width: 200px">
|
||||
<q-item clickable v-close-popup @click="runTask(props.row.id, props.row.enabled)">
|
||||
<q-item-section side>
|
||||
<q-icon name="play_arrow" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run task now</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="showEditTask(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="edit" />
|
||||
</q-item-section>
|
||||
<q-item-section>Edit</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="deleteTask(props.row.name, props.row.id)">
|
||||
<q-item-section side>
|
||||
<q-icon name="delete" />
|
||||
</q-item-section>
|
||||
<q-item-section>Delete</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
<q-item clickable v-close-popup @click="showStatus(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="sync" />
|
||||
</q-item-section>
|
||||
<q-item-section>Policy Status</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section>Close</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
<!-- tds -->
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="taskEnableorDisable(props.row.id, props.row.enabled)"
|
||||
v-model="props.row.enabled"
|
||||
/>
|
||||
</q-td>
|
||||
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="taskAlert(props.row.id, 'Text', props.row.text_alert, props.row.managed_by_policy)"
|
||||
v-model="props.row.text_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<!-- email alert -->
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="taskAlert(props.row.id, 'Email', props.row.email_alert, props.row.managed_by_policy)"
|
||||
v-model="props.row.email_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<!-- dashboard alert -->
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="taskAlert(props.row.id, 'Dashboard', props.row.dashboard_alert, props.row.managed_by_policy)"
|
||||
v-model="props.row.dashboard_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<!-- is collector task -->
|
||||
<q-td>
|
||||
<q-icon v-if="!!props.row.custom_field" style="font-size: 1.3rem" name="check">
|
||||
<q-tooltip>The task updates a custom field on the agent</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
<q-td>{{ props.row.name }}</q-td>
|
||||
<q-td>{{ props.row.schedule }}</q-td>
|
||||
<q-td>
|
||||
<span
|
||||
style="cursor: pointer; text-decoration: underline"
|
||||
@click="showStatus(props.row)"
|
||||
class="status-cell text-primary"
|
||||
>See Status</span
|
||||
>
|
||||
</q-td>
|
||||
<q-td v-if="props.row.assigned_check">{{ props.row.assigned_check.readable_desc }}</q-td>
|
||||
<q-td v-else></q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</template>
|
||||
</q-td>
|
||||
<q-td>{{ props.row.name }}</q-td>
|
||||
<q-td>{{ props.row.schedule }}</q-td>
|
||||
<q-td>
|
||||
<span
|
||||
style="cursor: pointer; text-decoration: underline"
|
||||
@click="showStatus(props.row)"
|
||||
class="status-cell text-primary"
|
||||
>See Status</span
|
||||
>
|
||||
</q-td>
|
||||
<q-td v-if="props.row.assigned_check">{{ props.row.assigned_check.readable_desc }}</q-td>
|
||||
<q-td v-else></q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
<!-- modals -->
|
||||
<q-dialog v-model="showAddTask" position="top">
|
||||
@@ -248,7 +246,7 @@ export default {
|
||||
},
|
||||
taskEnableorDisable(pk, action) {
|
||||
this.$q.loading.show();
|
||||
const data = { id: pk, enableordisable: action };
|
||||
const data = { id: pk, enableordisable: !action };
|
||||
this.$axios
|
||||
.patch(`/tasks/${pk}/automatedtasks/`, data)
|
||||
.then(r => {
|
||||
@@ -260,7 +258,7 @@ export default {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
},
|
||||
taskAlert(pk, alert_type, action, managed_by_policy) {
|
||||
taskAlert(pk, alert_type, action) {
|
||||
this.$q.loading.show();
|
||||
|
||||
const data = {
|
||||
@@ -268,14 +266,14 @@ export default {
|
||||
};
|
||||
|
||||
if (alert_type === "Email") {
|
||||
data.email_alert = action;
|
||||
data.email_alert = !action;
|
||||
} else if (alert_type === "Text") {
|
||||
data.text_alert = action;
|
||||
data.text_alert = !action;
|
||||
} else {
|
||||
data.dashboard_alert = action;
|
||||
data.dashboard_alert = !action;
|
||||
}
|
||||
|
||||
const act = action ? "enabled" : "disabled";
|
||||
const act = !action ? "enabled" : "disabled";
|
||||
this.$axios
|
||||
.put(`/tasks/${pk}/automatedtasks/`, data)
|
||||
.then(r => {
|
||||
@@ -290,8 +288,9 @@ export default {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: EditAutomatedTask,
|
||||
parent: this,
|
||||
task: task,
|
||||
componentProps: {
|
||||
task: task,
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
this.getTasks();
|
||||
@@ -300,9 +299,10 @@ export default {
|
||||
showStatus(task) {
|
||||
this.$q.dialog({
|
||||
component: PolicyStatus,
|
||||
parent: this,
|
||||
type: "task",
|
||||
item: task,
|
||||
componentProps: {
|
||||
type: "task",
|
||||
item: task,
|
||||
},
|
||||
});
|
||||
},
|
||||
runTask(pk, enabled) {
|
||||
|
||||
@@ -50,127 +50,125 @@
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
<q-btn v-if="!!selectedPolicy" dense flat push @click="getChecks" icon="refresh" />
|
||||
<template>
|
||||
<q-table
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="tabs-tbl-sticky"
|
||||
:data="checks"
|
||||
:columns="columns"
|
||||
:pagination.sync="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
dense
|
||||
hide-pagination
|
||||
virtual-scroll
|
||||
>
|
||||
<!-- No data Slot -->
|
||||
<template v-slot:no-data>
|
||||
<div class="full-width row flex-center q-gutter-sm">
|
||||
<span v-if="!selectedPolicy">Click on a policy to see the checks</span>
|
||||
<span v-else>There are no checks added to this policy</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- header slots -->
|
||||
<template v-slot:header-cell-smsalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="phone_android" size="1.5em">
|
||||
<q-tooltip>SMS Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
<template v-slot:header-cell-emailalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="email" size="1.5em">
|
||||
<q-tooltip>Email Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
<template v-slot:header-cell-dashboardalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="notifications" size="1.5em">
|
||||
<q-tooltip>Dashboard Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
<template v-slot:header-cell-statusicon="props">
|
||||
<q-th auto-width :props="props"></q-th>
|
||||
</template>
|
||||
<!-- body slots -->
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props" class="cursor-pointer" @dblclick="showEditDialog(props.row)">
|
||||
<!-- context menu -->
|
||||
<q-menu context-menu>
|
||||
<q-list dense style="min-width: 200px">
|
||||
<q-item clickable v-close-popup @click="showEditDialog(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="edit" />
|
||||
</q-item-section>
|
||||
<q-item-section>Edit</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="deleteCheck(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="delete" />
|
||||
</q-item-section>
|
||||
<q-item-section>Delete</q-item-section>
|
||||
</q-item>
|
||||
<q-table
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="tabs-tbl-sticky"
|
||||
:rows="checks"
|
||||
:columns="columns"
|
||||
v-model:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
dense
|
||||
hide-pagination
|
||||
virtual-scroll
|
||||
>
|
||||
<!-- No data Slot -->
|
||||
<template v-slot:no-data>
|
||||
<div class="full-width row flex-center q-gutter-sm">
|
||||
<span v-if="!selectedPolicy">Click on a policy to see the checks</span>
|
||||
<span v-else>There are no checks added to this policy</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- header slots -->
|
||||
<template v-slot:header-cell-smsalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="phone_android" size="1.5em">
|
||||
<q-tooltip>SMS Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
<template v-slot:header-cell-emailalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="email" size="1.5em">
|
||||
<q-tooltip>Email Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
<template v-slot:header-cell-dashboardalert="props">
|
||||
<q-th auto-width :props="props">
|
||||
<q-icon name="notifications" size="1.5em">
|
||||
<q-tooltip>Dashboard Alert</q-tooltip>
|
||||
</q-icon>
|
||||
</q-th>
|
||||
</template>
|
||||
<template v-slot:header-cell-statusicon="props">
|
||||
<q-th auto-width :props="props"></q-th>
|
||||
</template>
|
||||
<!-- body slots -->
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props" class="cursor-pointer" @dblclick="showEditDialog(props.row)">
|
||||
<!-- context menu -->
|
||||
<q-menu context-menu>
|
||||
<q-list dense style="min-width: 200px">
|
||||
<q-item clickable v-close-popup @click="showEditDialog(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="edit" />
|
||||
</q-item-section>
|
||||
<q-item-section>Edit</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="deleteCheck(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="delete" />
|
||||
</q-item-section>
|
||||
<q-item-section>Delete</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator></q-separator>
|
||||
<q-separator></q-separator>
|
||||
|
||||
<q-item clickable v-close-popup @click="showPolicyStatus(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="sync" />
|
||||
</q-item-section>
|
||||
<q-item-section>Policy Status</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="showPolicyStatus(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon name="sync" />
|
||||
</q-item-section>
|
||||
<q-item-section>Policy Status</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator></q-separator>
|
||||
<q-separator></q-separator>
|
||||
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section>Close</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
<!-- tds -->
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="checkAlert(props.row.id, 'Text', props.row.text_alert)"
|
||||
v-model="props.row.text_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="checkAlert(props.row.id, 'Email', props.row.email_alert)"
|
||||
v-model="props.row.email_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@input="checkAlert(props.row.id, 'Dashboard', props.row.dashboard_alert)"
|
||||
v-model="props.row.dashboard_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<q-td>{{ props.row.readable_desc }}</q-td>
|
||||
<q-td>
|
||||
<span
|
||||
style="cursor: pointer; text-decoration: underline"
|
||||
@click="showPolicyStatus(props.row)"
|
||||
class="status-cell text-primary"
|
||||
>See Status</span
|
||||
>
|
||||
</q-td>
|
||||
<q-td v-if="props.row.assignedtask !== null && props.row.assignedtask.length === 1">{{
|
||||
props.row.assignedtask[0].name
|
||||
}}</q-td>
|
||||
<q-td v-else-if="props.row.assignedtask">{{ props.row.assignedtask.length }} Tasks</q-td>
|
||||
<q-td v-else></q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</template>
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section>Close</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
<!-- tds -->
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@update:model-value="checkAlert(props.row.id, 'Text', props.row.text_alert)"
|
||||
v-model="props.row.text_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@update:model-value="checkAlert(props.row.id, 'Email', props.row.email_alert)"
|
||||
v-model="props.row.email_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
dense
|
||||
@update:model-value="checkAlert(props.row.id, 'Dashboard', props.row.dashboard_alert)"
|
||||
v-model="props.row.dashboard_alert"
|
||||
/>
|
||||
</q-td>
|
||||
<q-td>{{ props.row.readable_desc }}</q-td>
|
||||
<q-td>
|
||||
<span
|
||||
style="cursor: pointer; text-decoration: underline"
|
||||
@click="showPolicyStatus(props.row)"
|
||||
class="status-cell text-primary"
|
||||
>See Status</span
|
||||
>
|
||||
</q-td>
|
||||
<q-td v-if="props.row.assignedtask !== null && props.row.assignedtask.length === 1">{{
|
||||
props.row.assignedtask[0].name
|
||||
}}</q-td>
|
||||
<q-td v-else-if="props.row.assignedtask">{{ props.row.assignedtask.length }} Tasks</q-td>
|
||||
<q-td v-else></q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
|
||||
<!-- add/edit modals -->
|
||||
@@ -257,16 +255,16 @@ export default {
|
||||
const data = {};
|
||||
|
||||
if (alert_type === "Email") {
|
||||
data.email_alert = action;
|
||||
data.email_alert = !action;
|
||||
} else if (alert_type === "Text") {
|
||||
data.text_alert = action;
|
||||
data.text_alert = !action;
|
||||
} else {
|
||||
data.dashboard_alert = action;
|
||||
data.dashboard_alert = !action;
|
||||
}
|
||||
|
||||
data.check_alert = true;
|
||||
const act = action ? "enabled" : "disabled";
|
||||
const color = action ? "positive" : "warning";
|
||||
const act = !action ? "enabled" : "disabled";
|
||||
const color = !action ? "positive" : "warning";
|
||||
this.$axios
|
||||
.patch(`/checks/${id}/check/`, data)
|
||||
.then(r => {
|
||||
@@ -344,9 +342,10 @@ export default {
|
||||
showPolicyStatus(check) {
|
||||
this.$q.dialog({
|
||||
component: PolicyStatus,
|
||||
parent: this,
|
||||
type: "check",
|
||||
item: check,
|
||||
componentProps: {
|
||||
type: "check",
|
||||
item: check,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<q-btn @click="getPolicyTree" class="q-mr-sm" dense flat push icon="refresh" />Policy Overview
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<q-splitter v-model="splitterModel" style="height: 600px">
|
||||
@@ -16,7 +16,7 @@
|
||||
:nodes="clientSiteTree"
|
||||
node-key="key"
|
||||
selected-color="primary"
|
||||
:selected.sync="selectedPolicyId"
|
||||
v-model:selected="selectedPolicyId"
|
||||
></q-tree>
|
||||
</div>
|
||||
</template>
|
||||
@@ -63,6 +63,7 @@ import PolicyAutomatedTasksTab from "@/components/automation/PolicyAutomatedTask
|
||||
|
||||
export default {
|
||||
name: "PolicyOverview",
|
||||
emits: ["hide", "ok", "cancel"],
|
||||
components: {
|
||||
PolicyAutomatedTasksTab,
|
||||
PolicyChecksTab,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Edit policies assigned to {{ type }}
|
||||
<q-space />
|
||||
<q-btn dense flat icon="close" v-close-popup>
|
||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
||||
</q-btn>
|
||||
</q-bar>
|
||||
<q-form @submit="submit">
|
||||
@@ -72,6 +72,7 @@ import mixins from "@/mixins/mixins";
|
||||
|
||||
export default {
|
||||
name: "PolicyAdd",
|
||||
emits: ["hide", "ok", "cancel"],
|
||||
props: {
|
||||
object: !Object,
|
||||
type: {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user