forked from Hostea/dashboard
feat: views for listing, viewing and deleting specific VMs
parent
2fb1a3d0d1
commit
d5dc06be18
|
@ -4,7 +4,21 @@
|
||||||
<p class="secondary-nav__option-link">Hello, {{ username }}!</p>
|
<p class="secondary-nav__option-link">Hello, {{ username }}!</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="secondary-nav__options">
|
<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 }}>
|
<details class="secondary-nav__options-group" {{ open_support }}>
|
||||||
<summary><a href="{% url 'support.home' %}">Support</a></summary>
|
<summary><a href="{% url 'support.home' %}">Support</a></summary>
|
||||||
<div class="secondary-nav__options-group-options">
|
<div class="secondary-nav__options-group-options">
|
||||||
|
@ -16,6 +30,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<a href="/foo" class="secondary-nav__option-link">Billing</a>
|
<a href="/foo" class="secondary-nav__option-link">Billing</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="secondary-nav__options">
|
<div class="secondary-nav__options">
|
||||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -174,6 +174,7 @@ class CreateInstance(TestCase):
|
||||||
self.assertEqual(b"Logout" in resp.content, True)
|
self.assertEqual(b"Logout" in resp.content, True)
|
||||||
self.assertEqual(str.encode(test) in resp.content, True)
|
self.assertEqual(str.encode(test) in resp.content, True)
|
||||||
|
|
||||||
|
# create instance
|
||||||
payload = {
|
payload = {
|
||||||
"name": "test_create_instance_renders",
|
"name": "test_create_instance_renders",
|
||||||
"configuration": self.instance_config[0].name,
|
"configuration": self.instance_config[0].name,
|
||||||
|
@ -193,11 +194,14 @@ class CreateInstance(TestCase):
|
||||||
).exists(),
|
).exists(),
|
||||||
True,
|
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)
|
resp = c.post(reverse("dash.instances.new"), payload)
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
self.assertEqual(b"Instance name exists" in resp.content, True)
|
self.assertEqual(b"Instance name exists" in resp.content, True)
|
||||||
|
|
||||||
|
# try to create instance with a VM configuration that doesn't exist
|
||||||
payload = {
|
payload = {
|
||||||
"name": f"2{payload['name']}",
|
"name": f"2{payload['name']}",
|
||||||
"configuration": f"2{payload['name']}",
|
"configuration": f"2{payload['name']}",
|
||||||
|
@ -205,3 +209,48 @@ class CreateInstance(TestCase):
|
||||||
resp = c.post(reverse("dash.instances.new"), payload)
|
resp = c.post(reverse("dash.instances.new"), payload)
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
self.assertEqual(b"Configuration doesn" in resp.content, True)
|
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,
|
||||||
|
)
|
||||||
|
|
|
@ -15,9 +15,12 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
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 = [
|
urlpatterns = [
|
||||||
path("instance/new/", create_instance, name="dash.instances.new"),
|
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"),
|
path("", home, name="dash.home"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -23,6 +23,8 @@ from django.urls import reverse
|
||||||
|
|
||||||
from .models import Instance, InstanceConfiguration
|
from .models import Instance, InstanceConfiguration
|
||||||
|
|
||||||
|
from accounts.decorators import confirm_access
|
||||||
|
|
||||||
|
|
||||||
def default_ctx(title: str, username: str):
|
def default_ctx(title: str, username: str):
|
||||||
"""
|
"""
|
||||||
|
@ -31,6 +33,7 @@ def default_ctx(title: str, username: str):
|
||||||
return {
|
return {
|
||||||
"title": title,
|
"title": title,
|
||||||
"username": username,
|
"username": username,
|
||||||
|
"open_instances": "open",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,3 +98,42 @@ def create_instance(request):
|
||||||
)
|
)
|
||||||
instance.save()
|
instance.save()
|
||||||
return redirect(reverse("dash.home"))
|
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"))
|
||||||
|
|
|
@ -595,6 +595,20 @@ fieldset {
|
||||||
margin: 10px;
|
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 {
|
.form__label {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
|
|
Loading…
Reference in New Issue