mirror of
https://github.com/jpros/tacticalrmm-web.git
synced 2026-01-20 12:00:20 +00:00
added agent status page and added #332
This commit is contained in:
@@ -1,7 +1,19 @@
|
||||
import axios from "axios"
|
||||
import { openURL } from "quasar";
|
||||
import { router } from "@/router"
|
||||
|
||||
const baseUrl = "/agents"
|
||||
|
||||
export function runTakeControl(agent_id) {
|
||||
const url = router.resolve(`/takecontrol/${agent_id}`).href;
|
||||
openURL(url, null, { popup: true, scrollbars: false, location: false, status: false, toolbar: false, menubar: false, width: 1600, height: 900 });
|
||||
}
|
||||
|
||||
export function runRemoteBackground(agent_id) {
|
||||
const url = router.resolve(`/remotebackground/${agent_id}`).href;
|
||||
openURL(url, null, { popup: true, scrollbars: false, location: false, status: false, toolbar: false, menubar: false, width: 1280, height: 900 });
|
||||
}
|
||||
|
||||
export async function fetchAgents(params = {}) {
|
||||
try {
|
||||
const { data } = await axios.get(`${baseUrl}/`, { params: params })
|
||||
@@ -18,6 +30,16 @@ export async function fetchAgent(agent_id, params = {}) {
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
|
||||
export async function editAgent(agent_id, payload) {
|
||||
const { data } = await axios.put(`${baseUrl}/${agent_id}/`, payload)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function removeAgent(agent_id) {
|
||||
const { data } = await axios.delete(`${baseUrl}/${agent_id}/`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function fetchAgentHistory(agent_id, params = {}) {
|
||||
try {
|
||||
const { data } = await axios.get(`${baseUrl}/${agent_id}/history/`, { params: params })
|
||||
@@ -101,11 +123,21 @@ export async function scheduleAgentReboot(agent_id, payload) {
|
||||
return data
|
||||
}
|
||||
|
||||
export async function agentRebootNow(agent_id) {
|
||||
const { data } = await axios.post(`${baseUrl}/${agent_id}/reboot/`, payload)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function sendAgentRecoverMesh(agent_id, params = {}) {
|
||||
const { data } = await axios.post(`${baseUrl}/${agent_id}/meshcentral/recover/`, { params: params })
|
||||
return data
|
||||
}
|
||||
|
||||
export async function sendAgentPing(agent_id, params = {}) {
|
||||
const { data } = await axios.get(`${baseUrl}/${agent_id}/ping/`, { params: params })
|
||||
return data
|
||||
}
|
||||
|
||||
// agent notes
|
||||
export async function fetchAgentNotes(agent_id, params = {}) {
|
||||
try {
|
||||
|
||||
@@ -29,4 +29,9 @@ export async function removeCheck(id) {
|
||||
export async function resetCheck(id) {
|
||||
const { data } = await axios.post(`${baseUrl}/${id}/reset/`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function runAgentChecks(agent_id) {
|
||||
const { data } = await axios.post(`${baseUrl}/${agent_id}/run/`)
|
||||
return data
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from "axios"
|
||||
import { openURL } from "quasar";
|
||||
|
||||
const baseUrl = "/core"
|
||||
|
||||
@@ -17,4 +18,18 @@ export async function uploadMeshAgent(payload) {
|
||||
export async function fetchDashboardInfo(params = {}) {
|
||||
const { data } = await axios.get(`${baseUrl}/dashinfo/`, { params: params })
|
||||
return data
|
||||
}
|
||||
|
||||
export async function fetchURLActions(params = {}) {
|
||||
try {
|
||||
const { data } = await axios.get(`${baseUrl}/urlaction/`, { params: params })
|
||||
return data
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
|
||||
export async function runURLAction(payload) {
|
||||
try {
|
||||
const { data } = await axios.patch(`${baseUrl}/urlaction/run/`, payload)
|
||||
openURL(data)
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
dense
|
||||
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
|
||||
class="agents-tbl-sticky"
|
||||
:table-style="{ 'max-height': agentTableHeight }"
|
||||
:rows="frame"
|
||||
:table-style="{ 'max-height': tableHeight }"
|
||||
:rows="agents"
|
||||
:filter="search"
|
||||
:filter-method="filterTable"
|
||||
:columns="columns"
|
||||
@@ -84,189 +84,8 @@
|
||||
@click="agentRowSelected(props.row.agent_id)"
|
||||
@dblclick="rowDoubleClicked(props.row.agent_id)"
|
||||
>
|
||||
<!-- context menu -->
|
||||
<q-menu context-menu>
|
||||
<q-list dense style="min-width: 200px">
|
||||
<!-- edit agent -->
|
||||
<q-item clickable v-close-popup @click="showEditAgent(props.row.agent_id)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-edit" />
|
||||
</q-item-section>
|
||||
<q-item-section>Edit {{ props.row.hostname }}</q-item-section>
|
||||
</q-item>
|
||||
<!-- agent pending actions -->
|
||||
<q-item clickable v-close-popup @click="showPendingActionsModal(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="far fa-clock" />
|
||||
</q-item-section>
|
||||
<q-item-section>Pending Agent Actions</q-item-section>
|
||||
</q-item>
|
||||
<!-- take control -->
|
||||
<q-item clickable v-ripple v-close-popup @click.stop.prevent="takeControl(props.row.agent_id)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-desktop" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section>Take Control</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-ripple @click="getURLActions">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="open_in_new" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run URL Action</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="keyboard_arrow_right" />
|
||||
</q-item-section>
|
||||
<q-menu auto-close anchor="top end" self="top start">
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="action in urlActions"
|
||||
:key="action.id"
|
||||
dense
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="runURLAction(props.row.agent_id, action.id)"
|
||||
>
|
||||
{{ action.name }}
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-ripple v-close-popup @click="showSendCommand(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-terminal" />
|
||||
</q-item-section>
|
||||
<q-item-section>Send Command</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-ripple v-close-popup @click="showRunScript(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-terminal" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run Script</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-ripple @click="getFavoriteScripts">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="star" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run Favorited Script</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="keyboard_arrow_right" />
|
||||
</q-item-section>
|
||||
<q-menu auto-close anchor="top end" self="top start">
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="script in favoriteScripts"
|
||||
:key="script.value"
|
||||
dense
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="showRunScript(props.row, script.value)"
|
||||
>
|
||||
{{ script.label }}
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click.stop.prevent="remoteBG(props.row.agent_id)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-cogs" />
|
||||
</q-item-section>
|
||||
<q-item-section>Remote Background</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- maintenance mode -->
|
||||
<q-item clickable v-close-popup @click="toggleMaintenance(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="construction" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ props.row.maintenance_mode ? "Disable Maintenance Mode" : "Enable Maintenance Mode" }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- patch management -->
|
||||
<q-item clickable>
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="system_update" />
|
||||
</q-item-section>
|
||||
<q-item-section>Patch Management</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="keyboard_arrow_right" />
|
||||
</q-item-section>
|
||||
|
||||
<q-menu anchor="top right" self="top left">
|
||||
<q-list dense style="min-width: 100px">
|
||||
<q-item clickable v-ripple v-close-popup @click.stop.prevent="runPatchStatusScan(props.row)">
|
||||
<q-item-section>Run Patch Status Scan</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-ripple v-close-popup @click.stop.prevent="installPatches(props.row)">
|
||||
<q-item-section>Install Patches Now</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click.stop.prevent="runChecks(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-check-double" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run Checks</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable>
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="power_settings_new" />
|
||||
</q-item-section>
|
||||
<q-item-section>Reboot</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="keyboard_arrow_right" />
|
||||
</q-item-section>
|
||||
|
||||
<q-menu anchor="top right" self="top left">
|
||||
<q-list dense style="min-width: 100px">
|
||||
<!-- reboot now -->
|
||||
<q-item clickable v-ripple v-close-popup @click.stop.prevent="rebootNow(props.row)">
|
||||
<q-item-section>Now</q-item-section>
|
||||
</q-item>
|
||||
<!-- reboot later -->
|
||||
<q-item clickable v-ripple v-close-popup @click.stop.prevent="showRebootLaterModal(props.row)">
|
||||
<q-item-section>Later</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click.stop.prevent="showPolicyAdd(props.row)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="policy" />
|
||||
</q-item-section>
|
||||
<q-item-section>Assign Automation Policy</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click.stop.prevent="showAgentRecovery(props.row)">
|
||||
<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)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="delete" />
|
||||
</q-item-section>
|
||||
<q-item-section>Remove Agent</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator />
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section>Close</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<AgentActionMenu :agent="props.row" />
|
||||
</q-menu>
|
||||
<q-td>
|
||||
<q-checkbox
|
||||
@@ -389,19 +208,20 @@
|
||||
|
||||
<script>
|
||||
import mixins from "@/mixins/mixins";
|
||||
import { mapGetters } from "vuex";
|
||||
import { date, openURL } from "quasar";
|
||||
import { mapState } from "vuex";
|
||||
import { date } from "quasar";
|
||||
import EditAgent from "@/components/modals/agents/EditAgent";
|
||||
import RebootLater from "@/components/modals/agents/RebootLater";
|
||||
import PendingActions from "@/components/logs/PendingActions";
|
||||
import PolicyAdd from "@/components/automation/modals/PolicyAdd";
|
||||
import SendCommand from "@/components/modals/agents/SendCommand";
|
||||
import AgentRecovery from "@/components/modals/agents/AgentRecovery";
|
||||
import RunScript from "@/components/modals/agents/RunScript";
|
||||
import AgentActionMenu from "@/components/agents/AgentActionMenu";
|
||||
import { runURLAction } from "@/api/core";
|
||||
import { runTakeControl, runRemoteBackground } from "@/api/agents";
|
||||
|
||||
export default {
|
||||
name: "AgentTable",
|
||||
props: ["frame", "columns", "userName", "search", "visibleColumns"],
|
||||
components: {
|
||||
AgentActionMenu,
|
||||
},
|
||||
props: ["agents", "columns", "search", "visibleColumns"],
|
||||
inject: ["refreshDashboard"],
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
@@ -411,8 +231,6 @@ export default {
|
||||
sortBy: "hostname",
|
||||
descending: false,
|
||||
},
|
||||
favoriteScripts: [],
|
||||
urlActions: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -479,167 +297,24 @@ export default {
|
||||
this.showEditAgent(agent_id);
|
||||
break;
|
||||
case "takecontrol":
|
||||
this.takeControl(agent_id);
|
||||
runTakeControl(agent_id);
|
||||
break;
|
||||
case "remotebg":
|
||||
this.remoteBG(agent_id);
|
||||
runRemoteBackground(agent_id);
|
||||
break;
|
||||
case "urlaction":
|
||||
this.runURLAction(agent_id, this.agentUrlAction);
|
||||
runURLAction({ agent_id: agent_id, action: this.agentUrlAction });
|
||||
break;
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
getFavoriteScripts() {
|
||||
this.favoriteScripts = [];
|
||||
this.$axios
|
||||
.get("/scripts/", { params: { showCommunityScripts: this.showCommunityScripts } })
|
||||
.then(r => {
|
||||
if (r.data.filter(k => k.favorite === true).length === 0) {
|
||||
this.notifyWarning("You don't have any scripts favorited!");
|
||||
return;
|
||||
}
|
||||
this.favoriteScripts = r.data
|
||||
.filter(k => k.favorite === true)
|
||||
.map(script => ({
|
||||
label: script.name,
|
||||
value: script.id,
|
||||
timeout: script.default_timeout,
|
||||
args: script.args,
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
})
|
||||
.catch(e => {});
|
||||
},
|
||||
runPatchStatusScan(agent) {
|
||||
this.$axios
|
||||
.post(`/winupdate/${agent.agent_id}/scan/`)
|
||||
.then(r => {
|
||||
this.notifySuccess(`Scan will be run shortly on ${agent.hostname}`);
|
||||
})
|
||||
.catch(e => {});
|
||||
},
|
||||
installPatches(agent) {
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.post(`/winupdate/${agent.agent_id}/install/`)
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess(r.data);
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
},
|
||||
showPendingActionsModal(agent) {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: PendingActions,
|
||||
componentProps: {
|
||||
agent: agent,
|
||||
},
|
||||
})
|
||||
.onDismiss(this.refreshDashboard);
|
||||
},
|
||||
takeControl(agent_id) {
|
||||
const url = this.$router.resolve(`/takecontrol/${agent_id}`).href;
|
||||
window.open(url, "", "scrollbars=no,location=no,status=no,toolbar=no,menubar=no,width=1600,height=900");
|
||||
},
|
||||
remoteBG(agent_id) {
|
||||
const url = this.$router.resolve(`/remotebackground/${agent_id}`).href;
|
||||
window.open(url, "", "scrollbars=no,location=no,status=no,toolbar=no,menubar=no,width=1280,height=826");
|
||||
},
|
||||
runChecks(agent) {
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.get(`/checks/${agent.agent_id}/run/`)
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess(r.data);
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
},
|
||||
removeAgent(agent) {
|
||||
this.$q
|
||||
.dialog({
|
||||
title: `Please type <code style="color:red">yes</code> in the box below to confirm deletion.`,
|
||||
prompt: {
|
||||
model: "",
|
||||
type: "text",
|
||||
isValid: val => val === "yes",
|
||||
},
|
||||
cancel: true,
|
||||
ok: { label: "Uninstall", color: "negative" },
|
||||
persistent: true,
|
||||
html: true,
|
||||
})
|
||||
.onOk(val => {
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.delete(`/agents/${agent.agent_id}/`)
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess(r.data);
|
||||
this.refreshDashboard();
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
});
|
||||
},
|
||||
pingAgent(agent) {
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.get(`/agents/${agent.agent_id}/ping/`)
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
if (r.data.status === "offline") {
|
||||
this.$q
|
||||
.dialog({
|
||||
title: "Agent offline",
|
||||
message: `${agent.hostname} 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(agent))
|
||||
.onCancel(() => {
|
||||
return;
|
||||
});
|
||||
} else if (r.data.status === "online") {
|
||||
this.removeAgent(agent);
|
||||
} else {
|
||||
this.notifyError("Something went wrong");
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
},
|
||||
rebootNow(agent) {
|
||||
this.$q
|
||||
.dialog({
|
||||
title: "Are you sure?",
|
||||
message: `Reboot ${agent.hostname} now`,
|
||||
cancel: true,
|
||||
persistent: true,
|
||||
})
|
||||
.onOk(() => {
|
||||
this.$q.loading.show();
|
||||
this.$axios
|
||||
.post(`/agents/${agent.agent_id}/reboot/`)
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess(`${agent.hostname} will now be restarted`);
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
});
|
||||
});
|
||||
this.$q.dialog({
|
||||
component: PendingActions,
|
||||
componentProps: {
|
||||
agent: agent,
|
||||
},
|
||||
});
|
||||
},
|
||||
agentRowSelected(agent_id) {
|
||||
this.$store.commit("setActiveRow", agent_id);
|
||||
@@ -675,34 +350,6 @@ export default {
|
||||
return "agent-normal";
|
||||
}
|
||||
},
|
||||
showPolicyAdd(agent) {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: PolicyAdd,
|
||||
componentProps: {
|
||||
type: "agent",
|
||||
object: agent,
|
||||
},
|
||||
})
|
||||
.onOk(this.refreshDashboard);
|
||||
},
|
||||
toggleMaintenance(agent) {
|
||||
let data = {
|
||||
maintenance_mode: !agent.maintenance_mode,
|
||||
};
|
||||
|
||||
this.$axios
|
||||
.put(`/agents/${agent.agent_id}/`, data)
|
||||
.then(r => {
|
||||
this.notifySuccess(
|
||||
`Maintenance mode was ${agent.maintenance_mode ? "disabled" : "enabled"} on ${agent.hostname}`
|
||||
);
|
||||
this.refreshDashboard();
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e);
|
||||
});
|
||||
},
|
||||
rowSelectedClass(agent_id) {
|
||||
if (agent_id === this.selectedRow) {
|
||||
return this.$q.dark.isActive ? "highlight-dark" : "highlight";
|
||||
@@ -710,57 +357,6 @@ export default {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
getURLActions() {
|
||||
this.$axios
|
||||
.get("/core/urlaction/")
|
||||
.then(r => {
|
||||
if (r.data.length === 0) {
|
||||
this.notifyWarning("No URL Actions configured. Go to Settings > Global Settings > URL Actions");
|
||||
return;
|
||||
}
|
||||
this.urlActions = r.data;
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
runURLAction(agent_id, action) {
|
||||
const data = {
|
||||
agent_id: agent_id,
|
||||
action: action,
|
||||
};
|
||||
this.$axios
|
||||
.patch("/core/urlaction/run/", data)
|
||||
.then(r => {
|
||||
openURL(r.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
showRunScript(agent, script = undefined) {
|
||||
this.$q.dialog({
|
||||
component: RunScript,
|
||||
componentProps: {
|
||||
agent,
|
||||
script,
|
||||
},
|
||||
});
|
||||
},
|
||||
showSendCommand(agent) {
|
||||
this.$q.dialog({
|
||||
component: SendCommand,
|
||||
componentProps: {
|
||||
agent: agent,
|
||||
},
|
||||
});
|
||||
},
|
||||
showRebootLaterModal(agent) {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: RebootLater,
|
||||
componentProps: {
|
||||
agent: agent,
|
||||
},
|
||||
})
|
||||
.onOk(this.refreshDashboard);
|
||||
},
|
||||
showEditAgent(agent_id) {
|
||||
this.$q
|
||||
.dialog({
|
||||
@@ -769,19 +365,14 @@ export default {
|
||||
agent_id: agent_id,
|
||||
},
|
||||
})
|
||||
.onOk(this.refreshDashboard);
|
||||
},
|
||||
showAgentRecovery(agent) {
|
||||
this.$q.dialog({
|
||||
component: AgentRecovery,
|
||||
componentProps: {
|
||||
agent: agent,
|
||||
},
|
||||
});
|
||||
.onOk(() => {
|
||||
this.refreshDashboard();
|
||||
this.$store.commit("setRefreshSummaryTab", true);
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["agentTableHeight", "showCommunityScripts"]),
|
||||
...mapState(["tableHeight"]),
|
||||
agentDblClickAction() {
|
||||
return this.$store.state.agentDblClickAction;
|
||||
},
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
<q-item v-if="alertsCount === 0">No New Alerts</q-item>
|
||||
<q-item v-for="alert in topAlerts" :key="alert.id">
|
||||
<q-item-section>
|
||||
<q-item-label overline>{{ alert.client }} - {{ alert.site }} - {{ alert.hostname }}</q-item-label>
|
||||
<q-item-label overline
|
||||
><router-link :to="`/agents/${alert.agent_id}`"
|
||||
>{{ alert.client }} - {{ alert.site }} - {{ alert.hostname }}</router-link
|
||||
></q-item-label
|
||||
>
|
||||
<q-item-label lines="1">
|
||||
<q-icon size="xs" :class="`text-${alertIconColor(alert.severity)}`" :name="alert.severity"></q-icon>
|
||||
{{ alert.message }}
|
||||
|
||||
@@ -229,6 +229,7 @@ export default {
|
||||
this.selectedTemplate = null;
|
||||
},
|
||||
refresh() {
|
||||
this.$store.dispatch("refreshDashboard");
|
||||
this.getTemplates();
|
||||
this.clearRow();
|
||||
},
|
||||
@@ -246,7 +247,6 @@ export default {
|
||||
.then(r => {
|
||||
this.refresh();
|
||||
this.$q.loading.hide();
|
||||
|
||||
this.notifySuccess(`Alert template ${template.name} was deleted!`);
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -308,6 +308,7 @@ export default {
|
||||
.put(`alerts/templates/${template.id}/`, data)
|
||||
.then(r => {
|
||||
this.notifySuccess(text);
|
||||
this.$store.dispatch("refreshDashboard");
|
||||
})
|
||||
.catch(error => {});
|
||||
},
|
||||
|
||||
@@ -201,7 +201,6 @@ import PermissionsManager from "@/components/accounts/PermissionsManager";
|
||||
|
||||
export default {
|
||||
name: "FileBar",
|
||||
inject: ["refreshDashboard"],
|
||||
components: {
|
||||
UpdateAgents,
|
||||
EditCoreSettings,
|
||||
@@ -254,32 +253,30 @@ export default {
|
||||
});
|
||||
},
|
||||
showAlertsManager() {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: AlertsManager,
|
||||
})
|
||||
.onDismiss(this.refreshDashboard);
|
||||
this.$q.dialog({
|
||||
component: AlertsManager,
|
||||
});
|
||||
},
|
||||
showClientsManager() {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: ClientsManager,
|
||||
})
|
||||
.onDismiss(() => this.refreshDashboard(false));
|
||||
.onDismiss(() => this.$store.dispatch("refreshDashboard", true));
|
||||
},
|
||||
showAddClientModal() {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: ClientsForm,
|
||||
})
|
||||
.onOk(this.refreshDashboard);
|
||||
.onOk(() => this.$store.dispatch("loadTree"));
|
||||
},
|
||||
showAddSiteModal() {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: SitesForm,
|
||||
})
|
||||
.onOk(this.refreshDashboard);
|
||||
.onOk(() => this.$store.dispatch("loadTree"));
|
||||
},
|
||||
showPermissionsManager() {
|
||||
this.$q.dialog({
|
||||
@@ -334,11 +331,9 @@ export default {
|
||||
});
|
||||
},
|
||||
showPendingActions() {
|
||||
this.$q
|
||||
.dialog({
|
||||
component: PendingActions,
|
||||
})
|
||||
.onDismiss(this.refreshDashboard);
|
||||
this.$q.dialog({
|
||||
component: PendingActions,
|
||||
});
|
||||
},
|
||||
showDeployments() {
|
||||
this.$q.dialog({
|
||||
|
||||
@@ -12,49 +12,110 @@
|
||||
narrow-indicator
|
||||
no-caps
|
||||
>
|
||||
<q-tab content-class="min-width" name="summary" icon="fas fa-info-circle" size="xs" label="Summary" />
|
||||
<q-tab content-class="min-width" name="checks" icon="fas fa-check-double" label="Checks" />
|
||||
<q-tab content-class="min-width" name="tasks" icon="fas fa-tasks" label="Tasks" />
|
||||
<q-tab content-class="min-width" name="patches" icon="system_update" label="Patches" />
|
||||
<q-tab content-class="min-width" name="software" icon="fab fa-windows" label="Software" />
|
||||
<q-tab content-class="min-width" name="history" icon="history" label="History" />
|
||||
<q-tab content-class="min-width" name="notes" icon="far fa-sticky-note" label="Notes" />
|
||||
<q-tab content-class="min-width" name="assets" icon="fas fa-barcode" label="Assets" />
|
||||
<q-tab content-class="min-width" name="debug" icon="bug_report" label="Debug" />
|
||||
<q-tab content-class="min-width" name="audit" icon="travel_explore" label="Audit" />
|
||||
<q-tab
|
||||
v-if="activeTabs.includes('summary')"
|
||||
content-class="min-width"
|
||||
name="summary"
|
||||
icon="fas fa-info-circle"
|
||||
size="xs"
|
||||
label="Summary"
|
||||
/>
|
||||
<q-tab
|
||||
v-if="activeTabs.includes('checks')"
|
||||
content-class="min-width"
|
||||
name="checks"
|
||||
icon="fas fa-check-double"
|
||||
label="Checks"
|
||||
/>
|
||||
<q-tab
|
||||
v-if="activeTabs.includes('tasks')"
|
||||
content-class="min-width"
|
||||
name="tasks"
|
||||
icon="fas fa-tasks"
|
||||
label="Tasks"
|
||||
/>
|
||||
<q-tab
|
||||
v-if="activeTabs.includes('patches')"
|
||||
content-class="min-width"
|
||||
name="patches"
|
||||
icon="system_update"
|
||||
label="Patches"
|
||||
/>
|
||||
<q-tab
|
||||
v-if="activeTabs.includes('software')"
|
||||
content-class="min-width"
|
||||
name="software"
|
||||
icon="fab fa-windows"
|
||||
label="Software"
|
||||
/>
|
||||
<q-tab
|
||||
v-if="activeTabs.includes('history')"
|
||||
content-class="min-width"
|
||||
name="history"
|
||||
icon="history"
|
||||
label="History"
|
||||
/>
|
||||
<q-tab
|
||||
v-if="activeTabs.includes('notes')"
|
||||
content-class="min-width"
|
||||
name="notes"
|
||||
icon="far fa-sticky-note"
|
||||
label="Notes"
|
||||
/>
|
||||
<q-tab
|
||||
v-if="activeTabs.includes('assets')"
|
||||
content-class="min-width"
|
||||
name="assets"
|
||||
icon="fas fa-barcode"
|
||||
label="Assets"
|
||||
/>
|
||||
<q-tab
|
||||
v-if="activeTabs.includes('debug')"
|
||||
content-class="min-width"
|
||||
name="debug"
|
||||
icon="bug_report"
|
||||
label="Debug"
|
||||
/>
|
||||
<q-tab
|
||||
v-if="activeTabs.includes('audit')"
|
||||
content-class="min-width"
|
||||
name="audit"
|
||||
icon="travel_explore"
|
||||
label="Audit"
|
||||
/>
|
||||
</q-tabs>
|
||||
<q-separator />
|
||||
</q-header>
|
||||
<q-page-container>
|
||||
<q-tab-panels v-model="subtab" :animated="false">
|
||||
<q-tab-panel name="summary" class="q-pa-none">
|
||||
<q-tab-panel v-if="activeTabs.includes('summary')" name="summary" class="q-pa-none">
|
||||
<SummaryTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="checks" class="q-pa-none">
|
||||
<q-tab-panel v-if="activeTabs.includes('checks')" name="checks" class="q-pa-none">
|
||||
<ChecksTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="tasks" class="q-pa-none">
|
||||
<q-tab-panel v-if="activeTabs.includes('tasks')" name="tasks" class="q-pa-none">
|
||||
<AutomatedTasksTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="patches" class="q-pa-none">
|
||||
<q-tab-panel v-if="activeTabs.includes('patches')" name="patches" class="q-pa-none">
|
||||
<WinUpdateTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="software" class="q-pa-none">
|
||||
<q-tab-panel v-if="activeTabs.includes('software')" name="software" class="q-pa-none">
|
||||
<SoftwareTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="history" class="q-pa-none">
|
||||
<q-tab-panel v-if="activeTabs.includes('history')" name="history" class="q-pa-none">
|
||||
<HistoryTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="notes" class="q-pa-none">
|
||||
<q-tab-panel v-if="activeTabs.includes('notes')" name="notes" class="q-pa-none">
|
||||
<NotesTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="assets" class="q-pa-none">
|
||||
<q-tab-panel v-if="activeTabs.includes('assets')" name="assets" class="q-pa-none">
|
||||
<AssetsTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="debug" class="q-pa-none">
|
||||
<q-tab-panel v-if="activeTabs.includes('debug')" name="debug" class="q-pa-none">
|
||||
<DebugTab />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="audit" class="q-pa-none">
|
||||
<q-tab-panel v-if="activeTabs.includes('audit')" name="audit" class="q-pa-none">
|
||||
<AuditTab />
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
@@ -92,9 +153,15 @@ export default {
|
||||
AssetsTab,
|
||||
NotesTab,
|
||||
},
|
||||
props: {
|
||||
activeTabs: {
|
||||
type: Array,
|
||||
default: ["summary", "checks", "tasks", "patches", "software", "history", "notes", "assets", "audit"],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
subtab: ref("summary"),
|
||||
subtab: ref(props.activeTabs[0]),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
468
src/components/agents/AgentActionMenu.vue
Normal file
468
src/components/agents/AgentActionMenu.vue
Normal file
@@ -0,0 +1,468 @@
|
||||
<template>
|
||||
<q-list dense style="min-width: 200px">
|
||||
<!-- edit agent -->
|
||||
<q-item clickable v-close-popup @click="showEditAgent(agent.agent_id)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-edit" />
|
||||
</q-item-section>
|
||||
<q-item-section>Edit {{ agent.hostname }}</q-item-section>
|
||||
</q-item>
|
||||
<!-- agent pending actions -->
|
||||
<q-item clickable v-close-popup @click="showPendingActionsModal(agent)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="far fa-clock" />
|
||||
</q-item-section>
|
||||
<q-item-section>Pending Agent Actions</q-item-section>
|
||||
</q-item>
|
||||
<!-- take control -->
|
||||
<q-item clickable v-ripple v-close-popup @click="runTakeControl(agent.agent_id)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-desktop" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section>Take Control</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-ripple @click="getURLActions">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="open_in_new" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run URL Action</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="keyboard_arrow_right" />
|
||||
</q-item-section>
|
||||
<q-menu auto-close anchor="top end" self="top start">
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="action in urlActions"
|
||||
:key="action.id"
|
||||
dense
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="runURLAction({ agent_id: agent.agent_id, action: action.id })"
|
||||
>
|
||||
{{ action.name }}
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-ripple v-close-popup @click="showSendCommand(agent)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-terminal" />
|
||||
</q-item-section>
|
||||
<q-item-section>Send Command</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-ripple v-close-popup @click="showRunScript(agent)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-terminal" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run Script</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-ripple @click="getFavoriteScripts">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="star" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run Favorited Script</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="keyboard_arrow_right" />
|
||||
</q-item-section>
|
||||
<q-menu auto-close anchor="top end" self="top start">
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="script in favoriteScripts"
|
||||
:key="script.value"
|
||||
dense
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="showRunScript(agent, script.value)"
|
||||
>
|
||||
{{ script.label }}
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click="runRemoteBackground(agent.agent_id)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-cogs" />
|
||||
</q-item-section>
|
||||
<q-item-section>Remote Background</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- maintenance mode -->
|
||||
<q-item clickable v-close-popup @click="toggleMaintenance(agent)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="construction" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ agent.maintenance_mode ? "Disable Maintenance Mode" : "Enable Maintenance Mode" }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- patch management -->
|
||||
<q-item clickable>
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="system_update" />
|
||||
</q-item-section>
|
||||
<q-item-section>Patch Management</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="keyboard_arrow_right" />
|
||||
</q-item-section>
|
||||
|
||||
<q-menu auto-close anchor="top right" self="top left">
|
||||
<q-list dense style="min-width: 100px">
|
||||
<q-item clickable v-ripple @click="runPatchStatusScan(agent)">
|
||||
<q-item-section>Run Patch Status Scan</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-ripple @click="installPatches(agent)">
|
||||
<q-item-section>Install Patches Now</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click="runChecks(agent)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="fas fa-check-double" />
|
||||
</q-item-section>
|
||||
<q-item-section>Run Checks</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable>
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="power_settings_new" />
|
||||
</q-item-section>
|
||||
<q-item-section>Reboot</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon name="keyboard_arrow_right" />
|
||||
</q-item-section>
|
||||
|
||||
<q-menu auto-close anchor="top right" self="top left">
|
||||
<q-list dense style="min-width: 100px">
|
||||
<!-- reboot now -->
|
||||
<q-item clickable v-ripple @click="rebootNow(agent)">
|
||||
<q-item-section>Now</q-item-section>
|
||||
</q-item>
|
||||
<!-- reboot later -->
|
||||
<q-item clickable v-ripple @click="showRebootLaterModal(agent)">
|
||||
<q-item-section>Later</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click="showPolicyAdd(agent)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="policy" />
|
||||
</q-item-section>
|
||||
<q-item-section>Assign Automation Policy</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click="showAgentRecovery(agent)">
|
||||
<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="pingAgent(agent)">
|
||||
<q-item-section side>
|
||||
<q-icon size="xs" name="delete" />
|
||||
</q-item-section>
|
||||
<q-item-section>Remove Agent</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator />
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section>Close</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition imports
|
||||
import { ref, inject } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useQuasar } from "quasar";
|
||||
import { fetchURLActions, runURLAction } from "@/api/core";
|
||||
import {
|
||||
editAgent,
|
||||
agentRebootNow,
|
||||
sendAgentPing,
|
||||
removeAgent,
|
||||
runRemoteBackground,
|
||||
runTakeControl,
|
||||
} from "@/api/agents";
|
||||
import { runAgentUpdateScan, runAgentUpdateInstall } from "@/api/winupdates";
|
||||
import { runAgentChecks } from "@/api/checks";
|
||||
import { fetchScripts } from "@/api/scripts";
|
||||
import { notifySuccess, notifyWarning, notifyError } from "@/utils/notify";
|
||||
|
||||
// ui imports
|
||||
import PendingActions from "@/components/logs/PendingActions";
|
||||
import AgentRecovery from "@/components/modals/agents/AgentRecovery";
|
||||
import PolicyAdd from "@/components/automation/modals/PolicyAdd";
|
||||
import RebootLater from "@/components/modals/agents/RebootLater";
|
||||
import EditAgent from "@/components/modals/agents/EditAgent";
|
||||
import SendCommand from "@/components/modals/agents/SendCommand";
|
||||
import RunScript from "@/components/modals/agents/RunScript";
|
||||
|
||||
export default {
|
||||
name: "AgentActionMenu",
|
||||
props: {
|
||||
agent: !Object,
|
||||
},
|
||||
setup(props) {
|
||||
const $q = useQuasar();
|
||||
const store = useStore();
|
||||
|
||||
const refreshDashboard = inject("refreshDashboard");
|
||||
|
||||
const urlActions = ref([]);
|
||||
const favoriteScripts = ref([]);
|
||||
const menuLoading = ref(false);
|
||||
|
||||
function showEditAgent(agent_id) {
|
||||
$q.dialog({
|
||||
component: EditAgent,
|
||||
componentProps: {
|
||||
agent_id: agent_id,
|
||||
},
|
||||
}).onOk(refreshDashboard);
|
||||
}
|
||||
|
||||
function showPendingActionsModal(agent) {
|
||||
$q.dialog({
|
||||
component: PendingActions,
|
||||
componentProps: {
|
||||
agent: agent,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function getURLActions() {
|
||||
menuLoading.value = true;
|
||||
try {
|
||||
urlActions.value = await fetchURLActions();
|
||||
|
||||
if (urlActions.value.length === 0) {
|
||||
notifyWarning("No URL Actions configured. Go to Settings > Global Settings > URL Actions");
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
menuLoading.value = true;
|
||||
}
|
||||
|
||||
function showSendCommand(agent) {
|
||||
$q.dialog({
|
||||
component: SendCommand,
|
||||
componentProps: {
|
||||
agent: agent,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function showRunScript(agent, script = undefined) {
|
||||
$q.dialog({
|
||||
component: RunScript,
|
||||
componentProps: {
|
||||
agent,
|
||||
script,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function getFavoriteScripts() {
|
||||
favoriteScripts.value = [];
|
||||
|
||||
menuLoading.value = true;
|
||||
try {
|
||||
const data = await fetchScripts({ showCommunityScripts: store.state.showCommunityScripts });
|
||||
|
||||
const scripts = data.filter(script => !!script.favorite);
|
||||
|
||||
if (scripts.length === 0) {
|
||||
notifyWarning("You don't have any scripts favorited!");
|
||||
return;
|
||||
}
|
||||
|
||||
favoriteScripts.value = scripts
|
||||
.map(script => ({
|
||||
label: script.name,
|
||||
value: script.id,
|
||||
timeout: script.default_timeout,
|
||||
args: script.args,
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleMaintenance(agent) {
|
||||
let data = {
|
||||
maintenance_mode: !agent.maintenance_mode,
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await editAgent(agent.agent_id, data);
|
||||
notifySuccess(`Maintenance mode was ${agent.maintenance_mode ? "disabled" : "enabled"} on ${agent.hostname}`);
|
||||
store.commit("setRefreshSummaryTab", true);
|
||||
refreshDashboard();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function runPatchStatusScan(agent) {
|
||||
try {
|
||||
const result = await runAgentUpdateScan(agent.agent_id);
|
||||
notifySuccess(`Scan will be run shortly on ${agent.hostname}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function installPatches(agent) {
|
||||
try {
|
||||
const data = await runAgentUpdateInstall(agent.agent_id);
|
||||
notifySuccess(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function runChecks(agent) {
|
||||
try {
|
||||
const data = await runAgentChecks(agent.agent_id);
|
||||
notifySuccess(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function showRebootLaterModal(agent) {
|
||||
$q.dialog({
|
||||
component: RebootLater,
|
||||
componentProps: {
|
||||
agent: agent,
|
||||
},
|
||||
}).onOk(refreshDashboard);
|
||||
}
|
||||
|
||||
function rebootNow(agent) {
|
||||
$q.dialog({
|
||||
title: "Are you sure?",
|
||||
message: `Reboot ${agent.hostname} now`,
|
||||
cancel: true,
|
||||
persistent: true,
|
||||
}).onOk(async () => {
|
||||
try {
|
||||
const result = await agentRebootNow(agent.agent_id);
|
||||
notifySuccess(`${agent.hostname} will now be restarted`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showPolicyAdd(agent) {
|
||||
$q.dialog({
|
||||
component: PolicyAdd,
|
||||
componentProps: {
|
||||
type: "agent",
|
||||
object: agent,
|
||||
},
|
||||
}).onOk(refreshDashboard);
|
||||
}
|
||||
|
||||
function showAgentRecovery(agent) {
|
||||
$q.dialog({
|
||||
component: AgentRecovery,
|
||||
componentProps: {
|
||||
agent: agent,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function pingAgent(agent) {
|
||||
try {
|
||||
const data = await sendAgentPing(agent.agent_id);
|
||||
if (data.status === "offline") {
|
||||
$q.dialog({
|
||||
title: "Agent offline",
|
||||
message: `${agent.hostname} 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(() => deleteAgent(agent))
|
||||
.onCancel(() => {
|
||||
return;
|
||||
});
|
||||
} else if (data.status === "online") {
|
||||
deleteAgent(agent);
|
||||
} else {
|
||||
notifyError("Something went wrong");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteAgent(agent) {
|
||||
$q.dialog({
|
||||
title: `Please type <code style="color:red">yes</code> in the box below to confirm deletion.`,
|
||||
prompt: {
|
||||
model: "",
|
||||
type: "text",
|
||||
isValid: val => val === "yes",
|
||||
},
|
||||
cancel: true,
|
||||
ok: { label: "Uninstall", color: "negative" },
|
||||
persistent: true,
|
||||
html: true,
|
||||
}).onOk(async val => {
|
||||
try {
|
||||
const data = await removeAgent(agent.agent_id);
|
||||
notifySuccess(data);
|
||||
refreshDashboard(false /* clearTreeSelected */, true /* clearSubTable */);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// reactive data
|
||||
urlActions,
|
||||
favoriteScripts,
|
||||
|
||||
// methods
|
||||
showEditAgent,
|
||||
showPendingActionsModal,
|
||||
runTakeControl,
|
||||
runRemoteBackground,
|
||||
getURLActions,
|
||||
runURLAction,
|
||||
showSendCommand,
|
||||
showRunScript,
|
||||
getFavoriteScripts,
|
||||
toggleMaintenance,
|
||||
runPatchStatusScan,
|
||||
installPatches,
|
||||
runChecks,
|
||||
showRebootLaterModal,
|
||||
rebootNow,
|
||||
showPolicyAdd,
|
||||
showAgentRecovery,
|
||||
pingAgent,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -399,7 +399,7 @@ export default {
|
||||
const result = await resetCheck(check.id);
|
||||
await getChecks();
|
||||
notifySuccess(result);
|
||||
refreshDashboard();
|
||||
refreshDashboard(false /* clearTreeSelected */, false /* clearSubTable */);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,17 @@
|
||||
<q-circular-progress indeterminate size="50px" color="primary" class="q-ma-md" />
|
||||
</div>
|
||||
<div v-else-if="summary" class="q-pa-sm">
|
||||
<q-btn class="q-mr-sm" dense flat push icon="refresh" @click="refreshSummary" />
|
||||
<span>
|
||||
<q-bar dense style="background-color: transparent">
|
||||
<q-btn dense flat size="md" class="q-mr-sm" icon="refresh" @click="refreshSummary" />
|
||||
<b>{{ summary.hostname }}</b>
|
||||
<span v-if="summary.maintenance_mode"> • <q-badge color="green"> Maintenance Mode </q-badge> </span>
|
||||
• {{ summary.operating_system }} • Agent v{{ summary.version }}
|
||||
</span>
|
||||
<q-separator />
|
||||
<q-space />
|
||||
<q-btn-dropdown dense flat size="md" no-caps label="Actions">
|
||||
<AgentActionMenu :agent="summary" />
|
||||
</q-btn-dropdown>
|
||||
</q-bar>
|
||||
<q-separator class="q-mt-sm" />
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<!-- left -->
|
||||
@@ -120,17 +124,25 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition imports
|
||||
import { ref, computed, watch, onMounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { fetchAgent, refreshAgentWMI } from "@/api/agents";
|
||||
import { notifySuccess } from "@/utils/notify";
|
||||
|
||||
// ui imports
|
||||
import AgentActionMenu from "@/components/agents/AgentActionMenu";
|
||||
|
||||
export default {
|
||||
name: "SummaryTab",
|
||||
components: {
|
||||
AgentActionMenu,
|
||||
},
|
||||
setup(props) {
|
||||
// vuex setup
|
||||
const store = useStore();
|
||||
const selectedAgent = computed(() => store.state.selectedRow);
|
||||
const refreshSummaryTab = computed(() => store.state.refreshSummaryTab);
|
||||
|
||||
// summary tab logic
|
||||
const summary = ref(null);
|
||||
@@ -162,6 +174,7 @@ export default {
|
||||
async function getSummary() {
|
||||
loading.value = true;
|
||||
summary.value = await fetchAgent(selectedAgent.value);
|
||||
store.commit("setRefreshSummaryTab", false);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@@ -183,6 +196,14 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
watch(refreshSummaryTab, (newValue, oldValue) => {
|
||||
if (newValue && selectedAgent.value) {
|
||||
getSummary();
|
||||
}
|
||||
|
||||
store.commit("setRefreshSummaryTab", false);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (selectedAgent.value) getSummary();
|
||||
});
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
<script>
|
||||
// composition imports
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useQuasar, useDialogPluginComponent } from "quasar";
|
||||
import { fetchPendingActions, fetchAgentPendingActions, deletePendingAction } from "@/api/logs";
|
||||
import { getNextAgentUpdateTime } from "@/utils/format";
|
||||
@@ -123,6 +124,9 @@ export default {
|
||||
const { dialogRef, onDialogHide } = useDialogPluginComponent();
|
||||
const $q = useQuasar();
|
||||
|
||||
// vuex store
|
||||
const store = useStore();
|
||||
|
||||
// pending actions logic
|
||||
const actions = ref([]);
|
||||
const showCompleted = ref(false);
|
||||
@@ -178,6 +182,7 @@ export default {
|
||||
const result = await deletePendingAction(action.id);
|
||||
notifySuccess(result);
|
||||
await getPendingActions();
|
||||
store.dispatch("refreshDashboard");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
@@ -226,11 +226,6 @@ export default {
|
||||
editAgent() {
|
||||
delete this.agent.all_timezones;
|
||||
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
|
||||
// this way django will keep the db column as null and inherit from the global setting
|
||||
|
||||
@@ -1,108 +1,110 @@
|
||||
<template>
|
||||
<q-card style="min-width: 85vh">
|
||||
<q-splitter v-model="splitterModel">
|
||||
<template v-slot:before>
|
||||
<q-tabs dense v-model="tab" vertical class="text-primary">
|
||||
<q-tab name="ui" label="User Interface" />
|
||||
</q-tabs>
|
||||
</template>
|
||||
<template v-slot:after>
|
||||
<q-form @submit.prevent="editUserPrefs">
|
||||
<q-card-section class="row items-center">
|
||||
<div class="text-h6">Preferences</div>
|
||||
<q-space />
|
||||
<q-btn icon="close" flat round dense v-close-popup />
|
||||
</q-card-section>
|
||||
<q-tab-panels v-model="tab" animated transition-prev="jump-up" transition-next="jump-up">
|
||||
<!-- UI -->
|
||||
<q-tab-panel name="ui">
|
||||
<div class="text-subtitle2">User Interface</div>
|
||||
<q-separator />
|
||||
<q-card-section class="row">
|
||||
<div class="col-6">Agent double-click action:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-select
|
||||
map-options
|
||||
emit-value
|
||||
outlined
|
||||
dense
|
||||
options-dense
|
||||
v-model="agentDblClickAction"
|
||||
:options="agentDblClickOptions"
|
||||
class="col-4"
|
||||
@update:model-value="url_action = null"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row" v-if="agentDblClickAction === 'urlaction'">
|
||||
<div class="col-6">URL Action:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-select
|
||||
map-options
|
||||
emit-value
|
||||
outlined
|
||||
dense
|
||||
options-dense
|
||||
v-model="url_action"
|
||||
:options="urlActions"
|
||||
class="col-4"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-6">Agent table default tab:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-select
|
||||
map-options
|
||||
emit-value
|
||||
outlined
|
||||
dense
|
||||
options-dense
|
||||
v-model="defaultAgentTblTab"
|
||||
:options="defaultAgentTblTabOptions"
|
||||
class="col-4"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-4">Loading Bar Color:</div>
|
||||
<div class="col-4"></div>
|
||||
<q-select
|
||||
outlined
|
||||
dense
|
||||
options-dense
|
||||
v-model="loading_bar_color"
|
||||
:options="loadingBarColors"
|
||||
class="col-4"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-2">Client Sort:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-select
|
||||
map-options
|
||||
emit-value
|
||||
outlined
|
||||
dense
|
||||
options-dense
|
||||
v-model="clientTreeSort"
|
||||
:options="clientTreeSortOptions"
|
||||
class="col-8"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<q-checkbox
|
||||
v-model="clear_search_when_switching"
|
||||
label="Clear search field when switching client/site"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
<q-dialog ref="dialog" @hide="onHide">
|
||||
<q-card class="q-dialog-plugin" style="min-width: 85vh">
|
||||
<q-splitter v-model="splitterModel">
|
||||
<template v-slot:before>
|
||||
<q-tabs dense v-model="tab" vertical class="text-primary">
|
||||
<q-tab name="ui" label="User Interface" />
|
||||
</q-tabs>
|
||||
</template>
|
||||
<template v-slot:after>
|
||||
<q-form @submit.prevent="editUserPrefs">
|
||||
<q-card-section class="row items-center">
|
||||
<div class="text-h6">Preferences</div>
|
||||
<q-space />
|
||||
<q-btn icon="close" flat round dense v-close-popup />
|
||||
</q-card-section>
|
||||
<q-tab-panels v-model="tab" animated transition-prev="jump-up" transition-next="jump-up">
|
||||
<!-- UI -->
|
||||
<q-tab-panel name="ui">
|
||||
<div class="text-subtitle2">User Interface</div>
|
||||
<q-separator />
|
||||
<q-card-section class="row">
|
||||
<div class="col-6">Agent double-click action:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-select
|
||||
map-options
|
||||
emit-value
|
||||
outlined
|
||||
dense
|
||||
options-dense
|
||||
v-model="agentDblClickAction"
|
||||
:options="agentDblClickOptions"
|
||||
class="col-4"
|
||||
@update:model-value="url_action = null"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row" v-if="agentDblClickAction === 'urlaction'">
|
||||
<div class="col-6">URL Action:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-select
|
||||
map-options
|
||||
emit-value
|
||||
outlined
|
||||
dense
|
||||
options-dense
|
||||
v-model="url_action"
|
||||
:options="urlActions"
|
||||
class="col-4"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-6">Agent table default tab:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-select
|
||||
map-options
|
||||
emit-value
|
||||
outlined
|
||||
dense
|
||||
options-dense
|
||||
v-model="defaultAgentTblTab"
|
||||
:options="defaultAgentTblTabOptions"
|
||||
class="col-4"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-4">Loading Bar Color:</div>
|
||||
<div class="col-4"></div>
|
||||
<q-select
|
||||
outlined
|
||||
dense
|
||||
options-dense
|
||||
v-model="loading_bar_color"
|
||||
:options="loadingBarColors"
|
||||
class="col-4"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<div class="col-2">Client Sort:</div>
|
||||
<div class="col-2"></div>
|
||||
<q-select
|
||||
map-options
|
||||
emit-value
|
||||
outlined
|
||||
dense
|
||||
options-dense
|
||||
v-model="clientTreeSort"
|
||||
:options="clientTreeSortOptions"
|
||||
class="col-8"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-section class="row">
|
||||
<q-checkbox
|
||||
v-model="clear_search_when_switching"
|
||||
label="Clear search field when switching client/site"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
|
||||
<q-card-section class="row items-center">
|
||||
<q-btn label="Save" color="primary" type="submit" />
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
</template>
|
||||
</q-splitter>
|
||||
</q-card>
|
||||
<q-card-section class="row items-center">
|
||||
<q-btn label="Save" color="primary" type="submit" />
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
</template>
|
||||
</q-splitter>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -111,7 +113,7 @@ import mixins from "@/mixins/mixins";
|
||||
|
||||
export default {
|
||||
name: "UserPreferences",
|
||||
emits: ["edit", "close"],
|
||||
emits: ["hide", "ok", "cancel"],
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {
|
||||
@@ -219,12 +221,24 @@ export default {
|
||||
.patch("/accounts/users/ui/", data)
|
||||
.then(r => {
|
||||
this.notifySuccess("Preferences were saved!");
|
||||
this.$emit("edit");
|
||||
this.$store.dispatch("loadTree");
|
||||
this.$emit("close");
|
||||
this.onOk();
|
||||
})
|
||||
.catch(e => {});
|
||||
},
|
||||
show() {
|
||||
this.$refs.dialog.show();
|
||||
},
|
||||
hide() {
|
||||
this.$refs.dialog.hide();
|
||||
},
|
||||
onHide() {
|
||||
this.$emit("hide");
|
||||
},
|
||||
onOk() {
|
||||
this.$emit("ok");
|
||||
this.hide();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getUserPrefs();
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title><%= productName %></title>
|
||||
<title>
|
||||
<%= productName %>
|
||||
</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="robots" content="noindex">
|
||||
<meta name="description" content="<%= productDescription %>">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
|
||||
233
src/layouts/MainLayout.vue
Normal file
233
src/layouts/MainLayout.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<q-layout view="hHh lpR fFf">
|
||||
<q-header elevated class="bg-grey-9 text-white">
|
||||
<q-banner v-if="needRefresh" inline-actions class="bg-red text-white text-center">
|
||||
You are viewing an outdated version of this page.
|
||||
<q-btn color="dark" icon="refresh" label="Refresh" @click="$store.dispatch('reload')" />
|
||||
</q-banner>
|
||||
<q-toolbar>
|
||||
<q-btn
|
||||
dense
|
||||
flat
|
||||
@click="$store.dispatch('refreshDashboard')"
|
||||
icon="refresh"
|
||||
v-if="$route.name === 'Dashboard'"
|
||||
/>
|
||||
<q-btn v-else dense flat @click="$router.push({ name: 'Dashboard' })" icon="dashboard">
|
||||
<q-tooltip>Back to Dashboard</q-tooltip>
|
||||
</q-btn>
|
||||
<q-toolbar-title>
|
||||
Tactical RMM<span class="text-overline q-ml-sm">v{{ currentTRMMVersion }}</span>
|
||||
<span class="text-overline q-ml-md" v-if="latestTRMMVersion && currentTRMMVersion !== latestTRMMVersion"
|
||||
><q-badge color="warning"
|
||||
><a :href="latestReleaseURL" target="_blank">v{{ latestTRMMVersion }} available</a></q-badge
|
||||
></span
|
||||
>
|
||||
</q-toolbar-title>
|
||||
|
||||
<!-- temp dark mode toggle -->
|
||||
<q-toggle v-model="darkMode" class="q-mr-sm" checked-icon="nights_stay" unchecked-icon="wb_sunny" />
|
||||
|
||||
<!-- Devices Chip -->
|
||||
<q-chip class="cursor-pointer">
|
||||
<q-avatar size="md" icon="devices" color="primary" />
|
||||
<q-tooltip :delay="600" anchor="top middle" self="top middle">Agent Count</q-tooltip>
|
||||
{{ serverCount + workstationCount }}
|
||||
<q-menu>
|
||||
<q-list dense>
|
||||
<q-item-label header>Servers</q-item-label>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="fa fa-server" size="sm" color="primary" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section no-wrap>
|
||||
<q-item-label>Total: {{ serverCount }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="power_off" size="sm" color="negative" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section no-wrap>
|
||||
<q-item-label>Offline: {{ serverOfflineCount }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item-label header>Workstations</q-item-label>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="computer" size="sm" color="primary" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section no-wrap>
|
||||
<q-item-label>Total: {{ workstationCount }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="power_off" size="sm" color="negative" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section no-wrap>
|
||||
<q-item-label>Offline: {{ workstationOfflineCount }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-chip>
|
||||
|
||||
<AlertsIcon />
|
||||
|
||||
<q-btn-dropdown flat no-caps stretch :label="user">
|
||||
<q-list>
|
||||
<q-item clickable v-ripple @click="showUserPreferences" v-close-popup>
|
||||
<q-item-section>
|
||||
<q-item-label>Preferences</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item to="/expired" exact>
|
||||
<q-item-section>
|
||||
<q-item-label>Logout</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
<q-page-container>
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
<script>
|
||||
// composition imports
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
||||
import { useQuasar } from "quasar";
|
||||
import { useStore } from "vuex";
|
||||
import axios from "axios";
|
||||
import { getBaseUrl } from "@/boot/axios";
|
||||
|
||||
// ui imports
|
||||
import AlertsIcon from "@/components/AlertsIcon";
|
||||
import UserPreferences from "@/components/modals/coresettings/UserPreferences";
|
||||
|
||||
export default {
|
||||
name: "MainLayout",
|
||||
components: { AlertsIcon },
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
const $q = useQuasar();
|
||||
|
||||
const darkMode = computed({
|
||||
get: () => {
|
||||
return $q.dark.isActive;
|
||||
},
|
||||
set: value => {
|
||||
axios.patch("/accounts/users/ui/", { dark_mode: value }).catch(e => {});
|
||||
$q.dark.set(value);
|
||||
},
|
||||
});
|
||||
|
||||
const currentTRMMVersion = computed(() => store.state.currentTRMMVersion);
|
||||
const latestTRMMVersion = computed(() => store.state.latestTRMMVersion);
|
||||
const needRefresh = computed(() => store.state.needRefresh);
|
||||
const user = computed(() => store.state.username);
|
||||
const token = computed(() => store.state.token);
|
||||
|
||||
const latestReleaseURL = computed(() => {
|
||||
return latestTRMMVersion.value
|
||||
? `https://github.com/wh1te909/tacticalrmm/releases/tag/v${latestTRMMVersion.value}`
|
||||
: "";
|
||||
});
|
||||
|
||||
function showUserPreferences() {
|
||||
$q.dialog({
|
||||
component: UserPreferences,
|
||||
}).onOk(() => store.dispatch("getDashInfo"));
|
||||
}
|
||||
|
||||
function wsUrl() {
|
||||
return getBaseUrl().split("://")[1];
|
||||
}
|
||||
|
||||
const serverCount = ref(0);
|
||||
const serverOfflineCount = ref(0);
|
||||
const workstationCount = ref(0);
|
||||
const workstationOfflineCount = ref(0);
|
||||
|
||||
const ws = ref(null);
|
||||
|
||||
function setupWS() {
|
||||
console.log("Starting websocket");
|
||||
const proto = process.env.NODE_ENV === "production" || process.env.DOCKER_BUILD ? "wss" : "ws";
|
||||
ws.value = new WebSocket(`${proto}://${wsUrl()}/ws/dashinfo/?access_token=${token.value}`);
|
||||
ws.value.onopen = e => {
|
||||
console.log("Connected to ws");
|
||||
};
|
||||
ws.value.onmessage = e => {
|
||||
const data = JSON.parse(e.data);
|
||||
serverCount.value = data.total_server_count;
|
||||
serverOfflineCount.value = data.total_server_offline_count;
|
||||
workstationCount.value = data.total_workstation_count;
|
||||
workstationOfflineCount.value = data.total_workstation_offline_count;
|
||||
};
|
||||
ws.value.onclose = e => {
|
||||
try {
|
||||
console.log(`Closed code: ${e.code}`);
|
||||
if (e.code !== 1000 && e.code) {
|
||||
console.log("Retrying websocket connection...");
|
||||
setTimeout(() => {
|
||||
setupWS();
|
||||
}, 2 * 1000);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Websocket connection closed");
|
||||
}
|
||||
};
|
||||
ws.value.onerror = err => {
|
||||
console.log("There was an error");
|
||||
ws.value.onclose();
|
||||
};
|
||||
}
|
||||
|
||||
const poll = ref(null);
|
||||
function livePoll() {
|
||||
poll.value = setInterval(() => {
|
||||
store.dispatch("checkVer");
|
||||
store.dispatch("getDashInfo", false);
|
||||
}, 60 * 5 * 1000);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setupWS();
|
||||
store.dispatch("getDashInfo");
|
||||
store.dispatch("checkVer");
|
||||
|
||||
livePoll();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.value.close();
|
||||
clearInterval(poll.value);
|
||||
});
|
||||
|
||||
return {
|
||||
// reactive data
|
||||
serverCount,
|
||||
serverOfflineCount,
|
||||
workstationCount,
|
||||
workstationOfflineCount,
|
||||
latestReleaseURL,
|
||||
currentTRMMVersion,
|
||||
latestTRMMVersion,
|
||||
user,
|
||||
needRefresh,
|
||||
darkMode,
|
||||
|
||||
// methods
|
||||
showUserPreferences,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,6 +1,13 @@
|
||||
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router'
|
||||
import routes from './routes';
|
||||
|
||||
// useful for importing router outside of vue components
|
||||
// import {router} from "@/router"
|
||||
export const router = new createRouter({
|
||||
routes,
|
||||
history: createWebHistory(process.env.VUE_ROUTER_BASE)
|
||||
})
|
||||
|
||||
export default function ({ store }) {
|
||||
const createHistory = process.env.SERVER
|
||||
? createMemoryHistory
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Dashboard",
|
||||
component: () => import("@/views/Dashboard"),
|
||||
meta: {
|
||||
requireAuth: true
|
||||
}
|
||||
name: "MainLayout",
|
||||
component: () => import("@/layouts/MainLayout"),
|
||||
children: [
|
||||
{
|
||||
path: "agents/:agent_id",
|
||||
name: "Agent",
|
||||
component: () => import("@/views/Agent"),
|
||||
meta: {
|
||||
requireAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
name: "Dashboard",
|
||||
component: () => import("@/views/Dashboard"),
|
||||
meta: {
|
||||
requireAuth: true
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/setup",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createStore } from 'vuex'
|
||||
import { Screen } from 'quasar'
|
||||
import { Screen, Dark, LoadingBar } from 'quasar'
|
||||
import axios from "axios";
|
||||
|
||||
export default function () {
|
||||
@@ -8,12 +8,14 @@ export default function () {
|
||||
return {
|
||||
username: localStorage.getItem("user_name") || null,
|
||||
token: localStorage.getItem("access_token") || null,
|
||||
clients: {},
|
||||
tree: [],
|
||||
agents: [],
|
||||
treeReady: false,
|
||||
selectedTree: "",
|
||||
selectedRow: null,
|
||||
agentTableLoading: false,
|
||||
needrefresh: false,
|
||||
refreshSummaryTab: false,
|
||||
tableHeight: "300px",
|
||||
tabHeight: "300px",
|
||||
showCommunityScripts: false,
|
||||
@@ -23,7 +25,10 @@ export default function () {
|
||||
clientTreeSort: "alphafail",
|
||||
clientTreeSplitter: 20,
|
||||
noCodeSign: false,
|
||||
hosted: false
|
||||
hosted: false,
|
||||
clearSearchWhenSwitching: false,
|
||||
currentTRMMVersion: null,
|
||||
latestTRMMVersion: null
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
@@ -39,14 +44,8 @@ export default function () {
|
||||
showCommunityScripts(state) {
|
||||
return state.showCommunityScripts;
|
||||
},
|
||||
needRefresh(state) {
|
||||
return state.needrefresh;
|
||||
},
|
||||
agentTableHeight(state) {
|
||||
return state.tableHeight;
|
||||
},
|
||||
tabsTableHeight(state) {
|
||||
return state.tabHeight;
|
||||
allClientsSelected(state) {
|
||||
return !state.selectedTree;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
@@ -64,9 +63,6 @@ export default function () {
|
||||
state.token = null;
|
||||
state.username = null;
|
||||
},
|
||||
getUpdatedSites(state, clients) {
|
||||
state.clients = clients;
|
||||
},
|
||||
loadTree(state, treebar) {
|
||||
state.tree = treebar;
|
||||
state.treeReady = true;
|
||||
@@ -104,6 +100,24 @@ export default function () {
|
||||
},
|
||||
SET_HOSTED(state, val) {
|
||||
state.hosted = val
|
||||
},
|
||||
setClearSearchWhenSwitching(state, val) {
|
||||
state.clearSearchWhenSwitching = val
|
||||
},
|
||||
setLatestTRMMVersion(state, val) {
|
||||
state.latestTRMMVersion = val
|
||||
},
|
||||
setCurrentTRMMVersion(state, val) {
|
||||
state.currentTRMMVersion = val
|
||||
},
|
||||
setAgents(state, agents) {
|
||||
state.agents = agents
|
||||
},
|
||||
setRefreshSummaryTab(state, val) {
|
||||
state.refreshSummaryTab = val
|
||||
},
|
||||
setSelectedTree(state, val) {
|
||||
state.selectedTree = val
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -119,14 +133,53 @@ export default function () {
|
||||
})
|
||||
.catch(e => { })
|
||||
},
|
||||
getDashInfo(context) {
|
||||
return axios.get("/core/dashinfo/");
|
||||
refreshDashboard({ state, commit, dispatch }, clearTreeSelected = false) {
|
||||
if (clearTreeSelected || !state.selectedTree) {
|
||||
dispatch("loadAgents")
|
||||
commit("setSelectedTree", "")
|
||||
}
|
||||
else if (state.selectedTree.includes("Client")) {
|
||||
dispatch("loadAgents", `?client=${state.selectedTree.split("|")[1]}`)
|
||||
}
|
||||
else if (state.selectedTree.includes("Site")) {
|
||||
dispatch("loadAgents", `?site=${state.selectedTree.split("|")[1]}`)
|
||||
} else {
|
||||
console.error("refreshDashboard has incorrect parameters")
|
||||
return
|
||||
}
|
||||
|
||||
if (clearTreeSelected) commit("destroySubTable")
|
||||
|
||||
dispatch("loadTree");
|
||||
dispatch("getDashInfo", false);
|
||||
},
|
||||
getUpdatedSites(context) {
|
||||
axios.get("/clients/").then(r => {
|
||||
context.commit("getUpdatedSites", r.data);
|
||||
})
|
||||
.catch(e => { });
|
||||
async loadAgents(context, params = null) {
|
||||
context.commit("AGENT_TABLE_LOADING", true);
|
||||
try {
|
||||
const { data } = await axios.get(`/agents/${params ? params : ""}`)
|
||||
context.commit("setAgents", data);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
context.commit("AGENT_TABLE_LOADING", false);
|
||||
},
|
||||
async getDashInfo(context, edited = true) {
|
||||
const { data } = await axios.get("/core/dashinfo/");
|
||||
if (edited) {
|
||||
LoadingBar.setDefaults({ color: data.loading_bar_color });
|
||||
context.commit("setClearSearchWhenSwitching", data.clear_search_when_switching);
|
||||
context.commit("SET_DEFAULT_AGENT_TBL_TAB", data.default_agent_tbl_tab);
|
||||
context.commit("SET_CLIENT_TREE_SORT", data.client_tree_sort);
|
||||
context.commit("SET_CLIENT_SPLITTER", data.client_tree_splitter);
|
||||
}
|
||||
Dark.set(data.dark_mode);
|
||||
context.commit("setCurrentTRMMVersion", data.trmm_version);
|
||||
context.commit("setLatestTRMMVersion", data.latest_trmm_ver);
|
||||
context.commit("SET_AGENT_DBLCLICK_ACTION", data.dbl_click_action);
|
||||
context.commit("SET_URL_ACTION", data.url_action);
|
||||
context.commit("setShowCommunityScripts", data.show_community_scripts);
|
||||
context.commit("SET_HOSTED", data.hosted);
|
||||
},
|
||||
loadTree({ commit, state }) {
|
||||
axios.get("/clients/").then(r => {
|
||||
|
||||
49
src/views/Agent.vue
Normal file
49
src/views/Agent.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<q-page>
|
||||
<SummaryTab />
|
||||
<q-separator />
|
||||
<SubTableTabs
|
||||
:style="{ height: `${tabHeight + 38}px` }"
|
||||
:activeTabs="['checks', 'tasks', 'patches', 'software', 'history', 'notes', 'assets', 'audit']"
|
||||
/>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// composition imports
|
||||
import { ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useQuasar } from "quasar";
|
||||
|
||||
// ui imports
|
||||
import SummaryTab from "@/components/agents/SummaryTab";
|
||||
import SubTableTabs from "@/components/SubTableTabs";
|
||||
|
||||
export default {
|
||||
name: "Agent",
|
||||
components: {
|
||||
SummaryTab,
|
||||
SubTableTabs,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
refreshDashboard: () => {}, // noop
|
||||
};
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const $q = useQuasar();
|
||||
|
||||
const tabHeight = ref($q.screen.height - 309 - 50 - 36);
|
||||
|
||||
store.commit("setActiveRow", route.params.agent_id);
|
||||
store.state.tabHeight = `${tabHeight.value}px`;
|
||||
|
||||
return {
|
||||
tabHeight,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user