#wip: Python division subdirs and files; and fixed Bash agent. Needs implements Dispatchers and GUI only.

This commit is contained in:
KyMAN 2026-03-22 20:09:59 +01:00
parent 4f9e9acfe9
commit 2f4a5d4609
41 changed files with 1960 additions and 872 deletions

2
.gitignore vendored
View File

@ -3,7 +3,7 @@
__pycache__
*.[Ss]ecrets.*
*.[Ss]ecret.*
/Python/pyodbc.py
/Python/Assets
/SQLServer/data
/SQLServer/temporary
/SQLServer/scripts

View File

@ -1,2 +1,2 @@
#!/bin/bash
nohup ./tu_script.sh > /dev/null 2>&1 &
nohup ./NucelarMonitor.debian.script.sh > /dev/null 2>&1 &

View File

@ -7,7 +7,7 @@ candle_sleep_seconds=1
execute_sleep_seconds=0
show_json=true
send_json=true
url_server="http://192.168.1.131:13000/debian"
url_server="http://192.168.1.131:13000/agents/debian"
# Settings.
function get_net_data(){
@ -89,7 +89,7 @@ function execute(){
# cpu_average=$((cpu_average + cpu))
# memory_average=$((memory_average + memory))
cpu_average=$(echo "$cpu_average + $cpu"|bc -l)
cpu_average=$(echo "$cpu_average + ${cpu/,/.}"|bc -l)
memory_average=$(echo "$memory_average + $memory"|bc -l)
cpu=${cpu/,/.}

View File

@ -1 +1,54 @@
{}
{
"autostart" : true,
"print_format" : "[{type}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{line}]{file}({method}): {message}",
"exception_format" : " '[{line}]{file}({method})'{lines}\n\n{exception_message}",
"print_types" : [
["unkn", "unknown"],
["info", "information"],
["warn", "warning"],
["erro", "error", "wrong", "failure", "fail", "no"],
["exce", "exception", "except"],
[" ok ", "ok", "success", "succeed", "yes"],
["test", "debug"]
],
"web_servers" : {
"main" : {
"type" : "web_server",
"host" : "0.0.0.0",
"port" : 13000,
"cache_size" : 1024,
"maximum_connections" : 5,
"header_response" : [
"{protocol}/{version} {code} {message}",
"Content-Type: {mime}",
"Content-Length: {length}",
"Connection: close"
],
"protocol" : "HTTP",
"version" : "1.1",
"code" : 200,
"message" : "OK",
"encoder" : "utf-8",
"index_files" : ["index.html", "index.htm"]
}
},
"databases" : {
"sql_server" : {
"type" : "sql_server",
"driver" : "{ODBC Driver 17 for SQL Server}",
"host" : "127.0.0.1",
"port" : 1433,
"user" : "sa",
"password" : "password",
"database" : "NucelarMonitor"
}
},
"default_controllers" : {
"agents" : "agents"
},
"default_routes" : [
"post:/agents/debian/{key} debian@agents",
"get:/agents/test/{key} test@agents",
"get:/ /Public"
]
}

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, Callable
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Models.RequestModel import RequestModel
from Models.ResponseModel import ResponseModel
class ControllerAbstract:
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
def get_action(self:Self, action:str) -> Callable[[RequestModel, ResponseModel], None]|None:
method:Any|None = getattr(self, action, None)
return method if method is not None and callable(method) else None

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, Optional, Sequence
from abc import ABC, abstractmethod
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Models.QueryResponseModel import QueryResponseModel
class DatabaseAbstract(ABC):
def __init__(self:Self,
nucelar_monitor:NucelarMonitorInterface,
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
@abstractmethod
def close(self:Self) -> bool:pass
@abstractmethod
def query(self:Self,
query:str,
parameters:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> QueryResponseModel:pass

View File

@ -0,0 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
class DispatcherAbstract:
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, Optional, Sequence
from abc import ABC, abstractmethod
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
class WebServerAbstract(ABC):
def __init__(self:Self,
nucelar_monitor:NucelarMonitorInterface,
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
index_files:Sequence[str]|str = self.nucelar_monitor.settings.get("index_files", inputs, ["index.html", "index.htm"])
header_response:str|Sequence[str] = self.nucelar_monitor.settings.get("header_response", inputs, "HTTP/{version} {code} {message}\r\n")
self._host:str = self.nucelar_monitor.settings.get("host", inputs, "::1")
self._port:int = self.nucelar_monitor.settings.get("port", inputs, 13000)
self._header_response:str = header_response if isinstance(header_response, str) else "\r\n".join(header_response) + "\r\n\r\n"
self._protocol:str = self.nucelar_monitor.settings.get("protocol", inputs, "HTTP")
self._version:str = self.nucelar_monitor.settings.get("version", inputs, "1.1")
self._code:int = self.nucelar_monitor.settings.get("code", inputs, 200)
self._message:str = self.nucelar_monitor.settings.get("message", inputs, "OK")
self._encoder:str = self.nucelar_monitor.settings.get("encoder", inputs, "utf-8")
self._index_files:list[str] = [""] + (index_files if isinstance(index_files, list) else [index_files])
@abstractmethod
def start(self:Self) -> bool:pass
@abstractmethod
def stop(self:Self) -> bool:pass
@abstractmethod
def close(self:Self) -> bool:pass

View File

@ -0,0 +1,145 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import datetime
from typing import Any, Optional, Sequence, Self
from traceback import extract_tb as extract_traceback, format_stack as trace_format_stack
from re import Match as REMatch
from Utils.Patterns import RE
from Utils.Utils import Utils
from Drivers.FilesDrivers import FilesDriver
from Managers.I18NManager import I18NManager
from Managers.SettingsManager import SettingsManager
from Managers.ModelsManager import ModelsManager
from Managers.TerminalManager import TerminalManager
from Managers.RoutesManager import RoutesManager
from Managers.ControllersManager import ControllersManager
from Managers.DatabasesManager import DatabasesManager
from Managers.WebServersManager import WebServersManager
class NucelarMonitor:
def __init__(self:Self, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
self.__print_format:str = "[{type}] {yyyy}-{mm}-{dd} {hh}:{ii}:{ss} - {message}"
self.__print_types:list[list[str]] = [
["unkn", "unknown"],
["info", "information"],
["warn", "warning"],
["erro", "error", "wrong", "failure", "fail", "no"],
["exce", "exception", "except"],
[" ok ", "ok", "success", "succeed", "yes"],
["test", "debug"]
]
self.__exception_format:str = " '[{line}]{file}({method})'{lines}\n\n{exception_message}"
self.files:FilesDriver = FilesDriver(self)
self.i18n:I18NManager = I18NManager(self)
self.settings:SettingsManager = SettingsManager(self, inputs)
self.i18n.set_defaults()
self.__print_format:str = self.settings.get("print_format", None, self.__print_format)
self.__print_types:list[list[str]] = self.settings.get("print_types", None, self.__print_types)
self.__exception_format:str = self.settings.get("exception_format", None, self.__exception_format)
self.__started:bool = False
self.__working:bool = True
self.models:ModelsManager = ModelsManager(self)
self.terminal:TerminalManager = TerminalManager(self)
self.controllers:ControllersManager = ControllersManager(self)
self.routes:RoutesManager = RoutesManager(self)
self.databases:DatabasesManager = DatabasesManager(self)
self.web_servers:WebServersManager = WebServersManager(self)
if self.settings.get("autostart"):
self.start()
else:
self.terminal.start()
def start(self:Self) -> None:
if self.__started:
return
self.__started = True
self.__working = True
self.terminal.start()
self.web_servers.start()
def close(self:Self) -> None:
if not self.__started:
return
self.__started = False
self.__working = False
self.terminal.close()
self.web_servers.close()
self.databases.close()
def is_working(self:Self) -> bool:
return self.__working
def get_print_type(self:Self, _type:str) -> str:
group:list[str]
for group in self.__print_types:
if _type in group:
return group[0].upper()
return self.__print_types[0][0].upper()
def print(self:Self,
_type:str,
message:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
i:int = 0
) -> None:
date:datetime = datetime.datetime.now()
own:dict[str, Any|None] = {
"raw_type" : _type,
"type" : self.get_print_type(_type),
"i18n" : Utils.get_texts(message),
"message" : (
self.i18n.get(message, inputs) if hasattr(self, "i18n") else
Utils.string_variables(Utils.get_texts(message)[0], inputs)),
**Utils.get_dictionary(inputs),
**Utils.get_action_data(i + 1)
}
for key in ("year", "month", "day", "hour", "minute", "second"):
k:str = "i" if key == "minute" else key[0]
own[k] = own[key] = getattr(date, key)
own[k + k] = ("00" + str(own[key]))[-2:]
own["yyyy"] = own["year"]
print(Utils.string_variables(self.__print_format, own) + (own["end"] if "end" in own else ""))
def exception(self:Self,
exception:Exception,
message:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
i:int = 0
) -> None:
lines:list[str] = extract_traceback(exception.__traceback__).format()
matches:REMatch = RE.EXCEPTION.match(lines[-1])
data:dict[str, Any|None] = {
**Utils.get_dictionary(inputs),
"lines" : "",
"exception_message" : str(exception),
"method" : matches.group(3),
"line" : matches.group(2),
"file" : matches.group(1)
}
block:str
j:int
for j, block in enumerate(trace_format_stack()[:-2] + lines):
if block:
data["lines"] += "\n " + str(j) + " - " + RE.NEW_LINE.split(block.strip())[0]
data["end"] = Utils.string_variables(self.__exception_format, data)
message and self.print("exception", message, data, i + 2)

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self
from Abstracts.ControllerAbstract import ControllerAbstract
from Models.ResponseModel import ResponseModel
from Models.RequestModel import RequestModel
from Utils.Utils import Utils
class AgentsController(ControllerAbstract):
def debian(self:Self, request:RequestModel, response:ResponseModel) -> None:
key:str = request.get("key")
hostnames:list[str]
domain:str|None
interfaces:list[list[int, str, bool, str, int]]
disks:list[list[str, int, int, str|None]]
iterations:int
candle_times:list[int, int]
cpu:list[float, float, float, float]
memory:list[int, int, int, int, int, float]
net_use:list[list[list[str, int, int, int, int, int, int]]]
print(Utils.json_decode(request.body))
hostnames, domain, interfaces, disks, iterations, candle_times, cpu, memory, net_use = Utils.json_decode(request.body)
print([hostnames, domain, interfaces, disks, iterations, candle_times, cpu, memory, net_use])
def windows(self:Self, request:RequestModel, response:ResponseModel) -> None:
pass
def test(self:Self, request:RequestModel, response:ResponseModel) -> None:
response.set_data({
"message": "Test successful",
"key" : request.get("key")
}, "application/json")

View File

@ -0,0 +1,85 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any
from os.path import exists as path_exists, dirname as directory_name, abspath as absolute_path, isfile as is_file
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Utils.Utils import Utils
from Utils.Patterns import RE
class FilesDriver:
ROOT:str = directory_name(absolute_path(__file__))
SLASH:str = "/" if "/" in ROOT else "\\\\"
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
self.__root_paths:list[str] = ["", self.ROOT]
for _ in range(2):
self.__root_paths.append(RE.LAST_DIRECTORY.sub(r'\1', self.__root_paths[-1]))
@classmethod
def fix_path(cls:type[Self], path:str) -> str:
return RE.SLASHES.sub(cls.SLASH, path)
def get_absolute_path(self:Self, path:str) -> str|None:
root:str
absolute:str
for root in self.__root_paths:
absolute = self.fix_path((root + '/' if root else "") + path)
if path_exists(absolute):
return absolute
return None
def load_file(self:Self, path:str, mode:str = "r") -> str|bytes|None:
absolute_path:str = self.get_absolute_path(path)
if absolute_path and is_file(absolute_path):
with open(absolute_path, mode) as file:
return file.read()
return None
def load_json(self:Self, data:str|dict[str, Any|None]|list[Any|None], only_dictionaries:bool = True) -> list[dict[str, Any|None]|list[Any|None]]:
results:list[dict[str, Any|None]|list[Any|None]] = []
if isinstance(data, str):
json:list[Any|None]|dict[str, Any|None]|None
try:
json = Utils.json_decode(data)
except Exception as exception:
self.nucelar_monitor.exception(exception, "load_json_exception", {
"data" : data,
"length" : len(data)
})
if json:
results.extend(self.load_json(json, only_dictionaries))
try:
results.extend(self.load_json(Utils.json_decode(self.load_file(data)), only_dictionaries))
except Exception as exception:
self.nucelar_monitor.exception(exception, "load_json_by_file_exception", {
"path" : data
})
elif isinstance(data, dict):
results.append(data)
elif isinstance(data, (list, tuple)):
if only_dictionaries:
item:Any|None
for item in data:
results.extend(self.load_json(item, only_dictionaries))
else:
results.extend(data)
return results

View File

@ -0,0 +1,121 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, Optional, Sequence
from re import Match as REMatch
from Assets.pyodbc import Connection as Connection, connect as connect, Cursor as Cursor
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Abstracts.DatabaseAbstract import DatabaseAbstract
from Models.QueryResponseModel import QueryResponseModel
from Utils.Utils import Utils
from Utils.Patterns import RE
class SQLServerDriver(DatabaseAbstract):
def __init__(self:Self,
nucelar_monitor:NucelarMonitorInterface,
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> None:
super().__init__(nucelar_monitor, inputs)
self.__connection:Connection|None = None
self.__string_connection:str = Utils.string_variables(self.nucelar_monitor.settings.get((
"odbc_string_connection", "sql_string_connection", "string_connection"
), inputs, "DRIVER={driver};SERVER={host},{port};UID={user};PWD={password};DATABASE={database}"), {
"driver" : self.nucelar_monitor.settings.get("sql_driver", inputs, "{ODBC Driver 17 for SQL Server}"),
"host" : self.nucelar_monitor.settings.get(("sql_host", "odbc_host"), inputs, "localhost"),
"port" : self.nucelar_monitor.settings.get(("sql_port", "odbc_port"), inputs, 1433),
"user" : self.nucelar_monitor.settings.get(("sql_user", "odbc_user"), inputs, "sa"),
"password" : self.nucelar_monitor.settings.get(("sql_password", "odbc_password"), inputs, "password"),
"database" : self.nucelar_monitor.settings.get(("sql_database", "odbc_database"), inputs, "NucelarMonitor")
})
def close(self:Self) -> bool:
if self.__connection is not None:
try:
self.__connection.close()
return True
except Exception as exception:
self.nucelar_monitor.exception(exception, "sql_server_close_exception", {
"string_connection" : self.__string_connection
})
return False
return True
def __autoclose(self:Self) -> None:
pass
def format_query(self:Self,
query:str,
parameters:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> tuple[str, list[str]]:
variables:list[str] = []
def callback(matches:REMatch) -> str:
key:str = matches.group(1)
if key:
return (
"'" + str(parameters[key]).replace("'","''") + "'" if isinstance(parameters[key], str) else
str(parameters[key]) if key in parameters else matches.group(0))
key = matches.group(2)
variables.append(key)
return matches.group(0)
query = RE.ODBC_STRING_VARIABLE.sub(callback, query)
return (
"".join("declare @" + variable + " varchar(max)\n" for variable in variables) +
query +
"\nselect " + ", ".join("@" + variable for variable in variables)
) if len(variables) else query, variables
def query(self:Self,
query:str,
parameters:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> QueryResponseModel:
response:QueryResponseModel = QueryResponseModel()
cursor:Cursor|None = None
variables:list[str] = []
try:
if self.__connection is None:
self.__connection = connect(
self.__string_connection,
autocommit = True
)
cursor = self.__connection.cursor()
query, variables = self.format_query(query, parameters)
cursor.execute(query)
while True:
if cursor.description is not None:
response.columns.append([column[0] for column in cursor.description])
response.tables.append([tuple(row) for row in cursor.fetchall()])
if not cursor.nextset():
break
except Exception as exception:
self.nucelar_monitor.exception(exception, "connection_exception", {
"string_connection" : self.__string_connection
})
finally:
if cursor is not None:
cursor.close()
if len(variables) and len(response.tables):
for i, variable in enumerate(variables):
response.variables[variable] = response.tables[-1][0][i]
response.tables = response.tables[:-1]
response.columns = response.columns[:-1]
return response

View File

@ -0,0 +1,165 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Self, Optional, Sequence
from threading import Thread
from socket import socket as Socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR, SHUT_RDWR
from requests import get as get
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Abstracts.WebServerAbstract import WebServerAbstract
from Models.ResponseModel import ResponseModel
from Models.RequestModel import RequestModel
from Utils.Utils import Utils
class WebServerDriver(WebServerAbstract):
def __init__(self:Self,
nucelar_monitor:NucelarMonitorInterface,
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> None:
super().__init__(nucelar_monitor, inputs)
self.__server:Socket
self.__buffer_size:int = self.nucelar_monitor.settings.get("cache_size", inputs, 4096)
self.__started:bool = False
self.__thread:Thread|None = None
self.__clients:list[Socket] = []
def start(self:Self) -> bool:
if self.__started:
return False
self.__started = True
self.__server = Socket(AF_INET, SOCK_STREAM)
self.__server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
try:
self.__server.bind((self._host, self._port))
self.__server.listen()
self.__thread = Thread(target = self.__listen)
self.__thread.start()
return True
except Exception as exception:
self.nucelar_monitor.exception(exception, "nucelar_monitor_web_socket_driver_start_exception", {
"host" : self._host,
"port" : self._port,
})
self.close()
return False
def close(self:Self) -> bool:
if not self.__started:
return False
self.__started = False
i:int
for i in range(len(self.__clients)):
self.__close_client(i, True)
return True
def stop(self:Self) -> bool:
return self.close()
def __close_client(self:Self, i:int, send_close:bool = False) -> None:
if self.__clients[i] is None:
return
client:Socket = self.__clients[i]
self.__clients[i] = None
try:
if client:
client.shutdown(SHUT_RDWR)
client.close()
except Exception as _:
pass
def __listen_client(self:Self, client:Socket, address:str, port:int) -> None:
data:bytes = b""
route:str = ""
method:str = "UNKN"
response:ResponseModel = ResponseModel(self._encoder, {}, self._code, self._message)
i:int = 0
while i < len(self.__clients):
if self.__clients[i] is None:
break
i += 1
if i == len(self.__clients):
self.__clients.append(client)
else:
self.__clients[i] = client
try:
request:RequestModel
while True:
buffer:bytes = client.recv(self.__buffer_size)
if not buffer:
break
data += buffer
if len(buffer) != self.__buffer_size:
break
if data:
request = RequestModel(data, self._index_files, self._encoder)
self.nucelar_monitor.routes.get(request, response)
client.sendall(Utils.string_variables(self._header_response, {
"protocol" : self._protocol,
"version" : self._version,
**response.get_parameters()
}).encode(self._encoder) + response.body)
except Exception as exception:
self.nucelar_monitor.exception(exception, "server_client_exception", {
"host" : self._host,
"port" : self._port,
"client_address" : address,
"client_port" : port,
"length" : len(data),
"method" : method,
"route" : route,
"response_length" : len(response.body)
})
finally:
self.__close_client(i)
def __listen(self:Self) -> None:
while self.nucelar_monitor.is_working():
try:
client:Socket
address:str
port:int
client, (address, port) = self.__server.accept()
Thread(
target = self.__listen_client,
args = (client, address, port)
).start()
except Exception as exception:
self.nucelar_monitor.exception(exception, "server_listen_exception", {
"host" : self._host,
"port" : self._port,
})
try:
self.__server.close()
except Exception as exception:
self.nucelar_monitor.exception(exception, "nucelar_monitor_web_socket_driver_close_exception", {
"host" : self._host,
"port" : self._port,
})

View File

@ -0,0 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Callable
from abc import ABC, abstractmethod
from Models.RequestModel import RequestModel
from Models.ResponseModel import ResponseModel
class ControllerAbstractInterface(ABC):
@abstractmethod
def get_action(self:Self, action:str) -> Callable[[RequestModel, ResponseModel], None]|None:pass

View File

@ -0,0 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from abc import ABC
class WebSocketAbstractInterface(ABC):pass

View File

@ -0,0 +1,53 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from abc import ABC, abstractmethod
from typing import Self, Any, Optional, Sequence
from Interfaces.Drivers.FilesDriversInterface import FilesDriverInterface
from Interfaces.Managers.I18NManagersInterface import I18NManagerInterface
from Interfaces.Managers.SettingsManagersInterface import SettingsManagerInterface
from Interfaces.Managers.TerminalManagerInterface import TerminalManagerInterface
from Interfaces.Managers.ModelsManagerInterface import ModelsManagerInterface
from Interfaces.Managers.ControllersManagerInterface import ControllersManagerInterface
from Interfaces.Managers.RoutesManagerInterface import RoutesManagerInterface
from Interfaces.Abstracts.WebSocketAbstractInterface import WebSocketAbstractInterface
class NucelarMonitorInterface(ABC):
def __init__(self:Self, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
self.files:FilesDriverInterface = None
self.i18n:I18NManagerInterface = None
self.settings:SettingsManagerInterface = None
self.terminal:TerminalManagerInterface = None
self.models:ModelsManagerInterface = None
self.controllers:ControllersManagerInterface = None
self.routes:RoutesManagerInterface = None
self.web_socket:WebSocketAbstractInterface = None
@abstractmethod
def start(self:Self) -> bool:pass
@abstractmethod
def close(self:Self) -> bool:pass
@abstractmethod
def get_print_type(self:Self, _type:str) -> str:pass
@abstractmethod
def print(self:Self,
_type:str,
message:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
i:int = 0
) -> None:pass
@abstractmethod
def exception(self:Self,
exception:Exception,
message:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
i:int = 0
) -> None:pass
@abstractmethod
def is_working(self:Self) -> bool:pass

View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any
from abc import ABC, abstractmethod
class FilesDriverInterface(ABC):
ROOT:str = None
SLASH:str = None
@abstractmethod
def get_absolute_path(self:Self, path:str) -> str|None:pass
@abstractmethod
def load_file(self:Self, path:str, mode:str = "r") -> str|bytes|None:pass
@abstractmethod
def load_json(self:Self,
data:str|dict[str, Any|None]|list[Any|None],
only_dictionaries:bool = True
) -> list[dict[str, Any|None]|list[Any|None]]:pass

View File

@ -0,0 +1,14 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Self
from abc import ABC, abstractmethod
from Interfaces.Abstracts.ControllerAbstractInterface import ControllerAbstractInterface
class ControllersManagerInterface(ABC):
@abstractmethod
def get(self:Self, key:str) -> ControllerAbstractInterface|None:pass
@abstractmethod
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from abc import ABC, abstractmethod
from typing import Any, Self, Optional, Sequence
class I18NManagerInterface(ABC):
DEFAULT_SENTENCES:dict[str, dict[str, str]] = None
@abstractmethod
def get(self:Self,
keys:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
default:Any = None
) -> dict[str, Any|None]:pass
@abstractmethod
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, TypeVar
from abc import ABC, abstractmethod
T = TypeVar('T')
class ModelsManagerInterface(ABC):
@abstractmethod
def get(self:Self, Type:type[T], model:str|type[T]) -> type[T]|None:pass
@abstractmethod
def add(self:Self,
Type:type[T],
inputs:dict[str, Any]|list[Any|None]|tuple[Any|None, ...]|str,
overwrite:bool = False
) -> None:pass

View File

@ -0,0 +1,15 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any
from abc import ABC, abstractmethod
from Models.RequestModel import RequestModel
from Models.ResponseModel import ResponseModel
class RoutesManagerInterface(ABC):
@abstractmethod
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass
@abstractmethod
def get(self:Self, request:RequestModel, response:ResponseModel) -> None:pass

View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from abc import ABC, abstractmethod
from typing import Any, Self, Optional, Sequence
class SettingsManagerInterface(ABC):
DEFAULT_SETTINGS:dict[str, Any|None] = None
@abstractmethod
def get(self:Self,
strings:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
language:Optional[str] = None
) -> str:pass
@abstractmethod
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass
@abstractmethod
def add_secrets(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass

View File

@ -0,0 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any
from abc import ABC, abstractmethod
class TerminalManagerInterface(ABC):
@abstractmethod
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass

View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Self
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Abstracts.ControllerAbstract import ControllerAbstract
class ControllersManager:
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
key:str
self.__controllers:dict[str, ControllerAbstract] = {}
for key in (
"default_controllers_files", "controllers_files",
"default_controllers", "controllers"
):
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
def get(self:Self, key:str) -> ControllerAbstract|None:
return self.__controllers.get(key, None)
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
subinputs:dict[str, str|ControllerAbstract]
for subinputs in self.nucelar_monitor.files.load_json(inputs):
key:str
controller:ControllerAbstract|str
for key, controller in subinputs.items():
if isinstance(controller, str):
controller = self.nucelar_monitor.models.get(ControllerAbstract, controller)(self.nucelar_monitor)
if controller is not None and isinstance(controller, ControllerAbstract) and (
overwrite or key not in self.__controllers
):
self.__controllers[key] = controller

View File

@ -0,0 +1,53 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Abstracts.DatabaseAbstract import DatabaseAbstract
class DatabasesManager:
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
key:str
self.__databases:dict[str, DatabaseAbstract] = {}
for key in (
"default_databases_files", "databases_files",
"default_databases", "databases"
):
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
def get(self:Self, key:str) -> type[DatabaseAbstract]|None:
return self.__databases.get(key, None)
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
subinputs:dict[str, type[DatabaseAbstract]|dict[str, Any|None]]
for subinputs in self.nucelar_monitor.files.load_json(inputs):
key:str
database:DatabaseAbstract|dict[str, Any|None]
for key, database in subinputs.items():
if isinstance(database, dict):
database = self.nucelar_monitor.models.get(
DatabaseAbstract,
self.nucelar_monitor.settings.get(("database_type", "type"), database, "sql_server")
)(self.nucelar_monitor, database)
if database is None:
continue
if database is not None and isinstance(database, DatabaseAbstract) and (
overwrite or key not in self.__databases
):
self.__databases[key] = database
def close(self:Self) -> None:
database:type[DatabaseAbstract]
for database in self.__databases.values():
database.close()

View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Self
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Abstracts.DispatcherAbstract import DispatcherAbstract
class DispatchersManager:
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
key:str
self.__dispatcher:dict[str, DispatcherAbstract] = {}
for key in (
"default_dispatcher_files", "dispatcher_files",
"default_dispatcher", "dispatcher"
):
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
def get(self:Self, key:str) -> DispatcherAbstract|None:
return self.__dispatcher.get(key, None)
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
subinputs:dict[str, str|DispatcherAbstract]
for subinputs in self.nucelar_monitor.files.load_json(inputs):
key:str
controller:DispatcherAbstract|str
for key, controller in subinputs.items():
if isinstance(controller, str):
controller = self.nucelar_monitor.models.get(DispatcherAbstract, controller)(self.nucelar_monitor)
if controller is not None and isinstance(controller, DispatcherAbstract) and (
overwrite or key not in self.__dispatcher
):
self.__dispatcher[key] = controller

View File

@ -0,0 +1,87 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, Optional, Sequence
from Utils.Utils import Utils
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
class I18NManager:
DEFAULT_SENTENCES:dict[str, dict[str, str]] = {
"english" : {}
}
def set_defaults(self:Self) -> None:
key:str
self.__language = self.nucelar_monitor.settings.get("language", None, "english")
for key in (
"default_i18n_files", "i18n_files",
"default_i18n", "i18n"
):
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
self.__sentences:dict[str, dict[str, str]] = self.DEFAULT_SENTENCES
self.__language:str = "english"
def __get(self:Self,
strings:str|Sequence[str],
language:Optional[str] = None
) -> str|Sequence[str]:
keys:str = Utils.get_keys(strings)
if len(keys):
language:str
done:list[str] = []
for language in [language, self.__language] + list(self.__sentences.keys()):
if language not in done and language in self.__sentences:
key:str
done.append(language)
for key in keys:
if key in self.__sentences[language]:
return self.__sentences[language][key]
return Utils.get_texts(strings)[0]
def get(self:Self,
strings:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
language:Optional[str] = None
) -> str:
string:str|Sequence[str] = self.__get(strings, language)
return Utils.string_variables((
"".join(string) if isinstance(string, Sequence) else
string), inputs)
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
block:dict[str, dict[str, str|Sequence[str]]]
for block in self.nucelar_monitor.files.load_json(inputs):
language:str
sentences:dict[str, str|Sequence[str]]
for language, sentences in block.items():
if isinstance(sentences, dict):
key:str
sentence:str|Sequence[str]
if language not in self.__sentences:
self.__sentences[language] = {}
for key, sentence in sentences.items():
if overwrite or key not in self.__sentences[language]:
self.__sentences[language][key] = sentence

View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, TypeVar
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
T = TypeVar('T')
class ModelsManager:
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
key:str
self.__models:dict[str, Any] = {}
for key in (
"default_models_files", "models_files",
"default_models", "models"
):
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
def get(self:Self, Type:type[T], key:str) -> type[T]|None:
return self.__models.get(key, None) if key in self.__models and issubclass(self.__models[key], Type) else None
def add(self:Self,
inputs:dict[str, Any]|list[Any|None]|tuple[Any|None, ...]|str,
overwrite:bool = False
) -> None:
subinputs:dict[str, Any]
for subinputs in self.nucelar_monitor.files.load_json(inputs):
key:str
Model:type[T]
for key, Model in subinputs.items():
if overwrite or key not in self.__models:
self.__models[key] = Model

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, Sequence
from re import Match as REMatch
from mimetypes import guess_type as get_mime_by_extension
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Models.RouteModel import RouteModel
from Models.ResponseModel import ResponseModel
from Models.RequestModel import RequestModel
class RoutesManager:
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
key:str
self.__routes:list[RouteModel] = []
for key in (
"default_routes_files", "routes_files",
"default_routes", "routes"
):
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
route:str|Sequence[str|list[str]|RouteModel]
for route in self.nucelar_monitor.files.load_json(inputs, False):
item:RouteModel = route if isinstance(route, RouteModel) else RouteModel(self.nucelar_monitor, route)
if item.error:
self.add(route, overwrite)
continue
exists:RouteModel
done:bool = False
for exists in self.__routes:
if exists.method == item.method and exists.route == item.route:
if overwrite:
done = True
self.__routes.remove(exists)
else:
break
if not done:
self.__routes.append(item)
def get(self:Self, request:RequestModel, response:ResponseModel) -> None:
route:RouteModel
for route in self.__routes:
if route.method != request.method:
continue
matches:REMatch[str]|None = route.route.match(request.request)
if not matches:
continue
request.set_uri_variables(route.variables, matches)
if route.action:
try:
route.action(request, response)
except Exception as exception:
response.build({
"ok" : False,
"code" : 500,
"message" : "Internal server error",
"error" : str(exception)
}, "application/json", 500, "Internal server error")
self.nucelar_monitor.exception(exception, "nucelar_monitor_routes_manager_get_exception", {
"request" : request.request,
"method" : request.method
})
return
if route.path:
index:str
for index in request.index_files:
full_path = route.path + request.request + ("" if index == "" else "/" + index)
if (response_data := self.nucelar_monitor.files.load_file(full_path, "rb")) is not None:
response.set_data(
response_data,
get_mime_by_extension(full_path)[0] or "application/octet-stream"
)
return
response.build({
"ok" : False,
"code" : 404,
"message" : "Not found"
}, "application/json", 404, "Not found")

View File

@ -0,0 +1,85 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, Optional, Sequence
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Utils.Utils import Utils
class SettingsManager:
DEFAULT_SETTINGS:dict[str, Any|None] = {
"autostart" : True,
"print_format" : "[{type}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{line}]{file}({method}): {message}",
"exception_format" : " '[{line}]{file}({method})'{lines}\n\n{exception_message}",
"print_types" : [
["unkn", "unknown"],
["info", "information"],
["warn", "warning"],
["erro", "error", "wrong", "failure", "fail", "no"],
["exce", "exception", "except"],
[" ok ", "ok", "success", "succeed", "yes"],
["test", "debug"]
],
"default_settings_files" : "/JSON/NucelarMonitor.settings.json",
}
def __init__(self:Self,
nucelar_monitor:NucelarMonitorInterface,
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
key:str
self.__inputs:dict[str, Any|None] = Utils.get_dictionary(inputs)
self.__secrets:dict[str, Any|None] = {}
self.__settings:dict[str, Any|None] = {}
for key in (
"default_settings_files", "settings_files",
"default_settings", "settings"
):
self.add(self.get(key, None, []), True)
for key in (
"default_secrets_files", "secrets_files",
"default_secrets", "secrets"
):
self.add_secrets(self.get(key, None, []), True)
def get(self:Self,
keys:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
default:Any = None
) -> dict[str, Any|None]:
return Utils.get_value(keys, (
inputs, self.__inputs, self.__secrets, self.__settings, self.DEFAULT_SETTINGS
), default)
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
subinputs:dict[str, Any|None]
print(inputs)
for subinputs in self.nucelar_monitor.files.load_json(inputs):
key:str
value:Any|None
for key, value in subinputs.items():
if overwrite or key not in self.__settings:
self.__settings[key] = value
def add_secrets(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
subinputs:dict[str, Any|None]
for subinputs in self.nucelar_monitor.files.load_json(inputs):
key:str
value:Any|None
for key, value in subinputs.items():
if overwrite or key not in self.__secrets:
self.__secrets[key] = value

View File

@ -0,0 +1,89 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, Callable
from threading import Thread
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Utils.Utils import Utils
class TerminalManager:
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
key:str
self.__commands:list[tuple[list[str], Callable[[dict[str, Any|None], list[Any|None]], None]]] = [
[["close", "exit", "quit", "bye"], self.__close_command]
]
self.__thread:Thread|None
self.__started:bool = False
for key in (
"default_commands_files", "commands_files",
"default_commands", "commands"
):
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
def start(self:Self) -> None:
if self.__started:
return
self.__started = True
self.__thread = Thread(target = self.__listener)
self.__thread.start()
def close(self:Self) -> None:
if not self.__started:
return
self.__started = False
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
alternatives:list[str]
callback:Callable[[dict[str, Any|None], list[Any|None]], None]|str
for alternatives, callback in self.nucelar_monitor.files.load_json(inputs, False):
done:bool = False
command:tuple[list[str], Callable[[dict[str, Any|None], list[Any|None]], None]]
i:int
if isinstance(callback, str):
callback = Utils.get_function(callback, [self.nucelar_monitor])
for i, command in enumerate(self.__commands):
key:str
for key in alternatives:
if key in command[0]:
if overwrite:
self.__commands[i] = (alternatives, callback)
done = True
break
if not done:
self.__commands.append((alternatives, callback))
def __listener(self:Self) -> None:
while self.nucelar_monitor.is_working():
try:
parameters:dict[str, Any|None] = {}
arguments:list[Any|None] = []
command:str = input().strip().lower()
if command:
for commands, callback in self.__commands:
if command in commands:
callback(parameters, *arguments)
break
except Exception as exception:
self.nucelar_monitor.exception(exception, "command_listener_exception", {
"command" : command
})
def __close_command(self:Self, inputs:dict[str, Any|None], *arguments:list[Any|None]) -> None:
self.nucelar_monitor.close()

View File

@ -0,0 +1,57 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Abstracts.WebServerAbstract import WebServerAbstract
class WebServersManager:
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
key:str
self.__web_servers:dict[str, WebServerAbstract] = {}
for key in (
"default_web_servers_files", "web_servers_files",
"default_web_servers", "web_servers"
):
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
subinputs:dict[str, WebServerAbstract|dict[str, Any|None]]
for subinputs in self.nucelar_monitor.files.load_json(inputs):
key:str
web_server:WebServerAbstract|dict[str, Any|None]
for key, web_server in subinputs.items():
if isinstance(web_server, dict):
web_server = self.nucelar_monitor.models.get(
WebServerAbstract,
self.nucelar_monitor.settings.get(("web_server_type", "type"), web_server, "web_server")
)(self.nucelar_monitor, web_server)
if web_server is not None and isinstance(web_server, WebServerAbstract) and (
overwrite or key not in self.__web_servers
):
self.__web_servers[key] = web_server
def start(self:Self) -> None:
web_server:WebServerAbstract
for web_server in self.__web_servers.values():
if hasattr(web_server, "start") and callable(getattr(web_server, "start")):
web_server.start()
def close(self:Self) -> None:
web_server:WebServerAbstract
for web_server in self.__web_servers.values():
if hasattr(web_server, "close") and callable(getattr(web_server, "close")):
web_server.close()

View File

@ -0,0 +1,11 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any
class QueryResponseModel:
def __init__(self:Self) -> None:
self.tables:list[list[list[Any|None]]] = []
self.columns:list[list[str]] = []
self.variables:dict[str, Any|None] = {}

View File

@ -0,0 +1,74 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, Optional, Sequence
from re import Match as REMatch
from Utils.Utils import Utils
from Utils.Patterns import RE
class RequestModel:
def __init__(self:Self, data:bytes, index_files:tuple[str, ...], encoder:str = "utf-8") -> None:
self.method:str
self.request:str
self.value_get:str|None
self.variables_get:dict[str, str]
self.variables_post:dict[str, str]
self.protocol:str
self.protocol_version:str
self.body:str
self.variables_uri:dict[str, str] = {}
self.index_files:tuple[str, ...] = index_files
header, body = (lambda header, body:(
RE.NEW_LINE.split(str(header).strip()), body
))(*RE.HTTP_BLOCKS.match(data.decode(encoder)).groups())
(
self.method,
self.request,
self.value_get,
self.variables_get,
self.protocol,
self.protocol_version
) = (lambda method, request, variables, protocol, protocol_version:(
str(method).lower(),
request,
variables,
self.parse_variables(variables),
protocol,
protocol_version
))(*RE.HTTP_REQUEST.match(header[0]).groups())
self.body = body
self.variables_post = self.parse_variables(body)
def set_uri_variables(self:Self, keys:list[str], matches:REMatch) -> None:
i:int
value:str
for i, value in enumerate(matches.groups()):
self.variables_uri[keys[i]] = value
def get(self:Self, keys:str|Sequence[str], default:Optional[Any] = None) -> Any|None:
return Utils.get_value(keys, (
self.variables_uri, self.variables_get, self.variables_post
), default)
@classmethod
def parse_variables(cls:type[Self], string:Optional[str]) -> dict[str, str]:
if not string:
return {}
variables:dict[str, str] = {}
pair:str
for pair in string.split("&"):
if "=" in pair:
key, value = pair.split("=", 1)
variables[Utils.to_snake(key)] = value
else:
variables[Utils.to_snake(pair)] = ""
return variables

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Self, Any, Optional
from Utils.Utils import Utils
class ResponseModel:
def __init__(self:Self,
encoder:str,
variables:dict[str, Any|None],
default_http_code:int = 200,
default_http_message:str = "OK"
) -> None:
self.encoder:str = encoder
self.http_code:int = default_http_code
self.http_message:str = default_http_message
self.variables:dict[str, Any|None] = variables
self.body:bytes = b""
self.mime:str = "application/octet-stream;charset=" + encoder
self.code:int = 200
self.message:str = "OK"
def build(self:Self,
response:Optional[Any] = None,
mime:Optional[str] = None,
code:Optional[int] = None,
message:Optional[str] = None
) -> None:
self.code = code or self.http_code
self.message = self.http_message if message is None else message
self.set_data(response, mime)
def set_data(self:Self, data:Any|None, mime:Optional[str] = None) -> None:
if isinstance(data, bytes):
self.body = data
self.mime = mime or "application/octet-stream"
elif isinstance(data, str):
self.body = data.encode(self.encoder)
self.mime = mime or "text/plain;charset=" + self.encoder
elif isinstance(data, (dict, tuple, list)):
self.body = Utils.json_encode(data).encode(self.encoder)
self.mime = mime or "application/json;charset=" + self.encoder
else:
self.body = str(data).encode(self.encoder)
self.mime = mime or "text/plain;charset=" + self.encoder
def get_parameters(self:Self) -> dict[str, Any|None]:
return {
"http_code" : self.code,
"http_message" : self.message,
"mime" : self.mime,
"length" : len(self.body)
}

View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Callable, Self
from re import Pattern as REPattern, compile as re_compile, IGNORECASE as RE_IGNORE_CASE, Match as REMatch
from Abstracts.ControllerAbstract import ControllerAbstract
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
from Utils.Patterns import RE
from Models.RequestModel import RequestModel
from Models.ResponseModel import ResponseModel
from Utils.Utils import Utils
class RouteModel:
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface, inputs:str|dict[str, Any|None]|tuple[str, str, list[str]]) -> None:
self.method:str = ""
self.route:REPattern
self.path:str|None = None
self.action:Callable[[RequestModel, ResponseModel], tuple[Any|None, int]]|None = None
self.permissions:list[str] = []
self.variables:list[str] = []
self.error:int = 0
if isinstance(inputs, str):
matches:REMatch[str]|None = RE.ROUTE.match(inputs)
if matches is None:
self.error = 1
return
method:str|None
route:str
action:str|None
controller:str|None
permissions:str|None
method, route, action, controller, self.path, permissions = matches.groups()
self.method = (method or "get").lower()
self.route = re_compile(route, RE_IGNORE_CASE)
def callback(matches:REMatch) -> str:
self.variables.append(matches.group(1))
return r'([^\/]+)'
self.route = re_compile(r'^' + RE.ROUTE_KEY.sub(callback, Utils.to_regular_expression(
route[:-1] if route[-1] == "/" else route
)) + (r'\/?' if self.path is None else r'(\/.*)?') + r'$')
self.path and self.variables.append("path")
if permissions:
self.permissions = [permission.strip() for permission in permissions.split(",") if permission.strip()]
if controller and action:
controller_item:ControllerAbstract = nucelar_monitor.controllers.get(controller)
if controller_item:
self.action = controller_item.get_action(action)

View File

@ -1,865 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import datetime
from typing import Any, Optional, Sequence, Self, Callable
from re import compile as re_compile, Pattern as REPattern, Match as REMatch, IGNORECASE as RE_IGNORE_CASE
from pyodbc import connect as pyodbc_connect
from socket import socket as Socket, AF_INET as ADDRESS_FAMILY_IPV4, SOCK_STREAM as SOCKET_STREAM, SOL_SOCKET as SOCKET_LAYER, SO_REUSEADDR as SOCKET_REUSE_ADDRESS
from inspect import stack as get_stack, FrameInfo
from traceback import format_stack as trace_format_stack, extract_tb as extract_traceback
from threading import Thread
from os.path import exists as path_exists, dirname as directory_name, abspath as absolute_path
from json import loads as json_decode, dumps as json_encode
from mimetypes import guess_type as get_mime_by_extension
from pyodbc import connect as odbc_connect, Connection as ODBCConnection, Cursor as ODBCCursor
from time import time as timestamp, sleep
class NucelarMonitor:
DEFAULT_SETTINGS:dict[str, Any|None] = {
"autostart" : True,
"print_format" : "[{type}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{line}]{file}({method}): {message}",
"exception_format" : " '[{line}]{file}({method})'{lines}\n\n{exception_message}",
"print_types" : [
["unkn", "unknown"],
["info", "information"],
["warn", "warning"],
["erro", "error", "wrong", "failure", "fail", "no"],
["exce", "exception", "except"],
[" ok ", "ok", "success", "succeed", "yes"],
["test", "debug"]
],
"http_host" : "0.0.0.0",
"http_port" : 13000,
"http_cache_size" : 1024,
"http_maximum_connections" : 5,
"http_header_response" : (
"{http_protocol}/{http_version} {http_code} {http_message}\r\n" +
"Content-Type: {mime}\r\n" +
"Content-Length: {length}\r\n" +
"\r\n"
),
"http_protocol" : "HTTP",
"http_version" : "1.1",
"http_code" : 200,
"http_message" : "OK",
"http_encoder" : "utf-8",
"index_files" : ("index.html", "index.htm"),
"sql_host" : "127.0.0.1",
"sql_port" : 1433,
"sql_user" : "sa",
"sql_password" : "password",
"sql_database" : "NucelarMonitor",
"default_controllers" : {
"get" : {
"/" : "/Public"
},
"post" : {
"/debian/{key}" : "debian"
}
},
"default_connections" : {}
}
DEFAULT_I18N:dict[str, dict[str, str|Sequence[str]]] = {
"english" : {}
}
ROOT:str = directory_name(absolute_path(__file__))
SLASH:str = "/" if "/" in ROOT else "\\\\"
SPECIAL_REGULAR_EXPRESSION_CHARACTERS:dict[str, str] = {
"\r" : "r",
"\n" : "n",
"\t" : "t"
}
RE_KEY:REPattern = re_compile(r'^[a-z_][a-z0-9_]*$', RE_IGNORE_CASE)
RE_STRING_VARIABLE:REPattern = re_compile(r'\{([a-z_][a-z0-9_]*)\}', RE_IGNORE_CASE)
RE_EXCEPTION:REPattern = re_compile(r'^\s*File "([^"]+)", line ([0-9]+), in ([^\n]+)(.*|[\r\n]*)*$')
RE_NEW_LINE:REPattern = re_compile(r'\r\n|[\r\n]')
RE_TO_SNAKE:REPattern = re_compile(r'[^a-zA-Z0-9]*([A-Z][A-Z0-9]*)|[^a-z0-9]+')
RE_HTTP_REQUEST:REPattern = re_compile(r'^([^\s]+)\s([^\s\?\#]+)(?:\?([^#]+))?(?:\#[^\s]+)?\s([^\/]+)\/([0-9\.]+)$')
RE_HEADER_LINE:REPattern = re_compile(r'^([^\:]+)\:(.+)$')
RE_HTTP_BLOCKS:REPattern = re_compile(r'((?:(?!(?:(?:\r\n){2}|\n{2}|\r{2}))(?:.|[\r\n]+))+)(?:(?:(?:\r\n){2}|\n{2}|\r{2})((?:.+|[\r\n]+)*))?')
RE_LAST_DIRECTORY:REPattern = re_compile(r'^(.*)[\/\\\\][^\/\\\\]*[\/\\\\]?$')
RE_SLASHES:REPattern = re_compile(r'[\\\/]+')
RE_TO_REGULAR_EXPRESSION:REPattern = re_compile(r'[\(\)\{\}\/\\\.\-\+\*\^\$\?\|\!\<\>\r\n\t]')
RE_ROUTE_KEY:REPattern = re_compile(r'\\\{([a-z_][a-z0-9_]*)\\\}', RE_IGNORE_CASE)
RE_ODBC_STRING_VARIABLE:REPattern = re_compile(r'\{([a-z_][a-z0-9_]*)\}|@([a-z0-9_]+)', RE_IGNORE_CASE)
class Request:
def __init__(self:Self, data:bytes, encoder:str = "utf-8") -> None:
self.method:str
self.request:str
self.value_get:str|None
self.variables_get:dict[str, str]
self.variables_post:dict[str, str]
self.protocol:str
self.protocol_version:str
self.body:str
self.variables_uri:dict[str, str] = {}
header, body = (lambda header, body:(
NucelarMonitor.RE_NEW_LINE.split(str(header).strip()), body
))(*NucelarMonitor.RE_HTTP_BLOCKS.match(data.decode(encoder)).groups())
(
self.method,
self.request,
self.value_get,
self.variables_get,
self.protocol,
self.protocol_version
) = (lambda method, request, variables, protocol, protocol_version:(
str(method).lower(),
request,
variables,
self.parse_variables(variables),
protocol,
protocol_version
))(*NucelarMonitor.RE_HTTP_REQUEST.match(header[0]).groups())
self.body = body
self.variables_post = self.parse_variables(body)
def set_uri_variables(self:Self, keys:list[str], matches:REMatch) -> None:
i:int
value:str
for i, value in enumerate(matches.groups()):
self.variables_uri[keys[i]] = value
def get(self:Self, keys:str|Sequence[str], default:Optional[Any] = None) -> Any|None:
return NucelarMonitor.get_value(keys, (
self.variables_uri, self.variables_get, self.variables_post
), default)
@classmethod
def parse_variables(cls:type[Self], string:Optional[str]) -> dict[str, str]:
if not string:
return {}
variables:dict[str, str] = {}
pair:str
for pair in string.split("&"):
if "=" in pair:
key, value = pair.split("=", 1)
variables[cls.to_snake(key)] = value
else:
variables[cls.to_snake(pair)] = ""
return variables
@staticmethod
def to_snake(string:str) -> str:
def callback(matches:REMatch) -> str:
upper:str|None = matches.group(1)
return "_" + upper.lower() if upper else "_"
return NucelarMonitor.RE_TO_SNAKE.sub(callback, string).lower()
class Response:
def __init__(self:Self,
nucelar_monitor:type[Self],
response:Optional[Any] = None,
mime:Optional[str] = None,
code:Optional[int] = None,
message:Optional[str] = None
) -> None:
default_code:int
default_message:str
self.nucelar_monitor:NucelarMonitor = nucelar_monitor
default_code, default_message = self.nucelar_monitor.get_http_default_code()
self.body:bytes = b""
self.mime:str
self.code:str = code or default_code
self.message:str = default_message if message is None else message
self.set_data(response, mime)
def set_data(self:Self, data:Any|None, mime:Optional[str] = None) -> None:
if isinstance(data, bytes):
self.body = data
self.mime = mime or "application/octet-stream"
elif isinstance(data, str):
self.body = data.encode(self.nucelar_monitor.get_encoder())
self.mime = mime or "text/plain;charset=" + self.nucelar_monitor.get_encoder()
elif isinstance(data, (dict, tuple, list)):
self.body = json_encode(data).encode(self.nucelar_monitor.get_encoder())
self.mime = mime or "application/json;charset=" + self.nucelar_monitor.get_encoder()
else:
self.body = str(data).encode(self.nucelar_monitor.get_encoder())
self.mime = mime or "text/plain;charset=" + self.nucelar_monitor.get_encoder()
def get_parameters(self:Self) -> dict[str, Any|None]:
return {
"http_code" : self.code,
"http_message" : self.message,
"mime" : self.mime,
"length" : len(self.body)
}
def __init__(self:Self, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
key:str
self.__inputs:dict[str, Any|None] = self.get_dictionary(inputs)
self.__sentences:dict[str, dict[str, str|Sequence[str]]] = self.DEFAULT_I18N
self.__language:str = self.get("language", None, "english")
self.__print_format:str = self.get("print_format")
self.__print_types:list[list[str]] = self.DEFAULT_SETTINGS["print_types"]
self.__exception_format:str = self.get("exception_format")
self.__controllers:dict[str, list[tuple[
REPattern,
tuple[tuple[str, ...]],
Callable[[NucelarMonitor.Request, NucelarMonitor.Response], None]|None,
str|None]
]] = {}
self.__http_host:str = self.get("http_host")
self.__http_port:int = self.get("http_port")
self.__http_server:Socket
self.__http_buffer_size:int = self.get("http_cache_size")
self.__http_header_response:str = self.get("http_header_response")
self.__http_protocol:str = self.get("http_protocol")
self.__http_version:str = self.get("http_version")
self.__http_code:int = self.get("http_code")
self.__http_message:str = self.get("http_message")
self.__http_encoder:str = self.get("http_encoder")
self.__index_files:tuple[str, ...] = tuple(self.get("index_files"))
self.__started:bool = False
self.__working:bool = False
self.__root_paths:list[str] = ["", self.ROOT]
self.__commands:list[list[list[str], Callable[[dict[str, Any|None], list[Any|None]], None]]] = [
[["close", "exit", "quit", "bye"], self.__close_command]
]
self.__odbc_connection:ODBCConnection|None = None
self.__odbc_string_connection:str = self.string_variables(self.get((
"odbc_string_connection", "sql_string_connection", "string_connection"
), None, "DRIVER={driver};SERVER={host},{port};UID={user};PWD={password};DATABASE={database}"), {
"driver" : self.get("sql_driver", None, "{ODBC Driver 17 for SQL Server}"),
"host" : self.get(("sql_host", "odbc_host"), None, "localhost"),
"port" : self.get(("sql_port", "odbc_port"), None, 1433),
"user" : self.get(("sql_user", "odbc_user"), None, "sa"),
"password" : self.get(("sql_password", "odbc_password"), None, "password"),
"database" : self.get(("sql_database", "odbc_database"), None, "NucelarMonitor")
})
for _ in range(2):
self.__root_paths.append(self.RE_LAST_DIRECTORY.sub(r'\1', self.__root_paths[-1]))
for key in ("default_controllers", "controllers"):
self.add_controllers(self.get(key))
self.get("autostart") and self.start()
def start(self:Self) -> None:
if self.__started:
return
self.__started = True
self.__http_server = Socket(ADDRESS_FAMILY_IPV4, SOCKET_STREAM)
self.__working = True
Thread(target = self.__command_listener).start()
try:
self.__http_server.setsockopt(SOCKET_LAYER, SOCKET_REUSE_ADDRESS, 1)
self.__http_server.bind((self.__http_host, self.__http_port))
self.__http_server.listen(self.get("http_maximum_connections"))
Thread(target = self.__listen).start()
except Exception as exception:
self.exception(exception, "http_server_start_exception", {
"host" : self.__http_host,
"port" : self.__http_port,
})
self.close()
def close(self:Self) -> None:
if not self.__started:
return
self.__started = False
self.__working = False
try:
self.__http_server.close()
except Exception as exception:
self.exception(exception, "http_server_close_exception", {
"host" : self.__http_host,
"port" : self.__http_port,
})
def __command_listener(self:Self) -> None:
while self.__working:
try:
parameters:dict[str, Any|None] = {}
arguments:list[Any|None] = []
command:str = input().strip().lower()
if command:
for commands, callback in self.__commands:
if command in commands:
callback(self.get_dictionary(self.__inputs), [parameters, *arguments])
break
except Exception as exception:
self.exception(exception, "command_listener_exception", {
"command" : command
})
def __close_command(self:Self, inputs:dict[str, Any|None], *arguments:list[Any|None]) -> None:
self.close()
def get_print_type(self:Self, _type:str) -> str:
group:list[str]
for group in self.__print_types:
if _type in group:
return group[0].upper()
return self.__print_types[0][0].upper()
def print(self:Self,
_type:str,
message:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
i:int = 0
) -> None:
date:datetime = datetime.datetime.now()
own:dict[str, Any|None] = {
"raw_type" : _type,
"type" : self.get_print_type(_type),
"i18n" : self.get_texts(message),
"message" : self.i18n(message, inputs),
**self.get_dictionary(inputs),
**self.get_action_data(i + 1)
}
for key in ("year", "month", "day", "hour", "minute", "second"):
k:str = "i" if key == "minute" else key[0]
own[k] = own[key] = getattr(date, key)
own[k + k] = ("00" + str(own[key]))[-2:]
own["yyyy"] = own["year"]
print(self.string_variables(self.__print_format, own) + (own["end"] if "end" in own else ""))
def exception(self:Self,
exception:Exception,
message:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
i:int = 0
) -> None:
lines:list[str] = extract_traceback(exception.__traceback__).format()
matches:REMatch = self.RE_EXCEPTION.match(lines[-1])
data:dict[str, Any|None] = {
**self.get_dictionary(inputs),
"lines" : "",
"exception_message" : str(exception),
"method" : matches.group(3),
"line" : matches.group(2),
"file" : matches.group(1)
}
block:str
j:int
for j, block in enumerate(trace_format_stack()[:-2] + lines):
if block:
data["lines"] += "\n " + str(j) + " - " + self.RE_NEW_LINE.split(block.strip())[0]
data["end"] = self.string_variables(self.__exception_format, data)
message and self.print("exception", message, data, i + 2)
@classmethod
def fix_path(cls:type[Self], path:str) -> str:
return cls.RE_SLASHES.sub(cls.SLASH, path)
def get_absolute_path(self:Self, path:str) -> str|None:
root:str
absolute:str
for root in self.__root_paths:
absolute = self.fix_path((root + '/' if root else "") + path)
if path_exists(absolute):
return absolute
return None
def load_file(self:Self, path:str, mode:str = "r") -> str|bytes|None:
absolute_path:str = self.get_absolute_path(path)
if absolute_path:
with open(absolute_path, mode) as file:
return file.read()
return None
def load_json(self:Self, data:str|dict[str, Any|None]|list[Any|None]) -> dict[str, Any|None]|list[Any|None]|None:
if isinstance(data, str):
json:list[Any|None]|dict[str, Any|None]|None
try:
json = json_decode(data)
except Exception as exception:
self.exception(exception, "load_json_exception", {
"data" : data,
"length" : len(data)
})
if json:
return json
try:
return json_decode(self.load_file(data))
except Exception as exception:
self.exception(exception, "load_json_by_file_exception", {
"path" : data
})
return None
elif isinstance(data, (dict, list)):
return data
return None
def get_encoder(self:Self) -> str:
return self.__http_encoder
def get_http_default_code(self:Self) -> tuple[int, str]:
return self.__http_code, self.__http_message
def get(self:Self,
keys:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
default:Optional[Any] = None
) -> Any|None:
return self.get_value(keys, (inputs, self.__inputs, self.DEFAULT_SETTINGS), default)
def __get_text(self:Self, strings:str|Sequence[str]) -> str:
keys:list[str] = self.get_keys(strings := self.get_list(strings))
if len(keys):
language:str
used:list[str] = []
for language in [self.__language] + list(self.__sentences.keys()):
if language not in used and language in self.__sentences:
key:str
used.append(language)
for key in keys:
if key in self.__sentences[language]:
return self.__sentences[language][key]
return strings[0]
def i18n(self:Self,
strings:str|Sequence[str],
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> str:
return self.string_variables(self.__get_text(strings), inputs)
def add_controllers(self:Self,
inputs:str|dict[str, dict[str, str|Callable[[Request], None]]]|Sequence[Any|None]
) -> None:
if isinstance(inputs, dict):
method:str
controllers:dict[str, str|Callable[[NucelarMonitor.Request], None]]
for method, controllers in inputs.items():
if (method := method.lower()) not in self.__controllers:
self.__controllers[method] = []
if isinstance(controllers, dict):
request:str
target:str|Callable[[NucelarMonitor.Request], None]
for request, target in controllers.items():
controller:Callable[[NucelarMonitor.Request, NucelarMonitor.Response], None]|None = None
path:str|None = None
if isinstance(target, str) and (controller := getattr(self, target, None)) is None:
path = self.get_absolute_path(target)
if callable(controller) or path is not None:
variables:list[str] = []
if path is not None:
variables.append("route")
def callback(matches:REMatch) -> str:
variables.append(matches.group(1))
return r'([^\/]+)'
self.__controllers[method].append((re_compile(r'^' + self.RE_ROUTE_KEY.sub(callback, self.to_regular_expression(
request[:-1] if request[-1] == "/" else request
)) + (r'' if path is None else r'(.*)') + r'\/?$'), tuple(variables), controller, path))
elif isinstance(inputs, (list, tuple)):
subinputs:Any|None
for subinputs in inputs:
self.add_controllers(subinputs)
elif isinstance(inputs, str):
self.add_controllers(self.load_json(inputs))
def __listen(self:Self) -> None:
while self.__working:
try:
client:Socket
address:str
port:int
client, (address, port) = self.__http_server.accept()
Thread(
target = self.__listen_client,
args = (client, address, port)
).start()
except Exception as exception:
self.exception(exception, "http_server_listen_exception", {
"host" : self.__http_host,
"port" : self.__http_port,
})
def __listen_client(self:Self, client:Socket, address:str, port:int) -> None:
data:bytes = b""
route:str = ""
method:str = "UNKN"
response:NucelarMonitor.Response = NucelarMonitor.Response(self)
try:
request:NucelarMonitor.Request
variables:tuple[str, ...]
controller:Callable[[NucelarMonitor.Request], Any|None]|None
path:str|None
response_data:Any|None = None
pattern:REPattern
done:bool = False
while True:
buffer:bytes = client.recv(self.__http_buffer_size)
if not buffer:
break
data += buffer
if len(buffer) != self.__http_buffer_size:
break
for pattern, variables, controller, path in self.__controllers[
method := (request := self.Request(data, self.__http_encoder)).method
]:
matches:REMatch = pattern.match(route := request.request)
if matches is not None:
request.set_uri_variables(variables, matches)
if done := path is not None:
for index in self.__index_files:
full_path = path + "/" + route + ("" if index == "" else "/" + index)
if done := (response_data := self.load_file(full_path, "rb")) is not None:
response.set_data(
response_data,
get_mime_by_extension(full_path)[0] or "application/octet-stream"
)
break
elif done := controller is not None:
controller(request, response)
if done:
break
if not done:
response.body = b"<h1>Not Found</h1>"
response.mime = "text/html;charset=" + self.__http_encoder
response.code = "404"
response.message = "Not Found"
client.sendall(self.string_variables(self.__http_header_response, {
"http_protocol" : self.__http_protocol,
"http_version" : self.__http_version,
**response.get_parameters()
}).encode(self.__http_encoder) + response.body)
client.close()
except Exception as exception:
self.exception(exception, "http_server_client_exception", {
"host" : self.__http_host,
"port" : self.__http_port,
"client_address" : address,
"client_port" : port,
"length" : len(data),
"method" : method,
"route" : route,
"response_length" : len(response.body)
})
def __odbc_connection_autoclose(self:Self) -> None:
pass
def format_odbc_query(self:Self,
query:str,
parameters:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> tuple[str, list[str]]:
variables:list[str] = []
def callback(matches:REMatch) -> str:
key:str = matches.group(1)
if key:
return (
"'" + str(parameters[key]).replace("'","''") + "'" if isinstance(parameters[key], str) else
str(parameters[key]) if key in parameters else matches.group(0))
key = matches.group(2)
variables.append(key)
return matches.group(0)
query = self.RE_ODBC_STRING_VARIABLE.sub(callback, query)
return (
"".join("declare @" + variable + " varchar(max)\n" for variable in variables) +
query +
"\nselect " + ", ".join("@" + variable for variable in variables)
) if len(variables) else query, variables
def odbc_query(self:Self,
query:str,
parameters:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> dict[str, dict[str, Any|None]|list[list[list[Any|None]]]|list[list[str]]]:
response:dict[str, dict[str, Any|None]|list[list[list[Any|None]]]|list[list[str]]] = {
"tables" : [],
"columns" : [],
"variables" : {}
}
cursor:ODBCCursor|None = None
variables:list[str] = []
try:
if self.__odbc_connection is None:
self.__odbc_connection = odbc_connect(
self.__odbc_string_connection,
autocommit = True
)
cursor = self.__odbc_connection.cursor()
query, variables = self.format_odbc_query(query, parameters)
cursor.execute(query)
while True:
if cursor.description is not None:
response["columns"].append([column[0] for column in cursor.description])
response["tables"].append([tuple(row) for row in cursor.fetchall()])
if not cursor.nextset():
break
except Exception as exception:
self.exception(exception, "odbc_connection_exception", {
"string_connection" : self.__odbc_string_connection
})
finally:
if cursor is not None:
cursor.close()
if len(variables) and len(response["tables"]):
for i, variable in enumerate(variables):
response["variables"][variable] = response["tables"][-1][0][i]
response["tables"] = response["tables"][:-1]
response["columns"] = response["columns"][:-1]
return response
def debian(self:Self, request:Request, response:Response) -> None:
key:str = request.get("key")
hostnames:list[str]
domain:str|None
interfaces:list[list[int, str, bool, str, int]]
disks:list[list[str, int, int, str|None]]
iterations:int
candle_times:list[int, int]
cpu:list[float, float, float, float, float]
memory:list[int, int, int, int, int, float]
net_use:list[list[list[str, int, int, int, int, int, int]]]
hostnames, domain, interfaces, disks, iterations, candle_times, cpu, memory, net_use = json_decode(request.body)
@classmethod
def get_dictionary(cls:type[Self], *items:Sequence[Any|None]) -> dict[str, Any|None]:
dictionary:dict[str, Any|None] = {}
item:Any|None
for item in items:
if isinstance(item, dict):
dictionary.update(item)
elif isinstance(item, (list, tuple)):
subitem:Any|None
for subitem in item:
dictionary.update(cls.get_dictionary(subitem))
return dictionary
@classmethod
def get_keys(cls:type[Self], *items:Sequence[Any|None]) -> list[str]:
keys:list[str] = []
item:Any|None
for item in items:
if isinstance(item, str):
cls.RE_KEY.match(item) and keys.append(item)
elif isinstance(item, (list, tuple)):
subitem:Any|None
for subitem in item:
keys.extend(cls.get_keys(subitem))
return keys
@classmethod
def get_dictionaries(cls:type[Self], *items:Sequence[Any|None]) -> list[dict[str, Any|None]]:
dictionaries:list[dict[str, Any|None]] = []
item:Any|None
for item in items:
if isinstance(item, dict):
dictionaries.append(item)
elif isinstance(item, (list, tuple)):
subitem:Any|None
for subitem in item:
dictionaries.extend(cls.get_dictionaries(subitem))
return dictionaries
@classmethod
def get_value(cls:type[Self],
keys:str|Sequence[str],
inputs:dict[str, Any|None]|Sequence[Any|None],
default:Optional[Any] = None
) -> Any|None:
if len(cls.get_keys(keys)):
dictionary:dict[str, Any|None]
for dictionary in cls.get_dictionaries(inputs):
key:str
for key in cls.get_keys(keys):
if key in dictionary:
return dictionary[key]
return default
@staticmethod
def get_list(item:Any|None) -> list[Any|None]:
return item if isinstance(item, (list, tuple)) else [item]
@classmethod
def string_variables(cls:type[Self],
string:str,
inputs:dict[str, Any|None]|Sequence[Any|None],
default:Optional[str] = None
) -> str:
variables:dict[str, Any|None] = cls.get_dictionary(inputs)
def callback(matches:REMatch) -> str:
key:str = matches.group(1)
return (
str(variables[key]) if key in variables else
default if default is not None else
matches.group(0))
return cls.RE_STRING_VARIABLE.sub(callback, string)
@staticmethod
def get_texts(*items:list[Any|None]) -> list[str]:
texts:list[str] = []
item:Any|None
for item in items:
if isinstance(item, str):
texts.append(item)
elif isinstance(item, (list, tuple)):
subitem:Any|None
for subitem in item:
texts.extend(NucelarMonitor.get_texts(subitem))
return texts
@staticmethod
def get_action_data(i:int = 0) -> dict[str, str|int]:
stack:FrameInfo = get_stack()[i]
return {
"file" : stack.filename,
"method" : stack.function,
"line" : stack.lineno
}
@classmethod
def to_regular_expression(cls:type[Self], string:str) -> str:
def callback(matches:REMatch) -> str:
character:str = matches.group(0)
return "\\" + (
cls.SPECIAL_REGULAR_EXPRESSION_CHARACTERS[character] if character in cls.SPECIAL_REGULAR_EXPRESSION_CHARACTERS else
character)
return cls.RE_TO_REGULAR_EXPRESSION.sub(callback, string)

21
Python/Utils/Patterns.py Normal file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from re import compile as re_compile, Pattern as REPattern, IGNORECASE as RE_IGNORE_CASE
class RE:
KEY:REPattern = re_compile(r'^[a-z_][a-z0-9_]*$', RE_IGNORE_CASE)
STRING_VARIABLE:REPattern = re_compile(r'\{([a-z_][a-z0-9_]*)\}', RE_IGNORE_CASE)
EXCEPTION:REPattern = re_compile(r'^\s*File "([^"]+)", line ([0-9]+), in ([^\n]+)(.*|[\r\n]*)*$')
NEW_LINE:REPattern = re_compile(r'\r\n|[\r\n]')
TO_SNAKE:REPattern = re_compile(r'[^a-zA-Z0-9]*([A-Z][A-Z0-9]*)|[^a-z0-9]+')
HTTP_REQUEST:REPattern = re_compile(r'^([^\s]+)\s([^\s\?\#]+)(?:\?([^#]+))?(?:\#[^\s]+)?\s([^\/]+)\/([0-9\.]+)$')
HEADER_LINE:REPattern = re_compile(r'^([^\:]+)\:(.+)$')
HTTP_BLOCKS:REPattern = re_compile(r'((?:(?!(?:(?:\r\n){2}|\n{2}|\r{2}))(?:.|[\r\n]+))+)(?:(?:(?:\r\n){2}|\n{2}|\r{2})((?:.+|[\r\n]+)*))?')
LAST_DIRECTORY:REPattern = re_compile(r'^(.*)[\/\\\\][^\/\\\\]*[\/\\\\]?$')
SLASHES:REPattern = re_compile(r'[\\\/]+')
TO_REGULAR_EXPRESSION:REPattern = re_compile(r'[\(\)\{\}\/\\\.\-\+\*\^\$\?\|\!\<\>\r\n\t]')
ROUTE_KEY:REPattern = re_compile(r'\\\{([a-z_][a-z0-9_]*)\\\}', RE_IGNORE_CASE)
ODBC_STRING_VARIABLE:REPattern = re_compile(r'\{([a-z_][a-z0-9_]*)\}|@([a-z0-9_]+)', RE_IGNORE_CASE)
ROUTE:REPattern = re_compile(r'^(?:([^\:]+)?\:)([^\s]+)\s+(?:([^\@\s]+)\@([^\s]+)|([^\s]+))(?:\s*(.+))?$', RE_IGNORE_CASE)

218
Python/Utils/Utils.py Normal file
View File

@ -0,0 +1,218 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Self, Optional, Sequence, Callable
from re import Match as REMatch
from inspect import FrameInfo, stack as get_stack
from Utils.Patterns import RE
from json import dumps as json_encode, loads as json_decode
class Utils:
SPECIAL_REGULAR_EXPRESSION_CHARACTERS:dict[str, str] = {
"\r" : "r",
"\n" : "n",
"\t" : "t"
}
@classmethod
def get_dictionary(cls:type[Self], *items:Sequence[Any|None]) -> dict[str, Any|None]:
dictionary:dict[str, Any|None] = {}
item:Any|None
for item in items:
if isinstance(item, dict):
dictionary.update(item)
elif isinstance(item, (list, tuple)):
subitem:Any|None
for subitem in item:
dictionary.update(cls.get_dictionary(subitem))
return dictionary
@classmethod
def get_keys(cls:type[Self], *items:Sequence[Any|None]) -> list[str]:
keys:list[str] = []
item:Any|None
for item in items:
if isinstance(item, str):
RE.KEY.match(item) and keys.append(item)
elif isinstance(item, (list, tuple)):
subitem:Any|None
for subitem in item:
keys.extend(cls.get_keys(subitem))
return keys
@classmethod
def get_dictionaries(cls:type[Self], *items:Sequence[Any|None]) -> list[dict[str, Any|None]]:
dictionaries:list[dict[str, Any|None]] = []
item:Any|None
for item in items:
if isinstance(item, dict):
dictionaries.append(item)
elif isinstance(item, (list, tuple)):
subitem:Any|None
for subitem in item:
dictionaries.extend(cls.get_dictionaries(subitem))
return dictionaries
@classmethod
def get_value(cls:type[Self],
keys:str|Sequence[str],
inputs:dict[str, Any|None]|Sequence[Any|None],
default:Optional[Any] = None
) -> Any|None:
if len(cls.get_keys(keys)):
dictionary:dict[str, Any|None]
for dictionary in cls.get_dictionaries(inputs):
key:str
for key in cls.get_keys(keys):
if key in dictionary:
return dictionary[key]
return default
@staticmethod
def get_list(item:Any|None) -> list[Any|None]:
return item if isinstance(item, (list, tuple)) else [item]
@classmethod
def string_variables(cls:type[Self],
string:str,
inputs:dict[str, Any|None]|Sequence[Any|None],
default:Optional[str] = None
) -> str:
variables:dict[str, Any|None] = cls.get_dictionary(inputs)
def callback(matches:REMatch) -> str:
key:str = matches.group(1)
return (
str(variables[key]) if key in variables else
default if default is not None else
matches.group(0))
return RE.STRING_VARIABLE.sub(callback, string)
@classmethod
def get_texts(cls:type[Self], *items:list[Any|None]) -> list[str]:
texts:list[str] = []
item:Any|None
for item in items:
if isinstance(item, str):
texts.append(item)
elif isinstance(item, (list, tuple)):
subitem:Any|None
for subitem in item:
texts.extend(cls.get_texts(subitem))
return texts
@staticmethod
def get_action_data(i:int = 0) -> dict[str, str|int]:
stack:FrameInfo = get_stack()[i]
return {
"file" : stack.filename,
"method" : stack.function,
"line" : stack.lineno
}
@classmethod
def to_regular_expression(cls:type[Self], string:str) -> str:
def callback(matches:REMatch) -> str:
character:str = matches.group(0)
return "\\" + (
cls.SPECIAL_REGULAR_EXPRESSION_CHARACTERS[character] if character in cls.SPECIAL_REGULAR_EXPRESSION_CHARACTERS else
character)
return RE.TO_REGULAR_EXPRESSION.sub(callback, string)
@staticmethod
def json_decode(data:str) -> Any|None:
try:
return json_decode(data)
except Exception as _:
pass
return None
@staticmethod
def json_encode(data:Sequence[Any|None]|dict[str, Any|None]) -> str|None:
try:
return json_encode(data)
except Exception as _:
pass
return None
@staticmethod
def to_snake(string:str) -> str:
def callback(matches:REMatch) -> str:
upper:str|None = matches.group(1)
return "_" + upper.lower() if upper else "_"
return RE.TO_SNAKE.sub(callback, string).lower()
@staticmethod
def get_function(string:str, _from:list[str] = []) -> Optional[Callable[[Any|None], Any|None]]:
item:Any|None
fragments:list[str] = string.split(".")
fragment:str
done:bool
for item in _from:
done = True
for fragment in fragments:
if done := hasattr(item, fragment):
item = getattr(item, fragment)
else:
break
if done and callable(item):
return item
if done := fragments[0] in globals():
item = globals()[fragments[0]]
for fragment in fragments[1:]:
if done := hasattr(item, fragment):
item = getattr(item, fragment)
else:
break
if done and callable(item):
return item
return None

View File

@ -1,6 +1,32 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from NucelarMonitor import NucelarMonitor
from typing import Any
from Application.NucelarMonitor import NucelarMonitor
from Controllers.AgentsController import AgentsController
from Drivers.SQLServerDriver import SQLServerDriver
from Drivers.WebServerDriver import WebServerDriver
nucelar_monitor:NucelarMonitor = NucelarMonitor()
inputs:dict[str, dict[str, Any|None]] = {
"default_models" : {
"agents" : AgentsController,
"sql_server" : SQLServerDriver,
"web_server" : WebServerDriver
}
}
try:
from secrets import secrets as custom_secrets
for key, value in dict(custom_secrets).items():
if key not in inputs or isinstance(inputs[key], dict):
inputs[key] = value
elif isinstance(value, dict):
for subkey, subvalue in value.items():
inputs[key][subkey] = subvalue
except ImportError:
pass
nucelar_monitor:NucelarMonitor = NucelarMonitor(inputs)

3
Tools/NucelarMonitor.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
cd `dirname $(readlink -f "$0")`/../Python
python3 "run.py"