Upload-collector
Upload-collector
3
collector/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.idea
|
||||||
|
__pycache__
|
||||||
|
HoneyPotCollector/settings.py
|
0
collector/CollectorAPI/__init__.py
Normal file
11
collector/CollectorAPI/admin.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import HoneyPotServer
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(HoneyPotServer)
|
||||||
|
class HoneyPotServerAdmin(admin.ModelAdmin):
|
||||||
|
ordering = ['id']
|
||||||
|
list_display = ('name', 'ip', 'isActive', 'last_input')
|
||||||
|
|
6
collector/CollectorAPI/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CollectorapiConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'CollectorAPI'
|
58
collector/CollectorAPI/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# Generated by Django 3.2.7 on 2021-09-30 11:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HenoypotInfoHourlyStatisticsAttackIPs',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('timestamp', models.DateTimeField()),
|
||||||
|
('src_ip', models.CharField(max_length=45)),
|
||||||
|
('count', models.IntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneyPotServer',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=32)),
|
||||||
|
('description', models.CharField(max_length=65500)),
|
||||||
|
('ip', models.CharField(max_length=15, unique=True)),
|
||||||
|
('key', models.CharField(help_text='Enter 0000 to autogenerate key', max_length=64, unique=True)),
|
||||||
|
('last_input', models.DateTimeField(auto_now=True)),
|
||||||
|
('isActive', models.BooleanField(default=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneypotInfo',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('shard_id', models.CharField(max_length=32)),
|
||||||
|
('src_ip', models.CharField(max_length=45)),
|
||||||
|
('dst_ip', models.CharField(max_length=45)),
|
||||||
|
('ip_rep', models.CharField(max_length=128, null=True)),
|
||||||
|
('protocol', models.CharField(max_length=45, null=True)),
|
||||||
|
('type', models.CharField(max_length=128)),
|
||||||
|
('eventid', models.CharField(max_length=128, null=True)),
|
||||||
|
('event_type', models.CharField(max_length=128, null=True)),
|
||||||
|
('countryISO', models.CharField(max_length=4, null=True)),
|
||||||
|
('event_timestamp', models.DateTimeField()),
|
||||||
|
('recieved_timestamp', models.DateTimeField(auto_now=True)),
|
||||||
|
('raw_entry', models.JSONField()),
|
||||||
|
('server_id', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='CollectorAPI.honeypotserver')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('shard_id', 'event_timestamp')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.2.7 on 2021-10-11 15:58
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('CollectorAPI', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='honeypotinfo',
|
||||||
|
index=models.Index(fields=['event_timestamp'], name='CollectorAP_event_t_38766e_idx'),
|
||||||
|
),
|
||||||
|
]
|
43
collector/CollectorAPI/migrations/0003_auto_20211017_1242.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Generated by Django 3.2.7 on 2021-10-17 09:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('CollectorAPI', '0002_honeypotinfo_collectorap_event_t_38766e_idx'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='honeypotinfo',
|
||||||
|
name='city_name',
|
||||||
|
field=models.CharField(max_length=128, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='honeypotinfo',
|
||||||
|
name='continent_code',
|
||||||
|
field=models.CharField(max_length=2, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='honeypotinfo',
|
||||||
|
name='latitude',
|
||||||
|
field=models.DecimalField(decimal_places=6, max_digits=9, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='honeypotinfo',
|
||||||
|
name='longitude',
|
||||||
|
field=models.DecimalField(decimal_places=6, max_digits=9, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='honeypotinfo',
|
||||||
|
name='region_code',
|
||||||
|
field=models.CharField(max_length=4, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='honeypotinfo',
|
||||||
|
name='region_name',
|
||||||
|
field=models.CharField(max_length=128, null=True),
|
||||||
|
),
|
||||||
|
]
|
22
collector/CollectorAPI/migrations/0004_honeypotrawdata.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 3.2.7 on 2021-10-19 16:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('CollectorAPI', '0003_auto_20211017_1242'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneypotRawData',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('raw_entry', models.JSONField()),
|
||||||
|
('record', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='CollectorAPI.honeypotinfo')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.2.7 on 2021-10-20 13:41
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('CollectorAPI', '0004_honeypotrawdata'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='honeypotinfo',
|
||||||
|
name='raw_entry',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Generated by Django 3.2.7 on 2021-10-20 18:58
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('CollectorAPI', '0005_remove_honeypotinfo_raw_entry'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneypotAgregate24hIps',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ip', models.CharField(max_length=45)),
|
||||||
|
('country_iso', models.CharField(max_length=4, null=True)),
|
||||||
|
('count', models.IntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneypotAgregate30DIps',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ip', models.CharField(max_length=45)),
|
||||||
|
('country_iso', models.CharField(max_length=4, null=True)),
|
||||||
|
('count', models.IntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneypotAgregate7DIps',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ip', models.CharField(max_length=45)),
|
||||||
|
('country_iso', models.CharField(max_length=4, null=True)),
|
||||||
|
('count', models.IntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Generated by Django 3.2.7 on 2021-10-20 20:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('CollectorAPI', '0006_honeypotagregate24hips_honeypotagregate30dips_honeypotagregate7dips'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneypotAgregate24hCountry',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('country_iso', models.CharField(max_length=4, null=True)),
|
||||||
|
('count', models.IntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneypotAgregate30DCountry',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('country_iso', models.CharField(max_length=4, null=True)),
|
||||||
|
('count', models.IntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneypotAgregate7DCountry',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('country_iso', models.CharField(max_length=4, null=True)),
|
||||||
|
('count', models.IntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 3.2.7 on 2021-10-20 21:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('CollectorAPI', '0007_honeypotagregate24hcountry_honeypotagregate30dcountry_honeypotagregate7dcountry'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneypotAgregatePerServer',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('data_id', models.CharField(max_length=45)),
|
||||||
|
('data', models.JSONField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 3.2.7 on 2021-11-28 22:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('CollectorAPI', '0008_honeypotagregateperserver'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HoneypotReportsStorage',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('from_date', models.DateTimeField()),
|
||||||
|
('to_date', models.DateTimeField()),
|
||||||
|
('affected_honeys', models.JSONField()),
|
||||||
|
('countries', models.JSONField()),
|
||||||
|
('data', models.JSONField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.2.7 on 2021-11-28 23:20
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('CollectorAPI', '0009_honeypotreportsstorage'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='honeypotreportsstorage',
|
||||||
|
unique_together={('from_date', 'to_date')},
|
||||||
|
),
|
||||||
|
]
|
23
collector/CollectorAPI/migrations/0011_permissionstable.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.7 on 2022-03-09 09:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('CollectorAPI', '0010_alter_honeypotreportsstorage_unique_together'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PermissionsTable',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('from_date', models.DateTimeField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'permissions': (('adv_reports', 'Може да създава подробни доклади'), ('graphs', 'Може да изпозлва Graphs'), ('tables', 'Може да използва tables')),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
collector/CollectorAPI/migrations/__init__.py
Normal file
127
collector/CollectorAPI/models.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
from django.db import models
|
||||||
|
import random
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
||||||
|
|
||||||
|
class HoneyPotServer(models.Model):
|
||||||
|
name = models.CharField(max_length=32)
|
||||||
|
description = models.CharField(max_length=65500)
|
||||||
|
ip = models.CharField(max_length=15, unique=True)
|
||||||
|
key = models.CharField(max_length=64, unique=True, help_text="Enter 0000 to autogenerate key")
|
||||||
|
last_input = models.DateTimeField(auto_now=True)
|
||||||
|
isActive = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name}"
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.key == '0000':
|
||||||
|
self.key = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).hexdigest()
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
class ServerKeys(models.Model):
|
||||||
|
server = models.ForeignKey(HoneyPotServer, on_delete=models.CASCADE, unique=True)
|
||||||
|
key = models.CharField(max_length=64, unique=True, help_text="Enter 0000 to autogenerate key")
|
||||||
|
isActive = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.server.name}"
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class HoneypotInfo(models.Model):
|
||||||
|
server_id = models.ForeignKey(HoneyPotServer, on_delete=models.PROTECT)
|
||||||
|
shard_id = models.CharField(max_length=32)
|
||||||
|
src_ip = models.CharField(max_length=45)
|
||||||
|
dst_ip = models.CharField(max_length=45)
|
||||||
|
ip_rep = models.CharField(max_length=128, null=True)
|
||||||
|
protocol = models.CharField(max_length=45, null=True)
|
||||||
|
type = models.CharField(max_length=128)
|
||||||
|
eventid = models.CharField(max_length=128, null=True)
|
||||||
|
event_type = models.CharField(max_length=128, null=True)
|
||||||
|
countryISO = models.CharField(max_length=4, null=True)
|
||||||
|
city_name = models.CharField(max_length=128, null=True)
|
||||||
|
region_code = models.CharField(max_length=4, null=True)
|
||||||
|
region_name = models.CharField(max_length=128, null=True)
|
||||||
|
continent_code = models.CharField(max_length=2, null=True)
|
||||||
|
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True)
|
||||||
|
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True)
|
||||||
|
event_timestamp = models.DateTimeField()
|
||||||
|
recieved_timestamp = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ("shard_id", "event_timestamp")
|
||||||
|
indexes = [models.Index(fields=["event_timestamp"])]
|
||||||
|
|
||||||
|
|
||||||
|
class HoneypotRawData(models.Model):
|
||||||
|
record = models.OneToOneField(HoneypotInfo, on_delete=models.PROTECT, to_field='id')
|
||||||
|
raw_entry = models.JSONField()
|
||||||
|
|
||||||
|
|
||||||
|
class HoneypotAgregate24hIps(models.Model):
|
||||||
|
ip = models.CharField(max_length=45)
|
||||||
|
country_iso = models.CharField(max_length=4, null=True)
|
||||||
|
count = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class HoneypotAgregate7DIps(models.Model):
|
||||||
|
ip = models.CharField(max_length=45)
|
||||||
|
country_iso = models.CharField(max_length=4, null=True)
|
||||||
|
count = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class HoneypotAgregate30DIps(models.Model):
|
||||||
|
ip = models.CharField(max_length=45)
|
||||||
|
country_iso = models.CharField(max_length=4, null=True)
|
||||||
|
count = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class HoneypotAgregate24hCountry(models.Model):
|
||||||
|
country_iso = models.CharField(max_length=4, null=True)
|
||||||
|
count = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class HoneypotAgregate7DCountry(models.Model):
|
||||||
|
country_iso = models.CharField(max_length=4, null=True)
|
||||||
|
count = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class HoneypotAgregate30DCountry(models.Model):
|
||||||
|
country_iso = models.CharField(max_length=4, null=True)
|
||||||
|
count = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class HoneypotAgregatePerServer(models.Model):
|
||||||
|
data_id = models.CharField(max_length=45)
|
||||||
|
data = models.JSONField()
|
||||||
|
|
||||||
|
|
||||||
|
class HenoypotInfoHourlyStatisticsAttackIPs(models.Model):
|
||||||
|
timestamp = models.DateTimeField()
|
||||||
|
src_ip = models.CharField(max_length=45)
|
||||||
|
count = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class HoneypotReportsStorage(models.Model):
|
||||||
|
from_date = models.DateTimeField()
|
||||||
|
to_date = models.DateTimeField()
|
||||||
|
affected_honeys = models.JSONField()
|
||||||
|
countries = models.JSONField()
|
||||||
|
data = models.JSONField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ("from_date", "to_date")
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionsTable(models.Model):
|
||||||
|
from_date = models.DateTimeField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
permissions = (("adv_reports", "Може да създава подробни доклади"), ("graphs", "Може да изпозлва Graphs"), ("tables", "Може да използва tables"),)
|
||||||
|
|
50
collector/CollectorAPI/reports.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
from datetime import datetime, timedelta, date
|
||||||
|
from time import gmtime, strftime, mktime
|
||||||
|
import json
|
||||||
|
import pytz
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
|
import CollectorAPI.models
|
||||||
|
import CollectorAPI.models as CollectorDB
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
|
||||||
|
def gen_report(from_date, to_date, affected_honeys, countries, with_data=0):
|
||||||
|
from_date = datetime.combine(from_date, datetime.min.time())
|
||||||
|
from_date = from_date.replace(minute=0, hour=0, second=0, microsecond= 0)
|
||||||
|
date_from = pytz.utc.localize(from_date)
|
||||||
|
to_date = datetime.combine(to_date, datetime.min.time())
|
||||||
|
to_date = to_date.replace(hour=23, minute=59, second=59, microsecond=99999)
|
||||||
|
date_to = pytz.utc.localize(to_date)
|
||||||
|
|
||||||
|
attack = CollectorAPI.models.HoneypotInfo.objects.values('src_ip').annotate(icount=Count('src_ip')).\
|
||||||
|
order_by('icount').filter(event_timestamp__gte=date_from, event_timestamp__lte=date_to,
|
||||||
|
server_id_id__in=affected_honeys)
|
||||||
|
if 'all' not in countries:
|
||||||
|
attack = attack.filter(countryISO__in=countries)
|
||||||
|
|
||||||
|
attackers = []
|
||||||
|
for attacker in attack:
|
||||||
|
print(attacker['src_ip'])
|
||||||
|
if with_data == 1:
|
||||||
|
attackers.append([attacker['src_ip'], attacker['icount']])
|
||||||
|
else:
|
||||||
|
attackers.append(attacker['src_ip'])
|
||||||
|
|
||||||
|
report = CollectorAPI.models.HoneypotReportsStorage.objects.filter(from_date=date_from, to_date=date_to).exists()
|
||||||
|
if report:
|
||||||
|
report = CollectorAPI.models.HoneypotReportsStorage.objects.filter(from_date=date_from, to_date=date_to)
|
||||||
|
report = report[0]
|
||||||
|
|
||||||
|
else:
|
||||||
|
#let's save to db
|
||||||
|
report = CollectorAPI.models.HoneypotReportsStorage()
|
||||||
|
report.from_date = date_from
|
||||||
|
report.to_date = date_to
|
||||||
|
report.affected_honeys = json.dumps(affected_honeys)
|
||||||
|
report.countries = json.dumps(countries)
|
||||||
|
report.data = json.dumps(attackers)
|
||||||
|
|
||||||
|
report.save()
|
||||||
|
return report.data
|
||||||
|
|
3
collector/CollectorAPI/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
29
collector/CollectorAPI/urls.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('post', views.post, name='post'),
|
||||||
|
path('post_local', views.post_local, name='post_local'),
|
||||||
|
path('targets', views.get_targets, name='get_targets'),
|
||||||
|
path('time/from', views.get_from_time, name='get_from_time'),
|
||||||
|
path('time/to', views.get_to_time, name='get_to_time'),
|
||||||
|
path('report/ips', views.get_attack_ips_json, name='get_attack_ips_json'),
|
||||||
|
path('report/countries', views.get_attack_countries_json, name='get_attack_ips_json'),
|
||||||
|
path('report/protocols', views.get_protocols_json, name='get_attack_ips_json'),
|
||||||
|
path('report/type_per_server', views.get_type_per_server_json, name='get_type_per_server_json'),
|
||||||
|
# path('sync', views.sync_missing_info, name='sync_missing_info'),
|
||||||
|
path('agregate/day/ip', views.agregate_ip_24h, name='agregate_ip_day'),
|
||||||
|
path('agregate/week/ip', views.agregate_ip_7d, name='agregate_ip_week'),
|
||||||
|
path('agregate/month/ip', views.agregate_ip_30d, name='agregate_ip_month'),
|
||||||
|
path('agregate/day/country', views.agregate_country_24h, name='agregate_country_day'),
|
||||||
|
path('agregate/week/country', views.agregate_country_7d, name='agregate_country_week'),
|
||||||
|
path('agregate/month/country', views.agregate_country_30d, name='agregate_country_month'),
|
||||||
|
path('agregate/day/perserver', views.agregate_per_server_24h, name='agregate_per_server_day'),
|
||||||
|
path('agregate/week/perserver', views.agregate_per_server_7d, name='agregate_per_server_week'),
|
||||||
|
path('agregate/month/perserver', views.agregate_per_server_30d, name='agregate_per_server_month'),
|
||||||
|
path('agregate/day/perserver/bg', views.agregate_per_server_bg_24h, name='agregate_per_server_bg_day'),
|
||||||
|
path('agregate/week/perserver/bg', views.agregate_per_server_bg_7d, name='agregate_per_server_bg_week'),
|
||||||
|
path('agregate/month/perserver/bg', views.agregate_per_server_bg_30d, name='agregate_per_server_bg_month'),
|
||||||
|
|
||||||
|
]
|
874
collector/CollectorAPI/views.py
Normal file
|
@ -0,0 +1,874 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import json
|
||||||
|
import pytz
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
import sys
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
import CollectorAPI.models as CollectorDB
|
||||||
|
from django.db import connection
|
||||||
|
# Create your views here.
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
|
||||||
|
# We need to disable CSRF as we are not using it.
|
||||||
|
@csrf_exempt
|
||||||
|
def post(request):
|
||||||
|
"""
|
||||||
|
URL end point: /API/post
|
||||||
|
Base post function.
|
||||||
|
Requires:
|
||||||
|
POST request
|
||||||
|
Authorization Header with type "Token xxxxxxx"
|
||||||
|
File containing the JSON
|
||||||
|
"""
|
||||||
|
if request.method != 'POST':
|
||||||
|
return HttpResponse('Only post method accepted')
|
||||||
|
|
||||||
|
if 'Authorization' not in request.headers:
|
||||||
|
return HttpResponse('Authorization token required')
|
||||||
|
|
||||||
|
token = request.headers['Authorization']
|
||||||
|
token = token.split()
|
||||||
|
if token[0] != 'Token':
|
||||||
|
return HttpResponse('What are you trying to pull?')
|
||||||
|
|
||||||
|
server = CollectorDB.HoneyPotServer.objects.filter(key=token[1])
|
||||||
|
|
||||||
|
if not server:
|
||||||
|
return HttpResponse('Invalid key ...')
|
||||||
|
|
||||||
|
if not server[0].isActive:
|
||||||
|
return HttpResponse('Invalid key ...')
|
||||||
|
|
||||||
|
if server[0].ip != get_client_ip(request):
|
||||||
|
return HttpResponse('Invalid key ...')
|
||||||
|
|
||||||
|
for key, file in request.FILES.items():
|
||||||
|
tmp_file = file.file
|
||||||
|
json_data = ''
|
||||||
|
try:
|
||||||
|
json_data = json.loads(tmp_file.read())
|
||||||
|
except:
|
||||||
|
print('Error loading JSON file from ', server[0])
|
||||||
|
|
||||||
|
if post_read_json_and_store(json_data, server[0]):
|
||||||
|
server[0].save()
|
||||||
|
|
||||||
|
print(server[0].name, file=sys.stderr)
|
||||||
|
return HttpResponse("200 OK")
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def post_local(request):
|
||||||
|
"""
|
||||||
|
URL end point: /API/post_local
|
||||||
|
Base post function.
|
||||||
|
Requires:
|
||||||
|
POST request
|
||||||
|
Authorization Header with type "IP xxxxxxx"
|
||||||
|
File containing the JSON
|
||||||
|
"""
|
||||||
|
if request.method != 'POST':
|
||||||
|
return HttpResponse('Only post method accepted')
|
||||||
|
|
||||||
|
if 'Authorization' not in request.headers:
|
||||||
|
return HttpResponse('Authorization token required')
|
||||||
|
|
||||||
|
token = request.headers['Authorization']
|
||||||
|
token = token.split()
|
||||||
|
if token[0] != 'IP':
|
||||||
|
return HttpResponse('What are you trying to pull?')
|
||||||
|
|
||||||
|
server = CollectorDB.HoneyPotServer.objects.filter(ip__exact=token[1])
|
||||||
|
|
||||||
|
if not server:
|
||||||
|
return HttpResponse('Invalid key ...')
|
||||||
|
|
||||||
|
if not server[0].isActive:
|
||||||
|
return HttpResponse('Invalid key ...')
|
||||||
|
|
||||||
|
if get_client_ip(request) != '127.0.0.1':
|
||||||
|
return HttpResponse('Invalid key ...')
|
||||||
|
|
||||||
|
for key, file in request.FILES.items():
|
||||||
|
tmp_file = file.file
|
||||||
|
json_data = ''
|
||||||
|
try:
|
||||||
|
json_data = json.loads(tmp_file.read())
|
||||||
|
except:
|
||||||
|
print('Error loading JSON file from ', server[0])
|
||||||
|
|
||||||
|
if post_read_json_and_store(json_data, server[0]):
|
||||||
|
server[0].save()
|
||||||
|
|
||||||
|
print(server[0].name, file=sys.stderr)
|
||||||
|
return HttpResponse("200 OK")
|
||||||
|
|
||||||
|
|
||||||
|
def get_targets(request):
|
||||||
|
if get_client_ip(request) != '127.0.0.1':
|
||||||
|
return HttpResponse('Invalid key ...')
|
||||||
|
servers = CollectorDB.HoneyPotServer.objects.filter(isActive=True)
|
||||||
|
response = []
|
||||||
|
for server in servers:
|
||||||
|
response.append(server.ip)
|
||||||
|
|
||||||
|
response = ', '.join(response)
|
||||||
|
get_from_time(request)
|
||||||
|
return HttpResponse(json.dumps(response), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def get_from_time(request):
|
||||||
|
if get_client_ip(request) != '127.0.0.1':
|
||||||
|
return HttpResponse('Invalid key ...')
|
||||||
|
now = datetime.now()
|
||||||
|
if now.minute < 15:
|
||||||
|
from_time = now.replace(minute=45, second=0, microsecond=0) - timedelta(hours=1)
|
||||||
|
elif now.minute < 30:
|
||||||
|
from_time = now.replace(minute=0, second=0, microsecond=0)
|
||||||
|
elif now.minute < 45:
|
||||||
|
from_time = now.replace(minute=15, second=0, microsecond=0)
|
||||||
|
else:
|
||||||
|
from_time = now.replace(minute=30, second=0, microsecond=0)
|
||||||
|
from_time = from_time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
|
return HttpResponse(from_time)
|
||||||
|
|
||||||
|
|
||||||
|
def get_to_time(request):
|
||||||
|
if get_client_ip(request) != '127.0.0.1':
|
||||||
|
return HttpResponse('Invalid key ...')
|
||||||
|
now = datetime.now()
|
||||||
|
if now.minute < 15:
|
||||||
|
from_to = now.replace(minute=59, second=59, microsecond=999999) - timedelta(hours=1)
|
||||||
|
elif now.minute < 30:
|
||||||
|
from_to = now.replace(minute=14, second=59, microsecond=999999)
|
||||||
|
elif now.minute < 45:
|
||||||
|
from_to = now.replace(minute=29, second=59, microsecond=999999)
|
||||||
|
else:
|
||||||
|
from_to = now.replace(minute=44, second=59, microsecond=999999)
|
||||||
|
from_to = from_to.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
|
return HttpResponse(from_to)
|
||||||
|
|
||||||
|
|
||||||
|
def get_attack_ips_json(request):
|
||||||
|
"""
|
||||||
|
Returns the URL /API/report/ips
|
||||||
|
Request accepts:
|
||||||
|
days = int
|
||||||
|
limit = int
|
||||||
|
ISO = str
|
||||||
|
no_count -> If set returns only IPs
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
|
||||||
|
|
||||||
|
days = 1
|
||||||
|
limit = 10
|
||||||
|
ISO = 'any'
|
||||||
|
no_count = True
|
||||||
|
show_iso = False
|
||||||
|
if 'no_count' in request.GET:
|
||||||
|
no_count = False
|
||||||
|
|
||||||
|
if 'days' in request.GET:
|
||||||
|
days = int(request.GET['days'])
|
||||||
|
|
||||||
|
if 'limit' in request.GET:
|
||||||
|
limit = int(request.GET['limit'])
|
||||||
|
|
||||||
|
if 'iso' in request.GET:
|
||||||
|
ISO = request.GET['iso']
|
||||||
|
ISO = ISO.upper()
|
||||||
|
|
||||||
|
if 'show_iso' in request.GET:
|
||||||
|
show_iso = True
|
||||||
|
|
||||||
|
if ISO == 'any' and days in [1, 7, 30]:
|
||||||
|
attacks = gen_agregated_ips(days, limit, no_count, show_iso)
|
||||||
|
else:
|
||||||
|
attacks = gen_attack_ips(days, limit, ISO, no_count, show_iso)
|
||||||
|
return HttpResponse(json.dumps(attacks), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def get_attack_countries_json(request):
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
|
||||||
|
days = 1
|
||||||
|
limit = 10
|
||||||
|
no_count = True
|
||||||
|
if 'no_count' in request.GET:
|
||||||
|
no_count = False
|
||||||
|
|
||||||
|
if 'days' in request.GET:
|
||||||
|
days = int(request.GET['days'])
|
||||||
|
|
||||||
|
if 'limit' in request.GET:
|
||||||
|
limit = int(request.GET['limit'])
|
||||||
|
|
||||||
|
if days in [1, 7, 30]:
|
||||||
|
attacks = gen_agregated_countries(days, limit, no_count)
|
||||||
|
else:
|
||||||
|
attacks = gen_attack_countries(days, limit, no_count)
|
||||||
|
return HttpResponse(json.dumps(attacks), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def get_protocols_json(request):
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
|
||||||
|
days = 1
|
||||||
|
limit = 10
|
||||||
|
no_count = True
|
||||||
|
if 'no_count' in request.GET:
|
||||||
|
no_count = False
|
||||||
|
|
||||||
|
if 'days' in request.GET:
|
||||||
|
days = int(request.GET['days'])
|
||||||
|
|
||||||
|
if 'limit' in request.GET:
|
||||||
|
limit = int(request.GET['limit'])
|
||||||
|
|
||||||
|
attacks = gen_protocols(days, limit, no_count)
|
||||||
|
return HttpResponse(json.dumps(attacks), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def get_type_per_server_json(request):
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
|
||||||
|
days = 0
|
||||||
|
if 'days' in request.GET:
|
||||||
|
days = int(request.GET['days'])
|
||||||
|
iso = 'any'
|
||||||
|
if 'iso' in request.GET:
|
||||||
|
iso = request.GET['iso']
|
||||||
|
|
||||||
|
if days in [1, 7, 30] and iso in ['any', 'bg', 'BG']:
|
||||||
|
types_per_server = gen_agregated_per_server(days, iso)
|
||||||
|
else:
|
||||||
|
types_per_server = json.dumps(gen_type_per_server(days, iso), sort_keys=True)
|
||||||
|
|
||||||
|
return HttpResponse(json.dumps(types_per_server), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_per_server_24h(request):
|
||||||
|
load_data = gen_type_per_server(1)
|
||||||
|
try:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='24h_all')
|
||||||
|
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer()
|
||||||
|
data.data_id = '24h_all'
|
||||||
|
|
||||||
|
data.data = load_data
|
||||||
|
data.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_per_server_7d(request):
|
||||||
|
load_data = gen_type_per_server(7)
|
||||||
|
try:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='7d_all')
|
||||||
|
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer()
|
||||||
|
data.data_id = '7d_all'
|
||||||
|
|
||||||
|
data.data = load_data
|
||||||
|
data.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_per_server_30d(request):
|
||||||
|
load_data = gen_type_per_server(30)
|
||||||
|
try:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='30d_all')
|
||||||
|
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer()
|
||||||
|
data.data_id = '30d_all'
|
||||||
|
|
||||||
|
data.data = load_data
|
||||||
|
data.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_per_server_bg_24h(request):
|
||||||
|
load_data = gen_type_per_server(1, 'bg')
|
||||||
|
try:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='24h_bg')
|
||||||
|
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer()
|
||||||
|
data.data_id = '24h_bg'
|
||||||
|
|
||||||
|
data.data = load_data
|
||||||
|
data.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_per_server_bg_7d(request):
|
||||||
|
load_data = gen_type_per_server(7, 'bg')
|
||||||
|
try:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='7d_bg')
|
||||||
|
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer()
|
||||||
|
data.data_id = '7d_bg'
|
||||||
|
|
||||||
|
data.data = load_data
|
||||||
|
data.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_per_server_bg_30d(request):
|
||||||
|
load_data = gen_type_per_server(30, 'bg')
|
||||||
|
try:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id='30d_bg')
|
||||||
|
except CollectorDB.HoneypotAgregatePerServer.DoesNotExist:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer()
|
||||||
|
data.data_id = '30d_bg'
|
||||||
|
|
||||||
|
data.data = load_data
|
||||||
|
data.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
def agregate_ip_24h(request):
|
||||||
|
load_data = gen_attack_ips(1, 0, 'any', True, True)
|
||||||
|
CollectorDB.HoneypotAgregate24hIps.objects.all().delete()
|
||||||
|
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate24hips_id_seq\" RESTART WITH 1;'
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query)
|
||||||
|
for data in load_data:
|
||||||
|
entry = CollectorDB.HoneypotAgregate24hIps()
|
||||||
|
entry.ip = data[0]
|
||||||
|
entry.country_iso = data[1]
|
||||||
|
entry.count = data[2]
|
||||||
|
entry.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_ip_bg_24h(request):
|
||||||
|
load_data = gen_attack_ips(1, 0, 'BG', True, True)
|
||||||
|
CollectorDB.HoneypotAgregate24hBGIps.objects.all().delete()
|
||||||
|
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate24hips_id_seq\" RESTART WITH 1;'
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query)
|
||||||
|
for data in load_data:
|
||||||
|
entry = CollectorDB.HoneypotAgregate24hBGIps()
|
||||||
|
entry.ip = data[0]
|
||||||
|
entry.country_iso = data[1]
|
||||||
|
entry.count = data[2]
|
||||||
|
entry.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_ip_7d(request):
|
||||||
|
load_data = gen_attack_ips(7, 0, 'any', True, True)
|
||||||
|
CollectorDB.HoneypotAgregate7DIps.objects.all().delete()
|
||||||
|
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate7dips_id_seq\" RESTART WITH 1;'
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query)
|
||||||
|
for data in load_data:
|
||||||
|
entry = CollectorDB.HoneypotAgregate7DIps()
|
||||||
|
entry.ip = data[0]
|
||||||
|
entry.country_iso = data[1]
|
||||||
|
entry.count = data[2]
|
||||||
|
entry.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_ip_bg_7d(request):
|
||||||
|
load_data = gen_attack_ips(7, 0, 'BG', True, True)
|
||||||
|
CollectorDB.HoneypotAgregate7DIBGps.objects.all().delete()
|
||||||
|
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate7dibgps_id_seq\" RESTART WITH 1;'
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query)
|
||||||
|
for data in load_data:
|
||||||
|
entry = CollectorDB.HoneypotAgregate7DIBGps()
|
||||||
|
entry.ip = data[0]
|
||||||
|
entry.country_iso = data[1]
|
||||||
|
entry.count = data[2]
|
||||||
|
entry.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_ip_30d(request):
|
||||||
|
load_data = gen_attack_ips(30, 0, 'any', True, True)
|
||||||
|
CollectorDB.HoneypotAgregate30DIps.objects.all().delete()
|
||||||
|
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate30dips_id_seq\" RESTART WITH 1;'
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query)
|
||||||
|
for data in load_data:
|
||||||
|
entry = CollectorDB.HoneypotAgregate30DIps()
|
||||||
|
entry.ip = data[0]
|
||||||
|
entry.country_iso = data[1]
|
||||||
|
entry.count = data[2]
|
||||||
|
entry.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_ip_bg_30d(request):
|
||||||
|
load_data = gen_attack_ips(30, 0, 'BG', True, True)
|
||||||
|
CollectorDB.HoneypotAgregate30DBGIps.objects.all().delete()
|
||||||
|
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate30dbgips_id_seq\" RESTART WITH 1;'
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query)
|
||||||
|
for data in load_data:
|
||||||
|
entry = CollectorDB.HoneypotAgregate30DBGIps()
|
||||||
|
entry.ip = data[0]
|
||||||
|
entry.country_iso = data[1]
|
||||||
|
entry.count = data[2]
|
||||||
|
entry.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_country_24h(request):
|
||||||
|
load_data = gen_attack_countries(1, 0)
|
||||||
|
CollectorDB.HoneypotAgregate24hCountry.objects.all().delete()
|
||||||
|
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate24hcountry_id_seq\" RESTART WITH 1;'
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query)
|
||||||
|
for data in load_data:
|
||||||
|
entry = CollectorDB.HoneypotAgregate24hCountry()
|
||||||
|
entry.country_iso = data[0]
|
||||||
|
entry.count = data[1]
|
||||||
|
entry.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_country_7d(request):
|
||||||
|
load_data = gen_attack_countries(7, 0)
|
||||||
|
CollectorDB.HoneypotAgregate7DCountry.objects.all().delete()
|
||||||
|
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate7dcountry_id_seq\" RESTART WITH 1;'
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query)
|
||||||
|
for data in load_data:
|
||||||
|
entry = CollectorDB.HoneypotAgregate7DCountry()
|
||||||
|
entry.country_iso = data[0]
|
||||||
|
entry.count = data[1]
|
||||||
|
entry.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def agregate_country_30d(request):
|
||||||
|
load_data = gen_attack_countries(30, 0)
|
||||||
|
CollectorDB.HoneypotAgregate30DCountry.objects.all().delete()
|
||||||
|
query = 'ALTER SEQUENCE \"CollectorAPI_honeypotagregate30dcountry_id_seq\" RESTART WITH 1;'
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query)
|
||||||
|
for data in load_data:
|
||||||
|
entry = CollectorDB.HoneypotAgregate30DCountry()
|
||||||
|
entry.country_iso = data[0]
|
||||||
|
entry.count = data[1]
|
||||||
|
entry.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def sync_missing_info(request):
|
||||||
|
missing = CollectorDB.HoneypotInfo.objects.filter(countryISO__isnull=False, longitude__isnull=True).order_by('id')\
|
||||||
|
.all()[:50000]
|
||||||
|
for p in missing:
|
||||||
|
hit = p.raw_entry
|
||||||
|
if 'geoip' in hit['_source']:
|
||||||
|
if 'city_name' in hit['_source']['geoip']:
|
||||||
|
p.city_name = hit['_source']['geoip']['city_name']
|
||||||
|
if 'region_code' in hit['_source']['geoip']:
|
||||||
|
p.region_code = hit['_source']['geoip']['region_code']
|
||||||
|
if 'region_name' in hit['_source']['geoip']:
|
||||||
|
p.region_name = hit['_source']['geoip']['region_name']
|
||||||
|
if 'continent_code' in hit['_source']['geoip']:
|
||||||
|
p.continent_code = hit['_source']['geoip']['continent_code']
|
||||||
|
if 'latitude' in hit['_source']['geoip']:
|
||||||
|
p.latitude = hit['_source']['geoip']['latitude']
|
||||||
|
if 'longitude' in hit['_source']['geoip']:
|
||||||
|
p.longitude = hit['_source']['geoip']['longitude']
|
||||||
|
p.save()
|
||||||
|
output = ['OK']
|
||||||
|
return HttpResponse(json.dumps(output), content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def post_read_json_and_store(json_data, server_id):
|
||||||
|
"""
|
||||||
|
Helper function that should read JSON line by line and set it up in DB
|
||||||
|
:param json_data:
|
||||||
|
:param server_id:
|
||||||
|
:return boolean:
|
||||||
|
"""
|
||||||
|
if 'hits' not in json_data:
|
||||||
|
return False
|
||||||
|
skipped = 0
|
||||||
|
for hit in json_data['hits']['hits']:
|
||||||
|
# If SRC ip is the recieving server - ignore it.
|
||||||
|
if hit['_source']['src_ip'] == "83.228.89.253":
|
||||||
|
skipped = skipped + 1
|
||||||
|
continue
|
||||||
|
if hit['_source']['src_ip'] == "78.83.66.168":
|
||||||
|
skipped = skipped + 1
|
||||||
|
continue
|
||||||
|
if hit['_source']['src_ip'] == "8.8.8.8":
|
||||||
|
skipped = skipped + 1
|
||||||
|
continue
|
||||||
|
if hit['_source']['src_ip'] == hit['_source']['t-pot_ip_int']:
|
||||||
|
skipped = skipped + 1
|
||||||
|
continue
|
||||||
|
db_hit = CollectorDB.HoneypotInfo()
|
||||||
|
db_hit.server_id = server_id
|
||||||
|
db_hit.shard_id = hit['_id']
|
||||||
|
db_hit.src_ip = hit['_source']['src_ip']
|
||||||
|
if 'ip_rep' in hit['_source']:
|
||||||
|
db_hit.ip_rep = hit['_source']['ip_rep']
|
||||||
|
try:
|
||||||
|
db_hit.dst_ip = hit['_source']['t-pot_ip_ext']
|
||||||
|
except:
|
||||||
|
print('There was error with Honey pot IP for the following record record')
|
||||||
|
print(hit['_source'])
|
||||||
|
print('Skipping')
|
||||||
|
continue
|
||||||
|
|
||||||
|
db_hit.type = hit['_source']['type']
|
||||||
|
if 'protocol' in hit['_source']:
|
||||||
|
db_hit.protocol = hit['_source']['protocol']
|
||||||
|
if 'eventid' in hit['_source']:
|
||||||
|
db_hit.eventid = hit['_source']['eventid']
|
||||||
|
if 'event_type' in hit['_source']:
|
||||||
|
db_hit.event_type = hit['_source']['event_type']
|
||||||
|
if 'geoip' in hit['_source']:
|
||||||
|
# print(hit['_source']['geoip'])
|
||||||
|
if 'country_code2' in hit['_source']['geoip']:
|
||||||
|
# print(hit['_source']['geoip']['country_code2'])
|
||||||
|
db_hit.countryISO = hit['_source']['geoip']['country_code2']
|
||||||
|
if 'city_name' in hit['_source']['geoip']:
|
||||||
|
# print(hit['_source']['geoip']['city_name'])
|
||||||
|
db_hit.city_name = hit['_source']['geoip']['city_name']
|
||||||
|
if 'region_code' in hit['_source']['geoip']:
|
||||||
|
# print(hit['_source']['geoip']['region_code'])
|
||||||
|
db_hit.region_code = hit['_source']['geoip']['region_code']
|
||||||
|
if 'region_name' in hit['_source']['geoip']:
|
||||||
|
# print(hit['_source']['geoip']['region_name'])
|
||||||
|
db_hit.region_name = hit['_source']['geoip']['region_name']
|
||||||
|
if 'continent_code' in hit['_source']['geoip']:
|
||||||
|
db_hit.continent_code = hit['_source']['geoip']['continent_code']
|
||||||
|
if 'latitude' in hit['_source']['geoip']:
|
||||||
|
# print(hit['_source']['geoip']['latitude'])
|
||||||
|
db_hit.latitude = hit['_source']['geoip']['latitude']
|
||||||
|
if 'longitude' in hit['_source']['geoip']:
|
||||||
|
# print(hit['_source']['geoip']['longitude'])
|
||||||
|
db_hit.longitude = hit['_source']['geoip']['longitude']
|
||||||
|
|
||||||
|
hit['_source']['timestamp'] = hit['_source']['timestamp'].replace('+0000', 'Z')
|
||||||
|
if hit['_source']['timestamp'][19] != '.':
|
||||||
|
datetimeOBJ = datetime.strptime(hit['_source']['timestamp'], '%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
elif hit['_source']['timestamp'][-1] == 'Z':
|
||||||
|
datetimeOBJ = datetime.strptime(hit['_source']['timestamp'], '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||||
|
else:
|
||||||
|
datetimeOBJ = datetime.strptime(hit['_source']['timestamp'], '%Y-%m-%dT%H:%M:%S.%f')
|
||||||
|
|
||||||
|
datetimeOBJ = pytz.utc.localize(datetimeOBJ)
|
||||||
|
db_hit.event_timestamp = datetimeOBJ
|
||||||
|
db_hit.recieved_timestamp = datetime.now()
|
||||||
|
db_hit.raw_entry = hit
|
||||||
|
try:
|
||||||
|
db_hit.save()
|
||||||
|
raw_data = CollectorDB.HoneypotRawData()
|
||||||
|
raw_data.record_id = db_hit.id
|
||||||
|
raw_data.raw_entry = hit
|
||||||
|
try:
|
||||||
|
raw_data.save()
|
||||||
|
except:
|
||||||
|
print('Issue with raw data at ' + hit['_source']['eventid'])
|
||||||
|
except:
|
||||||
|
print('Duplicate entry found, Skipping')
|
||||||
|
|
||||||
|
print('Entries skipped ' + str(skipped))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def rolling_update(self):
|
||||||
|
hour_from = datetime.now() - timedelta(hours=1)
|
||||||
|
hour_from = hour_from.replace(minute=0, second=0, microsecond=0)
|
||||||
|
hour_to = hour_from.replace(minute=59, second=59, microsecond=999999)
|
||||||
|
hour_from = pytz.utc.localize(hour_from)
|
||||||
|
hour_to = pytz.utc.localize(hour_to)
|
||||||
|
print(hour_from)
|
||||||
|
|
||||||
|
|
||||||
|
def get_client_ip(request):
|
||||||
|
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||||
|
if x_forwarded_for:
|
||||||
|
ip = x_forwarded_for.split(',')[0]
|
||||||
|
else:
|
||||||
|
ip = request.META.get('REMOTE_ADDR')
|
||||||
|
return ip
|
||||||
|
|
||||||
|
|
||||||
|
def gen_agregated_ips(time_limit=1, count_limit=0, show_count=True, show_iso=False):
|
||||||
|
params = []
|
||||||
|
query_attack = 'SELECT DISTINCT ip'
|
||||||
|
if show_iso:
|
||||||
|
query_attack = query_attack + ', \"country_iso\"'
|
||||||
|
if show_count:
|
||||||
|
query_attack = query_attack + ', count'
|
||||||
|
|
||||||
|
query_attack = query_attack + ' FROM'
|
||||||
|
if time_limit == 1:
|
||||||
|
query_attack = query_attack + ' \"CollectorAPI_honeypotagregate24hips\"'
|
||||||
|
elif time_limit == 7:
|
||||||
|
query_attack = query_attack + ' \"CollectorAPI_honeypotagregate7dips\"'
|
||||||
|
elif time_limit == 30:
|
||||||
|
query_attack = query_attack + ' \"CollectorAPI_honeypotagregate30dips\"'
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid Range')
|
||||||
|
if show_count:
|
||||||
|
query_attack = query_attack + ' ORDER BY count DESC'
|
||||||
|
if count_limit > 0:
|
||||||
|
query_attack = query_attack + ' LIMIT %s'
|
||||||
|
params.append(count_limit)
|
||||||
|
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query_attack, params)
|
||||||
|
attack = cursor.fetchall()
|
||||||
|
print(connection.queries)
|
||||||
|
print("debug")
|
||||||
|
print(attack)
|
||||||
|
return attack
|
||||||
|
|
||||||
|
|
||||||
|
def gen_attack_ips(time_limit=0, count_limit=0, iso='any', show_count=True, show_iso=False, show_city=False):
|
||||||
|
"""
|
||||||
|
:param time_limit: -> time in days
|
||||||
|
:param count_limit: -> How many entries to get
|
||||||
|
:param iso: -> country ISO
|
||||||
|
:param show_count: -> Should it get count
|
||||||
|
:return: Array
|
||||||
|
"""
|
||||||
|
params = []
|
||||||
|
query_attack = 'SELECT DISTINCT src_ip'
|
||||||
|
if show_iso:
|
||||||
|
query_attack = query_attack + ', \"countryISO\"'
|
||||||
|
if show_city:
|
||||||
|
query_attack = query_attack + ', city_name'
|
||||||
|
if show_count:
|
||||||
|
query_attack = query_attack + ', COUNT(src_ip) as count'
|
||||||
|
|
||||||
|
query_attack = query_attack + ' FROM \"CollectorAPI_honeypotinfo\" '
|
||||||
|
if time_limit > 0 or iso != 'any':
|
||||||
|
query_attack = query_attack + ' WHERE'
|
||||||
|
if time_limit > 0:
|
||||||
|
date_from = datetime.now() - timedelta(days=time_limit)
|
||||||
|
date_from = pytz.utc.localize(date_from)
|
||||||
|
params.append(date_from)
|
||||||
|
query_attack = query_attack + ' event_timestamp > %s'
|
||||||
|
|
||||||
|
if iso != 'any' and iso.isalnum():
|
||||||
|
if time_limit > 0:
|
||||||
|
query_attack = query_attack + ' AND'
|
||||||
|
query_attack = query_attack + ' \"countryISO\" LIKE %s'
|
||||||
|
params.append(iso.upper())
|
||||||
|
|
||||||
|
if show_count:
|
||||||
|
query_attack = query_attack + ' GROUP BY src_ip'
|
||||||
|
|
||||||
|
if show_iso:
|
||||||
|
query_attack = query_attack + ', \"countryISO\"'
|
||||||
|
if show_city:
|
||||||
|
query_attack = query_attack + ', city_name'
|
||||||
|
|
||||||
|
if show_count:
|
||||||
|
query_attack = query_attack + ' ORDER BY count DESC'
|
||||||
|
if count_limit > 0:
|
||||||
|
query_attack = query_attack + ' LIMIT %s'
|
||||||
|
params.append(count_limit)
|
||||||
|
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query_attack, params)
|
||||||
|
attack = cursor.fetchall()
|
||||||
|
|
||||||
|
return attack
|
||||||
|
|
||||||
|
|
||||||
|
def gen_agregated_countries(time_limit=0, count_limit=0, show_count=True):
|
||||||
|
params = []
|
||||||
|
query_countries = 'SELECT DISTINCT \"country_iso\"'
|
||||||
|
if show_count:
|
||||||
|
query_countries = query_countries + ', count'
|
||||||
|
|
||||||
|
if time_limit == 1:
|
||||||
|
query_countries = query_countries + ' FROM \"CollectorAPI_honeypotagregate24hcountry\"'
|
||||||
|
elif time_limit == 7:
|
||||||
|
query_countries = query_countries + ' FROM \"CollectorAPI_honeypotagregate7dcountry\"'
|
||||||
|
elif time_limit == 30:
|
||||||
|
query_countries = query_countries + ' FROM \"CollectorAPI_honeypotagregate30dcountry\"'
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid Range')
|
||||||
|
|
||||||
|
if show_count:
|
||||||
|
query_countries = query_countries + ' ORDER BY count DESC'
|
||||||
|
|
||||||
|
if count_limit > 0:
|
||||||
|
query_countries = query_countries + ' LIMIT %s'
|
||||||
|
params.append(count_limit)
|
||||||
|
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query_countries, params)
|
||||||
|
countries = cursor.fetchall()
|
||||||
|
|
||||||
|
return countries
|
||||||
|
|
||||||
|
|
||||||
|
def gen_attack_countries(time_limit=0, count_limit=0, show_count=True):
|
||||||
|
"""
|
||||||
|
Function used to generate array of countries and attacks
|
||||||
|
:param time_limit:
|
||||||
|
:param count_limit:
|
||||||
|
:param show_count:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
params = []
|
||||||
|
query_countries = 'SELECT DISTINCT \"countryISO\"'
|
||||||
|
if show_count:
|
||||||
|
query_countries = query_countries + ', COUNT(\"countryISO\") as count'
|
||||||
|
|
||||||
|
query_countries = query_countries + ' FROM \"CollectorAPI_honeypotinfo\"'
|
||||||
|
if time_limit > 0:
|
||||||
|
date_from = datetime.now() - timedelta(days=time_limit)
|
||||||
|
date_from = pytz.utc.localize(date_from)
|
||||||
|
params.append(date_from)
|
||||||
|
query_countries = query_countries + ' WHERE event_timestamp > %s'
|
||||||
|
|
||||||
|
if show_count:
|
||||||
|
query_countries = query_countries + ' GROUP BY \"countryISO\" ORDER BY count DESC'
|
||||||
|
|
||||||
|
if count_limit > 0:
|
||||||
|
query_countries = query_countries + ' LIMIT %s'
|
||||||
|
params.append(count_limit)
|
||||||
|
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query_countries, params)
|
||||||
|
countries = cursor.fetchall()
|
||||||
|
|
||||||
|
return countries
|
||||||
|
|
||||||
|
|
||||||
|
def gen_protocols(time_limit=0, count_limit=0, show_count=True):
|
||||||
|
"""
|
||||||
|
Generate protocol statistics
|
||||||
|
:param time_limit:
|
||||||
|
:param count_limit:
|
||||||
|
:param show_count:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
params = []
|
||||||
|
query_protocols = 'SELECT DISTINCT protocol'
|
||||||
|
if show_count:
|
||||||
|
query_protocols = query_protocols + ', COUNT(protocol) as count'
|
||||||
|
|
||||||
|
query_protocols = query_protocols + ' FROM \"CollectorAPI_honeypotinfo\"'
|
||||||
|
if time_limit > 0:
|
||||||
|
date_from = datetime.now() - timedelta(days=time_limit)
|
||||||
|
date_from = pytz.utc.localize(date_from)
|
||||||
|
params.append(date_from)
|
||||||
|
query_protocols = query_protocols + ' WHERE event_timestamp > %s'
|
||||||
|
if show_count:
|
||||||
|
query_protocols = query_protocols + ' GROUP BY \"protocol\" ORDER BY count DESC'
|
||||||
|
|
||||||
|
if count_limit > 0:
|
||||||
|
query_protocols = query_protocols + ' LIMIT %s'
|
||||||
|
params.append(count_limit)
|
||||||
|
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query_protocols, params)
|
||||||
|
protocols = cursor.fetchall()
|
||||||
|
|
||||||
|
return protocols
|
||||||
|
|
||||||
|
|
||||||
|
def gen_agregated_per_server(time_limit=0, iso='any'):
|
||||||
|
"""
|
||||||
|
Get already generated reports for attack types per server from DB and pass it as JSON
|
||||||
|
:param time_limit:
|
||||||
|
:param iso:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if time_limit == 1 and iso == 'any':
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="24h_all")
|
||||||
|
elif time_limit == 7 and iso == 'any':
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="7d_all")
|
||||||
|
elif time_limit == 30 and iso == 'any':
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="30d_all")
|
||||||
|
elif time_limit == 1 and iso in ['bg', 'BG']:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="24h_bg")
|
||||||
|
elif time_limit == 7 and iso in ['bg', 'BG']:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="7d_bg")
|
||||||
|
elif time_limit == 30 and iso in ['bg', 'BG']:
|
||||||
|
data = CollectorDB.HoneypotAgregatePerServer.objects.get(data_id="30d_bg")
|
||||||
|
else:
|
||||||
|
raise Exception('Invalid Range')
|
||||||
|
output = json.dumps(data.data, sort_keys=True)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def gen_type_per_server(time_limit=0, iso='any'):
|
||||||
|
"""
|
||||||
|
Generate attacks per server report and save it in the DB
|
||||||
|
:param time_limit:
|
||||||
|
:param iso:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
params = []
|
||||||
|
query_type_per_server = 'SELECT DISTINCT i.type, i.server_id_id, s.name, s.ip, COUNT(i.type) as count'
|
||||||
|
query_type_per_server = query_type_per_server + ' FROM "CollectorAPI_honeypotinfo" as i LEFT JOIN'
|
||||||
|
query_type_per_server = query_type_per_server + ' "CollectorAPI_honeypotserver" as s ON (s.id = i.server_id_id)'
|
||||||
|
if time_limit > 0 or iso != 'any':
|
||||||
|
query_type_per_server = query_type_per_server + ' WHERE'
|
||||||
|
if time_limit > 0:
|
||||||
|
date_from = datetime.now() - timedelta(days=time_limit)
|
||||||
|
date_from = pytz.utc.localize(date_from)
|
||||||
|
params.append(date_from)
|
||||||
|
query_type_per_server = query_type_per_server + ' event_timestamp > %s'
|
||||||
|
|
||||||
|
if iso != 'any' and iso.isalnum():
|
||||||
|
if time_limit > 0:
|
||||||
|
query_type_per_server = query_type_per_server + ' AND'
|
||||||
|
query_type_per_server = query_type_per_server + ' \"countryISO\" = %s'
|
||||||
|
params.append(iso.upper())
|
||||||
|
|
||||||
|
query_type_per_server = query_type_per_server + ' GROUP BY i.type, i.server_id_id, s.name, s.ip'
|
||||||
|
query_type_per_server = query_type_per_server + ' ORDER BY i.server_id_id, i.type;'
|
||||||
|
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(query_type_per_server, params)
|
||||||
|
type_per_server = cursor.fetchall()
|
||||||
|
|
||||||
|
output = {}
|
||||||
|
tmp_storage = {}
|
||||||
|
tmp_storage['01_comult'] = 0
|
||||||
|
for tps in type_per_server:
|
||||||
|
tmp_storage[tps[0]] = 0
|
||||||
|
|
||||||
|
for tps in type_per_server:
|
||||||
|
output[tps[1]] = {'name': tps[2], 'ip': tps[3], 'data': tmp_storage.copy()}
|
||||||
|
|
||||||
|
for tps in type_per_server:
|
||||||
|
if output[tps[1]]['data'][tps[0]] == 0:
|
||||||
|
output[tps[1]]['data']['01_comult'] += tps[4]
|
||||||
|
output[tps[1]]['data'][tps[0]] = tps[4]
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
0
collector/CollectorWEB/__init__.py
Normal file
3
collector/CollectorWEB/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
6
collector/CollectorWEB/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CollectorwebConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'CollectorWEB'
|
26
collector/CollectorWEB/forms.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from django import forms
|
||||||
|
import datetime
|
||||||
|
import CollectorAPI.models
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateAdvReport(forms.Form):
|
||||||
|
honey_request = CollectorAPI.models.HoneyPotServer.objects.all().values_list('id', 'name').order_by('id')
|
||||||
|
honeys = []
|
||||||
|
for honey in honey_request:
|
||||||
|
honeys.append([honey[0], honey[1]])
|
||||||
|
|
||||||
|
countries = [
|
||||||
|
['all', 'all'],
|
||||||
|
['BG', 'България'],
|
||||||
|
['RU', 'Русия']
|
||||||
|
]
|
||||||
|
from_date = forms.DateField(widget=forms.SelectDateWidget(years=range(2021, datetime.date.today().year+10)),
|
||||||
|
label='От дата')
|
||||||
|
to_date = forms.DateField(widget=forms.SelectDateWidget(years=range(2021, datetime.date.today().year+10)),
|
||||||
|
label='До дата',
|
||||||
|
initial=datetime.datetime.now())
|
||||||
|
affected_honeys = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=honeys,
|
||||||
|
label="Засегнати Хъни потове")
|
||||||
|
attacker_countries = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=countries,
|
||||||
|
label="Държава на атакуващият")
|
||||||
|
|
0
collector/CollectorWEB/migrations/__init__.py
Normal file
0
collector/CollectorWEB/models.py
Normal file
122
collector/CollectorWEB/templates/base.html
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
{% load static %}
|
||||||
|
<link rel="stylesheet" href="{% static 'bootstrap/icons/bootstrap-icons.css' %}">
|
||||||
|
<link href="{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
|
||||||
|
<script src="{% static 'js/lib/jquery-3.6.0.min.js' %}" ></script>
|
||||||
|
<script src="{% static 'js/lib/jquery-ui-1.13.0-rc.2/jquery-ui.min.js' %}" ></script>
|
||||||
|
<script src="{% static 'js/lib/echarts.min.js' %}" ></script>
|
||||||
|
<script src="{% static 'js/lib/mathjax/tex-chtml.js' %}"></script>
|
||||||
|
<script src="{% static 'js/popper.min.js' %}"></script>
|
||||||
|
<script src="{% static 'bootstrap/js/bootstrap.js' %}"></script>
|
||||||
|
<script src="{% static 'bootstrap/js/bootstrap.bundle.js' %}"></script>
|
||||||
|
|
||||||
|
<script src="{% static 'js/code/main.js' %}" ></script>
|
||||||
|
<link rel='stylesheet' href="{% static 'css/default.css' %}">
|
||||||
|
<script>
|
||||||
|
$(document).ready( function() {
|
||||||
|
$('.dropdown-toggle').dropdown();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="d-flex flex-column min-vh-100">
|
||||||
|
<header>
|
||||||
|
<!-- Image and text -->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
|
<a class="navbar-brand" href="#"><img src="{% static 'images/logo.png' %}" class="logo" width="35px" style="margin-right: 8px;">HoneyPot Admin</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav mr-auto">
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<li class="nav-item active">
|
||||||
|
{% url 'index' as the_url %}
|
||||||
|
<a class="nav-link" href="{{ the_url }}" target="_blank">Overview</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
{% url 'reports' as the_url %}
|
||||||
|
<a class="nav-link" href="{{ the_url }}" id="navbarReports" target="_blank">
|
||||||
|
Общи доклади
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% if perms.CollectorAPI.adv_reports %}
|
||||||
|
<li class="nav-item">
|
||||||
|
{% url 'reports_adv' as the_url %}
|
||||||
|
<a class="nav-link" href="{{ the_url }}" id="navbarReports" target="_blank">
|
||||||
|
Подробни доклади
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.CollectorAPI.graphs %}
|
||||||
|
<li class="nav-item">
|
||||||
|
{% url 'graphs' as the_url %}
|
||||||
|
<a class="nav-link" href="{{ the_url }}" id="navbarReports" target="_blank">
|
||||||
|
Graphs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.CollectorAPI.tables %}
|
||||||
|
<li class="nav-item">
|
||||||
|
{% url 'tables' as the_url %}
|
||||||
|
<a class="nav-link" href="{{ the_url }}" id="navbarReports" target="_blank">
|
||||||
|
Tables
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ user.username }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'logout' %}">(Излез</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="nav-link" href="{% url 'password_change' %}">Смени паролата)</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'login' %}">Log In</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</main>
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-dark text-center text-white mt-auto">
|
||||||
|
<!-- Grid container -->
|
||||||
|
<div class="container p-4">
|
||||||
|
|
||||||
|
<!-- Section: Text -->
|
||||||
|
<section class="mb-4">
|
||||||
|
<p>Статистическа система за анализ на информацията от HoneyPot порталите, поръчна и разработена за <a href="https://e-gov.bg" target="_blank">Държавна агенция "Електронно управление"</a></p>
|
||||||
|
<p>Внимание - Системата е само за вътрешно ползване!!!</p>
|
||||||
|
</section>
|
||||||
|
<!-- Section: Text -->
|
||||||
|
</div>
|
||||||
|
<!-- Grid container -->
|
||||||
|
|
||||||
|
<!-- Copyright -->
|
||||||
|
<!-- <div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.2);">
|
||||||
|
© 2021 Copyright:
|
||||||
|
<a class="text-white" href="https://anavaro.com/">Stanislav Atanasov</a> & <a class="text-white" href="https://www.linkedin.com/in/teodor-boyadzhiev-39463195/">Teodor Boyadzhiev</a>
|
||||||
|
</div> -->
|
||||||
|
<!-- Copyright -->
|
||||||
|
</footer>
|
||||||
|
<!-- Footer -->
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
{% block javascript %}
|
||||||
|
{% endblock %}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
18
collector/CollectorWEB/templates/graphs.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block title %}Графики{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<table class="center" id="main">
|
||||||
|
<tr> <td> {% include "registration/_global_filter.html" %}</td></tr>
|
||||||
|
<tr id="attack-sources"> <td> {% include "registration/_attack_sources.html" %} </td> </tr>
|
||||||
|
<tr id="attack-trend"> <td> {% include "registration/_attack_trend.html" %} </td> </tr>
|
||||||
|
<tr id="multiple-targets"> <td> {% include "registration/_multiple_targets.html" %}</td></tr>
|
||||||
|
<tr id="multiple-targets-summary"> <td> {% include "registration/_multiple_targets_summary.html" %}</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
|
||||||
|
{% endblock %}
|
358
collector/CollectorWEB/templates/home.html
Normal file
|
@ -0,0 +1,358 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Home{% endblock %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<nav>
|
||||||
|
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||||
|
<button class="nav-link active" id="nav-ips-24h-tab" data-bs-toggle="tab" data-bs-target="#nav-ips-24h" type="button" role="tab" aria-controls="nav-home1" aria-selected="true">24H</button>
|
||||||
|
<button class="nav-link" id="nav-ips-7d-tab" data-bs-toggle="tab" data-bs-target="#nav-ips-7d" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">7 Days</button>
|
||||||
|
<button class="nav-link" id="nav-ips-30d-tab" data-bs-toggle="tab" data-bs-target="#nav-ips-30d" type="button" role="tab" aria-controls="nav-contact" aria-selected="false">30 Days</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="tab-content" id="nav-tabContent">
|
||||||
|
<div class="tab-pane fade show active" id="nav-ips-24h" role="tabpanel" aria-labelledby="nav-home-tab">
|
||||||
|
<table id="table-24h" data-toggle="table" class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Top 10 атакуващи IP-та (24H):</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">IP</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
<th scope="col">Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="d-flex justify-content-center table-24h-spinner">
|
||||||
|
<div class="spinner-border table-24h-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-ips-7d" role="tabpanel" aria-labelledby="nav-profile-tab">
|
||||||
|
<table id="table-7D" data-toggle="table" data-ajax="ajaxRequest7D" class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Top 10 атакуващи IP-та (7D):</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">IP</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
<th scope="col">Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="d-flex justify-content-center table-7D-spinner">
|
||||||
|
<div class="spinner-border table-7D-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-ips-30d" role="tabpanel" aria-labelledby="nav-contact-tab">
|
||||||
|
<table id="table-30D" data-toggle="table" data-ajax="ajaxRequest30D" class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Top 10 атакуващи IP-та (30D):</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">IP</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
<th scope="col">Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="d-flex justify-content-center table-30D-spinner">
|
||||||
|
<div class="spinner-border table-30D-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<table class="table" id="table-proto-24h">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Top 10 attacked protocols (24H):</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Protocol</th>
|
||||||
|
<th scope="col">Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="d-flex justify-content-center table-proto-24h-spinner">
|
||||||
|
<div class="spinner-border table-proto-24h-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||||
|
<button class="nav-link active" id="nav-countries-24h-tab" data-bs-toggle="tab" data-bs-target="#nav-countries-24h" type="button" role="tab" aria-controls="nav-home1" aria-selected="true">24H</button>
|
||||||
|
<button class="nav-link" id="nav-countries-7D-tab" data-bs-toggle="tab" data-bs-target="#nav-countries-7D" type="button" role="tab" aria-controls="nav-home2" aria-selected="true">7D</button>
|
||||||
|
<button class="nav-link" id="nav-countries-30D-tab" data-bs-toggle="tab" data-bs-target="#nav-countries-30D" type="button" role="tab" aria-controls="nav-home3" aria-selected="true">30D</button>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content" id="nav-tabContent">
|
||||||
|
<div class="tab-pane fade show active" id="nav-countries-24h" role="tabpanel" aria-labelledby="nav-home-tab">
|
||||||
|
<table class="table" id="table-countries-24h">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Top 10 атакуващи страни (24H):</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Country</th>
|
||||||
|
<th scope="col">Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="d-flex justify-content-center table-countries-24h-spinner">
|
||||||
|
<div class="spinner-border table-countries-24h-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-countries-7D" role="tabpanel" aria-labelledby="nav-home-tab">
|
||||||
|
<table class="table" id="table-countries-7D">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Top 10 атакуващи страни (7D):</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Country</th>
|
||||||
|
<th scope="col">Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="d-flex justify-content-center table-countries-7D-spinner">
|
||||||
|
<div class="spinner-border table-countries-7D-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-countries-30D" role="tabpanel" aria-labelledby="nav-home-tab">
|
||||||
|
<table class="table" id="table-countries-30D">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Top 10 атакуващи страни (30D):</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Country</th>
|
||||||
|
<th scope="col">Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="d-flex justify-content-center table-countries-30D-spinner">
|
||||||
|
<div class="spinner-border table-countries-30D-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||||
|
<button class="nav-link active" id="nav-types-24h-bg-tab" data-bs-toggle="tab" data-bs-target="#nav-types-24h-bg" type="button" role="tab" aria-controls="nav-home1" aria-selected="true">24H</button>
|
||||||
|
<button class="nav-link" id="nav-types-7D-bg-tab" data-bs-toggle="tab" data-bs-target="#nav-types-7D-bg" type="button" role="tab" aria-controls="nav-home2" aria-selected="true">7D</button>
|
||||||
|
<button class="nav-link" id="nav-types-30D-bg-tab" data-bs-toggle="tab" data-bs-target="#nav-types-30D-bg" type="button" role="tab" aria-controls="nav-home3" aria-selected="true">30D</button>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content" id="nav-tabContent">
|
||||||
|
<div class="tab-pane fade show active" id="nav-types-24h-bg" role="tabpanel" aria-labelledby="nav-home-tab">
|
||||||
|
<table class="table table-type-per-server-24H-bg" id="table-type-per-server-24H-bg">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Per Server (BULGARIA)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% url 'report_per_server_csv' as the_url %}
|
||||||
|
<a href="{{ the_url }}?filter=24h_bg">CSV</a>
|
||||||
|
<div class="d-flex justify-content-center table-type-per-server-24H-bg-spinner">
|
||||||
|
<div class="spinner-border table-type-per-server-24H-bg-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-types-7D-bg" role="tabpanel" aria-labelledby="nav-home-tab">
|
||||||
|
<table class="table table-type-per-server-7D-bg" id="table-type-per-server-7D-bg">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Per Server (BULGARIA)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% url 'report_per_server_csv' as the_url %}
|
||||||
|
<a href="{{ the_url }}?filter=7d_bg">CSV</a>
|
||||||
|
<div class="d-flex justify-content-center table-type-per-server-7D-bg-spinner">
|
||||||
|
<div class="spinner-border table-type-per-server-7D-bg-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-types-30D-bg" role="tabpanel" aria-labelledby="nav-home-tab">
|
||||||
|
<table class="table table-type-per-server-30D-bg" id="table-type-per-server-30D-bg">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Per Server (BULGARIA)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% url 'report_per_server_csv' as the_url %}
|
||||||
|
<a href="{{ the_url }}?filter=30d_bg">CSV</a>
|
||||||
|
<div class="d-flex justify-content-center table-type-per-server-30D-bg-spinner">
|
||||||
|
<div class="spinner-border table-type-per-server-30D-bg-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||||
|
<button class="nav-link active" id="nav-types-24h-tab" data-bs-toggle="tab" data-bs-target="#nav-types-24h" type="button" role="tab" aria-controls="nav-home1" aria-selected="true">24H</button>
|
||||||
|
<button class="nav-link" id="nav-types-7D-tab" data-bs-toggle="tab" data-bs-target="#nav-types-7D" type="button" role="tab" aria-controls="nav-home2" aria-selected="true">7D</button>
|
||||||
|
<button class="nav-link" id="nav-types-30D-tab" data-bs-toggle="tab" data-bs-target="#nav-types-30D" type="button" role="tab" aria-controls="nav-home3" aria-selected="true">30D</button>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content" id="nav-tabContent">
|
||||||
|
<div class="tab-pane fade show active" id="nav-types-24h" role="tabpanel" aria-labelledby="nav-home-tab">
|
||||||
|
<table class="table table-type-per-server-24H" id="table-type-per-server-24H">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Per Server (International)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% url 'report_per_server_csv' as the_url %}
|
||||||
|
<a href="{{ the_url }}?filter=24h_all">CSV</a>
|
||||||
|
<div class="d-flex justify-content-center table-type-per-server-24H-spinner">
|
||||||
|
<div class="spinner-border table-type-per-server-24H-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-types-7D" role="tabpanel" aria-labelledby="nav-home-tab">
|
||||||
|
<table class="table table-type-per-server-7D" id="table-type-per-server-7D">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Per Server</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% url 'report_per_server_csv' as the_url %}
|
||||||
|
<a href="{{ the_url }}?filter=7d_all">CSV</a>
|
||||||
|
<div class="d-flex justify-content-center table-type-per-server-7D-spinner">
|
||||||
|
<div class="spinner-border table-countries-30D-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-types-30D" role="tabpanel" aria-labelledby="nav-home-tab">
|
||||||
|
<table class="table table-type-per-server-30D" id="table-type-per-server-30D">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Per Server</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% url 'report_per_server_csv' as the_url %}
|
||||||
|
<a href="{{ the_url }}?filter=30d_all">CSV</a>
|
||||||
|
<div class="d-flex justify-content-center table-type-per-server-30D-spinner">
|
||||||
|
<div class="spinner-border table-countries-30D-spinner" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{% load humanize %}
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="5">Статус на сървъра</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Име на сървъра</th>
|
||||||
|
<th scope="col">Описание на сървъра</th>
|
||||||
|
<th scope="col">IP на сървъра</th>
|
||||||
|
<th scope="col">Последно получена информация</th>
|
||||||
|
<th scope="col">Активен ли е?</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% if servers %}
|
||||||
|
{% for server in servers %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ server.1 }}</td>
|
||||||
|
<td>{{ server.2 }}</td>
|
||||||
|
<td>{{ server.3 }}</td>
|
||||||
|
<td>{{ server.6|naturaltime }}</td>
|
||||||
|
<td>{% if server.4 %}<i class="bi bi-check-circle-fill" style="color: #0f5132"></i>{% else %}<i class="bi bi-dash-circle-fill" style="color: #a52834"></i>{% endif %}</td>
|
||||||
|
<td><a href="https://{{ server.3 }}:64297" target="_blank"><i class="bi bi-terminal"></i></a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="{% static 'js/home.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,94 @@
|
||||||
|
<div id="attack-src-title"
|
||||||
|
class="stat-sec-title center">
|
||||||
|
<table class="fullwidth">
|
||||||
|
<tr>
|
||||||
|
<td class="fullwidth">
|
||||||
|
Attack Sources
|
||||||
|
</td>
|
||||||
|
<td class="section-controls">
|
||||||
|
<span id="attack-src-hide" class="button"
|
||||||
|
onclick="toggle('attack-src-body')"> Toggle </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="attack-src-body" class="stat-body">
|
||||||
|
<div id="attack-src-filters" class="stat-sec-filters center">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="filter-fields">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="attack-src-filters-startD"
|
||||||
|
class="filter-label">Start Date: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="datepicker"
|
||||||
|
id="attack-src-filters-startD"
|
||||||
|
onchange="setIfLess('attack-src-filters-startD', 'attack-src-filters-endD')">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="attack-src-filters-endD"
|
||||||
|
class="filter-label"> End Date: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="datepicker"
|
||||||
|
id="attack-src-filters-endD"
|
||||||
|
onchange="setIfGreater('attack-src-filters-endD', 'attack-src-filters-startD')">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="attack-src-filters-minattacks"
|
||||||
|
class="filter-label"> Minimum Attacks: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="numberpicker"
|
||||||
|
id="attack-src-filters-minattacks"
|
||||||
|
onchange="verifyPositiveNumber('attack-src-filters-minattacks')"
|
||||||
|
type="number">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label class="filter-label" for="attack-src-filters-bgonly"> Bulgarian IPs Only: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input id="attack-src-filters-bg_only" type="checkbox">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<table class="filter-buttons">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span id="attack-src-filters-clear" class="button"
|
||||||
|
onclick="clearFilters(['attack-src-filters-startD',
|
||||||
|
'attack-src-filters-endD',
|
||||||
|
'attack-src-filters-minattacks'])"> Clear </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span id="attack-src-filters-apply" class="button"
|
||||||
|
onclick="updateAttackGraph()"> Apply </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="attack-src-canvas" class="ga-charts center canvas" onclick="resizeEChart($('#attack-src-canvas'))"></div>
|
||||||
|
</div>
|
122
collector/CollectorWEB/templates/registration/_attack_trend.html
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
<div id="attack-trend-title"
|
||||||
|
class="stat-sec-title center">
|
||||||
|
<table class="fullwidth">
|
||||||
|
<tr>
|
||||||
|
<td class="fullwidth">
|
||||||
|
Attack Trend
|
||||||
|
</td>
|
||||||
|
<td class="section-controls">
|
||||||
|
<span id="attack-trend-hide" class="button"
|
||||||
|
onclick="toggle('attack-trend-body')"> Toggle </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="attack-trend-body" class="stat-body">
|
||||||
|
<div id="attack-trend-filters" class="stat-sec-filters center">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="filter-fields">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="attack-trend-filters-startD"
|
||||||
|
class="filter-label">Start Date: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="datepicker"
|
||||||
|
id="attack-trend-filters-startD"
|
||||||
|
onchange="setIfLess('attack-trend-filters-startD', 'attack-trend-filters-endD')">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="attack-trend-filters-endD"
|
||||||
|
class="filter-label"> End Date: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="datepicker"
|
||||||
|
id="attack-trend-filters-endD"
|
||||||
|
onchange="setIfGreater('attack-trend-filters-endD', 'attack-trend-filters-startD')">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="attack-trend-filters-resolution"
|
||||||
|
class="filter-label"> Resolution: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select class="select" id="attack-trend-filters-resolution">
|
||||||
|
<option value="hour"> Hour </option>
|
||||||
|
<option value="day" selected> Day </option>
|
||||||
|
<option value="week"> Week </option>
|
||||||
|
</select>
|
||||||
|
</td></tr>
|
||||||
|
<tr>
|
||||||
|
<td> <label
|
||||||
|
for="attack-trend-filters-bg_only"
|
||||||
|
class="filter-label"> Bulgarian IPs Only: </label> </td>
|
||||||
|
<td>
|
||||||
|
<input id="attack-trend-filters-bg_only" type="checkbox">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<table class="filter-buttons">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span id="attack-trend-filters-clear" class="button"
|
||||||
|
onclick="clearFilters(['attack-trend-filters-startD',
|
||||||
|
'attack-trend-filters-endD'])"> Clear </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span id="attack-trend-filters-apply" class="button"
|
||||||
|
onclick="updateAttackTrend()"> Apply </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="attack-trend-canvas" class="ga-charts center canvas" onclick="resizeEChart($('#attack-trend-canvas'))"></div>
|
||||||
|
<div id="attack-trend-info-panel" class="info-panel"> </div>
|
||||||
|
|
||||||
|
<table id="attack-trend-parameters" class="parameter-table">
|
||||||
|
<tr><td class="column-title">Parameter</td><td class="column-title">Value</td></tr>
|
||||||
|
<tr><td>a</td><td id="attack-trend-param-value-a"></td></tr>
|
||||||
|
<tr><td>b</td><td id="attack-trend-param-value-b"></td></tr>
|
||||||
|
<tr><td>\(\sigma_a\)</td><td id="attack-trend-param-value-sigma_a"></td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="details">
|
||||||
|
<details>
|
||||||
|
<summary>Details</summary>
|
||||||
|
<p> All numbers in the results are rounded to 2 digits after the decimal point. </p>
|
||||||
|
<p>
|
||||||
|
For modelling the trend of the attacks is used a linear model,
|
||||||
|
$$ y = a t + b + \epsilon$$
|
||||||
|
$$ \epsilon \sim N(0, \mu)$$
|
||||||
|
where y is the number of attacks, t is the time step, a is scale, and b is offset.
|
||||||
|
The time-step is determined by the resolution, which can be hour, day or week.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The parameters of the model are fit according to
|
||||||
|
$$ a = \frac{\sum_i (y - \mu_y)(t_i - \mu_t)}{\sum_i(t_i - \mu_t)^2} $$
|
||||||
|
$$ b = \mu_y - a \mu_t $$
|
||||||
|
$$ \mu_t = \frac{1}{n} \sum_{i=1}^n t_i $$
|
||||||
|
$$ \mu_y = \frac{1}{n} \sum_{i=1}^n y_i $$
|
||||||
|
Then the uncertainty of the scale is estimated by assuming Gaussian distribution of the errors, according to
|
||||||
|
$$ \sigma_a = \sqrt{\frac{\sum_i (y_i - a x_i - b)^2}{(n-2)\sum_i (x_i - \mu_x)^2}} $$
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p> For further details, please check <a href="https://en.wikipedia.org/wiki/Simple_linear_regression"> linear regression</a>. </p>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td> <label for="global-filters-ip"
|
||||||
|
class="filter-label"> IP: </label> </td>
|
||||||
|
<td> <input id="global-filters-ip"> </td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<span id="attack-trend-filters-clear" class="button"
|
||||||
|
onclick="clearFilters(['global-filters-ip'])"> Clear </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<div id="multiple-targets-title"
|
||||||
|
class="stat-sec-title center">
|
||||||
|
<table class="fullwidth">
|
||||||
|
<tr>
|
||||||
|
<td class="fullwidth">
|
||||||
|
Multiple Targets
|
||||||
|
</td>
|
||||||
|
<td class="section-controls">
|
||||||
|
<span id="attack-multi-hide" class="button"
|
||||||
|
onclick="toggle('attack-multi-body')"> Toggle </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="attack-multi-body" class="stat-body" >
|
||||||
|
<div id="attack-multi-filters" class="stat-sec-filters center">
|
||||||
|
<table class="filter-buttons">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span id="attack-multi-filters-apply" class="button"
|
||||||
|
onclick="updateMulti()"> Apply </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="attack-multi-canvas" class="ga-charts center canvas" onclick="resizeEChart($('#attack-multi-canvas'))"></div>
|
||||||
|
</div>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<div id="multiple-targets-summary-title"
|
||||||
|
class="stat-sec-title center">
|
||||||
|
<table class="fullwidth">
|
||||||
|
<tr>
|
||||||
|
<td class="fullwidth">
|
||||||
|
Multiple Targets Summary
|
||||||
|
</td>
|
||||||
|
<td class="section-controls">
|
||||||
|
<span id="attack-multi-summary-hide" class="button"
|
||||||
|
onclick="toggle('attack-multi-summary-body')"> Toggle </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="attack-multi-summary-body" class="stat-body" >
|
||||||
|
<div id="attack-multi-summary-filters" class="stat-sec-filters center">
|
||||||
|
<table class="filter-buttons">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span id="attack-multi-summary-filters-apply" class="button"
|
||||||
|
onclick="updateMultiSummary()"> Apply </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="attack-multi-summary-canvas" class="ga-charts center canvas" onclick="resizeEChart($('#attack-multi-summary-canvas'))"></div>
|
||||||
|
</div>
|
12
collector/CollectorWEB/templates/registration/login.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Login{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Log In</h2>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button type="submit">Log In</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
194
collector/CollectorWEB/templates/report.html
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Доклади{% endblock %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<H1>Създаване на репорти</H1>
|
||||||
|
<p>Можете да използвате предоставените връзки за да генерирате различни репорти. За удобство може да избирате между CSV и JSON.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<H1>България</H1>
|
||||||
|
<p>Репорти на регионални атаки.</p>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="8">CSV</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">24 Часа</th>
|
||||||
|
<th colspan="2">7 дни</th>
|
||||||
|
<th colspan="2">30 дни</th>
|
||||||
|
<th colspan="2">365 дни</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{% url 'report_ips_csv' as reports_ips_csv %}
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=1&iso=BG"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=1&iso=BG&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=7&iso=BG"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=7&iso=BG&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=30&iso=BG"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=30&iso=BG&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=365&iso=BG"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=365&iso=BG&no_count"> Без статистика</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="8">JSON</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">24 Часа</th>
|
||||||
|
<th colspan="2">7 дни</th>
|
||||||
|
<th colspan="2">30 дни</th>
|
||||||
|
<th colspan="2">365 дни</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{% url 'report_ips_json' as report_ips_json %}
|
||||||
|
<td><a href="{{ report_ips_json }}?days=1&iso=BG"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=1&iso=BG&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=7&iso=BG"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=7&iso=BG&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=30&iso=BG"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=30&iso=BG&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=365&iso=BG"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=365&iso=BG&no_count"> Без статистика</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<H1>Русия</H1>
|
||||||
|
<p>Репорти на регионални атаки.</p>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="8">CSV</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">24 Часа</th>
|
||||||
|
<th colspan="2">7 дни</th>
|
||||||
|
<th colspan="2">30 дни</th>
|
||||||
|
<th colspan="2">365 дни</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{% url 'report_ips_csv' as reports_ips_csv %}
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=1&iso=RU"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=1&iso=RU&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=7&iso=RU"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=7&iso=RU&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=30&iso=RU"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=30&iso=RU&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=365&iso=RU"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=365&iso=RU&no_count"> Без статистика</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="8">JSON</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">24 Часа</th>
|
||||||
|
<th colspan="2">7 дни</th>
|
||||||
|
<th colspan="2">30 дни</th>
|
||||||
|
<th colspan="2">365 дни</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{% url 'report_ips_json' as report_ips_json %}
|
||||||
|
<td><a href="{{ report_ips_json }}?days=1&iso=RU"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=1&iso=RU&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=7&iso=RU"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=7&iso=RU&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=30&iso=RU"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=30&iso=RU&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=365&iso=RU"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=365&iso=RU&no_count"> Без статистика</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<H1>Света</H1>
|
||||||
|
<p>Репорти на международни атаки.</p>
|
||||||
|
<p class="alert-warning">Ако желаете да генерирате доклади за определна държава - използвайте специфичен URL или се обърнете към програмиста</p>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="8">CSV</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">24 Часа</th>
|
||||||
|
<th colspan="2">7 дни</th>
|
||||||
|
<th colspan="2">30 дни</th>
|
||||||
|
<th colspan="2">365 дни</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{% url 'report_ips_csv' as reports_ips_csv %}
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=1"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=1&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=7"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=7&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=30"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=30&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=365"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ reports_ips_csv }}?days=365&no_count"> Без статистика</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="8">JSON</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">24 Часа</th>
|
||||||
|
<th colspan="2">7 дни</th>
|
||||||
|
<th colspan="2">30 дни</th>
|
||||||
|
<th colspan="2">365 дни</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{% url 'report_ips_json' as report_ips_json %}
|
||||||
|
<td><a href="{{ report_ips_json }}?days=1"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=1&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=7"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=7&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=30"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=30&no_count"> Без статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=365"> Със статистика</a></td>
|
||||||
|
<td><a href="{{ report_ips_json }}?days=365&no_count"> Без статистика</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
{% endblock %}
|
66
collector/CollectorWEB/templates/report_adv.html
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Създаване на доклади{% endblock %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<H1>Създаване на репорти</H1>
|
||||||
|
<p>Можете да използвате предоставените връзки за да генерирате различни репорти. За удобство може да избирате между CSV и JSON.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<form action="/reports_adv" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<input type="submit">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-md-12 center">
|
||||||
|
<h1>Генерирани доклади</h1>
|
||||||
|
<table class="table table-type-per-server-24H" id="table-type-per-server-24H">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Начална дата</th>
|
||||||
|
<th>Крайна дата</th>
|
||||||
|
<th>Хъни потове</th>
|
||||||
|
<th>Държава</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for report in reports %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ report.from_date }}</td>
|
||||||
|
<td>{{ report.to_date }}</td>
|
||||||
|
<td>
|
||||||
|
{% for honey in report.affected_honeys %}
|
||||||
|
{{ honey }} <br/>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for country in report.countries %}
|
||||||
|
{{ country }}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% url 'reports_adv' as the_url %}
|
||||||
|
<a href="{{ the_url }}?r={{ report.id }}">CSV</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
{% endblock %}
|
51
collector/CollectorWEB/templates/table_daily.html
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{% block title %}Дневна таблица{% endblock %}
|
||||||
|
{% if has_data %}
|
||||||
|
|
||||||
|
<table id="table-daily-value">
|
||||||
|
<tr>
|
||||||
|
{% for target, data in table_data.items %}
|
||||||
|
<td class="tables-target">
|
||||||
|
<table class="tables-rows">
|
||||||
|
<tr class="column-title">
|
||||||
|
<td colspan="5" class="tables-target-title"> {{ target }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td rowspan="2"> Дата</td>
|
||||||
|
<td rowspan="2"> Всички Атаки</td>
|
||||||
|
<td colspan="3"> България</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> Атаки</td>
|
||||||
|
<td> IP</td>
|
||||||
|
<td> Брой</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="summary">
|
||||||
|
{{ date }}
|
||||||
|
</td>
|
||||||
|
<td class="summary">
|
||||||
|
{{ data.all_attacks }}
|
||||||
|
</td>
|
||||||
|
<td class="summary">
|
||||||
|
{{ data.bg_attacks }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ data.top_ip }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ data.top_count }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% for count, ip in data.rest_top_ips %}
|
||||||
|
<tr>
|
||||||
|
<td> </td> <td> </td> <td> </td> <td> {{ ip }} </td> <td> {{ count }} </td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<div> Няма данни за {{ date }}</div>
|
||||||
|
{% endif %}
|
52
collector/CollectorWEB/templates/table_details.html
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{% block title %}Детайли{% endblock %}
|
||||||
|
<table class="tables-rows" id="table-details-value">
|
||||||
|
<tr class="column-title">
|
||||||
|
<td> Дата </td>
|
||||||
|
{% for target in targets %}
|
||||||
|
<td colspan="3"> {{ target }} </td>
|
||||||
|
{% endfor %}
|
||||||
|
<td colspan="3"> Общо </td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
{% for target in targets %}
|
||||||
|
<td> Всички </td>
|
||||||
|
<td> България </td>
|
||||||
|
<td> България IPs </td>
|
||||||
|
{% endfor %}
|
||||||
|
<td> Всички </td>
|
||||||
|
<td> България </td>
|
||||||
|
<td> България IPs </td>
|
||||||
|
</tr>
|
||||||
|
{% for time_point in time_points %}
|
||||||
|
<tr>
|
||||||
|
<td> {{ time_point }} </td>
|
||||||
|
{% for target in targets %}
|
||||||
|
{% if time_point in data and target in data|keyvalue:time_point %}
|
||||||
|
<td> {{ data|keyvalue:time_point|keyvalue:target|keyvalue:"all"|keyvalue:"attacks" }} </td>
|
||||||
|
<td> {{ data|keyvalue:time_point|keyvalue:target|keyvalue:"bg"|keyvalue:"attacks" }} </td>
|
||||||
|
<td> {{ data|keyvalue:time_point|keyvalue:target|keyvalue:"bg"|keyvalue:"ips" }} </td>
|
||||||
|
{% else %}
|
||||||
|
<td> 0 </td>
|
||||||
|
<td> 0 </td>
|
||||||
|
<td> 0 </td>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<td class="summary"> {{ total_over_targets|keyvalue:time_point|keyvalue:"all"|keyvalue:"attacks" }}</td>
|
||||||
|
<td class="summary"> {{ total_over_targets|keyvalue:time_point|keyvalue:"bg"|keyvalue:"attacks" }}</td>
|
||||||
|
<td class="summary"> {{ total_over_targets|keyvalue:time_point|keyvalue:"bg"|keyvalue:"ips" }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr class="summary">
|
||||||
|
<td></td>
|
||||||
|
{% for target in targets %}
|
||||||
|
<td> {{ total_over_time|keyvalue:target|keyvalue:"all"|keyvalue:"attacks" }}</td>
|
||||||
|
<td> {{ total_over_time|keyvalue:target|keyvalue:"bg"|keyvalue:"attacks" }}</td>
|
||||||
|
<td> {{ total_over_time|keyvalue:target|keyvalue:"bg"|keyvalue:"ips" }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
<td> {{ total|keyvalue:"all"|keyvalue:"attacks" }}</td>
|
||||||
|
<td> {{ total|keyvalue:"bg"|keyvalue:"attacks" }}</td>
|
||||||
|
<td> {{ total|keyvalue:"bg"|keyvalue:"ips" }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
61
collector/CollectorWEB/templates/table_simple.html
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{% block title %}Опростен таблица{% endblock %}
|
||||||
|
<table class="tables-rows" id="table-simple-value">
|
||||||
|
<tr class="column-title">
|
||||||
|
<td></td>
|
||||||
|
<td colspan="2"> {{ date24h.start }} - {{ date24h.end }}</td>
|
||||||
|
<td colspan="2"> {{ date7d.start }} - {{ date7d.end }}</td>
|
||||||
|
<td colspan="2"> {{ date30d.start }} - {{ date30d.end }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="2"> 24 часа </td>
|
||||||
|
<td colspan="2"> 7 дена </td>
|
||||||
|
<td colspan="2"> 30 дена </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td> Всички </td>
|
||||||
|
<td> България </td>
|
||||||
|
<td> Всички </td>
|
||||||
|
<td> България </td>
|
||||||
|
<td> Всички </td>
|
||||||
|
<td> България </td>
|
||||||
|
</tr>
|
||||||
|
{% for target in all_targets %}
|
||||||
|
<tr>
|
||||||
|
<td> {{ target }}</td>
|
||||||
|
{% if target in summary24h %}
|
||||||
|
<td> {{ summary24h|keyvalue:target|keyvalue:"total" }}</td>
|
||||||
|
<td> {{ summary24h|keyvalue:target|keyvalue:"domestic" }} </td>
|
||||||
|
{% else %}
|
||||||
|
<td> 0 </td>
|
||||||
|
<td> 0 </td>
|
||||||
|
{% endif %}
|
||||||
|
{% if target in summary7d %}
|
||||||
|
<td> {{ summary7d|keyvalue:target|keyvalue:"total" }}</td>
|
||||||
|
<td> {{ summary7d|keyvalue:target|keyvalue:"domestic" }} </td>
|
||||||
|
{% else %}
|
||||||
|
<td> 0 </td>
|
||||||
|
<td> 0 </td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if target in summary30d %}
|
||||||
|
<td> {{ summary30d|keyvalue:target|keyvalue:"total" }}</td>
|
||||||
|
<td> {{ summary30d|keyvalue:target|keyvalue:"domestic" }} </td>
|
||||||
|
{% else %}
|
||||||
|
<td> 0 </td>
|
||||||
|
<td> 0 </td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr class="summary">
|
||||||
|
<td> </td>
|
||||||
|
<td> {{ total|keyvalue:"summary24h"|keyvalue:"total"}} </td>
|
||||||
|
<td> {{ total|keyvalue:"summary24h"|keyvalue:"domestic"}} </td>
|
||||||
|
<td> {{ total|keyvalue:"summary7d"|keyvalue:"total"}} </td>
|
||||||
|
<td> {{ total|keyvalue:"summary7d"|keyvalue:"domestic"}} </td>
|
||||||
|
<td> {{ total|keyvalue:"summary30d"|keyvalue:"total"}} </td>
|
||||||
|
<td> {{ total|keyvalue:"summary30d"|keyvalue:"domestic"}} </td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</table>
|
163
collector/CollectorWEB/templates/tables.html
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Таблици{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div id="tables-daily">
|
||||||
|
<div id="tables-daily-filters" class="stat-sec-filters center">
|
||||||
|
<div class="stat-sec-title"> Daily </div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="tables-daily-filters-date"
|
||||||
|
class="filter-label"> Избор на ден: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="datepicker"
|
||||||
|
id="tables-daily-filters-date">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="filter-buttons">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span id="tables-daily-filters-apply" class="button"
|
||||||
|
onclick="updateTablesDaily()"> Приложи </span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span id="tables-daily-filters-clear" class="button"
|
||||||
|
onclick="clearFilters(['tables-daily-filters-date'])"> Изчисти </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span onclick="exportTableToCSV('table-daily-value', 'daily.csv')" class="button"> Export to CSV </span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span onclick="exportTableToExcel('table-daily-value', 'daily.xls')" class="button"> Export to Excel </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="tables-daily-result"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tables-simple">
|
||||||
|
<div id="tables-simple-filters" class="stat-sec-filters center">
|
||||||
|
<div class="stat-sec-title"> Simple </div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="tables-simple-filters-date"
|
||||||
|
class="filter-label"> Дата: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="datepicker"
|
||||||
|
id="tables-simple-filters-date">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="filter-buttons">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span id="tables-simple-filters-apply" class="button"
|
||||||
|
onclick="updateTablesSimple()"> Приложи </span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span id="tables-simple-filters-clear" class="button"
|
||||||
|
onclick="clearFilters(['tables-simple-filters-date'])"> Изчисти </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span onclick="exportTableToCSV('table-simple-value', 'simple.csv')" class="button"> Export to CSV </span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span onclick="exportTableToExcel('table-simple-value', 'simple.xls')" class="button"> Export to Excel </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="tables-simple-result"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tables-details">
|
||||||
|
<div id="tables-details-filters" class="stat-sec-filters center">
|
||||||
|
<div class="stat-sec-title"> Details </div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="tables-details-filters-startD"
|
||||||
|
class="filter-label"> Начална Дата: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="datepicker"
|
||||||
|
id="tables-details-filters-startD"
|
||||||
|
onchange="setIfLess('tables-details-filters-startD', 'tables-details-filters-endD')">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="tables-details-filters-startD"
|
||||||
|
class="filter-label"> Крайна Дата: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="datepicker"
|
||||||
|
id="tables-details-filters-endD"
|
||||||
|
onchange="setIfGreater('tables-details-filters-endD', 'tables-details-filters-startD')">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="tables-details-filters-resolution"
|
||||||
|
class="filter-label"> Резолюция: </label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select class="select" id="tables-details-filters-resolution">
|
||||||
|
<option value="day"> Ден </option>
|
||||||
|
<option value="week" selected> Седмица </option>
|
||||||
|
<option value="month"> Месец </option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="filter-buttons">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span id="tables-details-filters-apply" class="button"
|
||||||
|
onclick="updateTablesDetails()"> Приложи </span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span id="tables-details-filters-clear" class="button"
|
||||||
|
onclick="clearFilters([
|
||||||
|
'tables-details-filters-startD',
|
||||||
|
'tables-details-filters-endD'])"> Изчисти </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span onclick="exportTableToCSV('table-details-value', 'details.csv')" class="button"> Export to CSV </span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span onclick="exportTableToExcel('table-details-value', 'details.xls')" class="button"> Export to Excel </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="tables-details-result"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
|
||||||
|
{% endblock %}
|
0
collector/CollectorWEB/templatetags/__init__.py
Normal file
5
collector/CollectorWEB/templatetags/dict_key.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.template.defaultfilters import register
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def keyvalue(dict, key):
|
||||||
|
return dict[key]
|
3
collector/CollectorWEB/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
21
collector/CollectorWEB/urls.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.index, name='index'),
|
||||||
|
path('reports', views.reports, name='reports'),
|
||||||
|
path('reports_adv', views.reports_adv, name='reports_adv'),
|
||||||
|
path('reports/ips_csv', views.get_report_ips_csv, name='report_ips_csv'),
|
||||||
|
path('reports/ips_json', views.get_report_ips_json, name='report_ips_json'),
|
||||||
|
path('reports/per_server_csv', views.get_report_agregated_per_server_csv, name='report_per_server_csv'),
|
||||||
|
path('attack_graph/<str:start_date>/<str:end_date>/<int:min_attacks>/<str:global_ip>/<int:bg_only>', views.attack_graph, name='attack_graph'),
|
||||||
|
path('attack_trend/<str:start_date>/<str:end_date>/<str:resolution>/<str:global_ip>/<int:bg_only>', views.attack_trend, name='attack_trend'),
|
||||||
|
path('multi_target/<str:ip>', views.multi_target, name='multi_target'),
|
||||||
|
path('multi_target_summary/<str:ip>', views.multi_target_summary, name='multi_target_summary'),
|
||||||
|
path('graphs', views.graphs, name='graphs'),
|
||||||
|
path('tables', views.tables, name='tables'),
|
||||||
|
path('table_daily/<str:date>', views.table_daily, name='table_daily'),
|
||||||
|
path('table_simple/<str:date>', views.table_simple, name='table_simple'),
|
||||||
|
path('table_details/<str:start>/<str:end>/<str:res>', views.table_details, name='table_details')
|
||||||
|
]
|
1056
collector/CollectorWEB/views.py
Normal file
0
collector/HoneyPotCollector/__init__.py
Normal file
16
collector/HoneyPotCollector/asgi.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
ASGI config for HoneyPotCollector project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'HoneyPotCollector.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
24
collector/HoneyPotCollector/urls.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
"""HoneyPotCollector URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include('CollectorWEB.urls')),
|
||||||
|
path('API/', include('CollectorAPI.urls')),
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('accounts/', include('django.contrib.auth.urls')), # new
|
||||||
|
]
|
16
collector/HoneyPotCollector/wsgi.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
WSGI config for HoneyPotCollector project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'HoneyPotCollector.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
|
@ -1 +1,121 @@
|
||||||
### collector
|
# HoneyPot Data collector
|
||||||
|
|
||||||
|
HoneyPot Data collector is software intended to collect information from all TPot CE honey pots, deployed in DAEU.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.9
|
||||||
|
- PostgreSQL (developed on 13.4, but should work on any)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone repo
|
||||||
|
|
||||||
|
``
|
||||||
|
git cone https://github.com/satanasov/HoneyPotCollector.git
|
||||||
|
``
|
||||||
|
|
||||||
|
2. Install requirements
|
||||||
|
|
||||||
|
```
|
||||||
|
cd HonePotCollector
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Config SQL user
|
||||||
|
4. Make migrations
|
||||||
|
```
|
||||||
|
python3 manage.py migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Create super user
|
||||||
|
```
|
||||||
|
python manage.py createsuperuser
|
||||||
|
```
|
||||||
|
6. Start server as daemon
|
||||||
|
```
|
||||||
|
nohup python3 manage.py runserver 0.0.0.0:8000 > ~/collector.log &
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### SSH Keys
|
||||||
|
|
||||||
|
Please create private/public key pair for the user you are going to run the server. Copy public keys to all machines you are going to scrape.
|
||||||
|
|
||||||
|
### Add target servers
|
||||||
|
|
||||||
|
Go to `http://<serverip>:8000/admin` and login with superuser.
|
||||||
|
|
||||||
|
Go to "Collectorapi" > "Honey pot servers" and add servers, with IPs (use `0000` as key to generate random key)
|
||||||
|
|
||||||
|
### Automate
|
||||||
|
|
||||||
|
Add crontab to run `collector.sh` each 15 minutes (+1 just in case)
|
||||||
|
|
||||||
|
```
|
||||||
|
1,16,31,46 * * * * /bin/bash /home/collector/HoneyPotCollector/collector.sh 1> /home/collector/cron.log
|
||||||
|
```
|
||||||
|
|
||||||
|
# API Documentation
|
||||||
|
|
||||||
|
## CollectorAPI
|
||||||
|
|
||||||
|
### /API/targets
|
||||||
|
|
||||||
|
Get list of all active IPs we have to crawl
|
||||||
|
|
||||||
|
**Responds only on localhost requests**
|
||||||
|
|
||||||
|
### /API/from_time
|
||||||
|
|
||||||
|
Get from time for the current request
|
||||||
|
|
||||||
|
**Responds only on localhost requests**
|
||||||
|
|
||||||
|
### /API/to_time
|
||||||
|
|
||||||
|
Get to time for the current request
|
||||||
|
|
||||||
|
**Responds only on localhost requests**
|
||||||
|
|
||||||
|
### /API/post_local
|
||||||
|
|
||||||
|
Local post instance. Accepts only from localhost and is used to load the JSON files we get with the collector scripts.
|
||||||
|
|
||||||
|
**Responds only on localhost requests**
|
||||||
|
|
||||||
|
### /API/post
|
||||||
|
|
||||||
|
Remote post instance. Accepts only from verified active keys and servers. Loads the remote send JSON files.
|
||||||
|
|
||||||
|
### /API/report/ips
|
||||||
|
|
||||||
|
Returns JSON formatted list of results related to attacking IPs
|
||||||
|
|
||||||
|
Accepts the following GET paramaters:
|
||||||
|
|
||||||
|
- **days** (int) - how many hours back should the report return
|
||||||
|
- **limit** (int)- How many results should it return
|
||||||
|
- **iso** (str) - for which country should results be returned
|
||||||
|
- **no_count** - boolen for should the attack count be returned
|
||||||
|
|
||||||
|
### /API/report/countries
|
||||||
|
|
||||||
|
Returns JSON formatted list of results related to attacking countries
|
||||||
|
|
||||||
|
Accepts the following GET paramaters:
|
||||||
|
|
||||||
|
- **days** (int) - how many hours back should the report return
|
||||||
|
- **limit** (int)- How many results should it return
|
||||||
|
- **no_count** - boolen for should the attack count be returned
|
||||||
|
|
||||||
|
### /API/report/protocols
|
||||||
|
|
||||||
|
Returns JSON formatted list of results related to attacked protocols
|
||||||
|
|
||||||
|
Accepts the following GET paramaters:
|
||||||
|
|
||||||
|
- **days** (int) - how many hours back should the report return
|
||||||
|
- **limit** (int)- How many results should it return
|
||||||
|
- **no_count** - boolen for should the attack count be returned
|
29
collector/collector.sh
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Get times
|
||||||
|
from=$(curl http://127.0.0.1:8000/API/time/from)
|
||||||
|
to=$(curl http://127.0.0.1:8000/API/time/to)
|
||||||
|
|
||||||
|
# Get all targets
|
||||||
|
arr=$(curl http://127.0.0.1:8000/API/targets)
|
||||||
|
# Split targets
|
||||||
|
targets=$(echo $arr | sed -e 's/\[ //g' -e 's/\ ]//g' -e 's/\,//g')
|
||||||
|
|
||||||
|
# For each target DO
|
||||||
|
for target in ${targets[@]}; do
|
||||||
|
|
||||||
|
# Remove quotations
|
||||||
|
clean_target=${target//\"}
|
||||||
|
|
||||||
|
# Execute SSH command to get data.json
|
||||||
|
ssh -p 64295 tsec@${clean_target} "curl -XGET --header 'Content-Type: application/json' http://localhost:64298/logstash-*/_search?pretty=true -d'{\"size\":10000,\"query\":{\"range\":{\"timestamp\":{\"from\":\"${from}\",\"to\":\"${to}\",\"time_zone\":\"+03:00\"}}}}'" > /tmp/data.json
|
||||||
|
|
||||||
|
# Push data to local
|
||||||
|
curl -XPOST --header "Authorization: IP ${clean_target}" http://127.0.0.1:8000/API/post_local -F 'data=@/tmp/data.json'
|
||||||
|
rm /tmp/data.json
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/day/ip
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/day/country
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/day/perserver
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/day/perserver/bg
|
41740
collector/fixtures/honey.sql
Normal file
9
collector/long_agregate.sh
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/bash
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/week/ip
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/week/country
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/week/perserver
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/week/perserver/bg
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/month/ip
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/month/country
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/month/perserver
|
||||||
|
curl http://127.0.0.1:8000/API/agregate/month/perserver/bg
|
22
collector/manage.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'HoneyPotCollector.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
7
collector/requirements.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Django==3.2.7
|
||||||
|
psycopg2==2.9.1
|
||||||
|
psycopg2-binary==2.9.1
|
||||||
|
pytz==2021.1
|
||||||
|
sqlparse==0.4.2
|
||||||
|
asgiref==3.4.1
|
||||||
|
numpy==1.21.2
|
5051
collector/static/bootstrap/css/bootstrap-grid.css
vendored
Normal file
1
collector/static/bootstrap/css/bootstrap-grid.css.map
Normal file
7
collector/static/bootstrap/css/bootstrap-grid.min.css
vendored
Normal file
5050
collector/static/bootstrap/css/bootstrap-grid.rtl.css
vendored
Normal file
7
collector/static/bootstrap/css/bootstrap-grid.rtl.min.css
vendored
Normal file
481
collector/static/bootstrap/css/bootstrap-reboot.css
vendored
Normal file
|
@ -0,0 +1,481 @@
|
||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v5.1.1 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2021 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2021 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--bs-blue: #0d6efd;
|
||||||
|
--bs-indigo: #6610f2;
|
||||||
|
--bs-purple: #6f42c1;
|
||||||
|
--bs-pink: #d63384;
|
||||||
|
--bs-red: #dc3545;
|
||||||
|
--bs-orange: #fd7e14;
|
||||||
|
--bs-yellow: #ffc107;
|
||||||
|
--bs-green: #198754;
|
||||||
|
--bs-teal: #20c997;
|
||||||
|
--bs-cyan: #0dcaf0;
|
||||||
|
--bs-white: #fff;
|
||||||
|
--bs-gray: #6c757d;
|
||||||
|
--bs-gray-dark: #343a40;
|
||||||
|
--bs-gray-100: #f8f9fa;
|
||||||
|
--bs-gray-200: #e9ecef;
|
||||||
|
--bs-gray-300: #dee2e6;
|
||||||
|
--bs-gray-400: #ced4da;
|
||||||
|
--bs-gray-500: #adb5bd;
|
||||||
|
--bs-gray-600: #6c757d;
|
||||||
|
--bs-gray-700: #495057;
|
||||||
|
--bs-gray-800: #343a40;
|
||||||
|
--bs-gray-900: #212529;
|
||||||
|
--bs-primary: #0d6efd;
|
||||||
|
--bs-secondary: #6c757d;
|
||||||
|
--bs-success: #198754;
|
||||||
|
--bs-info: #0dcaf0;
|
||||||
|
--bs-warning: #ffc107;
|
||||||
|
--bs-danger: #dc3545;
|
||||||
|
--bs-light: #f8f9fa;
|
||||||
|
--bs-dark: #212529;
|
||||||
|
--bs-primary-rgb: 13, 110, 253;
|
||||||
|
--bs-secondary-rgb: 108, 117, 125;
|
||||||
|
--bs-success-rgb: 25, 135, 84;
|
||||||
|
--bs-info-rgb: 13, 202, 240;
|
||||||
|
--bs-warning-rgb: 255, 193, 7;
|
||||||
|
--bs-danger-rgb: 220, 53, 69;
|
||||||
|
--bs-light-rgb: 248, 249, 250;
|
||||||
|
--bs-dark-rgb: 33, 37, 41;
|
||||||
|
--bs-white-rgb: 255, 255, 255;
|
||||||
|
--bs-black-rgb: 0, 0, 0;
|
||||||
|
--bs-body-color-rgb: 33, 37, 41;
|
||||||
|
--bs-body-bg-rgb: 255, 255, 255;
|
||||||
|
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||||
|
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||||
|
--bs-body-font-size: 1rem;
|
||||||
|
--bs-body-font-weight: 400;
|
||||||
|
--bs-body-line-height: 1.5;
|
||||||
|
--bs-body-color: #212529;
|
||||||
|
--bs-body-bg: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:root {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--bs-body-font-family);
|
||||||
|
font-size: var(--bs-body-font-size);
|
||||||
|
font-weight: var(--bs-body-font-weight);
|
||||||
|
line-height: var(--bs-body-line-height);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
text-align: var(--bs-body-text-align);
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: inherit;
|
||||||
|
background-color: currentColor;
|
||||||
|
border: 0;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr:not([size]) {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6, h5, h4, h3, h2, h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title],
|
||||||
|
abbr[data-bs-original-title] {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
-webkit-text-decoration-skip-ink: none;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
padding: 0.2em;
|
||||||
|
background-color: #fcf8e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0d6efd;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #0a58ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: var(--bs-font-monospace);
|
||||||
|
font-size: 1em;
|
||||||
|
direction: ltr /* rtl:ignore */;
|
||||||
|
unicode-bidi: bidi-override;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: #d63384;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
a > code {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #212529;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
}
|
||||||
|
kbd kbd {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
caption-side: bottom;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: #6c757d;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
text-align: -webkit-match-parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
tr,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border-color: inherit;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus:not(:focus-visible) {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role=button] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
select:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[list]::-webkit-calendar-picker-indicator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type=button],
|
||||||
|
[type=reset],
|
||||||
|
[type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
button:not(:disabled),
|
||||||
|
[type=button]:not(:disabled),
|
||||||
|
[type=reset]:not(:disabled),
|
||||||
|
[type=submit]:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
legend {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
legend + * {
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit-fields-wrapper,
|
||||||
|
::-webkit-datetime-edit-text,
|
||||||
|
::-webkit-datetime-edit-minute,
|
||||||
|
::-webkit-datetime-edit-hour-field,
|
||||||
|
::-webkit-datetime-edit-day-field,
|
||||||
|
::-webkit-datetime-edit-month-field,
|
||||||
|
::-webkit-datetime-edit-year-field {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type=search] {
|
||||||
|
outline-offset: -2px;
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rtl:raw:
|
||||||
|
[type="tel"],
|
||||||
|
[type="url"],
|
||||||
|
[type="email"],
|
||||||
|
[type="number"] {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::file-selector-button {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
1
collector/static/bootstrap/css/bootstrap-reboot.css.map
Normal file
8
collector/static/bootstrap/css/bootstrap-reboot.min.css
vendored
Normal file
478
collector/static/bootstrap/css/bootstrap-reboot.rtl.css
vendored
Normal file
|
@ -0,0 +1,478 @@
|
||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v5.1.1 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2021 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2021 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--bs-blue: #0d6efd;
|
||||||
|
--bs-indigo: #6610f2;
|
||||||
|
--bs-purple: #6f42c1;
|
||||||
|
--bs-pink: #d63384;
|
||||||
|
--bs-red: #dc3545;
|
||||||
|
--bs-orange: #fd7e14;
|
||||||
|
--bs-yellow: #ffc107;
|
||||||
|
--bs-green: #198754;
|
||||||
|
--bs-teal: #20c997;
|
||||||
|
--bs-cyan: #0dcaf0;
|
||||||
|
--bs-white: #fff;
|
||||||
|
--bs-gray: #6c757d;
|
||||||
|
--bs-gray-dark: #343a40;
|
||||||
|
--bs-gray-100: #f8f9fa;
|
||||||
|
--bs-gray-200: #e9ecef;
|
||||||
|
--bs-gray-300: #dee2e6;
|
||||||
|
--bs-gray-400: #ced4da;
|
||||||
|
--bs-gray-500: #adb5bd;
|
||||||
|
--bs-gray-600: #6c757d;
|
||||||
|
--bs-gray-700: #495057;
|
||||||
|
--bs-gray-800: #343a40;
|
||||||
|
--bs-gray-900: #212529;
|
||||||
|
--bs-primary: #0d6efd;
|
||||||
|
--bs-secondary: #6c757d;
|
||||||
|
--bs-success: #198754;
|
||||||
|
--bs-info: #0dcaf0;
|
||||||
|
--bs-warning: #ffc107;
|
||||||
|
--bs-danger: #dc3545;
|
||||||
|
--bs-light: #f8f9fa;
|
||||||
|
--bs-dark: #212529;
|
||||||
|
--bs-primary-rgb: 13, 110, 253;
|
||||||
|
--bs-secondary-rgb: 108, 117, 125;
|
||||||
|
--bs-success-rgb: 25, 135, 84;
|
||||||
|
--bs-info-rgb: 13, 202, 240;
|
||||||
|
--bs-warning-rgb: 255, 193, 7;
|
||||||
|
--bs-danger-rgb: 220, 53, 69;
|
||||||
|
--bs-light-rgb: 248, 249, 250;
|
||||||
|
--bs-dark-rgb: 33, 37, 41;
|
||||||
|
--bs-white-rgb: 255, 255, 255;
|
||||||
|
--bs-black-rgb: 0, 0, 0;
|
||||||
|
--bs-body-color-rgb: 33, 37, 41;
|
||||||
|
--bs-body-bg-rgb: 255, 255, 255;
|
||||||
|
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||||
|
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||||
|
--bs-body-font-size: 1rem;
|
||||||
|
--bs-body-font-weight: 400;
|
||||||
|
--bs-body-line-height: 1.5;
|
||||||
|
--bs-body-color: #212529;
|
||||||
|
--bs-body-bg: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:root {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--bs-body-font-family);
|
||||||
|
font-size: var(--bs-body-font-size);
|
||||||
|
font-weight: var(--bs-body-font-weight);
|
||||||
|
line-height: var(--bs-body-line-height);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
text-align: var(--bs-body-text-align);
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: inherit;
|
||||||
|
background-color: currentColor;
|
||||||
|
border: 0;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr:not([size]) {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6, h5, h4, h3, h2, h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title],
|
||||||
|
abbr[data-bs-original-title] {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
-webkit-text-decoration-skip-ink: none;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
padding: 0.2em;
|
||||||
|
background-color: #fcf8e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0d6efd;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #0a58ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: var(--bs-font-monospace);
|
||||||
|
font-size: 1em;
|
||||||
|
direction: ltr ;
|
||||||
|
unicode-bidi: bidi-override;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: #d63384;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
a > code {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #212529;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
}
|
||||||
|
kbd kbd {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
caption-side: bottom;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: #6c757d;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
text-align: -webkit-match-parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
tr,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border-color: inherit;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus:not(:focus-visible) {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role=button] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
select:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[list]::-webkit-calendar-picker-indicator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type=button],
|
||||||
|
[type=reset],
|
||||||
|
[type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
button:not(:disabled),
|
||||||
|
[type=button]:not(:disabled),
|
||||||
|
[type=reset]:not(:disabled),
|
||||||
|
[type=submit]:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
float: right;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
legend {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
legend + * {
|
||||||
|
clear: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit-fields-wrapper,
|
||||||
|
::-webkit-datetime-edit-text,
|
||||||
|
::-webkit-datetime-edit-minute,
|
||||||
|
::-webkit-datetime-edit-hour-field,
|
||||||
|
::-webkit-datetime-edit-day-field,
|
||||||
|
::-webkit-datetime-edit-month-field,
|
||||||
|
::-webkit-datetime-edit-year-field {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type=search] {
|
||||||
|
outline-offset: -2px;
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="tel"],
|
||||||
|
[type="url"],
|
||||||
|
[type="email"],
|
||||||
|
[type="number"] {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::file-selector-button {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
8
collector/static/bootstrap/css/bootstrap-reboot.rtl.min.css
vendored
Normal file
4866
collector/static/bootstrap/css/bootstrap-utilities.css
vendored
Normal file
7
collector/static/bootstrap/css/bootstrap-utilities.min.css
vendored
Normal file
4857
collector/static/bootstrap/css/bootstrap-utilities.rtl.css
vendored
Normal file
7
collector/static/bootstrap/css/bootstrap-utilities.rtl.min.css
vendored
Normal file
11222
collector/static/bootstrap/css/bootstrap.css
vendored
Normal file
1
collector/static/bootstrap/css/bootstrap.css.map
Normal file
7
collector/static/bootstrap/css/bootstrap.min.css
vendored
Normal file
1
collector/static/bootstrap/css/bootstrap.min.css.map
Normal file
11198
collector/static/bootstrap/css/bootstrap.rtl.css
vendored
Normal file
1
collector/static/bootstrap/css/bootstrap.rtl.css.map
Normal file
7
collector/static/bootstrap/css/bootstrap.rtl.min.css
vendored
Normal file
1
collector/static/bootstrap/css/bootstrap.rtl.min.css.map
Normal file
3
collector/static/bootstrap/icons/alarm-fill.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm-fill" viewBox="0 0 16 16">
|
||||||
|
<path d="M6 .5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1H9v1.07a7.001 7.001 0 0 1 3.274 12.474l.601.602a.5.5 0 0 1-.707.708l-.746-.746A6.97 6.97 0 0 1 8 16a6.97 6.97 0 0 1-3.422-.892l-.746.746a.5.5 0 0 1-.707-.708l.602-.602A7.001 7.001 0 0 1 7 2.07V1h-.5A.5.5 0 0 1 6 .5zm2.5 5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5zM.86 5.387A2.5 2.5 0 1 1 4.387 1.86 8.035 8.035 0 0 0 .86 5.387zM11.613 1.86a2.5 2.5 0 1 1 3.527 3.527 8.035 8.035 0 0 0-3.527-3.527z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 626 B |
4
collector/static/bootstrap/icons/alarm.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm" viewBox="0 0 16 16">
|
||||||
|
<path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z"/>
|
||||||
|
<path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 711 B |
4
collector/static/bootstrap/icons/align-bottom.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-bottom" viewBox="0 0 16 16">
|
||||||
|
<rect width="4" height="12" x="6" y="1" rx="1"/>
|
||||||
|
<path d="M1.5 14a.5.5 0 0 0 0 1v-1zm13 1a.5.5 0 0 0 0-1v1zm-13 0h13v-1h-13v1z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 271 B |
3
collector/static/bootstrap/icons/align-center.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-center" viewBox="0 0 16 16">
|
||||||
|
<path d="M8 1a.5.5 0 0 1 .5.5V6h-1V1.5A.5.5 0 0 1 8 1zm0 14a.5.5 0 0 1-.5-.5V10h1v4.5a.5.5 0 0 1-.5.5zM2 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 315 B |
4
collector/static/bootstrap/icons/align-end.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-end" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M14.5 1a.5.5 0 0 0-.5.5v13a.5.5 0 0 0 1 0v-13a.5.5 0 0 0-.5-.5z"/>
|
||||||
|
<path d="M13 7a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 318 B |
3
collector/static/bootstrap/icons/align-middle.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-middle" viewBox="0 0 16 16">
|
||||||
|
<path d="M6 13a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v10zM1 8a.5.5 0 0 0 .5.5H6v-1H1.5A.5.5 0 0 0 1 8zm14 0a.5.5 0 0 1-.5.5H10v-1h4.5a.5.5 0 0 1 .5.5z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 316 B |
4
collector/static/bootstrap/icons/align-start.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-start" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M1.5 1a.5.5 0 0 1 .5.5v13a.5.5 0 0 1-1 0v-13a.5.5 0 0 1 .5-.5z"/>
|
||||||
|
<path d="M3 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 318 B |
4
collector/static/bootstrap/icons/align-top.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-top" viewBox="0 0 16 16">
|
||||||
|
<rect width="4" height="12" rx="1" transform="matrix(1 0 0 -1 6 15)"/>
|
||||||
|
<path d="M1.5 2a.5.5 0 0 1 0-1v1zm13-1a.5.5 0 0 1 0 1V1zm-13 0h13v1h-13V1z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 287 B |
3
collector/static/bootstrap/icons/alt.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alt" viewBox="0 0 16 16">
|
||||||
|
<path d="M1 13.5a.5.5 0 0 0 .5.5h3.797a.5.5 0 0 0 .439-.26L11 3h3.5a.5.5 0 0 0 0-1h-3.797a.5.5 0 0 0-.439.26L5 13H1.5a.5.5 0 0 0-.5.5zm10 0a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 0-1h-3a.5.5 0 0 0-.5.5z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 326 B |
4
collector/static/bootstrap/icons/app-indicator.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app-indicator" viewBox="0 0 16 16">
|
||||||
|
<path d="M5.5 2A3.5 3.5 0 0 0 2 5.5v5A3.5 3.5 0 0 0 5.5 14h5a3.5 3.5 0 0 0 3.5-3.5V8a.5.5 0 0 1 1 0v2.5a4.5 4.5 0 0 1-4.5 4.5h-5A4.5 4.5 0 0 1 1 10.5v-5A4.5 4.5 0 0 1 5.5 1H8a.5.5 0 0 1 0 1H5.5z"/>
|
||||||
|
<path d="M16 3a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 387 B |
3
collector/static/bootstrap/icons/app.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app" viewBox="0 0 16 16">
|
||||||
|
<path d="M11 2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h6zM5 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4H5z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 282 B |