Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20befd1ca2 | ||
|
|
ac6c6130f8 | ||
|
|
d776a2325c | ||
|
|
4aec4257da | ||
|
|
d654f856d1 | ||
|
|
8d3b0a2069 | ||
|
|
54a96f35e8 | ||
|
|
2dc56d72f6 | ||
|
|
4b6ddb535a | ||
|
|
697e2250d4 | ||
|
|
6a75035b04 | ||
|
|
46b166bc41 | ||
|
|
6bbc0987ad | ||
|
|
8c480b43e2 | ||
|
|
079f6731dd | ||
|
|
f99d5754cd | ||
|
|
bf8c41e362 | ||
|
|
7f7bc06eb4 | ||
|
|
b507e59359 | ||
|
|
72078ac6bf | ||
|
|
0db9e082e2 | ||
|
|
0c44394a76 | ||
|
|
e20aa0cf04 | ||
|
|
fa30a50a91 | ||
|
|
f6629ff12c | ||
|
|
4128e4db73 | ||
|
|
34cac5685f | ||
|
|
4c9b91d536 | ||
|
|
95b95a8998 | ||
|
|
617738bb28 | ||
|
|
f6ac15d790 | ||
|
|
79e1324ead | ||
|
|
4ef9f010f0 | ||
|
|
e6e8865708 | ||
|
|
33cd8f9b0d | ||
|
|
a7138e019c | ||
|
|
049b72bd50 | ||
|
|
f3f1987515 | ||
|
|
a9395d89cd | ||
|
|
bc2fcee8ba | ||
|
|
242ff2ceca | ||
|
|
70790ac762 | ||
|
|
0f98869b61 | ||
|
|
9ddc02140f | ||
|
|
ee631b3d20 | ||
|
|
32f56e60d8 | ||
|
|
6102b51d9e | ||
|
|
2baee27859 | ||
|
|
144a3dedbb | ||
|
|
f90d966f1a | ||
|
|
b188e2ea97 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -34,6 +34,7 @@ app.ini
|
|||||||
create_services.py
|
create_services.py
|
||||||
gen_random.py
|
gen_random.py
|
||||||
sync_salt_modules.py
|
sync_salt_modules.py
|
||||||
|
change_times.py
|
||||||
rmm-*.exe
|
rmm-*.exe
|
||||||
rmm-*.ps1
|
rmm-*.ps1
|
||||||
api/tacticalrmm/accounts/management/commands/*.json
|
api/tacticalrmm/accounts/management/commands/*.json
|
||||||
|
|||||||
@@ -1,14 +1,34 @@
|
|||||||
from .models import Agent
|
import random
|
||||||
|
import string
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
from model_bakery.recipe import Recipe, seq
|
from model_bakery.recipe import Recipe, seq
|
||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
from django.utils import timezone as djangotime
|
from django.utils import timezone as djangotime
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from .models import Agent
|
||||||
|
|
||||||
|
|
||||||
|
def generate_agent_id(hostname):
|
||||||
|
rand = "".join(random.choice(string.ascii_letters) for _ in range(35))
|
||||||
|
return f"{rand}-{hostname}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_wmi_data():
|
||||||
|
with open(
|
||||||
|
os.path.join(settings.BASE_DIR, "tacticalrmm/test_data/wmi_python_agent.json")
|
||||||
|
) as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
agent = Recipe(
|
agent = Recipe(
|
||||||
Agent,
|
Agent,
|
||||||
client="Default",
|
hostname="DESKTOP-TEST123",
|
||||||
site="Default",
|
|
||||||
hostname=seq("TestHostname"),
|
|
||||||
monitoring_type=cycle(["workstation", "server"]),
|
monitoring_type=cycle(["workstation", "server"]),
|
||||||
|
salt_id=generate_agent_id("DESKTOP-TEST123"),
|
||||||
|
agent_id="71AHC-AA813-HH1BC-AAHH5-00013|DESKTOP-TEST123",
|
||||||
)
|
)
|
||||||
|
|
||||||
server_agent = agent.extend(
|
server_agent = agent.extend(
|
||||||
@@ -49,3 +69,5 @@ agent_with_services = agent.extend(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
agent_with_wmi = agent.extend(wmi=get_wmi_data())
|
||||||
|
|||||||
20
api/tacticalrmm/agents/migrations/0021_agent_site_link.py
Normal file
20
api/tacticalrmm/agents/migrations/0021_agent_site_link.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-01 22:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('clients', '0006_deployment'),
|
||||||
|
('agents', '0020_auto_20201025_2129'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='agent',
|
||||||
|
name='site_link',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='agents', to='clients.site'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-01 22:54
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def link_sites_to_agents(apps, schema_editor):
|
||||||
|
Agent = apps.get_model("agents", "Agent")
|
||||||
|
Site = apps.get_model("clients", "Site")
|
||||||
|
for agent in Agent.objects.all():
|
||||||
|
site = Site.objects.get(client__client=agent.client, site=agent.site)
|
||||||
|
agent.site_link = site
|
||||||
|
agent.save()
|
||||||
|
|
||||||
|
|
||||||
|
def reverse(apps, schema_editor):
|
||||||
|
Agent = apps.get_model("agents", "Agent")
|
||||||
|
for agent in Agent.objects.all():
|
||||||
|
agent.site = agent.site_link.site
|
||||||
|
agent.client = agent.site_link.client.client
|
||||||
|
agent.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("agents", "0021_agent_site_link"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(link_sites_to_agents, reverse),
|
||||||
|
]
|
||||||
21
api/tacticalrmm/agents/migrations/0023_auto_20201101_2312.py
Normal file
21
api/tacticalrmm/agents/migrations/0023_auto_20201101_2312.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-01 23:12
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('agents', '0022_update_site_primary_key'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='agent',
|
||||||
|
name='client',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='agent',
|
||||||
|
name='site',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
api/tacticalrmm/agents/migrations/0024_auto_20201101_2319.py
Normal file
18
api/tacticalrmm/agents/migrations/0024_auto_20201101_2319.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-01 23:19
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('agents', '0023_auto_20201101_2312'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='agent',
|
||||||
|
old_name='site_link',
|
||||||
|
new_name='site',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -44,9 +44,7 @@ class Agent(BaseAuditModel):
|
|||||||
boot_time = models.FloatField(null=True, blank=True)
|
boot_time = models.FloatField(null=True, blank=True)
|
||||||
logged_in_username = models.CharField(null=True, blank=True, max_length=255)
|
logged_in_username = models.CharField(null=True, blank=True, max_length=255)
|
||||||
last_logged_in_user = models.CharField(null=True, blank=True, max_length=255)
|
last_logged_in_user = models.CharField(null=True, blank=True, max_length=255)
|
||||||
client = models.CharField(max_length=200)
|
|
||||||
antivirus = models.CharField(default="n/a", max_length=255) # deprecated
|
antivirus = models.CharField(default="n/a", max_length=255) # deprecated
|
||||||
site = models.CharField(max_length=150)
|
|
||||||
monitoring_type = models.CharField(max_length=30)
|
monitoring_type = models.CharField(max_length=30)
|
||||||
description = models.CharField(null=True, blank=True, max_length=255)
|
description = models.CharField(null=True, blank=True, max_length=255)
|
||||||
mesh_node_id = models.CharField(null=True, blank=True, max_length=255)
|
mesh_node_id = models.CharField(null=True, blank=True, max_length=255)
|
||||||
@@ -62,6 +60,13 @@ class Agent(BaseAuditModel):
|
|||||||
max_length=255, choices=TZ_CHOICES, null=True, blank=True
|
max_length=255, choices=TZ_CHOICES, null=True, blank=True
|
||||||
)
|
)
|
||||||
maintenance_mode = models.BooleanField(default=False)
|
maintenance_mode = models.BooleanField(default=False)
|
||||||
|
site = models.ForeignKey(
|
||||||
|
"clients.Site",
|
||||||
|
related_name="agents",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
)
|
||||||
policy = models.ForeignKey(
|
policy = models.ForeignKey(
|
||||||
"automation.Policy",
|
"automation.Policy",
|
||||||
related_name="agents",
|
related_name="agents",
|
||||||
@@ -73,6 +78,10 @@ class Agent(BaseAuditModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.hostname
|
return self.hostname
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self):
|
||||||
|
return self.site.client
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timezone(self):
|
def timezone(self):
|
||||||
# return the default timezone unless the timezone is explicity set per agent
|
# return the default timezone unless the timezone is explicity set per agent
|
||||||
@@ -86,9 +95,9 @@ class Agent(BaseAuditModel):
|
|||||||
@property
|
@property
|
||||||
def arch(self):
|
def arch(self):
|
||||||
if self.operating_system is not None:
|
if self.operating_system is not None:
|
||||||
if "64 bit" in self.operating_system:
|
if "64 bit" in self.operating_system or "64bit" in self.operating_system:
|
||||||
return "64"
|
return "64"
|
||||||
elif "32 bit" in self.operating_system:
|
elif "32 bit" in self.operating_system or "32bit" in self.operating_system:
|
||||||
return "32"
|
return "32"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -281,11 +290,9 @@ class Agent(BaseAuditModel):
|
|||||||
|
|
||||||
# returns agent policy merged with a client or site specific policy
|
# returns agent policy merged with a client or site specific policy
|
||||||
def get_patch_policy(self):
|
def get_patch_policy(self):
|
||||||
from clients.models import Client, Site
|
|
||||||
|
|
||||||
# check if site has a patch policy and if so use it
|
# check if site has a patch policy and if so use it
|
||||||
client = Client.objects.get(client=self.client)
|
site = self.site
|
||||||
site = Site.objects.get(client=client, site=self.site)
|
|
||||||
core_settings = CoreSettings.objects.first()
|
core_settings = CoreSettings.objects.first()
|
||||||
patch_policy = None
|
patch_policy = None
|
||||||
agent_policy = self.winupdatepolicy.get()
|
agent_policy = self.winupdatepolicy.get()
|
||||||
@@ -667,10 +674,10 @@ class AgentOutage(models.Model):
|
|||||||
|
|
||||||
CORE = CoreSettings.objects.first()
|
CORE = CoreSettings.objects.first()
|
||||||
CORE.send_mail(
|
CORE.send_mail(
|
||||||
f"{self.agent.client}, {self.agent.site}, {self.agent.hostname} - data overdue",
|
f"{self.agent.client.name}, {self.agent.site.name}, {self.agent.hostname} - data overdue",
|
||||||
(
|
(
|
||||||
f"Data has not been received from client {self.agent.client}, "
|
f"Data has not been received from client {self.agent.client.name}, "
|
||||||
f"site {self.agent.site}, "
|
f"site {self.agent.site.name}, "
|
||||||
f"agent {self.agent.hostname} "
|
f"agent {self.agent.hostname} "
|
||||||
"within the expected time."
|
"within the expected time."
|
||||||
),
|
),
|
||||||
@@ -681,10 +688,10 @@ class AgentOutage(models.Model):
|
|||||||
|
|
||||||
CORE = CoreSettings.objects.first()
|
CORE = CoreSettings.objects.first()
|
||||||
CORE.send_mail(
|
CORE.send_mail(
|
||||||
f"{self.agent.client}, {self.agent.site}, {self.agent.hostname} - data received",
|
f"{self.agent.client.name}, {self.agent.site.name}, {self.agent.hostname} - data received",
|
||||||
(
|
(
|
||||||
f"Data has been received from client {self.agent.client}, "
|
f"Data has been received from client {self.agent.client.name}, "
|
||||||
f"site {self.agent.site}, "
|
f"site {self.agent.site.name}, "
|
||||||
f"agent {self.agent.hostname} "
|
f"agent {self.agent.hostname} "
|
||||||
"after an interruption in data transmission."
|
"after an interruption in data transmission."
|
||||||
),
|
),
|
||||||
@@ -695,7 +702,7 @@ class AgentOutage(models.Model):
|
|||||||
|
|
||||||
CORE = CoreSettings.objects.first()
|
CORE = CoreSettings.objects.first()
|
||||||
CORE.send_sms(
|
CORE.send_sms(
|
||||||
f"{self.agent.client}, {self.agent.site}, {self.agent.hostname} - data overdue"
|
f"{self.agent.client.name}, {self.agent.site.name}, {self.agent.hostname} - data overdue"
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_recovery_sms(self):
|
def send_recovery_sms(self):
|
||||||
@@ -703,7 +710,7 @@ class AgentOutage(models.Model):
|
|||||||
|
|
||||||
CORE = CoreSettings.objects.first()
|
CORE = CoreSettings.objects.first()
|
||||||
CORE.send_sms(
|
CORE.send_sms(
|
||||||
f"{self.agent.client}, {self.agent.site}, {self.agent.hostname} - data received"
|
f"{self.agent.client.name}, {self.agent.site.name}, {self.agent.hostname} - data received"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from rest_framework.fields import ReadOnlyField
|
||||||
|
|
||||||
from .models import Agent, Note
|
from .models import Agent, Note
|
||||||
|
|
||||||
from winupdate.serializers import WinUpdatePolicySerializer
|
from winupdate.serializers import WinUpdatePolicySerializer
|
||||||
|
from clients.serializers import ClientSerializer
|
||||||
|
|
||||||
|
|
||||||
class AgentSerializer(serializers.ModelSerializer):
|
class AgentSerializer(serializers.ModelSerializer):
|
||||||
@@ -19,6 +21,8 @@ class AgentSerializer(serializers.ModelSerializer):
|
|||||||
checks = serializers.ReadOnlyField()
|
checks = serializers.ReadOnlyField()
|
||||||
timezone = serializers.ReadOnlyField()
|
timezone = serializers.ReadOnlyField()
|
||||||
all_timezones = serializers.SerializerMethodField()
|
all_timezones = serializers.SerializerMethodField()
|
||||||
|
client_name = serializers.ReadOnlyField(source="client.name")
|
||||||
|
site_name = serializers.ReadOnlyField(source="site.name")
|
||||||
|
|
||||||
def get_all_timezones(self, obj):
|
def get_all_timezones(self, obj):
|
||||||
return pytz.all_timezones
|
return pytz.all_timezones
|
||||||
@@ -35,6 +39,8 @@ class AgentTableSerializer(serializers.ModelSerializer):
|
|||||||
status = serializers.ReadOnlyField()
|
status = serializers.ReadOnlyField()
|
||||||
checks = serializers.ReadOnlyField()
|
checks = serializers.ReadOnlyField()
|
||||||
last_seen = serializers.SerializerMethodField()
|
last_seen = serializers.SerializerMethodField()
|
||||||
|
client_name = serializers.ReadOnlyField(source="client.name")
|
||||||
|
site_name = serializers.ReadOnlyField(source="site.name")
|
||||||
|
|
||||||
def get_last_seen(self, obj):
|
def get_last_seen(self, obj):
|
||||||
if obj.time_zone is not None:
|
if obj.time_zone is not None:
|
||||||
@@ -50,8 +56,8 @@ class AgentTableSerializer(serializers.ModelSerializer):
|
|||||||
"id",
|
"id",
|
||||||
"hostname",
|
"hostname",
|
||||||
"agent_id",
|
"agent_id",
|
||||||
"client",
|
"site_name",
|
||||||
"site",
|
"client_name",
|
||||||
"monitoring_type",
|
"monitoring_type",
|
||||||
"description",
|
"description",
|
||||||
"needs_reboot",
|
"needs_reboot",
|
||||||
@@ -66,11 +72,13 @@ class AgentTableSerializer(serializers.ModelSerializer):
|
|||||||
"last_logged_in_user",
|
"last_logged_in_user",
|
||||||
"maintenance_mode",
|
"maintenance_mode",
|
||||||
]
|
]
|
||||||
|
depth = 2
|
||||||
|
|
||||||
|
|
||||||
class AgentEditSerializer(serializers.ModelSerializer):
|
class AgentEditSerializer(serializers.ModelSerializer):
|
||||||
winupdatepolicy = WinUpdatePolicySerializer(many=True, read_only=True)
|
winupdatepolicy = WinUpdatePolicySerializer(many=True, read_only=True)
|
||||||
all_timezones = serializers.SerializerMethodField()
|
all_timezones = serializers.SerializerMethodField()
|
||||||
|
client = ClientSerializer(read_only=True)
|
||||||
|
|
||||||
def get_all_timezones(self, obj):
|
def get_all_timezones(self, obj):
|
||||||
return pytz.all_timezones
|
return pytz.all_timezones
|
||||||
@@ -107,6 +115,9 @@ class WinAgentSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class AgentHostnameSerializer(serializers.ModelSerializer):
|
class AgentHostnameSerializer(serializers.ModelSerializer):
|
||||||
|
client = serializers.ReadOnlyField(source="client.name")
|
||||||
|
site = serializers.ReadOnlyField(source="site.name")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Agent
|
model = Agent
|
||||||
fields = (
|
fields = (
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ def send_agent_update_task(pks, version):
|
|||||||
|
|
||||||
# skip if we can't determine the arch
|
# skip if we can't determine the arch
|
||||||
if agent.arch is None:
|
if agent.arch is None:
|
||||||
|
logger.warning(
|
||||||
|
f"Unable to determine arch on {agent.salt_id}. Skipping."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# golang agent only backwards compatible with py agent 0.11.2
|
# golang agent only backwards compatible with py agent 0.11.2
|
||||||
@@ -47,6 +50,9 @@ def send_agent_update_task(pks, version):
|
|||||||
else:
|
else:
|
||||||
url = agent.winagent_dl
|
url = agent.winagent_dl
|
||||||
inno = agent.win_inno_exe
|
inno = agent.win_inno_exe
|
||||||
|
logger.info(
|
||||||
|
f"Updating {agent.salt_id} current version {agent.version} using {inno}"
|
||||||
|
)
|
||||||
r = agent.salt_api_async(
|
r = agent.salt_api_async(
|
||||||
func="win_agent.do_agent_update_v2",
|
func="win_agent.do_agent_update_v2",
|
||||||
kwargs={
|
kwargs={
|
||||||
@@ -54,13 +60,15 @@ def send_agent_update_task(pks, version):
|
|||||||
"url": url,
|
"url": url,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
logger.info(f"{agent.salt_id}: {r}")
|
||||||
sleep(10)
|
sleep(10)
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def auto_self_agent_update_task(test=False):
|
def auto_self_agent_update_task():
|
||||||
core = CoreSettings.objects.first()
|
core = CoreSettings.objects.first()
|
||||||
if not core.agent_auto_update:
|
if not core.agent_auto_update:
|
||||||
|
logger.info("Agent auto update is disabled. Skipping.")
|
||||||
return
|
return
|
||||||
|
|
||||||
q = Agent.objects.only("pk", "version")
|
q = Agent.objects.only("pk", "version")
|
||||||
@@ -69,6 +77,7 @@ def auto_self_agent_update_task(test=False):
|
|||||||
for i in q
|
for i in q
|
||||||
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
|
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
|
||||||
]
|
]
|
||||||
|
logger.info(f"Updating {len(agents)}")
|
||||||
|
|
||||||
chunks = (agents[i : i + 30] for i in range(0, len(agents), 30))
|
chunks = (agents[i : i + 30] for i in range(0, len(agents), 30))
|
||||||
|
|
||||||
@@ -78,6 +87,9 @@ def auto_self_agent_update_task(test=False):
|
|||||||
|
|
||||||
# skip if we can't determine the arch
|
# skip if we can't determine the arch
|
||||||
if agent.arch is None:
|
if agent.arch is None:
|
||||||
|
logger.warning(
|
||||||
|
f"Unable to determine arch on {agent.salt_id}. Skipping."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# golang agent only backwards compatible with py agent 0.11.2
|
# golang agent only backwards compatible with py agent 0.11.2
|
||||||
@@ -92,6 +104,9 @@ def auto_self_agent_update_task(test=False):
|
|||||||
else:
|
else:
|
||||||
url = agent.winagent_dl
|
url = agent.winagent_dl
|
||||||
inno = agent.win_inno_exe
|
inno = agent.win_inno_exe
|
||||||
|
logger.info(
|
||||||
|
f"Updating {agent.salt_id} current version {agent.version} using {inno}"
|
||||||
|
)
|
||||||
r = agent.salt_api_async(
|
r = agent.salt_api_async(
|
||||||
func="win_agent.do_agent_update_v2",
|
func="win_agent.do_agent_update_v2",
|
||||||
kwargs={
|
kwargs={
|
||||||
@@ -99,7 +114,7 @@ def auto_self_agent_update_task(test=False):
|
|||||||
"url": url,
|
"url": url,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if not test:
|
logger.info(f"{agent.salt_id}: {r}")
|
||||||
sleep(10)
|
sleep(10)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,20 +6,42 @@ from model_bakery import baker
|
|||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils import timezone as djangotime
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
from tacticalrmm.test import BaseTestCase, TacticalTestCase
|
from accounts.models import User
|
||||||
|
from tacticalrmm.test import TacticalTestCase
|
||||||
from .serializers import AgentSerializer
|
from .serializers import AgentSerializer
|
||||||
from winupdate.serializers import WinUpdatePolicySerializer
|
from winupdate.serializers import WinUpdatePolicySerializer
|
||||||
from .models import Agent
|
from .models import Agent
|
||||||
from .tasks import auto_self_agent_update_task, OLD_64_PY_AGENT, OLD_32_PY_AGENT
|
from .tasks import (
|
||||||
|
auto_self_agent_update_task,
|
||||||
|
update_salt_minion_task,
|
||||||
|
get_wmi_detail_task,
|
||||||
|
sync_salt_modules_task,
|
||||||
|
batch_sync_modules_task,
|
||||||
|
batch_sysinfo_task,
|
||||||
|
OLD_64_PY_AGENT,
|
||||||
|
OLD_32_PY_AGENT,
|
||||||
|
)
|
||||||
from winupdate.models import WinUpdatePolicy
|
from winupdate.models import WinUpdatePolicy
|
||||||
|
|
||||||
|
|
||||||
class TestAgentViews(BaseTestCase):
|
class TestAgentViews(TacticalTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.authenticate()
|
||||||
|
self.setup_coresettings()
|
||||||
|
|
||||||
|
client = baker.make("clients.Client", name="Google")
|
||||||
|
site = baker.make("clients.Site", client=client, name="LA Office")
|
||||||
|
self.agent = baker.make_recipe("agents.online_agent", site=site)
|
||||||
|
baker.make_recipe("winupdate.winupdate_policy", agent=self.agent)
|
||||||
|
|
||||||
def test_get_patch_policy(self):
|
def test_get_patch_policy(self):
|
||||||
# make sure get_patch_policy doesn't error out when agent has policy with
|
# make sure get_patch_policy doesn't error out when agent has policy with
|
||||||
# an empty patch policy
|
# an empty patch policy
|
||||||
self.agent.policy = self.policy
|
policy = baker.make("automation.Policy")
|
||||||
|
self.agent.policy = policy
|
||||||
self.agent.save(update_fields=["policy"])
|
self.agent.save(update_fields=["policy"])
|
||||||
_ = self.agent.get_patch_policy()
|
_ = self.agent.get_patch_policy()
|
||||||
|
|
||||||
@@ -30,8 +52,8 @@ class TestAgentViews(BaseTestCase):
|
|||||||
self.agent.policy = None
|
self.agent.policy = None
|
||||||
self.agent.save(update_fields=["policy"])
|
self.agent.save(update_fields=["policy"])
|
||||||
|
|
||||||
self.coresettings.server_policy = self.policy
|
self.coresettings.server_policy = policy
|
||||||
self.coresettings.workstation_policy = self.policy
|
self.coresettings.workstation_policy = policy
|
||||||
self.coresettings.save(update_fields=["server_policy", "workstation_policy"])
|
self.coresettings.save(update_fields=["server_policy", "workstation_policy"])
|
||||||
_ = self.agent.get_patch_policy()
|
_ = self.agent.get_patch_policy()
|
||||||
|
|
||||||
@@ -103,10 +125,16 @@ class TestAgentViews(BaseTestCase):
|
|||||||
|
|
||||||
@patch("agents.tasks.uninstall_agent_task.delay")
|
@patch("agents.tasks.uninstall_agent_task.delay")
|
||||||
def test_uninstall_catch_no_user(self, mock_task):
|
def test_uninstall_catch_no_user(self, mock_task):
|
||||||
|
# setup data
|
||||||
|
agent_user = User.objects.create_user(
|
||||||
|
username=self.agent.agent_id, password=User.objects.make_random_password(60)
|
||||||
|
)
|
||||||
|
agent_token = Token.objects.create(user=agent_user)
|
||||||
|
|
||||||
url = "/agents/uninstall/"
|
url = "/agents/uninstall/"
|
||||||
data = {"pk": self.agent.pk}
|
data = {"pk": self.agent.pk}
|
||||||
|
|
||||||
self.agent_user.delete()
|
agent_user.delete()
|
||||||
|
|
||||||
r = self.client.delete(url, data, format="json")
|
r = self.client.delete(url, data, format="json")
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
@@ -278,9 +306,10 @@ class TestAgentViews(BaseTestCase):
|
|||||||
def test_install_agent(self, mock_subprocess, mock_file_exists):
|
def test_install_agent(self, mock_subprocess, mock_file_exists):
|
||||||
url = f"/agents/installagent/"
|
url = f"/agents/installagent/"
|
||||||
|
|
||||||
|
site = baker.make("clients.Site")
|
||||||
data = {
|
data = {
|
||||||
"client": "Google",
|
"client": site.client.id,
|
||||||
"site": "LA Office",
|
"site": site.id,
|
||||||
"arch": "64",
|
"arch": "64",
|
||||||
"expires": 23,
|
"expires": 23,
|
||||||
"installMethod": "exe",
|
"installMethod": "exe",
|
||||||
@@ -382,12 +411,14 @@ class TestAgentViews(BaseTestCase):
|
|||||||
self.check_not_authenticated("get", url)
|
self.check_not_authenticated("get", url)
|
||||||
|
|
||||||
def test_edit_agent(self):
|
def test_edit_agent(self):
|
||||||
|
# setup data
|
||||||
|
site = baker.make("clients.Site", name="Ny Office")
|
||||||
|
|
||||||
url = "/agents/editagent/"
|
url = "/agents/editagent/"
|
||||||
|
|
||||||
edit = {
|
edit = {
|
||||||
"id": self.agent.pk,
|
"id": self.agent.pk,
|
||||||
"client": "Facebook",
|
"site": site.id,
|
||||||
"site": "NY Office",
|
|
||||||
"monitoring_type": "workstation",
|
"monitoring_type": "workstation",
|
||||||
"description": "asjdk234andasd",
|
"description": "asjdk234andasd",
|
||||||
"overdue_time": 300,
|
"overdue_time": 300,
|
||||||
@@ -417,7 +448,7 @@ class TestAgentViews(BaseTestCase):
|
|||||||
|
|
||||||
agent = Agent.objects.get(pk=self.agent.pk)
|
agent = Agent.objects.get(pk=self.agent.pk)
|
||||||
data = AgentSerializer(agent).data
|
data = AgentSerializer(agent).data
|
||||||
self.assertEqual(data["site"], "NY Office")
|
self.assertEqual(data["site"], site.id)
|
||||||
|
|
||||||
policy = WinUpdatePolicy.objects.get(agent=self.agent)
|
policy = WinUpdatePolicy.objects.get(agent=self.agent)
|
||||||
data = WinUpdatePolicySerializer(policy).data
|
data = WinUpdatePolicySerializer(policy).data
|
||||||
@@ -441,6 +472,8 @@ class TestAgentViews(BaseTestCase):
|
|||||||
self.assertIn("mstsc.html?login=", r.data["webrdp"])
|
self.assertIn("mstsc.html?login=", r.data["webrdp"])
|
||||||
|
|
||||||
self.assertEqual(self.agent.hostname, r.data["hostname"])
|
self.assertEqual(self.agent.hostname, r.data["hostname"])
|
||||||
|
self.assertEqual(self.agent.client.name, r.data["client"])
|
||||||
|
self.assertEqual(self.agent.site.name, r.data["site"])
|
||||||
|
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
@@ -451,28 +484,28 @@ class TestAgentViews(BaseTestCase):
|
|||||||
self.check_not_authenticated("get", url)
|
self.check_not_authenticated("get", url)
|
||||||
|
|
||||||
def test_by_client(self):
|
def test_by_client(self):
|
||||||
url = "/agents/byclient/Google/"
|
url = f"/agents/byclient/{self.agent.client.id}/"
|
||||||
|
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
self.assertTrue(r.data)
|
self.assertTrue(r.data)
|
||||||
|
|
||||||
url = f"/agents/byclient/Majh3 Akj34 ad/"
|
url = f"/agents/byclient/500/"
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertFalse(r.data) # returns empty list
|
self.assertFalse(r.data) # returns empty list
|
||||||
|
|
||||||
self.check_not_authenticated("get", url)
|
self.check_not_authenticated("get", url)
|
||||||
|
|
||||||
def test_by_site(self):
|
def test_by_site(self):
|
||||||
url = f"/agents/bysite/Google/Main Office/"
|
url = f"/agents/bysite/{self.agent.site.id}/"
|
||||||
|
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
self.assertTrue(r.data)
|
self.assertTrue(r.data)
|
||||||
|
|
||||||
url = f"/agents/bysite/Google/Ajdaksd Office/"
|
url = f"/agents/bysite/500/"
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertFalse(r.data)
|
self.assertEqual(r.data, [])
|
||||||
|
|
||||||
self.check_not_authenticated("get", url)
|
self.check_not_authenticated("get", url)
|
||||||
|
|
||||||
@@ -575,7 +608,7 @@ class TestAgentViews(BaseTestCase):
|
|||||||
payload = {
|
payload = {
|
||||||
"mode": "command",
|
"mode": "command",
|
||||||
"target": "client",
|
"target": "client",
|
||||||
"client": "Google",
|
"client": self.agent.client.id,
|
||||||
"site": None,
|
"site": None,
|
||||||
"agentPKs": [
|
"agentPKs": [
|
||||||
self.agent.pk,
|
self.agent.pk,
|
||||||
@@ -591,8 +624,8 @@ class TestAgentViews(BaseTestCase):
|
|||||||
payload = {
|
payload = {
|
||||||
"mode": "command",
|
"mode": "command",
|
||||||
"target": "client",
|
"target": "client",
|
||||||
"client": "Google",
|
"client": self.agent.client.id,
|
||||||
"site": "Main Office",
|
"site": self.agent.site.id,
|
||||||
"agentPKs": [
|
"agentPKs": [
|
||||||
self.agent.pk,
|
self.agent.pk,
|
||||||
],
|
],
|
||||||
@@ -604,25 +637,9 @@ class TestAgentViews(BaseTestCase):
|
|||||||
r = self.client.post(url, payload, format="json")
|
r = self.client.post(url, payload, format="json")
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
payload = {
|
|
||||||
"mode": "command",
|
|
||||||
"target": "site",
|
|
||||||
"client": "A ASJDHkjASHDASD",
|
|
||||||
"site": "asdasdasdasda",
|
|
||||||
"agentPKs": [
|
|
||||||
self.agent.pk,
|
|
||||||
],
|
|
||||||
"cmd": "gpupdate /force",
|
|
||||||
"timeout": 300,
|
|
||||||
"shell": "cmd",
|
|
||||||
}
|
|
||||||
|
|
||||||
r = self.client.post(url, payload, format="json")
|
|
||||||
self.assertEqual(r.status_code, 404)
|
|
||||||
|
|
||||||
mock_ret.return_value = "timeout"
|
mock_ret.return_value = "timeout"
|
||||||
payload["client"] = "Google"
|
payload["client"] = self.agent.client.id
|
||||||
payload["site"] = "Main Office"
|
payload["site"] = self.agent.site.id
|
||||||
r = self.client.post(url, payload, format="json")
|
r = self.client.post(url, payload, format="json")
|
||||||
self.assertEqual(r.status_code, 400)
|
self.assertEqual(r.status_code, 400)
|
||||||
|
|
||||||
@@ -643,7 +660,7 @@ class TestAgentViews(BaseTestCase):
|
|||||||
payload = {
|
payload = {
|
||||||
"mode": "install",
|
"mode": "install",
|
||||||
"target": "client",
|
"target": "client",
|
||||||
"client": "Google",
|
"client": self.agent.client.id,
|
||||||
"site": None,
|
"site": None,
|
||||||
"agentPKs": [
|
"agentPKs": [
|
||||||
self.agent.pk,
|
self.agent.pk,
|
||||||
@@ -712,7 +729,6 @@ class TestAgentViews(BaseTestCase):
|
|||||||
class TestAgentViewsNew(TacticalTestCase):
|
class TestAgentViewsNew(TacticalTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
self.setup_coresettings()
|
|
||||||
|
|
||||||
def test_agent_counts(self):
|
def test_agent_counts(self):
|
||||||
url = "/agents/agent_counts/"
|
url = "/agents/agent_counts/"
|
||||||
@@ -748,13 +764,13 @@ class TestAgentViewsNew(TacticalTestCase):
|
|||||||
|
|
||||||
def test_agent_maintenance_mode(self):
|
def test_agent_maintenance_mode(self):
|
||||||
url = "/agents/maintenance/"
|
url = "/agents/maintenance/"
|
||||||
# create data
|
|
||||||
client = baker.make("clients.Client", client="Default")
|
# setup data
|
||||||
site = baker.make("clients.Site", client=client, site="Site")
|
site = baker.make("clients.Site")
|
||||||
agent = baker.make_recipe("agents.agent", client=client.client, site=site.site)
|
agent = baker.make_recipe("agents.agent", site=site)
|
||||||
|
|
||||||
# Test client toggle maintenance mode
|
# Test client toggle maintenance mode
|
||||||
data = {"type": "Client", "id": client.id, "action": True}
|
data = {"type": "Client", "id": site.client.id, "action": True}
|
||||||
|
|
||||||
r = self.client.post(url, data, format="json")
|
r = self.client.post(url, data, format="json")
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
@@ -782,8 +798,125 @@ class TestAgentViewsNew(TacticalTestCase):
|
|||||||
|
|
||||||
self.check_not_authenticated("post", url)
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAgentTasks(TacticalTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.authenticate()
|
||||||
|
self.setup_coresettings()
|
||||||
|
|
||||||
|
@patch("agents.models.Agent.salt_api_async", return_value=None)
|
||||||
|
def test_get_wmi_detail_task(self, salt_api_async):
|
||||||
|
self.agent = baker.make_recipe("agents.agent")
|
||||||
|
ret = get_wmi_detail_task.s(self.agent.pk).apply()
|
||||||
|
salt_api_async.assert_called_with(timeout=30, func="win_agent.local_sys_info")
|
||||||
|
self.assertEqual(ret.status, "SUCCESS")
|
||||||
|
|
||||||
|
@patch("agents.models.Agent.salt_api_cmd")
|
||||||
|
def test_sync_salt_modules_task(self, salt_api_cmd):
|
||||||
|
self.agent = baker.make_recipe("agents.agent")
|
||||||
|
salt_api_cmd.return_value = {"return": [{f"{self.agent.salt_id}": []}]}
|
||||||
|
ret = sync_salt_modules_task.s(self.agent.pk).apply()
|
||||||
|
salt_api_cmd.assert_called_with(timeout=35, func="saltutil.sync_modules")
|
||||||
|
self.assertEqual(
|
||||||
|
ret.result, f"Successfully synced salt modules on {self.agent.hostname}"
|
||||||
|
)
|
||||||
|
self.assertEqual(ret.status, "SUCCESS")
|
||||||
|
|
||||||
|
salt_api_cmd.return_value = "timeout"
|
||||||
|
ret = sync_salt_modules_task.s(self.agent.pk).apply()
|
||||||
|
self.assertEqual(ret.result, f"Unable to sync modules {self.agent.salt_id}")
|
||||||
|
|
||||||
|
salt_api_cmd.return_value = "error"
|
||||||
|
ret = sync_salt_modules_task.s(self.agent.pk).apply()
|
||||||
|
self.assertEqual(ret.result, f"Unable to sync modules {self.agent.salt_id}")
|
||||||
|
|
||||||
|
@patch("agents.models.Agent.salt_batch_async", return_value=None)
|
||||||
|
@patch("agents.tasks.sleep", return_value=None)
|
||||||
|
def test_batch_sync_modules_task(self, mock_sleep, salt_batch_async):
|
||||||
|
# chunks of 50, 60 online should run only 2 times
|
||||||
|
baker.make_recipe(
|
||||||
|
"agents.online_agent", last_seen=djangotime.now(), _quantity=60
|
||||||
|
)
|
||||||
|
baker.make_recipe(
|
||||||
|
"agents.overdue_agent",
|
||||||
|
last_seen=djangotime.now() - djangotime.timedelta(minutes=9),
|
||||||
|
_quantity=115,
|
||||||
|
)
|
||||||
|
ret = batch_sync_modules_task.s().apply()
|
||||||
|
self.assertEqual(salt_batch_async.call_count, 2)
|
||||||
|
self.assertEqual(ret.status, "SUCCESS")
|
||||||
|
|
||||||
|
@patch("agents.models.Agent.salt_batch_async", return_value=None)
|
||||||
|
@patch("agents.tasks.sleep", return_value=None)
|
||||||
|
def test_batch_sysinfo_task(self, mock_sleep, salt_batch_async):
|
||||||
|
# chunks of 30, 70 online should run only 3 times
|
||||||
|
self.online = baker.make_recipe(
|
||||||
|
"agents.online_agent", version=settings.LATEST_AGENT_VER, _quantity=70
|
||||||
|
)
|
||||||
|
self.overdue = baker.make_recipe(
|
||||||
|
"agents.overdue_agent", version=settings.LATEST_AGENT_VER, _quantity=115
|
||||||
|
)
|
||||||
|
ret = batch_sysinfo_task.s().apply()
|
||||||
|
self.assertEqual(salt_batch_async.call_count, 3)
|
||||||
|
self.assertEqual(ret.status, "SUCCESS")
|
||||||
|
salt_batch_async.reset_mock()
|
||||||
|
[i.delete() for i in self.online]
|
||||||
|
[i.delete() for i in self.overdue]
|
||||||
|
|
||||||
|
# test old agents, should not run
|
||||||
|
self.online_old = baker.make_recipe(
|
||||||
|
"agents.online_agent", version="0.10.2", _quantity=70
|
||||||
|
)
|
||||||
|
self.overdue_old = baker.make_recipe(
|
||||||
|
"agents.overdue_agent", version="0.10.2", _quantity=115
|
||||||
|
)
|
||||||
|
ret = batch_sysinfo_task.s().apply()
|
||||||
|
salt_batch_async.assert_not_called()
|
||||||
|
self.assertEqual(ret.status, "SUCCESS")
|
||||||
|
|
||||||
|
@patch("agents.models.Agent.salt_api_async", return_value=None)
|
||||||
|
@patch("agents.tasks.sleep", return_value=None)
|
||||||
|
def test_update_salt_minion_task(self, mock_sleep, salt_api_async):
|
||||||
|
# test agents that need salt update
|
||||||
|
self.agents = baker.make_recipe(
|
||||||
|
"agents.agent",
|
||||||
|
version=settings.LATEST_AGENT_VER,
|
||||||
|
salt_ver="1.0.3",
|
||||||
|
_quantity=53,
|
||||||
|
)
|
||||||
|
ret = update_salt_minion_task.s().apply()
|
||||||
|
self.assertEqual(salt_api_async.call_count, 53)
|
||||||
|
self.assertEqual(ret.status, "SUCCESS")
|
||||||
|
[i.delete() for i in self.agents]
|
||||||
|
salt_api_async.reset_mock()
|
||||||
|
|
||||||
|
# test agents that need salt update but agent version too low
|
||||||
|
self.agents = baker.make_recipe(
|
||||||
|
"agents.agent",
|
||||||
|
version="0.10.2",
|
||||||
|
salt_ver="1.0.3",
|
||||||
|
_quantity=53,
|
||||||
|
)
|
||||||
|
ret = update_salt_minion_task.s().apply()
|
||||||
|
self.assertEqual(ret.status, "SUCCESS")
|
||||||
|
salt_api_async.assert_not_called()
|
||||||
|
[i.delete() for i in self.agents]
|
||||||
|
salt_api_async.reset_mock()
|
||||||
|
|
||||||
|
# test agents already on latest salt ver
|
||||||
|
self.agents = baker.make_recipe(
|
||||||
|
"agents.agent",
|
||||||
|
version=settings.LATEST_AGENT_VER,
|
||||||
|
salt_ver=settings.LATEST_SALT_VER,
|
||||||
|
_quantity=53,
|
||||||
|
)
|
||||||
|
ret = update_salt_minion_task.s().apply()
|
||||||
|
self.assertEqual(ret.status, "SUCCESS")
|
||||||
|
salt_api_async.assert_not_called()
|
||||||
|
|
||||||
@patch("agents.models.Agent.salt_api_async")
|
@patch("agents.models.Agent.salt_api_async")
|
||||||
def test_auto_self_agent_update_task(self, salt_api_async):
|
@patch("agents.tasks.sleep", return_value=None)
|
||||||
|
def test_auto_self_agent_update_task(self, mock_sleep, salt_api_async):
|
||||||
# test 64bit golang agent
|
# test 64bit golang agent
|
||||||
self.agent64 = baker.make_recipe(
|
self.agent64 = baker.make_recipe(
|
||||||
"agents.agent",
|
"agents.agent",
|
||||||
@@ -791,7 +924,7 @@ class TestAgentViewsNew(TacticalTestCase):
|
|||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
)
|
)
|
||||||
salt_api_async.return_value = True
|
salt_api_async.return_value = True
|
||||||
ret = auto_self_agent_update_task.s(test=True).apply()
|
ret = auto_self_agent_update_task.s().apply()
|
||||||
salt_api_async.assert_called_with(
|
salt_api_async.assert_called_with(
|
||||||
func="win_agent.do_agent_update_v2",
|
func="win_agent.do_agent_update_v2",
|
||||||
kwargs={
|
kwargs={
|
||||||
@@ -810,7 +943,7 @@ class TestAgentViewsNew(TacticalTestCase):
|
|||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
)
|
)
|
||||||
salt_api_async.return_value = True
|
salt_api_async.return_value = True
|
||||||
ret = auto_self_agent_update_task.s(test=True).apply()
|
ret = auto_self_agent_update_task.s().apply()
|
||||||
salt_api_async.assert_called_with(
|
salt_api_async.assert_called_with(
|
||||||
func="win_agent.do_agent_update_v2",
|
func="win_agent.do_agent_update_v2",
|
||||||
kwargs={
|
kwargs={
|
||||||
@@ -828,7 +961,7 @@ class TestAgentViewsNew(TacticalTestCase):
|
|||||||
operating_system=None,
|
operating_system=None,
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
)
|
)
|
||||||
ret = auto_self_agent_update_task.s(test=True).apply()
|
ret = auto_self_agent_update_task.s().apply()
|
||||||
salt_api_async.assert_not_called()
|
salt_api_async.assert_not_called()
|
||||||
self.agentNone.delete()
|
self.agentNone.delete()
|
||||||
salt_api_async.reset_mock()
|
salt_api_async.reset_mock()
|
||||||
@@ -841,7 +974,7 @@ class TestAgentViewsNew(TacticalTestCase):
|
|||||||
)
|
)
|
||||||
self.coresettings.agent_auto_update = False
|
self.coresettings.agent_auto_update = False
|
||||||
self.coresettings.save(update_fields=["agent_auto_update"])
|
self.coresettings.save(update_fields=["agent_auto_update"])
|
||||||
ret = auto_self_agent_update_task.s(test=True).apply()
|
ret = auto_self_agent_update_task.s().apply()
|
||||||
salt_api_async.assert_not_called()
|
salt_api_async.assert_not_called()
|
||||||
|
|
||||||
# reset core settings
|
# reset core settings
|
||||||
@@ -857,7 +990,7 @@ class TestAgentViewsNew(TacticalTestCase):
|
|||||||
version="0.11.1",
|
version="0.11.1",
|
||||||
)
|
)
|
||||||
salt_api_async.return_value = True
|
salt_api_async.return_value = True
|
||||||
ret = auto_self_agent_update_task.s(test=True).apply()
|
ret = auto_self_agent_update_task.s().apply()
|
||||||
salt_api_async.assert_called_with(
|
salt_api_async.assert_called_with(
|
||||||
func="win_agent.do_agent_update_v2",
|
func="win_agent.do_agent_update_v2",
|
||||||
kwargs={
|
kwargs={
|
||||||
@@ -876,7 +1009,7 @@ class TestAgentViewsNew(TacticalTestCase):
|
|||||||
version="0.11.1",
|
version="0.11.1",
|
||||||
)
|
)
|
||||||
salt_api_async.return_value = True
|
salt_api_async.return_value = True
|
||||||
ret = auto_self_agent_update_task.s(test=True).apply()
|
ret = auto_self_agent_update_task.s().apply()
|
||||||
salt_api_async.assert_called_with(
|
salt_api_async.assert_called_with(
|
||||||
func="win_agent.do_agent_update_v2",
|
func="win_agent.do_agent_update_v2",
|
||||||
kwargs={
|
kwargs={
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ urlpatterns = [
|
|||||||
path("listagents/", views.AgentsTableList.as_view()),
|
path("listagents/", views.AgentsTableList.as_view()),
|
||||||
path("listagentsnodetail/", views.list_agents_no_detail),
|
path("listagentsnodetail/", views.list_agents_no_detail),
|
||||||
path("<int:pk>/agenteditdetails/", views.agent_edit_details),
|
path("<int:pk>/agenteditdetails/", views.agent_edit_details),
|
||||||
path("byclient/<client>/", views.by_client),
|
path("byclient/<int:clientpk>/", views.by_client),
|
||||||
path("bysite/<client>/<site>/", views.by_site),
|
path("bysite/<int:sitepk>/", views.by_site),
|
||||||
path("overdueaction/", views.overdue_action),
|
path("overdueaction/", views.overdue_action),
|
||||||
path("sendrawcmd/", views.send_raw_cmd),
|
path("sendrawcmd/", views.send_raw_cmd),
|
||||||
path("<pk>/agentdetail/", views.agent_detail),
|
path("<pk>/agentdetail/", views.agent_detail),
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ def edit_agent(request):
|
|||||||
a_serializer.is_valid(raise_exception=True)
|
a_serializer.is_valid(raise_exception=True)
|
||||||
a_serializer.save()
|
a_serializer.save()
|
||||||
|
|
||||||
policy = WinUpdatePolicy.objects.get(agent=agent)
|
policy = agent.winupdatepolicy.get()
|
||||||
p_serializer = WinUpdatePolicySerializer(
|
p_serializer = WinUpdatePolicySerializer(
|
||||||
instance=policy, data=request.data["winupdatepolicy"][0]
|
instance=policy, data=request.data["winupdatepolicy"][0]
|
||||||
)
|
)
|
||||||
@@ -145,6 +145,8 @@ def meshcentral(request, pk):
|
|||||||
"file": file,
|
"file": file,
|
||||||
"webrdp": webrdp,
|
"webrdp": webrdp,
|
||||||
"status": agent.status,
|
"status": agent.status,
|
||||||
|
"client": agent.client.name,
|
||||||
|
"site": agent.site.name,
|
||||||
}
|
}
|
||||||
return Response(ret)
|
return Response(ret)
|
||||||
|
|
||||||
@@ -249,11 +251,13 @@ def send_raw_cmd(request):
|
|||||||
|
|
||||||
|
|
||||||
class AgentsTableList(generics.ListAPIView):
|
class AgentsTableList(generics.ListAPIView):
|
||||||
queryset = Agent.objects.prefetch_related("agentchecks").only(
|
queryset = (
|
||||||
|
Agent.objects.select_related("site")
|
||||||
|
.prefetch_related("agentchecks")
|
||||||
|
.only(
|
||||||
"pk",
|
"pk",
|
||||||
"hostname",
|
"hostname",
|
||||||
"agent_id",
|
"agent_id",
|
||||||
"client",
|
|
||||||
"site",
|
"site",
|
||||||
"monitoring_type",
|
"monitoring_type",
|
||||||
"description",
|
"description",
|
||||||
@@ -268,6 +272,7 @@ class AgentsTableList(generics.ListAPIView):
|
|||||||
"time_zone",
|
"time_zone",
|
||||||
"maintenance_mode",
|
"maintenance_mode",
|
||||||
)
|
)
|
||||||
|
)
|
||||||
serializer_class = AgentTableSerializer
|
serializer_class = AgentTableSerializer
|
||||||
|
|
||||||
def list(self, request):
|
def list(self, request):
|
||||||
@@ -281,7 +286,7 @@ class AgentsTableList(generics.ListAPIView):
|
|||||||
|
|
||||||
@api_view()
|
@api_view()
|
||||||
def list_agents_no_detail(request):
|
def list_agents_no_detail(request):
|
||||||
agents = Agent.objects.all()
|
agents = Agent.objects.select_related("site").only("pk", "hostname", "site")
|
||||||
return Response(AgentHostnameSerializer(agents, many=True).data)
|
return Response(AgentHostnameSerializer(agents, many=True).data)
|
||||||
|
|
||||||
|
|
||||||
@@ -292,15 +297,15 @@ def agent_edit_details(request, pk):
|
|||||||
|
|
||||||
|
|
||||||
@api_view()
|
@api_view()
|
||||||
def by_client(request, client):
|
def by_client(request, clientpk):
|
||||||
agents = (
|
agents = (
|
||||||
Agent.objects.filter(client=client)
|
Agent.objects.select_related("site")
|
||||||
|
.filter(site__client_id=clientpk)
|
||||||
.prefetch_related("agentchecks")
|
.prefetch_related("agentchecks")
|
||||||
.only(
|
.only(
|
||||||
"pk",
|
"pk",
|
||||||
"hostname",
|
"hostname",
|
||||||
"agent_id",
|
"agent_id",
|
||||||
"client",
|
|
||||||
"site",
|
"site",
|
||||||
"monitoring_type",
|
"monitoring_type",
|
||||||
"description",
|
"description",
|
||||||
@@ -321,15 +326,15 @@ def by_client(request, client):
|
|||||||
|
|
||||||
|
|
||||||
@api_view()
|
@api_view()
|
||||||
def by_site(request, client, site):
|
def by_site(request, sitepk):
|
||||||
agents = (
|
agents = (
|
||||||
Agent.objects.filter(client=client, site=site)
|
Agent.objects.filter(site_id=sitepk)
|
||||||
|
.select_related("site")
|
||||||
.prefetch_related("agentchecks")
|
.prefetch_related("agentchecks")
|
||||||
.only(
|
.only(
|
||||||
"pk",
|
"pk",
|
||||||
"hostname",
|
"hostname",
|
||||||
"agent_id",
|
"agent_id",
|
||||||
"client",
|
|
||||||
"site",
|
"site",
|
||||||
"monitoring_type",
|
"monitoring_type",
|
||||||
"description",
|
"description",
|
||||||
@@ -398,8 +403,8 @@ def reboot_later(request):
|
|||||||
def install_agent(request):
|
def install_agent(request):
|
||||||
from knox.models import AuthToken
|
from knox.models import AuthToken
|
||||||
|
|
||||||
client = get_object_or_404(Client, client=request.data["client"])
|
client_id = request.data["client"]
|
||||||
site = get_object_or_404(Site, client=client, site=request.data["site"])
|
site_id = request.data["site"]
|
||||||
version = settings.LATEST_AGENT_VER
|
version = settings.LATEST_AGENT_VER
|
||||||
arch = request.data["arch"]
|
arch = request.data["arch"]
|
||||||
|
|
||||||
@@ -454,8 +459,8 @@ def install_agent(request):
|
|||||||
"build",
|
"build",
|
||||||
f"-ldflags=\"-X 'main.Inno={inno}'",
|
f"-ldflags=\"-X 'main.Inno={inno}'",
|
||||||
f"-X 'main.Api={api}'",
|
f"-X 'main.Api={api}'",
|
||||||
f"-X 'main.Client={client.pk}'",
|
f"-X 'main.Client={client_id}'",
|
||||||
f"-X 'main.Site={site.pk}'",
|
f"-X 'main.Site={site_id}'",
|
||||||
f"-X 'main.Atype={atype}'",
|
f"-X 'main.Atype={atype}'",
|
||||||
f"-X 'main.Rdp={rdp}'",
|
f"-X 'main.Rdp={rdp}'",
|
||||||
f"-X 'main.Ping={ping}'",
|
f"-X 'main.Ping={ping}'",
|
||||||
@@ -563,9 +568,9 @@ def install_agent(request):
|
|||||||
"--api",
|
"--api",
|
||||||
request.data["api"],
|
request.data["api"],
|
||||||
"--client-id",
|
"--client-id",
|
||||||
client.pk,
|
client_id,
|
||||||
"--site-id",
|
"--site-id",
|
||||||
site.pk,
|
site_id,
|
||||||
"--agent-type",
|
"--agent-type",
|
||||||
request.data["agenttype"],
|
request.data["agenttype"],
|
||||||
"--auth",
|
"--auth",
|
||||||
@@ -597,8 +602,8 @@ def install_agent(request):
|
|||||||
|
|
||||||
replace_dict = {
|
replace_dict = {
|
||||||
"innosetupchange": inno,
|
"innosetupchange": inno,
|
||||||
"clientchange": str(client.pk),
|
"clientchange": str(client_id),
|
||||||
"sitechange": str(site.pk),
|
"sitechange": str(site_id),
|
||||||
"apichange": request.data["api"],
|
"apichange": request.data["api"],
|
||||||
"atypechange": request.data["agenttype"],
|
"atypechange": request.data["agenttype"],
|
||||||
"powerchange": str(request.data["power"]),
|
"powerchange": str(request.data["power"]),
|
||||||
@@ -807,14 +812,9 @@ def bulk(request):
|
|||||||
return notify_error("Must select at least 1 agent")
|
return notify_error("Must select at least 1 agent")
|
||||||
|
|
||||||
if request.data["target"] == "client":
|
if request.data["target"] == "client":
|
||||||
client = get_object_or_404(Client, client=request.data["client"])
|
agents = Agent.objects.filter(site__client_id=request.data["client"])
|
||||||
agents = Agent.objects.filter(client=client.client)
|
|
||||||
elif request.data["target"] == "site":
|
elif request.data["target"] == "site":
|
||||||
client = get_object_or_404(Client, client=request.data["client"])
|
agents = Agent.objects.filter(site_id=request.data["site"])
|
||||||
site = (
|
|
||||||
Site.objects.filter(client=client).filter(site=request.data["site"]).get()
|
|
||||||
)
|
|
||||||
agents = Agent.objects.filter(client=client.client).filter(site=site.site)
|
|
||||||
elif request.data["target"] == "agents":
|
elif request.data["target"] == "agents":
|
||||||
agents = Agent.objects.filter(pk__in=request.data["agentPKs"])
|
agents = Agent.objects.filter(pk__in=request.data["agentPKs"])
|
||||||
elif request.data["target"] == "all":
|
elif request.data["target"] == "all":
|
||||||
@@ -904,14 +904,12 @@ def agent_counts(request):
|
|||||||
@api_view(["POST"])
|
@api_view(["POST"])
|
||||||
def agent_maintenance(request):
|
def agent_maintenance(request):
|
||||||
if request.data["type"] == "Client":
|
if request.data["type"] == "Client":
|
||||||
client = Client.objects.get(pk=request.data["id"])
|
Agent.objects.filter(site__client_id=request.data["id"]).update(
|
||||||
Agent.objects.filter(client=client.client).update(
|
|
||||||
maintenance_mode=request.data["action"]
|
maintenance_mode=request.data["action"]
|
||||||
)
|
)
|
||||||
|
|
||||||
elif request.data["type"] == "Site":
|
elif request.data["type"] == "Site":
|
||||||
site = Site.objects.get(pk=request.data["id"])
|
Agent.objects.filter(site_id=request.data["id"]).update(
|
||||||
Agent.objects.filter(client=site.client.client, site=site.site).update(
|
|
||||||
maintenance_mode=request.data["action"]
|
maintenance_mode=request.data["action"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
from tacticalrmm.test import TacticalTestCase
|
from tacticalrmm.test import TacticalTestCase
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from model_bakery import baker
|
||||||
|
from itertools import cycle
|
||||||
|
|
||||||
|
|
||||||
class TestAPIv2(TacticalTestCase):
|
class TestAPIv2(TacticalTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
self.setup_coresettings()
|
self.setup_coresettings()
|
||||||
self.agent_setup()
|
|
||||||
|
|
||||||
@patch("agents.models.Agent.salt_api_cmd")
|
@patch("agents.models.Agent.salt_api_cmd")
|
||||||
def test_sync_modules(self, mock_ret):
|
def test_sync_modules(self, mock_ret):
|
||||||
|
# setup data
|
||||||
|
agent = baker.make_recipe("agents.agent")
|
||||||
url = "/api/v2/saltminion/"
|
url = "/api/v2/saltminion/"
|
||||||
payload = {"agent_id": self.agent.agent_id}
|
payload = {"agent_id": agent.agent_id}
|
||||||
|
|
||||||
mock_ret.return_value = "error"
|
mock_ret.return_value = "error"
|
||||||
r = self.client.patch(url, payload, format="json")
|
r = self.client.patch(url, payload, format="json")
|
||||||
|
|||||||
@@ -2,11 +2,18 @@ import os
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from tacticalrmm.test import BaseTestCase
|
from tacticalrmm.test import TacticalTestCase
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from model_bakery import baker
|
||||||
|
from itertools import cycle
|
||||||
|
|
||||||
|
|
||||||
class TestAPIv3(BaseTestCase):
|
class TestAPIv3(TacticalTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.authenticate()
|
||||||
|
self.setup_coresettings()
|
||||||
|
self.agent = baker.make_recipe("agents.agent")
|
||||||
|
|
||||||
def test_get_checks(self):
|
def test_get_checks(self):
|
||||||
url = f"/api/v3/{self.agent.agent_id}/checkrunner/"
|
url = f"/api/v3/{self.agent.agent_id}/checkrunner/"
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ from rest_framework.authtoken.models import Token
|
|||||||
from agents.models import Agent
|
from agents.models import Agent
|
||||||
from checks.models import Check
|
from checks.models import Check
|
||||||
from autotasks.models import AutomatedTask
|
from autotasks.models import AutomatedTask
|
||||||
from winupdate.models import WinUpdate
|
|
||||||
from accounts.models import User
|
from accounts.models import User
|
||||||
from clients.models import Client, Site
|
|
||||||
from winupdate.models import WinUpdatePolicy
|
from winupdate.models import WinUpdatePolicy
|
||||||
from checks.serializers import CheckRunnerGetSerializerV3
|
from checks.serializers import CheckRunnerGetSerializerV3
|
||||||
from agents.serializers import WinAgentSerializer
|
from agents.serializers import WinAgentSerializer
|
||||||
@@ -419,14 +417,10 @@ class NewAgent(APIView):
|
|||||||
"Agent already exists. Remove old agent first if trying to re-install"
|
"Agent already exists. Remove old agent first if trying to re-install"
|
||||||
)
|
)
|
||||||
|
|
||||||
client = get_object_or_404(Client, pk=int(request.data["client"]))
|
|
||||||
site = get_object_or_404(Site, pk=int(request.data["site"]))
|
|
||||||
|
|
||||||
agent = Agent(
|
agent = Agent(
|
||||||
agent_id=request.data["agent_id"],
|
agent_id=request.data["agent_id"],
|
||||||
hostname=request.data["hostname"],
|
hostname=request.data["hostname"],
|
||||||
client=client.client,
|
site_id=int(request.data["site"]),
|
||||||
site=site.site,
|
|
||||||
monitoring_type=request.data["monitoring_type"],
|
monitoring_type=request.data["monitoring_type"],
|
||||||
description=request.data["description"],
|
description=request.data["description"],
|
||||||
mesh_node_id=request.data["mesh_node_id"],
|
mesh_node_id=request.data["mesh_node_id"],
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Policy, PolicyExclusions
|
from .models import Policy
|
||||||
|
|
||||||
admin.site.register(Policy)
|
admin.site.register(Policy)
|
||||||
admin.site.register(PolicyExclusions)
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-02 19:13
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('automation', '0005_auto_20200922_1344'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='PolicyExclusions',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -32,16 +32,15 @@ class Policy(BaseAuditModel):
|
|||||||
|
|
||||||
filtered_agents_pks = Policy.objects.none()
|
filtered_agents_pks = Policy.objects.none()
|
||||||
|
|
||||||
for site in explicit_sites:
|
|
||||||
if site.client not in explicit_clients:
|
|
||||||
filtered_agents_pks |= Agent.objects.filter(
|
filtered_agents_pks |= Agent.objects.filter(
|
||||||
client=site.client.client,
|
site__in=[
|
||||||
site=site.site,
|
site for site in explicit_sites if site.client not in explicit_clients
|
||||||
|
],
|
||||||
monitoring_type=mon_type,
|
monitoring_type=mon_type,
|
||||||
).values_list("pk", flat=True)
|
).values_list("pk", flat=True)
|
||||||
|
|
||||||
filtered_agents_pks |= Agent.objects.filter(
|
filtered_agents_pks |= Agent.objects.filter(
|
||||||
client__in=[client.client for client in explicit_clients],
|
site__client__in=[client for client in explicit_clients],
|
||||||
monitoring_type=mon_type,
|
monitoring_type=mon_type,
|
||||||
).values_list("pk", flat=True)
|
).values_list("pk", flat=True)
|
||||||
|
|
||||||
@@ -68,8 +67,8 @@ class Policy(BaseAuditModel):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Get policies applied to agent and agent site and client
|
# Get policies applied to agent and agent site and client
|
||||||
client = Client.objects.get(client=agent.client)
|
client = agent.client
|
||||||
site = Site.objects.filter(client=client).get(site=agent.site)
|
site = agent.site
|
||||||
|
|
||||||
default_policy = None
|
default_policy = None
|
||||||
client_policy = None
|
client_policy = None
|
||||||
@@ -121,8 +120,8 @@ class Policy(BaseAuditModel):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Get policies applied to agent and agent site and client
|
# Get policies applied to agent and agent site and client
|
||||||
client = Client.objects.get(client=agent.client)
|
client = agent.client
|
||||||
site = Site.objects.filter(client=client).get(site=agent.site)
|
site = agent.site
|
||||||
|
|
||||||
default_policy = None
|
default_policy = None
|
||||||
client_policy = None
|
client_policy = None
|
||||||
@@ -300,11 +299,3 @@ class Policy(BaseAuditModel):
|
|||||||
if tasks:
|
if tasks:
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
task.create_policy_task(agent)
|
task.create_policy_task(agent)
|
||||||
|
|
||||||
|
|
||||||
class PolicyExclusions(models.Model):
|
|
||||||
policy = models.ForeignKey(
|
|
||||||
Policy, related_name="exclusions", on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
agents = models.ManyToManyField(Agent, related_name="policy_exclusions")
|
|
||||||
sites = models.ManyToManyField(Site, related_name="policy_exclusions")
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ from rest_framework.serializers import (
|
|||||||
ReadOnlyField,
|
ReadOnlyField,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from clients.serializers import ClientSerializer, SiteSerializer
|
||||||
|
from agents.serializers import AgentHostnameSerializer
|
||||||
|
|
||||||
from .models import Policy
|
from .models import Policy
|
||||||
from agents.models import Agent
|
from agents.models import Agent
|
||||||
from autotasks.models import AutomatedTask
|
from autotasks.models import AutomatedTask
|
||||||
@@ -21,11 +24,11 @@ class PolicySerializer(ModelSerializer):
|
|||||||
|
|
||||||
class PolicyTableSerializer(ModelSerializer):
|
class PolicyTableSerializer(ModelSerializer):
|
||||||
|
|
||||||
server_clients = StringRelatedField(many=True, read_only=True)
|
server_clients = ClientSerializer(many=True, read_only=True)
|
||||||
server_sites = StringRelatedField(many=True, read_only=True)
|
server_sites = SiteSerializer(many=True, read_only=True)
|
||||||
workstation_clients = StringRelatedField(many=True, read_only=True)
|
workstation_clients = ClientSerializer(many=True, read_only=True)
|
||||||
workstation_sites = StringRelatedField(many=True, read_only=True)
|
workstation_sites = SiteSerializer(many=True, read_only=True)
|
||||||
agents = StringRelatedField(many=True, read_only=True)
|
agents = AgentHostnameSerializer(many=True, read_only=True)
|
||||||
default_server_policy = ReadOnlyField(source="is_default_server_policy")
|
default_server_policy = ReadOnlyField(source="is_default_server_policy")
|
||||||
default_workstation_policy = ReadOnlyField(source="is_default_workstation_policy")
|
default_workstation_policy = ReadOnlyField(source="is_default_workstation_policy")
|
||||||
agents_count = SerializerMethodField(read_only=True)
|
agents_count = SerializerMethodField(read_only=True)
|
||||||
@@ -43,7 +46,7 @@ class PolicyTableSerializer(ModelSerializer):
|
|||||||
class PolicyOverviewSerializer(ModelSerializer):
|
class PolicyOverviewSerializer(ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Client
|
model = Client
|
||||||
fields = ("pk", "client", "sites", "workstation_policy", "server_policy")
|
fields = ("pk", "name", "sites", "workstation_policy", "server_policy")
|
||||||
depth = 2
|
depth = 2
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# create policy with tasks and checks
|
# create policy with tasks and checks
|
||||||
policy = baker.make("automation.Policy")
|
policy = baker.make("automation.Policy")
|
||||||
checks = self.create_checks(policy=policy)
|
self.create_checks(policy=policy)
|
||||||
tasks = baker.make("autotasks.AutomatedTask", policy=policy, _quantity=3)
|
baker.make("autotasks.AutomatedTask", policy=policy, _quantity=3)
|
||||||
|
|
||||||
# test copy tasks and checks to another policy
|
# test copy tasks and checks to another policy
|
||||||
data = {
|
data = {
|
||||||
@@ -152,7 +152,7 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# create policy with tasks
|
# create policy with tasks
|
||||||
policy = baker.make("automation.Policy")
|
policy = baker.make("automation.Policy")
|
||||||
tasks = baker.make("autotasks.AutomatedTask", policy=policy, _quantity=3)
|
baker.make("autotasks.AutomatedTask", policy=policy, _quantity=3)
|
||||||
url = f"/automation/{policy.pk}/policyautomatedtasks/"
|
url = f"/automation/{policy.pk}/policyautomatedtasks/"
|
||||||
|
|
||||||
resp = self.client.get(url, format="json")
|
resp = self.client.get(url, format="json")
|
||||||
@@ -202,6 +202,8 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
self.check_not_authenticated("patch", url)
|
self.check_not_authenticated("patch", url)
|
||||||
|
|
||||||
def test_policy_overview(self):
|
def test_policy_overview(self):
|
||||||
|
from clients.models import Client
|
||||||
|
|
||||||
url = "/automation/policies/overview/"
|
url = "/automation/policies/overview/"
|
||||||
|
|
||||||
policies = baker.make(
|
policies = baker.make(
|
||||||
@@ -213,7 +215,7 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
workstation_policy=cycle(policies),
|
workstation_policy=cycle(policies),
|
||||||
_quantity=5,
|
_quantity=5,
|
||||||
)
|
)
|
||||||
sites = baker.make(
|
baker.make(
|
||||||
"clients.Site",
|
"clients.Site",
|
||||||
client=cycle(clients),
|
client=cycle(clients),
|
||||||
server_policy=cycle(policies),
|
server_policy=cycle(policies),
|
||||||
@@ -221,8 +223,9 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
_quantity=4,
|
_quantity=4,
|
||||||
)
|
)
|
||||||
|
|
||||||
sites = baker.make("clients.Site", client=cycle(clients), _quantity=3)
|
baker.make("clients.Site", client=cycle(clients), _quantity=3)
|
||||||
resp = self.client.get(url, format="json")
|
resp = self.client.get(url, format="json")
|
||||||
|
clients = Client.objects.all()
|
||||||
serializer = PolicyOverviewSerializer(clients, many=True)
|
serializer = PolicyOverviewSerializer(clients, many=True)
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
@@ -256,31 +259,31 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# data setup
|
# data setup
|
||||||
policy = baker.make("automation.Policy")
|
policy = baker.make("automation.Policy")
|
||||||
client = baker.make("clients.Client", client="Test Client")
|
client = baker.make("clients.Client")
|
||||||
site = baker.make("clients.Site", client=client, site="Test Site")
|
site = baker.make("clients.Site", client=client)
|
||||||
agent = baker.make_recipe("agents.agent", client=client.client, site=site.site)
|
agent = baker.make_recipe("agents.agent", site=site)
|
||||||
|
|
||||||
# test add client to policy data
|
# test add client to policy data
|
||||||
client_server_payload = {
|
client_server_payload = {
|
||||||
"type": "client",
|
"type": "client",
|
||||||
"pk": client.pk,
|
"pk": agent.client.pk,
|
||||||
"server_policy": policy.pk,
|
"server_policy": policy.pk,
|
||||||
}
|
}
|
||||||
client_workstation_payload = {
|
client_workstation_payload = {
|
||||||
"type": "client",
|
"type": "client",
|
||||||
"pk": client.pk,
|
"pk": agent.client.pk,
|
||||||
"workstation_policy": policy.pk,
|
"workstation_policy": policy.pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
# test add site to policy data
|
# test add site to policy data
|
||||||
site_server_payload = {
|
site_server_payload = {
|
||||||
"type": "site",
|
"type": "site",
|
||||||
"pk": site.pk,
|
"pk": agent.site.pk,
|
||||||
"server_policy": policy.pk,
|
"server_policy": policy.pk,
|
||||||
}
|
}
|
||||||
site_workstation_payload = {
|
site_workstation_payload = {
|
||||||
"type": "site",
|
"type": "site",
|
||||||
"pk": site.pk,
|
"pk": agent.site.pk,
|
||||||
"workstation_policy": policy.pk,
|
"workstation_policy": policy.pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +296,7 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# called because the relation changed
|
# called because the relation changed
|
||||||
mock_checks_location_task.assert_called_with(
|
mock_checks_location_task.assert_called_with(
|
||||||
location={"client": client.client},
|
location={"site__client_id": client.id},
|
||||||
mon_type="server",
|
mon_type="server",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -306,7 +309,7 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# called because the relation changed
|
# called because the relation changed
|
||||||
mock_checks_location_task.assert_called_with(
|
mock_checks_location_task.assert_called_with(
|
||||||
location={"client": client.client},
|
location={"site__client_id": client.id},
|
||||||
mon_type="workstation",
|
mon_type="workstation",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -319,7 +322,7 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# called because the relation changed
|
# called because the relation changed
|
||||||
mock_checks_location_task.assert_called_with(
|
mock_checks_location_task.assert_called_with(
|
||||||
location={"client": site.client.client, "site": site.site},
|
location={"site_id": site.id},
|
||||||
mon_type="server",
|
mon_type="server",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -332,7 +335,7 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# called because the relation changed
|
# called because the relation changed
|
||||||
mock_checks_location_task.assert_called_with(
|
mock_checks_location_task.assert_called_with(
|
||||||
location={"client": site.client.client, "site": site.site},
|
location={"site_id": site.id},
|
||||||
mon_type="workstation",
|
mon_type="workstation",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -391,7 +394,7 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# called because the relation changed
|
# called because the relation changed
|
||||||
mock_checks_location_task.assert_called_with(
|
mock_checks_location_task.assert_called_with(
|
||||||
location={"client": client.client},
|
location={"site__client_id": client.id},
|
||||||
mon_type="server",
|
mon_type="server",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -404,7 +407,7 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# called because the relation changed
|
# called because the relation changed
|
||||||
mock_checks_location_task.assert_called_with(
|
mock_checks_location_task.assert_called_with(
|
||||||
location={"client": client.client},
|
location={"site__client_id": client.id},
|
||||||
mon_type="workstation",
|
mon_type="workstation",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -417,7 +420,7 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# called because the relation changed
|
# called because the relation changed
|
||||||
mock_checks_location_task.assert_called_with(
|
mock_checks_location_task.assert_called_with(
|
||||||
location={"client": site.client.client, "site": site.site},
|
location={"site_id": site.id},
|
||||||
mon_type="server",
|
mon_type="server",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -430,7 +433,7 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
# called because the relation changed
|
# called because the relation changed
|
||||||
mock_checks_location_task.assert_called_with(
|
mock_checks_location_task.assert_called_with(
|
||||||
location={"client": site.client.client, "site": site.site},
|
location={"site_id": site.id},
|
||||||
mon_type="workstation",
|
mon_type="workstation",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -471,14 +474,14 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
|
|
||||||
self.check_not_authenticated("post", url)
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
def test_relation_by_type(self):
|
def test_get_relation_by_type(self):
|
||||||
url = f"/automation/related/"
|
url = f"/automation/related/"
|
||||||
|
|
||||||
# data setup
|
# data setup
|
||||||
policy = baker.make("automation.Policy")
|
policy = baker.make("automation.Policy")
|
||||||
client = baker.make("clients.Client", client="Test Client")
|
client = baker.make("clients.Client", workstation_policy=policy)
|
||||||
site = baker.make("clients.Site", client=client, site="Test Site")
|
site = baker.make("clients.Site", server_policy=policy)
|
||||||
agent = baker.make_recipe("agents.agent", client=client.client, site=site.site)
|
agent = baker.make_recipe("agents.agent", site=site, policy=policy)
|
||||||
|
|
||||||
client_payload = {"type": "client", "pk": client.pk}
|
client_payload = {"type": "client", "pk": client.pk}
|
||||||
|
|
||||||
@@ -621,43 +624,38 @@ class TestPolicyViews(TacticalTestCase):
|
|||||||
"reprocess_failed_inherit": True,
|
"reprocess_failed_inherit": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
# create agents in sites
|
clients = baker.make("clients.Client", _quantity=6)
|
||||||
clients = baker.make("clients.Client", client=seq("Client"), _quantity=3)
|
sites = baker.make("clients.Site", client=cycle(clients), _quantity=10)
|
||||||
sites = baker.make(
|
|
||||||
"clients.Site", client=cycle(clients), site=seq("Site"), _quantity=6
|
|
||||||
)
|
|
||||||
|
|
||||||
agents = baker.make_recipe(
|
agents = baker.make_recipe(
|
||||||
"agents.agent",
|
"agents.agent",
|
||||||
client=cycle([x.client for x in clients]),
|
site=cycle(sites),
|
||||||
site=cycle([x.site for x in sites]),
|
|
||||||
_quantity=6,
|
_quantity=6,
|
||||||
)
|
)
|
||||||
|
|
||||||
# create patch policies
|
# create patch policies
|
||||||
patch_policies = baker.make_recipe(
|
baker.make_recipe(
|
||||||
"winupdate.winupdate_approve", agent=cycle(agents), _quantity=6
|
"winupdate.winupdate_approve", agent=cycle(agents), _quantity=6
|
||||||
)
|
)
|
||||||
|
|
||||||
# test reset agents in site
|
# test reset agents in site
|
||||||
data = {"client": clients[0].client, "site": "Site0"}
|
data = {"site": sites[0].id}
|
||||||
|
|
||||||
resp = self.client.patch(url, data, format="json")
|
resp = self.client.patch(url, data, format="json")
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
agents = Agent.objects.filter(client=clients[0].client, site="Site0")
|
agents = Agent.objects.filter(site=sites[0])
|
||||||
|
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
for k, v in inherit_fields.items():
|
for k, v in inherit_fields.items():
|
||||||
self.assertEqual(getattr(agent.winupdatepolicy.get(), k), v)
|
self.assertEqual(getattr(agent.winupdatepolicy.get(), k), v)
|
||||||
|
|
||||||
# test reset agents in client
|
# test reset agents in client
|
||||||
data = {"client": clients[1].client}
|
data = {"client": clients[1].id}
|
||||||
|
|
||||||
resp = self.client.patch(url, data, format="json")
|
resp = self.client.patch(url, data, format="json")
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
agents = Agent.objects.filter(client=clients[1].client)
|
agents = Agent.objects.filter(site__client=clients[1])
|
||||||
|
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
for k, v in inherit_fields.items():
|
for k, v in inherit_fields.items():
|
||||||
@@ -703,40 +701,24 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
def test_policy_related(self):
|
def test_policy_related(self):
|
||||||
|
|
||||||
# Get Site and Client from an agent in list
|
# Get Site and Client from an agent in list
|
||||||
clients = baker.make("clients.Client", client=seq("Client"), _quantity=5)
|
clients = baker.make("clients.Client", _quantity=5)
|
||||||
sites = baker.make(
|
sites = baker.make("clients.Site", client=cycle(clients), _quantity=25)
|
||||||
"clients.Site", client=cycle(clients), site=seq("Site"), _quantity=25
|
|
||||||
)
|
|
||||||
server_agents = baker.make_recipe(
|
server_agents = baker.make_recipe(
|
||||||
"agents.server_agent",
|
"agents.server_agent",
|
||||||
client=cycle([x.client for x in clients]),
|
site=cycle(sites),
|
||||||
site=seq("Site"),
|
|
||||||
_quantity=25,
|
_quantity=25,
|
||||||
)
|
)
|
||||||
workstation_agents = baker.make_recipe(
|
workstation_agents = baker.make_recipe(
|
||||||
"agents.workstation_agent",
|
"agents.workstation_agent",
|
||||||
client=cycle([x.client for x in clients]),
|
site=cycle(sites),
|
||||||
site=seq("Site"),
|
|
||||||
_quantity=25,
|
_quantity=25,
|
||||||
)
|
)
|
||||||
|
|
||||||
server_client = clients[3]
|
|
||||||
server_site = server_client.sites.all()[3]
|
|
||||||
workstation_client = clients[1]
|
|
||||||
workstation_site = server_client.sites.all()[2]
|
|
||||||
server_agent = baker.make_recipe(
|
|
||||||
"agents.server_agent", client=server_client.client, site=server_site.site
|
|
||||||
)
|
|
||||||
workstation_agent = baker.make_recipe(
|
|
||||||
"agents.workstation_agent",
|
|
||||||
client=workstation_client.client,
|
|
||||||
site=workstation_site.site,
|
|
||||||
)
|
|
||||||
policy = baker.make("automation.Policy", active=True)
|
policy = baker.make("automation.Policy", active=True)
|
||||||
|
|
||||||
# Add Client to Policy
|
# Add Client to Policy
|
||||||
policy.server_clients.add(server_client)
|
policy.server_clients.add(server_agents[13].client)
|
||||||
policy.workstation_clients.add(workstation_client)
|
policy.workstation_clients.add(workstation_agents[15].client)
|
||||||
|
|
||||||
resp = self.client.get(
|
resp = self.client.get(
|
||||||
f"/automation/policies/{policy.pk}/related/", format="json"
|
f"/automation/policies/{policy.pk}/related/", format="json"
|
||||||
@@ -747,19 +729,19 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
self.assertEquals(len(resp.data["server_sites"]), 5)
|
self.assertEquals(len(resp.data["server_sites"]), 5)
|
||||||
self.assertEquals(len(resp.data["workstation_clients"]), 1)
|
self.assertEquals(len(resp.data["workstation_clients"]), 1)
|
||||||
self.assertEquals(len(resp.data["workstation_sites"]), 5)
|
self.assertEquals(len(resp.data["workstation_sites"]), 5)
|
||||||
self.assertEquals(len(resp.data["agents"]), 12)
|
self.assertEquals(len(resp.data["agents"]), 10)
|
||||||
|
|
||||||
# Add Site to Policy and the agents and sites length shouldn't change
|
# Add Site to Policy and the agents and sites length shouldn't change
|
||||||
policy.server_sites.add(server_site)
|
policy.server_sites.add(server_agents[13].site)
|
||||||
policy.workstation_sites.add(workstation_site)
|
policy.workstation_sites.add(workstation_agents[15].site)
|
||||||
self.assertEquals(len(resp.data["server_sites"]), 5)
|
self.assertEquals(len(resp.data["server_sites"]), 5)
|
||||||
self.assertEquals(len(resp.data["workstation_sites"]), 5)
|
self.assertEquals(len(resp.data["workstation_sites"]), 5)
|
||||||
self.assertEquals(len(resp.data["agents"]), 12)
|
self.assertEquals(len(resp.data["agents"]), 10)
|
||||||
|
|
||||||
# Add Agent to Policy and the agents length shouldn't change
|
# Add Agent to Policy and the agents length shouldn't change
|
||||||
policy.agents.add(server_agent)
|
policy.agents.add(server_agents[13])
|
||||||
policy.agents.add(workstation_agent)
|
policy.agents.add(workstation_agents[15])
|
||||||
self.assertEquals(len(resp.data["agents"]), 12)
|
self.assertEquals(len(resp.data["agents"]), 10)
|
||||||
|
|
||||||
def test_generating_agent_policy_checks(self):
|
def test_generating_agent_policy_checks(self):
|
||||||
from .tasks import generate_agent_checks_from_policies_task
|
from .tasks import generate_agent_checks_from_policies_task
|
||||||
@@ -767,9 +749,8 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
# setup data
|
# setup data
|
||||||
policy = baker.make("automation.Policy", active=True)
|
policy = baker.make("automation.Policy", active=True)
|
||||||
checks = self.create_checks(policy=policy)
|
checks = self.create_checks(policy=policy)
|
||||||
client = baker.make("clients.Client", client="Default")
|
site = baker.make("clients.Site")
|
||||||
baker.make("clients.Site", client=client, site="Default")
|
agent = baker.make_recipe("agents.agent", site=site, policy=policy)
|
||||||
agent = baker.make_recipe("agents.agent", policy=policy)
|
|
||||||
|
|
||||||
# test policy assigned to agent
|
# test policy assigned to agent
|
||||||
generate_agent_checks_from_policies_task(policy.id, clear=True)
|
generate_agent_checks_from_policies_task(policy.id, clear=True)
|
||||||
@@ -815,9 +796,8 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
policy = baker.make("automation.Policy", active=True, enforced=True)
|
policy = baker.make("automation.Policy", active=True, enforced=True)
|
||||||
script = baker.make_recipe("scripts.script")
|
script = baker.make_recipe("scripts.script")
|
||||||
self.create_checks(policy=policy, script=script)
|
self.create_checks(policy=policy, script=script)
|
||||||
client = baker.make("clients.Client", client="Default")
|
site = baker.make("clients.Site")
|
||||||
baker.make("clients.Site", client=client, site="Default")
|
agent = baker.make_recipe("agents.agent", site=site, policy=policy)
|
||||||
agent = baker.make_recipe("agents.agent", policy=policy)
|
|
||||||
self.create_checks(agent=agent, script=script)
|
self.create_checks(agent=agent, script=script)
|
||||||
|
|
||||||
generate_agent_checks_from_policies_task(policy.id, create_tasks=True)
|
generate_agent_checks_from_policies_task(policy.id, create_tasks=True)
|
||||||
@@ -839,25 +819,18 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
self.create_checks(policy=policy)
|
self.create_checks(policy=policy)
|
||||||
clients = baker.make(
|
clients = baker.make(
|
||||||
"clients.Client",
|
"clients.Client",
|
||||||
client=seq("Default"),
|
|
||||||
_quantity=2,
|
_quantity=2,
|
||||||
server_policy=policy,
|
server_policy=policy,
|
||||||
workstation_policy=policy,
|
workstation_policy=policy,
|
||||||
)
|
)
|
||||||
baker.make(
|
sites = baker.make("clients.Site", client=cycle(clients), _quantity=4)
|
||||||
"clients.Site", client=cycle(clients), site=seq("Default"), _quantity=4
|
server_agent = baker.make_recipe("agents.server_agent", site=sites[0])
|
||||||
)
|
workstation_agent = baker.make_recipe("agents.workstation_agent", site=sites[2])
|
||||||
server_agent = baker.make_recipe(
|
agent1 = baker.make_recipe("agents.server_agent", site=sites[1])
|
||||||
"agents.server_agent", client="Default1", site="Default1"
|
agent2 = baker.make_recipe("agents.workstation_agent", site=sites[3])
|
||||||
)
|
|
||||||
workstation_agent = baker.make_recipe(
|
|
||||||
"agents.workstation_agent", client="Default1", site="Default3"
|
|
||||||
)
|
|
||||||
agent1 = baker.make_recipe("agents.agent", client="Default2", site="Default2")
|
|
||||||
agent2 = baker.make_recipe("agents.agent", client="Default2", site="Default4")
|
|
||||||
|
|
||||||
generate_agent_checks_by_location_task(
|
generate_agent_checks_by_location_task(
|
||||||
{"client": "Default1", "site": "Default1"},
|
{"site_id": sites[0].id},
|
||||||
"server",
|
"server",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -871,7 +844,10 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
self.assertEqual(Agent.objects.get(pk=agent1.id).agentchecks.count(), 0)
|
self.assertEqual(Agent.objects.get(pk=agent1.id).agentchecks.count(), 0)
|
||||||
|
|
||||||
generate_agent_checks_by_location_task(
|
generate_agent_checks_by_location_task(
|
||||||
{"client": "Default1"}, "workstation", clear=True, create_tasks=True
|
{"site__client_id": clients[0].id},
|
||||||
|
"workstation",
|
||||||
|
clear=True,
|
||||||
|
create_tasks=True,
|
||||||
)
|
)
|
||||||
# workstation_agent should now have policy checks and the other agents should not
|
# workstation_agent should now have policy checks and the other agents should not
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -888,18 +864,12 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
# setup data
|
# setup data
|
||||||
policy = baker.make("automation.Policy", active=True)
|
policy = baker.make("automation.Policy", active=True)
|
||||||
self.create_checks(policy=policy)
|
self.create_checks(policy=policy)
|
||||||
clients = baker.make("clients.Client", client=seq("Default"), _quantity=2)
|
|
||||||
baker.make(
|
site = baker.make("clients.Site")
|
||||||
"clients.Site", client=cycle(clients), site=seq("Default"), _quantity=4
|
server_agents = baker.make_recipe("agents.server_agent", site=site, _quantity=3)
|
||||||
|
workstation_agents = baker.make_recipe(
|
||||||
|
"agents.workstation_agent", site=site, _quantity=4
|
||||||
)
|
)
|
||||||
server_agent = baker.make_recipe(
|
|
||||||
"agents.server_agent", client="Default1", site="Default1"
|
|
||||||
)
|
|
||||||
workstation_agent = baker.make_recipe(
|
|
||||||
"agents.workstation_agent", client="Default1", site="Default3"
|
|
||||||
)
|
|
||||||
agent1 = baker.make_recipe("agents.agent", client="Default2", site="Default2")
|
|
||||||
agent2 = baker.make_recipe("agents.agent", client="Default2", site="Default4")
|
|
||||||
core = CoreSettings.objects.first()
|
core = CoreSettings.objects.first()
|
||||||
core.server_policy = policy
|
core.server_policy = policy
|
||||||
core.workstation_policy = policy
|
core.workstation_policy = policy
|
||||||
@@ -908,22 +878,20 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
generate_all_agent_checks_task("server", clear=True, create_tasks=True)
|
generate_all_agent_checks_task("server", clear=True, create_tasks=True)
|
||||||
|
|
||||||
# all servers should have 7 checks
|
# all servers should have 7 checks
|
||||||
self.assertEqual(
|
for agent in server_agents:
|
||||||
Agent.objects.get(pk=workstation_agent.id).agentchecks.count(), 0
|
self.assertEqual(Agent.objects.get(pk=agent.id).agentchecks.count(), 7)
|
||||||
)
|
|
||||||
self.assertEqual(Agent.objects.get(pk=server_agent.id).agentchecks.count(), 7)
|
for agent in workstation_agents:
|
||||||
self.assertEqual(Agent.objects.get(pk=agent1.id).agentchecks.count(), 7)
|
self.assertEqual(Agent.objects.get(pk=agent.id).agentchecks.count(), 0)
|
||||||
self.assertEqual(Agent.objects.get(pk=agent2.id).agentchecks.count(), 0)
|
|
||||||
|
|
||||||
generate_all_agent_checks_task("workstation", clear=True, create_tasks=True)
|
generate_all_agent_checks_task("workstation", clear=True, create_tasks=True)
|
||||||
|
|
||||||
# all agents should have 7 checks now
|
# all agents should have 7 checks now
|
||||||
self.assertEqual(
|
for agent in server_agents:
|
||||||
Agent.objects.get(pk=workstation_agent.id).agentchecks.count(), 7
|
self.assertEqual(Agent.objects.get(pk=agent.id).agentchecks.count(), 7)
|
||||||
)
|
|
||||||
self.assertEqual(Agent.objects.get(pk=server_agent.id).agentchecks.count(), 7)
|
for agent in workstation_agents:
|
||||||
self.assertEqual(Agent.objects.get(pk=agent1.id).agentchecks.count(), 7)
|
self.assertEqual(Agent.objects.get(pk=agent.id).agentchecks.count(), 7)
|
||||||
self.assertEqual(Agent.objects.get(pk=agent2.id).agentchecks.count(), 7)
|
|
||||||
|
|
||||||
def test_delete_policy_check(self):
|
def test_delete_policy_check(self):
|
||||||
from .tasks import delete_policy_check_task
|
from .tasks import delete_policy_check_task
|
||||||
@@ -931,11 +899,8 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
|
|
||||||
policy = baker.make("automation.Policy", active=True)
|
policy = baker.make("automation.Policy", active=True)
|
||||||
self.create_checks(policy=policy)
|
self.create_checks(policy=policy)
|
||||||
client = baker.make("clients.Client", client="Default", server_policy=policy)
|
site = baker.make("clients.Site")
|
||||||
baker.make("clients.Site", client=client, site="Default")
|
agent = baker.make_recipe("agents.server_agent", site=site, policy=policy)
|
||||||
agent = baker.make_recipe(
|
|
||||||
"agents.server_agent", client="Default", site="Default"
|
|
||||||
)
|
|
||||||
agent.generate_checks_from_policies()
|
agent.generate_checks_from_policies()
|
||||||
|
|
||||||
# make sure agent has 7 checks
|
# make sure agent has 7 checks
|
||||||
@@ -960,11 +925,7 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
|
|
||||||
policy = baker.make("automation.Policy", active=True)
|
policy = baker.make("automation.Policy", active=True)
|
||||||
self.create_checks(policy=policy)
|
self.create_checks(policy=policy)
|
||||||
client = baker.make("clients.Client", client="Default", server_policy=policy)
|
agent = baker.make_recipe("agents.server_agent", policy=policy)
|
||||||
baker.make("clients.Site", client=client, site="Default")
|
|
||||||
agent = baker.make_recipe(
|
|
||||||
"agents.server_agent", client="Default", site="Default"
|
|
||||||
)
|
|
||||||
agent.generate_checks_from_policies()
|
agent.generate_checks_from_policies()
|
||||||
|
|
||||||
# make sure agent has 7 checks
|
# make sure agent has 7 checks
|
||||||
@@ -997,11 +958,8 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
tasks = baker.make(
|
tasks = baker.make(
|
||||||
"autotasks.AutomatedTask", policy=policy, name=seq("Task"), _quantity=3
|
"autotasks.AutomatedTask", policy=policy, name=seq("Task"), _quantity=3
|
||||||
)
|
)
|
||||||
client = baker.make("clients.Client", client="Default")
|
site = baker.make("clients.Site")
|
||||||
baker.make("clients.Site", client=client, site="Default")
|
agent = baker.make_recipe("agents.server_agent", site=site, policy=policy)
|
||||||
agent = baker.make_recipe(
|
|
||||||
"agents.server_agent", client="Default", site="Default", policy=policy
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_agent_tasks_from_policies_task(policy.id, clear=True)
|
generate_agent_tasks_from_policies_task(policy.id, clear=True)
|
||||||
|
|
||||||
@@ -1027,33 +985,26 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
|
|
||||||
# setup data
|
# setup data
|
||||||
policy = baker.make("automation.Policy", active=True)
|
policy = baker.make("automation.Policy", active=True)
|
||||||
tasks = baker.make(
|
baker.make(
|
||||||
"autotasks.AutomatedTask", policy=policy, name=seq("Task"), _quantity=3
|
"autotasks.AutomatedTask", policy=policy, name=seq("Task"), _quantity=3
|
||||||
)
|
)
|
||||||
clients = baker.make(
|
clients = baker.make(
|
||||||
"clients.Client",
|
"clients.Client",
|
||||||
client=seq("Default"),
|
|
||||||
_quantity=2,
|
_quantity=2,
|
||||||
server_policy=policy,
|
server_policy=policy,
|
||||||
workstation_policy=policy,
|
workstation_policy=policy,
|
||||||
)
|
)
|
||||||
baker.make(
|
sites = baker.make("clients.Site", client=cycle(clients), _quantity=4)
|
||||||
"clients.Site", client=cycle(clients), site=seq("Default"), _quantity=4
|
server_agent = baker.make_recipe("agents.server_agent", site=sites[0])
|
||||||
)
|
workstation_agent = baker.make_recipe("agents.workstation_agent", site=sites[2])
|
||||||
server_agent = baker.make_recipe(
|
agent1 = baker.make_recipe("agents.agent", site=sites[1])
|
||||||
"agents.server_agent", client="Default1", site="Default1"
|
agent2 = baker.make_recipe("agents.agent", site=sites[3])
|
||||||
)
|
|
||||||
workstation_agent = baker.make_recipe(
|
|
||||||
"agents.workstation_agent", client="Default1", site="Default3"
|
|
||||||
)
|
|
||||||
agent1 = baker.make_recipe("agents.agent", client="Default2", site="Default2")
|
|
||||||
agent2 = baker.make_recipe("agents.agent", client="Default2", site="Default4")
|
|
||||||
|
|
||||||
generate_agent_tasks_by_location_task(
|
generate_agent_tasks_by_location_task(
|
||||||
{"client": "Default1", "site": "Default1"}, "server", clear=True
|
{"site_id": sites[0].id}, "server", clear=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# all servers in Default1 and site Default1 should have 3 tasks
|
# all servers in site1 and site2 should have 3 tasks
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
Agent.objects.get(pk=workstation_agent.id).autotasks.count(), 0
|
Agent.objects.get(pk=workstation_agent.id).autotasks.count(), 0
|
||||||
)
|
)
|
||||||
@@ -1062,7 +1013,7 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
self.assertEqual(Agent.objects.get(pk=agent2.id).autotasks.count(), 0)
|
self.assertEqual(Agent.objects.get(pk=agent2.id).autotasks.count(), 0)
|
||||||
|
|
||||||
generate_agent_tasks_by_location_task(
|
generate_agent_tasks_by_location_task(
|
||||||
{"client": "Default1"}, "workstation", clear=True
|
{"site__client_id": clients[0].id}, "workstation", clear=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# all workstations in Default1 should have 3 tasks
|
# all workstations in Default1 should have 3 tasks
|
||||||
@@ -1079,11 +1030,8 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
|
|
||||||
policy = baker.make("automation.Policy", active=True)
|
policy = baker.make("automation.Policy", active=True)
|
||||||
tasks = baker.make("autotasks.AutomatedTask", policy=policy, _quantity=3)
|
tasks = baker.make("autotasks.AutomatedTask", policy=policy, _quantity=3)
|
||||||
client = baker.make("clients.Client", client="Default", server_policy=policy)
|
site = baker.make("clients.Site")
|
||||||
baker.make("clients.Site", client=client, site="Default")
|
agent = baker.make_recipe("agents.server_agent", site=site, policy=policy)
|
||||||
agent = baker.make_recipe(
|
|
||||||
"agents.server_agent", client="Default", site="Default"
|
|
||||||
)
|
|
||||||
agent.generate_tasks_from_policies()
|
agent.generate_tasks_from_policies()
|
||||||
|
|
||||||
delete_policy_autotask_task(tasks[0].id)
|
delete_policy_autotask_task(tasks[0].id)
|
||||||
@@ -1103,7 +1051,7 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
for task in tasks:
|
for task in tasks:
|
||||||
run_win_task.assert_any_call(task.id)
|
run_win_task.assert_any_call(task.id)
|
||||||
|
|
||||||
def test_updated_policy_tasks(self):
|
def test_update_policy_tasks(self):
|
||||||
from .tasks import update_policy_task_fields_task
|
from .tasks import update_policy_task_fields_task
|
||||||
from autotasks.models import AutomatedTask
|
from autotasks.models import AutomatedTask
|
||||||
|
|
||||||
@@ -1112,11 +1060,8 @@ class TestPolicyTasks(TacticalTestCase):
|
|||||||
tasks = baker.make(
|
tasks = baker.make(
|
||||||
"autotasks.AutomatedTask", enabled=True, policy=policy, _quantity=3
|
"autotasks.AutomatedTask", enabled=True, policy=policy, _quantity=3
|
||||||
)
|
)
|
||||||
client = baker.make("clients.Client", client="Default", server_policy=policy)
|
site = baker.make("clients.Site")
|
||||||
baker.make("clients.Site", client=client, site="Default")
|
agent = baker.make_recipe("agents.server_agent", site=site, policy=policy)
|
||||||
agent = baker.make_recipe(
|
|
||||||
"agents.server_agent", client="Default", site="Default"
|
|
||||||
)
|
|
||||||
agent.generate_tasks_from_policies()
|
agent.generate_tasks_from_policies()
|
||||||
|
|
||||||
tasks[0].enabled = False
|
tasks[0].enabled = False
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from django.db import DataError
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
@@ -12,7 +11,7 @@ from checks.models import Check
|
|||||||
from autotasks.models import AutomatedTask
|
from autotasks.models import AutomatedTask
|
||||||
from winupdate.models import WinUpdatePolicy
|
from winupdate.models import WinUpdatePolicy
|
||||||
|
|
||||||
from clients.serializers import ClientSerializer, TreeSerializer
|
from clients.serializers import ClientSerializer, SiteSerializer
|
||||||
from agents.serializers import AgentHostnameSerializer
|
from agents.serializers import AgentHostnameSerializer
|
||||||
from winupdate.serializers import WinUpdatePolicySerializer
|
from winupdate.serializers import WinUpdatePolicySerializer
|
||||||
|
|
||||||
@@ -33,7 +32,6 @@ from .tasks import (
|
|||||||
generate_agent_checks_from_policies_task,
|
generate_agent_checks_from_policies_task,
|
||||||
generate_agent_checks_by_location_task,
|
generate_agent_checks_by_location_task,
|
||||||
generate_agent_tasks_from_policies_task,
|
generate_agent_tasks_from_policies_task,
|
||||||
generate_agent_tasks_by_location_task,
|
|
||||||
run_win_policy_autotask_task,
|
run_win_policy_autotask_task,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -172,7 +170,7 @@ class GetRelated(APIView):
|
|||||||
if site not in policy.server_sites.all():
|
if site not in policy.server_sites.all():
|
||||||
filtered_server_sites.append(site)
|
filtered_server_sites.append(site)
|
||||||
|
|
||||||
response["server_sites"] = TreeSerializer(
|
response["server_sites"] = SiteSerializer(
|
||||||
filtered_server_sites + list(policy.server_sites.all()), many=True
|
filtered_server_sites + list(policy.server_sites.all()), many=True
|
||||||
).data
|
).data
|
||||||
|
|
||||||
@@ -181,7 +179,7 @@ class GetRelated(APIView):
|
|||||||
if site not in policy.workstation_sites.all():
|
if site not in policy.workstation_sites.all():
|
||||||
filtered_workstation_sites.append(site)
|
filtered_workstation_sites.append(site)
|
||||||
|
|
||||||
response["workstation_sites"] = TreeSerializer(
|
response["workstation_sites"] = SiteSerializer(
|
||||||
filtered_workstation_sites + list(policy.workstation_sites.all()), many=True
|
filtered_workstation_sites + list(policy.workstation_sites.all()), many=True
|
||||||
).data
|
).data
|
||||||
|
|
||||||
@@ -218,7 +216,7 @@ class GetRelated(APIView):
|
|||||||
client.save()
|
client.save()
|
||||||
|
|
||||||
generate_agent_checks_by_location_task.delay(
|
generate_agent_checks_by_location_task.delay(
|
||||||
location={"client": client.client},
|
location={"site__client_id": client.id},
|
||||||
mon_type="workstation",
|
mon_type="workstation",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -236,7 +234,7 @@ class GetRelated(APIView):
|
|||||||
site.workstation_policy = policy
|
site.workstation_policy = policy
|
||||||
site.save()
|
site.save()
|
||||||
generate_agent_checks_by_location_task.delay(
|
generate_agent_checks_by_location_task.delay(
|
||||||
location={"client": site.client.client, "site": site.site},
|
location={"site_id": site.id},
|
||||||
mon_type="workstation",
|
mon_type="workstation",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -258,7 +256,7 @@ class GetRelated(APIView):
|
|||||||
client.server_policy = policy
|
client.server_policy = policy
|
||||||
client.save()
|
client.save()
|
||||||
generate_agent_checks_by_location_task.delay(
|
generate_agent_checks_by_location_task.delay(
|
||||||
location={"client": client.client},
|
location={"site__client_id": client.id},
|
||||||
mon_type="server",
|
mon_type="server",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -276,7 +274,7 @@ class GetRelated(APIView):
|
|||||||
site.server_policy = policy
|
site.server_policy = policy
|
||||||
site.save()
|
site.save()
|
||||||
generate_agent_checks_by_location_task.delay(
|
generate_agent_checks_by_location_task.delay(
|
||||||
location={"client": site.client.client, "site": site.site},
|
location={"site_id": site.id},
|
||||||
mon_type="server",
|
mon_type="server",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -296,7 +294,7 @@ class GetRelated(APIView):
|
|||||||
client.workstation_policy = None
|
client.workstation_policy = None
|
||||||
client.save()
|
client.save()
|
||||||
generate_agent_checks_by_location_task.delay(
|
generate_agent_checks_by_location_task.delay(
|
||||||
location={"client": client.client},
|
location={"site__client_id": client.id},
|
||||||
mon_type="workstation",
|
mon_type="workstation",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -311,7 +309,7 @@ class GetRelated(APIView):
|
|||||||
site.workstation_policy = None
|
site.workstation_policy = None
|
||||||
site.save()
|
site.save()
|
||||||
generate_agent_checks_by_location_task.delay(
|
generate_agent_checks_by_location_task.delay(
|
||||||
location={"client": site.client.client, "site": site.site},
|
location={"site_id": site.id},
|
||||||
mon_type="workstation",
|
mon_type="workstation",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -329,7 +327,7 @@ class GetRelated(APIView):
|
|||||||
client.server_policy = None
|
client.server_policy = None
|
||||||
client.save()
|
client.save()
|
||||||
generate_agent_checks_by_location_task.delay(
|
generate_agent_checks_by_location_task.delay(
|
||||||
location={"client": client.client},
|
location={"site__client_id": client.id},
|
||||||
mon_type="server",
|
mon_type="server",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -343,7 +341,7 @@ class GetRelated(APIView):
|
|||||||
site.server_policy = None
|
site.server_policy = None
|
||||||
site.save()
|
site.save()
|
||||||
generate_agent_checks_by_location_task.delay(
|
generate_agent_checks_by_location_task.delay(
|
||||||
location={"client": site.client.client, "site": site.site},
|
location={"site_id": site.pk},
|
||||||
mon_type="server",
|
mon_type="server",
|
||||||
clear=True,
|
clear=True,
|
||||||
create_tasks=True,
|
create_tasks=True,
|
||||||
@@ -423,12 +421,10 @@ class UpdatePatchPolicy(APIView):
|
|||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
|
|
||||||
agents = None
|
agents = None
|
||||||
if "client" in request.data and "site" in request.data:
|
if "client" in request.data:
|
||||||
agents = Agent.objects.filter(
|
agents = Agent.objects.filter(site__client_id=request.data["client"])
|
||||||
client=request.data["client"], site=request.data["site"]
|
elif "site" in request.data:
|
||||||
)
|
agents = Agent.objects.filter(site_id=request.data["site"])
|
||||||
elif "client" in request.data:
|
|
||||||
agents = Agent.objects.filter(client=request.data["client"])
|
|
||||||
else:
|
else:
|
||||||
agents = Agent.objects.all()
|
agents = Agent.objects.all()
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import pytz
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
@@ -122,6 +123,15 @@ class AutomatedTask(BaseAuditModel):
|
|||||||
days = ",".join(ret)
|
days = ",".join(ret)
|
||||||
return f"{days} at {run_time_nice}"
|
return f"{days} at {run_time_nice}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_run_as_timezone(self):
|
||||||
|
if self.last_run is not None and self.agent is not None:
|
||||||
|
return self.last_run.astimezone(
|
||||||
|
pytz.timezone(self.agent.timezone)
|
||||||
|
).strftime("%b-%d-%Y - %H:%M")
|
||||||
|
|
||||||
|
return self.last_run
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_task_name():
|
def generate_task_name():
|
||||||
chars = string.ascii_letters
|
chars = string.ascii_letters
|
||||||
@@ -137,7 +147,7 @@ class AutomatedTask(BaseAuditModel):
|
|||||||
def create_policy_task(self, agent=None, policy=None):
|
def create_policy_task(self, agent=None, policy=None):
|
||||||
from .tasks import create_win_task_schedule
|
from .tasks import create_win_task_schedule
|
||||||
|
|
||||||
# exit is neither are set or if both are set
|
# exit if neither are set or if both are set
|
||||||
if not agent and not policy or agent and policy:
|
if not agent and not policy or agent and policy:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import pytz
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import AutomatedTask
|
from .models import AutomatedTask
|
||||||
@@ -12,6 +13,7 @@ class TaskSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
assigned_check = CheckSerializer(read_only=True)
|
assigned_check = CheckSerializer(read_only=True)
|
||||||
schedule = serializers.ReadOnlyField()
|
schedule = serializers.ReadOnlyField()
|
||||||
|
last_run = serializers.ReadOnlyField(source="last_run_as_timezone")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AutomatedTask
|
model = AutomatedTask
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import pytz
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
@@ -9,6 +10,7 @@ from agents.models import Agent
|
|||||||
from checks.models import Check
|
from checks.models import Check
|
||||||
|
|
||||||
from scripts.models import Script
|
from scripts.models import Script
|
||||||
|
from core.models import CoreSettings
|
||||||
|
|
||||||
from .serializers import TaskSerializer, AutoTaskSerializer
|
from .serializers import TaskSerializer, AutoTaskSerializer
|
||||||
|
|
||||||
@@ -68,8 +70,12 @@ class AddAutoTask(APIView):
|
|||||||
class AutoTask(APIView):
|
class AutoTask(APIView):
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
agent = Agent.objects.only("pk").get(pk=pk)
|
agent = get_object_or_404(Agent, pk=pk)
|
||||||
return Response(AutoTaskSerializer(agent).data)
|
ctx = {
|
||||||
|
"default_tz": pytz.timezone(CoreSettings.objects.first().default_time_zone),
|
||||||
|
"agent_tz": agent.time_zone,
|
||||||
|
}
|
||||||
|
return Response(AutoTaskSerializer(agent, context=ctx).data)
|
||||||
|
|
||||||
def patch(self, request, pk):
|
def patch(self, request, pk):
|
||||||
from automation.tasks import update_policy_task_fields_task
|
from automation.tasks import update_policy_task_fields_task
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import base64
|
|||||||
import string
|
import string
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import pytz
|
||||||
import zlib
|
import zlib
|
||||||
from statistics import mean
|
from statistics import mean
|
||||||
|
|
||||||
@@ -177,6 +178,15 @@ class Check(BaseAuditModel):
|
|||||||
if self.check_type == "cpuload" or self.check_type == "memory":
|
if self.check_type == "cpuload" or self.check_type == "memory":
|
||||||
return ", ".join(str(f"{x}%") for x in self.history[-6:])
|
return ", ".join(str(f"{x}%") for x in self.history[-6:])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_run_as_timezone(self):
|
||||||
|
if self.last_run is not None and self.agent is not None:
|
||||||
|
return self.last_run.astimezone(
|
||||||
|
pytz.timezone(self.agent.timezone)
|
||||||
|
).strftime("%b-%d-%Y - %H:%M")
|
||||||
|
|
||||||
|
return self.last_run
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def non_editable_fields(self):
|
def non_editable_fields(self):
|
||||||
return [
|
return [
|
||||||
@@ -199,6 +209,10 @@ class Check(BaseAuditModel):
|
|||||||
"parent_check",
|
"parent_check",
|
||||||
"managed_by_policy",
|
"managed_by_policy",
|
||||||
"overriden_by_policy",
|
"overriden_by_policy",
|
||||||
|
"created_by",
|
||||||
|
"created_time",
|
||||||
|
"modified_by",
|
||||||
|
"modified_time",
|
||||||
]
|
]
|
||||||
|
|
||||||
def handle_checkv2(self, data):
|
def handle_checkv2(self, data):
|
||||||
@@ -518,7 +532,7 @@ class Check(BaseAuditModel):
|
|||||||
CORE = CoreSettings.objects.first()
|
CORE = CoreSettings.objects.first()
|
||||||
|
|
||||||
if self.agent:
|
if self.agent:
|
||||||
subject = f"{self.agent.client}, {self.agent.site}, {self} Failed"
|
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Failed"
|
||||||
else:
|
else:
|
||||||
subject = f"{self} Failed"
|
subject = f"{self} Failed"
|
||||||
|
|
||||||
@@ -594,7 +608,7 @@ class Check(BaseAuditModel):
|
|||||||
CORE = CoreSettings.objects.first()
|
CORE = CoreSettings.objects.first()
|
||||||
|
|
||||||
if self.agent:
|
if self.agent:
|
||||||
subject = f"{self.agent.client}, {self.agent.site}, {self} Failed"
|
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Failed"
|
||||||
else:
|
else:
|
||||||
subject = f"{self} Failed"
|
subject = f"{self} Failed"
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class CheckSerializer(serializers.ModelSerializer):
|
|||||||
readable_desc = serializers.ReadOnlyField()
|
readable_desc = serializers.ReadOnlyField()
|
||||||
script = ScriptSerializer(read_only=True)
|
script = ScriptSerializer(read_only=True)
|
||||||
assigned_task = serializers.SerializerMethodField()
|
assigned_task = serializers.SerializerMethodField()
|
||||||
|
last_run = serializers.ReadOnlyField(source="last_run_as_timezone")
|
||||||
history_info = serializers.ReadOnlyField()
|
history_info = serializers.ReadOnlyField()
|
||||||
|
|
||||||
## Change to return only array of tasks after 9/25/2020
|
## Change to return only array of tasks after 9/25/2020
|
||||||
@@ -47,7 +48,6 @@ class CheckSerializer(serializers.ModelSerializer):
|
|||||||
.filter(check_type="diskspace")
|
.filter(check_type="diskspace")
|
||||||
.exclude(managed_by_policy=True)
|
.exclude(managed_by_policy=True)
|
||||||
)
|
)
|
||||||
if checks:
|
|
||||||
for check in checks:
|
for check in checks:
|
||||||
if val["disk"] in check.disk:
|
if val["disk"] in check.disk:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
|
|||||||
@@ -1,26 +1,38 @@
|
|||||||
from tacticalrmm.test import BaseTestCase
|
from tacticalrmm.test import TacticalTestCase
|
||||||
from .serializers import CheckSerializer
|
from .serializers import CheckSerializer
|
||||||
|
|
||||||
|
from model_bakery import baker
|
||||||
|
from itertools import cycle
|
||||||
|
|
||||||
|
|
||||||
|
class TestCheckViews(TacticalTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.authenticate()
|
||||||
|
|
||||||
class TestCheckViews(BaseTestCase):
|
|
||||||
def test_get_disk_check(self):
|
def test_get_disk_check(self):
|
||||||
url = f"/checks/{self.agentDiskCheck.pk}/check/"
|
# setup data
|
||||||
|
disk_check = baker.make_recipe("checks.diskspace_check")
|
||||||
|
|
||||||
|
url = f"/checks/{disk_check.pk}/check/"
|
||||||
|
|
||||||
resp = self.client.get(url, format="json")
|
resp = self.client.get(url, format="json")
|
||||||
serializer = CheckSerializer(self.agentDiskCheck)
|
serializer = CheckSerializer(disk_check)
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
self.assertEqual(resp.data, serializer.data)
|
self.assertEqual(resp.data, serializer.data)
|
||||||
self.check_not_authenticated("post", url)
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
def test_add_disk_check(self):
|
def test_add_disk_check(self):
|
||||||
|
# setup data
|
||||||
|
agent = baker.make_recipe("agents.agent")
|
||||||
|
|
||||||
url = "/checks/checks/"
|
url = "/checks/checks/"
|
||||||
|
|
||||||
valid_payload = {
|
valid_payload = {
|
||||||
"pk": self.agent.pk,
|
"pk": agent.pk,
|
||||||
"check": {
|
"check": {
|
||||||
"check_type": "diskspace",
|
"check_type": "diskspace",
|
||||||
"disk": "D:",
|
"disk": "C:",
|
||||||
"threshold": 55,
|
"threshold": 55,
|
||||||
"fails_b4_alert": 3,
|
"fails_b4_alert": 3,
|
||||||
},
|
},
|
||||||
@@ -31,7 +43,7 @@ class TestCheckViews(BaseTestCase):
|
|||||||
|
|
||||||
# this should fail because we already have a check for drive C: in setup
|
# this should fail because we already have a check for drive C: in setup
|
||||||
invalid_payload = {
|
invalid_payload = {
|
||||||
"pk": self.agent.pk,
|
"pk": agent.pk,
|
||||||
"check": {
|
"check": {
|
||||||
"check_type": "diskspace",
|
"check_type": "diskspace",
|
||||||
"disk": "C:",
|
"disk": "C:",
|
||||||
@@ -44,23 +56,30 @@ class TestCheckViews(BaseTestCase):
|
|||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
|
|
||||||
def test_get_policy_disk_check(self):
|
def test_get_policy_disk_check(self):
|
||||||
url = f"/checks/{self.policyDiskCheck.pk}/check/"
|
# setup data
|
||||||
|
policy = baker.make("automation.Policy")
|
||||||
|
disk_check = baker.make_recipe("checks.diskspace_check", policy=policy)
|
||||||
|
|
||||||
|
url = f"/checks/{disk_check.pk}/check/"
|
||||||
|
|
||||||
resp = self.client.get(url, format="json")
|
resp = self.client.get(url, format="json")
|
||||||
serializer = CheckSerializer(self.policyDiskCheck)
|
serializer = CheckSerializer(disk_check)
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
self.assertEqual(resp.data, serializer.data)
|
self.assertEqual(resp.data, serializer.data)
|
||||||
self.check_not_authenticated("post", url)
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
def test_add_policy_disk_check(self):
|
def test_add_policy_disk_check(self):
|
||||||
|
# setup data
|
||||||
|
policy = baker.make("automation.Policy")
|
||||||
|
|
||||||
url = "/checks/checks/"
|
url = "/checks/checks/"
|
||||||
|
|
||||||
valid_payload = {
|
valid_payload = {
|
||||||
"policy": self.policy.pk,
|
"policy": policy.pk,
|
||||||
"check": {
|
"check": {
|
||||||
"check_type": "diskspace",
|
"check_type": "diskspace",
|
||||||
"disk": "D:",
|
"disk": "M:",
|
||||||
"threshold": 86,
|
"threshold": 86,
|
||||||
"fails_b4_alert": 2,
|
"fails_b4_alert": 2,
|
||||||
},
|
},
|
||||||
@@ -71,7 +90,7 @@ class TestCheckViews(BaseTestCase):
|
|||||||
|
|
||||||
# this should fail because we already have a check for drive M: in setup
|
# this should fail because we already have a check for drive M: in setup
|
||||||
invalid_payload = {
|
invalid_payload = {
|
||||||
"policy": self.policy.pk,
|
"policy": policy.pk,
|
||||||
"check": {
|
"check": {
|
||||||
"check_type": "diskspace",
|
"check_type": "diskspace",
|
||||||
"disk": "M:",
|
"disk": "M:",
|
||||||
@@ -90,8 +109,14 @@ class TestCheckViews(BaseTestCase):
|
|||||||
self.assertEqual(26, len(r.data))
|
self.assertEqual(26, len(r.data))
|
||||||
|
|
||||||
def test_edit_check_alert(self):
|
def test_edit_check_alert(self):
|
||||||
url_a = f"/checks/{self.agentDiskCheck.pk}/check/"
|
# setup data
|
||||||
url_p = f"/checks/{self.policyDiskCheck.pk}/check/"
|
policy = baker.make("automation.Policy")
|
||||||
|
agent = baker.make_recipe("agents.agent")
|
||||||
|
|
||||||
|
policy_disk_check = baker.make_recipe("checks.diskspace_check", policy=policy)
|
||||||
|
agent_disk_check = baker.make_recipe("checks.diskspace_check", agent=agent)
|
||||||
|
url_a = f"/checks/{agent_disk_check.pk}/check/"
|
||||||
|
url_p = f"/checks/{policy_disk_check.pk}/check/"
|
||||||
|
|
||||||
valid_payload = {"email_alert": False, "check_alert": True}
|
valid_payload = {"email_alert": False, "check_alert": True}
|
||||||
invalid_payload = {"email_alert": False}
|
invalid_payload = {"email_alert": False}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from django.urls import path
|
|||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("checks/", views.GetAddCheck.as_view()),
|
path("checks/", views.AddCheck.as_view()),
|
||||||
path("<int:pk>/check/", views.GetUpdateDeleteCheck.as_view()),
|
path("<int:pk>/check/", views.GetUpdateDeleteCheck.as_view()),
|
||||||
path("<pk>/loadchecks/", views.load_checks),
|
path("<pk>/loadchecks/", views.load_checks),
|
||||||
path("getalldisks/", views.get_disks_for_policies),
|
path("getalldisks/", views.get_disks_for_policies),
|
||||||
|
|||||||
@@ -22,11 +22,7 @@ from automation.tasks import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GetAddCheck(APIView):
|
class AddCheck(APIView):
|
||||||
def get(self, request):
|
|
||||||
checks = Check.objects.all()
|
|
||||||
return Response(CheckSerializer(checks, many=True).data)
|
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
policy = None
|
policy = None
|
||||||
agent = None
|
agent = None
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-02 19:20
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('clients', '0006_deployment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='client',
|
||||||
|
old_name='client',
|
||||||
|
new_name='name',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='site',
|
||||||
|
old_name='site',
|
||||||
|
new_name='name',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-03 14:30
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('clients', '0007_auto_20201102_1920'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='client',
|
||||||
|
options={'ordering': ('name',)},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='site',
|
||||||
|
options={'ordering': ('name',)},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -7,7 +7,7 @@ from logs.models import BaseAuditModel
|
|||||||
|
|
||||||
|
|
||||||
class Client(BaseAuditModel):
|
class Client(BaseAuditModel):
|
||||||
client = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
workstation_policy = models.ForeignKey(
|
workstation_policy = models.ForeignKey(
|
||||||
"automation.Policy",
|
"automation.Policy",
|
||||||
related_name="workstation_clients",
|
related_name="workstation_clients",
|
||||||
@@ -24,13 +24,16 @@ class Client(BaseAuditModel):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ("name",)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.client
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_maintenanace_mode_agents(self):
|
def has_maintenanace_mode_agents(self):
|
||||||
return (
|
return (
|
||||||
Agent.objects.filter(client=self.client, maintenance_mode=True).count() > 0
|
Agent.objects.filter(site__client=self, maintenance_mode=True).count() > 0
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -44,7 +47,7 @@ class Client(BaseAuditModel):
|
|||||||
"last_seen",
|
"last_seen",
|
||||||
"overdue_time",
|
"overdue_time",
|
||||||
)
|
)
|
||||||
.filter(client=self.client)
|
.filter(site__client=self)
|
||||||
.prefetch_related("agentchecks")
|
.prefetch_related("agentchecks")
|
||||||
)
|
)
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
@@ -67,7 +70,7 @@ class Client(BaseAuditModel):
|
|||||||
|
|
||||||
class Site(BaseAuditModel):
|
class Site(BaseAuditModel):
|
||||||
client = models.ForeignKey(Client, related_name="sites", on_delete=models.CASCADE)
|
client = models.ForeignKey(Client, related_name="sites", on_delete=models.CASCADE)
|
||||||
site = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
workstation_policy = models.ForeignKey(
|
workstation_policy = models.ForeignKey(
|
||||||
"automation.Policy",
|
"automation.Policy",
|
||||||
related_name="workstation_sites",
|
related_name="workstation_sites",
|
||||||
@@ -84,17 +87,15 @@ class Site(BaseAuditModel):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ("name",)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.site
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_maintenanace_mode_agents(self):
|
def has_maintenanace_mode_agents(self):
|
||||||
return (
|
return Agent.objects.filter(site=self, maintenance_mode=True).count() > 0
|
||||||
Agent.objects.filter(
|
|
||||||
client=self.client.client, site=self.site, maintenance_mode=True
|
|
||||||
).count()
|
|
||||||
> 0
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_failing_checks(self):
|
def has_failing_checks(self):
|
||||||
@@ -107,7 +108,7 @@ class Site(BaseAuditModel):
|
|||||||
"last_seen",
|
"last_seen",
|
||||||
"overdue_time",
|
"overdue_time",
|
||||||
)
|
)
|
||||||
.filter(client=self.client.client, site=self.site)
|
.filter(site=self)
|
||||||
.prefetch_related("agentchecks")
|
.prefetch_related("agentchecks")
|
||||||
)
|
)
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
@@ -128,13 +129,6 @@ class Site(BaseAuditModel):
|
|||||||
return SiteSerializer(site).data
|
return SiteSerializer(site).data
|
||||||
|
|
||||||
|
|
||||||
def validate_name(name):
|
|
||||||
if "|" in name:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
MON_TYPE_CHOICES = [
|
MON_TYPE_CHOICES = [
|
||||||
("server", "Server"),
|
("server", "Server"),
|
||||||
("workstation", "Workstation"),
|
("workstation", "Workstation"),
|
||||||
|
|||||||
@@ -3,19 +3,25 @@ from .models import Client, Site, Deployment
|
|||||||
|
|
||||||
|
|
||||||
class SiteSerializer(ModelSerializer):
|
class SiteSerializer(ModelSerializer):
|
||||||
|
client_name = ReadOnlyField(source="client.name")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
def validate(self, val):
|
def validate(self, val):
|
||||||
if "|" in val["site"]:
|
if "|" in val["name"]:
|
||||||
raise ValidationError("Site name cannot contain the | character")
|
raise ValidationError("Site name cannot contain the | character")
|
||||||
|
|
||||||
|
if self.context:
|
||||||
|
client = Client.objects.get(pk=self.context["clientpk"])
|
||||||
|
if Site.objects.filter(client=client, name=val["name"]).exists():
|
||||||
|
raise ValidationError(f"Site {val['name']} already exists")
|
||||||
|
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
class ClientSerializer(ModelSerializer):
|
class ClientSerializer(ModelSerializer):
|
||||||
|
|
||||||
sites = SiteSerializer(many=True, read_only=True)
|
sites = SiteSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -30,29 +36,38 @@ class ClientSerializer(ModelSerializer):
|
|||||||
if len(self.context["site"]) > 255:
|
if len(self.context["site"]) > 255:
|
||||||
raise ValidationError("Site name too long")
|
raise ValidationError("Site name too long")
|
||||||
|
|
||||||
if "|" in val["client"]:
|
if "|" in val["name"]:
|
||||||
raise ValidationError("Client name cannot contain the | character")
|
raise ValidationError("Client name cannot contain the | character")
|
||||||
|
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
class TreeSerializer(ModelSerializer):
|
class SiteTreeSerializer(ModelSerializer):
|
||||||
client_name = ReadOnlyField(source="client.client")
|
maintenance_mode = ReadOnlyField(source="has_maintenanace_mode_agents")
|
||||||
|
failing_checks = ReadOnlyField(source="has_failing_checks")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = (
|
fields = "__all__"
|
||||||
"id",
|
ordering = ("failing_checks",)
|
||||||
"site",
|
|
||||||
"client_name",
|
|
||||||
)
|
class ClientTreeSerializer(ModelSerializer):
|
||||||
|
sites = SiteTreeSerializer(many=True, read_only=True)
|
||||||
|
maintenance_mode = ReadOnlyField(source="has_maintenanace_mode_agents")
|
||||||
|
failing_checks = ReadOnlyField(source="has_failing_checks")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Client
|
||||||
|
fields = "__all__"
|
||||||
|
ordering = ("failing_checks",)
|
||||||
|
|
||||||
|
|
||||||
class DeploymentSerializer(ModelSerializer):
|
class DeploymentSerializer(ModelSerializer):
|
||||||
client_id = ReadOnlyField(source="client.id")
|
client_id = ReadOnlyField(source="client.id")
|
||||||
site_id = ReadOnlyField(source="site.id")
|
site_id = ReadOnlyField(source="site.id")
|
||||||
client_name = ReadOnlyField(source="client.client")
|
client_name = ReadOnlyField(source="client.name")
|
||||||
site_name = ReadOnlyField(source="site.site")
|
site_name = ReadOnlyField(source="site.name")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Deployment
|
model = Deployment
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from unittest import mock
|
from tacticalrmm.test import TacticalTestCase
|
||||||
|
from model_bakery import baker
|
||||||
from tacticalrmm.test import BaseTestCase
|
|
||||||
|
|
||||||
from .models import Client, Site, Deployment
|
from .models import Client, Site, Deployment
|
||||||
|
from rest_framework.serializers import ValidationError
|
||||||
|
|
||||||
|
from .serializers import (
|
||||||
|
ClientSerializer,
|
||||||
|
SiteSerializer,
|
||||||
|
ClientTreeSerializer,
|
||||||
|
DeploymentSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestClientViews(BaseTestCase):
|
class TestClientViews(TacticalTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.authenticate()
|
||||||
|
self.setup_coresettings()
|
||||||
|
|
||||||
def test_get_clients(self):
|
def test_get_clients(self):
|
||||||
|
# setup data
|
||||||
|
baker.make("clients.Client", _quantity=5)
|
||||||
|
clients = Client.objects.all()
|
||||||
|
|
||||||
url = "/clients/clients/"
|
url = "/clients/clients/"
|
||||||
r = self.client.get(url)
|
r = self.client.get(url, format="json")
|
||||||
|
serializer = ClientSerializer(clients, many=True)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.data, serializer.data)
|
||||||
|
|
||||||
self.check_not_authenticated("get", url)
|
self.check_not_authenticated("get", url)
|
||||||
|
|
||||||
@@ -21,15 +37,42 @@ class TestClientViews(BaseTestCase):
|
|||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
payload["client"] = "Company1|askd"
|
payload["client"] = "Company1|askd"
|
||||||
|
serializer = ClientSerializer(data={"name": payload["client"]}, context=payload)
|
||||||
|
with self.assertRaisesMessage(
|
||||||
|
ValidationError, "Client name cannot contain the | character"
|
||||||
|
):
|
||||||
|
self.assertFalse(serializer.is_valid(raise_exception=True))
|
||||||
|
|
||||||
r = self.client.post(url, payload, format="json")
|
r = self.client.post(url, payload, format="json")
|
||||||
self.assertEqual(r.status_code, 400)
|
self.assertEqual(r.status_code, 400)
|
||||||
|
|
||||||
payload = {"client": "Company 1", "site": "Site2|a34"}
|
payload = {"client": "Company 156", "site": "Site2|a34"}
|
||||||
|
serializer = ClientSerializer(data={"name": payload["client"]}, context=payload)
|
||||||
|
with self.assertRaisesMessage(
|
||||||
|
ValidationError, "Site name cannot contain the | character"
|
||||||
|
):
|
||||||
|
self.assertFalse(serializer.is_valid(raise_exception=True))
|
||||||
|
|
||||||
r = self.client.post(url, payload, format="json")
|
r = self.client.post(url, payload, format="json")
|
||||||
self.assertEqual(r.status_code, 400)
|
self.assertEqual(r.status_code, 400)
|
||||||
|
|
||||||
# test unique
|
# test unique
|
||||||
payload = {"client": "Company 1", "site": "Site 1"}
|
payload = {"client": "Company 1", "site": "Site 1"}
|
||||||
|
serializer = ClientSerializer(data={"name": payload["client"]}, context=payload)
|
||||||
|
with self.assertRaisesMessage(
|
||||||
|
ValidationError, "client with this name already exists."
|
||||||
|
):
|
||||||
|
self.assertFalse(serializer.is_valid(raise_exception=True))
|
||||||
|
|
||||||
|
r = self.client.post(url, payload, format="json")
|
||||||
|
self.assertEqual(r.status_code, 400)
|
||||||
|
|
||||||
|
# test long site name
|
||||||
|
payload = {"client": "Company 2394", "site": "Site123" * 100}
|
||||||
|
serializer = ClientSerializer(data={"name": payload["client"]}, context=payload)
|
||||||
|
with self.assertRaisesMessage(ValidationError, "Site name too long"):
|
||||||
|
self.assertFalse(serializer.is_valid(raise_exception=True))
|
||||||
|
|
||||||
r = self.client.post(url, payload, format="json")
|
r = self.client.post(url, payload, format="json")
|
||||||
self.assertEqual(r.status_code, 400)
|
self.assertEqual(r.status_code, 400)
|
||||||
|
|
||||||
@@ -41,88 +84,177 @@ class TestClientViews(BaseTestCase):
|
|||||||
r = self.client.post(url, payload, format="json")
|
r = self.client.post(url, payload, format="json")
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
def test_get_sites(self):
|
self.check_not_authenticated("post", url)
|
||||||
url = "/clients/sites/"
|
|
||||||
r = self.client.get(url)
|
def test_edit_client(self):
|
||||||
|
# setup data
|
||||||
|
client = baker.make("clients.Client")
|
||||||
|
|
||||||
|
# test invalid id
|
||||||
|
r = self.client.put("/clients/500/client/", format="json")
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
data = {"id": client.id, "name": "New Name"}
|
||||||
|
|
||||||
|
url = f"/clients/{client.id}/client/"
|
||||||
|
r = self.client.put(url, data, format="json")
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTrue(Client.objects.filter(name="New Name").exists())
|
||||||
|
|
||||||
|
self.check_not_authenticated("put", url)
|
||||||
|
|
||||||
|
def test_delete_client(self):
|
||||||
|
# setup data
|
||||||
|
client = baker.make("clients.Client")
|
||||||
|
site = baker.make("clients.Site", client=client)
|
||||||
|
agent = baker.make_recipe("agents.agent", site=site)
|
||||||
|
|
||||||
|
# test invalid id
|
||||||
|
r = self.client.delete("/clients/500/client/", format="json")
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
url = f"/clients/{client.id}/client/"
|
||||||
|
|
||||||
|
# test deleting with agents under client
|
||||||
|
r = self.client.delete(url, format="json")
|
||||||
|
self.assertEqual(r.status_code, 400)
|
||||||
|
|
||||||
|
# test successful deletion
|
||||||
|
agent.delete()
|
||||||
|
r = self.client.delete(url, format="json")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertFalse(Client.objects.filter(pk=client.id).exists())
|
||||||
|
self.assertFalse(Site.objects.filter(pk=site.id).exists())
|
||||||
|
|
||||||
|
self.check_not_authenticated("put", url)
|
||||||
|
|
||||||
|
def test_get_sites(self):
|
||||||
|
# setup data
|
||||||
|
baker.make("clients.Site", _quantity=5)
|
||||||
|
sites = Site.objects.all()
|
||||||
|
|
||||||
|
url = "/clients/sites/"
|
||||||
|
r = self.client.get(url, format="json")
|
||||||
|
serializer = SiteSerializer(sites, many=True)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.data, serializer.data)
|
||||||
|
|
||||||
self.check_not_authenticated("get", url)
|
self.check_not_authenticated("get", url)
|
||||||
|
|
||||||
def test_add_site(self):
|
def test_add_site(self):
|
||||||
url = "/clients/addsite/"
|
# setup data
|
||||||
|
site = baker.make("clients.Site")
|
||||||
|
|
||||||
payload = {"client": "Google", "site": "LA Office"}
|
url = "/clients/sites/"
|
||||||
r = self.client.post(url, payload, format="json")
|
|
||||||
self.assertEqual(r.status_code, 400)
|
|
||||||
|
|
||||||
payload = {"client": "Google", "site": "LA Off|ice |*&@#$"}
|
# test success add
|
||||||
r = self.client.post(url, payload, format="json")
|
payload = {"client": site.client.id, "name": "LA Office"}
|
||||||
self.assertEqual(r.status_code, 400)
|
|
||||||
|
|
||||||
payload = {"client": "Google", "site": "KN Office"}
|
|
||||||
r = self.client.post(url, payload, format="json")
|
r = self.client.post(url, payload, format="json")
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTrue(
|
||||||
|
Site.objects.filter(
|
||||||
|
name="LA Office", client__name=site.client.name
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
# test with | symbol
|
||||||
|
payload = {"client": site.client.id, "name": "LA Off|ice |*&@#$"}
|
||||||
|
serializer = SiteSerializer(data=payload, context={"clientpk": site.client.id})
|
||||||
|
with self.assertRaisesMessage(
|
||||||
|
ValidationError, "Site name cannot contain the | character"
|
||||||
|
):
|
||||||
|
self.assertFalse(serializer.is_valid(raise_exception=True))
|
||||||
|
|
||||||
|
r = self.client.post(url, payload, format="json")
|
||||||
|
self.assertEqual(r.status_code, 400)
|
||||||
|
|
||||||
|
# test site already exists
|
||||||
|
payload = {"client": site.client.id, "name": "LA Office"}
|
||||||
|
serializer = SiteSerializer(data=payload, context={"clientpk": site.client.id})
|
||||||
|
with self.assertRaisesMessage(ValidationError, "Site LA Office already exists"):
|
||||||
|
self.assertFalse(serializer.is_valid(raise_exception=True))
|
||||||
|
|
||||||
self.check_not_authenticated("post", url)
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
def test_list_clients(self):
|
def test_edit_site(self):
|
||||||
url = "/clients/listclients/"
|
# setup data
|
||||||
|
site = baker.make("clients.Site")
|
||||||
|
|
||||||
r = self.client.get(url)
|
# test invalid id
|
||||||
|
r = self.client.put("/clients/500/site/", format="json")
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
data = {"id": site.id, "name": "New Name", "client": site.client.id}
|
||||||
|
|
||||||
|
url = f"/clients/{site.id}/site/"
|
||||||
|
r = self.client.put(url, data, format="json")
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTrue(Site.objects.filter(name="New Name").exists())
|
||||||
|
|
||||||
self.check_not_authenticated("get", url)
|
self.check_not_authenticated("put", url)
|
||||||
|
|
||||||
def test_load_tree(self):
|
def test_delete_site(self):
|
||||||
|
# setup data
|
||||||
|
site = baker.make("clients.Site")
|
||||||
|
agent = baker.make_recipe("agents.agent", site=site)
|
||||||
|
|
||||||
with mock.patch(
|
# test invalid id
|
||||||
"clients.models.Client.has_failing_checks",
|
r = self.client.delete("/clients/500/site/", format="json")
|
||||||
new_callable=mock.PropertyMock,
|
self.assertEqual(r.status_code, 404)
|
||||||
return_value=True,
|
|
||||||
):
|
|
||||||
|
|
||||||
url = "/clients/loadtree/"
|
url = f"/clients/{site.id}/site/"
|
||||||
|
|
||||||
r = self.client.get(url)
|
# test deleting with last site under client
|
||||||
|
r = self.client.delete(url, format="json")
|
||||||
|
self.assertEqual(r.status_code, 400)
|
||||||
|
|
||||||
|
# test deletion when agents exist under site
|
||||||
|
baker.make("clients.Site", client=site.client)
|
||||||
|
r = self.client.delete(url, format="json")
|
||||||
|
self.assertEqual(r.status_code, 400)
|
||||||
|
|
||||||
|
# test successful deletion
|
||||||
|
agent.delete()
|
||||||
|
r = self.client.delete(url, format="json")
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertFalse(Site.objects.filter(pk=site.id).exists())
|
||||||
|
|
||||||
client = Client.objects.get(client="Facebook")
|
self.check_not_authenticated("delete", url)
|
||||||
self.assertTrue(f"Facebook|{client.pk}|negative" in r.data.keys())
|
|
||||||
|
|
||||||
with mock.patch(
|
def test_get_tree(self):
|
||||||
"clients.models.Site.has_failing_checks",
|
# setup data
|
||||||
new_callable=mock.PropertyMock,
|
baker.make("clients.Site", _quantity=10)
|
||||||
return_value=False,
|
clients = Client.objects.all()
|
||||||
):
|
|
||||||
|
|
||||||
client = Client.objects.get(client="Google")
|
url = "/clients/tree/"
|
||||||
site = Site.objects.get(client=client, site="LA Office")
|
|
||||||
self.assertTrue(
|
|
||||||
f"LA Office|{site.pk}|black" in [i for i in r.data.values()][0]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.check_not_authenticated("get", url)
|
r = self.client.get(url, format="json")
|
||||||
|
serializer = ClientTreeSerializer(clients, many=True)
|
||||||
def test_load_clients(self):
|
|
||||||
url = "/clients/loadclients/"
|
|
||||||
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.data, serializer.data)
|
||||||
|
|
||||||
self.check_not_authenticated("get", url)
|
self.check_not_authenticated("get", url)
|
||||||
|
|
||||||
def test_get_deployments(self):
|
def test_get_deployments(self):
|
||||||
|
# setup data
|
||||||
|
deployments = baker.make("clients.Deployment", _quantity=5)
|
||||||
|
|
||||||
url = "/clients/deployments/"
|
url = "/clients/deployments/"
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
|
serializer = DeploymentSerializer(deployments, many=True)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.data, serializer.data)
|
||||||
|
|
||||||
self.check_not_authenticated("get", url)
|
self.check_not_authenticated("get", url)
|
||||||
|
|
||||||
def test_add_deployment(self):
|
def test_add_deployment(self):
|
||||||
|
# setup data
|
||||||
|
site = baker.make("clients.Site")
|
||||||
|
|
||||||
url = "/clients/deployments/"
|
url = "/clients/deployments/"
|
||||||
payload = {
|
payload = {
|
||||||
"client": "Google",
|
"client": site.client.id,
|
||||||
"site": "Main Office",
|
"site": site.id,
|
||||||
"expires": "2037-11-23 18:53",
|
"expires": "2037-11-23 18:53",
|
||||||
"power": 1,
|
"power": 1,
|
||||||
"ping": 0,
|
"ping": 0,
|
||||||
@@ -134,36 +266,26 @@ class TestClientViews(BaseTestCase):
|
|||||||
r = self.client.post(url, payload, format="json")
|
r = self.client.post(url, payload, format="json")
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
payload["site"] = "ASDkjh23k4jh"
|
payload["site"] = "500"
|
||||||
r = self.client.post(url, payload, format="json")
|
r = self.client.post(url, payload, format="json")
|
||||||
self.assertEqual(r.status_code, 404)
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
payload["client"] = "324234ASDqwe"
|
payload["client"] = "500"
|
||||||
r = self.client.post(url, payload, format="json")
|
r = self.client.post(url, payload, format="json")
|
||||||
self.assertEqual(r.status_code, 404)
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
self.check_not_authenticated("post", url)
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
def test_delete_deployment(self):
|
def test_delete_deployment(self):
|
||||||
|
# setup data
|
||||||
|
deployment = baker.make("clients.Deployment")
|
||||||
|
|
||||||
url = "/clients/deployments/"
|
url = "/clients/deployments/"
|
||||||
payload = {
|
|
||||||
"client": "Google",
|
|
||||||
"site": "Main Office",
|
|
||||||
"expires": "2037-11-23 18:53",
|
|
||||||
"power": 1,
|
|
||||||
"ping": 0,
|
|
||||||
"rdp": 1,
|
|
||||||
"agenttype": "server",
|
|
||||||
"arch": "64",
|
|
||||||
}
|
|
||||||
|
|
||||||
r = self.client.post(url, payload, format="json")
|
url = f"/clients/{deployment.id}/deployment/"
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
|
|
||||||
dep = Deployment.objects.last()
|
|
||||||
url = f"/clients/{dep.pk}/deployment/"
|
|
||||||
r = self.client.delete(url)
|
r = self.client.delete(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertFalse(Deployment.objects.filter(pk=deployment.id).exists())
|
||||||
|
|
||||||
url = "/clients/32348/deployment/"
|
url = "/clients/32348/deployment/"
|
||||||
r = self.client.delete(url)
|
r = self.client.delete(url)
|
||||||
|
|||||||
@@ -4,14 +4,9 @@ from . import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("clients/", views.GetAddClients.as_view()),
|
path("clients/", views.GetAddClients.as_view()),
|
||||||
path("<int:pk>/client/", views.GetUpdateDeleteClient.as_view()),
|
path("<int:pk>/client/", views.GetUpdateDeleteClient.as_view()),
|
||||||
|
path("tree/", views.GetClientTree.as_view()),
|
||||||
path("sites/", views.GetAddSites.as_view()),
|
path("sites/", views.GetAddSites.as_view()),
|
||||||
path("listclients/", views.list_clients),
|
path("<int:pk>/site/", views.GetUpdateDeleteSite.as_view()),
|
||||||
path("listsites/", views.list_sites),
|
|
||||||
path("addsite/", views.add_site),
|
|
||||||
path("editsite/", views.edit_site),
|
|
||||||
path("deletesite/", views.delete_site),
|
|
||||||
path("loadtree/", views.load_tree),
|
|
||||||
path("loadclients/", views.load_clients),
|
|
||||||
path("deployments/", views.AgentDeployment.as_view()),
|
path("deployments/", views.AgentDeployment.as_view()),
|
||||||
path("<int:pk>/deployment/", views.AgentDeployment.as_view()),
|
path("<int:pk>/deployment/", views.AgentDeployment.as_view()),
|
||||||
path("<str:uid>/deploy/", views.GenerateAgent.as_view()),
|
path("<str:uid>/deploy/", views.GenerateAgent.as_view()),
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ from rest_framework.decorators import api_view
|
|||||||
from .serializers import (
|
from .serializers import (
|
||||||
ClientSerializer,
|
ClientSerializer,
|
||||||
SiteSerializer,
|
SiteSerializer,
|
||||||
TreeSerializer,
|
ClientTreeSerializer,
|
||||||
DeploymentSerializer,
|
DeploymentSerializer,
|
||||||
)
|
)
|
||||||
from .models import Client, Site, Deployment, validate_name
|
from .models import Client, Site, Deployment
|
||||||
from agents.models import Agent
|
from agents.models import Agent
|
||||||
from core.models import CoreSettings
|
from core.models import CoreSettings
|
||||||
from tacticalrmm.utils import notify_error
|
from tacticalrmm.utils import notify_error
|
||||||
@@ -39,51 +39,50 @@ class GetAddClients(APIView):
|
|||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
|
||||||
if "initialsetup" in request.data:
|
if "initialsetup" in request.data:
|
||||||
client = {"client": request.data["client"]["client"].strip()}
|
client = {"name": request.data["client"]["client"].strip()}
|
||||||
site = {"site": request.data["client"]["site"].strip()}
|
site = {"name": request.data["client"]["site"].strip()}
|
||||||
serializer = ClientSerializer(data=client, context=request.data["client"])
|
serializer = ClientSerializer(data=client, context=request.data["client"])
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
core = CoreSettings.objects.first()
|
core = CoreSettings.objects.first()
|
||||||
core.default_time_zone = request.data["timezone"]
|
core.default_time_zone = request.data["timezone"]
|
||||||
core.save(update_fields=["default_time_zone"])
|
core.save(update_fields=["default_time_zone"])
|
||||||
else:
|
else:
|
||||||
client = {"client": request.data["client"].strip()}
|
client = {"name": request.data["client"].strip()}
|
||||||
site = {"site": request.data["site"].strip()}
|
site = {"name": request.data["site"].strip()}
|
||||||
serializer = ClientSerializer(data=client, context=request.data)
|
serializer = ClientSerializer(data=client, context=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
obj = serializer.save()
|
obj = serializer.save()
|
||||||
Site(client=obj, site=site["site"]).save()
|
Site(client=obj, name=site["name"]).save()
|
||||||
|
|
||||||
return Response(f"{obj} was added!")
|
return Response(f"{obj} was added!")
|
||||||
|
|
||||||
|
|
||||||
class GetUpdateDeleteClient(APIView):
|
class GetUpdateDeleteClient(APIView):
|
||||||
def patch(self, request, pk):
|
def put(self, request, pk):
|
||||||
client = get_object_or_404(Client, pk=pk)
|
client = get_object_or_404(Client, pk=pk)
|
||||||
orig = client.client
|
|
||||||
|
|
||||||
serializer = ClientSerializer(data=request.data, instance=client)
|
serializer = ClientSerializer(data=request.data, instance=client)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
obj = serializer.save()
|
serializer.save()
|
||||||
|
|
||||||
agents = Agent.objects.filter(client=orig)
|
return Response("The Client was renamed")
|
||||||
for agent in agents:
|
|
||||||
agent.client = obj.client
|
|
||||||
agent.save(update_fields=["client"])
|
|
||||||
|
|
||||||
return Response(f"{orig} renamed to {obj}")
|
|
||||||
|
|
||||||
def delete(self, request, pk):
|
def delete(self, request, pk):
|
||||||
client = get_object_or_404(Client, pk=pk)
|
client = get_object_or_404(Client, pk=pk)
|
||||||
agents = Agent.objects.filter(client=client.client)
|
agent_count = Agent.objects.filter(site__client=client).count()
|
||||||
if agents.exists():
|
if agent_count > 0:
|
||||||
return notify_error(
|
return notify_error(
|
||||||
f"Cannot delete {client} while {agents.count()} agents exist in it. Move the agents to another client first."
|
f"Cannot delete {client} while {agent_count} agents exist in it. Move the agents to another client first."
|
||||||
)
|
)
|
||||||
|
|
||||||
client.delete()
|
client.delete()
|
||||||
return Response(f"{client.client} was deleted!")
|
return Response(f"{client.name} was deleted!")
|
||||||
|
|
||||||
|
|
||||||
|
class GetClientTree(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
clients = Client.objects.all()
|
||||||
|
return Response(ClientTreeSerializer(clients, many=True).data)
|
||||||
|
|
||||||
|
|
||||||
class GetAddSites(APIView):
|
class GetAddSites(APIView):
|
||||||
@@ -91,126 +90,42 @@ class GetAddSites(APIView):
|
|||||||
sites = Site.objects.all()
|
sites = Site.objects.all()
|
||||||
return Response(SiteSerializer(sites, many=True).data)
|
return Response(SiteSerializer(sites, many=True).data)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
@api_view(["POST"])
|
name = request.data["name"].strip()
|
||||||
def add_site(request):
|
serializer = SiteSerializer(
|
||||||
client = Client.objects.get(client=request.data["client"].strip())
|
data={"name": name, "client": request.data["client"]},
|
||||||
site = request.data["site"].strip()
|
context={"clientpk": request.data["client"]},
|
||||||
|
)
|
||||||
if not validate_name(site):
|
serializer.is_valid(raise_exception=True)
|
||||||
content = {"error": "Site name cannot contain the | character"}
|
serializer.save()
|
||||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
if Site.objects.filter(client=client).filter(site=site):
|
|
||||||
content = {"error": f"Site {site} already exists"}
|
|
||||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
try:
|
|
||||||
Site(client=client, site=site).save()
|
|
||||||
except DataError:
|
|
||||||
content = {"error": "Site name too long (max 255 chars)"}
|
|
||||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
else:
|
|
||||||
return Response("ok")
|
|
||||||
|
|
||||||
|
|
||||||
@api_view(["PATCH"])
|
|
||||||
def edit_site(request):
|
|
||||||
new_name = request.data["name"].strip()
|
|
||||||
|
|
||||||
if not validate_name(new_name):
|
|
||||||
err = "Site name cannot contain the | character"
|
|
||||||
return Response(err, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
client = get_object_or_404(Client, client=request.data["client"])
|
|
||||||
site = Site.objects.filter(client=client).filter(site=request.data["site"]).get()
|
|
||||||
|
|
||||||
agents = Agent.objects.filter(client=client.client).filter(site=site.site)
|
|
||||||
|
|
||||||
site.site = new_name
|
|
||||||
site.save(update_fields=["site"])
|
|
||||||
|
|
||||||
for agent in agents:
|
|
||||||
agent.site = new_name
|
|
||||||
agent.save(update_fields=["site"])
|
|
||||||
|
|
||||||
return Response("ok")
|
return Response("ok")
|
||||||
|
|
||||||
|
|
||||||
@api_view(["DELETE"])
|
class GetUpdateDeleteSite(APIView):
|
||||||
def delete_site(request):
|
def put(self, request, pk):
|
||||||
client = get_object_or_404(Client, client=request.data["client"])
|
|
||||||
if client.sites.count() == 1:
|
site = get_object_or_404(Site, pk=pk)
|
||||||
|
serializer = SiteSerializer(instance=site, data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
|
||||||
|
return Response("ok")
|
||||||
|
|
||||||
|
def delete(self, request, pk):
|
||||||
|
site = get_object_or_404(Site, pk=pk)
|
||||||
|
if site.client.sites.count() == 1:
|
||||||
return notify_error(f"A client must have at least 1 site.")
|
return notify_error(f"A client must have at least 1 site.")
|
||||||
|
|
||||||
site = Site.objects.filter(client=client).filter(site=request.data["site"]).get()
|
agent_count = Agent.objects.filter(site=site).count()
|
||||||
agents = Agent.objects.filter(client=client.client).filter(site=site.site)
|
|
||||||
|
|
||||||
if agents.exists():
|
if agent_count > 0:
|
||||||
return notify_error(
|
return notify_error(
|
||||||
f"Cannot delete {site} while {agents.count()} agents exist in it. Move the agents to another site first."
|
f"Cannot delete {site.name} while {agent_count} agents exist in it. Move the agents to another site first."
|
||||||
)
|
)
|
||||||
|
|
||||||
site.delete()
|
site.delete()
|
||||||
return Response(f"{site} was deleted!")
|
return Response(f"{site.name} was deleted!")
|
||||||
|
|
||||||
|
|
||||||
@api_view()
|
|
||||||
# for vue
|
|
||||||
def list_clients(request):
|
|
||||||
clients = Client.objects.all()
|
|
||||||
return Response(ClientSerializer(clients, many=True).data)
|
|
||||||
|
|
||||||
|
|
||||||
@api_view()
|
|
||||||
# for vue
|
|
||||||
def list_sites(request):
|
|
||||||
sites = Site.objects.all()
|
|
||||||
return Response(TreeSerializer(sites, many=True).data)
|
|
||||||
|
|
||||||
|
|
||||||
@api_view()
|
|
||||||
def load_tree(request):
|
|
||||||
clients = Client.objects.all()
|
|
||||||
new = {}
|
|
||||||
|
|
||||||
for x in clients:
|
|
||||||
b = []
|
|
||||||
|
|
||||||
sites = Site.objects.filter(client=x)
|
|
||||||
for i in sites:
|
|
||||||
|
|
||||||
if i.has_maintenanace_mode_agents:
|
|
||||||
b.append(f"{i.site}|{i.pk}|warning")
|
|
||||||
elif i.has_failing_checks:
|
|
||||||
b.append(f"{i.site}|{i.pk}|negative")
|
|
||||||
else:
|
|
||||||
b.append(f"{i.site}|{i.pk}|black")
|
|
||||||
|
|
||||||
if x.has_maintenanace_mode_agents:
|
|
||||||
new[f"{x.client}|{x.pk}|warning"] = b
|
|
||||||
elif x.has_failing_checks:
|
|
||||||
new[f"{x.client}|{x.pk}|negative"] = b
|
|
||||||
else:
|
|
||||||
new[f"{x.client}|{x.pk}|black"] = b
|
|
||||||
|
|
||||||
return Response(new)
|
|
||||||
|
|
||||||
|
|
||||||
@api_view()
|
|
||||||
def load_clients(request):
|
|
||||||
clients = Client.objects.all()
|
|
||||||
new = {}
|
|
||||||
|
|
||||||
for x in clients:
|
|
||||||
b = []
|
|
||||||
|
|
||||||
sites = Site.objects.filter(client=x)
|
|
||||||
for i in sites:
|
|
||||||
b.append(i.site)
|
|
||||||
new[x.client] = b
|
|
||||||
|
|
||||||
return Response(new)
|
|
||||||
|
|
||||||
|
|
||||||
class AgentDeployment(APIView):
|
class AgentDeployment(APIView):
|
||||||
@@ -221,8 +136,8 @@ class AgentDeployment(APIView):
|
|||||||
def post(self, request):
|
def post(self, request):
|
||||||
from knox.models import AuthToken
|
from knox.models import AuthToken
|
||||||
|
|
||||||
client = get_object_or_404(Client, client=request.data["client"])
|
client = get_object_or_404(Client, pk=request.data["client"])
|
||||||
site = get_object_or_404(Site, client=client, site=request.data["site"])
|
site = get_object_or_404(Site, pk=request.data["site"])
|
||||||
|
|
||||||
expires = dt.datetime.strptime(
|
expires = dt.datetime.strptime(
|
||||||
request.data["expires"], "%Y-%m-%d %H:%M"
|
request.data["expires"], "%Y-%m-%d %H:%M"
|
||||||
@@ -285,8 +200,8 @@ class GenerateAgent(APIView):
|
|||||||
)
|
)
|
||||||
download_url = settings.DL_64 if d.arch == "64" else settings.DL_32
|
download_url = settings.DL_64 if d.arch == "64" else settings.DL_32
|
||||||
|
|
||||||
client = d.client.client.replace(" ", "").lower()
|
client = d.client.name.replace(" ", "").lower()
|
||||||
site = d.site.site.replace(" ", "").lower()
|
site = d.site.name.replace(" ", "").lower()
|
||||||
client = re.sub(r"([^a-zA-Z0-9]+)", "", client)
|
client = re.sub(r"([^a-zA-Z0-9]+)", "", client)
|
||||||
site = re.sub(r"([^a-zA-Z0-9]+)", "", site)
|
site = re.sub(r"([^a-zA-Z0-9]+)", "", site)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
from tacticalrmm.test import BaseTestCase
|
from tacticalrmm.test import TacticalTestCase
|
||||||
from core.tasks import core_maintenance_tasks
|
from core.tasks import core_maintenance_tasks
|
||||||
|
|
||||||
|
|
||||||
class TestCoreTasks(BaseTestCase):
|
class TestCoreTasks(TacticalTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.setup_coresettings()
|
||||||
|
self.authenticate()
|
||||||
|
|
||||||
def test_core_maintenance_tasks(self):
|
def test_core_maintenance_tasks(self):
|
||||||
task = core_maintenance_tasks.s().apply()
|
task = core_maintenance_tasks.s().apply()
|
||||||
self.assertEqual(task.state, "SUCCESS")
|
self.assertEqual(task.state, "SUCCESS")
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ class PendingActionSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
hostname = serializers.ReadOnlyField(source="agent.hostname")
|
hostname = serializers.ReadOnlyField(source="agent.hostname")
|
||||||
salt_id = serializers.ReadOnlyField(source="agent.salt_id")
|
salt_id = serializers.ReadOnlyField(source="agent.salt_id")
|
||||||
client = serializers.ReadOnlyField(source="agent.client")
|
client = serializers.ReadOnlyField(source="agent.client.name")
|
||||||
site = serializers.ReadOnlyField(source="agent.site")
|
site = serializers.ReadOnlyField(source="agent.site.name")
|
||||||
due = serializers.ReadOnlyField()
|
due = serializers.ReadOnlyField()
|
||||||
description = serializers.ReadOnlyField()
|
description = serializers.ReadOnlyField()
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
amqp==2.6.1
|
amqp==2.6.1
|
||||||
asgiref==3.2.10
|
asgiref==3.3.0
|
||||||
billiard==3.6.3.0
|
billiard==3.6.3.0
|
||||||
celery==4.4.6
|
celery==4.4.6
|
||||||
certifi==2020.6.20
|
certifi==2020.11.8
|
||||||
cffi==1.14.3
|
cffi==1.14.3
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
cryptography==3.2
|
cryptography==3.2.1
|
||||||
decorator==4.4.2
|
decorator==4.4.2
|
||||||
Django==3.1.2
|
Django==3.1.3
|
||||||
django-cors-headers==3.5.0
|
django-cors-headers==3.5.0
|
||||||
django-rest-knox==4.1.0
|
django-rest-knox==4.1.0
|
||||||
djangorestframework==3.12.1
|
djangorestframework==3.12.2
|
||||||
future==0.18.2
|
future==0.18.2
|
||||||
idna==2.10
|
idna==2.10
|
||||||
kombu==4.6.11
|
kombu==4.6.11
|
||||||
@@ -19,19 +19,19 @@ msgpack==1.0.0
|
|||||||
packaging==20.4
|
packaging==20.4
|
||||||
psycopg2-binary==2.8.6
|
psycopg2-binary==2.8.6
|
||||||
pycparser==2.20
|
pycparser==2.20
|
||||||
pycryptodome==3.9.8
|
pycryptodome==3.9.9
|
||||||
pyotp==2.4.1
|
pyotp==2.4.1
|
||||||
pyparsing==2.4.7
|
pyparsing==2.4.7
|
||||||
pytz==2020.1
|
pytz==2020.4
|
||||||
qrcode==6.1
|
qrcode==6.1
|
||||||
redis==3.5.3
|
redis==3.5.3
|
||||||
requests==2.24.0
|
requests==2.24.0
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
sqlparse==0.4.1
|
sqlparse==0.4.1
|
||||||
twilio==6.46.0
|
twilio==6.47.0
|
||||||
urllib3==1.25.10
|
urllib3==1.25.11
|
||||||
uWSGI==2.0.19.1
|
uWSGI==2.0.19.1
|
||||||
validators==0.18.1
|
validators==0.18.1
|
||||||
vine==1.3.0
|
vine==1.3.0
|
||||||
websockets==8.1
|
websockets==8.1
|
||||||
zipp==3.3.1
|
zipp==3.4.0
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe")
|
|||||||
AUTH_USER_MODEL = "accounts.User"
|
AUTH_USER_MODEL = "accounts.User"
|
||||||
|
|
||||||
# latest release
|
# latest release
|
||||||
TRMM_VERSION = "0.1.2"
|
TRMM_VERSION = "0.1.5"
|
||||||
|
|
||||||
# bump this version everytime vue code is changed
|
# bump this version everytime vue code is changed
|
||||||
# to alert user they need to manually refresh their browser
|
# to alert user they need to manually refresh their browser
|
||||||
APP_VER = "0.0.84"
|
APP_VER = "0.0.87"
|
||||||
|
|
||||||
# https://github.com/wh1te909/salt
|
# https://github.com/wh1te909/salt
|
||||||
LATEST_SALT_VER = "1.1.0"
|
LATEST_SALT_VER = "1.1.0"
|
||||||
@@ -25,7 +25,7 @@ LATEST_AGENT_VER = "1.0.1"
|
|||||||
MESH_VER = "0.6.62"
|
MESH_VER = "0.6.62"
|
||||||
|
|
||||||
# for the update script, bump when need to recreate venv or npm install
|
# for the update script, bump when need to recreate venv or npm install
|
||||||
PIP_VER = "1"
|
PIP_VER = "2"
|
||||||
NPM_VER = "1"
|
NPM_VER = "1"
|
||||||
|
|
||||||
DL_64 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}.exe"
|
DL_64 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}.exe"
|
||||||
|
|||||||
@@ -1,24 +1,11 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
|
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from django.utils import timezone as djangotime
|
|
||||||
from django.conf import settings
|
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
|
||||||
from rest_framework.test import APIClient
|
from rest_framework.test import APIClient
|
||||||
from rest_framework.authtoken.models import Token
|
|
||||||
|
|
||||||
from accounts.models import User
|
from accounts.models import User
|
||||||
from agents.models import Agent
|
|
||||||
from winupdate.models import WinUpdatePolicy
|
|
||||||
from clients.models import Client, Site
|
|
||||||
from automation.models import Policy
|
|
||||||
from core.models import CoreSettings
|
from core.models import CoreSettings
|
||||||
from checks.models import Check
|
from rest_framework.authtoken.models import Token
|
||||||
from autotasks.models import AutomatedTask
|
|
||||||
|
|
||||||
|
|
||||||
class TacticalTestCase(TestCase):
|
class TacticalTestCase(TestCase):
|
||||||
@@ -29,6 +16,12 @@ class TacticalTestCase(TestCase):
|
|||||||
self.client_setup()
|
self.client_setup()
|
||||||
self.client.force_authenticate(user=self.john)
|
self.client.force_authenticate(user=self.john)
|
||||||
|
|
||||||
|
def setup_agent_auth(self, agent):
|
||||||
|
agent_user = User.objects.create_user(
|
||||||
|
username=agent.agent_id, password=User.objects.make_random_password(60)
|
||||||
|
)
|
||||||
|
Token.objects.create(user=agent_user)
|
||||||
|
|
||||||
def client_setup(self):
|
def client_setup(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
|
|
||||||
@@ -51,62 +44,6 @@ class TacticalTestCase(TestCase):
|
|||||||
r = switch.get(method)
|
r = switch.get(method)
|
||||||
self.assertEqual(r.status_code, 401)
|
self.assertEqual(r.status_code, 401)
|
||||||
|
|
||||||
def agent_setup(self):
|
|
||||||
self.agent = Agent.objects.create(
|
|
||||||
operating_system="Windows 10",
|
|
||||||
plat="windows",
|
|
||||||
plat_release="windows-Server2019",
|
|
||||||
hostname="DESKTOP-TEST123",
|
|
||||||
salt_id="aksdjaskdjs",
|
|
||||||
local_ip="10.0.25.188",
|
|
||||||
agent_id="71AHC-AA813-HH1BC-AAHH5-00013|DESKTOP-TEST123",
|
|
||||||
services=[
|
|
||||||
{
|
|
||||||
"pid": 880,
|
|
||||||
"name": "AeLookupSvc",
|
|
||||||
"status": "stopped",
|
|
||||||
"binpath": "C:\\Windows\\system32\\svchost.exe -k netsvcs",
|
|
||||||
"username": "localSystem",
|
|
||||||
"start_type": "manual",
|
|
||||||
"description": "Processes application compatibility cache requests for applications as they are launched",
|
|
||||||
"display_name": "Application Experience",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pid": 812,
|
|
||||||
"name": "ALG",
|
|
||||||
"status": "stopped",
|
|
||||||
"binpath": "C:\\Windows\\System32\\alg.exe",
|
|
||||||
"username": "NT AUTHORITY\\LocalService",
|
|
||||||
"start_type": "manual",
|
|
||||||
"description": "Provides support for 3rd party protocol plug-ins for Internet Connection Sharing",
|
|
||||||
"display_name": "Application Layer Gateway Service",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
public_ip="74.13.24.14",
|
|
||||||
total_ram=16,
|
|
||||||
used_ram=33,
|
|
||||||
disks={
|
|
||||||
"C:": {
|
|
||||||
"free": "42.3G",
|
|
||||||
"used": "17.1G",
|
|
||||||
"total": "59.5G",
|
|
||||||
"device": "C:",
|
|
||||||
"fstype": "NTFS",
|
|
||||||
"percent": 28,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
boot_time=8173231.4,
|
|
||||||
logged_in_username="John",
|
|
||||||
client="Google",
|
|
||||||
site="Main Office",
|
|
||||||
monitoring_type="server",
|
|
||||||
description="Test PC",
|
|
||||||
mesh_node_id="abcdefghijklmnopAABBCCDD77443355##!!AI%@#$%#*",
|
|
||||||
last_seen=djangotime.now(),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.update_policy = WinUpdatePolicy.objects.create(agent=self.agent)
|
|
||||||
|
|
||||||
def create_checks(self, policy=None, agent=None, script=None):
|
def create_checks(self, policy=None, agent=None, script=None):
|
||||||
|
|
||||||
if not policy and not agent:
|
if not policy and not agent:
|
||||||
@@ -132,136 +69,3 @@ class TacticalTestCase(TestCase):
|
|||||||
baker.make_recipe(recipe, policy=policy, agent=agent, script=script)
|
baker.make_recipe(recipe, policy=policy, agent=agent, script=script)
|
||||||
)
|
)
|
||||||
return checks
|
return checks
|
||||||
|
|
||||||
|
|
||||||
class BaseTestCase(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
|
|
||||||
self.john = User(username="john")
|
|
||||||
self.john.set_password("password")
|
|
||||||
self.john.save()
|
|
||||||
self.client = APIClient()
|
|
||||||
self.client.force_authenticate(user=self.john)
|
|
||||||
|
|
||||||
self.coresettings = CoreSettings.objects.create()
|
|
||||||
self.agent = self.create_agent("DESKTOP-TEST123", "Google", "Main Office")
|
|
||||||
self.agent_user = User.objects.create_user(
|
|
||||||
username=self.agent.agent_id, password=User.objects.make_random_password(60)
|
|
||||||
)
|
|
||||||
self.agent_token = Token.objects.create(user=self.agent_user)
|
|
||||||
self.update_policy = WinUpdatePolicy.objects.create(agent=self.agent)
|
|
||||||
|
|
||||||
Client.objects.create(client="Google")
|
|
||||||
Client.objects.create(client="Facebook")
|
|
||||||
google = Client.objects.get(client="Google")
|
|
||||||
facebook = Client.objects.get(client="Facebook")
|
|
||||||
Site.objects.create(client=google, site="Main Office")
|
|
||||||
Site.objects.create(client=google, site="LA Office")
|
|
||||||
Site.objects.create(client=google, site="MO Office")
|
|
||||||
Site.objects.create(client=facebook, site="Main Office")
|
|
||||||
Site.objects.create(client=facebook, site="NY Office")
|
|
||||||
|
|
||||||
self.policy = Policy.objects.create(
|
|
||||||
name="testpolicy",
|
|
||||||
desc="my awesome policy",
|
|
||||||
active=True,
|
|
||||||
)
|
|
||||||
self.policy.server_clients.add(google)
|
|
||||||
self.policy.workstation_clients.add(facebook)
|
|
||||||
|
|
||||||
self.agentDiskCheck = Check.objects.create(
|
|
||||||
agent=self.agent,
|
|
||||||
check_type="diskspace",
|
|
||||||
disk="C:",
|
|
||||||
threshold=41,
|
|
||||||
fails_b4_alert=4,
|
|
||||||
)
|
|
||||||
self.policyDiskCheck = Check.objects.create(
|
|
||||||
policy=self.policy,
|
|
||||||
check_type="diskspace",
|
|
||||||
disk="M:",
|
|
||||||
threshold=87,
|
|
||||||
fails_b4_alert=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.policyTask = AutomatedTask.objects.create(
|
|
||||||
policy=self.policy, name="Test Task"
|
|
||||||
)
|
|
||||||
|
|
||||||
def check_not_authenticated(self, method, url):
|
|
||||||
self.client.logout()
|
|
||||||
switch = {
|
|
||||||
"get": self.client.get(url),
|
|
||||||
"post": self.client.post(url),
|
|
||||||
"put": self.client.put(url),
|
|
||||||
"patch": self.client.patch(url),
|
|
||||||
"delete": self.client.delete(url),
|
|
||||||
}
|
|
||||||
r = switch.get(method)
|
|
||||||
self.assertEqual(r.status_code, 401)
|
|
||||||
|
|
||||||
def create_agent(self, hostname, client, site, monitoring_type="server"):
|
|
||||||
with open(
|
|
||||||
os.path.join(
|
|
||||||
settings.BASE_DIR, "tacticalrmm/test_data/wmi_python_agent.json"
|
|
||||||
)
|
|
||||||
) as f:
|
|
||||||
wmi_py = json.load(f)
|
|
||||||
|
|
||||||
return Agent.objects.create(
|
|
||||||
operating_system="Windows 10",
|
|
||||||
plat="windows",
|
|
||||||
plat_release="windows-Server2019",
|
|
||||||
hostname=f"{hostname}",
|
|
||||||
salt_id=self.generate_agent_id(hostname),
|
|
||||||
local_ip="10.0.25.188",
|
|
||||||
agent_id="71AHC-AA813-HH1BC-AAHH5-00013|DESKTOP-TEST123",
|
|
||||||
services=[
|
|
||||||
{
|
|
||||||
"pid": 880,
|
|
||||||
"name": "AeLookupSvc",
|
|
||||||
"status": "stopped",
|
|
||||||
"binpath": "C:\\Windows\\system32\\svchost.exe -k netsvcs",
|
|
||||||
"username": "localSystem",
|
|
||||||
"start_type": "manual",
|
|
||||||
"description": "Processes application compatibility cache requests for applications as they are launched",
|
|
||||||
"display_name": "Application Experience",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pid": 812,
|
|
||||||
"name": "ALG",
|
|
||||||
"status": "stopped",
|
|
||||||
"binpath": "C:\\Windows\\System32\\alg.exe",
|
|
||||||
"username": "NT AUTHORITY\\LocalService",
|
|
||||||
"start_type": "manual",
|
|
||||||
"description": "Provides support for 3rd party protocol plug-ins for Internet Connection Sharing",
|
|
||||||
"display_name": "Application Layer Gateway Service",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
public_ip="74.13.24.14",
|
|
||||||
total_ram=16,
|
|
||||||
used_ram=33,
|
|
||||||
disks={
|
|
||||||
"C:": {
|
|
||||||
"free": "42.3G",
|
|
||||||
"used": "17.1G",
|
|
||||||
"total": "59.5G",
|
|
||||||
"device": "C:",
|
|
||||||
"fstype": "NTFS",
|
|
||||||
"percent": 28,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
boot_time=8173231.4,
|
|
||||||
logged_in_username="John",
|
|
||||||
client=f"{client}",
|
|
||||||
site=f"{site}",
|
|
||||||
monitoring_type=monitoring_type,
|
|
||||||
description="Test PC",
|
|
||||||
mesh_node_id="abcdefghijklmnopAABBCCDD77443355##!!AI%@#$%#*",
|
|
||||||
last_seen=djangotime.now(),
|
|
||||||
wmi_detail=wmi_py,
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_agent_id(self, hostname):
|
|
||||||
rand = "".join(random.choice(string.ascii_letters) for _ in range(35))
|
|
||||||
return f"{rand}-{hostname}"
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class TestWinUpdateViews(TacticalTestCase):
|
|||||||
def test_get_winupdates(self):
|
def test_get_winupdates(self):
|
||||||
|
|
||||||
agent = baker.make_recipe("agents.agent")
|
agent = baker.make_recipe("agents.agent")
|
||||||
winupdates = baker.make("winupdate.WinUpdate", agent=agent, _quantity=4)
|
baker.make("winupdate.WinUpdate", agent=agent, _quantity=4)
|
||||||
|
|
||||||
# test a call where agent doesn't exist
|
# test a call where agent doesn't exist
|
||||||
resp = self.client.get("/winupdate/500/getwinupdates/", format="json")
|
resp = self.client.get("/winupdate/500/getwinupdates/", format="json")
|
||||||
@@ -107,9 +107,11 @@ class WinupdateTasks(TacticalTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.setup_coresettings()
|
self.setup_coresettings()
|
||||||
|
|
||||||
baker.make("clients.Site", site="Default", client__client="Default")
|
site = baker.make("clients.Site")
|
||||||
self.online_agents = baker.make_recipe("agents.online_agent", _quantity=2)
|
self.online_agents = baker.make_recipe(
|
||||||
self.offline_agent = baker.make_recipe("agents.agent")
|
"agents.online_agent", site=site, _quantity=2
|
||||||
|
)
|
||||||
|
self.offline_agent = baker.make_recipe("agents.agent", site=site)
|
||||||
|
|
||||||
@patch("winupdate.tasks.check_for_updates_task.apply_async")
|
@patch("winupdate.tasks.check_for_updates_task.apply_async")
|
||||||
def test_auto_approve_task(self, check_updates_task):
|
def test_auto_approve_task(self, check_updates_task):
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
:style="{ 'max-height': agentTableHeight }"
|
:style="{ 'max-height': agentTableHeight }"
|
||||||
:data="filter"
|
:data="filter"
|
||||||
:filter="search"
|
:filter="search"
|
||||||
|
:filter-method="filterTable"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:visible-columns="visibleColumns"
|
:visible-columns="visibleColumns"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
@@ -84,7 +85,7 @@
|
|||||||
<q-item-section>Edit {{ props.row.hostname }}</q-item-section>
|
<q-item-section>Edit {{ props.row.hostname }}</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<!-- agent pending actions -->
|
<!-- agent pending actions -->
|
||||||
<q-item clickable v-close-popup @click="showPendingActions(props.row.id, props.row.hostname)">
|
<q-item clickable v-close-popup @click="showPendingActionsModal(props.row.id)">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon size="xs" name="far fa-clock" />
|
<q-icon size="xs" name="far fa-clock" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
@@ -255,8 +256,8 @@
|
|||||||
<q-tooltip>Checks passing</q-tooltip>
|
<q-tooltip>Checks passing</q-tooltip>
|
||||||
</q-icon>
|
</q-icon>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td key="client" :props="props">{{ props.row.client }}</q-td>
|
<q-td key="client_name" :props="props">{{ props.row.client_name }}</q-td>
|
||||||
<q-td key="site" :props="props">{{ props.row.site }}</q-td>
|
<q-td key="site_name" :props="props">{{ props.row.site_name }}</q-td>
|
||||||
|
|
||||||
<q-td key="hostname" :props="props">{{ props.row.hostname }}</q-td>
|
<q-td key="hostname" :props="props">{{ props.row.hostname }}</q-td>
|
||||||
<q-td key="description" :props="props">{{ props.row.description }}</q-td>
|
<q-td key="description" :props="props">{{ props.row.description }}</q-td>
|
||||||
@@ -306,7 +307,11 @@
|
|||||||
<RebootLater @close="showRebootLaterModal = false" />
|
<RebootLater @close="showRebootLaterModal = false" />
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
<!-- pending actions modal -->
|
<!-- pending actions modal -->
|
||||||
<PendingActions />
|
<div class="q-pa-md q-gutter-sm">
|
||||||
|
<q-dialog v-model="showPendingActions" @hide="closePendingActionsModal">
|
||||||
|
<PendingActions :agentpk="pendingActionAgentPk" @close="closePendingActionsModal" />
|
||||||
|
</q-dialog>
|
||||||
|
</div>
|
||||||
<!-- add policy modal -->
|
<!-- add policy modal -->
|
||||||
<q-dialog v-model="showPolicyAddModal">
|
<q-dialog v-model="showPolicyAddModal">
|
||||||
<PolicyAdd @close="showPolicyAddModal = false" type="agent" :pk="policyAddPk" />
|
<PolicyAdd @close="showPolicyAddModal = false" type="agent" :pk="policyAddPk" />
|
||||||
@@ -367,9 +372,61 @@ export default {
|
|||||||
showAgentRecovery: false,
|
showAgentRecovery: false,
|
||||||
showRunScript: false,
|
showRunScript: false,
|
||||||
policyAddPk: null,
|
policyAddPk: null,
|
||||||
|
showPendingActions: false,
|
||||||
|
pendingActionAgentPk: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
filterTable(rows, terms, cols, cellValue) {
|
||||||
|
const lowerTerms = terms ? terms.toLowerCase() : "";
|
||||||
|
let advancedFilter = false;
|
||||||
|
let availability = null;
|
||||||
|
let checks = false;
|
||||||
|
let patches = false;
|
||||||
|
let reboot = false;
|
||||||
|
let search = "";
|
||||||
|
|
||||||
|
const params = lowerTerms.trim().split(" ");
|
||||||
|
// parse search text and set variables
|
||||||
|
params.forEach(param => {
|
||||||
|
if (param.includes("is:")) {
|
||||||
|
advancedFilter = true;
|
||||||
|
let filter = param.split(":")[1];
|
||||||
|
if (filter === "patchespending") patches = true;
|
||||||
|
else if (filter === "checksfailing") checks = true;
|
||||||
|
else if (filter === "rebootneeded") reboot = true;
|
||||||
|
else if (filter === "online" || filter === "offline" || filter === "expired") availability = filter;
|
||||||
|
} else {
|
||||||
|
search = param + "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return rows.filter(row => {
|
||||||
|
if (advancedFilter) {
|
||||||
|
if (checks && !row.checks.has_failing_checks) return false;
|
||||||
|
if (patches && !row.patches_pending) 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;
|
||||||
|
else if (availability === "expired") {
|
||||||
|
const nowPlus30Days = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
||||||
|
const unixtime = Date.parse(row.last_seen);
|
||||||
|
if (unixtime > nowPlus30Days) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix for last_logged_in_user not filtering
|
||||||
|
if (row.logged_in_username === "None" && row.status === "online" && !!row.last_logged_in_user)
|
||||||
|
return row.last_logged_in_user.toLowerCase().indexOf(search) !== -1;
|
||||||
|
|
||||||
|
// Normal text filter
|
||||||
|
return cols.some(col => {
|
||||||
|
const val = cellValue(col, row) + "";
|
||||||
|
const haystack = val === "undefined" || val === "null" ? "" : val.toLowerCase();
|
||||||
|
return haystack.indexOf(search) !== -1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
rowDoubleClicked(pk) {
|
rowDoubleClicked(pk) {
|
||||||
this.$store.commit("setActiveRow", pk);
|
this.$store.commit("setActiveRow", pk);
|
||||||
this.$q.loading.show();
|
this.$q.loading.show();
|
||||||
@@ -400,9 +457,13 @@ export default {
|
|||||||
agentEdited() {
|
agentEdited() {
|
||||||
this.$emit("refreshEdit");
|
this.$emit("refreshEdit");
|
||||||
},
|
},
|
||||||
showPendingActions(pk, hostname) {
|
showPendingActionsModal(pk) {
|
||||||
const data = { action: true, agentpk: pk, hostname: hostname };
|
this.showPendingActions = true;
|
||||||
this.$store.commit("logs/TOGGLE_PENDING_ACTIONS", data);
|
this.pendingActionAgentPk = pk;
|
||||||
|
},
|
||||||
|
closePendingActionsModal() {
|
||||||
|
this.showPendingActions = false;
|
||||||
|
this.pendingActionAgentPk = null;
|
||||||
},
|
},
|
||||||
takeControl(pk) {
|
takeControl(pk) {
|
||||||
const url = this.$router.resolve(`/takecontrol/${pk}`).href;
|
const url = this.$router.resolve(`/takecontrol/${pk}`).href;
|
||||||
|
|||||||
@@ -182,8 +182,8 @@ export default {
|
|||||||
pattern: needle,
|
pattern: needle,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$store
|
this.$axios
|
||||||
.dispatch("logs/optionsFilter", data)
|
.post(`logs/auditlogs/optionsfilter/`, data)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
this.userOptions = r.data.map(user => user.username);
|
this.userOptions = r.data.map(user => user.username);
|
||||||
this.$q.loading.hide();
|
this.$q.loading.hide();
|
||||||
@@ -209,8 +209,8 @@ export default {
|
|||||||
pattern: needle,
|
pattern: needle,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$store
|
this.$axios
|
||||||
.dispatch("logs/optionsFilter", data)
|
.post(`logs/auditlogs/optionsfilter/`, data)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
this.agentOptions = r.data.map(agent => agent.hostname);
|
this.agentOptions = r.data.map(agent => agent.hostname);
|
||||||
this.$q.loading.hide();
|
this.$q.loading.hide();
|
||||||
@@ -264,8 +264,8 @@ export default {
|
|||||||
data["timeFilter"] = this.timeFilter;
|
data["timeFilter"] = this.timeFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store
|
this.$axios
|
||||||
.dispatch("logs/loadAuditLogs", data)
|
.patch("/logs/auditlogs/", data)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
this.$q.loading.hide();
|
this.$q.loading.hide();
|
||||||
this.auditLogs = r.data;
|
this.auditLogs = r.data;
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-menu anchor="top right" self="top left">
|
<q-menu anchor="top right" self="top left">
|
||||||
<q-list dense style="min-width: 100px">
|
<q-list dense style="min-width: 100px">
|
||||||
<q-item clickable v-close-popup @click="showAddClientModal = true">
|
<q-item clickable v-close-popup @click="showClientsFormModal('client', 'add')">
|
||||||
<q-item-section>Add Client</q-item-section>
|
<q-item-section>Add Client</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item clickable v-close-popup @click="showAddSiteModal = true">
|
<q-item clickable v-close-popup @click="showClientsFormModal('site', 'add')">
|
||||||
<q-item-section>Add Site</q-item-section>
|
<q-item-section>Add Site</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@@ -29,10 +29,10 @@
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-menu anchor="top right" self="top left">
|
<q-menu anchor="top right" self="top left">
|
||||||
<q-list dense style="min-width: 100px">
|
<q-list dense style="min-width: 100px">
|
||||||
<q-item clickable v-close-popup @click="showDeleteClientModal = true">
|
<q-item clickable v-close-popup @click="showClientsFormModal('client', 'delete')">
|
||||||
<q-item-section>Delete Client</q-item-section>
|
<q-item-section>Delete Client</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item clickable v-close-popup @click="showDeleteSiteModal = true">
|
<q-item clickable v-close-popup @click="showClientsFormModal('site', 'delete')">
|
||||||
<q-item-section>Delete Site</q-item-section>
|
<q-item-section>Delete Site</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<q-item clickable v-close-popup @click="showAuditManager = true">
|
<q-item clickable v-close-popup @click="showAuditManager = true">
|
||||||
<q-item-section>Audit Log</q-item-section>
|
<q-item-section>Audit Log</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item clickable v-close-popup @click="getLog">
|
<q-item clickable v-close-popup @click="showDebugLog = true">
|
||||||
<q-item-section>Debug Log</q-item-section>
|
<q-item-section>Debug Log</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@@ -55,10 +55,10 @@
|
|||||||
<q-btn size="md" dense no-caps flat label="Edit">
|
<q-btn size="md" dense no-caps flat label="Edit">
|
||||||
<q-menu>
|
<q-menu>
|
||||||
<q-list dense style="min-width: 100px">
|
<q-list dense style="min-width: 100px">
|
||||||
<q-item clickable v-close-popup @click="showEditClientsModal = true">
|
<q-item clickable v-close-popup @click="showClientsFormModal('client', 'edit')">
|
||||||
<q-item-section>Edit Clients</q-item-section>
|
<q-item-section>Edit Clients</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item clickable v-close-popup @click="showEditSitesModal = true">
|
<q-item clickable v-close-popup @click="showClientsFormModal('site', 'edit')">
|
||||||
<q-item-section>Edit Sites</q-item-section>
|
<q-item-section>Edit Sites</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
<q-btn size="md" dense no-caps flat label="View">
|
<q-btn size="md" dense no-caps flat label="View">
|
||||||
<q-menu auto-close>
|
<q-menu auto-close>
|
||||||
<q-list dense style="min-width: 100px">
|
<q-list dense style="min-width: 100px">
|
||||||
<q-item clickable v-close-popup @click="showPendingActions">
|
<q-item clickable v-close-popup @click="showPendingActions = true">
|
||||||
<q-item-section>Pending Actions</q-item-section>
|
<q-item-section>Pending Actions</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@@ -119,15 +119,15 @@
|
|||||||
<q-menu auto-close>
|
<q-menu auto-close>
|
||||||
<q-list dense style="min-width: 100px">
|
<q-list dense style="min-width: 100px">
|
||||||
<!-- bulk command -->
|
<!-- bulk command -->
|
||||||
<q-item clickable v-close-popup @click="showBulkCommand = true">
|
<q-item clickable v-close-popup @click="showBulkActionModal('command')">
|
||||||
<q-item-section>Bulk Command</q-item-section>
|
<q-item-section>Bulk Command</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<!-- bulk script -->
|
<!-- bulk script -->
|
||||||
<q-item clickable v-close-popup @click="showBulkScript = true">
|
<q-item clickable v-close-popup @click="showBulkActionModal('script')">
|
||||||
<q-item-section>Bulk Script</q-item-section>
|
<q-item-section>Bulk Script</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<!-- bulk patch management -->
|
<!-- bulk patch management -->
|
||||||
<q-item clickable v-close-popup @click="showBulkPatchManagement = true">
|
<q-item clickable v-close-popup @click="showBulkActionModal('scan')">
|
||||||
<q-item-section>Bulk Patch Management</q-item-section>
|
<q-item-section>Bulk Patch Management</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@@ -135,34 +135,31 @@
|
|||||||
</q-btn>
|
</q-btn>
|
||||||
</q-btn-group>
|
</q-btn-group>
|
||||||
<q-space />
|
<q-space />
|
||||||
<!-- add client modal -->
|
<!-- client form modal -->
|
||||||
<q-dialog v-model="showAddClientModal">
|
<q-dialog v-model="showClientFormModal" @hide="closeClientsFormModal">
|
||||||
<AddClient @close="showAddClientModal = false" />
|
<ClientsForm @close="closeClientsFormModal" :op="clientOp" @edited="edited" />
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
<q-dialog v-model="showEditClientsModal">
|
<!-- site form modal -->
|
||||||
<EditClients @close="showEditClientsModal = false" @edited="edited" />
|
<q-dialog v-model="showSiteFormModal" @hide="closeClientsFormModal">
|
||||||
</q-dialog>
|
<SitesForm @close="closeClientsFormModal" :op="clientOp" @edited="edited" />
|
||||||
<!-- add site modal -->
|
|
||||||
<q-dialog v-model="showAddSiteModal">
|
|
||||||
<AddSite @close="showAddSiteModal = false" :clients="clients" />
|
|
||||||
</q-dialog>
|
|
||||||
<q-dialog v-model="showEditSitesModal">
|
|
||||||
<EditSites @close="showEditSitesModal = false" @edited="edited" />
|
|
||||||
</q-dialog>
|
|
||||||
<!-- delete -->
|
|
||||||
<q-dialog v-model="showDeleteClientModal">
|
|
||||||
<DeleteClient @close="showDeleteClientModal = false" @edited="edited" />
|
|
||||||
</q-dialog>
|
|
||||||
<q-dialog v-model="showDeleteSiteModal">
|
|
||||||
<DeleteSite @close="showDeleteSiteModal = false" @edited="edited" />
|
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
<!-- edit core settings modal -->
|
<!-- edit core settings modal -->
|
||||||
<q-dialog v-model="showEditCoreSettingsModal">
|
<q-dialog v-model="showEditCoreSettingsModal">
|
||||||
<EditCoreSettings @close="showEditCoreSettingsModal = false" />
|
<EditCoreSettings @close="showEditCoreSettingsModal = false" />
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
<!-- debug log modal -->
|
<!-- debug log modal -->
|
||||||
<LogModal />
|
<div class="q-pa-md q-gutter-sm">
|
||||||
<!-- audit log modal -->
|
<q-dialog v-model="showDebugLog" maximized transition-show="slide-up" transition-hide="slide-down">
|
||||||
|
<LogModal @close="showDebugLog = false" />
|
||||||
|
</q-dialog>
|
||||||
|
</div>
|
||||||
|
<!-- pending actions modal -->
|
||||||
|
<div class="q-pa-md q-gutter-sm">
|
||||||
|
<q-dialog v-model="showPendingActions">
|
||||||
|
<PendingActions @close="showPendingActions = false" />
|
||||||
|
</q-dialog>
|
||||||
|
</div>
|
||||||
|
<!-- audit manager -->
|
||||||
<div class="q-pa-md q-gutter-sm">
|
<div class="q-pa-md q-gutter-sm">
|
||||||
<q-dialog v-model="showAuditManager" maximized transition-show="slide-up" transition-hide="slide-down">
|
<q-dialog v-model="showAuditManager" maximized transition-show="slide-up" transition-hide="slide-down">
|
||||||
<AuditManager @close="showAuditManager = false" />
|
<AuditManager @close="showAuditManager = false" />
|
||||||
@@ -200,19 +197,9 @@
|
|||||||
<UploadMesh @close="showUploadMesh = false" />
|
<UploadMesh @close="showUploadMesh = false" />
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
<!-- Bulk command modal -->
|
<!-- Bulk action modal -->
|
||||||
<q-dialog v-model="showBulkCommand" position="top">
|
<q-dialog v-model="showBulkAction" @hide="closeBulkActionModal" position="top">
|
||||||
<BulkCommand @close="showBulkCommand = false" />
|
<BulkAction :mode="bulkMode" @close="closeBulkActionModal" />
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<!-- Bulk script modal -->
|
|
||||||
<q-dialog v-model="showBulkScript" position="top">
|
|
||||||
<BulkScript @close="showBulkScript = false" />
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<!-- Bulk patch management -->
|
|
||||||
<q-dialog v-model="showBulkPatchManagement" position="top">
|
|
||||||
<BulkPatchManagement @close="showBulkPatchManagement = false" />
|
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
<!-- Agent Deployment -->
|
<!-- Agent Deployment -->
|
||||||
@@ -225,12 +212,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import LogModal from "@/components/modals/logs/LogModal";
|
import LogModal from "@/components/modals/logs/LogModal";
|
||||||
import AddClient from "@/components/modals/clients/AddClient";
|
import PendingActions from "@/components/modals/logs/PendingActions";
|
||||||
import EditClients from "@/components/modals/clients/EditClients";
|
import ClientsForm from "@/components/modals/clients/ClientsForm";
|
||||||
import AddSite from "@/components/modals/clients/AddSite";
|
import SitesForm from "@/components/modals/clients/SitesForm";
|
||||||
import EditSites from "@/components/modals/clients/EditSites";
|
|
||||||
import DeleteClient from "@/components/modals/clients/DeleteClient";
|
|
||||||
import DeleteSite from "@/components/modals/clients/DeleteSite";
|
|
||||||
import UpdateAgents from "@/components/modals/agents/UpdateAgents";
|
import UpdateAgents from "@/components/modals/agents/UpdateAgents";
|
||||||
import ScriptManager from "@/components/ScriptManager";
|
import ScriptManager from "@/components/ScriptManager";
|
||||||
import EditCoreSettings from "@/components/modals/coresettings/EditCoreSettings";
|
import EditCoreSettings from "@/components/modals/coresettings/EditCoreSettings";
|
||||||
@@ -239,21 +223,16 @@ import AdminManager from "@/components/AdminManager";
|
|||||||
import InstallAgent from "@/components/modals/agents/InstallAgent";
|
import InstallAgent from "@/components/modals/agents/InstallAgent";
|
||||||
import UploadMesh from "@/components/modals/core/UploadMesh";
|
import UploadMesh from "@/components/modals/core/UploadMesh";
|
||||||
import AuditManager from "@/components/AuditManager";
|
import AuditManager from "@/components/AuditManager";
|
||||||
import BulkCommand from "@/components/modals/agents/BulkCommand";
|
import BulkAction from "@/components/modals/agents/BulkAction";
|
||||||
import BulkScript from "@/components/modals/agents/BulkScript";
|
|
||||||
import BulkPatchManagement from "@/components/modals/agents/BulkPatchManagement";
|
|
||||||
import Deployment from "@/components/Deployment";
|
import Deployment from "@/components/Deployment";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "FileBar",
|
name: "FileBar",
|
||||||
components: {
|
components: {
|
||||||
LogModal,
|
LogModal,
|
||||||
AddClient,
|
PendingActions,
|
||||||
EditClients,
|
ClientsForm,
|
||||||
AddSite,
|
SitesForm,
|
||||||
EditSites,
|
|
||||||
DeleteClient,
|
|
||||||
DeleteSite,
|
|
||||||
UpdateAgents,
|
UpdateAgents,
|
||||||
ScriptManager,
|
ScriptManager,
|
||||||
EditCoreSettings,
|
EditCoreSettings,
|
||||||
@@ -262,20 +241,15 @@ export default {
|
|||||||
UploadMesh,
|
UploadMesh,
|
||||||
AdminManager,
|
AdminManager,
|
||||||
AuditManager,
|
AuditManager,
|
||||||
BulkCommand,
|
BulkAction,
|
||||||
BulkScript,
|
|
||||||
BulkPatchManagement,
|
|
||||||
Deployment,
|
Deployment,
|
||||||
},
|
},
|
||||||
props: ["clients"],
|
props: ["clients"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showAddClientModal: false,
|
showClientFormModal: false,
|
||||||
showEditClientsModal: false,
|
showSiteFormModal: false,
|
||||||
showAddSiteModal: false,
|
clientOp: null,
|
||||||
showEditSitesModal: false,
|
|
||||||
showDeleteClientModal: false,
|
|
||||||
showDeleteSiteModal: false,
|
|
||||||
showUpdateAgentsModal: false,
|
showUpdateAgentsModal: false,
|
||||||
showEditCoreSettingsModal: false,
|
showEditCoreSettingsModal: false,
|
||||||
showAutomationManager: false,
|
showAutomationManager: false,
|
||||||
@@ -283,19 +257,35 @@ export default {
|
|||||||
showInstallAgent: false,
|
showInstallAgent: false,
|
||||||
showUploadMesh: false,
|
showUploadMesh: false,
|
||||||
showAuditManager: false,
|
showAuditManager: false,
|
||||||
showBulkCommand: false,
|
showBulkAction: false,
|
||||||
showBulkScript: false,
|
showPendingActions: false,
|
||||||
showBulkPatchManagement: false,
|
bulkMode: null,
|
||||||
showDeployment: false,
|
showDeployment: false,
|
||||||
|
showDebugLog: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getLog() {
|
showClientsFormModal(type, op) {
|
||||||
this.$store.commit("logs/TOGGLE_LOG_MODAL", true);
|
this.clientOp = op;
|
||||||
|
|
||||||
|
if (type === "client") {
|
||||||
|
this.showClientFormModal = true;
|
||||||
|
} else if (type === "site") {
|
||||||
|
this.showSiteFormModal = true;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
showPendingActions() {
|
closeClientsFormModal() {
|
||||||
const data = { action: true, agentpk: null, hostname: null };
|
this.clientOp = null;
|
||||||
this.$store.commit("logs/TOGGLE_PENDING_ACTIONS", data);
|
this.showClientFormModal = null;
|
||||||
|
this.showSiteFormModal = null;
|
||||||
|
},
|
||||||
|
showBulkActionModal(mode) {
|
||||||
|
this.bulkMode = mode;
|
||||||
|
this.showBulkAction = true;
|
||||||
|
},
|
||||||
|
closeBulkActionModal() {
|
||||||
|
this.bulkMode = null;
|
||||||
|
this.showBulkAction = false;
|
||||||
},
|
},
|
||||||
showScriptManager() {
|
showScriptManager() {
|
||||||
this.$store.commit("TOGGLE_SCRIPT_MANAGER", true);
|
this.$store.commit("TOGGLE_SCRIPT_MANAGER", true);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="width: 900px; max-width: 90vw">
|
<div style="width: 60vw; max-width: 90vw">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-bar>
|
<q-bar>
|
||||||
<q-btn ref="refresh" @click="refresh" class="q-mr-sm" dense flat push icon="refresh" />Automation Manager
|
<q-btn ref="refresh" @click="refresh" class="q-mr-sm" dense flat push icon="refresh" />Automation Manager
|
||||||
@@ -208,9 +208,9 @@
|
|||||||
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
<q-tooltip content-class="bg-white text-primary">Close</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-bar>
|
</q-bar>
|
||||||
<q-scroll-area :thumb-style="thumbStyle" style="height: 70vh">
|
<div class="scroll" style="height: 70vh">
|
||||||
<PatchPolicyForm :policy="policy" @close="closePatchPolicyModal" />
|
<PatchPolicyForm :policy="policy" @close="closePatchPolicyModal" />
|
||||||
</q-scroll-area>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
@@ -277,13 +277,6 @@ export default {
|
|||||||
pagination: {
|
pagination: {
|
||||||
rowsPerPage: 9999,
|
rowsPerPage: 9999,
|
||||||
},
|
},
|
||||||
thumbStyle: {
|
|
||||||
right: "2px",
|
|
||||||
borderRadius: "5px",
|
|
||||||
backgroundColor: "#027be3",
|
|
||||||
width: "5px",
|
|
||||||
opacity: 0.75,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -37,12 +37,7 @@
|
|||||||
<q-tab name="checks" icon="fas fa-check-double" label="Checks" />
|
<q-tab name="checks" icon="fas fa-check-double" label="Checks" />
|
||||||
<q-tab name="tasks" icon="fas fa-tasks" label="Tasks" />
|
<q-tab name="tasks" icon="fas fa-tasks" label="Tasks" />
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
<q-tab-panels
|
<q-tab-panels v-model="selectedTab" animated transition-prev="jump-up" transition-next="jump-up">
|
||||||
v-model="selectedTab"
|
|
||||||
animated
|
|
||||||
transition-prev="jump-up"
|
|
||||||
transition-next="jump-up"
|
|
||||||
>
|
|
||||||
<q-tab-panel name="checks">
|
<q-tab-panel name="checks">
|
||||||
<PolicyChecksTab />
|
<PolicyChecksTab />
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
@@ -127,7 +122,7 @@ export default {
|
|||||||
for (let client in data) {
|
for (let client in data) {
|
||||||
var client_temp = {};
|
var client_temp = {};
|
||||||
|
|
||||||
client_temp["label"] = data[client].client;
|
client_temp["label"] = data[client].name;
|
||||||
client_temp["id"] = unique_id;
|
client_temp["id"] = unique_id;
|
||||||
client_temp["icon"] = "business";
|
client_temp["icon"] = "business";
|
||||||
client_temp["selectable"] = false;
|
client_temp["selectable"] = false;
|
||||||
@@ -170,7 +165,7 @@ export default {
|
|||||||
// Iterate through Sites
|
// Iterate through Sites
|
||||||
for (let site in data[client].sites) {
|
for (let site in data[client].sites) {
|
||||||
var site_temp = {};
|
var site_temp = {};
|
||||||
site_temp["label"] = data[client].sites[site].site;
|
site_temp["label"] = data[client].sites[site].name;
|
||||||
site_temp["id"] = unique_id;
|
site_temp["id"] = unique_id;
|
||||||
site_temp["icon"] = "apartment";
|
site_temp["icon"] = "apartment";
|
||||||
site_temp["selectable"] = false;
|
site_temp["selectable"] = false;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<q-list separator padding>
|
<q-list separator padding>
|
||||||
<q-item :key="item.id + 'servers'" v-for="item in related.server_clients">
|
<q-item :key="item.id + 'servers'" v-for="item in related.server_clients">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>{{ item.client }}</q-item-label>
|
<q-item-label>{{ item.name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-item-label>
|
<q-item-label>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</q-item>
|
</q-item>
|
||||||
<q-item :key="item.id + 'workstations'" v-for="item in related.workstation_clients">
|
<q-item :key="item.id + 'workstations'" v-for="item in related.workstation_clients">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>{{ item.client }}</q-item-label>
|
<q-item-label>{{ item.name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-item-label>
|
<q-item-label>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
<q-list separator padding>
|
<q-list separator padding>
|
||||||
<q-item :key="item.id + 'servers'" v-for="item in related.server_sites">
|
<q-item :key="item.id + 'servers'" v-for="item in related.server_sites">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>{{ item.site }}</q-item-label>
|
<q-item-label>{{ item.name }}</q-item-label>
|
||||||
<q-item-label caption>{{ item.client_name }}</q-item-label>
|
<q-item-label caption>{{ item.client_name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
</q-item>
|
</q-item>
|
||||||
<q-item :key="item.id + 'workstations'" v-for="item in related.workstation_sites">
|
<q-item :key="item.id + 'workstations'" v-for="item in related.workstation_sites">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>{{ item.site }}</q-item-label>
|
<q-item-label>{{ item.name }}</q-item-label>
|
||||||
<q-item-label caption>{{ item.client_name }}</q-item-label>
|
<q-item-label caption>{{ item.client_name }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<q-card style="min-width: 50vw">
|
<q-card style="min-width: 50vw">
|
||||||
<q-card-section class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
<div class="text-h6">
|
<div class="text-h6">
|
||||||
Run Bulk Script
|
{{ modalTitle }}
|
||||||
<div class="text-caption">Run a script on multiple agents in parallel</div>
|
<div v-if="modalCaption !== null" class="text-caption">{{ modalCaption }}</div>
|
||||||
</div>
|
</div>
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-btn icon="close" flat round dense v-close-popup />
|
<q-btn icon="close" flat round dense v-close-popup />
|
||||||
@@ -15,14 +15,25 @@
|
|||||||
<p>Choose Target</p>
|
<p>Choose Target</p>
|
||||||
<div class="q-gutter-sm">
|
<div class="q-gutter-sm">
|
||||||
<q-radio dense v-model="target" val="client" label="Client" @input="agentMultiple = []" />
|
<q-radio dense v-model="target" val="client" label="Client" @input="agentMultiple = []" />
|
||||||
<q-radio dense v-model="target" val="site" label="Site" @input="agentMultiple = []" />
|
<q-radio
|
||||||
|
dense
|
||||||
|
v-model="target"
|
||||||
|
val="site"
|
||||||
|
label="Site"
|
||||||
|
@input="
|
||||||
|
() => {
|
||||||
|
agentMultiple = [];
|
||||||
|
site = sites[0];
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
<q-radio dense v-model="target" val="agents" label="Selected Agents" />
|
<q-radio dense v-model="target" val="agents" label="Selected Agents" />
|
||||||
<q-radio dense v-model="target" val="all" label="All Agents" @input="agentMultiple = []" />
|
<q-radio dense v-model="target" val="all" label="All Agents" @input="agentMultiple = []" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-section v-if="tree !== null && client !== null && target === 'client'">
|
<q-card-section v-if="target === 'client' || target === 'site'" class="q-pb-none">
|
||||||
<q-select
|
<q-select
|
||||||
dense
|
dense
|
||||||
:rules="[val => !!val || '*Required']"
|
:rules="[val => !!val || '*Required']"
|
||||||
@@ -30,22 +41,11 @@
|
|||||||
options-dense
|
options-dense
|
||||||
label="Select client"
|
label="Select client"
|
||||||
v-model="client"
|
v-model="client"
|
||||||
:options="Object.keys(tree).sort()"
|
:options="client_options"
|
||||||
/>
|
@input="target === 'site' ? (site = sites[0]) : () => {}"
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section v-if="tree !== null && client !== null && target === 'site'">
|
|
||||||
<q-select
|
|
||||||
dense
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select client"
|
|
||||||
v-model="client"
|
|
||||||
:options="Object.keys(tree).sort()"
|
|
||||||
@input="site = sites[0]"
|
|
||||||
/>
|
/>
|
||||||
<q-select
|
<q-select
|
||||||
|
v-if="target === 'site'"
|
||||||
dense
|
dense
|
||||||
:rules="[val => !!val || '*Required']"
|
:rules="[val => !!val || '*Required']"
|
||||||
outlined
|
outlined
|
||||||
@@ -55,8 +55,7 @@
|
|||||||
:options="sites"
|
:options="sites"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
<q-card-section v-if="target === 'agents'">
|
||||||
<q-card-section v-if="agents.length !== 0 && target === 'agents'">
|
|
||||||
<q-select
|
<q-select
|
||||||
dense
|
dense
|
||||||
options-dense
|
options-dense
|
||||||
@@ -72,7 +71,7 @@
|
|||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-section>
|
<q-card-section v-if="mode === 'script'" class="q-pt-none">
|
||||||
<q-select
|
<q-select
|
||||||
:rules="[val => !!val || '*Required']"
|
:rules="[val => !!val || '*Required']"
|
||||||
dense
|
dense
|
||||||
@@ -85,7 +84,7 @@
|
|||||||
options-dense
|
options-dense
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
<q-card-section v-if="mode === 'script'" class="q-pt-none">
|
||||||
<q-select
|
<q-select
|
||||||
label="Script Arguments (press Enter after typing each argument)"
|
label="Script Arguments (press Enter after typing each argument)"
|
||||||
filled
|
filled
|
||||||
@@ -99,7 +98,28 @@
|
|||||||
new-value-mode="add"
|
new-value-mode="add"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
|
||||||
|
<q-card-section v-if="mode === 'command'">
|
||||||
|
<p>Shell</p>
|
||||||
|
<div class="q-gutter-sm">
|
||||||
|
<q-radio dense v-model="shell" val="cmd" label="CMD" />
|
||||||
|
<q-radio dense v-model="shell" val="powershell" label="Powershell" />
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-if="mode === 'command'">
|
||||||
|
<q-input
|
||||||
|
v-model="cmd"
|
||||||
|
outlined
|
||||||
|
label="Command"
|
||||||
|
stack-label
|
||||||
|
:placeholder="
|
||||||
|
shell === 'cmd' ? 'rmdir /S /Q C:\\Windows\\System32' : 'Remove-Item -Recurse -Force C:\\Windows\\System32'
|
||||||
|
"
|
||||||
|
:rules="[val => !!val || '*Required']"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section v-if="mode === 'script' || mode === 'command'">
|
||||||
<q-input
|
<q-input
|
||||||
v-model.number="timeout"
|
v-model.number="timeout"
|
||||||
dense
|
dense
|
||||||
@@ -115,6 +135,17 @@
|
|||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section v-if="mode === 'scan'">
|
||||||
|
<div class="q-pa-none">
|
||||||
|
<p>Action</p>
|
||||||
|
<div class="q-gutter-sm">
|
||||||
|
<q-radio dense v-model="selected_mode" val="scan" label="Run Patch Status Scan" />
|
||||||
|
<q-radio dense v-model="selected_mode" val="install" label="Install Pending Patches Now" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions align="center">
|
<q-card-actions align="center">
|
||||||
<q-btn label="Run" color="primary" class="full-width" type="submit" />
|
<q-btn label="Run" color="primary" class="full-width" type="submit" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
@@ -127,34 +158,36 @@ import mixins from "@/mixins/mixins";
|
|||||||
import { mapGetters } from "vuex";
|
import { mapGetters } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "BulkScript",
|
name: "BulkAction",
|
||||||
mixins: [mixins],
|
mixins: [mixins],
|
||||||
|
props: {
|
||||||
|
mode: !String,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
target: "client",
|
target: "client",
|
||||||
|
selected_mode: null,
|
||||||
scriptPK: null,
|
scriptPK: null,
|
||||||
timeout: 900,
|
timeout: 900,
|
||||||
tree: null,
|
|
||||||
client: null,
|
client: null,
|
||||||
|
client_options: [],
|
||||||
site: null,
|
site: null,
|
||||||
agents: [],
|
agents: [],
|
||||||
agentMultiple: [],
|
agentMultiple: [],
|
||||||
args: [],
|
args: [],
|
||||||
|
cmd: "",
|
||||||
|
shell: "cmd",
|
||||||
|
modalTitle: null,
|
||||||
|
modalCaption: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["scripts"]),
|
...mapGetters(["scripts"]),
|
||||||
sites() {
|
sites() {
|
||||||
if (this.tree !== null && this.client !== null) {
|
return !!this.client ? this.formatSiteOptions(this.client.sites) : [];
|
||||||
this.site = this.tree[this.client].sort()[0];
|
|
||||||
return this.tree[this.client].sort();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
scriptOptions() {
|
scriptOptions() {
|
||||||
const ret = [];
|
const ret = this.scripts.map(script => ({ label: script.name, value: script.id }));
|
||||||
this.scripts.forEach(i => {
|
|
||||||
ret.push({ label: i.name, value: i.id });
|
|
||||||
});
|
|
||||||
return ret.sort((a, b) => a.label.localeCompare(b.label));
|
return ret.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -162,14 +195,17 @@ export default {
|
|||||||
send() {
|
send() {
|
||||||
this.$q.loading.show();
|
this.$q.loading.show();
|
||||||
const data = {
|
const data = {
|
||||||
mode: "script",
|
mode: this.selected_mode,
|
||||||
target: this.target,
|
target: this.target,
|
||||||
client: this.client,
|
site: this.site.value,
|
||||||
site: this.site,
|
client: this.client.value,
|
||||||
agentPKs: this.agentMultiple,
|
agentPKs: this.agentMultiple,
|
||||||
scriptPK: this.scriptPK,
|
scriptPK: this.scriptPK,
|
||||||
timeout: this.timeout,
|
timeout: this.timeout,
|
||||||
args: this.args,
|
args: this.args,
|
||||||
|
shell: this.shell,
|
||||||
|
timeout: this.timeout,
|
||||||
|
cmd: this.cmd,
|
||||||
};
|
};
|
||||||
this.$axios
|
this.$axios
|
||||||
.post("/agents/bulk/", data)
|
.post("/agents/bulk/", data)
|
||||||
@@ -183,25 +219,42 @@ export default {
|
|||||||
this.notifyError(e.response.data);
|
this.notifyError(e.response.data);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getTree() {
|
getClients() {
|
||||||
this.$axios.get("/clients/loadclients/").then(r => {
|
this.$axios.get("/clients/clients/").then(r => {
|
||||||
this.tree = r.data;
|
this.client_options = this.formatClientOptions(r.data);
|
||||||
this.client = Object.keys(r.data).sort()[0];
|
|
||||||
|
this.client = this.client_options[0];
|
||||||
|
this.site = this.sites[0];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getAgents() {
|
getAgents() {
|
||||||
this.$axios.get("/agents/listagentsnodetail/").then(r => {
|
this.$axios.get("/agents/listagentsnodetail/").then(r => {
|
||||||
const ret = [];
|
const ret = r.data.map(agent => ({ label: agent.hostname, value: agent.pk }));
|
||||||
r.data.forEach(i => {
|
|
||||||
ret.push({ label: i.hostname, value: i.pk });
|
|
||||||
});
|
|
||||||
this.agents = Object.freeze(ret.sort((a, b) => a.label.localeCompare(b.label)));
|
this.agents = Object.freeze(ret.sort((a, b) => a.label.localeCompare(b.label)));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setTitles() {
|
||||||
|
switch (this.mode) {
|
||||||
|
case "command":
|
||||||
|
this.modalTitle = "Run Bulk Command";
|
||||||
|
this.modalCaption = "Run a shell command on multiple agents in parallel";
|
||||||
|
break;
|
||||||
|
case "script":
|
||||||
|
this.modalTitle = "Run Bulk Script";
|
||||||
|
this.modalCaption = "Run a script on multiple agents in parallel";
|
||||||
|
break;
|
||||||
|
case "scan":
|
||||||
|
this.modalTitle = "Bulk Patch Management";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getTree();
|
this.setTitles();
|
||||||
|
this.getClients();
|
||||||
this.getAgents();
|
this.getAgents();
|
||||||
|
|
||||||
|
this.selected_mode = this.mode;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-card style="min-width: 50vw">
|
|
||||||
<q-card-section class="row items-center">
|
|
||||||
<div class="text-h6">
|
|
||||||
Send Bulk Command
|
|
||||||
<div class="text-caption">Run a shell command on multiple agents in parallel</div>
|
|
||||||
</div>
|
|
||||||
<q-space />
|
|
||||||
<q-btn icon="close" flat round dense v-close-popup />
|
|
||||||
</q-card-section>
|
|
||||||
<q-form @submit.prevent="send">
|
|
||||||
<q-card-section>
|
|
||||||
<div class="q-pa-none">
|
|
||||||
<p>Choose Target</p>
|
|
||||||
<div class="q-gutter-sm">
|
|
||||||
<q-radio dense v-model="target" val="client" label="Client" @input="agentMultiple = []" />
|
|
||||||
<q-radio dense v-model="target" val="site" label="Site" @input="agentMultiple = []" />
|
|
||||||
<q-radio dense v-model="target" val="agents" label="Selected Agents" />
|
|
||||||
<q-radio dense v-model="target" val="all" label="All Agents" @input="agentMultiple = []" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section v-if="tree !== null && client !== null && target === 'client'">
|
|
||||||
<q-select
|
|
||||||
dense
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select client"
|
|
||||||
v-model="client"
|
|
||||||
:options="Object.keys(tree).sort()"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section v-if="tree !== null && client !== null && target === 'site'">
|
|
||||||
<q-select
|
|
||||||
dense
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select client"
|
|
||||||
v-model="client"
|
|
||||||
:options="Object.keys(tree).sort()"
|
|
||||||
@input="site = sites[0]"
|
|
||||||
/>
|
|
||||||
<q-select
|
|
||||||
dense
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select site"
|
|
||||||
v-model="site"
|
|
||||||
:options="sites"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section v-if="agents.length !== 0 && target === 'agents'">
|
|
||||||
<q-select
|
|
||||||
dense
|
|
||||||
options-dense
|
|
||||||
filled
|
|
||||||
v-model="agentMultiple"
|
|
||||||
multiple
|
|
||||||
:options="agents"
|
|
||||||
use-chips
|
|
||||||
stack-label
|
|
||||||
map-options
|
|
||||||
emit-value
|
|
||||||
label="Select Agents"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section>
|
|
||||||
<p>Shell</p>
|
|
||||||
<div class="q-gutter-sm">
|
|
||||||
<q-radio dense v-model="shell" val="cmd" label="CMD" />
|
|
||||||
<q-radio dense v-model="shell" val="powershell" label="Powershell" />
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-input
|
|
||||||
v-model="cmd"
|
|
||||||
outlined
|
|
||||||
label="Command"
|
|
||||||
stack-label
|
|
||||||
:placeholder="
|
|
||||||
shell === 'cmd' ? 'rmdir /S /Q C:\\Windows\\System32' : 'Remove-Item -Recurse -Force C:\\Windows\\System32'
|
|
||||||
"
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-input
|
|
||||||
v-model.number="timeout"
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
type="number"
|
|
||||||
style="max-width: 150px"
|
|
||||||
label="Timeout (seconds)"
|
|
||||||
stack-label
|
|
||||||
:rules="[
|
|
||||||
val => !!val || '*Required',
|
|
||||||
val => val >= 10 || 'Minimum is 10 seconds',
|
|
||||||
val => val <= 3600 || 'Maximum is 3600 seconds',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="center">
|
|
||||||
<q-btn label="Send" color="primary" class="full-width" type="submit" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import mixins from "@/mixins/mixins";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "BulkCommand",
|
|
||||||
mixins: [mixins],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
target: "client",
|
|
||||||
shell: "cmd",
|
|
||||||
timeout: 300,
|
|
||||||
cmd: null,
|
|
||||||
tree: null,
|
|
||||||
client: null,
|
|
||||||
site: null,
|
|
||||||
agents: [],
|
|
||||||
agentMultiple: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
sites() {
|
|
||||||
if (this.tree !== null && this.client !== null) {
|
|
||||||
this.site = this.tree[this.client].sort()[0];
|
|
||||||
return this.tree[this.client].sort();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
send() {
|
|
||||||
this.$q.loading.show();
|
|
||||||
const data = {
|
|
||||||
mode: "command",
|
|
||||||
target: this.target,
|
|
||||||
client: this.client,
|
|
||||||
site: this.site,
|
|
||||||
agentPKs: this.agentMultiple,
|
|
||||||
cmd: this.cmd,
|
|
||||||
timeout: this.timeout,
|
|
||||||
shell: this.shell,
|
|
||||||
};
|
|
||||||
this.$axios
|
|
||||||
.post("/agents/bulk/", data)
|
|
||||||
.then(r => {
|
|
||||||
this.$q.loading.hide();
|
|
||||||
this.$emit("close");
|
|
||||||
this.notifySuccess(r.data);
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
this.$q.loading.hide();
|
|
||||||
this.notifyError(e.response.data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getTree() {
|
|
||||||
this.$axios.get("/clients/loadclients/").then(r => {
|
|
||||||
this.tree = r.data;
|
|
||||||
this.client = Object.keys(r.data).sort()[0];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getAgents() {
|
|
||||||
this.$axios.get("/agents/listagentsnodetail/").then(r => {
|
|
||||||
const ret = [];
|
|
||||||
r.data.forEach(i => {
|
|
||||||
ret.push({ label: i.hostname, value: i.pk });
|
|
||||||
});
|
|
||||||
this.agents = Object.freeze(ret.sort((a, b) => a.label.localeCompare(b.label)));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getTree();
|
|
||||||
this.getAgents();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-card style="min-width: 50vw">
|
|
||||||
<q-card-section class="row items-center">
|
|
||||||
<div class="text-h6">Bulk Patch Management</div>
|
|
||||||
<q-space />
|
|
||||||
<q-btn icon="close" flat round dense v-close-popup />
|
|
||||||
</q-card-section>
|
|
||||||
<q-form @submit.prevent="send">
|
|
||||||
<q-card-section>
|
|
||||||
<div class="q-pa-none">
|
|
||||||
<p>Choose Target</p>
|
|
||||||
<div class="q-gutter-sm">
|
|
||||||
<q-radio dense v-model="target" val="client" label="Client" @input="agentMultiple = []" />
|
|
||||||
<q-radio dense v-model="target" val="site" label="Site" @input="agentMultiple = []" />
|
|
||||||
<q-radio dense v-model="target" val="agents" label="Selected Agents" />
|
|
||||||
<q-radio dense v-model="target" val="all" label="All Agents" @input="agentMultiple = []" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section v-if="tree !== null && client !== null && target === 'client'">
|
|
||||||
<q-select
|
|
||||||
dense
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select client"
|
|
||||||
v-model="client"
|
|
||||||
:options="Object.keys(tree).sort()"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section v-if="tree !== null && client !== null && target === 'site'">
|
|
||||||
<q-select
|
|
||||||
dense
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select client"
|
|
||||||
v-model="client"
|
|
||||||
:options="Object.keys(tree).sort()"
|
|
||||||
@input="site = sites[0]"
|
|
||||||
/>
|
|
||||||
<q-select
|
|
||||||
dense
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select site"
|
|
||||||
v-model="site"
|
|
||||||
:options="sites"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section v-if="agents.length !== 0 && target === 'agents'">
|
|
||||||
<q-select
|
|
||||||
dense
|
|
||||||
options-dense
|
|
||||||
filled
|
|
||||||
v-model="agentMultiple"
|
|
||||||
multiple
|
|
||||||
:options="agents"
|
|
||||||
use-chips
|
|
||||||
stack-label
|
|
||||||
map-options
|
|
||||||
emit-value
|
|
||||||
label="Select Agents"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section>
|
|
||||||
<div class="q-pa-none">
|
|
||||||
<p>Action</p>
|
|
||||||
<div class="q-gutter-sm">
|
|
||||||
<q-radio dense v-model="mode" val="scan" label="Run Patch Status Scan" />
|
|
||||||
<q-radio dense v-model="mode" val="install" label="Install Pending Patches Now" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-actions align="center">
|
|
||||||
<q-btn label="Send" color="primary" class="full-width" type="submit" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import mixins from "@/mixins/mixins";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "BulkPatchManagement",
|
|
||||||
mixins: [mixins],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
target: "client",
|
|
||||||
tree: null,
|
|
||||||
client: null,
|
|
||||||
site: null,
|
|
||||||
agents: [],
|
|
||||||
agentMultiple: [],
|
|
||||||
mode: "scan",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
sites() {
|
|
||||||
if (this.tree !== null && this.client !== null) {
|
|
||||||
this.site = this.tree[this.client].sort()[0];
|
|
||||||
return this.tree[this.client].sort();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
send() {
|
|
||||||
this.$q.loading.show();
|
|
||||||
const data = {
|
|
||||||
mode: this.mode,
|
|
||||||
target: this.target,
|
|
||||||
client: this.client,
|
|
||||||
site: this.site,
|
|
||||||
agentPKs: this.agentMultiple,
|
|
||||||
};
|
|
||||||
this.$axios
|
|
||||||
.post("/agents/bulk/", data)
|
|
||||||
.then(r => {
|
|
||||||
this.$q.loading.hide();
|
|
||||||
this.$emit("close");
|
|
||||||
this.notifySuccess(r.data);
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
this.$q.loading.hide();
|
|
||||||
this.notifyError(e.response.data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getTree() {
|
|
||||||
this.$axios.get("/clients/loadclients/").then(r => {
|
|
||||||
this.tree = r.data;
|
|
||||||
this.client = Object.keys(r.data).sort()[0];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getAgents() {
|
|
||||||
this.$axios.get("/agents/listagentsnodetail/").then(r => {
|
|
||||||
const ret = [];
|
|
||||||
r.data.forEach(i => {
|
|
||||||
ret.push({ label: i.hostname, value: i.pk });
|
|
||||||
});
|
|
||||||
this.agents = Object.freeze(ret.sort((a, b) => a.label.localeCompare(b.label)));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getTree();
|
|
||||||
this.getAgents();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<q-space />
|
<q-space />
|
||||||
<q-btn icon="close" flat round dense v-close-popup />
|
<q-btn icon="close" flat round dense v-close-popup />
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-scroll-area :thumb-style="thumbStyle" style="height: 500px">
|
<div class="scroll" style="max-height: 65vh">
|
||||||
<q-tab-panels v-model="tab" animated transition-prev="jump-up" transition-next="jump-up">
|
<q-tab-panels v-model="tab" animated transition-prev="jump-up" transition-next="jump-up">
|
||||||
<!-- general -->
|
<!-- general -->
|
||||||
<q-tab-panel name="general">
|
<q-tab-panel name="general">
|
||||||
@@ -22,19 +22,28 @@
|
|||||||
<div class="col-2">Client:</div>
|
<div class="col-2">Client:</div>
|
||||||
<div class="col-2"></div>
|
<div class="col-2"></div>
|
||||||
<q-select
|
<q-select
|
||||||
@input="agent.site = sites[0]"
|
@input="agent.site = site_options[0]"
|
||||||
dense
|
dense
|
||||||
options-dense
|
options-dense
|
||||||
outlined
|
outlined
|
||||||
v-model="agent.client"
|
v-model="agent.client"
|
||||||
:options="Object.keys(tree).sort()"
|
:options="client_options"
|
||||||
class="col-8"
|
class="col-8"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="row">
|
<q-card-section class="row">
|
||||||
<div class="col-2">Site:</div>
|
<div class="col-2">Site:</div>
|
||||||
<div class="col-2"></div>
|
<div class="col-2"></div>
|
||||||
<q-select class="col-8" dense options-dense outlined v-model="agent.site" :options="sites" />
|
<q-select
|
||||||
|
class="col-8"
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
outlined
|
||||||
|
v-model="agent.site"
|
||||||
|
:options="site_options"
|
||||||
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="row">
|
<q-card-section class="row">
|
||||||
<div class="col-2">Type:</div>
|
<div class="col-2">Type:</div>
|
||||||
@@ -106,10 +115,9 @@
|
|||||||
<PatchPolicyForm :agent="agent" />
|
<PatchPolicyForm :agent="agent" />
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
</q-tab-panels>
|
</q-tab-panels>
|
||||||
</q-scroll-area>
|
</div>
|
||||||
<q-card-section class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
<q-btn label="Save" color="primary" type="submit" />
|
<q-btn label="Save" color="primary" type="submit" />
|
||||||
<q-btn label="Cancel" v-close-popup />
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-form>
|
</q-form>
|
||||||
</template>
|
</template>
|
||||||
@@ -118,7 +126,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
|
||||||
import { mapGetters } from "vuex";
|
import { mapGetters } from "vuex";
|
||||||
import mixins from "@/mixins/mixins";
|
import mixins from "@/mixins/mixins";
|
||||||
import PatchPolicyForm from "@/components/modals/agents/PatchPolicyForm";
|
import PatchPolicyForm from "@/components/modals/agents/PatchPolicyForm";
|
||||||
@@ -133,25 +140,18 @@ export default {
|
|||||||
clientsLoaded: false,
|
clientsLoaded: false,
|
||||||
agent: {},
|
agent: {},
|
||||||
monTypes: ["server", "workstation"],
|
monTypes: ["server", "workstation"],
|
||||||
tree: {},
|
client_options: [],
|
||||||
splitterModel: 15,
|
splitterModel: 15,
|
||||||
tab: "general",
|
tab: "general",
|
||||||
timezone: null,
|
timezone: null,
|
||||||
tz_inherited: true,
|
tz_inherited: true,
|
||||||
original_tz: null,
|
original_tz: null,
|
||||||
allTimezones: [],
|
allTimezones: [],
|
||||||
thumbStyle: {
|
|
||||||
right: "2px",
|
|
||||||
borderRadius: "5px",
|
|
||||||
backgroundColor: "#027be3",
|
|
||||||
width: "5px",
|
|
||||||
opacity: 0.75,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getAgentInfo() {
|
getAgentInfo() {
|
||||||
axios.get(`/agents/${this.selectedAgentPk}/agenteditdetails/`).then(r => {
|
this.$axios.get(`/agents/${this.selectedAgentPk}/agenteditdetails/`).then(r => {
|
||||||
this.agent = r.data;
|
this.agent = r.data;
|
||||||
this.allTimezones = Object.freeze(r.data.all_timezones);
|
this.allTimezones = Object.freeze(r.data.all_timezones);
|
||||||
|
|
||||||
@@ -168,29 +168,38 @@ export default {
|
|||||||
this.original_tz = r.data.time_zone;
|
this.original_tz = r.data.time_zone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.agent.client = { label: r.data.client.name, id: r.data.client.id, sites: r.data.client.sites };
|
||||||
this.agentLoaded = true;
|
this.agentLoaded = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getClientsSites() {
|
getClientsSites() {
|
||||||
axios.get("/clients/loadclients/").then(r => {
|
this.$axios.get("/clients/clients/").then(r => {
|
||||||
this.tree = r.data;
|
this.client_options = this.formatClientOptions(r.data);
|
||||||
this.clientsLoaded = true;
|
this.clientsLoaded = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
editAgent() {
|
editAgent() {
|
||||||
let data = this.agent;
|
delete this.agent.all_timezones;
|
||||||
|
delete this.agent.client;
|
||||||
|
delete this.agent.sites;
|
||||||
|
delete this.agent.timezone;
|
||||||
|
delete this.agent.winupdatepolicy[0].created_by;
|
||||||
|
delete this.agent.winupdatepolicy[0].created_time;
|
||||||
|
delete this.agent.winupdatepolicy[0].modified_by;
|
||||||
|
delete this.agent.winupdatepolicy[0].modified_time;
|
||||||
|
delete this.agent.winupdatepolicy[0].policy;
|
||||||
|
|
||||||
// only send the timezone data if it has changed
|
// only send the timezone data if it has changed
|
||||||
// this way django will keep the db column as null and inherit from the global setting
|
// this way django will keep the db column as null and inherit from the global setting
|
||||||
// until we explicity change the agent's timezone
|
// until we explicity change the agent's timezone
|
||||||
if (this.timezone !== this.original_tz) {
|
if (this.timezone !== this.original_tz) {
|
||||||
data.time_zone = this.timezone;
|
this.agent.time_zone = this.timezone;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete data.all_timezones;
|
this.agent.site = this.agent.site.value;
|
||||||
|
|
||||||
axios
|
this.$axios
|
||||||
.patch("/agents/editagent/", data)
|
.patch("/agents/editagent/", this.agent)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
this.$emit("close");
|
this.$emit("close");
|
||||||
this.$emit("edited");
|
this.$emit("edited");
|
||||||
@@ -201,9 +210,9 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["selectedAgentPk"]),
|
...mapGetters(["selectedAgentPk"]),
|
||||||
sites() {
|
site_options() {
|
||||||
if (this.agentLoaded && this.clientsLoaded) {
|
if (this.agentLoaded && this.clientsLoaded) {
|
||||||
return this.tree[this.agent.client].sort();
|
return this.formatSiteOptions(this.agent.client["sites"]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-card style="min-width: 35vw" v-if="loaded">
|
<q-card style="min-width: 35vw">
|
||||||
<q-card-section class="row">
|
<q-card-section class="row">
|
||||||
<q-card-actions align="left">
|
<q-card-actions align="left">
|
||||||
<div class="text-h6">Add an agent</div>
|
<div class="text-h6">Add an agent</div>
|
||||||
@@ -11,14 +11,14 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-form @submit.prevent="addAgent">
|
<q-form @submit.prevent="addAgent">
|
||||||
<q-card-section v-if="tree !== null" class="q-gutter-sm">
|
<q-card-section class="q-gutter-sm">
|
||||||
<q-select
|
<q-select
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
options-dense
|
options-dense
|
||||||
label="Client"
|
label="Client"
|
||||||
v-model="client"
|
v-model="client"
|
||||||
:options="Object.keys(tree).sort()"
|
:options="client_options"
|
||||||
@input="site = sites[0]"
|
@input="site = sites[0]"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
@@ -88,8 +88,7 @@ export default {
|
|||||||
components: { AgentDownload },
|
components: { AgentDownload },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loaded: false,
|
client_options: [],
|
||||||
tree: {},
|
|
||||||
client: null,
|
client: null,
|
||||||
site: null,
|
site: null,
|
||||||
agenttype: "server",
|
agenttype: "server",
|
||||||
@@ -104,14 +103,14 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getClientsSites() {
|
getClients() {
|
||||||
this.$q.loading.show();
|
this.$q.loading.show();
|
||||||
axios
|
axios
|
||||||
.get("/clients/loadclients/")
|
.get("/clients/clients/")
|
||||||
.then(r => {
|
.then(r => {
|
||||||
this.tree = r.data;
|
this.client_options = this.formatClientOptions(r.data);
|
||||||
this.client = Object.keys(r.data).sort()[0];
|
this.client = this.client_options[0];
|
||||||
this.loaded = true;
|
this.site = this.sites[0];
|
||||||
this.$q.loading.hide();
|
this.$q.loading.hide();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -121,19 +120,19 @@ export default {
|
|||||||
},
|
},
|
||||||
addAgent() {
|
addAgent() {
|
||||||
const api = axios.defaults.baseURL;
|
const api = axios.defaults.baseURL;
|
||||||
const clientStripped = this.client
|
const clientStripped = this.client.label
|
||||||
.replace(/\s/g, "")
|
.replace(/\s/g, "")
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/([^a-zA-Z0-9]+)/g, "");
|
.replace(/([^a-zA-Z0-9]+)/g, "");
|
||||||
const siteStripped = this.site
|
const siteStripped = this.site.label
|
||||||
.replace(/\s/g, "")
|
.replace(/\s/g, "")
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/([^a-zA-Z0-9]+)/g, "");
|
.replace(/([^a-zA-Z0-9]+)/g, "");
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
installMethod: this.installMethod,
|
installMethod: this.installMethod,
|
||||||
client: this.client,
|
client: this.client.value,
|
||||||
site: this.site,
|
site: this.site.value,
|
||||||
expires: this.expires,
|
expires: this.expires,
|
||||||
agenttype: this.agenttype,
|
agenttype: this.agenttype,
|
||||||
power: this.power ? 1 : 0,
|
power: this.power ? 1 : 0,
|
||||||
@@ -240,17 +239,14 @@ export default {
|
|||||||
},
|
},
|
||||||
showDLMessage() {
|
showDLMessage() {
|
||||||
this.$q.dialog({
|
this.$q.dialog({
|
||||||
message: `Installer for ${this.client}, ${this.site} (${this.agenttype}) will now be downloaded.
|
message: `Installer for ${this.client.label}, ${this.site.label} (${this.agenttype}) will now be downloaded.
|
||||||
You may reuse this installer for ${this.expires} hours before it expires. No command line arguments are needed.`,
|
You may reuse this installer for ${this.expires} hours before it expires. No command line arguments are needed.`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
sites() {
|
sites() {
|
||||||
if (this.tree !== null && this.client !== null) {
|
return !!this.client ? this.formatSiteOptions(this.client.sites) : [];
|
||||||
this.site = this.tree[this.client].sort()[0];
|
|
||||||
return this.tree[this.client].sort();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
installButtonText() {
|
installButtonText() {
|
||||||
let text;
|
let text;
|
||||||
@@ -270,7 +266,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getClientsSites();
|
this.getClients();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-card style="min-width: 400px">
|
|
||||||
<q-card-section class="row">
|
|
||||||
<q-card-actions align="left">
|
|
||||||
<div class="text-h6">Add Client</div>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-space />
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn v-close-popup flat round dense icon="close" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-form @submit.prevent="addClient">
|
|
||||||
<q-card-section>
|
|
||||||
<q-input
|
|
||||||
outlined
|
|
||||||
v-model="client.client"
|
|
||||||
label="Client:"
|
|
||||||
:rules="[ val => val && val.length > 0 || '*Required']"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-input
|
|
||||||
outlined
|
|
||||||
v-model="client.site"
|
|
||||||
label="Default first site:"
|
|
||||||
:rules="[ val => val && val.length > 0 || '*Required']"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn label="Add Client" color="primary" type="submit" />
|
|
||||||
<q-btn label="Cancel" v-close-popup />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from "axios";
|
|
||||||
import mixins from "@/mixins/mixins";
|
|
||||||
export default {
|
|
||||||
name: "AddClient",
|
|
||||||
mixins: [mixins],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
client: {
|
|
||||||
client: null,
|
|
||||||
site: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addClient() {
|
|
||||||
axios
|
|
||||||
.post("/clients/clients/", this.client)
|
|
||||||
.then(r => {
|
|
||||||
this.$emit("close");
|
|
||||||
this.$store.dispatch("loadTree");
|
|
||||||
this.$store.dispatch("getUpdatedSites");
|
|
||||||
this.notifySuccess(r.data);
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
if (e.response.data.client) {
|
|
||||||
this.notifyError(e.response.data.client);
|
|
||||||
} else {
|
|
||||||
this.notifyError(e.response.data.non_field_errors);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-card style="min-width: 400px">
|
|
||||||
<q-card-section class="row">
|
|
||||||
<q-card-actions align="left">
|
|
||||||
<div class="text-h6">Add Site</div>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-space />
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn v-close-popup flat round dense icon="close" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-form @submit.prevent="addSite">
|
|
||||||
<q-card-section>
|
|
||||||
<q-select options-dense outlined v-model="clientName" :options="Object.keys(clients).sort()" />
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-input
|
|
||||||
outlined
|
|
||||||
v-model="siteName"
|
|
||||||
label="Site Name:"
|
|
||||||
:rules="[val => (val && val.length > 0) || 'This field is required']"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn label="Add Site" color="primary" type="submit" />
|
|
||||||
<q-btn label="Cancel" v-close-popup />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from "axios";
|
|
||||||
import mixins from "@/mixins/mixins";
|
|
||||||
export default {
|
|
||||||
name: "AddSite",
|
|
||||||
props: ["clients"],
|
|
||||||
mixins: [mixins],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
clientName: "",
|
|
||||||
siteName: "",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadFirstClient() {
|
|
||||||
axios.get("/clients/listclients/").then(resp => {
|
|
||||||
this.clientName = resp.data.map(k => k.client).sort()[0];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
addSite() {
|
|
||||||
axios
|
|
||||||
.post("/clients/addsite/", {
|
|
||||||
client: this.clientName,
|
|
||||||
site: this.siteName,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.$emit("close");
|
|
||||||
this.$store.dispatch("loadTree");
|
|
||||||
this.notifySuccess(`Site ${this.siteName} was added!`);
|
|
||||||
})
|
|
||||||
.catch(err => this.notifyError(err.response.data.error));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.loadFirstClient();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
189
web/src/components/modals/clients/ClientsForm.vue
Normal file
189
web/src/components/modals/clients/ClientsForm.vue
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<template>
|
||||||
|
<q-card style="min-width: 400px">
|
||||||
|
<q-card-section class="row">
|
||||||
|
<q-card-actions align="left">
|
||||||
|
<div class="text-h6">{{ modalTitle }}</div>
|
||||||
|
</q-card-actions>
|
||||||
|
<q-space />
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn v-close-popup flat round dense icon="close" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<q-form @submit.prevent="submit">
|
||||||
|
<q-card-section v-if="op === 'edit' || op === 'delete'">
|
||||||
|
<q-select
|
||||||
|
:rules="[val => !!val || '*Required']"
|
||||||
|
outlined
|
||||||
|
options-dense
|
||||||
|
label="Select client"
|
||||||
|
v-model="selected_client"
|
||||||
|
:options="client_options"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-if="op === 'add'">
|
||||||
|
<q-input
|
||||||
|
outlined
|
||||||
|
v-model="client.name"
|
||||||
|
label="Client"
|
||||||
|
:rules="[val => (val && val.length > 0) || '*Required']"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-if="op === 'add' || op === 'edit'">
|
||||||
|
<q-input
|
||||||
|
v-if="op === 'add'"
|
||||||
|
:rules="[val => !!val || '*Required']"
|
||||||
|
outlined
|
||||||
|
v-model="client.site"
|
||||||
|
label="Default first site"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-else-if="op === 'edit'"
|
||||||
|
:rules="[val => !!val || '*Required']"
|
||||||
|
outlined
|
||||||
|
v-model="client.name"
|
||||||
|
label="Rename client"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="left">
|
||||||
|
<q-btn
|
||||||
|
:label="capitalize(op)"
|
||||||
|
:color="op === 'delete' ? 'negative' : 'primary'"
|
||||||
|
type="submit"
|
||||||
|
class="full-width"
|
||||||
|
/>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-form>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import mixins from "@/mixins/mixins";
|
||||||
|
export default {
|
||||||
|
name: "ClientsForm",
|
||||||
|
mixins: [mixins],
|
||||||
|
props: {
|
||||||
|
op: !String,
|
||||||
|
clientpk: Number,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
client_options: [],
|
||||||
|
selected_client: {},
|
||||||
|
client: {
|
||||||
|
id: null,
|
||||||
|
name: "",
|
||||||
|
site: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selected_client(newClient, oldClient) {
|
||||||
|
this.client.id = newClient.value;
|
||||||
|
this.client.name = newClient.label;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
modalTitle() {
|
||||||
|
if (this.op === "add") return "Add Client";
|
||||||
|
if (this.op === "edit") return "Edit Client";
|
||||||
|
if (this.op === "delete") return "Delete Client";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
if (this.op === "add") this.addClient();
|
||||||
|
if (this.op === "edit") this.editClient();
|
||||||
|
if (this.op === "delete") this.deleteClient();
|
||||||
|
},
|
||||||
|
getClients() {
|
||||||
|
this.$axios.get("/clients/clients/").then(r => {
|
||||||
|
this.client_options = r.data.map(client => ({ label: client.name, value: client.id }));
|
||||||
|
|
||||||
|
if (this.clientpk !== undefined && this.clientpk !== null) {
|
||||||
|
let client = this.client_options.find(client => client.value === this.clientpk);
|
||||||
|
|
||||||
|
this.selected_client = client;
|
||||||
|
} else {
|
||||||
|
this.selected_client = this.client_options[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addClient() {
|
||||||
|
this.$q.loading.show();
|
||||||
|
const data = {
|
||||||
|
client: this.client.name,
|
||||||
|
site: this.client.site,
|
||||||
|
};
|
||||||
|
this.$axios
|
||||||
|
.post("/clients/clients/", data)
|
||||||
|
.then(r => {
|
||||||
|
this.$emit("close");
|
||||||
|
this.$store.dispatch("loadTree");
|
||||||
|
this.$store.dispatch("getUpdatedSites");
|
||||||
|
this.$q.loading.hide();
|
||||||
|
this.notifySuccess(r.data);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$q.loading.hide();
|
||||||
|
if (e.response.data.client) {
|
||||||
|
this.notifyError(e.response.data.client);
|
||||||
|
} else {
|
||||||
|
this.notifyError(e.response.data.non_field_errors);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editClient() {
|
||||||
|
this.$q.loading.show();
|
||||||
|
const data = {
|
||||||
|
id: this.client.id,
|
||||||
|
name: this.client.name,
|
||||||
|
};
|
||||||
|
this.$axios
|
||||||
|
.put(`/clients/${this.client.id}/client/`, this.client)
|
||||||
|
.then(r => {
|
||||||
|
this.$emit("edited");
|
||||||
|
this.$emit("close");
|
||||||
|
this.$q.loading.hide();
|
||||||
|
this.notifySuccess(r.data);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$q.loading.hide();
|
||||||
|
if (e.response.data.client) {
|
||||||
|
this.notifyError(e.response.data.client);
|
||||||
|
} else {
|
||||||
|
this.notifyError(e.response.data.non_field_errors);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteClient() {
|
||||||
|
this.$q
|
||||||
|
.dialog({
|
||||||
|
title: "Are you sure?",
|
||||||
|
message: `Delete client ${this.client.name}`,
|
||||||
|
cancel: true,
|
||||||
|
ok: { label: "Delete", color: "negative" },
|
||||||
|
})
|
||||||
|
.onOk(() => {
|
||||||
|
this.$q.loading.show();
|
||||||
|
this.$axios
|
||||||
|
.delete(`/clients/${this.client.id}/client/`)
|
||||||
|
.then(r => {
|
||||||
|
this.$q.loading.hide();
|
||||||
|
this.$emit("edited");
|
||||||
|
this.$emit("close");
|
||||||
|
this.notifySuccess(r.data);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$q.loading.hide();
|
||||||
|
this.notifyError(e.response.data, 6000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.op !== "add") this.getClients();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-card style="min-width: 400px">
|
|
||||||
<q-card-section class="row">
|
|
||||||
<q-card-actions align="left">
|
|
||||||
<div class="text-h6">Delete Client</div>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-space />
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn v-close-popup flat round dense icon="close" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-form @submit.prevent="deleteClient">
|
|
||||||
<q-card-section>
|
|
||||||
<q-select
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select client"
|
|
||||||
v-model="client.id"
|
|
||||||
:options="clients"
|
|
||||||
@input="onChange"
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section></q-card-section>
|
|
||||||
<q-card-actions align="left">
|
|
||||||
<q-btn
|
|
||||||
:disable="client.client === null"
|
|
||||||
:label="deleteLabel"
|
|
||||||
class="full-width"
|
|
||||||
color="negative"
|
|
||||||
type="submit"
|
|
||||||
/>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import mixins from "@/mixins/mixins";
|
|
||||||
export default {
|
|
||||||
name: "DeleteClient",
|
|
||||||
mixins: [mixins],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
clients: [],
|
|
||||||
client: {
|
|
||||||
client: null,
|
|
||||||
id: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
deleteLabel() {
|
|
||||||
return this.client.client !== null ? `Delete ${this.client.client}` : "Delete";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getClients() {
|
|
||||||
this.$axios.get("/clients/clients/").then(r => {
|
|
||||||
r.data.forEach(client => {
|
|
||||||
this.clients.push({ label: client.client, value: client.id });
|
|
||||||
});
|
|
||||||
this.clients.sort((a, b) => a.label.localeCompare(b.label));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onChange() {
|
|
||||||
this.client.client = this.clients.find(i => i.value === this.client.id).label;
|
|
||||||
},
|
|
||||||
deleteClient() {
|
|
||||||
this.$q
|
|
||||||
.dialog({
|
|
||||||
title: "Are you sure?",
|
|
||||||
message: `Delete client ${this.client.client}`,
|
|
||||||
cancel: true,
|
|
||||||
ok: { label: "Delete", color: "negative" },
|
|
||||||
})
|
|
||||||
.onOk(() => {
|
|
||||||
this.$axios
|
|
||||||
.delete(`/clients/${this.client.id}/client/`)
|
|
||||||
.then(r => {
|
|
||||||
this.$emit("edited");
|
|
||||||
this.$emit("close");
|
|
||||||
this.notifySuccess(r.data);
|
|
||||||
})
|
|
||||||
.catch(e => this.notifyError(e.response.data, 6000));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getClients();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-card style="min-width: 400px">
|
|
||||||
<q-card-section class="row">
|
|
||||||
<q-card-actions align="left">
|
|
||||||
<div class="text-h6">Delete Site</div>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-space />
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn v-close-popup flat round dense icon="close" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-form @submit.prevent="deleteSite">
|
|
||||||
<q-card-section v-if="tree !== null">
|
|
||||||
<q-select
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select client"
|
|
||||||
v-model="client"
|
|
||||||
:options="Object.keys(tree).sort()"
|
|
||||||
@input="site = sites[0]"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-select
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select site"
|
|
||||||
v-model="site"
|
|
||||||
:options="sites"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="left">
|
|
||||||
<q-btn :disable="site === null" :label="`Delete ${site}`" class="full-width" color="negative" type="submit" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import mixins from "@/mixins/mixins";
|
|
||||||
export default {
|
|
||||||
name: "DeleteSite",
|
|
||||||
mixins: [mixins],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
tree: null,
|
|
||||||
client: null,
|
|
||||||
site: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
sites() {
|
|
||||||
if (this.tree !== null && this.client !== null) {
|
|
||||||
this.site = this.tree[this.client].sort()[0];
|
|
||||||
return this.tree[this.client].sort();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getTree() {
|
|
||||||
this.$axios.get("/clients/loadclients/").then(r => {
|
|
||||||
this.tree = r.data;
|
|
||||||
this.client = Object.keys(r.data).sort()[0];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteSite() {
|
|
||||||
const data = {
|
|
||||||
client: this.client,
|
|
||||||
site: this.site,
|
|
||||||
};
|
|
||||||
this.$q
|
|
||||||
.dialog({
|
|
||||||
title: "Are you sure?",
|
|
||||||
message: `Delete site ${this.site}`,
|
|
||||||
cancel: true,
|
|
||||||
ok: { label: "Delete", color: "negative" },
|
|
||||||
})
|
|
||||||
.onOk(() => {
|
|
||||||
this.$axios
|
|
||||||
.delete("/clients/deletesite/", { data })
|
|
||||||
.then(r => {
|
|
||||||
this.$emit("edited");
|
|
||||||
this.$emit("close");
|
|
||||||
this.notifySuccess(r.data);
|
|
||||||
})
|
|
||||||
.catch(e => this.notifyError(e.response.data, 6000));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getTree();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-card style="min-width: 400px">
|
|
||||||
<q-card-section class="row">
|
|
||||||
<q-card-actions align="left">
|
|
||||||
<div class="text-h6">Edit Clients</div>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-space />
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn v-close-popup flat round dense icon="close" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-form @submit.prevent="editClient">
|
|
||||||
<q-card-section>
|
|
||||||
<q-select
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select client"
|
|
||||||
v-model="client.id"
|
|
||||||
:options="clients"
|
|
||||||
@input="onChange"
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-input :rules="[val => !!val || '*Required']" outlined v-model="client.client" label="Rename client" />
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="left">
|
|
||||||
<q-btn :disable="!nameChanged" label="Save" color="primary" type="submit" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from "axios";
|
|
||||||
import mixins from "@/mixins/mixins";
|
|
||||||
export default {
|
|
||||||
name: "EditClients",
|
|
||||||
mixins: [mixins],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
clients: [],
|
|
||||||
client: {
|
|
||||||
client: null,
|
|
||||||
id: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
nameChanged() {
|
|
||||||
if (this.clients.length !== 0 && this.client.client !== null) {
|
|
||||||
const origName = this.clients.find(i => i.value === this.client.id).label;
|
|
||||||
return this.client.client === origName ? false : true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getClients() {
|
|
||||||
axios.get("/clients/clients/").then(r => {
|
|
||||||
r.data.forEach(client => {
|
|
||||||
this.clients.push({ label: client.client, value: client.id });
|
|
||||||
});
|
|
||||||
this.clients.sort((a, b) => a.label.localeCompare(b.label));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onChange() {
|
|
||||||
this.client.client = this.clients.find(i => i.value === this.client.id).label;
|
|
||||||
},
|
|
||||||
editClient() {
|
|
||||||
axios
|
|
||||||
.patch(`/clients/${this.client.id}/client/`, this.client)
|
|
||||||
.then(r => {
|
|
||||||
this.$emit("edited");
|
|
||||||
this.$emit("close");
|
|
||||||
this.notifySuccess(r.data);
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
if (e.response.data.client) {
|
|
||||||
this.notifyError(e.response.data.client);
|
|
||||||
} else {
|
|
||||||
this.notifyError(e.response.data.non_field_errors);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getClients();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-card style="min-width: 400px">
|
|
||||||
<q-card-section class="row">
|
|
||||||
<q-card-actions align="left">
|
|
||||||
<div class="text-h6">Edit Sites</div>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-space />
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn v-close-popup flat round dense icon="close" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-form @submit.prevent="editSite">
|
|
||||||
<q-card-section v-if="tree !== null">
|
|
||||||
<q-select
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select client"
|
|
||||||
v-model="client"
|
|
||||||
:options="Object.keys(tree).sort()"
|
|
||||||
@input="
|
|
||||||
site = sites[0];
|
|
||||||
newName = sites[0];
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-select
|
|
||||||
:rules="[val => !!val || '*Required']"
|
|
||||||
outlined
|
|
||||||
options-dense
|
|
||||||
label="Select site"
|
|
||||||
v-model="site"
|
|
||||||
:options="sites"
|
|
||||||
@input="newName = site"
|
|
||||||
/>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-input :rules="[val => !!val || '*Required']" outlined v-model="newName" label="Rename site" />
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="left">
|
|
||||||
<q-btn :disable="!nameChanged" label="Save" color="primary" type="submit" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from "axios";
|
|
||||||
import mixins from "@/mixins/mixins";
|
|
||||||
export default {
|
|
||||||
name: "EditSites",
|
|
||||||
mixins: [mixins],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
tree: null,
|
|
||||||
client: null,
|
|
||||||
site: null,
|
|
||||||
newName: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
sites() {
|
|
||||||
if (this.tree !== null && this.client !== null) {
|
|
||||||
this.site = this.tree[this.client].sort()[0];
|
|
||||||
this.newName = this.tree[this.client].sort()[0];
|
|
||||||
return this.tree[this.client].sort();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nameChanged() {
|
|
||||||
if (this.site !== null) {
|
|
||||||
return this.newName === this.site ? false : true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getTree() {
|
|
||||||
axios.get("/clients/loadclients/").then(r => {
|
|
||||||
this.tree = r.data;
|
|
||||||
this.client = Object.keys(r.data).sort()[0];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
editSite() {
|
|
||||||
const data = {
|
|
||||||
client: this.client,
|
|
||||||
site: this.site,
|
|
||||||
name: this.newName,
|
|
||||||
};
|
|
||||||
axios
|
|
||||||
.patch("/clients/editsite/", data)
|
|
||||||
.then(() => {
|
|
||||||
this.$emit("edited");
|
|
||||||
this.$emit("close");
|
|
||||||
this.notifySuccess("Site was edited");
|
|
||||||
})
|
|
||||||
.catch(e => this.notifyError(e.response.data));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getTree();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-card style="min-width: 25vw" v-if="loaded">
|
<q-card style="min-width: 25vw">
|
||||||
<q-card-section class="row">
|
<q-card-section class="row">
|
||||||
<q-card-actions align="left">
|
<q-card-actions align="left">
|
||||||
<div class="text-h6">Create a Deployment</div>
|
<div class="text-h6">Create a Deployment</div>
|
||||||
@@ -11,19 +11,19 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-form @submit.prevent="create">
|
<q-form @submit.prevent="create">
|
||||||
<q-card-section v-if="tree !== null" class="q-gutter-sm">
|
<q-card-section class="q-gutter-sm">
|
||||||
<q-select
|
<q-select
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
options-dense
|
options-dense
|
||||||
label="Client"
|
label="Client"
|
||||||
v-model="client"
|
v-model="client"
|
||||||
:options="Object.keys(tree).sort()"
|
:options="client_options"
|
||||||
@input="site = sites[0]"
|
@input="site = sites[0].value"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-gutter-sm">
|
<q-card-section class="q-gutter-sm">
|
||||||
<q-select dense options-dense outlined label="Site" v-model="site" :options="sites" />
|
<q-select dense options-dense outlined label="Site" v-model="site" :options="sites" map-options emit-value />
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="q-gutter-sm">
|
<div class="q-gutter-sm">
|
||||||
@@ -84,9 +84,8 @@ export default {
|
|||||||
mixins: [mixins],
|
mixins: [mixins],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
client_options: [],
|
||||||
datetime: null,
|
datetime: null,
|
||||||
loaded: false,
|
|
||||||
tree: {},
|
|
||||||
client: null,
|
client: null,
|
||||||
site: null,
|
site: null,
|
||||||
agenttype: "server",
|
agenttype: "server",
|
||||||
@@ -99,7 +98,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
create() {
|
create() {
|
||||||
const data = {
|
const data = {
|
||||||
client: this.client,
|
client: this.client.value,
|
||||||
site: this.site,
|
site: this.site,
|
||||||
expires: this.datetime,
|
expires: this.datetime,
|
||||||
agenttype: this.agenttype,
|
agenttype: this.agenttype,
|
||||||
@@ -122,14 +121,14 @@ export default {
|
|||||||
d.setDate(d.getDate() + 30);
|
d.setDate(d.getDate() + 30);
|
||||||
this.datetime = date.formatDate(d, "YYYY-MM-DD HH:mm");
|
this.datetime = date.formatDate(d, "YYYY-MM-DD HH:mm");
|
||||||
},
|
},
|
||||||
getClientsSites() {
|
getClients() {
|
||||||
this.$q.loading.show();
|
this.$q.loading.show();
|
||||||
this.$axios
|
this.$axios
|
||||||
.get("/clients/loadclients/")
|
.get("/clients/clients/")
|
||||||
.then(r => {
|
.then(r => {
|
||||||
this.tree = r.data;
|
this.client_options = this.formatClientOptions(r.data);
|
||||||
this.client = Object.keys(r.data).sort()[0];
|
this.client = this.client_options[0];
|
||||||
this.loaded = true;
|
this.site = this.formatSiteOptions(this.client.sites)[0].value;
|
||||||
this.$q.loading.hide();
|
this.$q.loading.hide();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -140,15 +139,12 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
sites() {
|
sites() {
|
||||||
if (this.tree !== null && this.client !== null) {
|
return this.client !== null ? this.formatSiteOptions(this.client.sites) : [];
|
||||||
this.site = this.tree[this.client].sort()[0];
|
|
||||||
return this.tree[this.client].sort();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getCurrentDate();
|
this.getCurrentDate();
|
||||||
this.getClientsSites();
|
this.getClients();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
197
web/src/components/modals/clients/SitesForm.vue
Normal file
197
web/src/components/modals/clients/SitesForm.vue
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<template>
|
||||||
|
<q-card style="min-width: 400px">
|
||||||
|
<q-card-section class="row">
|
||||||
|
<q-card-actions align="left">
|
||||||
|
<div class="text-h6">{{ modalTitle }}</div>
|
||||||
|
</q-card-actions>
|
||||||
|
<q-space />
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn v-close-popup flat round dense icon="close" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<q-form @submit.prevent="submit">
|
||||||
|
<q-card-section>
|
||||||
|
<q-select
|
||||||
|
:rules="[val => !!val || '*Required']"
|
||||||
|
outlined
|
||||||
|
options-dense
|
||||||
|
label="Select client"
|
||||||
|
v-model="selected_client"
|
||||||
|
:options="client_options"
|
||||||
|
@input="op === 'edit' || op === 'delete' ? (selected_site = sites[0]) : () => {}"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-if="op === 'edit' || op === 'delete'">
|
||||||
|
<q-select
|
||||||
|
:rules="[val => !!val || '*Required']"
|
||||||
|
outlined
|
||||||
|
options-dense
|
||||||
|
label="Select site"
|
||||||
|
v-model="selected_site"
|
||||||
|
:options="sites"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-if="op === 'add' || op === 'edit'">
|
||||||
|
<q-input
|
||||||
|
v-if="op === 'add'"
|
||||||
|
outlined
|
||||||
|
v-model="site.name"
|
||||||
|
label="Site"
|
||||||
|
:rules="[val => !!val || '*Required']"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-else-if="op === 'edit'"
|
||||||
|
:rules="[val => !!val || '*Required']"
|
||||||
|
outlined
|
||||||
|
v-model="site.name"
|
||||||
|
label="Rename site"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="left">
|
||||||
|
<q-btn
|
||||||
|
:label="capitalize(op)"
|
||||||
|
:color="op === 'delete' ? 'negative' : 'primary'"
|
||||||
|
type="submit"
|
||||||
|
class="full-width"
|
||||||
|
/>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-form>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import mixins from "@/mixins/mixins";
|
||||||
|
export default {
|
||||||
|
name: "SitesForm",
|
||||||
|
mixins: [mixins],
|
||||||
|
props: {
|
||||||
|
op: !String,
|
||||||
|
sitepk: Number,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
client_options: [],
|
||||||
|
selected_client: null,
|
||||||
|
selected_site: null,
|
||||||
|
site: {
|
||||||
|
id: null,
|
||||||
|
name: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selected_site(newSite, oldSite) {
|
||||||
|
this.site.id = newSite.value;
|
||||||
|
this.site.name = newSite.label;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sites() {
|
||||||
|
return !!this.selected_client ? this.formatSiteOptions(this.selected_client.sites) : [];
|
||||||
|
},
|
||||||
|
modalTitle() {
|
||||||
|
if (this.op === "add") return "Add Site";
|
||||||
|
if (this.op === "edit") return "Edit Site";
|
||||||
|
if (this.op === "delete") return "Delete Site";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
if (this.op === "add") this.addSite();
|
||||||
|
if (this.op === "edit") this.editSite();
|
||||||
|
if (this.op === "delete") this.deleteSite();
|
||||||
|
},
|
||||||
|
getClients() {
|
||||||
|
this.$axios.get("/clients/clients/").then(r => {
|
||||||
|
this.client_options = this.formatClientOptions(r.data);
|
||||||
|
|
||||||
|
if (this.sitepk !== undefined && this.sitepk !== null) {
|
||||||
|
this.client_options.forEach(client => {
|
||||||
|
let site = client.sites.find(site => site.id === this.sitepk);
|
||||||
|
|
||||||
|
if (site !== undefined) {
|
||||||
|
this.selected_client = client;
|
||||||
|
this.selected_site = { value: site.id, label: site.name };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.selected_client = this.client_options[0];
|
||||||
|
if (this.op !== "add") this.selected_site = this.sites[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addSite() {
|
||||||
|
this.$q.loading.show();
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
client: this.selected_client.value,
|
||||||
|
name: this.site.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$axios
|
||||||
|
.post("/clients/sites/", data)
|
||||||
|
.then(() => {
|
||||||
|
this.$emit("close");
|
||||||
|
this.$store.dispatch("loadTree");
|
||||||
|
this.$q.loading.hide();
|
||||||
|
this.notifySuccess(`Site ${this.site.name} was added!`);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$q.loading.hide();
|
||||||
|
this.notifyError(e.response.data.non_field_errors);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editSite() {
|
||||||
|
this.$q.loading.show();
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
id: this.site.id,
|
||||||
|
name: this.site.name,
|
||||||
|
client: this.selected_client.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$axios
|
||||||
|
.put(`/clients/${this.site.id}/site/`, data)
|
||||||
|
.then(() => {
|
||||||
|
this.$emit("edited");
|
||||||
|
this.$emit("close");
|
||||||
|
this.$q.loading.hide();
|
||||||
|
this.notifySuccess("Site was edited");
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$q.loading.hide();
|
||||||
|
this.notifyError(e.response.data.non_field_errors);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteSite() {
|
||||||
|
this.$q
|
||||||
|
.dialog({
|
||||||
|
title: "Are you sure?",
|
||||||
|
message: `Delete site ${this.site.name}`,
|
||||||
|
cancel: true,
|
||||||
|
ok: { label: "Delete", color: "negative" },
|
||||||
|
})
|
||||||
|
.onOk(() => {
|
||||||
|
this.$q.loading.show();
|
||||||
|
this.$axios
|
||||||
|
.delete(`/clients/${this.site.id}/site/`)
|
||||||
|
.then(r => {
|
||||||
|
this.$emit("edited");
|
||||||
|
this.$emit("close");
|
||||||
|
this.$q.loading.hide();
|
||||||
|
this.notifySuccess(r.data);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$q.loading.hide();
|
||||||
|
this.notifyError(e.response.data, 6000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getClients();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
outlined
|
outlined
|
||||||
v-model="client"
|
v-model="client"
|
||||||
:options="client_options"
|
:options="client_options"
|
||||||
|
@input="client !== null ? (site = site_options[0]) : () => {}"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
@@ -48,10 +49,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import mixins from "@/mixins/mixins";
|
||||||
import { notifySuccessConfig, notifyErrorConfig } from "@/mixins/mixins";
|
import { notifySuccessConfig, notifyErrorConfig } from "@/mixins/mixins";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ResetPatchPolicy",
|
name: "ResetPatchPolicy",
|
||||||
|
mixins: [mixins],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
client: null,
|
client: null,
|
||||||
@@ -66,11 +69,11 @@ export default {
|
|||||||
let data = {};
|
let data = {};
|
||||||
|
|
||||||
if (this.client !== null) {
|
if (this.client !== null) {
|
||||||
data.client = this.client.label;
|
data.client = this.client.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.site !== null) {
|
if (this.site !== null) {
|
||||||
data.site = this.site.label;
|
data.site = this.site.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store
|
this.$store
|
||||||
@@ -89,7 +92,7 @@ export default {
|
|||||||
this.$store
|
this.$store
|
||||||
.dispatch("loadClients")
|
.dispatch("loadClients")
|
||||||
.then(r => {
|
.then(r => {
|
||||||
this.client_options = r.data.map(client => ({ label: client.client, value: client.id, sites: client.sites }));
|
this.client_options = this.formatClientOptions(r.data);
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this.$q.notify(notifyErrorConfig("There was an error loading the clients!"));
|
this.$q.notify(notifyErrorConfig("There was an error loading the clients!"));
|
||||||
@@ -105,7 +108,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
site_options() {
|
site_options() {
|
||||||
return !!this.client ? this.client.sites.map(site => ({ label: site.site, value: site.id })) : [];
|
return !!this.client ? this.formatSiteOptions(this.client.sites) : [];
|
||||||
},
|
},
|
||||||
buttonText() {
|
buttonText() {
|
||||||
return !this.client ? "Clear Policies for ALL Agents" : "Clear Policies";
|
return !this.client ? "Clear Policies for ALL Agents" : "Clear Policies";
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md q-gutter-sm">
|
|
||||||
<q-dialog
|
|
||||||
:value="toggleLogModal"
|
|
||||||
@hide="hideLogModal"
|
|
||||||
@show="getLog"
|
|
||||||
maximized
|
|
||||||
transition-show="slide-up"
|
|
||||||
transition-hide="slide-down"
|
|
||||||
>
|
|
||||||
<q-card class="bg-grey-10 text-white">
|
<q-card class="bg-grey-10 text-white">
|
||||||
<q-bar>
|
<q-bar>
|
||||||
<q-btn @click="getLog" class="q-mr-sm" dense flat push icon="refresh" label="Refresh" />Debug Log
|
<q-btn @click="getLog" class="q-mr-sm" dense flat push icon="refresh" label="Refresh" />Debug Log
|
||||||
@@ -32,16 +23,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
<q-select
|
<q-select dark dense options-dense outlined v-model="order" :options="orders" label="Order" @input="getLog" />
|
||||||
dark
|
|
||||||
dense
|
|
||||||
options-dense
|
|
||||||
outlined
|
|
||||||
v-model="order"
|
|
||||||
:options="orders"
|
|
||||||
label="Order"
|
|
||||||
@input="getLog"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
@@ -51,17 +33,10 @@
|
|||||||
<q-radio dark v-model="loglevel" color="yellow" val="warning" label="Warning" @input="getLog" />
|
<q-radio dark v-model="loglevel" color="yellow" val="warning" label="Warning" @input="getLog" />
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-card-section>
|
<q-card-section class="scroll" style="max-height: 80vh">
|
||||||
<q-scroll-area
|
|
||||||
:thumb-style="{ right: '4px', borderRadius: '5px', background: 'red', width: '10px', opacity: 1 }"
|
|
||||||
style="height: 60vh"
|
|
||||||
>
|
|
||||||
<pre>{{ logContent }}</pre>
|
<pre>{{ logContent }}</pre>
|
||||||
</q-scroll-area>
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -99,14 +74,9 @@ export default {
|
|||||||
this.agents.unshift("all");
|
this.agents.unshift("all");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
hideLogModal() {
|
|
||||||
this.$store.commit("logs/TOGGLE_LOG_MODAL", false);
|
|
||||||
},
|
},
|
||||||
},
|
created() {
|
||||||
computed: {
|
this.getLog();
|
||||||
...mapState({
|
|
||||||
toggleLogModal: state => state.logs.toggleLogModal,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -1,10 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md q-gutter-sm">
|
<q-card style="width: 900px; max-width: 90vw">
|
||||||
<q-dialog :value="togglePendingActions" @hide="hidePendingActions" @show="getPendingActions">
|
|
||||||
<q-card style="width: 900px; max-width: 90vw;">
|
|
||||||
<q-inner-loading :showing="actionsLoading">
|
|
||||||
<q-spinner size="40px" color="primary" />
|
|
||||||
</q-inner-loading>
|
|
||||||
<q-bar>
|
<q-bar>
|
||||||
<q-btn @click="getPendingActions" class="q-mr-sm" dense flat push icon="refresh" />
|
<q-btn @click="getPendingActions" class="q-mr-sm" dense flat push icon="refresh" />
|
||||||
{{ title }}
|
{{ title }}
|
||||||
@@ -67,9 +62,9 @@
|
|||||||
</q-td>
|
</q-td>
|
||||||
<q-td>{{ props.row.due }}</q-td>
|
<q-td>{{ props.row.due }}</q-td>
|
||||||
<q-td>{{ props.row.description }}</q-td>
|
<q-td>{{ props.row.description }}</q-td>
|
||||||
<q-td v-show="agentpk === null">{{ props.row.hostname }}</q-td>
|
<q-td v-show="!!!agentpk">{{ props.row.hostname }}</q-td>
|
||||||
<q-td v-show="agentpk === null">{{ props.row.client }}</q-td>
|
<q-td v-show="!!!agentpk">{{ props.row.client }}</q-td>
|
||||||
<q-td v-show="agentpk === null">{{ props.row.site }}</q-td>
|
<q-td v-show="!!!agentpk">{{ props.row.site }}</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
@@ -79,23 +74,24 @@
|
|||||||
<q-separator />
|
<q-separator />
|
||||||
<q-card-section></q-card-section>
|
<q-card-section></q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
|
||||||
import mixins from "@/mixins/mixins";
|
import mixins from "@/mixins/mixins";
|
||||||
import { mapGetters } from "vuex";
|
|
||||||
export default {
|
export default {
|
||||||
name: "PendingActions",
|
name: "PendingActions",
|
||||||
mixins: [mixins],
|
mixins: [mixins],
|
||||||
|
props: {
|
||||||
|
agentpk: Number,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
actions: [],
|
||||||
selectedRow: null,
|
selectedRow: null,
|
||||||
showCompleted: false,
|
showCompleted: false,
|
||||||
selectedStatus: null,
|
selectedStatus: null,
|
||||||
actionType: null,
|
actionType: null,
|
||||||
|
hostname: "",
|
||||||
pagination: {
|
pagination: {
|
||||||
rowsPerPage: 0,
|
rowsPerPage: 0,
|
||||||
sortBy: "due",
|
sortBy: "due",
|
||||||
@@ -124,8 +120,18 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getPendingActions() {
|
getPendingActions() {
|
||||||
|
this.$q.loading.show();
|
||||||
this.clearRow();
|
this.clearRow();
|
||||||
this.$store.dispatch("logs/getPendingActions");
|
this.$axios
|
||||||
|
.get(this.url)
|
||||||
|
.then(r => {
|
||||||
|
this.actions = Object.freeze(r.data);
|
||||||
|
if (!!this.agentpk) this.hostname = r.data[0].hostname;
|
||||||
|
this.$q.loading.hide();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$q.loading.hide();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
cancelPendingAction() {
|
cancelPendingAction() {
|
||||||
this.$q
|
this.$q
|
||||||
@@ -137,7 +143,7 @@ export default {
|
|||||||
.onOk(() => {
|
.onOk(() => {
|
||||||
this.$q.loading.show();
|
this.$q.loading.show();
|
||||||
const data = { pk: this.selectedRow };
|
const data = { pk: this.selectedRow };
|
||||||
axios
|
this.$axios
|
||||||
.delete("/logs/cancelpendingaction/", { data: data })
|
.delete("/logs/cancelpendingaction/", { data: data })
|
||||||
.then(r => {
|
.then(r => {
|
||||||
this.$q.loading.hide();
|
this.$q.loading.hide();
|
||||||
@@ -150,11 +156,6 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
hidePendingActions() {
|
|
||||||
this.showCompleted = false;
|
|
||||||
this.selectedStatus = null;
|
|
||||||
this.$store.commit("logs/CLEAR_PENDING_ACTIONS");
|
|
||||||
},
|
|
||||||
rowSelected(pk, status, actiontype) {
|
rowSelected(pk, status, actiontype) {
|
||||||
this.selectedRow = pk;
|
this.selectedRow = pk;
|
||||||
this.selectedStatus = status;
|
this.selectedStatus = status;
|
||||||
@@ -162,6 +163,8 @@ export default {
|
|||||||
},
|
},
|
||||||
clearRow() {
|
clearRow() {
|
||||||
this.selectedRow = null;
|
this.selectedRow = null;
|
||||||
|
this.selectedStatus = null;
|
||||||
|
this.actionType = null;
|
||||||
},
|
},
|
||||||
rowClass(id, status) {
|
rowClass(id, status) {
|
||||||
if (this.selectedRow === id && status !== "completed") {
|
if (this.selectedRow === id && status !== "completed") {
|
||||||
@@ -172,28 +175,27 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
url() {
|
||||||
hostname: "logs/actionsHostname",
|
return !!this.agentpk ? `/logs/${this.agentpk}/pendingactions/` : "/logs/allpendingactions/";
|
||||||
togglePendingActions: "logs/togglePendingActions",
|
},
|
||||||
actions: "logs/allPendingActions",
|
|
||||||
agentpk: "logs/actionsAgentPk",
|
|
||||||
actionsLoading: "logs/pendingActionsLoading",
|
|
||||||
}),
|
|
||||||
filter() {
|
filter() {
|
||||||
return this.showCompleted ? this.actions : this.actions.filter(k => k.status === "pending");
|
return this.showCompleted ? this.actions : this.actions.filter(k => k.status === "pending");
|
||||||
},
|
},
|
||||||
columns() {
|
columns() {
|
||||||
return this.agentpk === null ? this.all_columns : this.agent_columns;
|
return !!this.agentpk ? this.agent_columns : this.all_columns;
|
||||||
},
|
},
|
||||||
visibleColumns() {
|
visibleColumns() {
|
||||||
return this.agentpk === null ? this.all_visibleColumns : this.agent_visibleColumns;
|
return !!this.agentpk ? this.agent_visibleColumns : this.all_visibleColumns;
|
||||||
},
|
},
|
||||||
title() {
|
title() {
|
||||||
return this.agentpk === null ? "All Pending Actions" : `Pending Actions for ${this.hostname}`;
|
return !!this.agentpk ? `Pending Actions for ${this.hostname}` : "All Pending Actions";
|
||||||
},
|
},
|
||||||
completedCount() {
|
completedCount() {
|
||||||
return this.actions.filter(k => k.status === "completed").length;
|
return this.actions.filter(k => k.status === "completed").length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
this.getPendingActions();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
export default {
|
|
||||||
methods: {
|
|
||||||
formatClients(clients) {
|
|
||||||
return clients.map(client => ({
|
|
||||||
label: client.client,
|
|
||||||
value: client.id
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
formatSites(sites) {
|
|
||||||
return sites.map(site => ({
|
|
||||||
label: site.site,
|
|
||||||
value: site.id,
|
|
||||||
client: site.client_name
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -94,6 +94,15 @@ export default {
|
|||||||
let formatted = months[dt.getMonth()] + "-" + appendLeadingZeroes(dt.getDate()) + "-" + appendLeadingZeroes(dt.getFullYear()) + " - " + appendLeadingZeroes(dt.getHours()) + ":" + appendLeadingZeroes(dt.getMinutes())
|
let formatted = months[dt.getMonth()] + "-" + appendLeadingZeroes(dt.getDate()) + "-" + appendLeadingZeroes(dt.getFullYear()) + " - " + appendLeadingZeroes(dt.getHours()) + ":" + appendLeadingZeroes(dt.getMinutes())
|
||||||
|
|
||||||
return includeSeconds ? formatted + ":" + appendLeadingZeroes(dt.getSeconds()) : formatted
|
return includeSeconds ? formatted + ":" + appendLeadingZeroes(dt.getSeconds()) : formatted
|
||||||
|
},
|
||||||
|
formatClientOptions(clients) {
|
||||||
|
return clients.map(client => ({ label: client.name, value: client.id, sites: client.sites }))
|
||||||
|
},
|
||||||
|
formatSiteOptions(sites) {
|
||||||
|
return sites.map(site => ({ label: site.name, value: site.id }))
|
||||||
|
},
|
||||||
|
capitalize(string) {
|
||||||
|
return string[0].toUpperCase() + string.substring(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import Vue from "vue";
|
|||||||
import Vuex from "vuex";
|
import Vuex from "vuex";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Notify } from "quasar";
|
import { Notify } from "quasar";
|
||||||
import logModule from "./logs";
|
|
||||||
import alertsModule from "./alerts";
|
import alertsModule from "./alerts";
|
||||||
import automationModule from "./automation";
|
import automationModule from "./automation";
|
||||||
import adminModule from "./admin.js"
|
import adminModule from "./admin.js"
|
||||||
@@ -12,7 +11,6 @@ Vue.use(Vuex);
|
|||||||
export default function () {
|
export default function () {
|
||||||
const Store = new Vuex.Store({
|
const Store = new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
logs: logModule,
|
|
||||||
automation: automationModule,
|
automation: automationModule,
|
||||||
alerts: alertsModule,
|
alerts: alertsModule,
|
||||||
admin: adminModule
|
admin: adminModule
|
||||||
@@ -210,7 +208,7 @@ export default function () {
|
|||||||
return axios.delete(`/tasks/${pk}/automatedtasks/`);
|
return axios.delete(`/tasks/${pk}/automatedtasks/`);
|
||||||
},
|
},
|
||||||
getUpdatedSites(context) {
|
getUpdatedSites(context) {
|
||||||
axios.get("/clients/loadclients/").then(r => {
|
axios.get("/clients/clients/").then(r => {
|
||||||
context.commit("getUpdatedSites", r.data);
|
context.commit("getUpdatedSites", r.data);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -218,54 +216,56 @@ export default function () {
|
|||||||
return axios.get("/clients/clients/");
|
return axios.get("/clients/clients/");
|
||||||
},
|
},
|
||||||
loadSites(context) {
|
loadSites(context) {
|
||||||
return axios.get("/clients/listsites/");
|
return axios.get("/clients/sites/");
|
||||||
},
|
},
|
||||||
loadAgents(context) {
|
loadAgents(context) {
|
||||||
return axios.get("/agents/listagents/");
|
return axios.get("/agents/listagents/");
|
||||||
},
|
},
|
||||||
loadTree({ commit }) {
|
loadTree({ commit }) {
|
||||||
axios.get("/clients/loadtree/").then(r => {
|
axios.get("/clients/tree/").then(r => {
|
||||||
const input = r.data;
|
|
||||||
if (
|
if (r.data.length === 0) {
|
||||||
Object.entries(input).length === 0 &&
|
|
||||||
input.constructor === Object
|
|
||||||
) {
|
|
||||||
this.$router.push({ name: "InitialSetup" });
|
this.$router.push({ name: "InitialSetup" });
|
||||||
}
|
}
|
||||||
const output = [];
|
|
||||||
for (let prop in input) {
|
let output = [];
|
||||||
let sites_arr = input[prop];
|
for (let client of r.data) {
|
||||||
let child_single = [];
|
|
||||||
for (let i = 0; i < sites_arr.length; i++) {
|
let childSites = [];
|
||||||
child_single.push({
|
for (let site of client.sites) {
|
||||||
label: sites_arr[i].split("|")[0],
|
|
||||||
id: sites_arr[i].split("|")[1],
|
let site_color = "black"
|
||||||
raw: `Site|${sites_arr[i]}`,
|
if (site.maintenance_mode) { site_color = "warning" }
|
||||||
|
else if (site.failing_checks) { site_color = "negative" }
|
||||||
|
|
||||||
|
childSites.push({
|
||||||
|
label: site.name,
|
||||||
|
id: site.id,
|
||||||
|
raw: `Site|${site.id}`,
|
||||||
header: "generic",
|
header: "generic",
|
||||||
icon: "apartment",
|
icon: "apartment",
|
||||||
color: sites_arr[i].split("|")[2]
|
color: site_color
|
||||||
});
|
|
||||||
}
|
|
||||||
// sort alphabetically by site name
|
|
||||||
let alphaSort = child_single.sort((a, b) => a.label.toLowerCase() > b.label.toLowerCase() ? 1 : -1);
|
|
||||||
output.push({
|
|
||||||
label: prop.split("|")[0],
|
|
||||||
id: prop.split("|")[1],
|
|
||||||
raw: `Client|${prop}`,
|
|
||||||
header: "root",
|
|
||||||
icon: "business",
|
|
||||||
color: prop.split("|")[2],
|
|
||||||
children: alphaSort
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// first sort alphabetically, then move failing clients to the top
|
let client_color = "black"
|
||||||
const sortedAlpha = output.sort((a, b) => (a.label.toLowerCase() > b.label.toLowerCase() ? 1 : -1));
|
if (client.maintenance_mode) { client_color = "warning" }
|
||||||
const sortedByFailing = sortedAlpha.sort(a =>
|
else if (client.failing_checks) { client_color = "negative" }
|
||||||
a.color === "negative" ? -1 : 1
|
|
||||||
);
|
output.push({
|
||||||
|
label: client.name,
|
||||||
|
id: client.id,
|
||||||
|
raw: `Client|${client.id}`,
|
||||||
|
header: "root",
|
||||||
|
icon: "business",
|
||||||
|
color: client_color,
|
||||||
|
children: childSites
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// move failing clients to the top
|
||||||
|
const sortedByFailing = output.sort(a => a.color === "negative" ? -1 : 1)
|
||||||
commit("loadTree", sortedByFailing);
|
commit("loadTree", sortedByFailing);
|
||||||
//commit("destroySubTable");
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
checkVer(context) {
|
checkVer(context) {
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
namespaced: true,
|
|
||||||
state: {
|
|
||||||
toggleLogModal: false,
|
|
||||||
togglePendingActions: false,
|
|
||||||
pendingActionsLoading: false,
|
|
||||||
actionsAgentPk: null,
|
|
||||||
actionsHostname: null,
|
|
||||||
allPendingActions: []
|
|
||||||
},
|
|
||||||
getters: {
|
|
||||||
actionsHostname(state) {
|
|
||||||
return state.actionsHostname;
|
|
||||||
},
|
|
||||||
togglePendingActions(state) {
|
|
||||||
return state.togglePendingActions;
|
|
||||||
},
|
|
||||||
allPendingActions(state) {
|
|
||||||
return state.allPendingActions;
|
|
||||||
},
|
|
||||||
actionsAgentPk(state) {
|
|
||||||
return state.actionsAgentPk;
|
|
||||||
},
|
|
||||||
pendingActionsLoading(state) {
|
|
||||||
return state.pendingActionsLoading;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
PENDING_ACTIONS_LOADING(state, visible) {
|
|
||||||
state.pendingActionsLoading = visible;
|
|
||||||
},
|
|
||||||
TOGGLE_LOG_MODAL(state, action) {
|
|
||||||
state.toggleLogModal = action;
|
|
||||||
},
|
|
||||||
TOGGLE_PENDING_ACTIONS(state, { action, agentpk, hostname }) {
|
|
||||||
state.actionsAgentPk = agentpk;
|
|
||||||
state.actionsHostname = hostname;
|
|
||||||
state.togglePendingActions = action;
|
|
||||||
},
|
|
||||||
SET_PENDING_ACTIONS(state, actions) {
|
|
||||||
state.allPendingActions = actions;
|
|
||||||
},
|
|
||||||
CLEAR_PENDING_ACTIONS(state) {
|
|
||||||
state.togglePendingActions = false;
|
|
||||||
state.allPendingActions = [];
|
|
||||||
state.actionsAgentPk = null;
|
|
||||||
state.actionsHostname = null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
getPendingActions({ commit, state }) {
|
|
||||||
commit("PENDING_ACTIONS_LOADING", true);
|
|
||||||
const url = state.actionsAgentPk === null
|
|
||||||
? "/logs/allpendingactions/"
|
|
||||||
: `/logs/${state.actionsAgentPk}/pendingactions/`;
|
|
||||||
|
|
||||||
axios.get(url).then(r => {
|
|
||||||
commit("SET_PENDING_ACTIONS", r.data);
|
|
||||||
commit("PENDING_ACTIONS_LOADING", false);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
loadAuditLogs(context, data) {
|
|
||||||
return axios.patch("/logs/auditlogs/", data)
|
|
||||||
},
|
|
||||||
optionsFilter(context, data) {
|
|
||||||
return axios.post(`logs/auditlogs/optionsfilter/`, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
</q-menu>
|
</q-menu>
|
||||||
</q-chip>
|
</q-chip>
|
||||||
|
|
||||||
<AlertsIcon />
|
<!--<AlertsIcon />-->
|
||||||
|
|
||||||
<q-btn-dropdown flat no-caps stretch :label="user">
|
<q-btn-dropdown flat no-caps stretch :label="user">
|
||||||
<q-list>
|
<q-list>
|
||||||
@@ -105,13 +105,13 @@
|
|||||||
|
|
||||||
<q-menu context-menu>
|
<q-menu context-menu>
|
||||||
<q-list dense style="min-width: 200px">
|
<q-list dense style="min-width: 200px">
|
||||||
<q-item clickable v-close-popup @click="showEditModal(props.node)">
|
<q-item clickable v-close-popup @click="showEditModal(props.node, 'edit')">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon name="edit" />
|
<q-icon name="edit" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>Edit</q-item-section>
|
<q-item-section>Edit</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item clickable v-close-popup @click="showDeleteModal(props.node)">
|
<q-item clickable v-close-popup @click="showDeleteModal(props.node, 'delete')">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon name="delete" />
|
<q-icon name="delete" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
@@ -168,10 +168,111 @@
|
|||||||
<q-tab name="mixed" label="Mixed" />
|
<q-tab name="mixed" label="Mixed" />
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-input v-model="search" label="Search" dense outlined clearable class="q-pr-md q-pb-xs">
|
<q-input
|
||||||
|
autogrow
|
||||||
|
v-model="search"
|
||||||
|
style="width: 450px"
|
||||||
|
label="Search"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
clearable
|
||||||
|
@clear="clearFilter"
|
||||||
|
class="q-pr-md q-pb-xs"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="search" color="primary" />
|
<q-icon name="search" color="primary" />
|
||||||
</template>
|
</template>
|
||||||
|
<template v-slot:after>
|
||||||
|
<q-btn round dense flat icon="filter_alt" :color="isFilteringTable ? 'green' : 'black'">
|
||||||
|
<q-menu>
|
||||||
|
<q-list dense>
|
||||||
|
<q-item-label header>Filter Agent Table</q-item-label>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-checkbox v-model="filterChecksFailing" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Checks Failing</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-checkbox v-model="filterPatchesPending" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Patches Pending</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-checkbox v-model="filterRebootNeeded" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Reboot Needed</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item-label header>Availability</q-item-label>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-radio val="all" v-model="filterAvailability" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Show All Agents</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-radio val="online" v-model="filterAvailability" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Show Online Only</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-radio val="offline" v-model="filterAvailability" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Show Offline Only</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-radio val="offline_30days" v-model="filterAvailability" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Show Offline for over 30 days</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
|
||||||
|
<div class="row no-wrap q-pa-md">
|
||||||
|
<div class="column">
|
||||||
|
<q-btn v-close-popup label="Apply" color="primary" @click="applyFilter" />
|
||||||
|
</div>
|
||||||
|
<q-space />
|
||||||
|
<div class="column">
|
||||||
|
<q-btn label="Clear" @click="clearFilter" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
<AgentTable
|
<AgentTable
|
||||||
@@ -196,21 +297,23 @@
|
|||||||
</q-splitter>
|
</q-splitter>
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
|
|
||||||
<!-- edit client modal -->
|
<!-- client form modal -->
|
||||||
<q-dialog v-model="showEditClientModal">
|
<q-dialog v-model="showClientsFormModal" @hide="closeClientsFormModal">
|
||||||
<EditClients @close="showEditClientModal = false" @edited="refreshEntireSite" />
|
<ClientsForm
|
||||||
|
@close="closeClientsFormModal"
|
||||||
|
:op="clientOp"
|
||||||
|
:clientpk="deleteEditModalPk"
|
||||||
|
@edited="refreshEntireSite"
|
||||||
|
/>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
<!-- edit site modal -->
|
<!-- edit site modal -->
|
||||||
<q-dialog v-model="showEditSiteModal">
|
<q-dialog v-model="showSitesFormModal" @hide="closeClientsFormModal">
|
||||||
<EditSites @close="showEditSiteModal = false" @edited="refreshEntireSite" />
|
<SitesForm
|
||||||
</q-dialog>
|
@close="closeClientsFormModal"
|
||||||
<!-- delete client modal -->
|
:op="clientOp"
|
||||||
<q-dialog v-model="showDeleteClientModal">
|
:sitepk="deleteEditModalPk"
|
||||||
<DeleteClient @close="showDeleteClientModal = false" @edited="refreshEntireSite" />
|
@edited="refreshEntireSite"
|
||||||
</q-dialog>
|
/>
|
||||||
<!-- delete site modal -->
|
|
||||||
<q-dialog v-model="showDeleteSiteModal">
|
|
||||||
<DeleteSite @close="showDeleteSiteModal = false" @edited="refreshEntireSite" />
|
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
<!-- add policy modal -->
|
<!-- add policy modal -->
|
||||||
<q-dialog v-model="showPolicyAddModal">
|
<q-dialog v-model="showPolicyAddModal">
|
||||||
@@ -228,10 +331,8 @@ import AgentTable from "@/components/AgentTable";
|
|||||||
import SubTableTabs from "@/components/SubTableTabs";
|
import SubTableTabs from "@/components/SubTableTabs";
|
||||||
import AlertsIcon from "@/components/AlertsIcon";
|
import AlertsIcon from "@/components/AlertsIcon";
|
||||||
import PolicyAdd from "@/components/automation/modals/PolicyAdd";
|
import PolicyAdd from "@/components/automation/modals/PolicyAdd";
|
||||||
import EditSites from "@/components/modals/clients/EditSites";
|
import ClientsForm from "@/components/modals/clients/ClientsForm";
|
||||||
import EditClients from "@/components/modals/clients/EditClients";
|
import SitesForm from "@/components/modals/clients/SitesForm";
|
||||||
import DeleteClient from "@/components/modals/clients/DeleteClient";
|
|
||||||
import DeleteSite from "@/components/modals/clients/DeleteSite";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -240,18 +341,16 @@ export default {
|
|||||||
SubTableTabs,
|
SubTableTabs,
|
||||||
AlertsIcon,
|
AlertsIcon,
|
||||||
PolicyAdd,
|
PolicyAdd,
|
||||||
EditSites,
|
ClientsForm,
|
||||||
EditClients,
|
SitesForm,
|
||||||
DeleteClient,
|
|
||||||
DeleteSite,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showEditClientModal: false,
|
showClientsFormModal: false,
|
||||||
showEditSiteModal: false,
|
showSitesFormModal: false,
|
||||||
showDeleteClientModal: false,
|
|
||||||
showDeleteSiteModal: false,
|
|
||||||
showPolicyAddModal: false,
|
showPolicyAddModal: false,
|
||||||
|
deleteEditModalPk: null,
|
||||||
|
clientOp: null,
|
||||||
policyAddType: null,
|
policyAddType: null,
|
||||||
policyAddPk: null,
|
policyAddPk: null,
|
||||||
serverCount: 0,
|
serverCount: 0,
|
||||||
@@ -266,7 +365,12 @@ export default {
|
|||||||
siteActive: "",
|
siteActive: "",
|
||||||
frame: [],
|
frame: [],
|
||||||
poll: null,
|
poll: null,
|
||||||
search: null,
|
search: "",
|
||||||
|
filterTextLength: 0,
|
||||||
|
filterAvailability: "all",
|
||||||
|
filterPatchesPending: false,
|
||||||
|
filterChecksFailing: false,
|
||||||
|
filterRebootNeeded: false,
|
||||||
currentTRMMVersion: null,
|
currentTRMMVersion: null,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
@@ -280,18 +384,21 @@ export default {
|
|||||||
{
|
{
|
||||||
name: "checks-status",
|
name: "checks-status",
|
||||||
align: "left",
|
align: "left",
|
||||||
|
field: "checks",
|
||||||
|
sortable: true,
|
||||||
|
sort: (a, b, rowA, rowB) => parseInt(b.failing) - a.failing,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "client",
|
name: "client_name",
|
||||||
label: "Client",
|
label: "Client",
|
||||||
field: "client",
|
field: "client_name",
|
||||||
sortable: true,
|
sortable: true,
|
||||||
align: "left",
|
align: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "site",
|
name: "site_name",
|
||||||
label: "Site",
|
label: "Site",
|
||||||
field: "site",
|
field: "site_name",
|
||||||
sortable: true,
|
sortable: true,
|
||||||
align: "left",
|
align: "left",
|
||||||
},
|
},
|
||||||
@@ -325,17 +432,21 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "patchespending",
|
name: "patchespending",
|
||||||
|
field: "patches_pending",
|
||||||
align: "left",
|
align: "left",
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "agentstatus",
|
name: "agentstatus",
|
||||||
field: "status",
|
field: "status",
|
||||||
align: "left",
|
align: "left",
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "needsreboot",
|
name: "needsreboot",
|
||||||
field: "needs_reboot",
|
field: "needs_reboot",
|
||||||
align: "left",
|
align: "left",
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "lastseen",
|
name: "lastseen",
|
||||||
@@ -356,8 +467,8 @@ export default {
|
|||||||
"smsalert",
|
"smsalert",
|
||||||
"emailalert",
|
"emailalert",
|
||||||
"checks-status",
|
"checks-status",
|
||||||
"client",
|
"client_name",
|
||||||
"site",
|
"site_name",
|
||||||
"hostname",
|
"hostname",
|
||||||
"description",
|
"description",
|
||||||
"user",
|
"user",
|
||||||
@@ -369,6 +480,12 @@ export default {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
search(newVal, oldVal) {
|
||||||
|
if (newVal === "") this.clearFilter();
|
||||||
|
else if (newVal.length < this.filterTextLength) this.clearFilter();
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
refreshEntireSite() {
|
refreshEntireSite() {
|
||||||
this.$store.dispatch("loadTree");
|
this.$store.dispatch("loadTree");
|
||||||
@@ -394,23 +511,17 @@ export default {
|
|||||||
loadFrame(activenode, destroySub = true) {
|
loadFrame(activenode, destroySub = true) {
|
||||||
if (destroySub) this.$store.commit("destroySubTable");
|
if (destroySub) this.$store.commit("destroySubTable");
|
||||||
|
|
||||||
let client, site, url;
|
let url, urlType, id;
|
||||||
try {
|
if (typeof activenode === "string") {
|
||||||
client = this.$refs.tree.meta[activenode].parent.key.split("|")[1];
|
urlType = activenode.split("|")[0];
|
||||||
site = activenode.split("|")[1];
|
id = activenode.split("|")[1];
|
||||||
url = `/agents/bysite/${client}/${site}/`;
|
|
||||||
} catch (e) {
|
if (urlType === "Client") {
|
||||||
try {
|
url = `/agents/byclient/${id}/`;
|
||||||
client = activenode.split("|")[1];
|
} else if (urlType === "Site") {
|
||||||
} catch (e) {
|
url = `/agents/bysite/${id}/`;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (client === null || client === undefined) {
|
|
||||||
url = null;
|
|
||||||
} else {
|
|
||||||
url = `/agents/byclient/${client}/`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
this.$store.commit("AGENT_TABLE_LOADING", true);
|
this.$store.commit("AGENT_TABLE_LOADING", true);
|
||||||
axios.get(url).then(r => {
|
axios.get(url).then(r => {
|
||||||
@@ -418,6 +529,7 @@ export default {
|
|||||||
this.$store.commit("AGENT_TABLE_LOADING", false);
|
this.$store.commit("AGENT_TABLE_LOADING", false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getTree() {
|
getTree() {
|
||||||
this.loadAllClients();
|
this.loadAllClients();
|
||||||
@@ -451,20 +563,30 @@ export default {
|
|||||||
this.showPolicyAddModal = true;
|
this.showPolicyAddModal = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showEditModal(node) {
|
showEditModal(node, op) {
|
||||||
|
this.deleteEditModalPk = node.id;
|
||||||
|
this.clientOp = op;
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
this.showEditClientModal = true;
|
this.showClientsFormModal = true;
|
||||||
} else {
|
} else {
|
||||||
this.showEditSiteModal = true;
|
this.showSitesFormModal = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showDeleteModal(node) {
|
showDeleteModal(node, op) {
|
||||||
|
this.deleteEditModalPk = node.id;
|
||||||
|
this.clientOp = op;
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
this.showDeleteClientModal = true;
|
this.showClientsFormModal = true;
|
||||||
} else {
|
} else {
|
||||||
this.showDeleteSiteModal = true;
|
this.showSitesFormModal = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
closeClientsFormModal() {
|
||||||
|
this.showClientsFormModal = false;
|
||||||
|
this.showSitesFormModal = false;
|
||||||
|
this.deleteEditModalPk = null;
|
||||||
|
this.clientOp = null;
|
||||||
|
},
|
||||||
reload() {
|
reload() {
|
||||||
this.$store.dispatch("reload");
|
this.$store.dispatch("reload");
|
||||||
},
|
},
|
||||||
@@ -510,6 +632,52 @@ export default {
|
|||||||
menuMaintenanceText(node) {
|
menuMaintenanceText(node) {
|
||||||
return node.color === "warning" ? "Disable Maintenance Mode" : "Enable Maintenance Mode";
|
return node.color === "warning" ? "Disable Maintenance Mode" : "Enable Maintenance Mode";
|
||||||
},
|
},
|
||||||
|
clearFilter() {
|
||||||
|
this.filterTextLength = 0;
|
||||||
|
this.filterPatchesPending = false;
|
||||||
|
this.filterRebootNeeded = false;
|
||||||
|
this.filterChecksFailing = false;
|
||||||
|
this.filterAvailability = "all";
|
||||||
|
this.search = "";
|
||||||
|
},
|
||||||
|
applyFilter() {
|
||||||
|
// clear search if availability changes to all
|
||||||
|
if (
|
||||||
|
this.filterAvailability === "all" &&
|
||||||
|
(this.search.includes("is:online") || this.search.includes("is:offline") || this.search.includes("is:expired"))
|
||||||
|
)
|
||||||
|
this.clearFilter();
|
||||||
|
|
||||||
|
// don't apply filter if nothing is being filtered
|
||||||
|
if (!this.isFilteringTable) return;
|
||||||
|
|
||||||
|
let filterText = "";
|
||||||
|
|
||||||
|
if (this.filterPatchesPending) {
|
||||||
|
filterText += "is:patchespending ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filterChecksFailing) {
|
||||||
|
filterText += "is:checksfailing ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filterRebootNeeded) {
|
||||||
|
filterText += "is:rebootneeded ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filterAvailability !== "all") {
|
||||||
|
if (this.filterAvailability === "online") {
|
||||||
|
filterText += "is:online ";
|
||||||
|
} else if (this.filterAvailability === "offline") {
|
||||||
|
filterText += "is:offline ";
|
||||||
|
} else if (this.filterAvailability === "offline_30days") {
|
||||||
|
filterText += "is:expired ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.search = filterText;
|
||||||
|
this.filterTextLength = filterText.length - 1;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
@@ -534,6 +702,14 @@ export default {
|
|||||||
site: this.siteActive,
|
site: this.siteActive,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
isFilteringTable() {
|
||||||
|
return (
|
||||||
|
this.filterPatchesPending ||
|
||||||
|
this.filterChecksFailing ||
|
||||||
|
this.filterRebootNeeded ||
|
||||||
|
this.filterAvailability !== "all"
|
||||||
|
);
|
||||||
|
},
|
||||||
totalAgents() {
|
totalAgents() {
|
||||||
return this.serverCount + this.workstationCount;
|
return this.serverCount + this.workstationCount;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,12 +10,7 @@
|
|||||||
<q-form @submit.prevent="finish">
|
<q-form @submit.prevent="finish">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div>Add Client:</div>
|
<div>Add Client:</div>
|
||||||
<q-input
|
<q-input dense outlined v-model="client.client" :rules="[val => !!val || '*Required']">
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
v-model="client.client"
|
|
||||||
:rules="[ val => !!val || '*Required' ]"
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="business" />
|
<q-icon name="business" />
|
||||||
</template>
|
</template>
|
||||||
@@ -23,12 +18,7 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div>Add Site:</div>
|
<div>Add Site:</div>
|
||||||
<q-input
|
<q-input dense outlined v-model="client.site" :rules="[val => !!val || '*Required']">
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
v-model="client.site"
|
|
||||||
:rules="[ val => !!val || '*Required' ]"
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="apartment" />
|
<q-icon name="apartment" />
|
||||||
</template>
|
</template>
|
||||||
@@ -112,8 +102,8 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this.$q.loading.hide();
|
this.$q.loading.hide();
|
||||||
if (e.response.data.client) {
|
if (e.response.data.name) {
|
||||||
this.notifyError(e.response.data.client);
|
this.notifyError(e.response.data.name);
|
||||||
} else {
|
} else {
|
||||||
this.notifyError(e.response.data.non_field_errors);
|
this.notifyError(e.response.data.non_field_errors);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<q-tab-panels v-model="tab">
|
<q-tab-panels v-model="tab">
|
||||||
<q-tab-panel name="terminal">
|
<q-tab-panel name="terminal">
|
||||||
<iframe
|
<iframe
|
||||||
style="overflow:hidden;height:715px;"
|
style="overflow: hidden; height: 715px"
|
||||||
:src="terminal"
|
:src="terminal"
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
@@ -37,13 +37,7 @@
|
|||||||
<EventLog :pk="pk" />
|
<EventLog :pk="pk" />
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
<q-tab-panel name="filebrowser">
|
<q-tab-panel name="filebrowser">
|
||||||
<iframe
|
<iframe style="overflow: hidden; height: 715px" :src="file" width="100%" height="100%" scrolling="no"></iframe>
|
||||||
style="overflow:hidden;height:715px;"
|
|
||||||
:src="file"
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
scrolling="no"
|
|
||||||
></iframe>
|
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
</q-tab-panels>
|
</q-tab-panels>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,7 +69,7 @@ export default {
|
|||||||
axios.get(`/agents/${this.pk}/meshcentral/`).then(r => {
|
axios.get(`/agents/${this.pk}/meshcentral/`).then(r => {
|
||||||
this.terminal = r.data.terminal;
|
this.terminal = r.data.terminal;
|
||||||
this.file = r.data.file;
|
this.file = r.data.file;
|
||||||
this.title = `${r.data.hostname} | Remote Background`;
|
this.title = `${r.data.hostname} - ${r.data.client} - ${r.data.site} | Remote Background`;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,21 +6,8 @@
|
|||||||
<q-badge :color="statusColor" :label="status" />
|
<q-badge :color="statusColor" :label="status" />
|
||||||
</span>
|
</span>
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-btn
|
<q-btn class="q-mr-md" color="primary" size="sm" label="Restart Connection" icon="refresh" @click="restart" />
|
||||||
class="q-mr-md"
|
<q-btn color="negative" size="sm" label="Recover Connection" icon="fas fa-first-aid" @click="repair" />
|
||||||
color="primary"
|
|
||||||
size="sm"
|
|
||||||
label="Restart Connection"
|
|
||||||
icon="refresh"
|
|
||||||
@click="restart"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
color="negative"
|
|
||||||
size="sm"
|
|
||||||
label="Recover Connection"
|
|
||||||
icon="fas fa-first-aid"
|
|
||||||
@click="repair"
|
|
||||||
/>
|
|
||||||
<q-space />
|
<q-space />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -39,6 +26,7 @@ export default {
|
|||||||
control: "",
|
control: "",
|
||||||
visible: true,
|
visible: true,
|
||||||
status: null,
|
status: null,
|
||||||
|
title: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -60,6 +48,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
meta() {
|
||||||
|
return {
|
||||||
|
title: this.title,
|
||||||
|
};
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
genURL() {
|
genURL() {
|
||||||
this.$q.loading.show();
|
this.$q.loading.show();
|
||||||
@@ -67,6 +60,7 @@ export default {
|
|||||||
this.$axios
|
this.$axios
|
||||||
.get(`/agents/${this.$route.params.pk}/meshcentral/`)
|
.get(`/agents/${this.$route.params.pk}/meshcentral/`)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
|
this.title = `${r.data.hostname} - ${r.data.client} - ${r.data.site} | Take Control`;
|
||||||
this.control = r.data.control;
|
this.control = r.data.control;
|
||||||
this.status = r.data.status;
|
this.status = r.data.status;
|
||||||
this.$q.loading.hide();
|
this.$q.loading.hide();
|
||||||
|
|||||||
Reference in New Issue
Block a user