Skip to content

Commit

Permalink
Merge pull request #82 from innogames/servershell_hotfixes
Browse files Browse the repository at this point in the history
Minor Servershell + Resources bug fixes
  • Loading branch information
kofrezo committed Apr 2, 2020
2 parents c9ffdac + 6a76cc0 commit a80cd8d
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 69 deletions.
17 changes: 8 additions & 9 deletions serveradmin/resources/templates/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{% endblock %}

{% block content %}
<form method="get" action="{% url 'resources_index' %}">
<form id="resources-form" method="get" action="{% url 'resources_index' %}">
<div id="filterCollapse" class="collapse">
<div class="row">
<div class="col-md-12 controls">
Expand Down Expand Up @@ -39,7 +39,8 @@
<div class="form-group row input-controls">
<label for="per_page" class="col-sm-1 col-form-label">Per Page:</label>
<div class="col-md-4">
<input class="form-control form-control-sm" id="per_page" type="number" value="{{ per_page }}" />
<input class="form-control form-control-sm" min="1" id="per_page" name="per_page" type="number" value="{{ per_page }}" />
<input type="hidden" id="page" name="page" value="{{ page }}" />
</div>
</div>
</div>
Expand Down Expand Up @@ -113,17 +114,15 @@
<div class="pagination">
<span class="step-links">
{% if hosts.has_previous %}
<a href="?page=1">&laquo; first</a>
<a href="?page={{ hosts.previous_page_number }}">previous</a>
<a href="#" onclick="$('#page').val($(this).data('page')); $('#resources-form').submit();" data-page="1">&laquo; first</a>
<a href="#" onclick="$('#page').val($(this).data('page')); $('#resources-form').submit();" data-page="{{ hosts.previous_page_number }}">previous</a>
{% endif %}

<span class="current">
Page {{ hosts.number }} of {{ hosts.paginator.num_pages }}.
</span>

{% if hosts.has_next %}
<a href="?page={{ hosts.next_page_number }}">next</a>
<a href="?page={{ hosts.paginator.num_pages }}">last &raquo;</a>
<a href="#" onclick="$('#page').val($(this).data('page')); $('#resources-form').submit();" data-page="{{ hosts.next_page_number }}">next</a>
<a href="#" onclick="$('#page').val($(this).data('page')); $('#resources-form').submit();" data-page="{{ hosts.paginator.num_pages }}">last &raquo;</a>
{% endif %}
</span>
</div>
Expand Down Expand Up @@ -157,4 +156,4 @@
update_sprites();
});
</script>
{% endblock %}
{% endblock %}
33 changes: 23 additions & 10 deletions serveradmin/resources/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@

from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ValidationError
from django.core.paginator import Paginator
from django.urls import reverse
from django.core.exceptions import SuspiciousOperation
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.http import HttpResponseBadRequest
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.text import slugify
from django.views.decorators.csrf import ensure_csrf_cookie

from adminapi.datatype import DatatypeError
from adminapi.filters import Any
from adminapi.parse import parse_query
from serveradmin.dataset import Query
Expand All @@ -30,8 +29,7 @@ def index(request):
term = request.GET.get('term', request.session.get('term', ''))
collections = list(Collection.objects.filter(overview=True))

# If a graph collection was specified, use it. Otherwise use the first
# one.
# If a graph collection was specified, use it. Otherwise use the first one
for collection in collections:
if request.GET.get('current_collection'):
if str(collection.id) != request.GET['current_collection']:
Expand Down Expand Up @@ -110,21 +108,36 @@ def index(request):
for server in Query(filters, attribute_ids):
hosts[server['hostname']] = dict(server)

page = int(request.GET.get('page', 1))
per_page = int(request.GET.get('per_page', 8))
hosts_pager = Paginator(list(hosts.values()), per_page)
page = abs(int(request.GET.get('page', 1)))
per_page = int(request.GET.get(
'per_page', request.session.get('resources_per_page', 8)))

# Save settings in session
request.session['resources_per_page'] = per_page

try:
hosts_pager = Paginator(list(hosts.values()), per_page)

# Term or data in DB has changed
if page > hosts_pager.num_pages:
page = 1

hosts_pager = hosts_pager.page(page)
except (PageNotAnInteger, EmptyPage):
raise SuspiciousOperation('{} is not a valid!'.format(page))

sprite_url = settings.MEDIA_URL + 'graph_sprite/' + collection.name
template_info.update({
'columns': columns,
'hosts': hosts_pager.page(1),
'hosts': hosts_pager,
'page': page,
'per_page': per_page,
'matched_hostnames': matched_hostnames,
'understood': understood,
'error': None,
'sprite_url': sprite_url,
})

return TemplateResponse(request, 'resources/index.html', template_info)


Expand Down
123 changes: 73 additions & 50 deletions serveradmin/servershell/static/js/servershell/autocomplete/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,86 +14,110 @@ $(document).ready(function () {
return result;
};

let term_input = $('#term');
term_input.autocomplete({
delay: 50, // Wait n ms before starting to auto complete to avoid needles requests to backend
minLength: 0,
let _is_attribute_value = function(to_complete) {
/**
* Check if attribute value
*
* Check if to_complete is attribute value like for example a=b and
* if yes return attribute and value otherwise false.
*
* @type {Array|Boolean}
*/
let match = to_complete.match(/([a-z_]+)=([\S]+)?/);

if (!match) {
return false;
}

let attribute = match[1];
let value = match[2] === undefined ? '' : match[2];

return [attribute, value];
};

let _complete_filter = function(to_complete) {
let filters = [];
if (to_complete) {
return servershell.filters.filter(filter => filter[0].startsWith(to_complete));
} else {
return servershell.filters;
}
};

let autocomplete_search_input = $('#term');
let autocomplete_search_url = autocomplete_search_input.data('servershell-autocomplete-url');
autocomplete_search_input.autocomplete({
delay: 250, // Wait n ms before starting to auto complete to avoid needles requests to backend
autoFocus: true,
source: function (request, response) {
spinner.enable();

let limit = 20;
let choices = [];
let url = $('#term').data('servershell-autocomplete-url');

spinner.enable();
// Example input: "project=foo fun". Always focus on the last part
// of the term.
let to_complete = request.term.split(' ').pop();

let attribute_value = _is_attribute_value(to_complete);
if (attribute_value !== false) {
let attribute = attribute_value[0];
let value = attribute_value[1];

// project=onyx function=db, always focus on last part of query
let cur_term = request.term.split(' ').pop();
// Add filter functions matching
_complete_filter(value).forEach(function (filter) {
let filter_name = filter[0];
let new_term = _build_value(request.term, to_complete, attribute, filter_name);

// Autocomplete values of attributes e.g. function=<autocomplete>
let match = cur_term.match(/([a-z_]+)=([\S]+)?/);
if (match) {
let last_attribute = match[1];
let last_value = match[2] === undefined ? '' : match[2];
choices.push({
'label': `Filter: ${filter[0]}`,
'value': new_term + '(',
});
});

// Autocomplete attribute value if wanted
// @TODO support nested values e.g. "project=Any(Reg("
if (servershell.search_settings.autocomplete_values) {
// Add attribute values matching
let settings = {
'async': false,
'data': {
'attribute': last_attribute,
'value': last_value,
'attribute': attribute,
'value': value,
},
'async': false,
'success': function (data) {
data.autocomplete.forEach(function (attr_value) {
data.autocomplete.forEach(function(match) {
choices.push({
'label': `AttrVal: ${attr_value}`,
'value': _build_value(request.term, cur_term, last_attribute, attr_value),
'label': `AttrVal: ${match}`,
'value': _build_value(request.term, to_complete, attribute, match),
});
});
}
};
$.ajax(url, settings);
$.ajax(autocomplete_search_url, settings);
}

// Add filter to autocomplete ...
// @TODO: Add autocomplete for nested filter values
let filters;
if (last_value) {
filters = servershell.filters.filter(filter => filter[0].startsWith(last_value));
} else {
filters = servershell.filters;
}
filters.forEach(function (filter) {
choices.push({
'label': `Filter: ${filter[0]}`,
'value': _build_value(request.term, cur_term, last_attribute, filter[0]) + '(',
});
});
}

// Attributes available
// Autocomplete attributes
let attributes = servershell.attributes.filter(
attr => attr.attribute_id.startsWith(cur_term) && attr.type !== 'reverse'
attr => attr.attribute_id.startsWith(to_complete) && attr.type !== 'reverse'
);
attributes.slice(0, limit).forEach(function (attribute) {
attributes.slice(0, limit).forEach(function(attr) {
choices.push({
'label': `Attr: ${attribute.attribute_id}`,
'value': _build_value(request.term, cur_term, attribute.attribute_id) + '='
'label': `Attr: ${attr.attribute_id}`,
'value': _build_value(request.term, to_complete, attr.attribute_id) + '='
})
});

// Autocomplete hostnames for plain server search without
// key=value filters.
// This is the exception where we don't want to show autocomplete
// if nothing has been entered.
// Suggest hostnames if only a part of a hostname has been entered
if (
request.term.length &&
request.term.split(' ').length === 1 &&
request.term.indexOf('=') === -1
) {
// Servershell Host results from backend
let settings = {
'data': {'hostname': cur_term},
'async': false,
'data': {'hostname': to_complete},
'success': function (data) {
data.autocomplete.forEach(function (host) {
choices.push({
Expand All @@ -103,7 +127,7 @@ $(document).ready(function () {
});
},
};
$.ajax(url, settings);
$.ajax(autocomplete_search_url, settings);
}

spinner.disable();
Expand All @@ -118,7 +142,6 @@ $(document).ready(function () {
},
});

term_input.autocomplete($('#autocomplete')[0].checked ? 'enable' : 'disable');
term_input.autocomplete('option', 'autoFocus', $('#autoselect')[0].checked);
autocomplete_search_input.autocomplete($('#autocomplete')[0].checked ? 'enable' : 'disable');
autocomplete_search_input.autocomplete('option', 'autoFocus', $('#autoselect')[0].checked);
});

3 changes: 3 additions & 0 deletions serveradmin/servershell/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ def get_results(request):
'message': str(error)
}))

# Query successful term must be valid here, so we can save it safely now.
request.session['term'] = term

servers = list(islice(query, offset, offset + limit))

# Add information about available, editable attributes on servertypes
Expand Down

0 comments on commit a80cd8d

Please sign in to comment.