move some tasks to agent, agent removal improvements. requires agent v0.9.1
This commit is contained in:
parent
3d736db8d3
commit
0914e6b28e
|
@ -1,18 +0,0 @@
|
|||
from __future__ import absolute_import
|
||||
import psutil
|
||||
|
||||
|
||||
def fixmesh():
|
||||
mesh = [
|
||||
p.info
|
||||
for p in psutil.process_iter(attrs=["pid", "name"])
|
||||
if "meshagent" in p.info["name"].lower()
|
||||
]
|
||||
if mesh:
|
||||
try:
|
||||
mesh_pid = mesh[0]["pid"]
|
||||
x = psutil.Process(mesh_pid)
|
||||
except Exception as e:
|
||||
pass
|
||||
else:
|
||||
return x.cpu_percent(5)
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 3.0.7 on 2020-06-14 08:50
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agents', '0005_agent_policy'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='agent',
|
||||
name='uninstall_inprogress',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='agent',
|
||||
name='uninstall_pending',
|
||||
),
|
||||
]
|
|
@ -44,8 +44,6 @@ class Agent(models.Model):
|
|||
overdue_email_alert = models.BooleanField(default=False)
|
||||
overdue_text_alert = models.BooleanField(default=False)
|
||||
overdue_time = models.PositiveIntegerField(default=30)
|
||||
uninstall_pending = models.BooleanField(default=False)
|
||||
uninstall_inprogress = models.BooleanField(default=False)
|
||||
check_interval = models.PositiveIntegerField(default=120)
|
||||
needs_reboot = models.BooleanField(default=False)
|
||||
managed_by_wsus = models.BooleanField(default=False)
|
||||
|
@ -195,7 +193,6 @@ class Agent(models.Model):
|
|||
except:
|
||||
return [{"model": "unknown", "size": "unknown", "interfaceType": "unknown"}]
|
||||
|
||||
|
||||
def generate_checks_from_policies(self):
|
||||
# Clear agent checks managed by policy
|
||||
self.agentchecks.filter(managed_by_policy=True).delete()
|
||||
|
@ -368,37 +365,6 @@ class Agent(models.Model):
|
|||
else:
|
||||
return {"ret": True, "msg": salt_resp, "success": False}
|
||||
|
||||
def create_fix_salt_task(self):
|
||||
# https://github.com/wh1te909/winagent/commit/64bc96c131dbdb568e8552c85ed970d06af055df
|
||||
r = self.salt_api_cmd(
|
||||
hostname=self.salt_id,
|
||||
timeout=30,
|
||||
func="task.create_task",
|
||||
arg=[
|
||||
f"name=TacticalRMM_fixsalt",
|
||||
"force=True",
|
||||
"action_type=Execute",
|
||||
'cmd="C:\\Program Files\\TacticalAgent\\tacticalrmm.exe"',
|
||||
'arguments="-m fixsalt"',
|
||||
"trigger_type=Daily",
|
||||
"start_time='10:30'",
|
||||
"repeat_interval='1 hour'",
|
||||
"ac_only=False",
|
||||
"stop_if_on_batteries=False",
|
||||
],
|
||||
)
|
||||
|
||||
try:
|
||||
data = r.json()
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
ret = data["return"][0][self.salt_id]
|
||||
if isinstance(ret, bool) and ret:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class AgentOutage(models.Model):
|
||||
agent = models.ForeignKey(
|
||||
|
|
|
@ -2,6 +2,7 @@ import os
|
|||
import subprocess
|
||||
from loguru import logger
|
||||
from time import sleep
|
||||
import requests
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -44,32 +45,28 @@ def sync_salt_modules_task(pk):
|
|||
|
||||
|
||||
@app.task
|
||||
def uninstall_agent_task(pk, wait=True):
|
||||
agent = Agent.objects.get(pk=pk)
|
||||
salt_id = agent.salt_id
|
||||
agent.uninstall_inprogress = True
|
||||
agent.save(update_fields=["uninstall_inprogress"])
|
||||
logger.info(f"{agent.hostname} uninstall task is running")
|
||||
|
||||
if wait:
|
||||
logger.info(f"{agent.hostname} waiting 90 seconds before uninstalling")
|
||||
sleep(90) # need to give salt time to startup on the minion
|
||||
|
||||
def uninstall_agent_task(salt_id):
|
||||
attempts = 0
|
||||
error = False
|
||||
|
||||
while 1:
|
||||
attempts += 1
|
||||
r = agent.salt_api_cmd(
|
||||
hostname=salt_id, timeout=60, func="win_agent.uninstall_agent"
|
||||
)
|
||||
if r.json()["return"][0][salt_id] != "ok":
|
||||
if attempts >= 10:
|
||||
error = True
|
||||
break
|
||||
else:
|
||||
continue
|
||||
try:
|
||||
r = Agent.salt_api_cmd(
|
||||
hostname=salt_id, timeout=10, func="win_agent.uninstall_agent"
|
||||
)
|
||||
ret = r.json()["return"][0][salt_id]
|
||||
except Exception:
|
||||
attempts += 1
|
||||
else:
|
||||
if ret != "ok":
|
||||
attempts += 1
|
||||
else:
|
||||
attempts = 0
|
||||
|
||||
if attempts >= 10:
|
||||
error = True
|
||||
break
|
||||
elif attempts == 0:
|
||||
break
|
||||
|
||||
if error:
|
||||
|
@ -77,7 +74,25 @@ def uninstall_agent_task(pk, wait=True):
|
|||
else:
|
||||
logger.info(f"{salt_id} was successfully uninstalled")
|
||||
|
||||
return "agent uninstall"
|
||||
try:
|
||||
r = requests.post(
|
||||
f"http://{settings.SALT_HOST}:8123/run",
|
||||
json=[
|
||||
{
|
||||
"client": "wheel",
|
||||
"fun": "key.delete",
|
||||
"match": salt_id,
|
||||
"username": settings.SALT_USERNAME,
|
||||
"password": settings.SALT_PASSWORD,
|
||||
"eauth": "pam",
|
||||
}
|
||||
],
|
||||
timeout=30,
|
||||
)
|
||||
except Exception:
|
||||
logger.error(f"{salt_id} unable to remove salt-key")
|
||||
|
||||
return "ok"
|
||||
|
||||
|
||||
def service_action(hostname, action, service):
|
||||
|
|
|
@ -12,7 +12,7 @@ urlpatterns = [
|
|||
path("<pk>/meshtabs/", views.meshcentral_tabs),
|
||||
path("<pk>/takecontrol/", views.take_control),
|
||||
path("poweraction/", views.power_action),
|
||||
path("uninstallagent/", views.uninstall_agent),
|
||||
path("uninstall/", views.uninstall),
|
||||
path("editagent/", views.edit_agent),
|
||||
path("<pk>/geteventlog/<logtype>/<days>/", views.get_event_log),
|
||||
path("getagentversions/", views.get_agent_versions),
|
||||
|
@ -21,4 +21,5 @@ urlpatterns = [
|
|||
path("<pk>/<pid>/killproc/", views.kill_proc),
|
||||
path("rebootlater/", views.reboot_later),
|
||||
path("installagent/", views.install_agent),
|
||||
path("<int:pk>/ping/", views.ping),
|
||||
]
|
||||
|
|
|
@ -21,6 +21,7 @@ from rest_framework.authentication import BasicAuthentication, TokenAuthenticati
|
|||
from .models import Agent
|
||||
from winupdate.models import WinUpdatePolicy
|
||||
from clients.models import Client, Site
|
||||
from accounts.models import User
|
||||
|
||||
from .serializers import AgentSerializer, AgentHostnameSerializer, AgentTableSerializer
|
||||
from winupdate.serializers import WinUpdatePolicySerializer
|
||||
|
@ -65,40 +66,32 @@ def update_agents(request):
|
|||
return Response("ok")
|
||||
|
||||
|
||||
@api_view(["DELETE"])
|
||||
def uninstall_agent(request):
|
||||
pk = request.data["pk"]
|
||||
@api_view()
|
||||
def ping(request, pk):
|
||||
agent = get_object_or_404(Agent, pk=pk)
|
||||
|
||||
try:
|
||||
resp = agent.salt_api_cmd(hostname=agent.salt_id, timeout=30, func="test.ping")
|
||||
r = agent.salt_api_cmd(hostname=agent.salt_id, timeout=5, func="test.ping")
|
||||
except Exception:
|
||||
agent.uninstall_pending = True
|
||||
agent.save(update_fields=["uninstall_pending"])
|
||||
logger.warning(
|
||||
f"{agent.hostname} is offline. It will be removed on next check-in"
|
||||
)
|
||||
return Response(
|
||||
{"error": "Agent offline. It will be removed on next check-in"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
return Response({"name": agent.hostname, "status": "offline"})
|
||||
|
||||
data = resp.json()
|
||||
if not data["return"][0][agent.salt_id]:
|
||||
agent.uninstall_pending = True
|
||||
agent.save(update_fields=["uninstall_pending"])
|
||||
return Response(
|
||||
{"error": "Agent offline. It will be removed on next check-in"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
data = r.json()["return"][0][agent.salt_id]
|
||||
if isinstance(data, bool) and data:
|
||||
return Response({"name": agent.hostname, "status": "online"})
|
||||
else:
|
||||
return Response({"name": agent.hostname, "status": "offline"})
|
||||
|
||||
logger.info(
|
||||
f"{agent.hostname} has been marked for removal and will be uninstalled shortly"
|
||||
)
|
||||
uninstall_agent_task.delay(pk, wait=False)
|
||||
agent.uninstall_pending = True
|
||||
agent.save(update_fields=["uninstall_pending"])
|
||||
return Response("ok")
|
||||
|
||||
@api_view(["DELETE"])
|
||||
def uninstall(request):
|
||||
agent = get_object_or_404(Agent, pk=request.data["pk"])
|
||||
user = get_object_or_404(User, username=agent.agent_id)
|
||||
salt_id = agent.salt_id
|
||||
name = agent.hostname
|
||||
user.delete()
|
||||
agent.delete()
|
||||
|
||||
uninstall_agent_task.delay(salt_id)
|
||||
return Response(f"{name} will now be uninstalled.")
|
||||
|
||||
|
||||
@api_view(["PATCH"])
|
||||
|
|
|
@ -7,7 +7,6 @@ urlpatterns = [
|
|||
path("add/", views.add),
|
||||
path("token/", views.create_auth_token),
|
||||
path("acceptsaltkey/", views.accept_salt_key),
|
||||
path("deleteagent/", views.delete_agent),
|
||||
path("getmeshexe/", views.get_mesh_exe),
|
||||
path("triggerpatchscan/", views.trigger_patch_scan),
|
||||
path("firstinstall/", views.on_agent_first_install),
|
||||
|
|
|
@ -29,7 +29,6 @@ from autotasks.models import AutomatedTask
|
|||
from winupdate.models import WinUpdate, WinUpdatePolicy
|
||||
|
||||
from agents.tasks import (
|
||||
uninstall_agent_task,
|
||||
sync_salt_modules_task,
|
||||
get_wmi_detail_task,
|
||||
agent_recovery_email_task,
|
||||
|
@ -144,45 +143,6 @@ def accept_salt_key(request):
|
|||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
@authentication_classes((TokenAuthentication,))
|
||||
@permission_classes((IsAuthenticated,))
|
||||
def delete_agent(request):
|
||||
try:
|
||||
user = User.objects.get(username=request.data["agent_id"])
|
||||
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
|
||||
saltid = agent.salt_id
|
||||
user.delete()
|
||||
agent.delete()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return Response(
|
||||
{"error": "something went wrong"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
else:
|
||||
err = "Error removing agent from salt master. Please manually remove."
|
||||
try:
|
||||
resp = requests.post(
|
||||
"http://" + settings.SALT_HOST + ":8123/run",
|
||||
json=[
|
||||
{
|
||||
"client": "wheel",
|
||||
"fun": "key.delete",
|
||||
"match": saltid,
|
||||
"username": settings.SALT_USERNAME,
|
||||
"password": settings.SALT_PASSWORD,
|
||||
"eauth": "pam",
|
||||
}
|
||||
],
|
||||
timeout=30,
|
||||
)
|
||||
except requests.exceptions.Timeout:
|
||||
return Response(err, status=status.HTTP_400_BAD_REQUEST)
|
||||
except requests.exceptions.ConnectionError:
|
||||
return Response(err, status=status.HTTP_410_GONE)
|
||||
return Response("ok")
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
def add(request):
|
||||
data = request.data
|
||||
|
@ -254,14 +214,6 @@ def on_agent_first_install(request):
|
|||
if not data["return"][0][agent.salt_id]:
|
||||
return Response("err", status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
create_salt_fix = agent.create_fix_salt_task()
|
||||
if not create_salt_fix:
|
||||
return Response("err", status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
sleep(1)
|
||||
agent.salt_api_async(
|
||||
hostname=agent.salt_id, func="task.run", arg=["name='TacticalRMM_fixsalt'"],
|
||||
)
|
||||
get_wmi_detail_task.delay(agent.pk)
|
||||
get_installed_software.delay(agent.pk)
|
||||
check_for_updates_task.delay(agent.pk, wait=True)
|
||||
|
@ -274,13 +226,6 @@ def on_agent_first_install(request):
|
|||
def hello(request):
|
||||
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
|
||||
|
||||
if agent.uninstall_pending:
|
||||
if agent.uninstall_inprogress:
|
||||
return Response("uninstallip")
|
||||
else:
|
||||
uninstall_agent_task.delay(agent.pk)
|
||||
return Response("ok")
|
||||
|
||||
serializer = WinAgentSerializer(instance=agent, data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save(last_seen=djangotime.now())
|
||||
|
@ -311,10 +256,10 @@ class CheckRunner(APIView):
|
|||
|
||||
def get(self, request, pk):
|
||||
agent = get_object_or_404(Agent, pk=pk)
|
||||
|
||||
|
||||
if agent.policies_pending:
|
||||
agent.generate_checks_from_policies()
|
||||
|
||||
|
||||
checks = Check.objects.filter(agent__pk=pk, overriden_by_policy=False)
|
||||
|
||||
ret = {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import Vue from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
export default function ({ router, store }) {
|
||||
|
||||
Vue.prototype.$axios = axios;
|
||||
|
||||
axios.defaults.baseURL =
|
||||
process.env.NODE_ENV === "production"
|
||||
? process.env.PROD_API
|
||||
|
@ -33,7 +37,5 @@ export default function ({ router, store }) {
|
|||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
//Vue.prototype.$axios = axios;
|
||||
}
|
||||
|
||||
|
|
|
@ -183,11 +183,7 @@
|
|||
</q-item>
|
||||
|
||||
<q-separator />
|
||||
<q-item
|
||||
clickable
|
||||
v-close-popup
|
||||
@click.stop.prevent="showPolicyAdd(props.row.id)"
|
||||
>
|
||||
<q-item clickable v-close-popup @click.stop.prevent="showPolicyAdd(props.row.id)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="policy" />
|
||||
</q-item-section>
|
||||
|
@ -195,11 +191,7 @@
|
|||
</q-item>
|
||||
|
||||
<q-separator />
|
||||
<q-item
|
||||
clickable
|
||||
v-close-popup
|
||||
@click.stop.prevent="removeAgent(props.row.id, props.row.hostname)"
|
||||
>
|
||||
<q-item clickable v-close-popup @click.stop.prevent="pingAgent(props.row.id)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="delete" />
|
||||
</q-item-section>
|
||||
|
@ -326,11 +318,7 @@
|
|||
<PendingActions />
|
||||
<!-- add policy modal -->
|
||||
<q-dialog v-model="showPolicyAddModal">
|
||||
<PolicyAdd
|
||||
@close="showPolicyAddModal = false"
|
||||
type="agent"
|
||||
:pk="policyAddPk"
|
||||
/>
|
||||
<PolicyAdd @close="showPolicyAddModal = false" type="agent" :pk="policyAddPk" />
|
||||
</q-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -341,14 +329,14 @@ import mixins from "@/mixins/mixins";
|
|||
import EditAgent from "@/components/modals/agents/EditAgent";
|
||||
import RebootLater from "@/components/modals/agents/RebootLater";
|
||||
import PendingActions from "@/components/modals/logs/PendingActions";
|
||||
import PolicyAdd from "@/components/automation/modals/PolicyAdd"
|
||||
import PolicyAdd from "@/components/automation/modals/PolicyAdd";
|
||||
|
||||
export default {
|
||||
name: "AgentTable",
|
||||
props: ["frame", "columns", "tab", "filter", "userName"],
|
||||
components: {
|
||||
EditAgent,
|
||||
RebootLater,
|
||||
components: {
|
||||
EditAgent,
|
||||
RebootLater,
|
||||
PendingActions,
|
||||
PolicyAdd
|
||||
},
|
||||
|
@ -407,34 +395,63 @@ export default {
|
|||
.then(r => this.notifySuccess(`Checks will now be re-run on ${r.data}`))
|
||||
.catch(e => this.notifyError("Something went wrong"));
|
||||
},
|
||||
removeAgent(pk, hostname) {
|
||||
removeAgent(pk, name) {
|
||||
this.$q
|
||||
.dialog({
|
||||
title: "Are you sure?",
|
||||
message: `Delete agent ${hostname}`,
|
||||
title: `Please type <code style="color:red">${name}</code> to confirm deletion.`,
|
||||
prompt: {
|
||||
model: "",
|
||||
type: "text",
|
||||
isValid: val => val === name
|
||||
},
|
||||
cancel: true,
|
||||
persistent: true
|
||||
ok: { label: "Uninstall", color: "negative" },
|
||||
persistent: true,
|
||||
html: true
|
||||
})
|
||||
.onOk(() => {
|
||||
this.$q
|
||||
.dialog({
|
||||
title: `Please type <code style="color:red">${hostname}</code> to confirm`,
|
||||
prompt: { model: "", type: "text" },
|
||||
cancel: true,
|
||||
persistent: true,
|
||||
html: true
|
||||
.onOk(val => {
|
||||
const data = { pk: pk };
|
||||
this.$axios
|
||||
.delete("/agents/uninstall/", { data: data })
|
||||
.then(r => {
|
||||
this.notifySuccess(r.data);
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 2000);
|
||||
})
|
||||
.onOk(hostnameConfirm => {
|
||||
if (hostnameConfirm !== hostname) {
|
||||
this.notifyError("ERROR: Please type the correct hostname");
|
||||
} else {
|
||||
const data = { pk: pk };
|
||||
axios
|
||||
.delete("/agents/uninstallagent/", { data: data })
|
||||
.then(r => this.notifySuccess(`${hostname} will now be uninstalled!`))
|
||||
.catch(e => this.notifyInfo(e.response.data.error));
|
||||
}
|
||||
});
|
||||
.catch(() => this.notifyError("Something went wrong"));
|
||||
});
|
||||
},
|
||||
pingAgent(pk) {
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.get(`/agents/${pk}/ping/`)
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
if (r.data.status === "offline") {
|
||||
this.$q
|
||||
.dialog({
|
||||
title: "Agent offline",
|
||||
message: `${r.data.name} cannot be contacted.
|
||||
Would you like to continue with the uninstall?
|
||||
If so, the agent will need to be manually uninstalled from the computer.`,
|
||||
cancel: { label: "No", color: "negative" },
|
||||
ok: { label: "Yes", color: "positive" },
|
||||
persistent: true
|
||||
})
|
||||
.onOk(() => this.removeAgent(pk, r.data.name))
|
||||
.onCancel(() => {
|
||||
return;
|
||||
});
|
||||
} else if (r.data.status === "online") {
|
||||
this.removeAgent(pk, r.data.name);
|
||||
} else {
|
||||
this.notifyError("Something went wrong");
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
this.notifyError("Something went wrong");
|
||||
});
|
||||
},
|
||||
rebootNow(pk, hostname) {
|
||||
|
|
Loading…
Reference in New Issue