diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b2bdbaca18cf98079a5620d2d6228c4e40c0f397
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,49 @@
+# .gitignore for a Django project
+
+# Ignore Celery-related files
+celerybeat-schedule
+celerybeat.pid
+celeryd.pid
+celery_*.pickle
+
+# Ignore Celery worker state file
+worker_state
+
+# Ignore Celery task result files
+celery-results/
+
+# Ignore Python bytecode files
+__pycache__/
+*.pyc
+
+# Ignore database files
+*.sqlite3
+
+# Ignore system-specific files
+*.pyo
+*.pyd
+*.db
+
+# Ignore environment configuration files
+.env
+
+# Ignore virtual environment folders
+venv/
+env/
+*.env/
+
+# Ignore local development settings
+local_settings.py
+
+# Ignore logs and data
+log/
+*.log
+*.sql
+
+# Ignore backup files
+*.bak
+*.swp
+*~
+
+# Ignore local development files
+local/
diff --git a/README.md b/README.md
index 392c90e74ac272b4e55b1cc3552453107abf6307..0805048548b0729e0126938dc7320b73afbd993b 100644
--- a/README.md
+++ b/README.md
@@ -91,3 +91,10 @@ For open source projects, say how it is licensed.
 
 ## Project status
 If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
+
+## Setup instructions
+
+celery -A netmon2.celery beat -l info
+celery -A netmon2.celery worker -l info
+
+python manage.py runserver
diff --git a/crawler/__init__.py b/crawler/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/crawler/admin.py b/crawler/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..86f8c993668a8fc9995a0b97cf3e8232204bb654
--- /dev/null
+++ b/crawler/admin.py
@@ -0,0 +1,6 @@
+from django.contrib import admin
+
+from .models import Monitor, Node
+
+admin.site.register(Monitor)
+admin.site.register(Node)
diff --git a/crawler/apps.py b/crawler/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..88478dd0deea8cf509ed16ee133edd42ca6d667d
--- /dev/null
+++ b/crawler/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class CrawlerConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'crawler'
diff --git a/crawler/migrations/0001_initial.py b/crawler/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..8127f13771a904b97a68051ba919d625694e634f
--- /dev/null
+++ b/crawler/migrations/0001_initial.py
@@ -0,0 +1,36 @@
+# Generated by Django 4.2.7 on 2023-12-07 14:36
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Node',
+            fields=[
+                ('node_id', models.CharField(max_length=12, primary_key=True, serialize=False)),
+                ('node_name', models.CharField(max_length=64)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Monitor',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('notify', models.BooleanField(default=True)),
+                ('node_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crawler.node')),
+                ('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'unique_together': {('user_id', 'node_id')},
+            },
+        ),
+    ]
diff --git a/crawler/migrations/0002_node_entry_date.py b/crawler/migrations/0002_node_entry_date.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd052f64ae89ab425d1c51eb668577302fcfccb7
--- /dev/null
+++ b/crawler/migrations/0002_node_entry_date.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.2.7 on 2023-12-08 06:15
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('crawler', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='node',
+            name='entry_date',
+            field=models.DateField(auto_now_add=True, default=django.utils.timezone.now),
+            preserve_default=False,
+        ),
+    ]
diff --git a/crawler/migrations/0003_ownsettings.py b/crawler/migrations/0003_ownsettings.py
new file mode 100644
index 0000000000000000000000000000000000000000..7351ada184a87d1e7c83989441830dbb136410af
--- /dev/null
+++ b/crawler/migrations/0003_ownsettings.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.2.7 on 2023-12-08 08:09
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('crawler', '0002_node_entry_date'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='OwnSettings',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('TBL_NODE_DELETE_AFTER_AMOUNT_OF_DAYS', models.PositiveIntegerField(default=0)),
+            ],
+        ),
+    ]
diff --git a/crawler/migrations/0004_delete_ownsettings.py b/crawler/migrations/0004_delete_ownsettings.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d4f32969c3a517b396f0c743ac498c647af57f8
--- /dev/null
+++ b/crawler/migrations/0004_delete_ownsettings.py
@@ -0,0 +1,16 @@
+# Generated by Django 4.2.7 on 2023-12-08 13:07
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('crawler', '0003_ownsettings'),
+    ]
+
+    operations = [
+        migrations.DeleteModel(
+            name='OwnSettings',
+        ),
+    ]
diff --git a/crawler/migrations/0005_node_is_online.py b/crawler/migrations/0005_node_is_online.py
new file mode 100644
index 0000000000000000000000000000000000000000..f175764be4e4642e3212cc5c8b526b14f4bca563
--- /dev/null
+++ b/crawler/migrations/0005_node_is_online.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.7 on 2023-12-09 19:03
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('crawler', '0004_delete_ownsettings'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='node',
+            name='is_online',
+            field=models.BooleanField(default=True),
+        ),
+    ]
diff --git a/crawler/migrations/0006_alter_monitor_notify.py b/crawler/migrations/0006_alter_monitor_notify.py
new file mode 100644
index 0000000000000000000000000000000000000000..47dac1ffcdee70be2f5e5e9131676bbf98ac888a
--- /dev/null
+++ b/crawler/migrations/0006_alter_monitor_notify.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.7 on 2023-12-09 19:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('crawler', '0005_node_is_online'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='monitor',
+            name='notify',
+            field=models.BooleanField(),
+        ),
+    ]
diff --git a/crawler/migrations/__init__.py b/crawler/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/crawler/models.py b/crawler/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..35b182356f625d098183bc333721739841db6fd0
--- /dev/null
+++ b/crawler/models.py
@@ -0,0 +1,24 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+class Node(models.Model):
+    # id is a 12 char long mac addr
+    node_id = models.CharField(max_length=12, primary_key=True)
+    # hostname of the node
+    node_name = models.CharField(max_length=64)
+    entry_date = models.DateField(auto_now_add=True)
+    is_online = models.BooleanField(default=True)
+    
+    def __str__(self):
+        return f"{self.node_id} - {self.node_name} - {self.entry_date} - {self.is_online}"
+
+class Monitor(models.Model):
+    user_id = models.ForeignKey(User, on_delete=models.CASCADE)
+    node_id = models.ForeignKey(Node, on_delete=models.CASCADE)
+    notify = models.BooleanField()
+
+    def __str__(self):
+        return f"{self.user_id.username} - {self.node_id.node_id} - {self.notify}"
+
+    class Meta:
+        unique_together = ['user_id', 'node_id']
diff --git a/crawler/tasks.py b/crawler/tasks.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3a0345b7815fb0590ecfe6378fe81cf2f23b8b6
--- /dev/null
+++ b/crawler/tasks.py
@@ -0,0 +1,62 @@
+# tasks.py
+from celery import shared_task
+from django.conf import settings
+from crawler.models import Node, Monitor
+from datetime import timedelta
+from django.utils import timezone
+import requests
+import logging
+
+logger = logging.getLogger(__name__)
+
+# This task is responsible for cleaning up expired entries in the Node model based on the entry_date.
+@shared_task
+def cleanup_expired_entries():
+    expired_days = settings.EXPIRED_DAYS_FOR_CLEANUP
+    current_date = timezone.now().date()
+    cutoff_date = current_date + timedelta(days=-expired_days)
+
+    # Filter entries with entry_date older than the cutoff_date
+    nodes_to_delete = Node.objects.filter(entry_date__lt=cutoff_date)
+
+    # Exclude nodes that are referenced in the Monitor table
+    nodes_to_delete = nodes_to_delete.exclude(node_id__in=Monitor.objects.values('node_id'))
+
+    try:
+        # Delete the filtered entries
+        nodes_to_delete.delete()
+    except Exception as e:
+        logger.error(f'Error cleaning up expired entries: {e}')
+
+
+
+#This task fetches JSON data from a given URL and updates the Node model accordingly.
+@shared_task
+def update_node_table_task():
+    json_url = settings.JSON_DATA_URL
+    
+    try:
+        response = requests.get(json_url)
+        response.raise_for_status()
+
+        json_data = response.json()
+
+        for entry in json_data.get("nodes"):
+            node_id = entry.get('node_id')
+            node_name = entry.get('hostname')
+            is_online = entry.get('is_online')
+
+            # Update or create the node
+            node, created = Node.objects.update_or_create(
+                node_id=node_id,
+                defaults={
+                    'node_name': node_name,
+                    'entry_date': timezone.now().date().strftime('%Y-%m-%d'),
+                    'is_online': is_online
+                }
+            )
+
+        logger.info('Successfully updated the Node table')
+
+    except requests.RequestException as e:
+        logger.error(f'Error fetching JSON data: {e}')
diff --git a/crawler/tests.py b/crawler/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/crawler/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/crawler/views.py b/crawler/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..91ea44a218fbd2f408430959283f0419c921093e
--- /dev/null
+++ b/crawler/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000000000000000000000000000000000000..0a66c52b2cd36f9f5bd73e86f80eab50ced25669
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    """Run administrative tasks."""
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'netmon2.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/netmon2/__init__.py b/netmon2/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/netmon2/asgi.py b/netmon2/asgi.py
new file mode 100644
index 0000000000000000000000000000000000000000..188f1ab7c93dfc08df999eff50202c94509e549f
--- /dev/null
+++ b/netmon2/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for netmon2 project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'netmon2.settings')
+
+application = get_asgi_application()
diff --git a/netmon2/celery.py b/netmon2/celery.py
new file mode 100644
index 0000000000000000000000000000000000000000..13a1e8d1035cb02e890f56ec4729cc3db72ee55e
--- /dev/null
+++ b/netmon2/celery.py
@@ -0,0 +1,16 @@
+# celery.py
+from __future__ import absolute_import, unicode_literals
+import os
+from celery import Celery
+
+# set the default Django settings module for the 'celery' program.
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'netmon2.settings')
+
+# create a Celery instance and configure it using the settings from Django
+app = Celery('netmon2')
+
+# Load task modules from all registered Django app configs.
+app.config_from_object('django.conf:settings', namespace='CELERY')
+
+# Auto-discover tasks in all installed apps
+app.autodiscover_tasks()
diff --git a/netmon2/settings.py b/netmon2/settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..47930d97074079095ae871572ec67690a6ca1279
--- /dev/null
+++ b/netmon2/settings.py
@@ -0,0 +1,154 @@
+"""
+Django settings for netmon2 project.
+
+Generated by 'django-admin startproject' using Django 4.2.7.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/4.2/ref/settings/
+"""
+import os
+from pathlib import Path
+from celery.schedules import crontab
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = '<YOUR_SECRED_KEY>'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = False
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    "crawler.apps.CrawlerConfig",
+    "django_celery_results",
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'netmon2.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [os.path.join(BASE_DIR, 'netmon_core', 'templates')],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'netmon2.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': BASE_DIR / 'db.sqlite3',
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/4.2/topics/i18n/
+
+LANGUAGE_CODE = 'de'
+
+TIME_ZONE = 'Europe/Berlin'
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/4.2/howto/static-files/
+
+STATIC_URL = 'static/'
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+CELERY_BROKER_URL = 'redis://localhost:6379/0'  # Use your broker URL
+CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'  # Use your result backend URL
+CELERY_ACCEPT_CONTENT = ['json']
+CELERY_TASK_SERIALIZER = 'json'
+CELERY_RESULT_SERIALIZER = 'json'
+CELERY_TIMEZONE = 'Europe/Berlin'
+
+CELERY_BEAT_SCHEDULE = {
+    'update-node-table': {
+        'task': 'crawler.tasks.update_node_table_task',
+        'schedule': crontab(minute="*/5"),
+    },
+    'cleanup_expired_entries': {
+        'task': 'crawler.tasks.cleanup_expired_entries',
+        'schedule': crontab(hour="*/24"),
+    }
+}
+
+EXPIRED_DAYS_FOR_CLEANUP = 5  # Adjust this based on your needs
+JSON_DATA_URL = "<YOUR_JSON_URL>"
+
+AUTHENTICATION_BACKENDS = [
+    'django.contrib.auth.backends.ModelBackend',
+]
+
+LOGIN_REDIRECT_URL = 'unmonitored_nodes'
+LOGIN_URL = 'login'  # Adjust to match your login URL pattern name
diff --git a/netmon2/urls.py b/netmon2/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..26bf1b395cfd8f3e11e6f46b1ff7a5adbfdf87e1
--- /dev/null
+++ b/netmon2/urls.py
@@ -0,0 +1,24 @@
+"""
+URL configuration for netmon2 project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/4.2/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import include, path
+
+urlpatterns = [
+    path('admin/', admin.site.urls),
+    path('', include('netmon_core.urls')),
+    # Add other URL patterns as needed
+]
diff --git a/netmon2/wsgi.py b/netmon2/wsgi.py
new file mode 100644
index 0000000000000000000000000000000000000000..62b9def192fc81adbce4a7f4dc2a4e8c863aed6c
--- /dev/null
+++ b/netmon2/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for netmon2 project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'netmon2.settings')
+
+application = get_wsgi_application()
diff --git a/netmon_core/__init__.py b/netmon_core/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/netmon_core/admin.py b/netmon_core/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e
--- /dev/null
+++ b/netmon_core/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/netmon_core/apps.py b/netmon_core/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..143e765cefd33a566f248f7541f1e4fd4e45bb0e
--- /dev/null
+++ b/netmon_core/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class NetmonCoreConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'netmon_core'
diff --git a/netmon_core/forms.py b/netmon_core/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..60efcf055df0175987d6c98854cffc2c95b8b99f
--- /dev/null
+++ b/netmon_core/forms.py
@@ -0,0 +1,10 @@
+# forms.py
+from django import forms
+from django.contrib.auth.forms import UserCreationForm
+from django.contrib.auth.models import User
+
+class CustomUserCreationForm(UserCreationForm):
+    # Add any additional fields if needed
+    class Meta:
+        model = User
+        fields = ('username', 'email', 'password1', 'password2')
diff --git a/netmon_core/migrations/__init__.py b/netmon_core/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/netmon_core/models.py b/netmon_core/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..71a836239075aa6e6e4ecb700e9c42c95c022d91
--- /dev/null
+++ b/netmon_core/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/netmon_core/templates/index.html b/netmon_core/templates/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..94f981310c00018821481ac3f9c786fbb1c48177
--- /dev/null
+++ b/netmon_core/templates/index.html
@@ -0,0 +1,7 @@
+<!-- registration/login.html -->
+<form method="post" action="{% url 'login' %}">
+    {% csrf_token %}
+    {{ form.as_p }}
+    <button type="submit">Login</button>
+</form>
+<a href="{% url 'register' %}">Register</a>
diff --git a/netmon_core/templates/protected/unmonitored_nodes.html b/netmon_core/templates/protected/unmonitored_nodes.html
new file mode 100644
index 0000000000000000000000000000000000000000..0842841ede37b413ea4372f006a33d1a7660dae7
--- /dev/null
+++ b/netmon_core/templates/protected/unmonitored_nodes.html
@@ -0,0 +1,17 @@
+<!-- unmonitored_nodes.html -->
+
+<h2>Unmonitored Nodes</h2>
+<a href="{% url 'user_monitored_nodes' %}">Monitored</a>
+<a href="{% url 'user_logout' %}">Logout</a>
+
+<ul>
+  {% for node in nodes %}
+    <li>
+      {{ node.node_id }} - {{ node.node_name }} - {{ node.entry_date }}
+      <form method="post" action="{% url 'add_to_monitored' node.node_id %}">
+        {% csrf_token %}
+        <button type="submit">Add to Monitored</button>
+      </form>
+    </li>
+  {% endfor %}
+</ul>
diff --git a/netmon_core/templates/protected/user_monitored_nodes.html b/netmon_core/templates/protected/user_monitored_nodes.html
new file mode 100644
index 0000000000000000000000000000000000000000..cd6271e6a7fe3a3bbd9b412e9ad0e5f2b183b9ee
--- /dev/null
+++ b/netmon_core/templates/protected/user_monitored_nodes.html
@@ -0,0 +1,23 @@
+<!-- user_monitored_nodes.html -->
+
+<h2>Nodes Monitored by {{ request.user.username }}</h2>
+
+<a href="{% url 'unmonitored_nodes' %}">Unmonitored</a>
+<a href="{% url 'user_logout' %}">Logout</a>
+<ul>
+  {% for node in monitored_nodes %}
+    <li>
+	    {{ node.node_id.node_id }} - {{ node.node_id.node_name }} - {{ node.node_id.entry_date }} - {{ node.node_id.is_online }}
+      
+      <form method="post" action="{% url 'toggle_notify' node.id %}" class="notify-form">
+        {% csrf_token %}
+        <input type="checkbox" name="notify" {% if node.notify %}checked{% endif %} onclick="this.form.submit()">
+      </form>
+
+      <form method="post" action="{% url 'delete_monitored_node' node.id %}" class="delete-form">
+        {% csrf_token %}
+        <button type="submit">Delete</button>
+      </form>
+    </li>
+  {% endfor %}
+</ul>
diff --git a/netmon_core/templates/registration/register.html b/netmon_core/templates/registration/register.html
new file mode 100644
index 0000000000000000000000000000000000000000..f18cfb563bb27804f9f01e0236a8d1ea1d2ef325
--- /dev/null
+++ b/netmon_core/templates/registration/register.html
@@ -0,0 +1,7 @@
+<!-- registration/register.html -->
+<form method="post" action="{% url 'register' %}">
+    {% csrf_token %}
+    {{ form.as_p }}
+    <button type="submit">Register</button>
+</form>
+
diff --git a/netmon_core/tests.py b/netmon_core/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/netmon_core/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/netmon_core/urls.py b/netmon_core/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e7977ff5b9565ce07fd7f9a2e761517776af781
--- /dev/null
+++ b/netmon_core/urls.py
@@ -0,0 +1,16 @@
+# urls.py
+from django.urls import path
+from .views import register, CustomLoginView, unmonitored_nodes, user_monitored_nodes, delete_monitored_node, add_to_monitored, toggle_notify, user_logout
+
+urlpatterns = [
+    path('register/', register, name='register'),
+    path('', CustomLoginView.as_view(), name='login'),
+    path('logout/', user_logout, name='user_logout'),
+    path('protected/unmonitored/', unmonitored_nodes, name='unmonitored_nodes'),
+    path('protected/monitored/', user_monitored_nodes, name='user_monitored_nodes'),
+    path('protected/delete_monitored_node/<int:monitor_id>/', delete_monitored_node, name='delete_monitored_node'),
+    path('protected/add_to_monitored/<str:node_id>/', add_to_monitored, name='add_to_monitored'),
+    path('protected/toggle_notify/<int:monitor_id>/', toggle_notify, name='toggle_notify'),
+    # Add other URL patterns as needed
+]
+
diff --git a/netmon_core/views.py b/netmon_core/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..f78a421f9135abd1800c427e6916f54896e89ed2
--- /dev/null
+++ b/netmon_core/views.py
@@ -0,0 +1,89 @@
+from django.shortcuts import render, redirect, get_object_or_404
+from django.contrib.auth import login, logout
+from django.contrib.auth.views import LoginView
+from django.contrib.auth.decorators import login_required
+from crawler.models import Node, Monitor
+from .forms import CustomUserCreationForm
+
+
+def register(request):
+    if request.method == 'POST':
+        form = CustomUserCreationForm(request.POST)
+        if form.is_valid():
+            user = form.save()
+            login(request, user)  # Log the user in after registration
+            return redirect('login')  # Redirect to the home page or any other page
+    else:
+        form = CustomUserCreationForm()
+
+    return render(request, 'registration/register.html', {'form': form})
+
+class CustomLoginView(LoginView):
+    template_name = 'index.html'  # Specify the login template
+
+def user_logout(request):
+    # Logout the user
+    logout(request)
+    # Redirect to the home page or any other page after logout
+    return redirect('login')  # Replace 'home' with the actual name or URL of your home page
+
+
+@login_required
+def unmonitored_nodes(request):
+    # Query all Node entries that do not have corresponding entries in Monitor
+    nodes = Node.objects.exclude(monitor__isnull=False)
+
+    # Pass the queried data to the template
+    context = {'nodes': nodes}
+    
+    # Render the template with the context
+    return render(request, 'protected/unmonitored_nodes.html', context)
+
+@login_required
+def user_monitored_nodes(request):
+    # Query all Node entries monitored by the current user
+    monitored_nodes = Monitor.objects.filter(user_id=request.user)
+
+    # Pass the queried data to the template
+    context = {'monitored_nodes': monitored_nodes}
+    
+    # Render the template with the context
+    return render(request, 'protected/user_monitored_nodes.html', context)
+
+@login_required
+def delete_monitored_node(request, monitor_id):
+    # Get the monitoring entry
+    monitor_entry = get_object_or_404(Monitor, id=monitor_id)
+
+    # Ensure that the user making the request is the owner of the monitoring entry
+    if request.user == monitor_entry.user_id:
+        # Delete the monitoring entry
+        monitor_entry.delete()
+
+    # Redirect back to the user_monitored_nodes view
+    return redirect('user_monitored_nodes')
+
+@login_required
+def add_to_monitored(request, node_id):
+    # Get the Node entry
+    node = get_object_or_404(Node, node_id=node_id)
+
+    # Check if the monitoring entry already exists
+    if not Monitor.objects.filter(user_id=request.user, node_id=node).exists():
+        # Create a new monitoring entry
+        Monitor.objects.create(user_id=request.user, node_id=node)
+
+    # Redirect back to the unmonitored_nodes view
+    return redirect('unmonitored_nodes')
+
+@login_required
+def toggle_notify(request, monitor_id):
+    # Get the monitoring entry
+    monitor_entry = get_object_or_404(Monitor, id=monitor_id)
+
+    # Toggle the notify field
+    monitor_entry.notify = not monitor_entry.notify
+    monitor_entry.save()
+
+    # Redirect back to the user_monitored_nodes view
+    return redirect('user_monitored_nodes')