Compare commits

...

107 Commits

Author SHA1 Message Date
wh1te909
71e78bd0c5 Release 0.7.1 2021-06-28 07:13:33 +00:00
wh1te909
4766477c58 bump version 2021-06-28 07:13:19 +00:00
wh1te909
d97e49ff2b add button to test SMS closes #590 2021-06-28 07:05:57 +00:00
wh1te909
6b9d775cb9 add hostname to email subject/body fixes #589 2021-06-28 06:07:17 +00:00
wh1te909
e521f580d7 make clearing search field when switching client/site optional closes #597 2021-06-28 05:21:07 +00:00
wh1te909
bc6faf817f Release 0.7.0 2021-06-27 06:58:48 +00:00
wh1te909
d46ae55863 bump versions 2021-06-27 06:58:06 +00:00
wh1te909
bbd900ab25 move checkin to go 2021-06-27 06:23:37 +00:00
Dan
129ae93e2b Merge pull request #596 from rfost52/develop
Submitting System Report Generator to Community Scripts
2021-06-26 21:58:23 -07:00
rfost52
44dd59fa3f Merge branch 'develop' of https://github.com/rfost52/tacticalrmm into develop 2021-06-26 22:31:00 -04:00
rfost52
ec4e7559b0 updated script header 2021-06-26 22:30:52 -04:00
rfost52
dce40611cf Merge branch 'wh1te909:develop' into develop 2021-06-26 22:17:31 -04:00
rfost52
e71b8546f9 Submitting System Report Generator to Community Scripts 2021-06-26 22:09:56 -04:00
wh1te909
f827348467 style changes 2021-06-27 01:15:47 +00:00
wh1te909
f3978343db cache some values to speed up agent table loading 2021-06-27 00:51:34 +00:00
wh1te909
2654a7ea70 remove extra param 2021-06-27 00:05:00 +00:00
wh1te909
1068bf4ef7 fix row highlight 2021-06-26 17:53:06 +00:00
Dan
e7fccc97cc Merge pull request #595 from rfost52/develop
Initial Parameterization of System Report WIP Script
2021-06-25 23:57:11 -07:00
Dan
733e289852 Merge pull request #592 from silversword411/develop
Docs tweaks
2021-06-25 23:56:44 -07:00
rfost52
29d71a104c include check for C:\Temp folder 2021-06-25 00:36:16 -04:00
rfost52
05200420ad Merge branch 'develop' of https://github.com/rfost52/tacticalrmm into develop 2021-06-24 23:53:26 -04:00
rfost52
eb762d4bfd Initial Parameterization of variables 2021-06-24 23:53:06 -04:00
silversword411
58ace9eda1 Adding wip scripts 2021-06-24 17:20:49 -04:00
sadnub
eeb2623be0 Merge pull request #516 from sadnub/quasar-update
Quasar update to v2
2021-06-24 13:48:47 -04:00
sadnub
cfa242c2fe update loading bar delay 2021-06-24 13:41:34 -04:00
sadnub
ec0441ccc2 fix collector dropdown in policy task edit 2021-06-24 13:41:34 -04:00
sadnub
ae2782a8fe update quasar to v2 release 2021-06-24 13:41:34 -04:00
sadnub
58ff570251 fix assets tab 2021-06-24 13:41:34 -04:00
sadnub
7b554b12c7 update packages 2021-06-24 13:41:34 -04:00
sadnub
58f7603d4f fix agent drowndown in audit manager 2021-06-24 13:41:34 -04:00
sadnub
8895994c54 update packages 2021-06-24 13:41:34 -04:00
sadnub
de8f7e36d5 fix q-checkboxes that need to trigger actions and replace @input with @update:model-value 2021-06-24 13:41:34 -04:00
sadnub
88d7a50265 refactor user administration without vuex 2021-06-24 13:41:34 -04:00
sadnub
21e19fc7e5 add keys back to v-fors 2021-06-24 13:41:34 -04:00
sadnub
faf4935a69 fix saving custom field values and change sites dropdown in edit agent modal 2021-06-24 13:41:34 -04:00
sadnub
71a1f9d74a update reqs and fix custom field values 2021-06-24 13:41:34 -04:00
sadnub
bd8d523e10 stop blinking when loading 2021-06-24 13:41:34 -04:00
sadnub
60cae0e3ac remove 'created' hooks from components and fix agent and script optino dropdowns 2021-06-24 13:41:34 -04:00
sadnub
5a342ac012 removed key from v-for. Fixed custom dropdowns. other fixes 2021-06-24 13:41:34 -04:00
sadnub
bb8767dfc3 fix darkmode and policy check and task tables 2021-06-24 13:41:34 -04:00
sadnub
fcb2779c15 update quasar 2021-06-24 13:41:34 -04:00
sadnub
77dd6c1f61 more fixes 2021-06-24 13:41:34 -04:00
sadnub
8118eef300 upgrade to quasar v2 and vue3 initial 2021-06-24 13:41:34 -04:00
silversword411
802d1489fe adding to howitallworks 2021-06-24 02:42:41 -04:00
silversword411
443a029185 Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-06-24 02:00:51 -04:00
silversword411
4ee508fdd0 Docs tweaks 2021-06-24 01:55:50 -04:00
wh1te909
aa5608f7e8 fix custom field args in bulk script fixes #591 2021-06-24 01:34:14 +00:00
wh1te909
cc472b4613 update celery 2021-06-24 01:32:07 +00:00
wh1te909
764b945ddc fix pipelines 2 2021-06-22 06:51:44 +00:00
wh1te909
fd2206ce4c fix pipelines 2021-06-22 06:47:17 +00:00
Dan
48c0ac9f00 Merge pull request #588 from rfost52/develop
Moving Win_AD_Join_Computer.ps1 from WIP scripts to Community Scripts
2021-06-21 23:38:18 -07:00
silversword411
84eb4fe9ed Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-06-21 11:35:04 -04:00
silversword411
4a5428812c Docs tweaks 2021-06-21 11:34:10 -04:00
silversword411
023f98a89d Docs tweaks 2021-06-21 11:32:56 -04:00
rfost52
66893dd0c1 Update Win_AD_Join_Computer.ps1 2021-06-19 20:50:56 -04:00
rfost52
25a6666e35 Adding AD PC Join to Listings 2021-06-19 20:47:11 -04:00
rfost52
19d75309b5 Merge branch 'develop' of https://github.com/rfost52/tacticalrmm into develop 2021-06-19 20:21:21 -04:00
rfost52
11110d65c1 Adding to Community Scripts
Moving from WIP Scripts to Community Scripts after successful testing.
2021-06-19 20:21:11 -04:00
Dan
a348f58fe2 Merge pull request #585 from rfost52/develop
First rework of Join to AD PowerShell WIP Script
2021-06-19 11:41:52 -07:00
rfost52
13851dd976 Added new line at end of code 2021-06-18 23:25:15 -04:00
rfost52
2ec37c5da9 1st Code rework with parameterization 2021-06-18 22:57:23 -04:00
rfost52
8c127160de Updated synopsis and description 2021-06-18 22:51:21 -04:00
rfost52
2af820de9a Update Win_AD_Join_Computer.ps1
Parameters, error checking with exit codes
2021-06-18 22:43:26 -04:00
Dan
55fb0bb3a0 Merge pull request #584 from silversword411/develop
community script updates
2021-06-18 10:58:00 -07:00
silversword411
9f9ecc521f community script updates 2021-06-17 15:27:40 -04:00
Dan
dfd01df5ba Merge pull request #581 from silversword411/develop
Adding docs
2021-06-16 22:55:18 -07:00
silversword411
474090698c Merge branch 'wh1te909:develop' into develop 2021-06-17 01:00:40 -04:00
silversword411
6b71cdeea4 Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-06-17 00:53:58 -04:00
wh1te909
581e974236 add view setting perms closes #569 2021-06-17 04:36:34 +00:00
wh1te909
ba3c3a42ce add missing mypy types 2021-06-17 04:35:51 +00:00
silversword411
c8bc5671c5 adding all possible script variables to docs 2021-06-17 00:34:11 -04:00
wh1te909
ff9401a040 make failing tasks fail client tree closes #571 2021-06-17 03:51:20 +00:00
wh1te909
5e1bc1989f update reqs 2021-06-17 03:50:00 +00:00
wh1te909
a1dc91cd7d fix typo in docs #580 2021-06-16 16:46:24 +00:00
sadnub
99f2772bb3 Fixes #577 2021-06-14 20:27:41 -04:00
sadnub
e5d0e42655 fix agent policies not updating when monitoring mode is changed 2021-06-14 20:18:56 -04:00
Dan
2c914cc374 Merge pull request #576 from bradhawkins85/patch-19
Update installer.ps1
2021-06-14 09:45:13 -07:00
Dan
9bceb62381 Merge pull request #575 from nextgi/zak-develop
Updates to Devcontainer and Added #467
2021-06-14 09:44:58 -07:00
Zak
de7518a800 Added new community script
New script for auto documenting ADDS.
2021-06-13 17:56:44 -07:00
bradhawkins85
304fb63453 Update installer.ps1
Fix spelling errors
2021-06-13 17:22:13 +10:00
Zak
0f7ef60ca0 Added #467
Added QTooltip to the label of the QItem in the QTree.
2021-06-12 20:50:59 -07:00
Zak
07c74e4641 Updated devcontainer
Prior it was statically set to use a specific range of IPs. I changed this so it could be set via environment variables. Also, NATS port 4222 is a reserved port for Hyper-V. I updated this so it could be set in env variables as well.
2021-06-12 20:49:10 -07:00
wh1te909
de7f325cfb fix redis appendonly backup/restore 2021-06-13 00:10:58 +00:00
wh1te909
42cdf70cb4 Release 0.6.15 2021-06-12 20:41:19 +00:00
wh1te909
6beb6be131 bump version 2021-06-12 20:40:54 +00:00
wh1te909
fa4fc2a708 only parse script args for script checks 2021-06-12 20:24:51 +00:00
wh1te909
2db9758260 fix custom fields in script checks #568 2021-06-12 19:41:49 +00:00
wh1te909
715982e40a Release 0.6.14 2021-06-11 04:41:48 +00:00
wh1te909
d00cd4453a bump versions 2021-06-11 04:40:57 +00:00
wh1te909
429c08c24a fix width on q-file caused by recent quasar update 2021-06-11 03:58:57 +00:00
wh1te909
6a71490e20 update reqs 2021-06-11 02:40:22 +00:00
Dan
9bceda0646 Merge pull request #562 from diekinderwelt/nginx_enable_ipv6
enable ipv6 in nginx config
2021-06-10 18:59:34 -07:00
Dan
a1027a6773 Merge pull request #565 from silversword411/develop
Docs Update - adding design and tipsntricks
2021-06-10 18:59:12 -07:00
silversword411
302d4b75f9 formatting fix 2021-06-08 15:39:43 -04:00
silversword411
5f6ee0e883 Docs Update - adding design and tipsntricks 2021-06-08 14:45:02 -04:00
Silvio
27f9720de1 enable ipv6 in nginx config
Signed-off-by: Silvio <silvio.zimmer@die-kinderwelt.com>
2021-06-08 11:43:55 +02:00
sadnub
22aa3fdbbc fix bug with policy copy and task that triggers on check failure. Fix check history tests 2021-06-06 23:19:07 -04:00
sadnub
069ecdd33f apply redis configuration after restore 2021-06-06 22:58:32 -04:00
sadnub
dd545ae933 catch an exception that a celery task could potentially throw and configure automation task retries 2021-06-06 22:55:47 -04:00
sadnub
6650b705c4 configure redis to use an appendonly file for celery task reliability 2021-06-06 22:54:52 -04:00
sadnub
59b0350289 fix duplicate tasks when there is an assigned check 2021-06-06 22:54:06 -04:00
sadnub
1ad159f820 remove foreign key from checkhistory to make mass check deletes reliable. (This will not migrate check history data) 2021-06-06 22:53:11 -04:00
Dan
0bf42190e9 Merge pull request #544 from bbrendon/patch-1
check for proper OS support
2021-05-30 23:10:21 -07:00
bbrendon
d2fa836232 check for proper OS support 2021-05-30 10:39:08 -07:00
Dan
c387774093 Merge pull request #543 from bbrendon/develop
fixed an edge case and warning notes
2021-05-29 22:39:52 -07:00
bbrendon
e99736ba3c fixed an edge case and warning notes 2021-05-29 19:25:53 -07:00
wh1te909
16cb54fcc9 fix multiline output not working for automation task 2021-05-29 18:47:09 +00:00
169 changed files with 8067 additions and 17857 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ class UserUISerializer(ModelSerializer):
"client_tree_sort",
"client_tree_splitter",
"loading_bar_color",
"clear_search_when_switching",
]

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,4 +6,6 @@ mkdocs-material
pymdown-extensions
Pygments
isort
mypy
mypy
types-pytz
types-pytz

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,6 +61,8 @@ Category or Function - What It Does
![json_name_examples](images/community_scripts_name_field_example1.png)
*****
## 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)

View File

@@ -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
View 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`
![MeshService](images/trmm_services_mesh.png)
![MeshAgentTaskManager](images/trmm_services__taskmanager_mesh.png)
**AND**
* `TacticalAgent` and `Tactical RMM RPC Service`
![TacticalAgentServices](images/trmm_services.png)
![TacticalAgentTaskManager](images/trmm_services__taskmanager_agent.png)
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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -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
View 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
![User Preferences](images/trmm_user_preferences.png)
*****
## 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
View File

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

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

View File

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

View File

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

View File

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

View File

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

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

View 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

View File

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

View File

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

View 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

View 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

View File

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

View 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

View File

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

View 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

View File

@@ -0,0 +1,3 @@
# GET NETWORK STATUS PRIVATE/PUBLIC/HOME/WORK
Get-NetConnectionProfile
Set-NetConnectionProfile -InterfaceIndex <index number> -NetworkCategory Private

View 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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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