From 239f69a199dcf72e695f9a0c3cee67f34b56fa25 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 23 Mar 2026 01:01:02 +0000 Subject: [PATCH] RIPLEY --- core/__pycache__/models.cpython-311.pyc | Bin 4849 -> 5152 bytes core/__pycache__/urls.cpython-311.pyc | Bin 1037 -> 1132 bytes core/__pycache__/views.cpython-311.pyc | Bin 4063 -> 5369 bytes ...tity_last_seen_entity_metadata_and_more.py | 33 +++++++ ...n_entity_metadata_and_more.cpython-311.pyc | Bin 0 -> 1269 bytes core/models.py | 4 + .../__pycache__/resolution.cpython-311.pyc | Bin 7187 -> 8067 bytes core/services/resolution.py | 41 ++++++--- core/templates/core/dashboard.html | 82 +++++++----------- core/urls.py | 5 +- core/views.py | 15 +++- 11 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 core/migrations/0005_entity_last_seen_entity_metadata_and_more.py create mode 100644 core/migrations/__pycache__/0005_entity_last_seen_entity_metadata_and_more.cpython-311.pyc diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 75858ff642a4bd137dad3fe42a65264cfd915d2c..4346ab3e626846eef282a68fe6b758f8b2aee01a 100644 GIT binary patch delta 941 zcmb7>T}TvB6vyvfXU5$b-I-m#+V!habNwi*g>nsZEmL!$G9^p$WAl!-u?5XeMGujL z4@N@9DIy5!sgxuNUrGvsdJPnoTK3H+A8JX_=jfhUOi+60T<(u^&;QQ7=YMCV;4=77=0Hr>uOuBET_;bpM)W943M?{YCheDXGt9zf7(Vh3zQCe*r#7LLPfBsEe2UF$ z{sqk+*ZiZ$6Kd)B^|)F(CA?{wR|5-bAg%^R+hIrw*uGhgWWq!hATZnlW!akKfb-~LQG#7{CB;Jdx1t_f3XjjoL##}c1B zmYb%jQN|NPL2O6tguRY;Vhc~}Amyl}A;^*2Ng=pU@{+B^UN delta 723 zcmZ3W@llm;IWI340}#w8JCLc)wUIB5nTr|32ZGOsCd=~4PoBy=hbM)jg&~SBg)^8z zlWTG_%k;@wtXa~cKsCi6%?%7U_=S7wE^x>ei2%8Nn*5X3vnn$xPrl6RFxiq#nQ_MC zShl9g-0YT26BsAU@yTsYVGm+dac4*oY+*?x^fiKRIuMchDCAS7QL<%IYI{6N_6{GWH zaUK_4b&!A?h!6%5vLFKF&mtiZp$Q`-fQ(|p$#Zx~Qp62(yX53g{DpF^AVD-s z!LEq}O4v7SQBoug5|#lGIK3PMk_8b)lm7_5W(=DALP$deYz0^b)vdocY;yBcN^?@} zijpRq2nR-RGs=Bnz$7N5P09HP690mbl#*aH{=k4qbcFT9d<2PqK}ecRzANku0DCf? ADF6Tf diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 483a4fe31b9d557ab76ba76fd34a7ba40e1cb211..9ff043e658a99784025ffcd29de13c2e3f9b9d42 100644 GIT binary patch delta 375 zcmeC>c*CK-oR^o20SNp$4`l9SVqka-;=lk8l<`?@qPn$0E_W0+BSQ*v3TqBeE^ibs zkj)mw$Hb7zn8KdQn!+)OQFP*DGiL4-ejtlgAeA>o5XfK_O68dNPm-B4MHI-h5liJt z5nsc(jG2L9H4sBUI!6?LibODjrsPYIfF|QDmV(5R3?R)~mYG^!{E`tQ%04-e(NEwO zUwUdue0ou0K}LK^VoBoU`HY_OT!{sl`d~5rB9No2xIqHNsfk6&8Tv)MlfNnZ21-t2isF(5Y64kOtTy=T3 delta 288 zcmaFE(aWK}oR^o20SMSi4rFFCF)%y^abSQO%J|GNQQcaIks*aSg*AsemnVt`$YzV; zomdgd$TRVrBr`{f;KU1}tU{@rDZ)U8jYuk2is%~7Wy}l=tAQ8-(%GZat@k};lvSH+( zT*MT{dy5k&6A#t{6u-q3#U%sO3DRB6J=u;~PcQxg0|{vI0_NDsLM-0={2csD4cs7D IBn31K0G@$J!~g&Q diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 3d6173090b8ca8b012376060547e5e75c3058ec4..c4e6f284a4128970dad11dfaa30ba28121f6ba28 100644 GIT binary patch delta 2264 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 DA`vr~ delta 1830 zcmZWpQEU`N7~Z|Rz1_QI?|QxVTDZ0>O|NAO^a3JYP70Ayi%?3vJ~YANWw{-$OYd%v z*}VeIHjRLx7!o1#pb<6qU{IqmB=rH&L?4JgX$q;*tT7=zNutRScrftd{Cm|3Ozys& z`M-bunScIo=Im~g3Vkd|L58;X&VQ#rY`7e1Wv^fEd1cPSAolngAfO@XD$6jR(SN-i z4tba%M&r&gn*XYYGW5T~gk6TGjPMlG;4%Tq1fOCe8au`);ynm4qOgv%=knvBg%S1v z%LW(rc(?i34pQNJ*)XZ{-E4yVz>n^1q`r=~f(wp-ky$oMCUShHQ7PjDFoK56T-H+a zxC4fMy{P7qi8s&z?jmDCCmSWxLKoYxa7DQ96I;OE3b2m6BPHkJv^&F9G%eqM53+v5 zc4Np<@wnmfcHl!}gD+(U_Q`{p%%MzL#@*C7qJ*(Pzc>t_0Q3TQ0X6|70r~&}6dcYh z7jz46g}#tSR@OqNEFEtHhaQ0EC@3O!^W{!d5I*e$O{C)7WR_-+p9h~E6dazOL#1Gu zj+b^EPIa^212pUwrzw~s1$njMDET7P!ERjmF|@=cn!xQvfME)sGPoJ~Q2?0dLV$eV zu;1?zBKavkNM4I{i9BT?!3Q@zZ?jcDVY#T6 zv3b(aynaZS4cGx3wEd=F2Q*>TpT-dIMO)AUtF|(4^Zy^u2JIl8v4d3}Vv^ZtBU|tF z`@+E~*b{?Ctsp5k_4+ig9VCB7dpL2{XEjjM7FHx5HpNmGzqbA5h!(OS)s<#5Pb&Tj z^MS|avhGy@7n`e!?vnk!$ehhl`G#i(M-e_|;G(SQX3m(Vm69u_Udqek=%_q^)sqD} z*euifVl`(0QAC!isg^3MB~7kNv~Q;IKX-)X!BSo?A%(^5bWz7YI+z(dG@^JN5tS_6 zI-Rvnml63Z78BhXgG6zHgj&`?Noc!Ek}W&aGs#KIDx2wKQZ4I!nw~>Pset-&Mltz9 z->IZt(k)#rm`QUwzuQDe>sQv)D?Uulv~544zIIwKi@s|-cjgliCGb$x5>xRMmA#NK$3fv^sG>x3DS|~eB z`dX!^32)J=aAlcF_e|>$KS0gqS(YrezCKs;GcECXk$##wtDV)Y)y}18qQ-;+F&E|s zmgAjw;+t>BH-9pAt@-oAx8kX#cr?}no_ zCLqO@<*w@ZN7AC)e=T}#{L8jW(sER;F?=LeJ#r_Jx}8YfiY3xZiS%{!?bMB_uZrIk zZzm2cCJy{Yep$Pn^sZCp{;ma?ww}fK@x|WbKToKONAtHPCYL5A>5%^B{YygYvJiS( zd`r9_U6kgf<#6-7)iIbt`Rx+T23? z+O~x~Mgr}-(y;uqyTt`+#wP%t+GGiA0EoQNzC&_%4@0F9OXOzz=)M4b5{Hd^o=VR> zfgYIPzDDk*-1$$&d?f5F8d{}*cHxuM8D4MmF++@wJo%%ef8z$0p1`wkA8Zd-iI>U2 K_1k=~pML .node-group { cursor: pointer; } .node-circle { stroke: #fff; stroke-width: 2px; } - .node-text { font-size: 10px; pointer-events: none; } + .node-text { font-size: 10px; pointer-events: none; fill: #555; } #loader { display: none; } + #graphContainer { background: #1a1a1a; border-radius: 8px; } + .link { stroke: #444; stroke-opacity: 0.6; }
@@ -34,9 +36,8 @@
-
-
Network Visualization
-
+
+
@@ -53,22 +54,19 @@ document.getElementById('searchForm').addEventListener('submit', function(e) { const btnText = document.getElementById('btnText'); const loader = document.getElementById('loader'); - // UI Loading state searchBtn.disabled = true; - btnText.textContent = "Searching..."; + btnText.textContent = "Processing..."; loader.style.display = "inline-block"; - graphContainer.html('

Discovering network, please wait...

'); - - fetch(`{% url 'core:search_api' %}?q=${encodeURIComponent(query)}`, { method: 'GET' }) - .then(response => { - if (!response.ok) throw new Error("Search failed"); - return response.json(); - }) + + // Fetch from new graph endpoint + fetch(`{% url 'core:get_graph_data' %}?q=${encodeURIComponent(query)}`, { method: 'GET' }) + .then(response => response.json()) .then(data => { - graphContainer.html(''); // clear + graphContainer.html(''); renderGraph(data); }) .catch(err => { + console.error(err); graphContainer.html(`

Error: ${err.message}

`); }) .finally(() => { @@ -79,29 +77,27 @@ document.getElementById('searchForm').addEventListener('submit', function(e) { }); function renderGraph(data) { - const width = 800; - const height = 600; + const container = document.getElementById('graphContainer'); + const width = container.clientWidth; + const height = container.clientHeight; - // Create SVG and enforce bounds const svg = d3.select("#graphContainer") .append("svg") - .attr("width", "100%") - .attr("height", "100%") - .attr("viewBox", [0, 0, width, height]); + .attr("width", width) + .attr("height", height); const simulation = d3.forceSimulation(data.nodes) - .force("link", d3.forceLink(data.links).id(d => d.id).distance(100)) - .force("charge", d3.forceManyBody().strength(-300)) + .force("link", d3.forceLink(data.links).id(d => d.id).distance(150)) + .force("charge", d3.forceManyBody().strength(-400)) .force("center", d3.forceCenter(width / 2, height / 2)) - .force("x", d3.forceX(width / 2).strength(0.05)) - .force("y", d3.forceY(height / 2).strength(0.05)); + .force("collide", d3.forceCollide().radius(30)); const link = svg.append("g") .selectAll("line") .data(data.links) .join("line") - .attr("stroke", "#999") - .attr("stroke-width", 1); + .attr("class", "link") + .attr("stroke-width", d => Math.sqrt(d.value || 1)); const node = svg.append("g") .selectAll("g") @@ -113,36 +109,22 @@ function renderGraph(data) { .on("drag", dragged) .on("end", dragended)); - // Reduce node sizes significantly - const radius = 12; - node.append("circle") - .attr("r", radius) - .attr("class", "node-circle") - .attr("fill", d => d.type === 'PERSON' ? '#e74c3c' : '#3498db'); + // Colors for types + const color = d3.scaleOrdinal() + .domain(["PERSON", "EMAIL", "ENTITY", "ORG"]) + .range(["#e74c3c", "#3498db", "#2ecc71", "#f1c40f"]); - // Add Image if available, scaled down - node.filter(d => d.photo) - .append("image") - .attr("xlink:href", d => d.photo) - .attr("x", -radius) - .attr("y", -radius) - .attr("width", radius * 2) - .attr("height", radius * 2) - .attr("clip-path", `circle(${radius}px)`); + node.append("circle") + .attr("r", 15) + .attr("fill", d => color(d.type)); node.append("text") - .attr("dy", radius + 15) + .attr("dy", 25) .attr("text-anchor", "middle") .attr("class", "node-text") .text(d => d.name); simulation.on("tick", () => { - // Enforce boundary constraints - node.each(d => { - d.x = Math.max(radius, Math.min(width - radius, d.x)); - d.y = Math.max(radius, Math.min(height - radius, d.y)); - }); - 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("transform", d => `translate(${d.x},${d.y})`); @@ -153,12 +135,10 @@ function renderGraph(data) { event.subject.fx = event.subject.x; event.subject.fy = event.subject.y; } - function dragged(event) { event.subject.fx = event.x; event.subject.fy = event.y; } - function dragended(event) { if (!event.active) simulation.alphaTarget(0); event.subject.fx = null; @@ -166,4 +146,4 @@ function renderGraph(data) { } } -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index e069edd..b8916c9 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,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 .views import home, ingest_data, resolve_entities, login_view, dashboard_view, logout_view, get_graph_data from .api_views import search_api app_name = 'core' @@ -10,8 +10,9 @@ urlpatterns = [ path("", home, name="home"), path("api/ingest/", ingest_data, name="ingest_data"), path("api/resolve/", resolve_entities, name="resolve_entities"), + path("api/graph/", get_graph_data, name="get_graph_data"), 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"), -] +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index 5fc1bea..6bd09f5 100644 --- a/core/views.py +++ b/core/views.py @@ -3,6 +3,7 @@ from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from core.services.ingestion import IngestionService from core.services.resolution import EntityResolutionService +from core.models import Entity, Relationship from django.shortcuts import render, redirect from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required @@ -62,5 +63,15 @@ def resolve_entities(request): data = json.loads(request.body) # Using EntityResolutionService as a placeholder for actual processing result = EntityResolutionService.resolve(data) - return JsonResponse({'status': 'success', 'result': result}) - return JsonResponse({'error': 'Invalid request'}, status=400) \ No newline at end of file + return JsonResponse({'status': 'success', 'result': str(result)}) + return JsonResponse({'error': 'Invalid request'}, status=400) + +@login_required +def get_graph_data(request): + entities = Entity.objects.all()[:50] + relationships = Relationship.objects.filter(source_entity__in=entities, target_entity__in=entities) + + nodes = [{'id': e.value, 'name': e.value, 'type': e.entity_type, 'size': 5} for e in entities] + links = [{'source': r.source_entity.value, 'target': r.target_entity.value, 'value': r.weight} for r in relationships] + + return JsonResponse({'nodes': nodes, 'links': links}) \ No newline at end of file