Merge branch 'develop' into feature-policies-alerts

This commit is contained in:
wh1te909
2020-04-19 16:56:38 -07:00
committed by GitHub
12 changed files with 992 additions and 668 deletions

View File

@@ -1,2 +0,0 @@
> 1%
last 2 versions

View File

@@ -1,3 +1,16 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
};
"presets": [
"@vue/cli-plugin-babel/preset"
],
"plugins": [
[
"transform-imports",
{
"quasar": {
"transform": "quasar/dist/babel-transforms/imports.js",
"preventFullImport": true
}
}
]
]
}

1236
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,18 +10,25 @@
"@quasar/extras": "^1.6.3",
"axios": "^0.19.2",
"core-js": "^3.6.4",
"quasar": "^1.9.14",
"quasar": "^1.9.15",
"vue": "^2.6.11",
"vue-router": "^3.1.6",
"vuex": "^3.1.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.2.3",
"@vue/cli-service": "^4.2.3",
"babel-plugin-transform-imports": "2.0.0",
"@vue/cli-plugin-babel": "~4.3.0",
"@vue/cli-plugin-router": "~4.3.0",
"@vue/cli-plugin-vuex": "~4.3.0",
"@vue/cli-service": "~4.3.0",
"babel-plugin-transform-imports": "1.5.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"vue-cli-plugin-quasar": "^2.0.0",
"vue-cli-plugin-quasar": "~2.0.0",
"vue-template-compiler": "^2.6.11"
}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

View File

@@ -1,5 +0,0 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

View File

@@ -12,18 +12,37 @@
hide-bottom
>
<template v-slot:top>
<q-btn dense flat push @click="refreshServices" icon="refresh" />
<q-btn
dense
flat
push
@click="refreshServices"
icon="refresh"
/>
<q-space />
<q-input v-model="filter" outlined label="Search" dense clearable >
<q-input
v-model="filter"
outlined
label="Search"
dense
clearable
>
<template v-slot:prepend>
<q-icon name="search" />
</template>
</q-input>
</template>
<template slot="body" slot-scope="props" :props="props">
<template
slot="body"
slot-scope="props"
:props="props"
>
<q-tr :props="props">
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-list
dense
style="min-width: 200px"
>
<q-item
clickable
v-close-popup
@@ -46,19 +65,38 @@
<q-item-section>Restart</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup @click="editService(props.row.name)">
<q-item
clickable
v-close-popup
@click="editService(props.row.name)"
>
<q-item-section>Service Details</q-item-section>
</q-item>
</q-list>
</q-menu>
<q-td key="display_name" :props="props">
<q-td
key="display_name"
:props="props"
>
<q-icon name="fas fa-cogs" />
&nbsp;&nbsp;&nbsp;{{ props.row.display_name }}
</q-td>
<q-td key="start_type" :props="props">{{ props.row.start_type }}</q-td>
<q-td key="pid" :props="props">{{ props.row.pid }}</q-td>
<q-td key="status" :props="props">{{ props.row.status }}</q-td>
<q-td key="username" :props="props">{{ props.row.username }}</q-td>
<q-td
key="start_type"
:props="props"
>{{ props.row.start_type }}</q-td>
<q-td
key="pid"
:props="props"
>{{ props.row.pid }}</q-td>
<q-td
key="status"
:props="props"
>{{ props.row.status }}</q-td>
<q-td
key="username"
:props="props"
>{{ props.row.username }}</q-td>
</q-tr>
</template>
</q-table>
@@ -83,7 +121,10 @@
<div class="row">
<div class="col-3">Description:</div>
<div class="col-9">
<q-field outlined color="black">{{ serviceData.Description }}</q-field>
<q-field
outlined
color="black"
>{{ serviceData.Description }}</q-field>
</div>
</div>
<br />
@@ -145,7 +186,10 @@
</div>
</q-card-section>
<hr />
<q-card-actions align="left" class="bg-white text-teal">
<q-card-actions
align="left"
class="bg-white text-teal"
>
<q-btn
:disable="saveServiceDetailButton"
dense
@@ -153,7 +197,12 @@
color="positive"
@click="changeStartupType(startupType, serviceData.svc_name)"
/>
<q-btn dense label="Cancel" color="grey" v-close-popup />
<q-btn
dense
label="Cancel"
color="grey"
v-close-popup
/>
</q-card-actions>
<q-inner-loading :showing="serviceDetailVisible" />
</q-card>
@@ -168,7 +217,8 @@ import mixins from "@/mixins/mixins";
export default {
name: "Services",
props: ["pk"],
data() {
mixins: [mixins],
data () {
return {
servicesData: [],
serviceDetailsModal: false,
@@ -228,7 +278,7 @@ export default {
};
},
methods: {
changeStartupType(startuptype, name) {
changeStartupType (startuptype, name) {
let changed;
switch (startuptype) {
case "Automatic (Delayed Start)":
@@ -251,21 +301,24 @@ export default {
sv_name: name,
edit_action: changed
};
this.serviceDetailVisible = true;
axios
.post("/services/editservice/", data)
.then(r => {
this.serviceDetailVisible = false;
this.serviceDetailsModal = false;
this.refreshServices();
this.notifySuccess(`Service ${name} was edited!`);
})
.catch(err => {
this.serviceDetailVisible = false;
this.notifyError(err.response.data.error);
});
},
startupTypeChanged() {
startupTypeChanged () {
this.saveServiceDetailButton = false;
},
editService(name) {
editService (name) {
this.saveServiceDetailButton = true;
this.serviceDetailsModal = true;
this.serviceDetailVisible = true;
@@ -294,7 +347,7 @@ export default {
this.notifyError(err.response.data.error);
});
},
serviceAction(name, action, fullname) {
serviceAction (name, action, fullname) {
let msg, status;
switch (action) {
case "start":
@@ -330,15 +383,15 @@ export default {
this.notifyError(err.response.data.error);
});
},
async getServices() {
async getServices () {
try {
let r = await axios.get(`/services/${this.pk}/services/`);
this.servicesData = [r.data][0].services;
} catch(e) {
} catch (e) {
console.log(`ERROR!: ${e}`)
}
},
refreshServices() {
refreshServices () {
this.$q.loading.show({ message: "Reloading services..." });
axios
.get(`/services/${this.pk}/refreshedservices/`)
@@ -352,7 +405,7 @@ export default {
});
}
},
created() {
created () {
this.getServices();
}
};

View File

@@ -14,13 +14,13 @@
<q-item-section avatar>
<q-icon name="fas fa-desktop" />
</q-item-section>
<q-item-section>{{ makeModel }}</q-item-section>
<q-item-section>{{ summary.make_model }}</q-item-section>
</q-item>
<q-item>
<q-item-section avatar>
<q-icon name="fas fa-microchip" />
</q-item-section>
<q-item-section>{{ cpuModel }}</q-item-section>
<q-item-section>{{ summary.cpu_model }}</q-item-section>
</q-item>
<q-item>
<q-item-section avatar>
@@ -30,7 +30,10 @@
</q-item>
<!-- physical disks -->
<q-item v-for="disk in physicalDisks" :key="disk.model">
<q-item
v-for="disk in summary.physical_disks"
:key="disk.model"
>
<q-item-section avatar>
<q-icon name="far fa-hdd" />
</q-item-section>
@@ -46,7 +49,7 @@
<q-item-section avatar>
<q-icon name="fas fa-network-wired" />
</q-item-section>
<q-item-section>LAN IP: {{ localIPs }}</q-item-section>
<q-item-section>LAN IP: {{ summary.local_ips }}</q-item-section>
</q-item>
</q-list>
</div>
@@ -54,7 +57,10 @@
<!-- right -->
<div class="col-3">
<span class="text-subtitle2 text-bold">Disks</span>
<div v-for="disk in disks" :key="disk.device">
<div
v-for="disk in disks"
:key="disk.device"
>
<span>{{ disk.device }} ({{ disk.fstype }})</span>
<q-linear-progress
rounded
@@ -74,82 +80,22 @@
<script>
export default {
name: "SummaryTab",
data() {
data () {
return {};
},
methods: {
bytesToGB(bytes) {
return Math.round(parseInt(bytes) / 1073741824);
},
validateIPv4(ip) {
const rx = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
if (rx.test(ip)) {
return true;
}
return false;
}
},
computed: {
summary() {
summary () {
return this.$store.state.agentSummary;
},
disks() {
disks () {
const entries = Object.entries(this.summary.disks);
const ret = [];
for (let [k, v] of entries) {
ret.push(v);
}
return ret;
},
makeModel() {
const comp_sys = this.summary.wmi_detail.comp_sys[0];
const comp_sys_prod = this.summary.wmi_detail.comp_sys_prod[0];
let make = comp_sys_prod.filter(k => k.Vendor).map(k => k.Vendor)[0];
let model = comp_sys.filter(k => k.SystemFamily).map(k => k.SystemFamily)[0];
if (!model || !make) {
return comp_sys_prod.filter(k => k.Version).map(k => k.Version)[0];
} else {
return `${make} ${model}`;
}
},
physicalDisks() {
const ret = this.summary.wmi_detail.disk;
const phys = [];
ret.forEach(disk => {
const model = disk.filter(k => k.Caption).map(k => k.Caption)[0];
const size = disk.filter(k => k.Size).map(k => k.Size)[0];
const interfaceType = disk
.filter(k => k.InterfaceType)
.map(k => k.InterfaceType)[0];
phys.push({
model: model,
size: this.bytesToGB(size),
interfaceType: interfaceType
});
});
return phys;
},
localIPs() {
const ret = this.summary.wmi_detail.network_config;
const ips = [];
ret.forEach(ip => {
const x = ip.filter(k => k.IPAddress).map(k => k.IPAddress)[0];
if (x !== undefined) {
x.forEach(i => {
if (this.validateIPv4(i)) {
ips.push(i);
}
});
}
});
return (ips.length === 1 ? ips[0] : ips.join(", "))
},
cpuModel() {
const cpu = this.summary.wmi_detail.cpu[0];
return cpu.filter(k => k.Name).map(k => k.Name)[0];
}
}
};

View File

@@ -1,10 +1,24 @@
<template>
<q-card style="min-width: 800px" v-if="agentLoaded && clientsLoaded">
<q-card
style="min-width: 800px"
v-if="agentLoaded && clientsLoaded"
>
<q-splitter v-model="splitterModel">
<template v-slot:before>
<q-tabs dense v-model="tab" vertical class="text-primary">
<q-tab name="general" label="General" />
<q-tab name="patch" label="Patches" />
<q-tabs
dense
v-model="tab"
vertical
class="text-primary"
>
<q-tab
name="general"
label="General"
/>
<q-tab
name="patch"
label="Patches"
/>
</q-tabs>
</template>
<template v-slot:after>
@@ -12,9 +26,18 @@
<q-card-section class="row items-center">
<div class="text-h6">Edit {{ agent.hostname }}</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
<q-btn
icon="close"
flat
round
dense
v-close-popup
/>
</q-card-section>
<q-scroll-area :thumb-style="thumbStyle" style="height: 500px;">
<q-scroll-area
:thumb-style="thumbStyle"
style="height: 500px;"
>
<q-tab-panels
v-model="tab"
animated
@@ -38,7 +61,13 @@
<q-card-section class="row">
<div class="col-2">Site:</div>
<div class="col-2"></div>
<q-select class="col-8" dense outlined v-model="agent.site" :options="sites" />
<q-select
class="col-8"
dense
outlined
v-model="agent.site"
:options="sites"
/>
</q-card-section>
<q-card-section class="row">
<div class="col-2">Type:</div>
@@ -95,8 +124,14 @@
/>
</q-card-section>
<q-card-section class="row">
<q-checkbox v-model="agent.overdue_email_alert" label="Get overdue email alerts" />
<q-checkbox v-model="agent.overdue_text_alert" label="Get overdue sms alerts" />
<q-checkbox
v-model="agent.overdue_email_alert"
label="Get overdue email alerts"
/>
<q-checkbox
v-model="agent.overdue_text_alert"
label="Get overdue sms alerts"
/>
</q-card-section>
</q-tab-panel>
<!-- patch -->
@@ -282,8 +317,15 @@
</q-tab-panels>
</q-scroll-area>
<q-card-section class="row items-center">
<q-btn label="Save" color="primary" type="submit" />
<q-btn label="Cancel" v-close-popup />
<q-btn
label="Save"
color="primary"
type="submit"
/>
<q-btn
label="Cancel"
v-close-popup
/>
</q-card-section>
</q-form>
</template>
@@ -299,7 +341,7 @@ import { scheduledTimes } from "@/mixins/data";
export default {
name: "EditAgent",
mixins: [mixins],
data() {
data () {
return {
agentLoaded: false,
clientsLoaded: false,
@@ -324,22 +366,21 @@ export default {
};
},
methods: {
getAgentInfo() {
getAgentInfo () {
axios.get(`/agents/${this.selectedAgentPk}/agentdetail/`).then(r => {
this.agent = r.data;
this.agentLoaded = true;
});
},
getClientsSites() {
getClientsSites () {
axios.get("/clients/loadclients/").then(r => {
this.tree = r.data;
this.clientsLoaded = true;
});
},
editAgent() {
editAgent () {
let data = this.agent;
delete data.services;
delete data.wmi_detail;
delete data.disks;
delete data.local_ip;
@@ -355,13 +396,13 @@ export default {
},
computed: {
...mapGetters(["selectedAgentPk"]),
sites() {
sites () {
if (this.agentLoaded && this.clientsLoaded) {
return this.tree[this.agent.client];
}
}
},
created() {
created () {
this.getAgentInfo();
this.getClientsSites();
}

View File

@@ -3,8 +3,14 @@ import Vue from "vue";
import "./styles/quasar.styl";
import "@quasar/extras/material-icons/material-icons.css";
import "@quasar/extras/fontawesome-v5/fontawesome-v5.css";
import "@quasar/extras/mdi-v3/mdi-v3.css";
import Quasar from "quasar";
import {
Quasar,
Dialog,
Loading,
LoadingBar,
Meta,
Notify
} from 'quasar';
Vue.use(Quasar, {
config: {
@@ -18,5 +24,13 @@ Vue.use(Quasar, {
textColor: "white",
actions: [{ icon: "close", color: "white" }]
}
},
plugins: {
Dialog,
Loading,
LoadingBar,
Meta,
Notify
}
});

View File

@@ -1,16 +1,16 @@
import Vue from "vue";
import Router from "vue-router";
import VueRouter from "vue-router";
Vue.use(Router);
Vue.use(VueRouter);
export default new Router({
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "Dashboard",
component: () => import ("@/views/Dashboard"),
component: () => import("@/views/Dashboard"),
meta: {
requireAuth: true
}
@@ -18,7 +18,7 @@ export default new Router({
{
path: "/setup",
name: "InitialSetup",
component: () => import ("@/views/InitialSetup"),
component: () => import("@/views/InitialSetup"),
meta: {
requireAuth: true
}
@@ -26,7 +26,7 @@ export default new Router({
{
path: "/takecontrol/:pk",
name: "TakeControl",
component: () => import ("@/views/TakeControl"),
component: () => import("@/views/TakeControl"),
meta: {
requireAuth: true
}
@@ -34,7 +34,7 @@ export default new Router({
{
path: "/remotebackground/:pk",
name: "RemoteBackground",
component: () => import ("@/views/RemoteBackground"),
component: () => import("@/views/RemoteBackground"),
meta: {
requireAuth: true
}
@@ -42,7 +42,7 @@ export default new Router({
{
path: "/login",
name: "Login",
component: () => import ("@/views/Login"),
component: () => import("@/views/Login"),
meta: {
requiresVisitor: true
}
@@ -50,16 +50,18 @@ export default new Router({
{
path: "/logout",
name: "Logout",
component: () => import ("@/views/Logout")
component: () => import("@/views/Logout")
},
{
path: "/expired",
name: "SessionExpired",
component: () => import ("@/views/SessionExpired"),
component: () => import("@/views/SessionExpired"),
meta: {
requireAuth: true
}
},
{ path: "*", component: () => import ("@/views/NotFound") }
{ path: "*", component: () => import("@/views/NotFound") }
]
});
export default router;

View File

@@ -36,19 +36,19 @@ export const store = new Vuex.Store({
toggleAutomationManager: false
},
getters: {
loggedIn(state) {
loggedIn (state) {
return state.token !== null;
},
selectedAgentPk(state) {
selectedAgentPk (state) {
return state.agentSummary.id;
},
selectedPolicyPk(state) {
return state.selectedPolicy;
},
managedByWsus(state) {
managedByWsus (state) {
return state.agentSummary.managed_by_wsus;
},
sortedUpdates(state) {
sortedUpdates (state) {
// sort patches by latest then not installed
if (!state.winUpdates.winupdates) {
return [];
@@ -61,57 +61,57 @@ export const store = new Vuex.Store({
);
return sortedByInstall;
},
agentHostname(state) {
agentHostname (state) {
return state.agentSummary.hostname;
},
scripts(state) {
scripts (state) {
return state.scripts;
},
policies(state) {
policies (state) {
return state.policies;
}
},
mutations: {
TOGGLE_AUTOMATION_MANAGER(state, action) {
TOGGLE_AUTOMATION_MANAGER (state, action) {
state.toggleAutomationManager = action;
},
TOGGLE_SCRIPT_MANAGER(state, action) {
TOGGLE_SCRIPT_MANAGER (state, action) {
state.toggleScriptManager = action;
},
AGENT_TABLE_LOADING(state, visible) {
AGENT_TABLE_LOADING (state, visible) {
state.agentTableLoading = visible;
},
setActiveRow(state, pk) {
setActiveRow (state, pk) {
state.selectedRow = pk;
},
retrieveToken(state, { token, username }) {
retrieveToken (state, { token, username }) {
state.token = token;
state.username = username;
},
destroyCommit(state) {
destroyCommit (state) {
state.token = null;
state.username = null;
},
getUpdatedSites(state, clients) {
getUpdatedSites (state, clients) {
state.clients = clients;
},
loadTree(state, treebar) {
loadTree (state, treebar) {
state.tree = treebar;
state.treeReady = true;
},
setSummary(state, summary) {
setSummary (state, summary) {
state.agentSummary = summary;
},
SET_WIN_UPDATE(state, updates) {
SET_WIN_UPDATE (state, updates) {
state.winUpdates = updates;
},
SET_INSTALLED_SOFTWARE(state, software) {
SET_INSTALLED_SOFTWARE (state, software) {
state.installedSoftware = software;
},
setChecks(state, checks) {
setChecks (state, checks) {
state.agentChecks = checks;
},
SET_AUTOMATED_TASKS(state, tasks) {
SET_AUTOMATED_TASKS (state, tasks) {
state.automatedTasks = tasks;
},
setPolicyChecks(state, checks) {
@@ -120,17 +120,17 @@ export const store = new Vuex.Store({
setPolicyAutomatedTasks(state, tasks) {
state.policyAutomatedTasks = tasks;
},
destroySubTable(state) {
destroySubTable (state) {
(state.agentSummary = {}),
(state.agentChecks = {}),
(state.winUpdates = {});
(state.installedSoftware = []);
(state.installedSoftware = []);
state.selectedRow = "";
},
SET_SCRIPTS(state, scripts) {
SET_SCRIPTS (state, scripts) {
state.scripts = scripts;
},
SET_POLICIES(state, policies) {
SET_POLICIES (state, policies) {
state.policies = policies;
},
setSelectedPolicy(state, pk) {
@@ -138,12 +138,12 @@ export const store = new Vuex.Store({
},
},
actions: {
getPolicies(context) {
getPolicies (context) {
axios.get("/automation/policies/").then(r => {
context.commit("SET_POLICIES", r.data);
})
},
loadAutomatedTasks(context, pk) {
loadAutomatedTasks (context, pk) {
axios.get(`/automation/${pk}/automatedtasks/`).then(r => {
context.commit("SET_AUTOMATED_TASKS", r.data);
})
@@ -153,27 +153,27 @@ export const store = new Vuex.Store({
context.commit("setPolicyAutomatedTasks", r.data);
})
},
getScripts(context) {
getScripts (context) {
axios.get("/checks/getscripts/").then(r => {
context.commit("SET_SCRIPTS", r.data);
})
},
loadInstalledSoftware(context, pk) {
loadInstalledSoftware (context, pk) {
axios.get(`/software/installed/${pk}`).then(r => {
context.commit("SET_INSTALLED_SOFTWARE", r.data.software);
});
},
loadWinUpdates(context, pk) {
loadWinUpdates (context, pk) {
axios.get(`/winupdate/${pk}/getwinupdates/`).then(r => {
context.commit("SET_WIN_UPDATE", r.data);
});
},
loadSummary(context, pk) {
loadSummary (context, pk) {
axios.get(`/agents/${pk}/agentdetail/`).then(r => {
context.commit("setSummary", r.data);
});
},
loadChecks(context, pk) {
loadChecks (context, pk) {
axios.get(`/checks/${pk}/loadchecks/`).then(r => {
context.commit("setChecks", r.data);
});
@@ -183,12 +183,12 @@ export const store = new Vuex.Store({
context.commit("setPolicyChecks", r.data);
});
},
getUpdatedSites(context) {
getUpdatedSites (context) {
axios.get("/clients/loadclients/").then(r => {
context.commit("getUpdatedSites", r.data);
});
},
loadTree({ commit }) {
loadTree ({ commit }) {
axios.get("/clients/loadtree/").then(r => {
const input = r.data;
if (
@@ -231,7 +231,7 @@ export const store = new Vuex.Store({
//commit("destroySubTable");
});
},
retrieveToken(context, credentials) {
retrieveToken (context, credentials) {
return new Promise((resolve, reject) => {
axios
.post("/login/", credentials)
@@ -253,7 +253,7 @@ export const store = new Vuex.Store({
});
});
},
destroyToken(context) {
destroyToken (context) {
if (context.getters.loggedIn) {
return new Promise((resolve, reject) => {
axios

View File

@@ -3,7 +3,12 @@ module.exports = {
host: "192.168.99.150"
},
pluginOptions: {
quasar: {}
quasar: {
importStrategy: 'kebab',
rtlSupport: false
}
},
transpileDependencies: [/[\\\/]node_modules[\\\/]quasar[\\\/]/]
};
transpileDependencies: [
'quasar'
]
}