diff --git a/src/api/agents.js b/src/api/agents.js index 68845b2..a397720 100644 --- a/src/api/agents.js +++ b/src/api/agents.js @@ -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 { diff --git a/src/api/checks.js b/src/api/checks.js index ec5436a..bd28025 100644 --- a/src/api/checks.js +++ b/src/api/checks.js @@ -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 } \ No newline at end of file diff --git a/src/api/core.js b/src/api/core.js index 3c04358..f6293cc 100644 --- a/src/api/core.js +++ b/src/api/core.js @@ -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) } } \ No newline at end of file diff --git a/src/components/AgentTable.vue b/src/components/AgentTable.vue index 1b29069..fa1e6c5 100644 --- a/src/components/AgentTable.vue +++ b/src/components/AgentTable.vue @@ -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)" > - - - - - - - - Edit {{ props.row.hostname }} - - - - - - - Pending Agent Actions - - - - - - - - Take Control - - - - - - - Run URL Action - - - - - - - {{ action.name }} - - - - - - - - - - Send Command - - - - - - - Run Script - - - - - - - Run Favorited Script - - - - - - - {{ script.label }} - - - - - - - - - - Remote Background - - - - - - - - - {{ props.row.maintenance_mode ? "Disable Maintenance Mode" : "Enable Maintenance Mode" }} - - - - - - - - - Patch Management - - - - - - - - Run Patch Status Scan - - - Install Patches Now - - - - - - - - - - Run Checks - - - - - - - Reboot - - - - - - - - - Now - - - - Later - - - - - - - - - - Assign Automation Policy - - - - - - - Agent Recovery - - - - - - - Remove Agent - - - - - Close - - + 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 yes 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; }, diff --git a/src/components/AlertsIcon.vue b/src/components/AlertsIcon.vue index 6d38dff..b6ff4fd 100644 --- a/src/components/AlertsIcon.vue +++ b/src/components/AlertsIcon.vue @@ -6,7 +6,11 @@ No New Alerts - {{ alert.client }} - {{ alert.site }} - {{ alert.hostname }} + {{ alert.client }} - {{ alert.site }} - {{ alert.hostname }} {{ alert.message }} diff --git a/src/components/AlertsManager.vue b/src/components/AlertsManager.vue index 54cc22a..f57fc64 100644 --- a/src/components/AlertsManager.vue +++ b/src/components/AlertsManager.vue @@ -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 => {}); }, diff --git a/src/components/FileBar.vue b/src/components/FileBar.vue index 2bac216..4b82ff2 100644 --- a/src/components/FileBar.vue +++ b/src/components/FileBar.vue @@ -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({ diff --git a/src/components/SubTableTabs.vue b/src/components/SubTableTabs.vue index a28be3a..012b8fd 100644 --- a/src/components/SubTableTabs.vue +++ b/src/components/SubTableTabs.vue @@ -12,49 +12,110 @@ narrow-indicator no-caps > - - - - - - - - - - + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + @@ -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]), }; }, }; diff --git a/src/components/agents/AgentActionMenu.vue b/src/components/agents/AgentActionMenu.vue new file mode 100644 index 0000000..55a6883 --- /dev/null +++ b/src/components/agents/AgentActionMenu.vue @@ -0,0 +1,468 @@ + + + \ No newline at end of file diff --git a/src/components/agents/ChecksTab.vue b/src/components/agents/ChecksTab.vue index 71fc136..d8511d9 100644 --- a/src/components/agents/ChecksTab.vue +++ b/src/components/agents/ChecksTab.vue @@ -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); } diff --git a/src/components/agents/SummaryTab.vue b/src/components/agents/SummaryTab.vue index b42efee..fd314fd 100644 --- a/src/components/agents/SummaryTab.vue +++ b/src/components/agents/SummaryTab.vue @@ -4,13 +4,17 @@
- - + + {{ summary.hostname }} Maintenance Mode • {{ summary.operating_system }} • Agent v{{ summary.version }} - - + + + + + +
@@ -120,17 +124,25 @@ \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index 1d5f4d1..eb0ae1c 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -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 diff --git a/src/router/routes.js b/src/router/routes.js index 3d36372..4cd141e 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -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", diff --git a/src/store/index.js b/src/store/index.js index dc5a0fa..889a311 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -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 => { diff --git a/src/views/Agent.vue b/src/views/Agent.vue new file mode 100644 index 0000000..1265bba --- /dev/null +++ b/src/views/Agent.vue @@ -0,0 +1,49 @@ + + + \ No newline at end of file diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 524103e..f32b83a 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -1,469 +1,351 @@ + - - - - - +