feat: views for listing, viewing and deleting specific VMs
ci/woodpecker/push/woodpecker Pipeline failed Details

wip-payments
Aravinth Manivannan 2022-06-18 22:12:07 +05:30
parent 2fb1a3d0d1
commit d5dc06be18
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88
8 changed files with 172 additions and 2 deletions

View File

@ -4,7 +4,21 @@
<p class="secondary-nav__option-link">Hello, {{ username }}!</p>
</div>
<div class="secondary-nav__options">
<a href="{% url 'dash.instances.new' %}" class="secondary-nav__option-link">Instances</a>
<details class="secondary-nav__options-group" {{ open_instances }}>
<summary><a href="{% url 'dash.instances.new' %}">Manage Instances</a></summary>
<div class="secondary-nav__options-group-options">
<a href="{% url 'dash.instances.new' %}" class="secondary-nav__option-link">
Create Instance
</a>
</div>
<div class="secondary-nav__options-group-options">
<a href="{% url 'dash.instances.list' %}" class="secondary-nav__option-link">
My Instances
</a>
</div>
</details>
<details class="secondary-nav__options-group" {{ open_support }}>
<summary><a href="{% url 'support.home' %}">Support</a></summary>
<div class="secondary-nav__options-group-options">
@ -16,6 +30,7 @@
</a>
</div>
</details>
<a href="/foo" class="secondary-nav__option-link">Billing</a>
</div>
<div class="secondary-nav__options">

View File

@ -0,0 +1,17 @@
{% extends 'dash/common/base.html' %} {% block dash %}
<h2>Are you sure you wan't to delete VM {{ instance.name }}</h2>
<form
action="{% url 'dash.instances.delete' name=instance.name %}"
method="POST"
class="dash__form"
accept-charset="utf-8"
>
{% include "common/components/error.html" %} {% csrf_token %}
<div class="form__action-container">
<button class="form__submit--danger" type="submit">Delete Instance</button>
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends 'dash/common/base.html' %} {% block dash %}
<h2>{{ title }}</h2>
<ul class="list-instance__container">
{% for instance in instances %}
<li class="list-instance__item">
<a
href="{% url 'dash.instances.view' name=instance.name %}"
class="href">{{ instance.name }}</a>
</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends 'dash/common/base.html' %} {% block dash %}
<h2>{{ title }}</h2>
<p>
Name: {{ instance.name }}
</p>
<p>
Configuration: {{ instance.configuration }}
</p>
<p>
Created On: {{ instance.created_at }}
</p>
<p>
Click <a href="{% url 'dash.instances.delete' name=instance.name %}">here</a> to delete instance
</p>
{% endblock %}

View File

@ -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,
)

View File

@ -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/<str:name>/", view_instance, name="dash.instances.view"),
path("instance/delete/<str:name>/", delete_instance, name="dash.instances.delete"),
path("", home, name="dash.home"),
]

View File

@ -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"))

View File

@ -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;