finish agent recovery

This commit is contained in:
wh1te909 2020-08-09 09:25:57 +00:00
parent 94c8ef5bb1
commit aa6ab33cc2
5 changed files with 163 additions and 35 deletions

View File

@ -21,4 +21,5 @@ urlpatterns = [
path("rebootlater/", views.reboot_later),
path("installagent/", views.install_agent),
path("<int:pk>/ping/", views.ping),
path("recover/", views.recover),
]

View File

@ -4,6 +4,7 @@ import zlib
import json
import base64
import datetime as dt
from packaging import version as pyver
from django.conf import settings
from django.shortcuts import get_object_or_404
@ -17,7 +18,7 @@ from rest_framework.response import Response
from rest_framework import status, generics
from rest_framework.authentication import BasicAuthentication, TokenAuthentication
from .models import Agent
from .models import Agent, RecoveryAction
from winupdate.models import WinUpdatePolicy
from clients.models import Client, Site
from accounts.models import User
@ -298,3 +299,28 @@ def install_agent(request):
resp = {"token": token, "client": client.pk, "site": site.pk}
return Response(resp)
@api_view(["POST"])
def recover(request):
agent = get_object_or_404(Agent, pk=request.data["pk"])
if pyver.parse(agent.version) <= pyver.parse("0.9.5"):
return notify_error("Only available in agent version greater than 0.9.5")
if agent.recoveryactions.filter(last_run=None).exists():
return notify_error(
"A recovery action is currently pending. Please wait for the next agent check-in."
)
if request.data["mode"] == "command" and not request.data["cmd"]:
return notify_error("Command is required")
RecoveryAction(
agent=agent,
mode=request.data["mode"],
command=request.data["cmd"] if request.data["mode"] == "command" else None,
).save()
return Response(f"Recovery will be attempted on the agent's next check-in")

View File

@ -11,7 +11,7 @@ AUTH_USER_MODEL = "accounts.User"
# bump this version everytime vue code is changed
# to alert user they need to manually refresh their browser
APP_VER = "0.0.17"
APP_VER = "0.0.18"
# https://github.com/wh1te909/salt
LATEST_SALT_VER = "1.0.3"

View File

@ -120,7 +120,6 @@
<q-item-section>Send Command</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup @click.stop.prevent="remoteBG(props.row.id)">
<q-item-section side>
<q-icon size="xs" name="fas fa-cogs" />
@ -129,7 +128,6 @@
</q-item>
<!-- patch management -->
<q-separator />
<q-item clickable>
<q-item-section side>
<q-icon size="xs" name="system_update" />
@ -160,7 +158,7 @@
</q-list>
</q-menu>
</q-item>
<q-separator />
<q-item clickable v-close-popup @click.stop.prevent="runChecks(props.row.id)">
<q-item-section side>
<q-icon size="xs" name="fas fa-check-double" />
@ -168,7 +166,6 @@
<q-item-section>Run Checks</q-item-section>
</q-item>
<q-separator />
<q-item clickable>
<q-item-section side>
<q-icon size="xs" name="power_settings_new" />
@ -202,7 +199,6 @@
</q-menu>
</q-item>
<q-separator />
<q-item clickable v-close-popup @click.stop.prevent="showPolicyAdd(props.row.id)">
<q-item-section side>
<q-icon size="xs" name="policy" />
@ -210,7 +206,13 @@
<q-item-section>Edit Policies</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup @click.stop.prevent="showAgentRecovery = true">
<q-item-section side>
<q-icon size="xs" name="fas fa-first-aid" />
</q-item-section>
<q-item-section>Agent Recovery</q-item-section>
</q-item>
<q-item clickable v-close-popup @click.stop.prevent="pingAgent(props.row.id)">
<q-item-section side>
<q-icon size="xs" name="delete" />
@ -328,6 +330,10 @@
<q-dialog v-model="showSendCommand">
<SendCommand @close="showSendCommand = false" :pk="selectedAgentPk" />
</q-dialog>
<!-- agent recovery modal -->
<q-dialog v-model="showAgentRecovery">
<AgentRecovery @close="showAgentRecovery = false" :pk="selectedAgentPk" />
</q-dialog>
</div>
</template>
@ -341,6 +347,7 @@ import RebootLater from "@/components/modals/agents/RebootLater";
import PendingActions from "@/components/modals/logs/PendingActions";
import PolicyAdd from "@/components/automation/modals/PolicyAdd";
import SendCommand from "@/components/modals/agents/SendCommand";
import AgentRecovery from "@/components/modals/agents/AgentRecovery";
export default {
name: "AgentTable",
@ -350,7 +357,8 @@ export default {
RebootLater,
PendingActions,
PolicyAdd,
SendCommand
SendCommand,
AgentRecovery,
},
mixins: [mixins],
data() {
@ -358,13 +366,14 @@ export default {
pagination: {
rowsPerPage: 0,
sortBy: "hostname",
descending: false
descending: false,
},
showSendCommand: false,
showEditAgentModal: false,
showRebootLaterModal: false,
showPolicyAddModal: false,
policyAddPk: null
showAgentRecovery: false,
policyAddPk: null,
};
},
methods: {
@ -378,7 +387,7 @@ export default {
}, 500);
},
runPatchStatusScan(pk, hostname) {
axios.get(`/winupdate/${pk}/runupdatescan/`).then(r => {
axios.get(`/winupdate/${pk}/runupdatescan/`).then((r) => {
this.notifySuccess(`Scan will be run shortly on ${hostname}`);
});
},
@ -386,11 +395,11 @@ export default {
this.$q.loading.show();
this.$axios
.get(`/winupdate/${pk}/installnow/`)
.then(r => {
.then((r) => {
this.$q.loading.hide();
this.notifySuccess(r.data);
})
.catch(e => {
.catch((e) => {
this.$q.loading.hide();
this.notifyError(e.response.data, 5000);
});
@ -413,8 +422,8 @@ export default {
runChecks(pk) {
axios
.get(`/checks/runchecks/${pk}/`)
.then(r => this.notifySuccess(`Checks will now be re-run on ${r.data}`))
.catch(e => this.notifyError("Something went wrong"));
.then((r) => this.notifySuccess(`Checks will now be re-run on ${r.data}`))
.catch((e) => this.notifyError("Something went wrong"));
},
removeAgent(pk, name) {
this.$q
@ -423,18 +432,18 @@ export default {
prompt: {
model: "",
type: "text",
isValid: val => val === name
isValid: (val) => val === name,
},
cancel: true,
ok: { label: "Uninstall", color: "negative" },
persistent: true,
html: true
html: true,
})
.onOk(val => {
.onOk((val) => {
const data = { pk: pk };
this.$axios
.delete("/agents/uninstall/", { data: data })
.then(r => {
.then((r) => {
this.notifySuccess(r.data);
setTimeout(() => {
location.reload();
@ -447,7 +456,7 @@ export default {
this.$q.loading.show();
this.$axios
.get(`/agents/${pk}/ping/`)
.then(r => {
.then((r) => {
this.$q.loading.hide();
if (r.data.status === "offline") {
this.$q
@ -458,7 +467,7 @@ export default {
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
persistent: true,
})
.onOk(() => this.removeAgent(pk, r.data.name))
.onCancel(() => {
@ -470,7 +479,7 @@ export default {
this.notifyError("Something went wrong");
}
})
.catch(e => {
.catch((e) => {
this.$q.loading.hide();
this.notifyError("Something went wrong");
});
@ -481,14 +490,14 @@ export default {
title: "Are you sure?",
message: `Reboot ${hostname} now`,
cancel: true,
persistent: true
persistent: true,
})
.onOk(() => {
const data = { pk: pk, action: "rebootnow" };
axios.post("/agents/poweraction/", data).then(r => {
axios.post("/agents/poweraction/", data).then((r) => {
this.$q.dialog({
title: `Restarting ${hostname}`,
message: `${hostname} will now be restarted`
message: `${hostname} will now be restarted`,
});
});
});
@ -506,19 +515,19 @@ export default {
const data = {
pk: pk,
alertType: category,
action: action
action: action,
};
const alertColor = alert_action ? "positive" : "warning";
axios
.post("/agents/overdueaction/", data)
.then(r => {
.then((r) => {
this.$q.notify({
color: alertColor,
icon: "fas fa-check-circle",
message: `Overdue ${category} alerts ${action} on ${r.data}`
message: `Overdue ${category} alerts ${action} on ${r.data}`,
});
})
.catch(e => this.notifyError(e.response.data.error));
.catch((e) => this.notifyError(e.response.data.error));
},
agentClass(status) {
if (status === "offline") {
@ -537,15 +546,15 @@ export default {
this.$q.loading.show();
this.$axios
.get(`/agents/${pk}/meshcentral/`)
.then(r => {
.then((r) => {
this.$q.loading.hide();
openURL(r.data.webrdp);
})
.catch(e => {
.catch((e) => {
this.$q.loading.hide();
this.notifyError(e.response.data);
});
}
},
},
computed: {
...mapGetters(["selectedAgentPk"]),
@ -554,8 +563,8 @@ export default {
},
agentTableLoading() {
return this.$store.state.agentTableLoading;
}
}
},
},
};
</script>

View File

@ -0,0 +1,92 @@
<template>
<q-card style="min-width: 50vw;">
<q-card-section class="row items-center">
<div class="text-h6">{{ hostname }} Agent Recovery</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-form @submit.prevent="recover">
<q-card-section></q-card-section>
<q-card-section>
<div class="q-gutter-sm">
<q-radio dense v-model="mode" val="mesh" label="Mesh Agent" />
<q-radio dense v-model="mode" val="salt" label="Salt Minion" />
<q-radio dense v-model="mode" val="command" label="Shell Command" />
</div>
</q-card-section>
<q-card-section v-show="mode === 'mesh'">
<p>Fix issues with the Mesh Agent which handles take control, terminal and file browser.</p>
</q-card-section>
<q-card-section v-show="mode === 'salt'">
<p>Fix issues with the salt-minion (do this if getting alot of errors about not being able to contact the agent even if it's online).</p>
</q-card-section>
<q-card-section v-show="mode === 'command'">
<p>Run a shell command on the agent.</p>
<p>You should use the 'Send Command' feature from the agent's context menu for sending shell commands.</p>
<p>Only use this as a last resort if unable to recover the salt-minion.</p>
<q-input
ref="input"
v-model="cmd"
outlined
label="Command"
bottom-slots
stack-label
error-message="*Required"
:error="!isValid"
/>
</q-card-section>
<q-card-actions align="center">
<q-btn label="Recover" color="primary" class="full-width" type="submit" />
</q-card-actions>
</q-form>
</q-card>
</template>
<script>
import mixins from "@/mixins/mixins";
export default {
name: "AgentRecovery",
mixins: [mixins],
props: {
pk: Number,
},
data() {
return {
mode: "mesh",
cmd: null,
};
},
computed: {
hostname() {
return this.$store.state.agentSummary.hostname;
},
isValid() {
if (this.mode === "command") {
if (this.cmd === null || this.cmd === "") {
return false;
}
}
return true;
},
},
methods: {
recover() {
const data = {
pk: this.pk,
cmd: this.cmd,
mode: this.mode,
};
this.$axios
.post("/agents/recover/", data)
.then((r) => {
this.$emit("close");
this.notifySuccess(r.data, 5000);
})
.catch((e) => {
this.notifyError(e.response.data, 5000);
});
},
},
};
</script>