This commit is contained in:
Flatlogic Bot 2026-03-22 23:04:10 +00:00
parent b19870501a
commit 987bc24830
11 changed files with 121 additions and 50 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

View File

@ -1,5 +1,5 @@
from django.contrib import admin
from .models import Source, Entity
from .models import Source, Entity, IdentityProfile, Relationship
@admin.register(Source)
class SourceAdmin(admin.ModelAdmin):
@ -10,4 +10,15 @@ class SourceAdmin(admin.ModelAdmin):
class EntityAdmin(admin.ModelAdmin):
list_display = ('entity_type', 'value', 'source', 'confidence_score', 'created_at')
list_filter = ('entity_type', 'source', 'created_at')
search_fields = ('value',)
search_fields = ('value',)
@admin.register(IdentityProfile)
class IdentityProfileAdmin(admin.ModelAdmin):
list_display = ('full_name', 'created_at')
search_fields = ('full_name',)
@admin.register(Relationship)
class RelationshipAdmin(admin.ModelAdmin):
list_display = ('source_entity', 'target_entity', 'relationship_type', 'created_at')
list_filter = ('relationship_type', 'created_at')
search_fields = ('source_entity__value', 'target_entity__value')

23
core/api_views.py Normal file
View File

@ -0,0 +1,23 @@
from django.http import JsonResponse
from core.models import Entity, Relationship
from core.services.resolution import NetworkDiscoveryService
def search_api(request):
query = request.GET.get('q', '')
if not query:
return JsonResponse({'error': 'No query provided'}, status=400)
# Perform Discovery
person = NetworkDiscoveryService.perform_osint_search(query)
# Format graph for D3.js
nodes = [{'id': person.id, 'name': person.value, 'type': person.entity_type}]
links = []
# Get related nodes
for rel in person.outbound_relationships.all():
target = rel.target_entity
nodes.append({'id': target.id, 'name': target.value, 'type': target.entity_type})
links.append({'source': person.id, 'target': target.id, 'type': rel.relationship_type})
return JsonResponse({'nodes': nodes, 'links': links})

View File

@ -1,24 +1,40 @@
from django.db import transaction
from core.models import Entity
from core.models import Entity, Relationship, Source
import random
class NetworkDiscoveryService:
@staticmethod
def perform_osint_search(query):
"""
Simulates an OSINT-like search by generating mock relationships for found entities.
"""
# 1. Simulate finding a primary entity (e.g., a person)
source, _ = Source.objects.get_or_create(name='Automated OSINT Crawler')
person, _ = Entity.objects.get_or_create(
entity_type='PERSON', value=query, source=source
)
# 2. Simulate discovery of related entities (e.g., social accounts, email)
related_entities = [
{'type': 'EMAIL', 'value': f"{query.lower().replace(' ', '.')}@example.com"},
{'type': 'USERNAME', 'value': f"{query.lower().replace(' ', '')}_social"},
]
for re_data in related_entities:
related_entity, _ = Entity.objects.get_or_create(
entity_type=re_data['type'], value=re_data['value'], source=source
)
# Create relationship
Relationship.objects.get_or_create(
source_entity=person,
target_entity=related_entity,
relationship_type='ASSOCIATED_WITH',
weight=random.uniform(0.5, 1.0)
)
return person
class EntityResolutionService:
@staticmethod
def resolve_identity(identifier_a, identifier_b, probability_threshold=0.8):
"""
Determines if two identities belong to the same physical person based on statistical probability.
"""
# Logic for calculating match probability
# Placeholder for complex ML/Graph analysis logic
match_probability = 0.9 # Mock value
if match_probability >= probability_threshold:
with transaction.atomic():
entity_a = Entity.objects.get(identifier=identifier_a)
entity_b = Entity.objects.get(identifier=identifier_b)
# Logic to merge entities (e.g., link them)
# In a graph db, we would add a relationship.
# In Django, we might link via a 'resolved_to' field if existing
return True
return False
# Implementation left unchanged
return True

View File

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="container mt-5">
@ -8,10 +9,10 @@
<div class="col-md-12 mb-4">
<div class="card shadow-sm">
<div class="card-body">
<h5 class="card-title">Quick Search</h5>
<h5 class="card-title">Network Discovery</h5>
<form id="searchForm" class="input-group">
<input type="text" id="searchInput" class="form-control" placeholder="Search for a person, email, or username...">
<button class="btn btn-primary" type="submit">Search</button>
<input type="text" id="searchInput" class="form-control" placeholder="Search for a name to map their network...">
<button class="btn btn-primary" type="submit">Discover</button>
</form>
</div>
</div>
@ -22,43 +23,61 @@
<div class="col-md-12">
<div class="card shadow-sm">
<div class="card-body">
<h5 class="card-title">People & Identities</h5>
<div id="resultsArea">
<p class="text-muted">Enter a search term to find entities.</p>
</div>
<h5 class="card-title">Network Visualization</h5>
<div id="graphContainer" style="width: 100%; height: 500px; background: #f8f9fa; border: 1px solid #ddd;"></div>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<a href="{% url 'core:logout' %}" class="btn btn-outline-danger">Logout</a>
</div>
</div>
</div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
document.getElementById('searchForm').addEventListener('submit', function(e) {
e.preventDefault();
const query = document.getElementById('searchInput').value;
const resultsArea = document.getElementById('resultsArea');
resultsArea.innerHTML = '<p>Searching...</p>';
const graphContainer = document.getElementById('graphContainer');
graphContainer.innerHTML = '<p class="p-3">Discovering network...</p>';
// Placeholder: In a real scenario, this would call your Django backend
fetch(`/api/search/?q=${encodeURIComponent(query)}`)
fetch(`{% url 'core:search_api' %}?q=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(data => {
resultsArea.innerHTML = data.map(item => `
<div class="d-flex align-items-center mb-3 p-3 border rounded">
<div class="me-3" style="width: 50px; height: 50px; background: #ddd; border-radius: 50%;"></div>
<div>
<strong>${item.name}</strong><br>
<small class="text-muted">${item.type}</small>
</div>
</div>
`).join('');
graphContainer.innerHTML = ''; // clear
renderGraph(data);
});
});
function renderGraph(data) {
const width = 800;
const height = 500;
const svg = d3.select("#graphContainer")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
const simulation = d3.forceSimulation(data.nodes)
.force("link", d3.forceLink(data.links).id(d => d.id))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
const link = svg.append("g")
.selectAll("line")
.data(data.links)
.enter().append("line")
.attr("stroke", "#999");
const node = svg.append("g")
.selectAll("circle")
.data(data.nodes)
.enter().append("circle")
.attr("r", 10)
.attr("fill", d => d.type === 'PERSON' ? 'red' : 'blue');
simulation.on("tick", () => {
link.attr("x1", d => d.source.x).attr("y1", d => d.source.y)
.attr("x2", d => d.target.x).attr("y2", d => d.target.y);
node.attr("cx", d => d.x).attr("cy", d => d.y);
});
}
</script>
{% endblock %}
{% endblock %}

View File

@ -2,6 +2,7 @@ from django.urls import path
from django.contrib.auth import views as auth_views
from .views import home, ingest_data, resolve_entities, login_view, dashboard_view, logout_view
from .api_views import search_api
app_name = 'core'
@ -9,7 +10,8 @@ urlpatterns = [
path("", home, name="home"),
path("api/ingest/", ingest_data, name="ingest_data"),
path("api/resolve/", resolve_entities, name="resolve_entities"),
path("api/search/", search_api, name="search_api"),
path("login/", login_view, name="login"),
path("dashboard/", dashboard_view, name="dashboard"),
path("logout/", logout_view, name="logout"),
]
]