Skip to content

Commit

Permalink
Added filters to system-wide connections.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jahaja committed Jul 24, 2014
1 parent e02f3db commit 8d23278
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 54 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
--- v0.4.0 [Not released] ---
--- v0.4.0 [2014-07-24] ---

* [NEW] Added a environment variables sub-page to the process view.

Expand All @@ -12,6 +12,10 @@

* [NEW] Added Travis CI setup and test coverage.

* [NEW] Added filters to system-wide connections table

* [NEW] Some rather large refactorings in general due to added tests.

--- v0.3.0 [2014-04-15] ---

* [NEW] Added a PSDASH_URL_PREFIX configuration setting to allow psdash to be served from a sub-url rather than always on root.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Available command-line arguments:
```
usage: psdash [-h] [-l path] [-b host] [-p port] [-d]
psdash 0.3.0 - system information web dashboard
psdash 0.4.0 - system information web dashboard
optional arguments:
-h, --help show this help message and exit
Expand Down
2 changes: 1 addition & 1 deletion psdash/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.3.0"
__version__ = "0.4.0"
2 changes: 1 addition & 1 deletion psdash/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def update_net_io_counters(self):
return self.app.psdash.net_io_counters.update()

def run(self):
logger.info('Starting psdash v0.3.0')
logger.info('Starting psdash v0.4.0')

self._setup_locale()
self._setup_timers()
Expand Down
19 changes: 19 additions & 0 deletions psdash/static/js/psdash.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,12 @@ function init_log() {
scroll_down($el);
}

var skip_updates = false;

function init_updater() {
function update() {
if (skip_updates) return;

$.ajax({
url: location.href,
cache: false,
Expand All @@ -123,7 +127,22 @@ function init_updater() {
setInterval(update, 3000);
}

function init_connections_filter() {
var $content = $("#content");
$content.on("change", "#connections-form select", function () {
$content.find("#connections-form").submit();
});
$content.on("focus", "#connections-form select", function () {
skip_updates = true;
});
$content.on("blur", "#connections-form select", function () {
skip_updates = false;
});
}

$(document).ready(function() {
init_connections_filter();

if($("#log").length == 0) {
init_updater();
} else {
Expand Down
150 changes: 103 additions & 47 deletions psdash/templates/network.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,52 +40,108 @@
</table>
</div>
<div id="connections" class="panel panel-primary">
<div class="panel-heading"><span class="glyphicon glyphicon-transfer"></span> Connections</div>
<table class="table table-striped table-condensed table-hover">
<thead>
<tr>
<th>FD</th>
<th>PID</th>
<th>Family</th>
<th>Type</th>
<th>Local address</th>
<th>Remote address</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for c in net_connections %}
<tr>
<td>
{{ c.fd if c.fd > 0 else "-" }}
</td>
<td>
{% if c.pid %}
<a href="{{ url_for(".process", pid=c.pid) }}">{{ c.pid }}</a>
{% else %}
-
{% endif %}
</td>
<td>{{ socket_families[c.family] }}</td>
<td>{{ socket_types[c.type] }}</td>
<td>
{% if c.laddr[0] and c.laddr[1] %}
{{ c.laddr[0] }}:{{ c.laddr[1] }}
{% else %}
-
{% endif %}
</td>
<td>
{% if c.raddr[0] and c.raddr[1] %}
{{ c.raddr[0] }}:{{ c.raddr[1] }}
{% else %}
-
{% endif %}
</td>
<td>{{ c.status }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="panel-heading">
<span class="glyphicon glyphicon-transfer"></span> Connections
<span class="pull-right">Listing {{ num_conns }} connections</span>
</div>
<form id="connections-form" action="" method="get">
<table class="table table-striped table-condensed table-hover">
<thead>
<tr>
<th>FD</th>
<th>PID</th>
<th>Family</th>
<th>Type</th>
<th>Local address</th>
<th>Remote address</th>
<th>State</th>
</tr>
<tr>
<th></th>
<th>
<select name="pid">
<option value="">All</option>
{% for p in pids %}
<option value="{{ p }}" {{'selected="selected"' if pid == p}}>{{ p }}</option>
{% endfor %}
</select>
</th>
<th>
<select name="family">
<option value="">All</option>
{% for val, name in socket_families.iteritems() %}
<option value="{{ val }}" {{'selected="selected"' if family == val|string()}}>{{ name }}</option>
{% endfor %}
</select>
</th>
<th>
<select name="type">
<option value="">All</option>
{% for val, name in socket_types.iteritems() %}
<option value="{{ val }}" {{'selected="selected"' if type == val|string()}}>{{ name }}</option>
{% endfor %}
</select>
</th>
<th>
<select name="laddr">
<option value="">All</option>
{% for val in local_addresses %}
<option value="{{ val }}" {{'selected="selected"' if laddr == val}}>{{ val }}</option>
{% endfor %}
</select>
</th>
<th>
<select name="raddr">
<option value="">All</option>
{% for val in remote_addresses %}
<option value="{{ val }}" {{'selected="selected"' if raddr == val}}>{{ val }}</option>
{% endfor %}
</select>
</th>
<th>
<select name="status">
<option value="">All</option>
{% for val in states %}
<option value="{{ val }}" {{'selected="selected"' if status == val}}>{{ val }}</option>
{% endfor %}
</select>
</th>
</tr>
</thead>
<tbody>
{% for c in net_connections %}
<tr>
<td>
{{ c.fd if c.fd > 0 else "-" }}
</td>
<td>
{% if c.pid %}
<a href="{{ url_for(".process", pid=c.pid) }}">{{ c.pid }}</a>
{% else %}
-
{% endif %}
</td>
<td>{{ socket_families[c.family] }}</td>
<td>{{ socket_types[c.type] }}</td>
<td>
{% if c.laddr[0] and c.laddr[1] %}
{{ c.laddr[0] }}:{{ c.laddr[1] }}
{% else %}
-
{% endif %}
</td>
<td>
{% if c.raddr[0] and c.raddr[1] %}
{{ c.raddr[0] }}:{{ c.raddr[1] }}
{% else %}
-
{% endif %}
</td>
<td>{{ c.status }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
</div>
{% endblock %}
47 changes: 44 additions & 3 deletions psdash/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,23 +238,64 @@ def process(pid, section):
**context
)

def filter_connections(conns, filters):
for k, v in filters.iteritems():
if not v:
continue

if k in ('laddr', 'raddr'):
conns = [c for c in conns if getattr(c, k) and str(getattr(c, k)[0]) == v]
else:
conns = [c for c in conns if str(getattr(c, k)) == v]

return conns

@webapp.route('/network')
def view_networks():
netifs = build_network_interfaces()
netifs.sort(key=lambda x: x.get('bytes_sent'), reverse=True)

# {'key', 'default_value'}
# An empty string means that no filtering will take place on that key
form_keys = {
'pid': '',
'family': str(socket.AF_INET),
'type': str(socket.SOCK_STREAM),
'laddr': '',
'raddr': '',
'status': 'LISTEN'
}

form_values = dict((k, request.args.get(k, default_val))
for k, default_val in form_keys.iteritems())

conns = psutil.net_connections()

# populate filter dropdowns
pids = set(str(c.pid) for c in conns if c.pid)
local_addresses = set(c.laddr[0] for c in conns if c.laddr)
remote_addresses = set(c.raddr[0] for c in conns if c.raddr)
states = set(c.status for c in conns)
families = dict((k, str(v)) for k, v in socket_families.iteritems())
types = dict((k, str(v)) for k, v in socket_types.iteritems())

conns = filter_connections(conns, form_values)
conns.sort(key=lambda x: x.status)

return render_template(
'network.html',
page='network',
network_interfaces=netifs,
net_connections=conns,
socket_families=socket_families,
socket_types=socket_types,
is_xhr=request.is_xhr
socket_families=families,
socket_types=types,
pids=pids,
local_addresses=local_addresses,
remote_addresses=remote_addresses,
states=states,
is_xhr=request.is_xhr,
num_conns=len(conns),
**form_values
)


Expand Down
4 changes: 4 additions & 0 deletions tests/test_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ def test_non_existing(self):
resp = self.client.get('/prettywronghuh')
self.assertEqual(resp.status_code, httplib.NOT_FOUND)

def test_connection_filters(self):
resp = self.client.get('/network?laddr=127.0.0.1')
self.assertEqual(resp.status_code, httplib.OK)


class TestLogs(unittest2.TestCase):
def _create_log_file(self):
Expand Down

0 comments on commit 8d23278

Please sign in to comment.