add live process manager
This commit is contained in:
parent
822ea426b2
commit
f83baaa121
|
@ -0,0 +1,33 @@
|
|||
from __future__ import absolute_import
|
||||
import psutil
|
||||
from time import sleep
|
||||
import random
|
||||
import string
|
||||
|
||||
def get_procs():
|
||||
ret = []
|
||||
|
||||
# setup
|
||||
for proc in psutil.process_iter():
|
||||
with proc.oneshot():
|
||||
proc.cpu_percent(interval=None)
|
||||
|
||||
# need time for psutil to record cpu percent
|
||||
sleep(1)
|
||||
|
||||
for proc in psutil.process_iter():
|
||||
x = {}
|
||||
with proc.oneshot():
|
||||
x['name'] = proc.name()
|
||||
x['cpu_percent'] = proc.cpu_percent(interval=None) / psutil.cpu_count()
|
||||
x['memory_percent'] = proc.memory_percent()
|
||||
x['pid'] = proc.pid
|
||||
x['ppid'] = proc.ppid()
|
||||
x['status'] = proc.status()
|
||||
x['username'] = proc.username()
|
||||
x['id'] = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(10)])
|
||||
|
||||
if proc.pid != 0:
|
||||
ret.append(x)
|
||||
|
||||
return ret
|
|
@ -16,4 +16,6 @@ urlpatterns = [
|
|||
path("<pk>/geteventlog/<logtype>/<days>/", views.get_event_log),
|
||||
path("getagentversions/", views.get_agent_versions),
|
||||
path("updateagents/", views.update_agents),
|
||||
path("<pk>/getprocs/", views.get_processes),
|
||||
path("<pk>/<pid>/killproc/", views.kill_proc),
|
||||
]
|
||||
|
|
|
@ -194,6 +194,42 @@ def agent_detail(request, pk):
|
|||
return Response(AgentSerializer(agent).data)
|
||||
|
||||
|
||||
@api_view()
|
||||
def get_processes(request, pk):
|
||||
agent = get_object_or_404(Agent, pk=pk)
|
||||
try:
|
||||
resp = agent.salt_api_cmd(
|
||||
hostname=agent.salt_id,
|
||||
timeout=70,
|
||||
func="process_manager.get_procs"
|
||||
)
|
||||
data = resp.json()
|
||||
except Exception:
|
||||
return Response(
|
||||
{"error": "unable to contact the agent"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
return Response(data["return"][0][agent.salt_id])
|
||||
|
||||
@api_view()
|
||||
def kill_proc(request, pk, pid):
|
||||
agent = get_object_or_404(Agent, pk=pk)
|
||||
resp = agent.salt_api_cmd(
|
||||
hostname=agent.salt_id,
|
||||
timeout=60,
|
||||
func="ps.kill_pid",
|
||||
arg=int(pid)
|
||||
)
|
||||
data = resp.json()
|
||||
|
||||
if not data["return"][0][agent.salt_id]:
|
||||
return Response(
|
||||
{"error": "Unable to kill the process"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
else:
|
||||
return Response("ok")
|
||||
|
||||
|
||||
@api_view()
|
||||
def get_event_log(request, pk, logtype, days):
|
||||
agent = get_object_or_404(Agent, pk=pk)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "web",
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.8",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table
|
||||
dense
|
||||
class="procs-sticky-header-table"
|
||||
:data="procs"
|
||||
:columns="columns"
|
||||
:pagination.sync="pagination"
|
||||
:filter="filter"
|
||||
row-key="id"
|
||||
binary-state-sort
|
||||
hide-bottom
|
||||
>
|
||||
<template v-slot:top>
|
||||
<q-btn v-if="poll" dense flat push @click="stopPoll" icon="stop" label="Stop Live Refresh" />
|
||||
<q-btn
|
||||
v-else
|
||||
dense
|
||||
flat
|
||||
push
|
||||
@click="startPoll"
|
||||
icon="play_arrow"
|
||||
label="Resume Live Refresh"
|
||||
/>
|
||||
<q-space />
|
||||
<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">
|
||||
<q-tr :props="props">
|
||||
<q-menu context-menu>
|
||||
<q-list dense style="min-width: 200px">
|
||||
<q-item clickable v-close-popup @click="killProc(props.row.pid, props.row.name)">
|
||||
<q-item-section thumbnail>
|
||||
<q-icon name="fas fa-trash-alt" size="xs" />
|
||||
</q-item-section>
|
||||
<q-item-section>End task</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
<q-td>{{ props.row.name }}</q-td>
|
||||
<q-td>{{ Math.ceil(props.row.cpu_percent) }}%</q-td>
|
||||
<q-td>{{ convert(props.row.memory_percent) }} MB</q-td>
|
||||
<q-td>{{ props.row.username }}</q-td>
|
||||
<q-td>{{ props.row.pid }}</q-td>
|
||||
<q-td>{{ props.row.status }}</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import mixins from "@/mixins/mixins";
|
||||
|
||||
export default {
|
||||
name: "ProcessManager",
|
||||
props: ["pk"],
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {
|
||||
polling: null,
|
||||
mem: null,
|
||||
poll: true,
|
||||
procs: [],
|
||||
filter: "",
|
||||
pagination: {
|
||||
rowsPerPage: 99999,
|
||||
sortBy: "cpu_percent",
|
||||
descending: true
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
name: "name",
|
||||
label: "Name",
|
||||
field: "name",
|
||||
align: "left",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: "cpu_percent",
|
||||
label: "CPU",
|
||||
field: "cpu_percent",
|
||||
align: "left",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: "memory_percent",
|
||||
label: "Memory",
|
||||
field: "memory_percent",
|
||||
align: "left",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
label: "User",
|
||||
field: "username",
|
||||
align: "left",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: "pid",
|
||||
label: "PID",
|
||||
field: "pid",
|
||||
align: "left",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
label: "Status",
|
||||
field: "status",
|
||||
align: "left",
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getProcesses() {
|
||||
this.procs = [];
|
||||
this.$q.loading.show({ message: "Loading Processes..." });
|
||||
axios
|
||||
.get(`/agents/${this.pk}/getprocs/`)
|
||||
.then(r => {
|
||||
this.procs = r.data;
|
||||
this.$q.loading.hide();
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
this.notifyError(e.response.data.error);
|
||||
});
|
||||
},
|
||||
refreshProcs() {
|
||||
this.polling = setInterval(() => {
|
||||
axios
|
||||
.get(`/agents/${this.pk}/getprocs/`)
|
||||
.then(r => (this.procs = r.data))
|
||||
.catch(() => this.notifyError("Unable to contact the agent"));
|
||||
}, 10000);
|
||||
},
|
||||
killProc(pid, name) {
|
||||
this.$q.loading.show({ message: `Attempting to kill process ${name}` });
|
||||
axios
|
||||
.get(`/agents/${this.pk}/${pid}/killproc/`)
|
||||
.then(r => {
|
||||
this.$q.loading.hide();
|
||||
this.notifySuccess(`${name} was terminated!`);
|
||||
})
|
||||
.catch(e => {
|
||||
this.$q.loading.hide();
|
||||
this.notifyError(e.response.data.error);
|
||||
});
|
||||
},
|
||||
stopPoll() {
|
||||
this.poll = false;
|
||||
clearInterval(this.polling);
|
||||
},
|
||||
startPoll() {
|
||||
this.poll = true;
|
||||
axios
|
||||
.get(`/agents/${this.pk}/getprocs/`)
|
||||
.then(r => (this.procs = r.data));
|
||||
this.refreshProcs();
|
||||
},
|
||||
getAgent() {
|
||||
axios
|
||||
.get(`/agents/${this.pk}/agentdetail/`)
|
||||
.then(r => (this.mem = r.data.total_ram));
|
||||
},
|
||||
convert(percent) {
|
||||
const mb = this.mem * 1024;
|
||||
return Math.ceil((percent * mb) / 100).toLocaleString();
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.polling);
|
||||
},
|
||||
created() {
|
||||
this.getAgent();
|
||||
// disable loading bar
|
||||
this.$q.loadingBar.setDefaults({ size: "0px" });
|
||||
|
||||
this.getProcesses();
|
||||
},
|
||||
mounted() {
|
||||
this.refreshProcs();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.procs-sticky-header-table {
|
||||
/* max height is important */
|
||||
.q-table__middle {
|
||||
max-height: 650px;
|
||||
}
|
||||
|
||||
.q-table__top, .q-table__bottom, thead tr:first-child th {
|
||||
background-color: #f5f4f2;
|
||||
}
|
||||
|
||||
thead tr:first-child th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -13,7 +13,8 @@
|
|||
<q-tab name="terminal" icon="fas fa-terminal" label="Terminal" />
|
||||
<q-tab name="filebrowser" icon="far fa-folder-open" label="File Browser" />
|
||||
<q-tab name="services" icon="fas fa-cogs" label="Services" />
|
||||
<q-tab name="eventlog" icon="fas fa-cogs" label="Event Log" />
|
||||
<q-tab name="processes" icon="fas fa-chart-area" label="Processes" />
|
||||
<q-tab name="eventlog" icon="fas fa-clipboard-list" label="Event Log" />
|
||||
</q-tabs>
|
||||
<q-separator />
|
||||
<q-tab-panels v-model="tab">
|
||||
|
@ -24,6 +25,9 @@
|
|||
>
|
||||
</iframe>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="processes">
|
||||
<ProcessManager :pk="pk" />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="services">
|
||||
<Services :pk="pk" />
|
||||
</q-tab-panel>
|
||||
|
@ -43,6 +47,7 @@
|
|||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import ProcessManager from "@/components/ProcessManager";
|
||||
import Services from "@/components/Services";
|
||||
import EventLog from "@/components/EventLog";
|
||||
|
||||
|
@ -50,7 +55,8 @@ export default {
|
|||
name: "RemoteBackground",
|
||||
components: {
|
||||
Services,
|
||||
EventLog
|
||||
EventLog,
|
||||
ProcessManager
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
Loading…
Reference in New Issue