Compare commits

...

3 Commits

Author SHA1 Message Date
Flatlogic Bot
5b806425d9 1.3 2025-10-28 05:18:37 +00:00
Flatlogic Bot
e741fe47b1 1.2 2025-10-28 05:01:04 +00:00
Flatlogic Bot
5614a714bc 1.1 fix 2025-10-28 04:58:10 +00:00
27 changed files with 440 additions and 216 deletions

View File

@ -1,8 +1,13 @@
from django.contrib import admin
from .models import Ticket
from .models import Application, Vulnerability
@admin.register(Ticket)
class TicketAdmin(admin.ModelAdmin):
list_display = ('subject', 'status', 'priority', 'requester_email', 'created_at')
list_filter = ('status', 'priority')
search_fields = ('subject', 'requester_email', 'description')
@admin.register(Application)
class ApplicationAdmin(admin.ModelAdmin):
list_display = ('name', 'version', 'vendor')
search_fields = ('name', 'vendor')
@admin.register(Vulnerability)
class VulnerabilityAdmin(admin.ModelAdmin):
list_display = ('cve_id', 'application', 'severity', 'status', 'discovered_at')
list_filter = ('severity', 'status', 'application__name')
search_fields = ('cve_id', 'description')

View File

@ -1,7 +1,8 @@
from django import forms
from .models import Ticket
class TicketForm(forms.ModelForm):
class Meta:
model = Ticket
fields = ['subject', 'requester_email', 'priority', 'description']
class UploadFileForm(forms.Form):
file = forms.FileField()
class VulnerabilitySearchForm(forms.Form):
application_name = forms.CharField(max_length=100, label="Application Name")
application_website = forms.URLField(label="Application Website", required=False)

View File

@ -0,0 +1,39 @@
# Generated by Django 5.2.7 on 2025-10-27 12:23
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Application',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('version', models.CharField(max_length=100)),
('vendor', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='Vulnerability',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cve_id', models.CharField(max_length=50, unique=True)),
('description', models.TextField()),
('severity', models.CharField(choices=[('Critical', 'Critical'), ('High', 'High'), ('Medium', 'Medium'), ('Low', 'Low')], max_length=10)),
('status', models.CharField(choices=[('New', 'New'), ('Acknowledged', 'Acknowledged'), ('In-Progress', 'In-Progress'), ('Resolved', 'Resolved')], default='New', max_length=20)),
('discovered_at', models.DateTimeField(auto_now_add=True)),
('last_updated', models.DateTimeField(auto_now=True)),
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vulnerabilities', to='core.application')),
],
),
migrations.DeleteModel(
name='Ticket',
),
]

View File

@ -1,25 +1,34 @@
from django.db import models
class Ticket(models.Model):
STATUS_CHOICES = [
('open', 'Open'),
('in_progress', 'In Progress'),
('closed', 'Closed'),
]
PRIORITY_CHOICES = [
('low', 'Low'),
('medium', 'Medium'),
('high', 'High'),
]
subject = models.CharField(max_length=255)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='open')
priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default='medium')
requester_email = models.EmailField()
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Application(models.Model):
name = models.CharField(max_length=255)
version = models.CharField(max_length=100)
vendor = models.CharField(max_length=255)
def __str__(self):
return self.subject
return f"{self.name} {self.version}"
class Vulnerability(models.Model):
SEVERITY_CHOICES = [
('Critical', 'Critical'),
('High', 'High'),
('Medium', 'Medium'),
('Low', 'Low'),
]
STATUS_CHOICES = [
('New', 'New'),
('Acknowledged', 'Acknowledged'),
('In-Progress', 'In-Progress'),
('Resolved', 'Resolved'),
]
cve_id = models.CharField(max_length=50, unique=True)
description = models.TextField()
severity = models.CharField(max_length=10, choices=SEVERITY_CHOICES)
application = models.ForeignKey(Application, on_delete=models.CASCADE, related_name='vulnerabilities')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='New')
discovered_at = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.cve_id

69
core/templates/base.html Normal file
View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ project_name|default:"Vulnerability Scanner" }}{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
{% load static %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}">
</head>
<body>
<div class="d-flex" id="wrapper">
<!-- Sidebar -->
<div class="bg-light border-right" id="sidebar-wrapper">
<div class="sidebar-heading">
<a class="text-decoration-none text-dark" href="/">
<i class="bi bi-shield-shaded"></i> {{ project_name|default:"Vulnerability Scanner" }}
</a>
</div>
<div class="list-group list-group-flush">
<a href="/" class="list-group-item list-group-item-action bg-light"><i class="bi bi-speedometer2 me-2"></i>Dashboard</a>
<a href="#" class="list-group-item list-group-item-action bg-light"><i class="bi bi-file-earmark-arrow-up me-2"></i>Upload Inventory</a>
<a href="#" class="list-group-item list-group-item-action bg-light"><i class="bi bi-bug me-2"></i>Vulnerabilities</a>
<a href="#" class="list-group-item list-group-item-action bg-light"><i class="bi bi-gear me-2"></i>Settings</a>
</div>
</div>
<!-- /#sidebar-wrapper -->
<!-- Page Content -->
<div id="page-content-wrapper">
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<button class="btn btn-primary" id="menu-toggle"><i class="bi bi-list"></i></button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mt-2 mt-lg-0">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> Admin
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Profile</a></li>
<li><a class="dropdown-item" href="#">Logout</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container-fluid p-4">
{% block content %}{% endblock %}
</main>
</div>
<!-- /#page-content-wrapper -->
</div>
<!-- /#wrapper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.getElementById("menu-toggle").addEventListener("click", function(e) {
e.preventDefault();
document.getElementById("wrapper").classList.toggle("toggled");
});
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
{% extends 'base.html' %}
{% block title %}{{ article.title }}{% endblock %}
{% block content %}
<div class="container mt-5">
<h1>{{ article.title }}</h1>
<p class="text-muted">Published on {{ article.created_at|date:"F d, Y" }}</p>
<hr>
<div>
{{ article.content|safe }}
</div>
</div>
{% endblock %}

View File

@ -1,157 +1,122 @@
<!doctype html>
<html lang="en">
{% extends 'base.html' %}
{% load static %}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ project_name }}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}">
<meta property="twitter:description" content="{{ project_description }}">
{% endif %}
{% if project_image_url %}
<meta property="og:image" content="{{ project_image_url }}">
<meta property="twitter:image" content="{{ project_image_url }}">
{% endif %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.08);
--card-border-color: rgba(255, 255, 255, 0.18);
}
{% block title %}Dashboard - {{ project_name }}{% endblock %}
* {
box-sizing: border-box;
}
{% block content %}
<div class="container-fluid">
<h1 class="mt-4">Dashboard</h1>
<p>Overview of application vulnerabilities.</p>
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(130deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
<div class="row">
<div class="col-lg-3 col-md-6 mb-4">
<div class="card text-white bg-primary">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="fs-4 fw-bold">{{ total_applications }}</div>
<div>Total Applications</div>
</div>
<i class="bi bi-box-fill fs-1"></i>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-4">
<div class="card text-white bg-warning">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="fs-4 fw-bold">{{ total_vulnerabilities }}</div>
<div>Total Vulnerabilities</div>
</div>
<i class="bi bi-bug-fill fs-1"></i>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-4">
<div class="card text-white bg-danger">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="fs-4 fw-bold">{{ critical_vulnerabilities }}</div>
<div>Critical Vulnerabilities</div>
</div>
<i class="bi bi-exclamation-octagon-fill fs-1"></i>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-4">
<div class="card text-white bg-info">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="fs-4 fw-bold">{{ new_vulnerabilities }}</div>
<div>New Vulnerabilities</div>
</div>
<i class="bi bi-patch-plus-fill fs-1"></i>
</div>
</div>
</div>
</div>
</div>
body::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140' viewBox='0 0 140 140'><path d='M-20 20L160 20M20 -20L20 160' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
animation: bg-pan 24s linear infinite;
z-index: -1;
}
<div class="card mb-4">
<div class="card-header">
<i class="bi bi-search me-2"></i>
Vulnerability Search
</div>
<div class="card-body">
<form action="{% url 'vulnerability_search' %}" method="get">
<div class="input-group">
<input type="text" class="form-control" name="application_name" placeholder="Enter application name...">
<button class="btn btn-primary" type="submit">Search</button>
</div>
</form>
</div>
</div>
@keyframes bg-pan {
0% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(-140px, -140px, 0);
}
}
main {
padding: clamp(2rem, 4vw, 3rem);
width: min(640px, 92vw);
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 20px;
padding: clamp(2rem, 4vw, 3rem);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.35);
}
h1 {
margin: 0 0 1.2rem;
font-weight: 700;
font-size: clamp(2.2rem, 3vw + 1.3rem, 3rem);
letter-spacing: -0.04em;
}
p {
margin: 0.6rem 0;
font-size: 1.1rem;
line-height: 1.7;
opacity: 0.92;
}
.loader {
margin: 1.5rem auto;
width: 56px;
height: 56px;
border: 4px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
code {
background: rgba(15, 23, 42, 0.35);
padding: 0.2rem 0.6rem;
border-radius: 0.5rem;
font-size: 0.95rem;
}
footer {
margin-top: 2.4rem;
font-size: 0.86rem;
opacity: 0.7;
}
</style>
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<i class="bi bi-box-seam me-2"></i>
Application Inventory
</div>
<a href="{% url 'upload_inventory' %}" class="btn btn-primary">Upload Inventory</a>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Version</th>
<th>Vendor</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for app in applications %}
<tr>
<td>{{ app.name }}</td>
<td>{{ app.version }}</td>
<td>{{ app.vendor }}</td>
<td>
<button class="btn btn-sm btn-primary">Details</button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center">No applications found.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<p>Appwizzy AI is collecting your requirements and applying the first changes.</p>
<p>This page will refresh automatically as the plan is implemented.</p>
<p>
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
</p>
</div>
<footer>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
</footer>
</main>
</body>
</html>
</div>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-4">
<h2>Upload Application Inventory</h2>
<p>Upload a CSV file with the following columns: Name, Version, Vendor.</p>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Upload</button>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends 'base.html' %}
{% block content %}
<div class="container mt-4">
<h2>Vulnerability Search</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Search</button>
</form>
{% if results %}
<hr>
<h3>Search Results</h3>
<div class="list-group">
{% for result in results %}
<div class="list-group-item">
<h5 class="mb-1">{{ result.cve_id }}</h5>
<p class="mb-1">{{ result.description }}</p>
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endblock %}

View File

@ -1,7 +1,9 @@
from django.urls import path
from .views import home
from .views import dashboard, upload_inventory, vulnerability_search
urlpatterns = [
path("", home, name="home"),
path("", dashboard, name="dashboard"),
path("upload/", upload_inventory, name="upload_inventory"),
path("search/", vulnerability_search, name="vulnerability_search"),
]

View File

@ -1,37 +1,72 @@
import os
import platform
from django.shortcuts import render, redirect
from .models import Application, Vulnerability
from .forms import UploadFileForm, VulnerabilitySearchForm
import csv
import io
import requests
from django import get_version as django_version
from django.shortcuts import render
from django.urls import reverse_lazy
from django.utils import timezone
from django.views.generic.edit import CreateView
from .forms import TicketForm
from .models import Ticket
def home(request):
"""Render the landing screen with loader and environment details."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
def dashboard(request):
# Placeholder data
total_apps = Application.objects.count()
total_vulns = Vulnerability.objects.count()
critical_vulns = Vulnerability.objects.filter(severity='Critical').count()
new_vulns = Vulnerability.objects.filter(status='New').count()
applications = Application.objects.all()
context = {
"project_name": "New Style",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
'total_applications': total_apps,
'total_vulnerabilities': total_vulns,
'critical_vulnerabilities': critical_vulns,
'new_vulnerabilities': new_vulns,
'applications': applications,
"project_name": "Vulnerability Scanner",
}
return render(request, "core/index.html", context)
def upload_inventory(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
try:
csv_file = request.FILES['file']
decoded_file = io.TextIOWrapper(csv_file.file, encoding='utf-8', newline='', errors='ignore')
reader = csv.reader(decoded_file)
# Skip header row
next(reader)
for row in reader:
if row and len(row) == 3:
print(f"Processing row: {row}")
Application.objects.create(
name=row[0],
version=row[1],
vendor=row[2],
)
return redirect('dashboard')
except Exception as e:
print(f"An error occurred: {e}")
form.add_error(None, f"An error occurred: {e}")
else:
form = UploadFileForm()
return render(request, 'core/upload_inventory.html', {'form': form})
class TicketCreateView(CreateView):
model = Ticket
form_class = TicketForm
template_name = "core/ticket_create.html"
success_url = reverse_lazy("home")
def vulnerability_search(request):
form = VulnerabilitySearchForm()
results = []
if request.method == 'POST':
form = VulnerabilitySearchForm(request.POST)
if form.is_valid():
application_name = form.cleaned_data['application_name']
# Basic search using NVD API
url = f"https://services.nvd.nist.gov/rest/json/cves/1.0?keyword={application_name}"
try:
response = requests.get(url)
data = response.json()
if 'result' in data:
for cve_item in data['result']['CVE_Items']:
cve_id = cve_item['cve']['CVE_data_meta']['ID']
description = cve_item['cve']['description']['description_data'][0]['value']
results.append({'cve_id': cve_id, 'description': description})
except requests.exceptions.RequestException as e:
form.add_error(None, f"Error fetching data from NVD: {e}")
return render(request, 'core/vulnerability_search.html', {'form': form, 'results': results})

View File

@ -1,3 +1,4 @@
Django==5.2.7
mysqlclient==2.2.7
python-dotenv==1.1.1
requests

46
static/css/custom.css Normal file
View File

@ -0,0 +1,46 @@
body {
overflow-x: hidden;
}
#sidebar-wrapper {
min-height: 100vh;
margin-left: -15rem;
transition: margin .25s ease-out;
}
#sidebar-wrapper .sidebar-heading {
padding: 0.875rem 1.25rem;
font-size: 1.2rem;
font-weight: bold;
}
#sidebar-wrapper .list-group {
width: 15rem;
}
#page-content-wrapper {
min-width: 100vw;
}
#wrapper.toggled #sidebar-wrapper {
margin-left: 0;
}
@media (min-width: 768px) {
#sidebar-wrapper {
margin-left: 0;
}
#page-content-wrapper {
min-width: 0;
width: 100%;
}
#wrapper.toggled #sidebar-wrapper {
margin-left: -15rem;
}
}
.card-body .fs-1 {
opacity: 0.7;
}