diff --git a/computes/models.py b/computes/models.py index 2b74de2..cfe0317 100644 --- a/computes/models.py +++ b/computes/models.py @@ -1,6 +1,9 @@ -from django.db.models import Model, CharField, IntegerField +from django.db.models import CharField, IntegerField, Model +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ +from vrtManager.connection import connection_manager + class Compute(Model): name = CharField(_('name'), max_length=64, unique=True) @@ -10,5 +13,9 @@ class Compute(Model): details = CharField(_('details'), max_length=64, null=True, blank=True) type = IntegerField() + @cached_property + def status(self): + return connection_manager.host_is_up(self.type, self.hostname) + def __str__(self): - return self.hostname + return self.name diff --git a/computes/templates/computes.html b/computes/templates/computes.html deleted file mode 100644 index 3e7f98b..0000000 --- a/computes/templates/computes.html +++ /dev/null @@ -1,246 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} -{% block title %}{% trans "Computes" %}{% endblock %} -{% block content %} - <!-- Page Heading --> - <div class="row"> - <div class="col-lg-12"> - {% include 'create_comp_block.html' %} - <h2 class="page-header">{% trans "Computes" %}</h2> - </div> - </div> - <!-- /.row --> - - {% include 'errors_block.html' %} - - <div class="row"> - {% if computes_info %} - {% for compute in computes_info %} - <div id="{{ compute.name }}" class="mb-3 col-12 col-sm-4"> - {% if compute.status is True %} - <div class="card border-success shadow h-100"> - <div class="card-header bg-success"> - {% else %} - <div class="card border-danger shadow h-100"> - <div class="card-header bg-danger"> - {% endif %} - <h5 class="my-0 card-title"> - {% if compute.status is True %} - <a class="card-link text-light" href="{% url 'overview' compute.id %}"><strong>{{ compute.name }}</strong></a> - {% else %} - <span class="card-link text-light" href="#"><strong>{{ compute.name }}</strong></span> - {% endif %} - <a class="card-link text-light float-right" data-toggle="modal" href="#editHost{{ compute.id }}" title="{% trans "Edit" %}"> - <i class="fa fa-cog"></i> - </a> - </h5> - </div> - <div class="card-body"> - <dl class="row"> - <dt class="col-5">{% trans "Status" %}</dt> - {% if compute.status %} - <dd class="col-7">{% trans "Connected" %}</dd> - {% else %} - <dd class="col-7">{% trans "Not Connected" %}</dd> - {% endif %} - <dt class="col-5">{% trans "Details" %}</dt> - {% if compute.details %} - <dd class="col-7">{% trans compute.details %}</dd> - {% else %} - <dd class="col-7">{% trans "No details available" %}</dd> - {% endif %} - </dl> - - <!-- Modal Edit --> - <div class="modal fade" id="editHost{{ compute.id }}" tabindex="-1" role="dialog" aria-labelledby="editHostLabel" aria-hidden="true"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title">{% trans "Edit connection" %}</h5> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> - </div> - {% if compute.type == 1 %} - <form method="post" role="form" aria-label="Edit tcp host form">{% csrf_token %} - <div class="modal-body"> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Name" %}</label> - <div class="col-sm-6"> - <input type="hidden" name="host_id" value="{{ compute.id }}"> - <input type="text" name="name" class="form-control" value="{{ compute.name }}" maxlength="20" required pattern="[a-zA-Z0-9\.\-_]+"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "FQDN / IP" %}</label> - <div class="col-sm-6"> - <input type="text" name="hostname" class="form-control" value="{{ compute.hostname }}" required pattern="[a-z0-9\.\-_]+"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Username" %}</label> - <div class="col-sm-6"> - <input type="text" name="login" class="form-control" value="{{ compute.login }}"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Password" %}</label> - <div class="col-sm-6"> - <input type="password" name="password" class="form-control" value="{{ compute.password }}"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Details" %}</label> - <div class="col-sm-6"> - <input type="text" name="details" class="form-control" placeholder="{% trans "Details" %}" value="{{ compute.details }}"> - </div> - </div></div> - <div class="modal-footer"> - <button type="submit" class="btn btn-danger mr-auto" name="host_del"> - {% trans "Delete" %} - </button> - <button type="button" class="btn btn-secondary" data-dismiss="modal"> - {% trans "Close" %} - </button> - <button type="submit" class="btn btn-primary" name="host_edit" autofocus> - {% trans "Change" %} - </button> - </div> - </form> - {% endif %} - {% if compute.type == 2 %} - <form method="post" role="form" aria-label="Edit ssh host form">{% csrf_token %} - <div class="modal-body"> - <p class="modal-body">{% trans "Need create ssh <a href='https://github.com/retspen/webvirtmgr/wiki/Setup-SSH-Authorization'>authorization key</a>. If you have another SSH port on your server, you can add IP:PORT like '192.168.1.1:2222'." %}</p> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Name" %}</label> - <div class="col-sm-6"> - <input type="hidden" name="host_id" value="{{ compute.id }}"> - <input type="text" name="name" class="form-control" value="{{ compute.name }}" maxlength="20" required pattern="[a-z0-9\.\-_]+"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "FQDN / IP" %}</label> - <div class="col-sm-6"> - <input type="text" name="hostname" class="form-control" value="{{ compute.hostname }}" required pattern="[a-z0-9\:\.\-_]+"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Username" %}</label> - <div class="col-sm-6"> - <input type="text" name="login" class="form-control" value="{{ compute.login }}"> - <input type="hidden" name="password" value="{{ compute.password }}"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Details" %}</label> - <div class="col-sm-6"> - <input type="text" name="details" class="form-control" placeholder="{% trans "Details" %}" value="{{ compute.details }}"> - </div> - </div> - </div> - <div class="modal-footer"> - <button type="submit" class="btn btn-danger mr-auto" name="host_del"> - {% trans "Delete" %} - </button> - <button type="button" class="btn btn-secondary" data-dismiss="modal"> - {% trans "Close" %} - </button> - <button type="submit" class="btn btn-primary" name="host_edit"> - {% trans "Change" %} - </button> - </div> - </form> - {% endif %} - {% if compute.type == 3 %} - <form method="post" role="form" aria-label="Edit tls host form">{% csrf_token %} - <div class="modal-body"> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Name" %}</label> - <div class="col-sm-6"> - <input type="hidden" name="host_id" value="{{ compute.id }}"> - <input type="text" name="name" class="form-control" value="{{ compute.name }}" maxlength="20" required pattern="[a-z0-9\.\-_]+"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "FQDN / IP" %}</label> - <div class="col-sm-6"> - <input type="text" name="hostname" class="form-control" value="{{ compute.hostname }}" required pattern="[a-z0-9\:\.\-_]+"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Username" %}</label> - <div class="col-sm-6"> - <input type="text" name="login" class="form-control" placeholder="{% trans "Name" %}"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Password" %}</label> - <div class="col-sm-6"> - <input type="password" name="password" class="form-control" value="{{ compute.password }}"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Details" %}</label> - <div class="col-sm-6"> - <input type="text" name="details" class="form-control" placeholder="{% trans "Details" %}" value="{{ compute.details }}"> - </div> - </div> - </div> - <div class="modal-footer"> - <button type="submit" class="btn btn-danger mr-auto" name="host_del"> - {% trans "Delete" %} - </button> - <button type="button" class="btn btn-secondary" data-dismiss="modal"> - {% trans "Close" %} - </button> - <button type="submit" class="btn btn-primary" name="host_edit"> - {% trans "Change" %} - </button> - </div> - </form> - {% endif %} - {% if compute.type == 4 %} - <form method="post" role="form" aria-label="Edit/delete host form">{% csrf_token %} - <div class="modal-body"> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Name" %}</label> - <div class="col-sm-6"> - <input type="hidden" name="host_id" value="{{ compute.id }}"> - <input type="text" name="name" class="form-control" value="{{ compute.name }}" maxlength="20" required pattern="[a-z0-9\.\-_]+"> - </div> - </div> - <div class="form-group row"> - <label class="col-sm-4 col-form-label">{% trans "Details" %}</label> - <div class="col-sm-6"> - <input type="text" name="details" class="form-control" placeholder="{% trans 'Details' %}" value="{{ compute.details }}"> - </div> - </div> - </div> - <div class="modal-footer"> - <button type="submit" class="btn btn-danger mr-auto" name="host_del"> - {% trans "Delete" %} - </button> - <button type="button" class="btn btn-secondary" data-dismiss="modal"> - {% trans "Close" %} - <button type="submit" class="btn btn-primary" name="host_edit"> - {% trans "Change" %} - </button> - </div> - </form> - {% endif %} - </div><!-- /.modal-content --> - </div><!-- /.modal-dialog --> - </div><!-- /.modal --> - </div> - </div> - </div> - {% endfor %} - {% else %} - <div class="col-lg-12"> - <div class="alert alert-warning alert-dismissable"> - <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> - <i class="fa fa-exclamation-triangle"></i> <strong>{% trans "Warning" %}:</strong> {% trans "Hypervisor doesn't have any Computes" %} - </div> - </div> - {% endif %} - </div> -{% endblock %} diff --git a/computes/templates/computes/list.html b/computes/templates/computes/list.html new file mode 100644 index 0000000..18a2886 --- /dev/null +++ b/computes/templates/computes/list.html @@ -0,0 +1,69 @@ +{% extends "base.html" %} +{% load i18n %} +{% load static %} +{% load common_tags %} +{% load icons %} +{% block title %}{% trans "Computes" %}{% endblock %} +{% block content %} +<div class="row"> + <div class="col-lg-12"> + {% include 'create_comp_block.html' %} + {% include 'search_block.html' %} + <h3 class="page-header">{% trans "Computes" %}</h3> + </div> +</div> +{% include 'errors_block.html' %} +<div class="row"> + {% if not computes %} + <div class="col-lg-12"> + <div class="alert alert-warning alert-dismissable"> + <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> + {% icon 'exclamation-triangle '%} <strong>{% trans "Warning" %}:</strong> {% trans "You don't have any computes" %} + </div> + </div> + {% else %} + <div class="col-lg-12"> + <table class="table table-striped table-hover"> + <thead> + <tr> + <th span="col">{% trans "Name" %}</th> + <th span="col">{% trans "Status" %}</th> + <th span="col">{% trans "Details" %}</th> + <th span="col">{% trans "Actions" %}</th> + </tr> + </thead> + <tbody class="searchable"> + {% for compute in computes %} + <tr> + <td> + {{ compute.name }} + </td> + <td> + {% if compute.status is True %}{% trans "Connected" %}{% else %}{% trans "Not Connected" %}{% endif %} + </td> + <td> + {{ compute.details|default:"" }} + </td> + <td> + <div class="float-right btn-group"> + {% if compute.status is True %} + <a class="btn btn-success" title="{%trans "Overview" %}" href="{% url 'overview' compute.id %}">{% icon 'eye' %}</a> + {% else %} + <a class="btn btn-light" title="{%trans "Overview" %}">{% icon 'eye' %}</a> + {% endif %} + <a class="btn btn-primary" title="{%trans "Edit" %}" href="{% url 'compute_update' compute.id %}">{% icon 'pencil' %}</a> + <a class="btn btn-danger" title="{%trans "Delete" %}" href="{% url 'compute_delete' compute.id %}">{% icon 'times' %}</a> + </div> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {% endif %} +</div> +{% endblock content %} + +{% block script %} +<script src="{% static "js/filter-table.js" %}"></script> +{% endblock script %} diff --git a/computes/templates/create_comp_block.html b/computes/templates/create_comp_block.html index 37f5d5a..478fea4 100644 --- a/computes/templates/create_comp_block.html +++ b/computes/templates/create_comp_block.html @@ -1,7 +1,7 @@ {% load i18n %} {% load bootstrap4 %} {% load icons %} -<div class="btn-group float-right" role="group" aria-label="Add host button group"> +<div class="btn-group float-right mt-1" role="group" aria-label="Add host button group"> <a href="{% url 'add_tcp_host' %}" class="btn btn-success">{% trans "TCP" %}</a> <a href="{% url 'add_ssh_host' %}" class="btn btn-success">{% trans "SSH" %}</a> <a href="{% url 'add_tls_host' %}" class="btn btn-success">{% trans "TLS" %}</a> diff --git a/computes/tests.py b/computes/tests.py index ca4cb4c..391abd5 100644 --- a/computes/tests.py +++ b/computes/tests.py @@ -1,3 +1,4 @@ +from django.core.exceptions import ObjectDoesNotExist from django.shortcuts import reverse from django.test import TestCase @@ -20,6 +21,52 @@ class ComputesTestCase(TestCase): response = self.client.get(reverse('computes')) self.assertEqual(response.status_code, 200) + def test_create_update_delete(self): + response = self.client.get(reverse('add_socket_host')) + self.assertEqual(response.status_code, 200) + + response = self.client.post( + reverse('add_socket_host'), + { + 'name': 'l1', + 'details': 'Created', + 'hostname': 'localhost', + 'type': 4, + }, + ) + self.assertRedirects(response, reverse('computes')) + + compute = Compute.objects.get(pk=2) + self.assertEqual(compute.name, 'l1') + self.assertEqual(compute.details, 'Created') + + response = self.client.get(reverse('compute_update', args=[2])) + self.assertEqual(response.status_code, 200) + + response = self.client.post( + reverse('compute_update', args=[2]), + { + 'name': 'l2', + 'details': 'Updated', + 'hostname': 'localhost', + 'type': 4, + }, + ) + self.assertRedirects(response, reverse('computes')) + + compute = Compute.objects.get(pk=2) + self.assertEqual(compute.name, 'l2') + self.assertEqual(compute.details, 'Updated') + + response = self.client.get(reverse('compute_delete', args=[2])) + self.assertEqual(response.status_code, 200) + + response = self.client.post(reverse('compute_delete', args=[2])) + self.assertRedirects(response, reverse('computes')) + + with self.assertRaises(ObjectDoesNotExist): + Compute.objects.get(id=2) + def test_overview(self): response = self.client.get(reverse('overview', args=[1])) self.assertEqual(response.status_code, 200) diff --git a/computes/urls.py b/computes/urls.py index 54f67ff..81c37a5 100644 --- a/computes/urls.py +++ b/computes/urls.py @@ -12,28 +12,32 @@ from storages.views import get_volumes, storage, storages urlpatterns = [ path('', views.computes, name='computes'), - path('add_tcp_host/', views.add_host, {'FormClass': forms.TcpComputeForm}, name='add_tcp_host'), - path('add_ssh_host/', views.add_host, {'FormClass': forms.SshComputeForm}, name='add_ssh_host'), - path('add_tls_host/', views.add_host, {'FormClass': forms.TlsComputeForm}, name='add_tls_host'), - path('add_socket_host/', views.add_host, {'FormClass': forms.SocketComputeForm}, name='add_socket_host'), - path('<int:compute_id>/', include([ - path('', views.overview, name='overview'), - path('statistics', views.compute_graph, name='compute_graph'), - path('instances/', instances, name='instances'), - path('storages/', storages, name='storages'), - path('storage/<str:pool>/volumes', get_volumes, name='volumes'), - path('storage/<str:pool>/', storage, name='storage'), - path('networks/', networks, name='networks'), - path('network/<str:pool>/', network, name='network'), - path('interfaces/', interfaces, name='interfaces'), - path('interface/<str:iface>/', interface, name='interface'), - path('nwfilters/', nwfilters, name='nwfilters'), - path('nwfilter/<str:nwfltr>/', nwfilter, name='nwfilter'), - path('secrets/', secrets, name='secrets'), - path('create/', create_instance_select_type, name='create_instance_select_type'), - path('create/archs/<str:arch>/machines/<str:machine>/', create_instance, name='create_instance'), - path('archs/<str:arch>/machines', views.get_compute_machine_types, name='machines'), - path('archs/<str:arch>/machines/<str:machine>/disks/<str:disk>/buses', views.get_compute_disk_buses, name='buses'), - path('archs/<str:arch>/machines/<str:machine>/capabilities', views.get_dom_capabilities, name='domcaps'), - ])), + path('add_tcp_host/', views.compute_create, {'FormClass': forms.TcpComputeForm}, name='add_tcp_host'), + path('add_ssh_host/', views.compute_create, {'FormClass': forms.SshComputeForm}, name='add_ssh_host'), + path('add_tls_host/', views.compute_create, {'FormClass': forms.TlsComputeForm}, name='add_tls_host'), + path('add_socket_host/', views.compute_create, {'FormClass': forms.SocketComputeForm}, name='add_socket_host'), + path( + '<int:compute_id>/', + include([ + path('', views.overview, name='overview'), + path('update/', views.compute_update, name='compute_update'), + path('delete/', views.compute_delete, name='compute_delete'), + path('statistics', views.compute_graph, name='compute_graph'), + path('instances/', instances, name='instances'), + path('storages/', storages, name='storages'), + path('storage/<str:pool>/volumes', get_volumes, name='volumes'), + path('storage/<str:pool>/', storage, name='storage'), + path('networks/', networks, name='networks'), + path('network/<str:pool>/', network, name='network'), + path('interfaces/', interfaces, name='interfaces'), + path('interface/<str:iface>/', interface, name='interface'), + path('nwfilters/', nwfilters, name='nwfilters'), + path('nwfilter/<str:nwfltr>/', nwfilter, name='nwfilter'), + path('secrets/', secrets, name='secrets'), + path('create/', create_instance_select_type, name='create_instance_select_type'), + path('create/archs/<str:arch>/machines/<str:machine>/', create_instance, name='create_instance'), + path('archs/<str:arch>/machines', views.get_compute_machine_types, name='machines'), + path('archs/<str:arch>/machines/<str:machine>/disks/<str:disk>/buses', views.get_compute_disk_buses, name='buses'), + path('archs/<str:arch>/machines/<str:machine>/capabilities', views.get_dom_capabilities, name='domcaps'), + ])), ] diff --git a/computes/views.py b/computes/views.py index 2685760..7a947d7 100644 --- a/computes/views.py +++ b/computes/views.py @@ -22,58 +22,10 @@ def computes(request): :param request: :return: """ - def get_hosts_status(computes): - """ - Function return all hosts all vds on host - """ - compute_data = [] - for compute in computes: - compute_data.append({ - 'id': compute.id, - 'name': compute.name, - 'hostname': compute.hostname, - 'status': connection_manager.host_is_up(compute.type, compute.hostname), - 'type': compute.type, - 'login': compute.login, - 'password': compute.password, - 'details': compute.details - }) - return compute_data - error_messages = [] computes = Compute.objects.filter().order_by('name') - computes_info = get_hosts_status(computes) - if request.method == 'POST': - if 'host_del' in request.POST: - compute_id = request.POST.get('host_id', '') - try: - del_user_inst_on_host = UserInstance.objects.filter(instance__compute_id=compute_id) - del_user_inst_on_host.delete() - finally: - try: - del_inst_on_host = Instance.objects.filter(compute_id=compute_id) - del_inst_on_host.delete() - finally: - del_host = Compute.objects.get(id=compute_id) - del_host.delete() - return HttpResponseRedirect(request.get_full_path()) - if 'host_edit' in request.POST: - form = ComputeEditHostForm(request.POST) - if form.is_valid(): - data = form.cleaned_data - compute_edit = Compute.objects.get(id=data['host_id']) - compute_edit.name = data['name'] - compute_edit.hostname = data['hostname'] - compute_edit.login = data['login'] - compute_edit.password = data['password'] - compute_edit.details = data['details'] - compute_edit.save() - return HttpResponseRedirect(request.get_full_path()) - else: - for msg_err in form.errors.values(): - error_messages.append(msg_err.as_text()) - return render(request, 'computes.html', locals()) + return render(request, 'computes/list.html', {'computes': computes}) @superuser_only @@ -87,7 +39,7 @@ def overview(request, compute_id): error_messages = [] compute = get_object_or_404(Compute, pk=compute_id) status = 'true' if connection_manager.host_is_up(compute.type, compute.hostname) is True else 'false' - + try: conn = wvmHostDetails( compute.hostname, @@ -108,6 +60,51 @@ def overview(request, compute_id): return render(request, 'overview.html', locals()) +@superuser_only +def compute_create(request, FormClass): + form = FormClass(request.POST or None) + if form.is_valid(): + form.save() + return redirect(reverse('computes')) + + return render(request, 'computes/form.html', {'form': form}) + + +@superuser_only +def compute_update(request, compute_id): + compute = get_object_or_404(Compute, pk=compute_id) + + if compute.type == 1: + FormClass = TcpComputeForm + elif compute.type == 2: + FormClass = SshComputeForm + elif compute.type == 3: + FormClass = TlsComputeForm + elif compute.type == 4: + FormClass = SocketComputeForm + + form = FormClass(request.POST or None, instance=compute) + if form.is_valid(): + form.save() + return redirect(reverse('computes')) + + return render(request, 'computes/form.html', {'form': form}) + + +@superuser_only +def compute_delete(request, compute_id): + compute = get_object_or_404(Compute, pk=compute_id) + if request.method == 'POST': + compute.delete() + return redirect('computes') + + return render( + request, + 'common/confirm_delete.html', + {'object': compute}, + ) + + def compute_graph(request, compute_id): """ :param request: @@ -248,13 +245,3 @@ def get_dom_capabilities(request, compute_id, arch, machine): pass return HttpResponse(json.dumps(data)) - - -@superuser_only -def add_host(request, FormClass): - form = FormClass(request.POST or None) - if form.is_valid(): - form.save() - return redirect(reverse('computes')) - - return render(request, 'computes/form.html', {'form': form}) diff --git a/templates/search_block.html b/templates/search_block.html new file mode 100644 index 0000000..7f2b515 --- /dev/null +++ b/templates/search_block.html @@ -0,0 +1,4 @@ +{% load i18n %} +<div class="float-right search"> + <input id="filter" class="form-control" type="text" placeholder="{% trans "Search" %}"> +</div>