Browse Source

working map

Sebastian Lohff 5 years ago
parent
commit
ff61388de5

+ 19
- 0
bgpdata/api.py View File

@@ -0,0 +1,19 @@
1
+#from tastypie.resources import ModelResource, ALL_WITH_RELATIONS
2
+#from tastypie import fields
3
+#from bgpdata.models import AS, CrawlRun
4
+#
5
+#class ASResource(ModelResource):
6
+#	crawl = fields.ForeignKey("bgpdata.api.CrawlResource", "crawl")
7
+#	class Meta:
8
+#		list_allowed_methods = ['get']
9
+#		detail_allowed_methods = ['get']
10
+#		filtering = {'crawl': ALL_WITH_RELATIONS}
11
+#
12
+#		queryset = AS.objects.all()
13
+#		resource_name = "as"
14
+#
15
+#class CrawlResource(ModelResource):
16
+#	class Meta:
17
+#		queryset = CrawlRun.objects.all()
18
+#		resource_name = "crawl"
19
+

+ 27
- 0
bgpdata/migrations/0009_auto_20150326_2207.py View File

@@ -0,0 +1,27 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+import django.db.models.deletion
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        ('bgpdata', '0008_auto_20150322_1906'),
12
+    ]
13
+
14
+    operations = [
15
+        migrations.AddField(
16
+            model_name='as',
17
+            name='directlyCrawled',
18
+            field=models.BooleanField(default=False),
19
+            preserve_default=True,
20
+        ),
21
+        migrations.AlterField(
22
+            model_name='crawllog',
23
+            name='host',
24
+            field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='bgpdata.ConfigHost', null=True),
25
+            preserve_default=True,
26
+        ),
27
+    ]

+ 26
- 0
bgpdata/migrations/0010_auto_20150327_0003.py View File

@@ -0,0 +1,26 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('bgpdata', '0009_auto_20150326_2207'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='as',
16
+            name='lastSeen',
17
+            field=models.ForeignKey(related_name='as_lastseen', default=None, blank=True, to='bgpdata.CrawlRun', null=True),
18
+            preserve_default=True,
19
+        ),
20
+        migrations.AddField(
21
+            model_name='as',
22
+            name='online',
23
+            field=models.BooleanField(default=True),
24
+            preserve_default=True,
25
+        ),
26
+    ]

+ 5
- 1
bgpdata/models.py View File

@@ -39,7 +39,7 @@ class CrawlLog(models.Model):
39 39
 	)
40 40
 
41 41
 	crawl = models.ForeignKey(CrawlRun)
42
-	host = models.ForeignKey(ConfigHost, null=True, blank=True)
42
+	host = models.ForeignKey(ConfigHost, null=True, blank=True, on_delete=models.SET_NULL)
43 43
 	logtime = models.DateTimeField(auto_now_add=True)
44 44
 	severity = models.CharField(max_length=10, choices=SEVERITY)
45 45
 	message = models.TextField()
@@ -65,6 +65,10 @@ class AS(models.Model):
65 65
 	crawl = models.ForeignKey(CrawlRun)
66 66
 	number = models.IntegerField()
67 67
 
68
+	directlyCrawled = models.BooleanField(default=False)
69
+	online = models.BooleanField(default=True)
70
+	lastSeen = models.ForeignKey(CrawlRun, blank=True, null=True, default=None, related_name='as_lastseen')
71
+
68 72
 	def __unicode__(self):
69 73
 		return u"AS %s (crawl %d)" % (self.number, self.crawl.pk)
70 74
 

+ 114
- 0
bgpdata/templates/bgpdata/map-2.html View File

@@ -0,0 +1,114 @@
1
+{% extends "base.html" %}
2
+{% block head %}
3
+{% load static from staticfiles %}
4
+<script src="{% static "js/d3.js" %}" charset="utf-8"></script>
5
+<style>
6
+.node {
7
+  stroke: #fff;
8
+  stroke-width: 1.5px;
9
+}
10
+
11
+.link {
12
+  stroke: #999;
13
+  stroke-opacity: .6;
14
+}
15
+</style>
16
+{% endblock %}
17
+{% block body %}
18
+
19
+<h3>Crawl run {{crawl.pk}} from {{crawl.startTime}}</h3>
20
+
21
+<div id="plotwin"></div>
22
+<script type="text/javascript" charset="utf-8">
23
+
24
+var asdata = [
25
+{% for AS in ASses %}
26
+	{id: {{AS.pk}}, numer: {{AS.number}}, label: 'MAUNZ'}{%if not forloop.last%},{%endif%}
27
+{%endfor%}
28
+];
29
+
30
+var asdict = {
31
+{% for AS in ASses %}
32
+	{{AS.number}}: {{forloop.counter0}}{%if not forloop.last%},{%endif%}
33
+{% endfor %}
34
+};
35
+
36
+var peerings = [
37
+{% for p in peerings %}
38
+	{
39
+		source: asdict[{{p.as1.number}}],
40
+		target: asdict[{{p.as2.number}}],
41
+
42
+		id: {{p.pk}},
43
+		as1id: {{p.as1.id}},
44
+		as1number: {{p.as1.number}},
45
+		as2id: {{p.as2.id}},
46
+		as2number: {{p.as2.number}},
47
+
48
+		origin: "{{p.get_origin_display}}"
49
+	}{%if not forloop.last%},{%endif%}
50
+{%endfor%}
51
+];
52
+
53
+
54
+var width = 960,
55
+    height = 600;
56
+
57
+var svg = d3.select('#plotwin')
58
+			.append('svg')
59
+			.attr('width', width)
60
+			.attr('height', height);
61
+
62
+console.log(asdata);
63
+var force = d3.layout.force()
64
+	.nodes(asdata)
65
+	.links(peerings)
66
+    .charge(-200)
67
+    .linkDistance(70)
68
+    .size([width, height])
69
+	.start();
70
+   // .charge(-120)
71
+   // .linkDistance(30)
72
+
73
+
74
+var link = svg.selectAll(".link")
75
+			.data(peerings)
76
+			.enter()
77
+			.append("line")
78
+			.attr("class", "link")
79
+			.style("stroke-width", 3);
80
+
81
+var node = svg.selectAll('.node')
82
+			.data(asdata)
83
+			.enter()
84
+			.append('circle')
85
+			.attr('class', 'node')
86
+			.attr('r', 10)
87
+			.call(force.drag);
88
+
89
+node.append('text')
90
+	.text("maunz");
91
+
92
+force.on("tick", function() {
93
+	node.attr('cx', function(d) { return d.x; })
94
+		.attr('cy', function(d) { return d.y; });
95
+
96
+	link.attr("x1", function(d) { return d.source.x; })
97
+		.attr("y1", function(d) { return d.source.y; })
98
+		.attr("x2", function(d) { return d.target.x; })
99
+		.attr("y2", function(d) { return d.target.y; });
100
+});
101
+
102
+force.start();
103
+
104
+//var node = svg.selectAll(".node").data(nodes).enter().append("circle");
105
+//node.append("title").text(function(d) { return d.name });
106
+</script>
107
+
108
+
109
+<p>
110
+{% for AS in ASses %}
111
+	{{ AS.number }}<br />
112
+{% endfor %}
113
+</p>
114
+{% endblock %}

+ 64
- 0
bgpdata/templates/bgpdata/map-example.html View File

@@ -0,0 +1,64 @@
1
+{% extends "base.html" %}
2
+{% block head %}
3
+<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
4
+{% endblock %}
5
+{% block body %}
6
+
7
+<h3>Crawl run {{crawl.pk}} from {{crawl.startTime}}</h3>
8
+
9
+<script type="text/javascript" charset="utf-8">
10
+
11
+var data = [{% for AS in ASses%}{{AS.number}}{%if not forloop.last%},{%endif%}{%endfor%}];
12
+var width = 960,
13
+    height = 500;
14
+
15
+var color = d3.scale.category20();
16
+
17
+var force = d3.layout.force()
18
+    .charge(-120)
19
+    .linkDistance(30)
20
+    .size([width, height]);
21
+
22
+var svg = d3.select("body").append("svg")
23
+    .attr("width", width)
24
+    .attr("height", height);
25
+
26
+d3.json("miserables.json", function(error, graph) {
27
+  force
28
+      .nodes(graph.nodes)
29
+      .links(graph.links)
30
+      .start();
31
+
32
+  var link = svg.selectAll(".link")
33
+      .data(graph.links)
34
+    .enter().append("line")
35
+      .attr("class", "link")
36
+      .style("stroke-width", function(d) { return Math.sqrt(d.value); });
37
+
38
+  var node = svg.selectAll(".node")
39
+      .data(graph.nodes)
40
+    .enter().append("circle")
41
+      .attr("class", "node")
42
+      .attr("r", 5)
43
+      .style("fill", function(d) { return color(d.group); })
44
+      .call(force.drag);
45
+
46
+  node.append("title")
47
+      .text(function(d) { return d.name; });
48
+
49
+  force.on("tick", function() {
50
+    link.attr("x1", function(d) { return d.source.x; })
51
+        .attr("y1", function(d) { return d.source.y; })
52
+        .attr("x2", function(d) { return d.target.x; })
53
+        .attr("y2", function(d) { return d.target.y; });
54
+
55
+    node.attr("cx", function(d) { return d.x; })
56
+        .attr("cy", function(d) { return d.y; });
57
+  });
58
+});
59
+</script>
60
+
61
+{% for AS in ASses %}
62
+	{{ AS.number }}<br />
63
+{% endfor %}
64
+{% endblock %}

+ 142
- 0
bgpdata/templates/bgpdata/map.html View File

@@ -0,0 +1,142 @@
1
+{% extends "base.html" %}
2
+{% block head %}
3
+{% load static from staticfiles %}
4
+<script src="{% static "js/d3.js" %}" charset="utf-8"></script>
5
+<style>
6
+.node {
7
+  stroke: #fff;
8
+  stroke-width: 1.5px;
9
+}
10
+
11
+.link {
12
+  stroke: #999;
13
+  stroke-opacity: .6;
14
+}
15
+</style>
16
+{% endblock %}
17
+{% block body %}
18
+
19
+<h3>Crawl run {{crawl.pk}} from {{crawl.startTime}}</h3>
20
+
21
+<div id="plotwin"></div>
22
+<script type="text/javascript" charset="utf-8">
23
+
24
+var asdata = [
25
+{% for AS in ASses %}
26
+	{id: {{AS.pk}}, nodetype: "AS", asnumber: {{AS.number}}, label: "{{AS.number}}", neighbors: {{AS.getPeerings.count}}, crawled: {%if AS.directlyCrawled%}true{%else%}false{%endif%}, online: {%if AS.online%}true{%else%}false{%endif%}}{%if not forloop.last%},{%endif%}
27
+{%endfor%}
28
+];
29
+
30
+var asdict = {
31
+{% for AS in ASses %}
32
+	{{AS.number}}: {{forloop.counter0}}{%if not forloop.last%},{%endif%}
33
+{% endfor %}
34
+};
35
+
36
+var peerings = [
37
+{% for p in peerings %}
38
+	{
39
+		source: asdict[{{p.as1.number}}],
40
+		target: asdict[{{p.as2.number}}],
41
+
42
+		id: {{p.pk}},
43
+		as1id: {{p.as1.id}},
44
+		as1number: {{p.as1.number}},
45
+		as2id: {{p.as2.id}},
46
+		as2number: {{p.as2.number}},
47
+
48
+		origin: "{{p.get_origin_display}}"
49
+	}{%if not forloop.last%},{%endif%}
50
+{%endfor%}
51
+];
52
+
53
+
54
+var width = 960,
55
+    height = 800;
56
+
57
+var svg = d3.select('#plotwin')
58
+			.append('svg')
59
+			.attr('width', width)
60
+			.attr('height', height);
61
+
62
+console.log(asdata);
63
+var force = d3.layout.force()
64
+	.nodes(asdata)
65
+	.links(peerings)
66
+    .charge(-500)
67
+//	.chargeDistance(300)
68
+//    .linkDistance(70)
69
+	.linkStrength(0.65)
70
+	.linkDistance(function(l) {
71
+		console.log(l);
72
+		neighs = Math.min(l.source.neighbors, l.target.neighbors);
73
+		console.log(neighs, "neighbors");
74
+		switch(neighs) {
75
+			case 0: return 40;
76
+			case 1: 
77
+			case 2: return 120;
78
+			case 3:
79
+			case 4: return 200;
80
+			default: return 250;
81
+		}
82
+	})
83
+    .size([width, height])
84
+	.start();
85
+
86
+
87
+var link = svg.selectAll(".link")
88
+			.data(peerings)
89
+			.enter()
90
+			.append("line")
91
+			.attr("class", "link")
92
+			.style("stroke-width", function(l) {
93
+				neighs = Math.min(l.source.neighbors, l.target.neighbors);
94
+				return 1 + Math.min(5, neighs);
95
+			});
96
+			//.style("stroke-width", 3);
97
+
98
+var node = svg.selectAll('.node')
99
+			.data(asdata)
100
+			.enter()
101
+			.append("g")
102
+			.call(force.drag);
103
+
104
+node.append("ellipse")
105
+	.attr("rx", 40)
106
+	.attr("ry", 20)
107
+	.attr("fill", function(d) {
108
+		if(d.crawled)
109
+			return "#94FF70";
110
+		else if(d.online)
111
+			return "#D1FFC2";
112
+			// return "#F0FFEB";
113
+		else
114
+			return "#FFCCCC";
115
+	})
116
+	.attr("stroke", "black")
117
+	.attr("stroke-width", "1px");
118
+
119
+node.append('text')
120
+	.attr("font-family", "sans-serif")
121
+	.attr("font-size", "13px")
122
+	.attr("font-weight", "bold")
123
+	.attr("dy", "4")
124
+	.attr("text-anchor", "middle")
125
+	.text(function(d) { return d.label; });
126
+
127
+force.on("tick", function() {
128
+	//node.attr('cx', function(d) { return d.x; })
129
+	//	.attr('cy', function(d) { return d.y; });
130
+	node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
131
+
132
+	link.attr("x1", function(d) { return d.source.x; })
133
+		.attr("y1", function(d) { return d.source.y; })
134
+		.attr("x2", function(d) { return d.target.x; })
135
+		.attr("y2", function(d) { return d.target.y; });
136
+});
137
+
138
+force.start();
139
+
140
+</script>
141
+
142
+{% endblock %}

+ 9
- 0
bgpdata/templates/bgpdata/overview.html View File

@@ -0,0 +1,9 @@
1
+{% extends "base.html" %}
2
+{% block body %}
3
+<h3>Crawl results</h3>
4
+
5
+{% for crawl in crawls %}
6
+	<a href="/map/{{crawl.id}}/">Crawl {{crawl.id}} from {{crawl.startTime}}</a><br />
7
+{% endfor %}
8
+
9
+{% endblock %}

+ 17
- 0
bgpdata/urls.py View File

@@ -0,0 +1,17 @@
1
+from django.conf.urls import patterns, url, include
2
+#from django.views.generic import RedirectView
3
+#from api import ASResource, CrawlResource
4
+
5
+#asResource = ASResource()
6
+#crawlResource = CrawlResource()
7
+
8
+urlpatterns = patterns('',
9
+	url(r'^$', 'bgpdata.views.overview'),
10
+	url(r'^([0-9]+)/$', 'bgpdata.views.showMap'),
11
+
12
+	#url(r'^api/crawl/(?P<crawlID>\d+)/asses/$', 'bgpdata.api.asses'),
13
+	#(r'^api/', include(asResource.urls)),
14
+	#(r'^api/', include(asResource.urls)),
15
+	#(r'^api/', include(crawlResource.urls)),
16
+)
17
+

+ 11
- 1
bgpdata/views.py View File

@@ -1,3 +1,13 @@
1 1
 from django.shortcuts import render
2
+from bgpdata.models import CrawlRun, AS, Peering
2 3
 
3
-# Create your views here.
4
+def overview(request):
5
+	crawls = CrawlRun.objects.order_by("-startTime")
6
+	return render(request, 'bgpdata/overview.html', {"crawls": crawls})
7
+
8
+def showMap(request, crawlId):
9
+	crawl = CrawlRun.objects.get(id=crawlId)
10
+	ASses = AS.objects.filter(crawl=crawl)
11
+	peerings = Peering.objects.filter(as1__crawl=crawl)
12
+
13
+	return render(request, 'bgpdata/map.html', {"crawl": crawl, 'ASses': ASses, 'peerings': peerings})

+ 42
- 4
bin/crawl.py View File

@@ -9,19 +9,20 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dnmapper.settings")
9 9
 import django
10 10
 django.setup()
11 11
 
12
+import datetime
12 13
 from django.utils import timezone
13
-from django.db.models import Q
14
+from django.db.models import Q, Max
14 15
 
15 16
 from bgpdata.models import ConfigHost, CrawlRun, CrawlLog, AS, BorderRouter, Announcement, Peering, BorderRouterPair
16 17
 from routerparsers import getBGPData, RouterParserException
17 18
 
18 19
 
19
-def getOrCreateAS(crawl, number):
20
+def getOrCreateAS(crawl, number, online=True):
20 21
 	currAS = None
21 22
 	try:
22 23
 		currAS = AS.objects.get(crawl=crawl, number=number)
23 24
 	except AS.DoesNotExist:
24
-		currAS = AS(crawl=crawl, number=number)
25
+		currAS = AS(crawl=crawl, number=number, online=online)
25 26
 		currAS.save()
26 27
 
27 28
 	return currAS
@@ -55,6 +56,9 @@ def main():
55 56
 		currASno = int(data["local_as"])
56 57
 		currAS = getOrCreateAS(crawl, currASno)
57 58
 
59
+		currAS.directlyCrawled = True
60
+		currAS.save()
61
+
58 62
 		currRouter = None
59 63
 		try:
60 64
 			currRouter = BorderRouter.objects.get(AS=currAS, routerID=data["local_id"])
@@ -115,10 +119,14 @@ def main():
115 119
 				print(" ---->", route["prefix"])
116 120
 				if "/" not in route["prefix"]:
117 121
 					continue
122
+
123
+				originAS = currAS
124
+				if len(route["path"]) > 0:
125
+					originAS = getOrCreateAS(crawl, route["path"][0])
118 126
 				ip, prefix = route["prefix"].split("/")
119 127
 				a = Announcement(router=currRouter, ip=ip, prefix=prefix,
120 128
 									ASPath=" ".join(route["path"]), nextHop=route["nexthop"],
121
-									originAS=currAS)
129
+									originAS=originAS)
122 130
 				a.save()
123 131
 		else:
124 132
 			print(" !! No routes found in host output")
@@ -144,6 +152,36 @@ def main():
144 152
 
145 153
 				firstAS = secondAS
146 154
 
155
+	# 3.2 add ASses, routers and peerings from old crawlruns (last should suffice)
156
+	# find 
157
+	print(" --> copy old ASses")
158
+	timerangeStart = crawl.startTime - datetime.timedelta(7)
159
+	oldASses = AS.objects.filter(crawl__startTime__gte=timerangeStart).values("number").annotate(lastSeen=Max('crawl_id')).filter(~Q(lastSeen=crawl.pk))
160
+
161
+	# 3.2.1. copy old asses
162
+	print(" ----> create ASses")
163
+	for oldASdata in oldASses:
164
+		print(" ------> AS", oldASdata["number"])
165
+		oldAS = AS.objects.get(number=oldASdata["number"], crawl=oldASdata["lastSeen"])
166
+
167
+		newAS = AS(number=oldAS.number, crawl=crawl, lastSeen=oldAS.crawl, directlyCrawled=False, online=False)
168
+		newAS.save()
169
+
170
+	# 3.2.2 copy peerings between old asses
171
+	print(" ----> copy peerings")
172
+	for oldASdata in oldASses:
173
+		print(" ------> AS", oldASdata["number"])
174
+		oldAS = AS.objects.get(number=oldASdata["number"], crawl=oldASdata["lastSeen"])
175
+		for peering in oldAS.getPeerings():
176
+			print(" --------> Peering %s <--> %s" % (peering.as1.number, peering.as2.number))
177
+			peering = Peering(
178
+						as1=AS.objects.get(number=peering.as1.number, crawl=crawl), 
179
+						as2=AS.objects.get(number=peering.as2.number, crawl=crawl),
180
+						origin=peering.origin)
181
+			peering.save()
182
+
183
+	# 3.3 FIXME: do we also want to have old peerings which do not exist anymore?
184
+
147 185
 	# 4. end crawl run
148 186
 	crawl.endTime = timezone.now()
149 187
 	crawl.save()

+ 9
- 0
dnmapper/settings.py View File

@@ -26,6 +26,9 @@ TEMPLATE_DEBUG = True
26 26
 
27 27
 ALLOWED_HOSTS = []
28 28
 
29
+STATICFILES_DIRS = (
30
+	'static/',
31
+)
29 32
 
30 33
 # Application definition
31 34
 
@@ -37,8 +40,11 @@ INSTALLED_APPS = (
37 40
     'django.contrib.messages',
38 41
     'django.contrib.staticfiles',
39 42
 	'bgpdata',
43
+#	'tastypie',
40 44
 )
41 45
 
46
+API_LIMIT_PER_PAGE = 100
47
+
42 48
 MIDDLEWARE_CLASSES = (
43 49
     'django.contrib.sessions.middleware.SessionMiddleware',
44 50
     'django.middleware.common.CommonMiddleware',
@@ -76,6 +82,9 @@ USE_I18N = True
76 82
 USE_L10N = True
77 83
 
78 84
 USE_TZ = True
85
+TEMPLATE_DIRS = (
86
+	'templates/',
87
+)
79 88
 
80 89
 
81 90
 # Static files (CSS, JavaScript, Images)

+ 5
- 1
dnmapper/urls.py View File

@@ -1,10 +1,14 @@
1 1
 from django.conf.urls import patterns, include, url
2 2
 from django.contrib import admin
3
+from django.views.generic import RedirectView
4
+import bgpdata.urls
3 5
 
4 6
 urlpatterns = patterns('',
5 7
     # Examples:
6 8
     # url(r'^$', 'dnmapper.views.home', name='home'),
7 9
     # url(r'^blog/', include('blog.urls')),
10
+	url(r'^$', RedirectView.as_view(url='/map/')),
11
+	url(r'^map/', include(bgpdata.urls)),
8 12
 
9
-    url(r'^admin/', include(admin.site.urls)),
13
+	url(r'^admin/', include(admin.site.urls)),
10 14
 )

+ 26
- 0
static/js/LICENSE View File

@@ -0,0 +1,26 @@
1
+Copyright (c) 2010-2015, Michael Bostock
2
+All rights reserved.
3
+
4
+Redistribution and use in source and binary forms, with or without
5
+modification, are permitted provided that the following conditions are met:
6
+
7
+* Redistributions of source code must retain the above copyright notice, this
8
+  list of conditions and the following disclaimer.
9
+
10
+* Redistributions in binary form must reproduce the above copyright notice,
11
+  this list of conditions and the following disclaimer in the documentation
12
+  and/or other materials provided with the distribution.
13
+
14
+* The name Michael Bostock may not be used to endorse or promote products
15
+  derived from this software without specific prior written permission.
16
+
17
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
21
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 9504
- 0
static/js/d3.js
File diff suppressed because it is too large
View File


+ 5
- 0
static/js/d3.min.js
File diff suppressed because it is too large
View File


+ 10
- 0
templates/base.html View File

@@ -0,0 +1,10 @@
1
+<!doctype html5>
2
+<html>
3
+<head>
4
+{% block head %}{% endblock %}
5
+</head>
6
+<body>
7
+<h1>DarkMap</h1>
8
+{% block body %}{% endblock %}
9
+</body>
10
+</html>

+ 3
- 0
templates/templ.html View File

@@ -0,0 +1,3 @@
1
+{% extends "base.html" %}
2
+{% block body %}
3
+{% endblock %}

Loading…
Cancel
Save