finish agent recovery
This commit is contained in:
parent
94c8ef5bb1
commit
aa6ab33cc2
|
@ -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),
|
||||
]
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue