diff --git a/compose/local/django/Dockerfile b/compose/local/django/Dockerfile
index 7437a999..60314eb9 100644
--- a/compose/local/django/Dockerfile
+++ b/compose/local/django/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.12-slim-bookworm as common-base
+FROM python:3.12.2-slim-bookworm as common-base
ENV DJANGO_SETTINGS_MODULE config.settings.local
ENV PYTHONUNBUFFERED 1
diff --git a/orochi/api/models.py b/orochi/api/models.py
index 02becbcc..433206a8 100644
--- a/orochi/api/models.py
+++ b/orochi/api/models.py
@@ -51,6 +51,9 @@ class SuccessResponse(Schema):
message: str
+###################################################
+# Utils
+###################################################
class DaskStatusOut(Schema):
running: int = 0
diff --git a/orochi/api/routers/utils.py b/orochi/api/routers/utils.py
index 2a002075..ac4a892b 100644
--- a/orochi/api/routers/utils.py
+++ b/orochi/api/routers/utils.py
@@ -1,17 +1,62 @@
+import json
+from pathlib import Path
+from typing import Any
+
+import geoip2.database
from dask.distributed import Client
from django.conf import settings
+from geoip2.errors import GeoIP2Error
from ninja import Router
from ninja.security import django_auth
-from orochi.api.models import DaskStatusOut
+from orochi.api.models import DaskStatusOut, ErrorsOut
router = Router()
+@router.get("changelog", auth=django_auth, response={200: Any, 400: ErrorsOut})
+def changelog(request):
+ """
+ Summary:
+ Endpoint to retrieve the changelog content.
+
+ Explanation:
+ Retrieves the content of the CHANGELOG.md file and returns it as a response. If an exception occurs during the process, it returns an error response.
+
+ Args:
+ - request: The request object.
+
+ Returns:
+ - Tuple[int, Dict[str, str]]: A tuple containing the status code and a dictionary with the changelog content.
+
+ Raises:
+ - ErrorsOut: If an exception occurs during the file reading process.
+ """
+ try:
+ changelog_path = Path("/app/CHANGELOG.md")
+ with open(changelog_path, "r") as f:
+ changelog_content = "".join(f.readlines())
+ return 200, {"note": changelog_content}
+ except Exception as excp:
+ return 400, ErrorsOut(errors=str(excp))
+
+
@router.get(
- "/dask_status", auth=django_auth, response=DaskStatusOut, url_name="dask_status"
+ "/dask_status",
+ auth=django_auth,
+ response=DaskStatusOut,
+ url_name="dask_status",
)
def dask_status(request):
+ """
+ Get the total number of running tasks on the Dask scheduler.
+
+ Args:
+ request: The request object.
+
+ Returns:
+ int: The total number of running tasks on the Dask scheduler.
+ """
dask_client = Client(settings.DASK_SCHEDULER_URL)
res = dask_client.run_on_scheduler(
lambda dask_scheduler: {
@@ -21,3 +66,52 @@ def dask_status(request):
)
dask_client.close()
return sum(len(running_tasks) for running_tasks in res.values())
+
+
+@router.get(
+ "/maxmind",
+ auth=django_auth,
+ url_name="maxmind",
+ response={200: Any, 400: ErrorsOut},
+)
+def maxmind(request, ip: str):
+ """
+ Retrieve geolocation data for the given IP address using MaxMind databases.
+
+ Args:
+ request: The request object.
+ ip (str): The IP address for which geolocation data is to be retrieved.
+
+ Returns:
+ tuple: A tuple containing the HTTP status code and the geolocation data as a dictionary.
+ The status code 200 indicates success, while 400 indicates an error.
+ """
+ if (
+ not Path("/maxmind/GeoLite2-ASN.mmdb").exists()
+ and not Path("/maxmind/GeoLite2-City.mmdb").exists()
+ and not Path("/maxmind/GeoLite2-Country.mmdb").exists()
+ ):
+ return 400, ErrorsOut(errors="Maxmind databases not found.")
+
+ try:
+ data = {}
+ if Path("/maxmind/GeoLite2-ASN.mmdb").exists():
+ with geoip2.database.Reader("/maxmind/GeoLite2-ASN.mmdb") as reader:
+ data |= reader.asn(ip).raw
+ if Path("/maxmind/GeoLite2-City.mmdb").exists():
+ with geoip2.database.Reader("/maxmind/GeoLite2-City.mmdb") as reader:
+ data |= reader.city(ip).raw
+ if Path("/maxmind/GeoLite2-Country.mmdb").exists():
+ with geoip2.database.Reader("/maxmind/GeoLite2-Country.mmdb") as reader:
+ data |= reader.country(ip).raw
+ return 200, data
+ except (GeoIP2Error, Exception) as excp:
+ return 400, ErrorsOut(errors=str(excp))
+
+
+@router.get("/vt", url_name="vt", response={200: Any, 400: ErrorsOut}, auth=django_auth)
+def get_extracted_dump_vt_report(request, path: str):
+ path = Path(path)
+ if path.exists():
+ return 200, json.loads(open(path, "r").read())
+ return 400, ErrorsOut(errors="File not found.")
diff --git a/orochi/static/js/handlebars/maxmind.js b/orochi/static/js/handlebars/maxmind.js
new file mode 100644
index 00000000..d238cf4b
--- /dev/null
+++ b/orochi/static/js/handlebars/maxmind.js
@@ -0,0 +1,37 @@
+(function() {
+ var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
+templates['maxmind'] = template({"1":function(container,depth0,helpers,partials,data) {
+ var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
+ if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
+ return parent[propertyName];
+ }
+ return undefined
+ };
+
+ return "
\n "
+ + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"errors") || (depth0 != null ? lookupProperty(depth0,"errors") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"errors","hash":{},"data":data,"loc":{"start":{"line":8,"column":4},"end":{"line":8,"column":14}}}) : helper)))
+ + "\n
\n";
+},"3":function(container,depth0,helpers,partials,data) {
+ var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
+ if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
+ return parent[propertyName];
+ }
+ return undefined
+ };
+
+ return " \n \n";
+},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
+ var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
+ if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
+ return parent[propertyName];
+ }
+ return undefined
+ };
+
+ return "\n\n"
+ + ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"errors") : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data,"loc":{"start":{"line":6,"column":4},"end":{"line":24,"column":11}}})) != null ? stack1 : "")
+ + "
\n \n\n";
+},"useData":true});
+})();
diff --git a/orochi/static/js/handlebars/vt.js b/orochi/static/js/handlebars/vt.js
new file mode 100644
index 00000000..f5cea5a1
--- /dev/null
+++ b/orochi/static/js/handlebars/vt.js
@@ -0,0 +1,37 @@
+(function() {
+ var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
+templates['vt'] = template({"1":function(container,depth0,helpers,partials,data) {
+ var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
+ if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
+ return parent[propertyName];
+ }
+ return undefined
+ };
+
+ return " \n "
+ + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"errors") || (depth0 != null ? lookupProperty(depth0,"errors") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"errors","hash":{},"data":data,"loc":{"start":{"line":8,"column":4},"end":{"line":8,"column":14}}}) : helper)))
+ + "\n
\n";
+},"3":function(container,depth0,helpers,partials,data) {
+ var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
+ if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
+ return parent[propertyName];
+ }
+ return undefined
+ };
+
+ return " \n \n";
+},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
+ var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
+ if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
+ return parent[propertyName];
+ }
+ return undefined
+ };
+
+ return "\n\n"
+ + ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"errors") : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data,"loc":{"start":{"line":6,"column":4},"end":{"line":24,"column":11}}})) != null ? stack1 : "")
+ + "
\n \n\n";
+},"useData":true});
+})();
diff --git a/orochi/templates/base.html b/orochi/templates/base.html
index 1185639c..b670c7c6 100644
--- a/orochi/templates/base.html
+++ b/orochi/templates/base.html
@@ -20,6 +20,7 @@
+
{% endblock %}
@@ -160,6 +161,8 @@
+
+
+
+ {{/if}}
+
+
+
diff --git a/orochi/templates/handlebars/vt.handlebars b/orochi/templates/handlebars/vt.handlebars
new file mode 100644
index 00000000..94f99011
--- /dev/null
+++ b/orochi/templates/handlebars/vt.handlebars
@@ -0,0 +1,29 @@
+
+
+ {{#if errors}}
+
+ {{errors}}
+
+ {{else}}
+
+
+ {{/if}}
+
+
+
diff --git a/orochi/templates/users/user_bookmarks.html b/orochi/templates/users/user_bookmarks.html
index b27ce47d..2d156827 100644
--- a/orochi/templates/users/user_bookmarks.html
+++ b/orochi/templates/users/user_bookmarks.html
@@ -91,6 +91,7 @@
type: 'get',
dataType: 'json',
beforeSend: function () {
+ $("#modal-update .modal-content").html('');
$("#modal-update").modal("show");
row = table.row($(btn).closest('tr')).index();
},
@@ -210,4 +211,4 @@
});
-{% endblock javascript %}
\ No newline at end of file
+{% endblock javascript %}
diff --git a/orochi/templates/users/user_rules.html b/orochi/templates/users/user_rules.html
index 4fa41b51..a0cdb6d7 100644
--- a/orochi/templates/users/user_rules.html
+++ b/orochi/templates/users/user_rules.html
@@ -260,6 +260,7 @@
dataType: 'json',
beforeSend: function () {
$("#modal-update .modal-dialog").removeClass('modal-xl');
+ $("#modal-update .modal-content").html('');
$("#modal-update").modal("show");
},
success: function (data) {
@@ -310,6 +311,7 @@
dataType: 'json',
success: function (data) {
$("#modal-update .modal-dialog").addClass('modal-xl');
+ $("#modal-update .modal-content").html('');
$("#modal-update").modal("show");
$("#modal-update .modal-content").html(data.html_form);
},
@@ -386,4 +388,4 @@
});
-{% endblock javascript %}
\ No newline at end of file
+{% endblock javascript %}
diff --git a/orochi/templates/website/file_download.html b/orochi/templates/website/file_download.html
index 9d7292eb..4b3b8750 100644
--- a/orochi/templates/website/file_download.html
+++ b/orochi/templates/website/file_download.html
@@ -15,7 +15,7 @@
{% endif %}
{% if vt %}
-
+
{% endif %}
diff --git a/orochi/templates/website/index.html b/orochi/templates/website/index.html
index 2757a036..ba3ed74c 100644
--- a/orochi/templates/website/index.html
+++ b/orochi/templates/website/index.html
@@ -72,6 +72,12 @@ History Log