RIPLEY
This commit is contained in:
parent
b19870501a
commit
987bc24830
BIN
assets/pasted-20260322-225544-bb21f3a7.png
Normal file
BIN
assets/pasted-20260322-225544-bb21f3a7.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
assets/vm-shot-2026-03-22T22-55-29-490Z.jpg
Normal file
BIN
assets/vm-shot-2026-03-22T22-55-29-490Z.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
Binary file not shown.
BIN
core/__pycache__/api_views.cpython-311.pyc
Normal file
BIN
core/__pycache__/api_views.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -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
23
core/api_views.py
Normal 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})
|
||||
Binary file not shown.
@ -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
|
||||
@ -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 %}
|
||||
@ -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"),
|
||||
]
|
||||
]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user