diff --git a/dash/admin.py b/dash/admin.py index ec19ca5..4fc4fa8 100644 --- a/dash/admin.py +++ b/dash/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin -from .models import InstanceConfiguration +from .models import InstanceConfiguration, Instance admin.site.register(InstanceConfiguration) +admin.site.register(Instance) diff --git a/dash/migrations/0002_instance.py b/dash/migrations/0002_instance.py new file mode 100644 index 0000000..4b1ceb9 --- /dev/null +++ b/dash/migrations/0002_instance.py @@ -0,0 +1,45 @@ +# Generated by Django 4.0.3 on 2022-06-17 15:47 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("dash", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Instance", + fields=[ + ("ID", models.AutoField(primary_key=True, serialize=False)), + ( + "name", + models.CharField( + max_length=32, + unique=True, + verbose_name="Name of this Instance. Also Serves as subdomain", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "configuration_id", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="dash.instanceconfiguration", + ), + ), + ( + "owned_by", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/dash/migrations/0003_alter_instance_name.py b/dash/migrations/0003_alter_instance_name.py new file mode 100644 index 0000000..b6561e5 --- /dev/null +++ b/dash/migrations/0003_alter_instance_name.py @@ -0,0 +1,22 @@ +# Generated by Django 4.0.3 on 2022-06-17 17:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("dash", "0002_instance"), + ] + + operations = [ + migrations.AlterField( + model_name="instance", + name="name", + field=models.CharField( + max_length=200, + unique=True, + verbose_name="Name of this Instance. Also Serves as subdomain", + ), + ), + ] diff --git a/dash/models.py b/dash/models.py index b400f2f..f932660 100644 --- a/dash/models.py +++ b/dash/models.py @@ -50,3 +50,25 @@ class InstanceConfiguration(models.Model): fields=["ram", "cpu", "storage"], name="no_repeat_config" ) ] + + +class Instance(models.Model): + """ + Hostea instances + """ + + owned_by = models.OneToOneField(User, on_delete=models.CASCADE) + ID = models.AutoField(primary_key=True) + name = models.CharField( + "Name of this Instance. Also Serves as subdomain", + unique=True, + max_length=200, + ) + configuration_id = models.ForeignKey( + InstanceConfiguration, on_delete=models.PROTECT + ) + + created_at = models.DateTimeField(auto_now_add=True, blank=True) + + def __str__(self): + return f"{self.owned_by}'s instance '{self.name}'" diff --git a/dash/templates/dash/common/components/secondary-nav.html b/dash/templates/dash/common/components/secondary-nav.html index a883290..410bf9c 100644 --- a/dash/templates/dash/common/components/secondary-nav.html +++ b/dash/templates/dash/common/components/secondary-nav.html @@ -4,7 +4,7 @@
- Instances + Instances
Support
diff --git a/dash/templates/dash/instances/new/index.html b/dash/templates/dash/instances/new/index.html new file mode 100644 index 0000000..796a24f --- /dev/null +++ b/dash/templates/dash/instances/new/index.html @@ -0,0 +1,53 @@ +{% extends 'dash/common/base.html' %} {% block dash %} +

{{ title }}

+
+ {% include "common/components/error.html" %} {% csrf_token %} + + +
+ Instance Configuration + {% for config in configurations %} + + {% endfor %} +
+
+ +
+
+ +{% endblock %} diff --git a/dash/tests.py b/dash/tests.py index 7af6025..b9d36ee 100644 --- a/dash/tests.py +++ b/dash/tests.py @@ -19,7 +19,45 @@ from django.test import TestCase, Client, override_settings from django.conf import settings from django.db.utils import IntegrityError -from .models import InstanceConfiguration +from .models import InstanceConfiguration, Instance + + +def register_util(t: TestCase, username: str): + t.password = "password121231" + t.username = username + t.email = f"{t.username}@example.org" + t.user = get_user_model().objects.create( + username=t.username, + email=t.email, + ) + t.user.set_password(t.password) + t.user.save() + + +def create_configurations(t: TestCase): + t.instance_config = [ + InstanceConfiguration(name="Personal", ram=0.5, cpu=1, storage=25), + InstanceConfiguration(name="Enthusiast", ram=2, cpu=2, storage=50), + InstanceConfiguration(name="Small Business", ram=8, cpu=4, storage=64), + InstanceConfiguration(name="Enterprise", ram=64, cpu=24, storage=1024), + ] + + for instance in t.instance_config: + instance.save() + print(f"[*][init] Instance {instance.name} is saved") + t.assertEqual( + InstanceConfiguration.objects.filter(name=instance.name).exists(), True + ) + + +def login_util(t: TestCase, c: Client, redirect_to: str): + payload = { + "login": t.username, + "password": t.password, + } + resp = c.post(reverse("accounts.login"), payload) + t.assertEqual(resp.status_code, 302) + t.assertEqual(resp.headers["location"], reverse(redirect_to)) class DashHome(TestCase): @@ -28,15 +66,7 @@ class DashHome(TestCase): """ def setUp(self): - self.password = "password121231" - self.username = "dashboard_home_user" - self.email = f"{self.username}@example.org" - self.user = get_user_model().objects.create( - username=self.username, - email=self.email, - ) - self.user.set_password(self.password) - self.user.save() + register_util(t=self, username="dashboard_home_user") def test_dash_is_protected(self): """ @@ -60,13 +90,7 @@ class DashHome(TestCase): c = Client() # username login works - payload = { - "login": self.username, - "password": self.password, - } - resp = c.post(reverse("accounts.login"), payload) - self.assertEqual(resp.status_code, 302) - self.assertEqual(resp.headers["location"], reverse("accounts.home")) + login_util(t=self, c=c, redirect_to="accounts.home") # email login works resp = c.get(reverse("dash.home")) @@ -98,3 +122,54 @@ class InstancesConfig(TestCase): name="test config 3", ram=0.5, cpu=1, storage=0.5 ) config3.save() + + +class CreateInstance(TestCase): + def setUp(self): + register_util(t=self, username="createinstance_user") + create_configurations(t=self) + + def test_create_instance_renders(self): + c = Client() + login_util(self, c, "accounts.home") + urls = [(reverse("dash.instances.new"), "Instance Configuration")] + for (url, test) in urls: + print(f"[*] Testing URI: {url}") + resp = c.get(url) + self.assertEqual(resp.status_code, 200) + self.assertEqual(b"Billing" in resp.content, True) + self.assertEqual(b"Support" in resp.content, True) + self.assertEqual(b"Logout" in resp.content, True) + self.assertEqual(str.encode(test) in resp.content, True) + + payload = { + "name": "test_create_instance_renders", + "configuration": self.instance_config[0].name, + } + + self.assertEqual(Instance.objects.filter(name=payload["name"]).exists(), False) + + resp = c.post(reverse("dash.instances.new"), payload) + self.assertEqual(resp.status_code, 302) + self.assertEqual(resp.headers["location"], reverse("dash.home")) + + self.assertEqual( + Instance.objects.filter( + name=payload["name"], + owned_by=self.user, + configuration_id=self.instance_config[0], + ).exists(), + True, + ) + + resp = c.post(reverse("dash.instances.new"), payload) + self.assertEqual(resp.status_code, 400) + self.assertEqual(b"Instance name exists" in resp.content, True) + + payload = { + "name": f"2{payload['name']}", + "configuration": f"2{payload['name']}", + } + resp = c.post(reverse("dash.instances.new"), payload) + self.assertEqual(resp.status_code, 400) + self.assertEqual(b"Configuration doesn" in resp.content, True) diff --git a/dash/urls.py b/dash/urls.py index 437c358..a9d857a 100644 --- a/dash/urls.py +++ b/dash/urls.py @@ -15,8 +15,9 @@ from django.contrib import admin from django.urls import path, include -from .views import home +from .views import home, create_instance urlpatterns = [ + path("instance/new/", create_instance, name="dash.instances.new"), path("", home, name="dash.home"), ] diff --git a/dash/views.py b/dash/views.py index a5e17c6..41bd15e 100644 --- a/dash/views.py +++ b/dash/views.py @@ -21,6 +21,8 @@ from django.http import HttpResponse from django.views.decorators.csrf import csrf_protect from django.urls import reverse +from .models import Instance, InstanceConfiguration + def default_ctx(title: str, username: str): """ @@ -38,7 +40,58 @@ def home(request): Dashboard homepage view """ PAGE_TITLE = "Home" - username = request.user - ctx = default_ctx(title=PAGE_TITLE, username=username.username) + user = request.user + ctx = default_ctx(title=PAGE_TITLE, username=user.username) + + instances = Instance.objects.filter(owned_by=user) + if instances: + ctx["instances"] = instances return render(request, "dash/home/index.html", context=ctx) + + +@login_required +@csrf_protect +def create_instance(request): + """ + Create instance view + """ + PAGE_TITLE = "Create Instance" + user = request.user + + def get_ctx(): + ctx = default_ctx(title=PAGE_TITLE, username=user.username) + configurations = InstanceConfiguration.objects.all() + ctx["configurations"] = InstanceConfiguration.objects.all() + return ctx + + if request.method == "GET": + ctx = get_ctx() + return render(request, "dash/instances/new/index.html", context=ctx) + + name = request.POST["name"] + if Instance.objects.filter(name=name).exists(): + ctx = get_ctx() + ctx["error"] = { + "title": "Can't create instance", + "reason": "Instance name exists, please try again with a different name", + } + print(ctx["error"]["reason"]) + return render(request, "dash/instances/new/index.html", status=400, context=ctx) + + configuration = request.POST["configuration"] + if not InstanceConfiguration.objects.filter(name=configuration).exists(): + ctx = get_ctx() + ctx["error"] = { + "title": "Can't create instance", + "reason": "Configuration doesn't exist, please try again.", + } + print(ctx["error"]["reason"]) + return render(request, "dash/instances/new/index.html", status=400, context=ctx) + + configuration = get_object_or_404(InstanceConfiguration, name=configuration) + instance = Instance( + name=name, configuration_id=configuration, owned_by=request.user + ) + instance.save() + return redirect(reverse("dash.home")) diff --git a/static/css/main.css b/static/css/main.css index 338f6ac..816fda1 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -522,7 +522,6 @@ footer { content: " ►"; } - .secondary-nav__options-group[open] > summary::after { margin-left: 30px; content: " ▼"; @@ -531,11 +530,72 @@ footer { /* secondary nav ends */ .dash__main { - flex: 2; display: flex; flex-direction: column; justify-content: space-between; min-height: calc(-63px + 100vh); + flex:2; } +/* dash forms starts */ +.dash__form { + display: flex; + flex-direction: column; + width: 50%; + margin: auto; + background-color: ping; + padding: 0 10px; +} + +fieldset { + border: none; +} + +.configuration__container { + padding: 20px; + padding-left: 0; + margin: 10px 20px; + margin-left: 0; + border: 1px solid rgb(211, 211, 211); + display: inline-flex; + flex-direction: column; +} + +.configuration__name { + margin-left: 20px; +} + +.configuration__spec { + margin-left: 40px; +} + +/* +.form__label { + margin: 5px 0; +} + +.form__input { + display: block; + width: 100%; + margin: 10px 0; + padding: 5px 0; + height: 25px; +} + +.form__submit { + width: 100%; + display: block; + margin: 10px 0; + color: #fff; + border: none; + font-weight: 400; + padding: 15px; + cursor: pointer; + background-color: rgb(0, 128, 0); +} + +*/ + +/* dash forms ends */ + /* dashbaord ends */