diff --git a/.gitignore b/.gitignore index 18a1921..7110aeb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ __pycache__ *.[Ss]ecrets.* *.[Ss]ecret.* -/Python/pyodbc.py +/Python/Assets /SQLServer/data /SQLServer/temporary /SQLServer/scripts \ No newline at end of file diff --git a/Bash/NucelarMonitor.debian.execute.sh b/Bash/NucelarMonitor.debian.execute.sh index 8d0bb2b..2ec0d61 100755 --- a/Bash/NucelarMonitor.debian.execute.sh +++ b/Bash/NucelarMonitor.debian.execute.sh @@ -1,2 +1,2 @@ #!/bin/bash -nohup ./tu_script.sh > /dev/null 2>&1 & \ No newline at end of file +nohup ./NucelarMonitor.debian.script.sh > /dev/null 2>&1 & diff --git a/Bash/NucelarMonitor.debian.script.sh b/Bash/NucelarMonitor.debian.script.sh index 0197c2d..802f5e5 100755 --- a/Bash/NucelarMonitor.debian.script.sh +++ b/Bash/NucelarMonitor.debian.script.sh @@ -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/,/.} diff --git a/JSON/NucelarMonitor.settings.json b/JSON/NucelarMonitor.settings.json index 9e26dfe..f9ccac5 100644 --- a/JSON/NucelarMonitor.settings.json +++ b/JSON/NucelarMonitor.settings.json @@ -1 +1,54 @@ -{} \ No newline at end of file +{ + "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" + ] +} \ No newline at end of file diff --git a/Python/Abstracts/ControllerAbstract.py b/Python/Abstracts/ControllerAbstract.py new file mode 100644 index 0000000..59b8c8b --- /dev/null +++ b/Python/Abstracts/ControllerAbstract.py @@ -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 \ No newline at end of file diff --git a/Python/Abstracts/DatabaseAbstract.py b/Python/Abstracts/DatabaseAbstract.py new file mode 100644 index 0000000..b6ef994 --- /dev/null +++ b/Python/Abstracts/DatabaseAbstract.py @@ -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 \ No newline at end of file diff --git a/Python/Abstracts/DispatcherAbstract.py b/Python/Abstracts/DispatcherAbstract.py new file mode 100644 index 0000000..fc9c771 --- /dev/null +++ b/Python/Abstracts/DispatcherAbstract.py @@ -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 \ No newline at end of file diff --git a/Python/Abstracts/WebServerAbstract.py b/Python/Abstracts/WebServerAbstract.py new file mode 100644 index 0000000..7d0cb9b --- /dev/null +++ b/Python/Abstracts/WebServerAbstract.py @@ -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 \ No newline at end of file diff --git a/Python/Application/NucelarMonitor.py b/Python/Application/NucelarMonitor.py new file mode 100644 index 0000000..57c5a4f --- /dev/null +++ b/Python/Application/NucelarMonitor.py @@ -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) \ No newline at end of file diff --git a/Python/Controllers/AgentsController.py b/Python/Controllers/AgentsController.py new file mode 100644 index 0000000..16f9b16 --- /dev/null +++ b/Python/Controllers/AgentsController.py @@ -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") \ No newline at end of file diff --git a/Python/Drivers/FilesDrivers.py b/Python/Drivers/FilesDrivers.py new file mode 100644 index 0000000..444a6ab --- /dev/null +++ b/Python/Drivers/FilesDrivers.py @@ -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 \ No newline at end of file diff --git a/Python/Drivers/SQLServerDriver.py b/Python/Drivers/SQLServerDriver.py new file mode 100644 index 0000000..bed748f --- /dev/null +++ b/Python/Drivers/SQLServerDriver.py @@ -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 \ No newline at end of file diff --git a/Python/Drivers/WebServerDriver.py b/Python/Drivers/WebServerDriver.py new file mode 100644 index 0000000..d905653 --- /dev/null +++ b/Python/Drivers/WebServerDriver.py @@ -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, + }) \ No newline at end of file diff --git a/Python/Interfaces/Abstracts/ControllerAbstractInterface.py b/Python/Interfaces/Abstracts/ControllerAbstractInterface.py new file mode 100644 index 0000000..a421906 --- /dev/null +++ b/Python/Interfaces/Abstracts/ControllerAbstractInterface.py @@ -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 \ No newline at end of file diff --git a/Python/Interfaces/Abstracts/WebSocketAbstractInterface.py b/Python/Interfaces/Abstracts/WebSocketAbstractInterface.py new file mode 100644 index 0000000..95f0311 --- /dev/null +++ b/Python/Interfaces/Abstracts/WebSocketAbstractInterface.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from abc import ABC + +class WebSocketAbstractInterface(ABC):pass \ No newline at end of file diff --git a/Python/Interfaces/Application/NucelarMonitorInterface.py b/Python/Interfaces/Application/NucelarMonitorInterface.py new file mode 100644 index 0000000..d6df0e8 --- /dev/null +++ b/Python/Interfaces/Application/NucelarMonitorInterface.py @@ -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 \ No newline at end of file diff --git a/Python/Interfaces/Drivers/FilesDriversInterface.py b/Python/Interfaces/Drivers/FilesDriversInterface.py new file mode 100644 index 0000000..5419950 --- /dev/null +++ b/Python/Interfaces/Drivers/FilesDriversInterface.py @@ -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 \ No newline at end of file diff --git a/Python/Interfaces/Managers/ControllersManagerInterface.py b/Python/Interfaces/Managers/ControllersManagerInterface.py new file mode 100644 index 0000000..559d769 --- /dev/null +++ b/Python/Interfaces/Managers/ControllersManagerInterface.py @@ -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 \ No newline at end of file diff --git a/Python/Interfaces/Managers/I18NManagersInterface.py b/Python/Interfaces/Managers/I18NManagersInterface.py new file mode 100644 index 0000000..812f001 --- /dev/null +++ b/Python/Interfaces/Managers/I18NManagersInterface.py @@ -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 \ No newline at end of file diff --git a/Python/Interfaces/Managers/ModelsManagerInterface.py b/Python/Interfaces/Managers/ModelsManagerInterface.py new file mode 100644 index 0000000..bc53f60 --- /dev/null +++ b/Python/Interfaces/Managers/ModelsManagerInterface.py @@ -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 \ No newline at end of file diff --git a/Python/Interfaces/Managers/RoutesManagerInterface.py b/Python/Interfaces/Managers/RoutesManagerInterface.py new file mode 100644 index 0000000..33bfaad --- /dev/null +++ b/Python/Interfaces/Managers/RoutesManagerInterface.py @@ -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 \ No newline at end of file diff --git a/Python/Interfaces/Managers/SettingsManagersInterface.py b/Python/Interfaces/Managers/SettingsManagersInterface.py new file mode 100644 index 0000000..5e1af2b --- /dev/null +++ b/Python/Interfaces/Managers/SettingsManagersInterface.py @@ -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 \ No newline at end of file diff --git a/Python/Interfaces/Managers/TerminalManagerInterface.py b/Python/Interfaces/Managers/TerminalManagerInterface.py new file mode 100644 index 0000000..c51be1a --- /dev/null +++ b/Python/Interfaces/Managers/TerminalManagerInterface.py @@ -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 \ No newline at end of file diff --git a/Python/Managers/ControllersManager.py b/Python/Managers/ControllersManager.py new file mode 100644 index 0000000..48f89ee --- /dev/null +++ b/Python/Managers/ControllersManager.py @@ -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 \ No newline at end of file diff --git a/Python/Managers/DatabasesManager.py b/Python/Managers/DatabasesManager.py new file mode 100644 index 0000000..b9c1a71 --- /dev/null +++ b/Python/Managers/DatabasesManager.py @@ -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() \ No newline at end of file diff --git a/Python/Managers/DispatchersManager.py b/Python/Managers/DispatchersManager.py new file mode 100644 index 0000000..a2494a4 --- /dev/null +++ b/Python/Managers/DispatchersManager.py @@ -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 \ No newline at end of file diff --git a/Python/Managers/I18NManager.py b/Python/Managers/I18NManager.py new file mode 100644 index 0000000..a96909b --- /dev/null +++ b/Python/Managers/I18NManager.py @@ -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 \ No newline at end of file diff --git a/Python/Managers/ModelsManager.py b/Python/Managers/ModelsManager.py new file mode 100644 index 0000000..74368eb --- /dev/null +++ b/Python/Managers/ModelsManager.py @@ -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 \ No newline at end of file diff --git a/Python/Managers/RoutesManager.py b/Python/Managers/RoutesManager.py new file mode 100644 index 0000000..6c0f1db --- /dev/null +++ b/Python/Managers/RoutesManager.py @@ -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") \ No newline at end of file diff --git a/Python/Managers/SettingsManager.py b/Python/Managers/SettingsManager.py new file mode 100644 index 0000000..617e0f9 --- /dev/null +++ b/Python/Managers/SettingsManager.py @@ -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 \ No newline at end of file diff --git a/Python/Managers/TerminalManager.py b/Python/Managers/TerminalManager.py new file mode 100644 index 0000000..f8b8f4a --- /dev/null +++ b/Python/Managers/TerminalManager.py @@ -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() \ No newline at end of file diff --git a/Python/Managers/WebServersManager.py b/Python/Managers/WebServersManager.py new file mode 100644 index 0000000..01e881e --- /dev/null +++ b/Python/Managers/WebServersManager.py @@ -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() \ No newline at end of file diff --git a/Python/Models/QueryResponseModel.py b/Python/Models/QueryResponseModel.py new file mode 100644 index 0000000..761fba0 --- /dev/null +++ b/Python/Models/QueryResponseModel.py @@ -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] = {} \ No newline at end of file diff --git a/Python/Models/RequestModel.py b/Python/Models/RequestModel.py new file mode 100644 index 0000000..9d37903 --- /dev/null +++ b/Python/Models/RequestModel.py @@ -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 \ No newline at end of file diff --git a/Python/Models/ResponseModel.py b/Python/Models/ResponseModel.py new file mode 100644 index 0000000..04ea85d --- /dev/null +++ b/Python/Models/ResponseModel.py @@ -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) + } \ No newline at end of file diff --git a/Python/Models/RouteModel.py b/Python/Models/RouteModel.py new file mode 100644 index 0000000..53d4977 --- /dev/null +++ b/Python/Models/RouteModel.py @@ -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) \ No newline at end of file diff --git a/Python/NucelarMonitor.py b/Python/NucelarMonitor.py deleted file mode 100644 index dbba3d2..0000000 --- a/Python/NucelarMonitor.py +++ /dev/null @@ -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"

Not Found

" - 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) \ No newline at end of file diff --git a/Python/Utils/Patterns.py b/Python/Utils/Patterns.py new file mode 100644 index 0000000..bc5aa30 --- /dev/null +++ b/Python/Utils/Patterns.py @@ -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) \ No newline at end of file diff --git a/Python/Utils/Utils.py b/Python/Utils/Utils.py new file mode 100644 index 0000000..ef8c340 --- /dev/null +++ b/Python/Utils/Utils.py @@ -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 diff --git a/Python/run.py b/Python/run.py index df7d197..60d2494 100644 --- a/Python/run.py +++ b/Python/run.py @@ -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() \ No newline at end of file +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) \ No newline at end of file diff --git a/Tools/NucelarMonitor.sh b/Tools/NucelarMonitor.sh new file mode 100755 index 0000000..b51fe32 --- /dev/null +++ b/Tools/NucelarMonitor.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd `dirname $(readlink -f "$0")`/../Python +python3 "run.py" \ No newline at end of file