environment: add kebab menu to services on homepage (#64924)
- kebab menu on services allows to : - edit them - delete them (when available) - main kebab menu has now an extra option to create a new service
This commit is contained in:
parent
e6c0555f78
commit
e635dd1818
|
@ -0,0 +1,62 @@
|
|||
{% extends "hobo/base.html" %}
|
||||
{% load i18n service %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Add a service' %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form method="post">
|
||||
<div id="form-content">
|
||||
<span>{% trans 'Add new service:' %}</span>
|
||||
<span id="new-service">
|
||||
{% for service in available_services %}
|
||||
<p>
|
||||
<a rel="popup" data-service="{{ service.id }}" href="{% url 'create-service' service=service.id %}">{{ service.label }}</a>a
|
||||
</p>
|
||||
{% endfor %}
|
||||
</span>
|
||||
|
||||
{% block buttons %}
|
||||
<div class="buttons">
|
||||
<a class="cancel" href="{% url 'environment-home' %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
<script>
|
||||
$(function() {
|
||||
/* turn the new service links into a select box */
|
||||
var select_new_service = $('<select><option></option></select>').insertAfter($('#new-service'));
|
||||
$('#new-service').hide();
|
||||
$('#new-service a').each(function(index, element) {
|
||||
var text = $(element).text();
|
||||
var option = $('<option value="' + $(element).data('service') + '">' + text + "</option>"
|
||||
).appendTo(select_new_service);
|
||||
});
|
||||
$(select_new_service).change(function() {
|
||||
var service_id = $(this).val();
|
||||
if (service_id) {
|
||||
$('#new-service a[data-service=' + service_id + ']').click();
|
||||
$(this).val('');
|
||||
}
|
||||
});
|
||||
|
||||
$('a.update-variable').hide();
|
||||
$('p.variable label, p.variable input').click(function() {
|
||||
$(this).parent().find('a.update-variable').click();
|
||||
});
|
||||
|
||||
$("div[data-wants-check='true']").each(function(index, element) {
|
||||
$(element).operational_check();
|
||||
});
|
||||
|
||||
$('button.enable-on-change').each(function(index, element) {
|
||||
var button = $(element);
|
||||
$(element).parent('form').find('input').on('change keydown',
|
||||
function() { $(button).prop('disabled', null); });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,24 @@
|
|||
{% extends "hobo/base.html" %}
|
||||
{% load i18n service %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{{ model_name }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div id="form-content">
|
||||
{% csrf_token %}
|
||||
{{ object|as_update_form }}
|
||||
</div>
|
||||
{% block buttons %}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans 'Save' %}</button>
|
||||
<a class="cancel" href="{% url 'environment-home' %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -33,6 +33,7 @@ urlpatterns = [
|
|||
views.operational_check_view,
|
||||
name='operational-check',
|
||||
),
|
||||
path('select_create_service', views.ServiceSelectCreateView.as_view(), name='environment-create-service'),
|
||||
re_path(r'^new-(?P<service>\w+)$', views.ServiceCreateView.as_view(), name='create-service'),
|
||||
re_path(
|
||||
r'^save-(?P<service>\w+)/(?P<slug>[\w-]+)$', views.ServiceUpdateView.as_view(), name='save-service'
|
||||
|
|
|
@ -124,8 +124,17 @@ class VariableDeleteView(DeleteView):
|
|||
return reverse_lazy('environment-home')
|
||||
|
||||
|
||||
class ServiceSelectCreateView(TemplateView):
|
||||
template_name = 'environment/select_new_service.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['available_services'] = [AvailableService(x) for x in AVAILABLE_SERVICES if x.is_enabled()]
|
||||
return context
|
||||
|
||||
|
||||
class ServiceCreateView(CreateView):
|
||||
success_url = reverse_lazy('environment-home')
|
||||
success_url = reverse_lazy('home')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
@ -141,7 +150,7 @@ class ServiceCreateView(CreateView):
|
|||
return initial
|
||||
|
||||
def get_template_names(self):
|
||||
return 'environment/service_form.html'
|
||||
return 'environment/service_create_form.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.service_id = kwargs.pop('service')
|
||||
|
@ -161,7 +170,7 @@ class ServiceCreateView(CreateView):
|
|||
|
||||
|
||||
class ServiceUpdateView(UpdateView):
|
||||
success_url = reverse_lazy('environment-home')
|
||||
success_url = reverse_lazy('home')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
@ -169,7 +178,7 @@ class ServiceUpdateView(UpdateView):
|
|||
return context
|
||||
|
||||
def get_template_names(self):
|
||||
return 'environment/service_form.html'
|
||||
return 'environment/service_edit_form.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.service_id = kwargs.pop('service')
|
||||
|
@ -191,7 +200,7 @@ class ServiceUpdateView(UpdateView):
|
|||
|
||||
|
||||
class ServiceDeleteView(DeleteView):
|
||||
success_url = reverse_lazy('environment-home')
|
||||
success_url = reverse_lazy('home')
|
||||
template_name = 'environment/generic_confirm_delete.html'
|
||||
context_object_name = 'object'
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<span class="actions">
|
||||
<a class="extra-actions-menu-opener"></a>
|
||||
<ul class="extra-actions-menu">
|
||||
<li><a rel="popup" href="{% url 'environment-create-service' %}">{% trans 'Add new service' %}</a></li>
|
||||
<li><a rel="popup" href="{% url 'environment-import' %}">{% trans 'Import' %}</a></li>
|
||||
<li><a href="{% url 'environment-export' %}">{% trans 'Export' %}</a></li>
|
||||
</ul>
|
||||
|
@ -17,18 +18,35 @@
|
|||
{% if services %}
|
||||
<div class="services">
|
||||
{% for service in services %}
|
||||
<a href="{{ service.base_url }}" class="service-link" data-service-slug="{{ service.slug }}">
|
||||
<div class="service" data-service-slug="{{ service.slug }}">
|
||||
<h3 class="service-title">{{ service.title }} <span class="service-url">{{ service.base_url }}</span></h3>
|
||||
<p class="service-status-items">
|
||||
<span class="checking">{% trans "checking..." %}</span>
|
||||
<span style="display: none" class="dns">{% trans "DNS" %}</span>
|
||||
<span style="display: none" class="certificate">{% trans "Certificate" %}</span>
|
||||
<span style="display: none" class="web">{% trans "Web" %}</span>
|
||||
<span style="display: none" class="security">{% trans "Security" %}</span>
|
||||
</p>
|
||||
<div class="service-link" data-service-slug="{{ service.slug }}">
|
||||
<a href="{{ service.base_url }}" data-service-slug="{{ service.slug }}">
|
||||
<div class="service" data-service-slug="{{ service.slug }}">
|
||||
<h3 class="service-title">{{ service.title }} <span class="service-url">{{ service.base_url }}</span></h3>
|
||||
<p class="service-status-items">
|
||||
<span class="checking">{% trans "checking..." %}</span>
|
||||
{% if not service.is_operational and service.wants_frequent_checks %}
|
||||
<span style="display: none" class="info being-deployed">{% trans 'This service is still being deployed.' %}</span>
|
||||
{% endif %}
|
||||
<span style="display: none" class="dns">{% trans "DNS" %}</span>
|
||||
<span style="display: none" class="certificate">{% trans "Certificate" %}</span>
|
||||
<span style="display: none" class="web">{% trans "Web" %}</span>
|
||||
<span style="display: none" class="security">{% trans "Security" %}</span>
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<div class="menu-opener">
|
||||
<span class="actions">
|
||||
<a class="extra-actions-menu-opener"></a>
|
||||
<ul class="extra-actions-menu">
|
||||
<li><a rel="popup" href="{% url 'save-service' service=service.Extra.service_id slug=service.slug %}">{% trans 'Edit' %}</a></li>
|
||||
|
||||
{% if not service.is_operational and not service.wants_frequent_checks %}
|
||||
<li><a rel="popup" href="{% url 'delete-service' service=service.Extra.service_id slug=service.slug %}">{% trans 'Delete service' %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
|
|
|
@ -4,11 +4,22 @@ import pytest
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.core.management import call_command
|
||||
from django.db.utils import IntegrityError
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from webtest import Upload
|
||||
|
||||
from hobo.environment import models as environment_models
|
||||
from hobo.environment.models import AVAILABLE_SERVICES, Combo, Passerelle, ServiceBase, Variable
|
||||
from hobo.environment.models import (
|
||||
AVAILABLE_SERVICES,
|
||||
Authentic,
|
||||
Chrono,
|
||||
Combo,
|
||||
Hobo,
|
||||
Passerelle,
|
||||
ServiceBase,
|
||||
Variable,
|
||||
Wcs,
|
||||
)
|
||||
from hobo.environment.utils import get_installed_services_dict
|
||||
from hobo.profile.models import AttributeDefinition
|
||||
|
||||
|
@ -122,39 +133,67 @@ def test_base_url_field_validator():
|
|||
combo.save()
|
||||
|
||||
|
||||
def test_service_creation_filling(app, admin_user, monkeypatch):
|
||||
@pytest.mark.parametrize(
|
||||
'service_name,',
|
||||
['authentic', 'chrono', 'combo', 'hobo', 'passerelle', 'wcs'],
|
||||
)
|
||||
def test_service_creation_filling(app, admin_user, monkeypatch, service_name):
|
||||
from django.http.request import HttpRequest
|
||||
|
||||
monkeypatch.setattr(HttpRequest, 'get_host', lambda x: 'test.example.net')
|
||||
app = login(app)
|
||||
response = app.get('/sites/new-combo')
|
||||
assert 'value="http://portal.example.net"' in response.text
|
||||
response = app.get('/sites/new-%s' % service_name)
|
||||
slug = response.pyquery('#id_slug').val()
|
||||
url = response.pyquery('#id_base_url').val()
|
||||
assert url == 'http://%s.example.net' % slug
|
||||
|
||||
monkeypatch.setattr(HttpRequest, 'get_host', lambda x: 'hobo-test.example.net')
|
||||
monkeypatch.setattr(HttpRequest, 'get_host', lambda x: 'some-test.example.net')
|
||||
app = login(app)
|
||||
response = app.get('/sites/new-combo')
|
||||
assert 'value="http://portal-test.example.net"' in response.text
|
||||
response = app.get('/sites/new-%s' % service_name)
|
||||
slug = response.pyquery('#id_slug').val()
|
||||
url = response.pyquery('#id_base_url').val()
|
||||
assert url == 'http://%s-test.example.net' % slug
|
||||
|
||||
|
||||
def test_service_creation_url_validation(app, admin_user, monkeypatch):
|
||||
@pytest.mark.parametrize(
|
||||
'service_name,service_cls',
|
||||
[
|
||||
('authentic', Authentic),
|
||||
('chrono', Chrono),
|
||||
('combo', Combo),
|
||||
('hobo', Hobo),
|
||||
('passerelle', Passerelle),
|
||||
('wcs', Wcs),
|
||||
],
|
||||
)
|
||||
def test_service_creation_url_validation(app, admin_user, monkeypatch, service_name, service_cls):
|
||||
app = login(app)
|
||||
response = app.get('/sites/new-combo')
|
||||
response = app.get('/sites/new-%s' % service_name)
|
||||
form = response.form
|
||||
form['title'] = 'test'
|
||||
form['base_url'] = 'http://portal-test.example.net'
|
||||
form['base_url'] = 'http://some-test.example.net'
|
||||
response = form.submit()
|
||||
assert 'not resolvable' in response
|
||||
|
||||
monkeypatch.setattr(environment_models, 'is_resolvable', lambda x: True)
|
||||
form = response.form
|
||||
form.fields['slug'][0].value += '-uniq'
|
||||
response = form.submit()
|
||||
assert 'no valid certificate' in response
|
||||
|
||||
assert not Combo.objects.exists()
|
||||
if service_name == 'hobo':
|
||||
assert service_cls.objects.count() == 1
|
||||
else:
|
||||
assert not service_cls.objects.exists()
|
||||
monkeypatch.setattr(environment_models, 'has_valid_certificate', lambda x: True)
|
||||
form = response.form
|
||||
form.fields['slug'][0].value += '-uniq'
|
||||
response = form.submit()
|
||||
assert Combo.objects.exists()
|
||||
|
||||
if service_name == 'hobo':
|
||||
assert service_cls.objects.count() == 2
|
||||
else:
|
||||
assert service_cls.objects.exists()
|
||||
|
||||
|
||||
def test_home_view(app, admin_user, settings):
|
||||
|
@ -295,16 +334,47 @@ def test_variable_delete_view(app, admin_user):
|
|||
assert Variable.objects.count() == 0
|
||||
|
||||
|
||||
def test_service_update_view(app, admin_user):
|
||||
@pytest.mark.parametrize(
|
||||
'service_name,service_cls',
|
||||
[
|
||||
('authentic', Authentic),
|
||||
('chrono', Chrono),
|
||||
('combo', Combo),
|
||||
('hobo', Hobo),
|
||||
('passerelle', Passerelle),
|
||||
('wcs', Wcs),
|
||||
],
|
||||
)
|
||||
def test_service_update_view(app, admin_user, monkeypatch, service_name, service_cls):
|
||||
import socket
|
||||
|
||||
monkeypatch.setattr(socket, 'gethostbyname', lambda _: '127.1.2.3')
|
||||
monkeypatch.setattr(environment_models, 'has_valid_certificate', lambda x: True)
|
||||
app = login(app)
|
||||
Combo.objects.create(
|
||||
base_url='https://combo.agglo.love', template_name='...portal-user...', slug='portal'
|
||||
)
|
||||
response = app.get('/sites/save-combo/portal')
|
||||
response.form['title'] = 'foobar'
|
||||
response = app.get('/sites/new-%s' % service_name)
|
||||
form = response.form
|
||||
form['title'] = 'test-%s' % service_name
|
||||
hostname = '%s-test.agglo.love' % service_name
|
||||
form['base_url'] = 'https://%s/foobar' % hostname
|
||||
slug = 'slug-%s' % service_name
|
||||
form['slug'] = slug
|
||||
form.submit()
|
||||
if service_name == 'authentic':
|
||||
# Fake operationnal authentic with IDP set
|
||||
authentic_service = service_cls.objects.filter(slug=slug)[0]
|
||||
authentic_service.use_as_idp_for_self = True
|
||||
authentic_service.save()
|
||||
monkeypatch.setattr(authentic_service, 'is_operational', lambda _: True)
|
||||
|
||||
response = app.get(reverse('save-service', kwargs={'service': service_name, 'slug': slug}))
|
||||
response.form['title'] = 'foobar-%s' % service_name
|
||||
if service_name == 'authentic':
|
||||
response.form['use_as_idp_for_self'] = False
|
||||
response = response.form.submit()
|
||||
assert response.location == '/sites/'
|
||||
assert Combo.objects.all()[0].title == 'foobar'
|
||||
assert response.location == '/'
|
||||
assert service_cls.objects.filter(slug=slug)[0].title == ('foobar-%s' % service_name)
|
||||
if service_name == 'authentic':
|
||||
assert not service_cls.objects.filter(slug=slug)[0].use_as_idp_for_self
|
||||
|
||||
|
||||
def test_service_save_extra_variables(app, admin_user, settings):
|
||||
|
@ -334,7 +404,7 @@ def test_service_delete_view(app, admin_user):
|
|||
response = app.get('/sites/delete-combo/portal')
|
||||
assert response.html.find('h2').text == 'Removal of "foo"'
|
||||
response = response.form.submit()
|
||||
assert response.location == '/sites/'
|
||||
assert response.location == '/'
|
||||
assert Combo.objects.count() == 0
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue