From d5dc06be18314735bc337a1e9e0dce3269b6397f Mon Sep 17 00:00:00 2001 From: realaravinth Date: Sat, 18 Jun 2022 22:12:07 +0530 Subject: [PATCH] feat: views for listing, viewing and deleting specific VMs --- .../dash/common/components/secondary-nav.html | 17 ++++++- .../dash/instances/delete/index.html | 17 +++++++ dash/templates/dash/instances/list/index.html | 13 +++++ dash/templates/dash/instances/view/index.html | 17 +++++++ dash/tests.py | 49 +++++++++++++++++++ dash/urls.py | 5 +- dash/views.py | 42 ++++++++++++++++ static/css/main.css | 14 ++++++ 8 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 dash/templates/dash/instances/delete/index.html create mode 100644 dash/templates/dash/instances/list/index.html create mode 100644 dash/templates/dash/instances/view/index.html diff --git a/dash/templates/dash/common/components/secondary-nav.html b/dash/templates/dash/common/components/secondary-nav.html index 410bf9c..8a476c5 100644 --- a/dash/templates/dash/common/components/secondary-nav.html +++ b/dash/templates/dash/common/components/secondary-nav.html @@ -4,7 +4,21 @@
- Instances +
+ Manage Instances + + +
+ +
Support
@@ -16,6 +30,7 @@
+ Billing
diff --git a/dash/templates/dash/instances/delete/index.html b/dash/templates/dash/instances/delete/index.html new file mode 100644 index 0000000..b6647e5 --- /dev/null +++ b/dash/templates/dash/instances/delete/index.html @@ -0,0 +1,17 @@ +{% extends 'dash/common/base.html' %} {% block dash %} + +

Are you sure you wan't to delete VM {{ instance.name }}

+
+ {% include "common/components/error.html" %} {% csrf_token %} +
+ +
+
+ + +{% endblock %} diff --git a/dash/templates/dash/instances/list/index.html b/dash/templates/dash/instances/list/index.html new file mode 100644 index 0000000..bfe6d29 --- /dev/null +++ b/dash/templates/dash/instances/list/index.html @@ -0,0 +1,13 @@ +{% extends 'dash/common/base.html' %} {% block dash %} +

{{ title }}

+ + +{% endblock %} diff --git a/dash/templates/dash/instances/view/index.html b/dash/templates/dash/instances/view/index.html new file mode 100644 index 0000000..7c5d07c --- /dev/null +++ b/dash/templates/dash/instances/view/index.html @@ -0,0 +1,17 @@ +{% extends 'dash/common/base.html' %} {% block dash %} +

{{ title }}

+

+Name: {{ instance.name }} +

+

+Configuration: {{ instance.configuration }} +

+

+Created On: {{ instance.created_at }} +

+ +

+Click here to delete instance +

+ +{% endblock %} diff --git a/dash/tests.py b/dash/tests.py index f22131a..d3ebca3 100644 --- a/dash/tests.py +++ b/dash/tests.py @@ -174,6 +174,7 @@ class CreateInstance(TestCase): self.assertEqual(b"Logout" in resp.content, True) self.assertEqual(str.encode(test) in resp.content, True) + # create instance payload = { "name": "test_create_instance_renders", "configuration": self.instance_config[0].name, @@ -193,11 +194,14 @@ class CreateInstance(TestCase): ).exists(), True, ) + instance = Instance.objects.get(name=payload["name"], owned_by=self.user) + # try to create instance with duplicate name resp = c.post(reverse("dash.instances.new"), payload) self.assertEqual(resp.status_code, 400) self.assertEqual(b"Instance name exists" in resp.content, True) + # try to create instance with a VM configuration that doesn't exist payload = { "name": f"2{payload['name']}", "configuration": f"2{payload['name']}", @@ -205,3 +209,48 @@ class CreateInstance(TestCase): resp = c.post(reverse("dash.instances.new"), payload) self.assertEqual(resp.status_code, 400) self.assertEqual(b"Configuration doesn" in resp.content, True) + + # list instances + resp = c.get(reverse("dash.instances.list")) + self.assertEqual(resp.status_code, 200) + self.assertEqual(str.encode(instance.name) in resp.content, True) + + # view instance details + resp = c.get(reverse("dash.instances.view", args=(instance.name,))) + self.assertEqual(resp.status_code, 200) + self.assertEqual(str.encode(instance.name) in resp.content, True) + + # delete instance details + delete_uri = reverse("dash.instances.delete", args=(instance.name,)) + + ## will ask for sudo confirmation + resp = c.get(delete_uri) + self.assertEqual(resp.status_code, 302) + ctx = {"next": delete_uri} + self.assertEqual( + resp.headers["location"], f"{reverse('accounts.sudo')}?{urlencode(ctx)}" + ) + + ## give sudo confirmation + payload = {"password": self.password, "next": ctx["next"]} + resp = c.post(reverse("accounts.sudo"), payload) + self.assertEqual(resp.status_code, 302) + self.assertEqual(resp.headers["location"], ctx["next"]) + + resp = c.get(delete_uri) + self.assertEqual(resp.status_code, 200) + self.assertEqual( + str.encode(f"to delete VM {instance.name}") in resp.content, True + ) + + resp = c.post(delete_uri) + self.assertEqual(resp.status_code, 302) + self.assertEqual(resp.headers["location"], reverse("dash.home")) + self.assertEqual( + Instance.objects.filter( + name=instance.name, + owned_by=self.user, + configuration_id=self.instance_config[0], + ).exists(), + False, + ) diff --git a/dash/urls.py b/dash/urls.py index a9d857a..9537f16 100644 --- a/dash/urls.py +++ b/dash/urls.py @@ -15,9 +15,12 @@ from django.contrib import admin from django.urls import path, include -from .views import home, create_instance +from .views import home, create_instance, delete_instance, list_instances, view_instance urlpatterns = [ path("instance/new/", create_instance, name="dash.instances.new"), + path("instance/list/", list_instances, name="dash.instances.list"), + path("instance/view//", view_instance, name="dash.instances.view"), + path("instance/delete//", delete_instance, name="dash.instances.delete"), path("", home, name="dash.home"), ] diff --git a/dash/views.py b/dash/views.py index 41bd15e..b78090a 100644 --- a/dash/views.py +++ b/dash/views.py @@ -23,6 +23,8 @@ from django.urls import reverse from .models import Instance, InstanceConfiguration +from accounts.decorators import confirm_access + def default_ctx(title: str, username: str): """ @@ -31,6 +33,7 @@ def default_ctx(title: str, username: str): return { "title": title, "username": username, + "open_instances": "open", } @@ -95,3 +98,42 @@ def create_instance(request): ) instance.save() return redirect(reverse("dash.home")) + + +@login_required +def list_instances(request): + PAGE_TITLE = "My Instances" + user = request.user + + instances = Instance.objects.filter(owned_by=user) + ctx = default_ctx(title=PAGE_TITLE, username=user.username) + ctx["instances"] = instances + return render(request, "dash/instances/list/index.html", context=ctx) + + +@login_required +def view_instance(request, name: str): + PAGE_TITLE = f"Hostea Instance: {name}" + user = request.user + + instance = get_object_or_404(Instance, owned_by=user, name=name) + ctx = default_ctx(title=PAGE_TITLE, username=user.username) + ctx["instance"] = instance + return render(request, "dash/instances/view/index.html", context=ctx) + + +@login_required +@csrf_protect +@confirm_access +def delete_instance(request, name): + PAGE_TITLE = f"Delete Instance {name}" + user = request.user + + instance = get_object_or_404(Instance, name=name, owned_by=user) + if request.method == "GET": + ctx = default_ctx(title=PAGE_TITLE, username=user.username) + ctx["instance"] = instance + return render(request, "dash/instances/delete/index.html", context=ctx) + + instance.delete() + return redirect(reverse("dash.home")) diff --git a/static/css/main.css b/static/css/main.css index 9ce7339..4b633f9 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -595,6 +595,20 @@ fieldset { margin: 10px; } +.form__submit--danger { + width: 100%; + display: block; + margin: 10px 0; + color: #fff; + border: none; + font-weight: 400; + padding: 15px; + cursor: pointer; + background-color: #e11d21; +} + + + /* .form__label { margin: 5px 0;