#wip: Python division subdirs and files; and fixed Bash agent. Needs implements Dispatchers and GUI only.
This commit is contained in:
parent
4f9e9acfe9
commit
2f4a5d4609
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,7 +3,7 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
*.[Ss]ecrets.*
|
*.[Ss]ecrets.*
|
||||||
*.[Ss]ecret.*
|
*.[Ss]ecret.*
|
||||||
/Python/pyodbc.py
|
/Python/Assets
|
||||||
/SQLServer/data
|
/SQLServer/data
|
||||||
/SQLServer/temporary
|
/SQLServer/temporary
|
||||||
/SQLServer/scripts
|
/SQLServer/scripts
|
||||||
@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
nohup ./tu_script.sh > /dev/null 2>&1 &
|
nohup ./NucelarMonitor.debian.script.sh > /dev/null 2>&1 &
|
||||||
|
|||||||
@ -7,7 +7,7 @@ candle_sleep_seconds=1
|
|||||||
execute_sleep_seconds=0
|
execute_sleep_seconds=0
|
||||||
show_json=true
|
show_json=true
|
||||||
send_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.
|
# Settings.
|
||||||
|
|
||||||
function get_net_data(){
|
function get_net_data(){
|
||||||
@ -89,7 +89,7 @@ function execute(){
|
|||||||
|
|
||||||
# cpu_average=$((cpu_average + cpu))
|
# cpu_average=$((cpu_average + cpu))
|
||||||
# memory_average=$((memory_average + memory))
|
# 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)
|
memory_average=$(echo "$memory_average + $memory"|bc -l)
|
||||||
|
|
||||||
cpu=${cpu/,/.}
|
cpu=${cpu/,/.}
|
||||||
|
|||||||
@ -1 +1,54 @@
|
|||||||
{}
|
{
|
||||||
|
"autostart" : true,
|
||||||
|
"print_format" : "[{type}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{line}]{file}({method}): {message}",
|
||||||
|
"exception_format" : " '[{line}]{file}({method})'{lines}\n\n{exception_message}",
|
||||||
|
"print_types" : [
|
||||||
|
["unkn", "unknown"],
|
||||||
|
["info", "information"],
|
||||||
|
["warn", "warning"],
|
||||||
|
["erro", "error", "wrong", "failure", "fail", "no"],
|
||||||
|
["exce", "exception", "except"],
|
||||||
|
[" ok ", "ok", "success", "succeed", "yes"],
|
||||||
|
["test", "debug"]
|
||||||
|
],
|
||||||
|
"web_servers" : {
|
||||||
|
"main" : {
|
||||||
|
"type" : "web_server",
|
||||||
|
"host" : "0.0.0.0",
|
||||||
|
"port" : 13000,
|
||||||
|
"cache_size" : 1024,
|
||||||
|
"maximum_connections" : 5,
|
||||||
|
"header_response" : [
|
||||||
|
"{protocol}/{version} {code} {message}",
|
||||||
|
"Content-Type: {mime}",
|
||||||
|
"Content-Length: {length}",
|
||||||
|
"Connection: close"
|
||||||
|
],
|
||||||
|
"protocol" : "HTTP",
|
||||||
|
"version" : "1.1",
|
||||||
|
"code" : 200,
|
||||||
|
"message" : "OK",
|
||||||
|
"encoder" : "utf-8",
|
||||||
|
"index_files" : ["index.html", "index.htm"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"databases" : {
|
||||||
|
"sql_server" : {
|
||||||
|
"type" : "sql_server",
|
||||||
|
"driver" : "{ODBC Driver 17 for SQL Server}",
|
||||||
|
"host" : "127.0.0.1",
|
||||||
|
"port" : 1433,
|
||||||
|
"user" : "sa",
|
||||||
|
"password" : "password",
|
||||||
|
"database" : "NucelarMonitor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default_controllers" : {
|
||||||
|
"agents" : "agents"
|
||||||
|
},
|
||||||
|
"default_routes" : [
|
||||||
|
"post:/agents/debian/{key} debian@agents",
|
||||||
|
"get:/agents/test/{key} test@agents",
|
||||||
|
"get:/ /Public"
|
||||||
|
]
|
||||||
|
}
|
||||||
18
Python/Abstracts/ControllerAbstract.py
Normal file
18
Python/Abstracts/ControllerAbstract.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, Callable
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Models.RequestModel import RequestModel
|
||||||
|
from Models.ResponseModel import ResponseModel
|
||||||
|
|
||||||
|
class ControllerAbstract:
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
def get_action(self:Self, action:str) -> Callable[[RequestModel, ResponseModel], None]|None:
|
||||||
|
|
||||||
|
method:Any|None = getattr(self, action, None)
|
||||||
|
|
||||||
|
return method if method is not None and callable(method) else None
|
||||||
24
Python/Abstracts/DatabaseAbstract.py
Normal file
24
Python/Abstracts/DatabaseAbstract.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, Optional, Sequence
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Models.QueryResponseModel import QueryResponseModel
|
||||||
|
|
||||||
|
class DatabaseAbstract(ABC):
|
||||||
|
|
||||||
|
def __init__(self:Self,
|
||||||
|
nucelar_monitor:NucelarMonitorInterface,
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
||||||
|
) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def close(self:Self) -> bool:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def query(self:Self,
|
||||||
|
query:str,
|
||||||
|
parameters:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
||||||
|
) -> QueryResponseModel:pass
|
||||||
10
Python/Abstracts/DispatcherAbstract.py
Normal file
10
Python/Abstracts/DispatcherAbstract.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
|
||||||
|
class DispatcherAbstract:
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
35
Python/Abstracts/WebServerAbstract.py
Normal file
35
Python/Abstracts/WebServerAbstract.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, Optional, Sequence
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
|
||||||
|
class WebServerAbstract(ABC):
|
||||||
|
|
||||||
|
def __init__(self:Self,
|
||||||
|
nucelar_monitor:NucelarMonitorInterface,
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
||||||
|
) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
index_files:Sequence[str]|str = self.nucelar_monitor.settings.get("index_files", inputs, ["index.html", "index.htm"])
|
||||||
|
header_response:str|Sequence[str] = self.nucelar_monitor.settings.get("header_response", inputs, "HTTP/{version} {code} {message}\r\n")
|
||||||
|
self._host:str = self.nucelar_monitor.settings.get("host", inputs, "::1")
|
||||||
|
self._port:int = self.nucelar_monitor.settings.get("port", inputs, 13000)
|
||||||
|
self._header_response:str = header_response if isinstance(header_response, str) else "\r\n".join(header_response) + "\r\n\r\n"
|
||||||
|
self._protocol:str = self.nucelar_monitor.settings.get("protocol", inputs, "HTTP")
|
||||||
|
self._version:str = self.nucelar_monitor.settings.get("version", inputs, "1.1")
|
||||||
|
self._code:int = self.nucelar_monitor.settings.get("code", inputs, 200)
|
||||||
|
self._message:str = self.nucelar_monitor.settings.get("message", inputs, "OK")
|
||||||
|
self._encoder:str = self.nucelar_monitor.settings.get("encoder", inputs, "utf-8")
|
||||||
|
self._index_files:list[str] = [""] + (index_files if isinstance(index_files, list) else [index_files])
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def start(self:Self) -> bool:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def stop(self:Self) -> bool:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def close(self:Self) -> bool:pass
|
||||||
145
Python/Application/NucelarMonitor.py
Normal file
145
Python/Application/NucelarMonitor.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from typing import Any, Optional, Sequence, Self
|
||||||
|
from traceback import extract_tb as extract_traceback, format_stack as trace_format_stack
|
||||||
|
from re import Match as REMatch
|
||||||
|
from Utils.Patterns import RE
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
from Drivers.FilesDrivers import FilesDriver
|
||||||
|
from Managers.I18NManager import I18NManager
|
||||||
|
from Managers.SettingsManager import SettingsManager
|
||||||
|
from Managers.ModelsManager import ModelsManager
|
||||||
|
from Managers.TerminalManager import TerminalManager
|
||||||
|
from Managers.RoutesManager import RoutesManager
|
||||||
|
from Managers.ControllersManager import ControllersManager
|
||||||
|
from Managers.DatabasesManager import DatabasesManager
|
||||||
|
from Managers.WebServersManager import WebServersManager
|
||||||
|
|
||||||
|
class NucelarMonitor:
|
||||||
|
|
||||||
|
def __init__(self:Self, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
|
||||||
|
|
||||||
|
self.__print_format:str = "[{type}] {yyyy}-{mm}-{dd} {hh}:{ii}:{ss} - {message}"
|
||||||
|
self.__print_types:list[list[str]] = [
|
||||||
|
["unkn", "unknown"],
|
||||||
|
["info", "information"],
|
||||||
|
["warn", "warning"],
|
||||||
|
["erro", "error", "wrong", "failure", "fail", "no"],
|
||||||
|
["exce", "exception", "except"],
|
||||||
|
[" ok ", "ok", "success", "succeed", "yes"],
|
||||||
|
["test", "debug"]
|
||||||
|
]
|
||||||
|
self.__exception_format:str = " '[{line}]{file}({method})'{lines}\n\n{exception_message}"
|
||||||
|
self.files:FilesDriver = FilesDriver(self)
|
||||||
|
self.i18n:I18NManager = I18NManager(self)
|
||||||
|
self.settings:SettingsManager = SettingsManager(self, inputs)
|
||||||
|
|
||||||
|
self.i18n.set_defaults()
|
||||||
|
self.__print_format:str = self.settings.get("print_format", None, self.__print_format)
|
||||||
|
self.__print_types:list[list[str]] = self.settings.get("print_types", None, self.__print_types)
|
||||||
|
self.__exception_format:str = self.settings.get("exception_format", None, self.__exception_format)
|
||||||
|
self.__started:bool = False
|
||||||
|
self.__working:bool = True
|
||||||
|
|
||||||
|
self.models:ModelsManager = ModelsManager(self)
|
||||||
|
self.terminal:TerminalManager = TerminalManager(self)
|
||||||
|
self.controllers:ControllersManager = ControllersManager(self)
|
||||||
|
self.routes:RoutesManager = RoutesManager(self)
|
||||||
|
self.databases:DatabasesManager = DatabasesManager(self)
|
||||||
|
self.web_servers:WebServersManager = WebServersManager(self)
|
||||||
|
|
||||||
|
if self.settings.get("autostart"):
|
||||||
|
self.start()
|
||||||
|
else:
|
||||||
|
self.terminal.start()
|
||||||
|
|
||||||
|
def start(self:Self) -> None:
|
||||||
|
if self.__started:
|
||||||
|
return
|
||||||
|
self.__started = True
|
||||||
|
self.__working = True
|
||||||
|
|
||||||
|
self.terminal.start()
|
||||||
|
self.web_servers.start()
|
||||||
|
|
||||||
|
def close(self:Self) -> None:
|
||||||
|
if not self.__started:
|
||||||
|
return
|
||||||
|
self.__started = False
|
||||||
|
self.__working = False
|
||||||
|
|
||||||
|
self.terminal.close()
|
||||||
|
self.web_servers.close()
|
||||||
|
self.databases.close()
|
||||||
|
|
||||||
|
def is_working(self:Self) -> bool:
|
||||||
|
return self.__working
|
||||||
|
|
||||||
|
def get_print_type(self:Self, _type:str) -> str:
|
||||||
|
|
||||||
|
group:list[str]
|
||||||
|
|
||||||
|
for group in self.__print_types:
|
||||||
|
if _type in group:
|
||||||
|
return group[0].upper()
|
||||||
|
return self.__print_types[0][0].upper()
|
||||||
|
|
||||||
|
def print(self:Self,
|
||||||
|
_type:str,
|
||||||
|
message:str|Sequence[str],
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
||||||
|
i:int = 0
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
date:datetime = datetime.datetime.now()
|
||||||
|
own:dict[str, Any|None] = {
|
||||||
|
"raw_type" : _type,
|
||||||
|
"type" : self.get_print_type(_type),
|
||||||
|
"i18n" : Utils.get_texts(message),
|
||||||
|
"message" : (
|
||||||
|
self.i18n.get(message, inputs) if hasattr(self, "i18n") else
|
||||||
|
Utils.string_variables(Utils.get_texts(message)[0], inputs)),
|
||||||
|
**Utils.get_dictionary(inputs),
|
||||||
|
**Utils.get_action_data(i + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in ("year", "month", "day", "hour", "minute", "second"):
|
||||||
|
|
||||||
|
k:str = "i" if key == "minute" else key[0]
|
||||||
|
|
||||||
|
own[k] = own[key] = getattr(date, key)
|
||||||
|
own[k + k] = ("00" + str(own[key]))[-2:]
|
||||||
|
|
||||||
|
own["yyyy"] = own["year"]
|
||||||
|
|
||||||
|
print(Utils.string_variables(self.__print_format, own) + (own["end"] if "end" in own else ""))
|
||||||
|
|
||||||
|
def exception(self:Self,
|
||||||
|
exception:Exception,
|
||||||
|
message:str|Sequence[str],
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
||||||
|
i:int = 0
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
lines:list[str] = extract_traceback(exception.__traceback__).format()
|
||||||
|
matches:REMatch = RE.EXCEPTION.match(lines[-1])
|
||||||
|
data:dict[str, Any|None] = {
|
||||||
|
**Utils.get_dictionary(inputs),
|
||||||
|
"lines" : "",
|
||||||
|
"exception_message" : str(exception),
|
||||||
|
"method" : matches.group(3),
|
||||||
|
"line" : matches.group(2),
|
||||||
|
"file" : matches.group(1)
|
||||||
|
}
|
||||||
|
block:str
|
||||||
|
j:int
|
||||||
|
|
||||||
|
for j, block in enumerate(trace_format_stack()[:-2] + lines):
|
||||||
|
if block:
|
||||||
|
data["lines"] += "\n " + str(j) + " - " + RE.NEW_LINE.split(block.strip())[0]
|
||||||
|
|
||||||
|
data["end"] = Utils.string_variables(self.__exception_format, data)
|
||||||
|
|
||||||
|
message and self.print("exception", message, data, i + 2)
|
||||||
38
Python/Controllers/AgentsController.py
Normal file
38
Python/Controllers/AgentsController.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self
|
||||||
|
from Abstracts.ControllerAbstract import ControllerAbstract
|
||||||
|
from Models.ResponseModel import ResponseModel
|
||||||
|
from Models.RequestModel import RequestModel
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
|
||||||
|
class AgentsController(ControllerAbstract):
|
||||||
|
|
||||||
|
def debian(self:Self, request:RequestModel, response:ResponseModel) -> None:
|
||||||
|
|
||||||
|
key:str = request.get("key")
|
||||||
|
hostnames:list[str]
|
||||||
|
domain:str|None
|
||||||
|
interfaces:list[list[int, str, bool, str, int]]
|
||||||
|
disks:list[list[str, int, int, str|None]]
|
||||||
|
iterations:int
|
||||||
|
candle_times:list[int, int]
|
||||||
|
cpu:list[float, float, float, float]
|
||||||
|
memory:list[int, int, int, int, int, float]
|
||||||
|
net_use:list[list[list[str, int, int, int, int, int, int]]]
|
||||||
|
|
||||||
|
print(Utils.json_decode(request.body))
|
||||||
|
|
||||||
|
hostnames, domain, interfaces, disks, iterations, candle_times, cpu, memory, net_use = Utils.json_decode(request.body)
|
||||||
|
|
||||||
|
print([hostnames, domain, interfaces, disks, iterations, candle_times, cpu, memory, net_use])
|
||||||
|
|
||||||
|
def windows(self:Self, request:RequestModel, response:ResponseModel) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test(self:Self, request:RequestModel, response:ResponseModel) -> None:
|
||||||
|
response.set_data({
|
||||||
|
"message": "Test successful",
|
||||||
|
"key" : request.get("key")
|
||||||
|
}, "application/json")
|
||||||
85
Python/Drivers/FilesDrivers.py
Normal file
85
Python/Drivers/FilesDrivers.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any
|
||||||
|
from os.path import exists as path_exists, dirname as directory_name, abspath as absolute_path, isfile as is_file
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
from Utils.Patterns import RE
|
||||||
|
|
||||||
|
class FilesDriver:
|
||||||
|
|
||||||
|
ROOT:str = directory_name(absolute_path(__file__))
|
||||||
|
SLASH:str = "/" if "/" in ROOT else "\\\\"
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
self.__root_paths:list[str] = ["", self.ROOT]
|
||||||
|
|
||||||
|
for _ in range(2):
|
||||||
|
self.__root_paths.append(RE.LAST_DIRECTORY.sub(r'\1', self.__root_paths[-1]))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fix_path(cls:type[Self], path:str) -> str:
|
||||||
|
return RE.SLASHES.sub(cls.SLASH, path)
|
||||||
|
|
||||||
|
def get_absolute_path(self:Self, path:str) -> str|None:
|
||||||
|
|
||||||
|
root:str
|
||||||
|
absolute:str
|
||||||
|
|
||||||
|
for root in self.__root_paths:
|
||||||
|
absolute = self.fix_path((root + '/' if root else "") + path)
|
||||||
|
if path_exists(absolute):
|
||||||
|
return absolute
|
||||||
|
return None
|
||||||
|
|
||||||
|
def load_file(self:Self, path:str, mode:str = "r") -> str|bytes|None:
|
||||||
|
|
||||||
|
absolute_path:str = self.get_absolute_path(path)
|
||||||
|
|
||||||
|
if absolute_path and is_file(absolute_path):
|
||||||
|
with open(absolute_path, mode) as file:
|
||||||
|
return file.read()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def load_json(self:Self, data:str|dict[str, Any|None]|list[Any|None], only_dictionaries:bool = True) -> list[dict[str, Any|None]|list[Any|None]]:
|
||||||
|
|
||||||
|
results:list[dict[str, Any|None]|list[Any|None]] = []
|
||||||
|
|
||||||
|
if isinstance(data, str):
|
||||||
|
|
||||||
|
json:list[Any|None]|dict[str, Any|None]|None
|
||||||
|
|
||||||
|
try:
|
||||||
|
json = Utils.json_decode(data)
|
||||||
|
except Exception as exception:
|
||||||
|
self.nucelar_monitor.exception(exception, "load_json_exception", {
|
||||||
|
"data" : data,
|
||||||
|
"length" : len(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
if json:
|
||||||
|
results.extend(self.load_json(json, only_dictionaries))
|
||||||
|
try:
|
||||||
|
results.extend(self.load_json(Utils.json_decode(self.load_file(data)), only_dictionaries))
|
||||||
|
|
||||||
|
except Exception as exception:
|
||||||
|
self.nucelar_monitor.exception(exception, "load_json_by_file_exception", {
|
||||||
|
"path" : data
|
||||||
|
})
|
||||||
|
|
||||||
|
elif isinstance(data, dict):
|
||||||
|
results.append(data)
|
||||||
|
elif isinstance(data, (list, tuple)):
|
||||||
|
if only_dictionaries:
|
||||||
|
|
||||||
|
item:Any|None
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
results.extend(self.load_json(item, only_dictionaries))
|
||||||
|
|
||||||
|
else:
|
||||||
|
results.extend(data)
|
||||||
|
|
||||||
|
return results
|
||||||
121
Python/Drivers/SQLServerDriver.py
Normal file
121
Python/Drivers/SQLServerDriver.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, Optional, Sequence
|
||||||
|
from re import Match as REMatch
|
||||||
|
from Assets.pyodbc import Connection as Connection, connect as connect, Cursor as Cursor
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Abstracts.DatabaseAbstract import DatabaseAbstract
|
||||||
|
from Models.QueryResponseModel import QueryResponseModel
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
from Utils.Patterns import RE
|
||||||
|
|
||||||
|
class SQLServerDriver(DatabaseAbstract):
|
||||||
|
|
||||||
|
def __init__(self:Self,
|
||||||
|
nucelar_monitor:NucelarMonitorInterface,
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
||||||
|
) -> None:
|
||||||
|
super().__init__(nucelar_monitor, inputs)
|
||||||
|
self.__connection:Connection|None = None
|
||||||
|
self.__string_connection:str = Utils.string_variables(self.nucelar_monitor.settings.get((
|
||||||
|
"odbc_string_connection", "sql_string_connection", "string_connection"
|
||||||
|
), inputs, "DRIVER={driver};SERVER={host},{port};UID={user};PWD={password};DATABASE={database}"), {
|
||||||
|
"driver" : self.nucelar_monitor.settings.get("sql_driver", inputs, "{ODBC Driver 17 for SQL Server}"),
|
||||||
|
"host" : self.nucelar_monitor.settings.get(("sql_host", "odbc_host"), inputs, "localhost"),
|
||||||
|
"port" : self.nucelar_monitor.settings.get(("sql_port", "odbc_port"), inputs, 1433),
|
||||||
|
"user" : self.nucelar_monitor.settings.get(("sql_user", "odbc_user"), inputs, "sa"),
|
||||||
|
"password" : self.nucelar_monitor.settings.get(("sql_password", "odbc_password"), inputs, "password"),
|
||||||
|
"database" : self.nucelar_monitor.settings.get(("sql_database", "odbc_database"), inputs, "NucelarMonitor")
|
||||||
|
})
|
||||||
|
|
||||||
|
def close(self:Self) -> bool:
|
||||||
|
if self.__connection is not None:
|
||||||
|
try:
|
||||||
|
self.__connection.close()
|
||||||
|
return True
|
||||||
|
except Exception as exception:
|
||||||
|
self.nucelar_monitor.exception(exception, "sql_server_close_exception", {
|
||||||
|
"string_connection" : self.__string_connection
|
||||||
|
})
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __autoclose(self:Self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def format_query(self:Self,
|
||||||
|
query:str,
|
||||||
|
parameters:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
||||||
|
) -> tuple[str, list[str]]:
|
||||||
|
|
||||||
|
variables:list[str] = []
|
||||||
|
|
||||||
|
def callback(matches:REMatch) -> str:
|
||||||
|
|
||||||
|
key:str = matches.group(1)
|
||||||
|
|
||||||
|
if key:
|
||||||
|
return (
|
||||||
|
"'" + str(parameters[key]).replace("'","''") + "'" if isinstance(parameters[key], str) else
|
||||||
|
str(parameters[key]) if key in parameters else matches.group(0))
|
||||||
|
|
||||||
|
key = matches.group(2)
|
||||||
|
|
||||||
|
variables.append(key)
|
||||||
|
|
||||||
|
return matches.group(0)
|
||||||
|
|
||||||
|
query = RE.ODBC_STRING_VARIABLE.sub(callback, query)
|
||||||
|
|
||||||
|
return (
|
||||||
|
"".join("declare @" + variable + " varchar(max)\n" for variable in variables) +
|
||||||
|
query +
|
||||||
|
"\nselect " + ", ".join("@" + variable for variable in variables)
|
||||||
|
) if len(variables) else query, variables
|
||||||
|
|
||||||
|
def query(self:Self,
|
||||||
|
query:str,
|
||||||
|
parameters:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
||||||
|
) -> QueryResponseModel:
|
||||||
|
|
||||||
|
response:QueryResponseModel = QueryResponseModel()
|
||||||
|
cursor:Cursor|None = None
|
||||||
|
variables:list[str] = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
if self.__connection is None:
|
||||||
|
self.__connection = connect(
|
||||||
|
self.__string_connection,
|
||||||
|
autocommit = True
|
||||||
|
)
|
||||||
|
cursor = self.__connection.cursor()
|
||||||
|
query, variables = self.format_query(query, parameters)
|
||||||
|
|
||||||
|
cursor.execute(query)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
if cursor.description is not None:
|
||||||
|
response.columns.append([column[0] for column in cursor.description])
|
||||||
|
response.tables.append([tuple(row) for row in cursor.fetchall()])
|
||||||
|
|
||||||
|
if not cursor.nextset():
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as exception:
|
||||||
|
self.nucelar_monitor.exception(exception, "connection_exception", {
|
||||||
|
"string_connection" : self.__string_connection
|
||||||
|
})
|
||||||
|
finally:
|
||||||
|
if cursor is not None:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
if len(variables) and len(response.tables):
|
||||||
|
for i, variable in enumerate(variables):
|
||||||
|
response.variables[variable] = response.tables[-1][0][i]
|
||||||
|
response.tables = response.tables[:-1]
|
||||||
|
response.columns = response.columns[:-1]
|
||||||
|
|
||||||
|
return response
|
||||||
165
Python/Drivers/WebServerDriver.py
Normal file
165
Python/Drivers/WebServerDriver.py
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Any, Self, Optional, Sequence
|
||||||
|
from threading import Thread
|
||||||
|
from socket import socket as Socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR, SHUT_RDWR
|
||||||
|
from requests import get as get
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Abstracts.WebServerAbstract import WebServerAbstract
|
||||||
|
from Models.ResponseModel import ResponseModel
|
||||||
|
from Models.RequestModel import RequestModel
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
|
||||||
|
class WebServerDriver(WebServerAbstract):
|
||||||
|
|
||||||
|
def __init__(self:Self,
|
||||||
|
nucelar_monitor:NucelarMonitorInterface,
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
||||||
|
) -> None:
|
||||||
|
super().__init__(nucelar_monitor, inputs)
|
||||||
|
self.__server:Socket
|
||||||
|
self.__buffer_size:int = self.nucelar_monitor.settings.get("cache_size", inputs, 4096)
|
||||||
|
self.__started:bool = False
|
||||||
|
self.__thread:Thread|None = None
|
||||||
|
self.__clients:list[Socket] = []
|
||||||
|
|
||||||
|
def start(self:Self) -> bool:
|
||||||
|
if self.__started:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.__started = True
|
||||||
|
|
||||||
|
self.__server = Socket(AF_INET, SOCK_STREAM)
|
||||||
|
self.__server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.__server.bind((self._host, self._port))
|
||||||
|
self.__server.listen()
|
||||||
|
self.__thread = Thread(target = self.__listen)
|
||||||
|
self.__thread.start()
|
||||||
|
return True
|
||||||
|
except Exception as exception:
|
||||||
|
self.nucelar_monitor.exception(exception, "nucelar_monitor_web_socket_driver_start_exception", {
|
||||||
|
"host" : self._host,
|
||||||
|
"port" : self._port,
|
||||||
|
})
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self:Self) -> bool:
|
||||||
|
if not self.__started:
|
||||||
|
return False
|
||||||
|
self.__started = False
|
||||||
|
|
||||||
|
i:int
|
||||||
|
|
||||||
|
for i in range(len(self.__clients)):
|
||||||
|
self.__close_client(i, True)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def stop(self:Self) -> bool:
|
||||||
|
return self.close()
|
||||||
|
|
||||||
|
def __close_client(self:Self, i:int, send_close:bool = False) -> None:
|
||||||
|
if self.__clients[i] is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
client:Socket = self.__clients[i]
|
||||||
|
|
||||||
|
self.__clients[i] = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
if client:
|
||||||
|
client.shutdown(SHUT_RDWR)
|
||||||
|
client.close()
|
||||||
|
except Exception as _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __listen_client(self:Self, client:Socket, address:str, port:int) -> None:
|
||||||
|
|
||||||
|
data:bytes = b""
|
||||||
|
route:str = ""
|
||||||
|
method:str = "UNKN"
|
||||||
|
response:ResponseModel = ResponseModel(self._encoder, {}, self._code, self._message)
|
||||||
|
i:int = 0
|
||||||
|
|
||||||
|
while i < len(self.__clients):
|
||||||
|
if self.__clients[i] is None:
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if i == len(self.__clients):
|
||||||
|
self.__clients.append(client)
|
||||||
|
else:
|
||||||
|
self.__clients[i] = client
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
request:RequestModel
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
buffer:bytes = client.recv(self.__buffer_size)
|
||||||
|
|
||||||
|
if not buffer:
|
||||||
|
break
|
||||||
|
data += buffer
|
||||||
|
if len(buffer) != self.__buffer_size:
|
||||||
|
break
|
||||||
|
|
||||||
|
if data:
|
||||||
|
|
||||||
|
request = RequestModel(data, self._index_files, self._encoder)
|
||||||
|
self.nucelar_monitor.routes.get(request, response)
|
||||||
|
|
||||||
|
client.sendall(Utils.string_variables(self._header_response, {
|
||||||
|
"protocol" : self._protocol,
|
||||||
|
"version" : self._version,
|
||||||
|
**response.get_parameters()
|
||||||
|
}).encode(self._encoder) + response.body)
|
||||||
|
|
||||||
|
except Exception as exception:
|
||||||
|
self.nucelar_monitor.exception(exception, "server_client_exception", {
|
||||||
|
"host" : self._host,
|
||||||
|
"port" : self._port,
|
||||||
|
"client_address" : address,
|
||||||
|
"client_port" : port,
|
||||||
|
"length" : len(data),
|
||||||
|
"method" : method,
|
||||||
|
"route" : route,
|
||||||
|
"response_length" : len(response.body)
|
||||||
|
})
|
||||||
|
finally:
|
||||||
|
self.__close_client(i)
|
||||||
|
|
||||||
|
def __listen(self:Self) -> None:
|
||||||
|
while self.nucelar_monitor.is_working():
|
||||||
|
try:
|
||||||
|
|
||||||
|
client:Socket
|
||||||
|
address:str
|
||||||
|
port:int
|
||||||
|
|
||||||
|
client, (address, port) = self.__server.accept()
|
||||||
|
|
||||||
|
Thread(
|
||||||
|
target = self.__listen_client,
|
||||||
|
args = (client, address, port)
|
||||||
|
).start()
|
||||||
|
|
||||||
|
except Exception as exception:
|
||||||
|
self.nucelar_monitor.exception(exception, "server_listen_exception", {
|
||||||
|
"host" : self._host,
|
||||||
|
"port" : self._port,
|
||||||
|
})
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.__server.close()
|
||||||
|
except Exception as exception:
|
||||||
|
self.nucelar_monitor.exception(exception, "nucelar_monitor_web_socket_driver_close_exception", {
|
||||||
|
"host" : self._host,
|
||||||
|
"port" : self._port,
|
||||||
|
})
|
||||||
12
Python/Interfaces/Abstracts/ControllerAbstractInterface.py
Normal file
12
Python/Interfaces/Abstracts/ControllerAbstractInterface.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Callable
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from Models.RequestModel import RequestModel
|
||||||
|
from Models.ResponseModel import ResponseModel
|
||||||
|
|
||||||
|
class ControllerAbstractInterface(ABC):
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_action(self:Self, action:str) -> Callable[[RequestModel, ResponseModel], None]|None:pass
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
class WebSocketAbstractInterface(ABC):pass
|
||||||
53
Python/Interfaces/Application/NucelarMonitorInterface.py
Normal file
53
Python/Interfaces/Application/NucelarMonitorInterface.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Self, Any, Optional, Sequence
|
||||||
|
from Interfaces.Drivers.FilesDriversInterface import FilesDriverInterface
|
||||||
|
from Interfaces.Managers.I18NManagersInterface import I18NManagerInterface
|
||||||
|
from Interfaces.Managers.SettingsManagersInterface import SettingsManagerInterface
|
||||||
|
from Interfaces.Managers.TerminalManagerInterface import TerminalManagerInterface
|
||||||
|
from Interfaces.Managers.ModelsManagerInterface import ModelsManagerInterface
|
||||||
|
from Interfaces.Managers.ControllersManagerInterface import ControllersManagerInterface
|
||||||
|
from Interfaces.Managers.RoutesManagerInterface import RoutesManagerInterface
|
||||||
|
from Interfaces.Abstracts.WebSocketAbstractInterface import WebSocketAbstractInterface
|
||||||
|
|
||||||
|
class NucelarMonitorInterface(ABC):
|
||||||
|
|
||||||
|
def __init__(self:Self, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
|
||||||
|
self.files:FilesDriverInterface = None
|
||||||
|
self.i18n:I18NManagerInterface = None
|
||||||
|
self.settings:SettingsManagerInterface = None
|
||||||
|
self.terminal:TerminalManagerInterface = None
|
||||||
|
self.models:ModelsManagerInterface = None
|
||||||
|
self.controllers:ControllersManagerInterface = None
|
||||||
|
self.routes:RoutesManagerInterface = None
|
||||||
|
self.web_socket:WebSocketAbstractInterface = None
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def start(self:Self) -> bool:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def close(self:Self) -> bool:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_print_type(self:Self, _type:str) -> str:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def print(self:Self,
|
||||||
|
_type:str,
|
||||||
|
message:str|Sequence[str],
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
||||||
|
i:int = 0
|
||||||
|
) -> None:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def exception(self:Self,
|
||||||
|
exception:Exception,
|
||||||
|
message:str|Sequence[str],
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
||||||
|
i:int = 0
|
||||||
|
) -> None:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def is_working(self:Self) -> bool:pass
|
||||||
22
Python/Interfaces/Drivers/FilesDriversInterface.py
Normal file
22
Python/Interfaces/Drivers/FilesDriversInterface.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
class FilesDriverInterface(ABC):
|
||||||
|
|
||||||
|
ROOT:str = None
|
||||||
|
SLASH:str = None
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_absolute_path(self:Self, path:str) -> str|None:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def load_file(self:Self, path:str, mode:str = "r") -> str|bytes|None:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def load_json(self:Self,
|
||||||
|
data:str|dict[str, Any|None]|list[Any|None],
|
||||||
|
only_dictionaries:bool = True
|
||||||
|
) -> list[dict[str, Any|None]|list[Any|None]]:pass
|
||||||
14
Python/Interfaces/Managers/ControllersManagerInterface.py
Normal file
14
Python/Interfaces/Managers/ControllersManagerInterface.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Any, Self
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from Interfaces.Abstracts.ControllerAbstractInterface import ControllerAbstractInterface
|
||||||
|
|
||||||
|
class ControllersManagerInterface(ABC):
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get(self:Self, key:str) -> ControllerAbstractInterface|None:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass
|
||||||
19
Python/Interfaces/Managers/I18NManagersInterface.py
Normal file
19
Python/Interfaces/Managers/I18NManagersInterface.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Any, Self, Optional, Sequence
|
||||||
|
|
||||||
|
class I18NManagerInterface(ABC):
|
||||||
|
|
||||||
|
DEFAULT_SENTENCES:dict[str, dict[str, str]] = None
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get(self:Self,
|
||||||
|
keys:str|Sequence[str],
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
||||||
|
default:Any = None
|
||||||
|
) -> dict[str, Any|None]:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass
|
||||||
19
Python/Interfaces/Managers/ModelsManagerInterface.py
Normal file
19
Python/Interfaces/Managers/ModelsManagerInterface.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, TypeVar
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
class ModelsManagerInterface(ABC):
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get(self:Self, Type:type[T], model:str|type[T]) -> type[T]|None:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def add(self:Self,
|
||||||
|
Type:type[T],
|
||||||
|
inputs:dict[str, Any]|list[Any|None]|tuple[Any|None, ...]|str,
|
||||||
|
overwrite:bool = False
|
||||||
|
) -> None:pass
|
||||||
15
Python/Interfaces/Managers/RoutesManagerInterface.py
Normal file
15
Python/Interfaces/Managers/RoutesManagerInterface.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from Models.RequestModel import RequestModel
|
||||||
|
from Models.ResponseModel import ResponseModel
|
||||||
|
|
||||||
|
class RoutesManagerInterface(ABC):
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get(self:Self, request:RequestModel, response:ResponseModel) -> None:pass
|
||||||
22
Python/Interfaces/Managers/SettingsManagersInterface.py
Normal file
22
Python/Interfaces/Managers/SettingsManagersInterface.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Any, Self, Optional, Sequence
|
||||||
|
|
||||||
|
class SettingsManagerInterface(ABC):
|
||||||
|
|
||||||
|
DEFAULT_SETTINGS:dict[str, Any|None] = None
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get(self:Self,
|
||||||
|
strings:str|Sequence[str],
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
||||||
|
language:Optional[str] = None
|
||||||
|
) -> str:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def add_secrets(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass
|
||||||
10
Python/Interfaces/Managers/TerminalManagerInterface.py
Normal file
10
Python/Interfaces/Managers/TerminalManagerInterface.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
class TerminalManagerInterface(ABC):
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass
|
||||||
41
Python/Managers/ControllersManager.py
Normal file
41
Python/Managers/ControllersManager.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Any, Self
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Abstracts.ControllerAbstract import ControllerAbstract
|
||||||
|
|
||||||
|
class ControllersManager:
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
self.__controllers:dict[str, ControllerAbstract] = {}
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"default_controllers_files", "controllers_files",
|
||||||
|
"default_controllers", "controllers"
|
||||||
|
):
|
||||||
|
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
|
||||||
|
|
||||||
|
def get(self:Self, key:str) -> ControllerAbstract|None:
|
||||||
|
return self.__controllers.get(key, None)
|
||||||
|
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
|
||||||
|
|
||||||
|
subinputs:dict[str, str|ControllerAbstract]
|
||||||
|
|
||||||
|
for subinputs in self.nucelar_monitor.files.load_json(inputs):
|
||||||
|
|
||||||
|
key:str
|
||||||
|
controller:ControllerAbstract|str
|
||||||
|
|
||||||
|
for key, controller in subinputs.items():
|
||||||
|
if isinstance(controller, str):
|
||||||
|
controller = self.nucelar_monitor.models.get(ControllerAbstract, controller)(self.nucelar_monitor)
|
||||||
|
if controller is not None and isinstance(controller, ControllerAbstract) and (
|
||||||
|
overwrite or key not in self.__controllers
|
||||||
|
):
|
||||||
|
self.__controllers[key] = controller
|
||||||
53
Python/Managers/DatabasesManager.py
Normal file
53
Python/Managers/DatabasesManager.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Abstracts.DatabaseAbstract import DatabaseAbstract
|
||||||
|
|
||||||
|
class DatabasesManager:
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
self.__databases:dict[str, DatabaseAbstract] = {}
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"default_databases_files", "databases_files",
|
||||||
|
"default_databases", "databases"
|
||||||
|
):
|
||||||
|
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
|
||||||
|
|
||||||
|
def get(self:Self, key:str) -> type[DatabaseAbstract]|None:
|
||||||
|
return self.__databases.get(key, None)
|
||||||
|
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
|
||||||
|
|
||||||
|
subinputs:dict[str, type[DatabaseAbstract]|dict[str, Any|None]]
|
||||||
|
|
||||||
|
for subinputs in self.nucelar_monitor.files.load_json(inputs):
|
||||||
|
|
||||||
|
key:str
|
||||||
|
database:DatabaseAbstract|dict[str, Any|None]
|
||||||
|
|
||||||
|
for key, database in subinputs.items():
|
||||||
|
if isinstance(database, dict):
|
||||||
|
database = self.nucelar_monitor.models.get(
|
||||||
|
DatabaseAbstract,
|
||||||
|
self.nucelar_monitor.settings.get(("database_type", "type"), database, "sql_server")
|
||||||
|
)(self.nucelar_monitor, database)
|
||||||
|
if database is None:
|
||||||
|
continue
|
||||||
|
if database is not None and isinstance(database, DatabaseAbstract) and (
|
||||||
|
overwrite or key not in self.__databases
|
||||||
|
):
|
||||||
|
self.__databases[key] = database
|
||||||
|
|
||||||
|
def close(self:Self) -> None:
|
||||||
|
|
||||||
|
database:type[DatabaseAbstract]
|
||||||
|
|
||||||
|
for database in self.__databases.values():
|
||||||
|
database.close()
|
||||||
41
Python/Managers/DispatchersManager.py
Normal file
41
Python/Managers/DispatchersManager.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Any, Self
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Abstracts.DispatcherAbstract import DispatcherAbstract
|
||||||
|
|
||||||
|
class DispatchersManager:
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
self.__dispatcher:dict[str, DispatcherAbstract] = {}
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"default_dispatcher_files", "dispatcher_files",
|
||||||
|
"default_dispatcher", "dispatcher"
|
||||||
|
):
|
||||||
|
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
|
||||||
|
|
||||||
|
def get(self:Self, key:str) -> DispatcherAbstract|None:
|
||||||
|
return self.__dispatcher.get(key, None)
|
||||||
|
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
|
||||||
|
|
||||||
|
subinputs:dict[str, str|DispatcherAbstract]
|
||||||
|
|
||||||
|
for subinputs in self.nucelar_monitor.files.load_json(inputs):
|
||||||
|
|
||||||
|
key:str
|
||||||
|
controller:DispatcherAbstract|str
|
||||||
|
|
||||||
|
for key, controller in subinputs.items():
|
||||||
|
if isinstance(controller, str):
|
||||||
|
controller = self.nucelar_monitor.models.get(DispatcherAbstract, controller)(self.nucelar_monitor)
|
||||||
|
if controller is not None and isinstance(controller, DispatcherAbstract) and (
|
||||||
|
overwrite or key not in self.__dispatcher
|
||||||
|
):
|
||||||
|
self.__dispatcher[key] = controller
|
||||||
87
Python/Managers/I18NManager.py
Normal file
87
Python/Managers/I18NManager.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, Optional, Sequence
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
|
||||||
|
class I18NManager:
|
||||||
|
|
||||||
|
DEFAULT_SENTENCES:dict[str, dict[str, str]] = {
|
||||||
|
"english" : {}
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_defaults(self:Self) -> None:
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
self.__language = self.nucelar_monitor.settings.get("language", None, "english")
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"default_i18n_files", "i18n_files",
|
||||||
|
"default_i18n", "i18n"
|
||||||
|
):
|
||||||
|
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
self.__sentences:dict[str, dict[str, str]] = self.DEFAULT_SENTENCES
|
||||||
|
self.__language:str = "english"
|
||||||
|
|
||||||
|
def __get(self:Self,
|
||||||
|
strings:str|Sequence[str],
|
||||||
|
language:Optional[str] = None
|
||||||
|
) -> str|Sequence[str]:
|
||||||
|
|
||||||
|
keys:str = Utils.get_keys(strings)
|
||||||
|
|
||||||
|
if len(keys):
|
||||||
|
|
||||||
|
language:str
|
||||||
|
done:list[str] = []
|
||||||
|
|
||||||
|
for language in [language, self.__language] + list(self.__sentences.keys()):
|
||||||
|
if language not in done and language in self.__sentences:
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
done.append(language)
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
if key in self.__sentences[language]:
|
||||||
|
return self.__sentences[language][key]
|
||||||
|
return Utils.get_texts(strings)[0]
|
||||||
|
|
||||||
|
def get(self:Self,
|
||||||
|
strings:str|Sequence[str],
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
||||||
|
language:Optional[str] = None
|
||||||
|
) -> str:
|
||||||
|
|
||||||
|
string:str|Sequence[str] = self.__get(strings, language)
|
||||||
|
|
||||||
|
return Utils.string_variables((
|
||||||
|
"".join(string) if isinstance(string, Sequence) else
|
||||||
|
string), inputs)
|
||||||
|
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
|
||||||
|
|
||||||
|
block:dict[str, dict[str, str|Sequence[str]]]
|
||||||
|
|
||||||
|
for block in self.nucelar_monitor.files.load_json(inputs):
|
||||||
|
|
||||||
|
language:str
|
||||||
|
sentences:dict[str, str|Sequence[str]]
|
||||||
|
|
||||||
|
for language, sentences in block.items():
|
||||||
|
if isinstance(sentences, dict):
|
||||||
|
|
||||||
|
key:str
|
||||||
|
sentence:str|Sequence[str]
|
||||||
|
|
||||||
|
if language not in self.__sentences:
|
||||||
|
self.__sentences[language] = {}
|
||||||
|
|
||||||
|
for key, sentence in sentences.items():
|
||||||
|
if overwrite or key not in self.__sentences[language]:
|
||||||
|
self.__sentences[language][key] = sentence
|
||||||
41
Python/Managers/ModelsManager.py
Normal file
41
Python/Managers/ModelsManager.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, TypeVar
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
class ModelsManager:
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
self.__models:dict[str, Any] = {}
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"default_models_files", "models_files",
|
||||||
|
"default_models", "models"
|
||||||
|
):
|
||||||
|
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
|
||||||
|
|
||||||
|
def get(self:Self, Type:type[T], key:str) -> type[T]|None:
|
||||||
|
return self.__models.get(key, None) if key in self.__models and issubclass(self.__models[key], Type) else None
|
||||||
|
|
||||||
|
def add(self:Self,
|
||||||
|
inputs:dict[str, Any]|list[Any|None]|tuple[Any|None, ...]|str,
|
||||||
|
overwrite:bool = False
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
subinputs:dict[str, Any]
|
||||||
|
|
||||||
|
for subinputs in self.nucelar_monitor.files.load_json(inputs):
|
||||||
|
|
||||||
|
key:str
|
||||||
|
Model:type[T]
|
||||||
|
|
||||||
|
for key, Model in subinputs.items():
|
||||||
|
if overwrite or key not in self.__models:
|
||||||
|
self.__models[key] = Model
|
||||||
102
Python/Managers/RoutesManager.py
Normal file
102
Python/Managers/RoutesManager.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, Sequence
|
||||||
|
from re import Match as REMatch
|
||||||
|
from mimetypes import guess_type as get_mime_by_extension
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Models.RouteModel import RouteModel
|
||||||
|
from Models.ResponseModel import ResponseModel
|
||||||
|
from Models.RequestModel import RequestModel
|
||||||
|
|
||||||
|
class RoutesManager:
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
self.__routes:list[RouteModel] = []
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"default_routes_files", "routes_files",
|
||||||
|
"default_routes", "routes"
|
||||||
|
):
|
||||||
|
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
|
||||||
|
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
|
||||||
|
|
||||||
|
route:str|Sequence[str|list[str]|RouteModel]
|
||||||
|
|
||||||
|
for route in self.nucelar_monitor.files.load_json(inputs, False):
|
||||||
|
|
||||||
|
item:RouteModel = route if isinstance(route, RouteModel) else RouteModel(self.nucelar_monitor, route)
|
||||||
|
|
||||||
|
if item.error:
|
||||||
|
self.add(route, overwrite)
|
||||||
|
continue
|
||||||
|
|
||||||
|
exists:RouteModel
|
||||||
|
done:bool = False
|
||||||
|
|
||||||
|
for exists in self.__routes:
|
||||||
|
if exists.method == item.method and exists.route == item.route:
|
||||||
|
if overwrite:
|
||||||
|
done = True
|
||||||
|
self.__routes.remove(exists)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not done:
|
||||||
|
self.__routes.append(item)
|
||||||
|
|
||||||
|
def get(self:Self, request:RequestModel, response:ResponseModel) -> None:
|
||||||
|
|
||||||
|
route:RouteModel
|
||||||
|
|
||||||
|
for route in self.__routes:
|
||||||
|
if route.method != request.method:
|
||||||
|
continue
|
||||||
|
|
||||||
|
matches:REMatch[str]|None = route.route.match(request.request)
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
continue
|
||||||
|
|
||||||
|
request.set_uri_variables(route.variables, matches)
|
||||||
|
|
||||||
|
if route.action:
|
||||||
|
try:
|
||||||
|
route.action(request, response)
|
||||||
|
except Exception as exception:
|
||||||
|
response.build({
|
||||||
|
"ok" : False,
|
||||||
|
"code" : 500,
|
||||||
|
"message" : "Internal server error",
|
||||||
|
"error" : str(exception)
|
||||||
|
}, "application/json", 500, "Internal server error")
|
||||||
|
self.nucelar_monitor.exception(exception, "nucelar_monitor_routes_manager_get_exception", {
|
||||||
|
"request" : request.request,
|
||||||
|
"method" : request.method
|
||||||
|
})
|
||||||
|
return
|
||||||
|
if route.path:
|
||||||
|
|
||||||
|
index:str
|
||||||
|
|
||||||
|
for index in request.index_files:
|
||||||
|
|
||||||
|
full_path = route.path + request.request + ("" if index == "" else "/" + index)
|
||||||
|
|
||||||
|
if (response_data := self.nucelar_monitor.files.load_file(full_path, "rb")) is not None:
|
||||||
|
response.set_data(
|
||||||
|
response_data,
|
||||||
|
get_mime_by_extension(full_path)[0] or "application/octet-stream"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
response.build({
|
||||||
|
"ok" : False,
|
||||||
|
"code" : 404,
|
||||||
|
"message" : "Not found"
|
||||||
|
}, "application/json", 404, "Not found")
|
||||||
85
Python/Managers/SettingsManager.py
Normal file
85
Python/Managers/SettingsManager.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, Optional, Sequence
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
|
||||||
|
class SettingsManager:
|
||||||
|
|
||||||
|
DEFAULT_SETTINGS:dict[str, Any|None] = {
|
||||||
|
"autostart" : True,
|
||||||
|
"print_format" : "[{type}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{line}]{file}({method}): {message}",
|
||||||
|
"exception_format" : " '[{line}]{file}({method})'{lines}\n\n{exception_message}",
|
||||||
|
"print_types" : [
|
||||||
|
["unkn", "unknown"],
|
||||||
|
["info", "information"],
|
||||||
|
["warn", "warning"],
|
||||||
|
["erro", "error", "wrong", "failure", "fail", "no"],
|
||||||
|
["exce", "exception", "except"],
|
||||||
|
[" ok ", "ok", "success", "succeed", "yes"],
|
||||||
|
["test", "debug"]
|
||||||
|
],
|
||||||
|
"default_settings_files" : "/JSON/NucelarMonitor.settings.json",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self:Self,
|
||||||
|
nucelar_monitor:NucelarMonitorInterface,
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
||||||
|
) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
self.__inputs:dict[str, Any|None] = Utils.get_dictionary(inputs)
|
||||||
|
self.__secrets:dict[str, Any|None] = {}
|
||||||
|
self.__settings:dict[str, Any|None] = {}
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"default_settings_files", "settings_files",
|
||||||
|
"default_settings", "settings"
|
||||||
|
):
|
||||||
|
self.add(self.get(key, None, []), True)
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"default_secrets_files", "secrets_files",
|
||||||
|
"default_secrets", "secrets"
|
||||||
|
):
|
||||||
|
self.add_secrets(self.get(key, None, []), True)
|
||||||
|
|
||||||
|
def get(self:Self,
|
||||||
|
keys:str|Sequence[str],
|
||||||
|
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
||||||
|
default:Any = None
|
||||||
|
) -> dict[str, Any|None]:
|
||||||
|
return Utils.get_value(keys, (
|
||||||
|
inputs, self.__inputs, self.__secrets, self.__settings, self.DEFAULT_SETTINGS
|
||||||
|
), default)
|
||||||
|
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
|
||||||
|
|
||||||
|
subinputs:dict[str, Any|None]
|
||||||
|
|
||||||
|
print(inputs)
|
||||||
|
|
||||||
|
for subinputs in self.nucelar_monitor.files.load_json(inputs):
|
||||||
|
|
||||||
|
key:str
|
||||||
|
value:Any|None
|
||||||
|
|
||||||
|
for key, value in subinputs.items():
|
||||||
|
if overwrite or key not in self.__settings:
|
||||||
|
self.__settings[key] = value
|
||||||
|
|
||||||
|
def add_secrets(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
|
||||||
|
|
||||||
|
subinputs:dict[str, Any|None]
|
||||||
|
|
||||||
|
for subinputs in self.nucelar_monitor.files.load_json(inputs):
|
||||||
|
|
||||||
|
key:str
|
||||||
|
value:Any|None
|
||||||
|
|
||||||
|
for key, value in subinputs.items():
|
||||||
|
if overwrite or key not in self.__secrets:
|
||||||
|
self.__secrets[key] = value
|
||||||
89
Python/Managers/TerminalManager.py
Normal file
89
Python/Managers/TerminalManager.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, Callable
|
||||||
|
from threading import Thread
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
|
||||||
|
class TerminalManager:
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
self.__commands:list[tuple[list[str], Callable[[dict[str, Any|None], list[Any|None]], None]]] = [
|
||||||
|
[["close", "exit", "quit", "bye"], self.__close_command]
|
||||||
|
]
|
||||||
|
self.__thread:Thread|None
|
||||||
|
self.__started:bool = False
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"default_commands_files", "commands_files",
|
||||||
|
"default_commands", "commands"
|
||||||
|
):
|
||||||
|
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
|
||||||
|
|
||||||
|
def start(self:Self) -> None:
|
||||||
|
if self.__started:
|
||||||
|
return
|
||||||
|
self.__started = True
|
||||||
|
|
||||||
|
self.__thread = Thread(target = self.__listener)
|
||||||
|
self.__thread.start()
|
||||||
|
|
||||||
|
def close(self:Self) -> None:
|
||||||
|
if not self.__started:
|
||||||
|
return
|
||||||
|
self.__started = False
|
||||||
|
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
|
||||||
|
|
||||||
|
alternatives:list[str]
|
||||||
|
callback:Callable[[dict[str, Any|None], list[Any|None]], None]|str
|
||||||
|
|
||||||
|
for alternatives, callback in self.nucelar_monitor.files.load_json(inputs, False):
|
||||||
|
|
||||||
|
done:bool = False
|
||||||
|
command:tuple[list[str], Callable[[dict[str, Any|None], list[Any|None]], None]]
|
||||||
|
i:int
|
||||||
|
|
||||||
|
if isinstance(callback, str):
|
||||||
|
callback = Utils.get_function(callback, [self.nucelar_monitor])
|
||||||
|
|
||||||
|
for i, command in enumerate(self.__commands):
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
for key in alternatives:
|
||||||
|
if key in command[0]:
|
||||||
|
if overwrite:
|
||||||
|
self.__commands[i] = (alternatives, callback)
|
||||||
|
done = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not done:
|
||||||
|
self.__commands.append((alternatives, callback))
|
||||||
|
|
||||||
|
def __listener(self:Self) -> None:
|
||||||
|
while self.nucelar_monitor.is_working():
|
||||||
|
try:
|
||||||
|
|
||||||
|
parameters:dict[str, Any|None] = {}
|
||||||
|
arguments:list[Any|None] = []
|
||||||
|
command:str = input().strip().lower()
|
||||||
|
|
||||||
|
if command:
|
||||||
|
for commands, callback in self.__commands:
|
||||||
|
if command in commands:
|
||||||
|
callback(parameters, *arguments)
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as exception:
|
||||||
|
self.nucelar_monitor.exception(exception, "command_listener_exception", {
|
||||||
|
"command" : command
|
||||||
|
})
|
||||||
|
|
||||||
|
def __close_command(self:Self, inputs:dict[str, Any|None], *arguments:list[Any|None]) -> None:
|
||||||
|
self.nucelar_monitor.close()
|
||||||
57
Python/Managers/WebServersManager.py
Normal file
57
Python/Managers/WebServersManager.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Abstracts.WebServerAbstract import WebServerAbstract
|
||||||
|
|
||||||
|
class WebServersManager:
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface) -> None:
|
||||||
|
self.nucelar_monitor:NucelarMonitorInterface = nucelar_monitor
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
self.__web_servers:dict[str, WebServerAbstract] = {}
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"default_web_servers_files", "web_servers_files",
|
||||||
|
"default_web_servers", "web_servers"
|
||||||
|
):
|
||||||
|
self.add(self.nucelar_monitor.settings.get(key, None, []), True)
|
||||||
|
|
||||||
|
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
|
||||||
|
|
||||||
|
subinputs:dict[str, WebServerAbstract|dict[str, Any|None]]
|
||||||
|
|
||||||
|
for subinputs in self.nucelar_monitor.files.load_json(inputs):
|
||||||
|
|
||||||
|
key:str
|
||||||
|
web_server:WebServerAbstract|dict[str, Any|None]
|
||||||
|
|
||||||
|
for key, web_server in subinputs.items():
|
||||||
|
if isinstance(web_server, dict):
|
||||||
|
web_server = self.nucelar_monitor.models.get(
|
||||||
|
WebServerAbstract,
|
||||||
|
self.nucelar_monitor.settings.get(("web_server_type", "type"), web_server, "web_server")
|
||||||
|
)(self.nucelar_monitor, web_server)
|
||||||
|
if web_server is not None and isinstance(web_server, WebServerAbstract) and (
|
||||||
|
overwrite or key not in self.__web_servers
|
||||||
|
):
|
||||||
|
self.__web_servers[key] = web_server
|
||||||
|
|
||||||
|
def start(self:Self) -> None:
|
||||||
|
|
||||||
|
web_server:WebServerAbstract
|
||||||
|
|
||||||
|
for web_server in self.__web_servers.values():
|
||||||
|
if hasattr(web_server, "start") and callable(getattr(web_server, "start")):
|
||||||
|
web_server.start()
|
||||||
|
|
||||||
|
def close(self:Self) -> None:
|
||||||
|
|
||||||
|
web_server:WebServerAbstract
|
||||||
|
|
||||||
|
for web_server in self.__web_servers.values():
|
||||||
|
if hasattr(web_server, "close") and callable(getattr(web_server, "close")):
|
||||||
|
web_server.close()
|
||||||
11
Python/Models/QueryResponseModel.py
Normal file
11
Python/Models/QueryResponseModel.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any
|
||||||
|
|
||||||
|
class QueryResponseModel:
|
||||||
|
|
||||||
|
def __init__(self:Self) -> None:
|
||||||
|
self.tables:list[list[list[Any|None]]] = []
|
||||||
|
self.columns:list[list[str]] = []
|
||||||
|
self.variables:dict[str, Any|None] = {}
|
||||||
74
Python/Models/RequestModel.py
Normal file
74
Python/Models/RequestModel.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, Optional, Sequence
|
||||||
|
from re import Match as REMatch
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
from Utils.Patterns import RE
|
||||||
|
|
||||||
|
class RequestModel:
|
||||||
|
|
||||||
|
def __init__(self:Self, data:bytes, index_files:tuple[str, ...], encoder:str = "utf-8") -> None:
|
||||||
|
|
||||||
|
self.method:str
|
||||||
|
self.request:str
|
||||||
|
self.value_get:str|None
|
||||||
|
self.variables_get:dict[str, str]
|
||||||
|
self.variables_post:dict[str, str]
|
||||||
|
self.protocol:str
|
||||||
|
self.protocol_version:str
|
||||||
|
self.body:str
|
||||||
|
self.variables_uri:dict[str, str] = {}
|
||||||
|
self.index_files:tuple[str, ...] = index_files
|
||||||
|
|
||||||
|
header, body = (lambda header, body:(
|
||||||
|
RE.NEW_LINE.split(str(header).strip()), body
|
||||||
|
))(*RE.HTTP_BLOCKS.match(data.decode(encoder)).groups())
|
||||||
|
|
||||||
|
(
|
||||||
|
self.method,
|
||||||
|
self.request,
|
||||||
|
self.value_get,
|
||||||
|
self.variables_get,
|
||||||
|
self.protocol,
|
||||||
|
self.protocol_version
|
||||||
|
) = (lambda method, request, variables, protocol, protocol_version:(
|
||||||
|
str(method).lower(),
|
||||||
|
request,
|
||||||
|
variables,
|
||||||
|
self.parse_variables(variables),
|
||||||
|
protocol,
|
||||||
|
protocol_version
|
||||||
|
))(*RE.HTTP_REQUEST.match(header[0]).groups())
|
||||||
|
self.body = body
|
||||||
|
self.variables_post = self.parse_variables(body)
|
||||||
|
|
||||||
|
def set_uri_variables(self:Self, keys:list[str], matches:REMatch) -> None:
|
||||||
|
|
||||||
|
i:int
|
||||||
|
value:str
|
||||||
|
|
||||||
|
for i, value in enumerate(matches.groups()):
|
||||||
|
self.variables_uri[keys[i]] = value
|
||||||
|
|
||||||
|
def get(self:Self, keys:str|Sequence[str], default:Optional[Any] = None) -> Any|None:
|
||||||
|
return Utils.get_value(keys, (
|
||||||
|
self.variables_uri, self.variables_get, self.variables_post
|
||||||
|
), default)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_variables(cls:type[Self], string:Optional[str]) -> dict[str, str]:
|
||||||
|
if not string:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
variables:dict[str, str] = {}
|
||||||
|
pair:str
|
||||||
|
|
||||||
|
for pair in string.split("&"):
|
||||||
|
if "=" in pair:
|
||||||
|
key, value = pair.split("=", 1)
|
||||||
|
variables[Utils.to_snake(key)] = value
|
||||||
|
else:
|
||||||
|
variables[Utils.to_snake(pair)] = ""
|
||||||
|
|
||||||
|
return variables
|
||||||
56
Python/Models/ResponseModel.py
Normal file
56
Python/Models/ResponseModel.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Self, Any, Optional
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
|
||||||
|
class ResponseModel:
|
||||||
|
|
||||||
|
def __init__(self:Self,
|
||||||
|
encoder:str,
|
||||||
|
variables:dict[str, Any|None],
|
||||||
|
default_http_code:int = 200,
|
||||||
|
default_http_message:str = "OK"
|
||||||
|
) -> None:
|
||||||
|
self.encoder:str = encoder
|
||||||
|
self.http_code:int = default_http_code
|
||||||
|
self.http_message:str = default_http_message
|
||||||
|
self.variables:dict[str, Any|None] = variables
|
||||||
|
self.body:bytes = b""
|
||||||
|
self.mime:str = "application/octet-stream;charset=" + encoder
|
||||||
|
self.code:int = 200
|
||||||
|
self.message:str = "OK"
|
||||||
|
|
||||||
|
def build(self:Self,
|
||||||
|
response:Optional[Any] = None,
|
||||||
|
mime:Optional[str] = None,
|
||||||
|
code:Optional[int] = None,
|
||||||
|
message:Optional[str] = None
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
self.code = code or self.http_code
|
||||||
|
self.message = self.http_message if message is None else message
|
||||||
|
|
||||||
|
self.set_data(response, mime)
|
||||||
|
|
||||||
|
def set_data(self:Self, data:Any|None, mime:Optional[str] = None) -> None:
|
||||||
|
if isinstance(data, bytes):
|
||||||
|
self.body = data
|
||||||
|
self.mime = mime or "application/octet-stream"
|
||||||
|
elif isinstance(data, str):
|
||||||
|
self.body = data.encode(self.encoder)
|
||||||
|
self.mime = mime or "text/plain;charset=" + self.encoder
|
||||||
|
elif isinstance(data, (dict, tuple, list)):
|
||||||
|
self.body = Utils.json_encode(data).encode(self.encoder)
|
||||||
|
self.mime = mime or "application/json;charset=" + self.encoder
|
||||||
|
else:
|
||||||
|
self.body = str(data).encode(self.encoder)
|
||||||
|
self.mime = mime or "text/plain;charset=" + self.encoder
|
||||||
|
|
||||||
|
def get_parameters(self:Self) -> dict[str, Any|None]:
|
||||||
|
return {
|
||||||
|
"http_code" : self.code,
|
||||||
|
"http_message" : self.message,
|
||||||
|
"mime" : self.mime,
|
||||||
|
"length" : len(self.body)
|
||||||
|
}
|
||||||
62
Python/Models/RouteModel.py
Normal file
62
Python/Models/RouteModel.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Any, Callable, Self
|
||||||
|
from re import Pattern as REPattern, compile as re_compile, IGNORECASE as RE_IGNORE_CASE, Match as REMatch
|
||||||
|
from Abstracts.ControllerAbstract import ControllerAbstract
|
||||||
|
from Interfaces.Application.NucelarMonitorInterface import NucelarMonitorInterface
|
||||||
|
from Utils.Patterns import RE
|
||||||
|
from Models.RequestModel import RequestModel
|
||||||
|
from Models.ResponseModel import ResponseModel
|
||||||
|
from Utils.Utils import Utils
|
||||||
|
|
||||||
|
class RouteModel:
|
||||||
|
|
||||||
|
def __init__(self:Self, nucelar_monitor:NucelarMonitorInterface, inputs:str|dict[str, Any|None]|tuple[str, str, list[str]]) -> None:
|
||||||
|
|
||||||
|
self.method:str = ""
|
||||||
|
self.route:REPattern
|
||||||
|
self.path:str|None = None
|
||||||
|
self.action:Callable[[RequestModel, ResponseModel], tuple[Any|None, int]]|None = None
|
||||||
|
self.permissions:list[str] = []
|
||||||
|
self.variables:list[str] = []
|
||||||
|
self.error:int = 0
|
||||||
|
|
||||||
|
if isinstance(inputs, str):
|
||||||
|
|
||||||
|
matches:REMatch[str]|None = RE.ROUTE.match(inputs)
|
||||||
|
|
||||||
|
if matches is None:
|
||||||
|
self.error = 1
|
||||||
|
return
|
||||||
|
|
||||||
|
method:str|None
|
||||||
|
route:str
|
||||||
|
action:str|None
|
||||||
|
controller:str|None
|
||||||
|
permissions:str|None
|
||||||
|
|
||||||
|
method, route, action, controller, self.path, permissions = matches.groups()
|
||||||
|
|
||||||
|
self.method = (method or "get").lower()
|
||||||
|
self.route = re_compile(route, RE_IGNORE_CASE)
|
||||||
|
|
||||||
|
def callback(matches:REMatch) -> str:
|
||||||
|
|
||||||
|
self.variables.append(matches.group(1))
|
||||||
|
|
||||||
|
return r'([^\/]+)'
|
||||||
|
|
||||||
|
self.route = re_compile(r'^' + RE.ROUTE_KEY.sub(callback, Utils.to_regular_expression(
|
||||||
|
route[:-1] if route[-1] == "/" else route
|
||||||
|
)) + (r'\/?' if self.path is None else r'(\/.*)?') + r'$')
|
||||||
|
self.path and self.variables.append("path")
|
||||||
|
|
||||||
|
if permissions:
|
||||||
|
self.permissions = [permission.strip() for permission in permissions.split(",") if permission.strip()]
|
||||||
|
if controller and action:
|
||||||
|
|
||||||
|
controller_item:ControllerAbstract = nucelar_monitor.controllers.get(controller)
|
||||||
|
|
||||||
|
if controller_item:
|
||||||
|
self.action = controller_item.get_action(action)
|
||||||
@ -1,865 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
from typing import Any, Optional, Sequence, Self, Callable
|
|
||||||
from re import compile as re_compile, Pattern as REPattern, Match as REMatch, IGNORECASE as RE_IGNORE_CASE
|
|
||||||
from pyodbc import connect as pyodbc_connect
|
|
||||||
from socket import socket as Socket, AF_INET as ADDRESS_FAMILY_IPV4, SOCK_STREAM as SOCKET_STREAM, SOL_SOCKET as SOCKET_LAYER, SO_REUSEADDR as SOCKET_REUSE_ADDRESS
|
|
||||||
from inspect import stack as get_stack, FrameInfo
|
|
||||||
from traceback import format_stack as trace_format_stack, extract_tb as extract_traceback
|
|
||||||
from threading import Thread
|
|
||||||
from os.path import exists as path_exists, dirname as directory_name, abspath as absolute_path
|
|
||||||
from json import loads as json_decode, dumps as json_encode
|
|
||||||
from mimetypes import guess_type as get_mime_by_extension
|
|
||||||
from pyodbc import connect as odbc_connect, Connection as ODBCConnection, Cursor as ODBCCursor
|
|
||||||
from time import time as timestamp, sleep
|
|
||||||
|
|
||||||
class NucelarMonitor:
|
|
||||||
|
|
||||||
DEFAULT_SETTINGS:dict[str, Any|None] = {
|
|
||||||
"autostart" : True,
|
|
||||||
"print_format" : "[{type}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{line}]{file}({method}): {message}",
|
|
||||||
"exception_format" : " '[{line}]{file}({method})'{lines}\n\n{exception_message}",
|
|
||||||
"print_types" : [
|
|
||||||
["unkn", "unknown"],
|
|
||||||
["info", "information"],
|
|
||||||
["warn", "warning"],
|
|
||||||
["erro", "error", "wrong", "failure", "fail", "no"],
|
|
||||||
["exce", "exception", "except"],
|
|
||||||
[" ok ", "ok", "success", "succeed", "yes"],
|
|
||||||
["test", "debug"]
|
|
||||||
],
|
|
||||||
"http_host" : "0.0.0.0",
|
|
||||||
"http_port" : 13000,
|
|
||||||
"http_cache_size" : 1024,
|
|
||||||
"http_maximum_connections" : 5,
|
|
||||||
"http_header_response" : (
|
|
||||||
"{http_protocol}/{http_version} {http_code} {http_message}\r\n" +
|
|
||||||
"Content-Type: {mime}\r\n" +
|
|
||||||
"Content-Length: {length}\r\n" +
|
|
||||||
"\r\n"
|
|
||||||
),
|
|
||||||
"http_protocol" : "HTTP",
|
|
||||||
"http_version" : "1.1",
|
|
||||||
"http_code" : 200,
|
|
||||||
"http_message" : "OK",
|
|
||||||
"http_encoder" : "utf-8",
|
|
||||||
"index_files" : ("index.html", "index.htm"),
|
|
||||||
"sql_host" : "127.0.0.1",
|
|
||||||
"sql_port" : 1433,
|
|
||||||
"sql_user" : "sa",
|
|
||||||
"sql_password" : "password",
|
|
||||||
"sql_database" : "NucelarMonitor",
|
|
||||||
"default_controllers" : {
|
|
||||||
"get" : {
|
|
||||||
"/" : "/Public"
|
|
||||||
},
|
|
||||||
"post" : {
|
|
||||||
"/debian/{key}" : "debian"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"default_connections" : {}
|
|
||||||
}
|
|
||||||
DEFAULT_I18N:dict[str, dict[str, str|Sequence[str]]] = {
|
|
||||||
"english" : {}
|
|
||||||
}
|
|
||||||
ROOT:str = directory_name(absolute_path(__file__))
|
|
||||||
SLASH:str = "/" if "/" in ROOT else "\\\\"
|
|
||||||
SPECIAL_REGULAR_EXPRESSION_CHARACTERS:dict[str, str] = {
|
|
||||||
"\r" : "r",
|
|
||||||
"\n" : "n",
|
|
||||||
"\t" : "t"
|
|
||||||
}
|
|
||||||
|
|
||||||
RE_KEY:REPattern = re_compile(r'^[a-z_][a-z0-9_]*$', RE_IGNORE_CASE)
|
|
||||||
RE_STRING_VARIABLE:REPattern = re_compile(r'\{([a-z_][a-z0-9_]*)\}', RE_IGNORE_CASE)
|
|
||||||
RE_EXCEPTION:REPattern = re_compile(r'^\s*File "([^"]+)", line ([0-9]+), in ([^\n]+)(.*|[\r\n]*)*$')
|
|
||||||
RE_NEW_LINE:REPattern = re_compile(r'\r\n|[\r\n]')
|
|
||||||
RE_TO_SNAKE:REPattern = re_compile(r'[^a-zA-Z0-9]*([A-Z][A-Z0-9]*)|[^a-z0-9]+')
|
|
||||||
RE_HTTP_REQUEST:REPattern = re_compile(r'^([^\s]+)\s([^\s\?\#]+)(?:\?([^#]+))?(?:\#[^\s]+)?\s([^\/]+)\/([0-9\.]+)$')
|
|
||||||
RE_HEADER_LINE:REPattern = re_compile(r'^([^\:]+)\:(.+)$')
|
|
||||||
RE_HTTP_BLOCKS:REPattern = re_compile(r'((?:(?!(?:(?:\r\n){2}|\n{2}|\r{2}))(?:.|[\r\n]+))+)(?:(?:(?:\r\n){2}|\n{2}|\r{2})((?:.+|[\r\n]+)*))?')
|
|
||||||
RE_LAST_DIRECTORY:REPattern = re_compile(r'^(.*)[\/\\\\][^\/\\\\]*[\/\\\\]?$')
|
|
||||||
RE_SLASHES:REPattern = re_compile(r'[\\\/]+')
|
|
||||||
RE_TO_REGULAR_EXPRESSION:REPattern = re_compile(r'[\(\)\{\}\/\\\.\-\+\*\^\$\?\|\!\<\>\r\n\t]')
|
|
||||||
RE_ROUTE_KEY:REPattern = re_compile(r'\\\{([a-z_][a-z0-9_]*)\\\}', RE_IGNORE_CASE)
|
|
||||||
RE_ODBC_STRING_VARIABLE:REPattern = re_compile(r'\{([a-z_][a-z0-9_]*)\}|@([a-z0-9_]+)', RE_IGNORE_CASE)
|
|
||||||
|
|
||||||
class Request:
|
|
||||||
|
|
||||||
def __init__(self:Self, data:bytes, encoder:str = "utf-8") -> None:
|
|
||||||
|
|
||||||
self.method:str
|
|
||||||
self.request:str
|
|
||||||
self.value_get:str|None
|
|
||||||
self.variables_get:dict[str, str]
|
|
||||||
self.variables_post:dict[str, str]
|
|
||||||
self.protocol:str
|
|
||||||
self.protocol_version:str
|
|
||||||
self.body:str
|
|
||||||
self.variables_uri:dict[str, str] = {}
|
|
||||||
|
|
||||||
header, body = (lambda header, body:(
|
|
||||||
NucelarMonitor.RE_NEW_LINE.split(str(header).strip()), body
|
|
||||||
))(*NucelarMonitor.RE_HTTP_BLOCKS.match(data.decode(encoder)).groups())
|
|
||||||
|
|
||||||
(
|
|
||||||
self.method,
|
|
||||||
self.request,
|
|
||||||
self.value_get,
|
|
||||||
self.variables_get,
|
|
||||||
self.protocol,
|
|
||||||
self.protocol_version
|
|
||||||
) = (lambda method, request, variables, protocol, protocol_version:(
|
|
||||||
str(method).lower(),
|
|
||||||
request,
|
|
||||||
variables,
|
|
||||||
self.parse_variables(variables),
|
|
||||||
protocol,
|
|
||||||
protocol_version
|
|
||||||
))(*NucelarMonitor.RE_HTTP_REQUEST.match(header[0]).groups())
|
|
||||||
self.body = body
|
|
||||||
self.variables_post = self.parse_variables(body)
|
|
||||||
|
|
||||||
def set_uri_variables(self:Self, keys:list[str], matches:REMatch) -> None:
|
|
||||||
|
|
||||||
i:int
|
|
||||||
value:str
|
|
||||||
|
|
||||||
for i, value in enumerate(matches.groups()):
|
|
||||||
self.variables_uri[keys[i]] = value
|
|
||||||
|
|
||||||
def get(self:Self, keys:str|Sequence[str], default:Optional[Any] = None) -> Any|None:
|
|
||||||
return NucelarMonitor.get_value(keys, (
|
|
||||||
self.variables_uri, self.variables_get, self.variables_post
|
|
||||||
), default)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def parse_variables(cls:type[Self], string:Optional[str]) -> dict[str, str]:
|
|
||||||
if not string:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
variables:dict[str, str] = {}
|
|
||||||
pair:str
|
|
||||||
|
|
||||||
for pair in string.split("&"):
|
|
||||||
if "=" in pair:
|
|
||||||
key, value = pair.split("=", 1)
|
|
||||||
variables[cls.to_snake(key)] = value
|
|
||||||
else:
|
|
||||||
variables[cls.to_snake(pair)] = ""
|
|
||||||
|
|
||||||
return variables
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def to_snake(string:str) -> str:
|
|
||||||
|
|
||||||
def callback(matches:REMatch) -> str:
|
|
||||||
|
|
||||||
upper:str|None = matches.group(1)
|
|
||||||
|
|
||||||
return "_" + upper.lower() if upper else "_"
|
|
||||||
|
|
||||||
return NucelarMonitor.RE_TO_SNAKE.sub(callback, string).lower()
|
|
||||||
|
|
||||||
class Response:
|
|
||||||
|
|
||||||
def __init__(self:Self,
|
|
||||||
nucelar_monitor:type[Self],
|
|
||||||
response:Optional[Any] = None,
|
|
||||||
mime:Optional[str] = None,
|
|
||||||
code:Optional[int] = None,
|
|
||||||
message:Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
|
|
||||||
default_code:int
|
|
||||||
default_message:str
|
|
||||||
|
|
||||||
self.nucelar_monitor:NucelarMonitor = nucelar_monitor
|
|
||||||
default_code, default_message = self.nucelar_monitor.get_http_default_code()
|
|
||||||
self.body:bytes = b""
|
|
||||||
self.mime:str
|
|
||||||
self.code:str = code or default_code
|
|
||||||
self.message:str = default_message if message is None else message
|
|
||||||
|
|
||||||
self.set_data(response, mime)
|
|
||||||
|
|
||||||
def set_data(self:Self, data:Any|None, mime:Optional[str] = None) -> None:
|
|
||||||
if isinstance(data, bytes):
|
|
||||||
self.body = data
|
|
||||||
self.mime = mime or "application/octet-stream"
|
|
||||||
elif isinstance(data, str):
|
|
||||||
self.body = data.encode(self.nucelar_monitor.get_encoder())
|
|
||||||
self.mime = mime or "text/plain;charset=" + self.nucelar_monitor.get_encoder()
|
|
||||||
elif isinstance(data, (dict, tuple, list)):
|
|
||||||
self.body = json_encode(data).encode(self.nucelar_monitor.get_encoder())
|
|
||||||
self.mime = mime or "application/json;charset=" + self.nucelar_monitor.get_encoder()
|
|
||||||
else:
|
|
||||||
self.body = str(data).encode(self.nucelar_monitor.get_encoder())
|
|
||||||
self.mime = mime or "text/plain;charset=" + self.nucelar_monitor.get_encoder()
|
|
||||||
|
|
||||||
def get_parameters(self:Self) -> dict[str, Any|None]:
|
|
||||||
return {
|
|
||||||
"http_code" : self.code,
|
|
||||||
"http_message" : self.message,
|
|
||||||
"mime" : self.mime,
|
|
||||||
"length" : len(self.body)
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self:Self, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
|
|
||||||
|
|
||||||
key:str
|
|
||||||
|
|
||||||
self.__inputs:dict[str, Any|None] = self.get_dictionary(inputs)
|
|
||||||
self.__sentences:dict[str, dict[str, str|Sequence[str]]] = self.DEFAULT_I18N
|
|
||||||
self.__language:str = self.get("language", None, "english")
|
|
||||||
self.__print_format:str = self.get("print_format")
|
|
||||||
self.__print_types:list[list[str]] = self.DEFAULT_SETTINGS["print_types"]
|
|
||||||
self.__exception_format:str = self.get("exception_format")
|
|
||||||
self.__controllers:dict[str, list[tuple[
|
|
||||||
REPattern,
|
|
||||||
tuple[tuple[str, ...]],
|
|
||||||
Callable[[NucelarMonitor.Request, NucelarMonitor.Response], None]|None,
|
|
||||||
str|None]
|
|
||||||
]] = {}
|
|
||||||
self.__http_host:str = self.get("http_host")
|
|
||||||
self.__http_port:int = self.get("http_port")
|
|
||||||
self.__http_server:Socket
|
|
||||||
self.__http_buffer_size:int = self.get("http_cache_size")
|
|
||||||
self.__http_header_response:str = self.get("http_header_response")
|
|
||||||
self.__http_protocol:str = self.get("http_protocol")
|
|
||||||
self.__http_version:str = self.get("http_version")
|
|
||||||
self.__http_code:int = self.get("http_code")
|
|
||||||
self.__http_message:str = self.get("http_message")
|
|
||||||
self.__http_encoder:str = self.get("http_encoder")
|
|
||||||
self.__index_files:tuple[str, ...] = tuple(self.get("index_files"))
|
|
||||||
self.__started:bool = False
|
|
||||||
self.__working:bool = False
|
|
||||||
self.__root_paths:list[str] = ["", self.ROOT]
|
|
||||||
self.__commands:list[list[list[str], Callable[[dict[str, Any|None], list[Any|None]], None]]] = [
|
|
||||||
[["close", "exit", "quit", "bye"], self.__close_command]
|
|
||||||
]
|
|
||||||
self.__odbc_connection:ODBCConnection|None = None
|
|
||||||
self.__odbc_string_connection:str = self.string_variables(self.get((
|
|
||||||
"odbc_string_connection", "sql_string_connection", "string_connection"
|
|
||||||
), None, "DRIVER={driver};SERVER={host},{port};UID={user};PWD={password};DATABASE={database}"), {
|
|
||||||
"driver" : self.get("sql_driver", None, "{ODBC Driver 17 for SQL Server}"),
|
|
||||||
"host" : self.get(("sql_host", "odbc_host"), None, "localhost"),
|
|
||||||
"port" : self.get(("sql_port", "odbc_port"), None, 1433),
|
|
||||||
"user" : self.get(("sql_user", "odbc_user"), None, "sa"),
|
|
||||||
"password" : self.get(("sql_password", "odbc_password"), None, "password"),
|
|
||||||
"database" : self.get(("sql_database", "odbc_database"), None, "NucelarMonitor")
|
|
||||||
})
|
|
||||||
|
|
||||||
for _ in range(2):
|
|
||||||
self.__root_paths.append(self.RE_LAST_DIRECTORY.sub(r'\1', self.__root_paths[-1]))
|
|
||||||
|
|
||||||
for key in ("default_controllers", "controllers"):
|
|
||||||
self.add_controllers(self.get(key))
|
|
||||||
|
|
||||||
self.get("autostart") and self.start()
|
|
||||||
|
|
||||||
def start(self:Self) -> None:
|
|
||||||
if self.__started:
|
|
||||||
return
|
|
||||||
self.__started = True
|
|
||||||
|
|
||||||
self.__http_server = Socket(ADDRESS_FAMILY_IPV4, SOCKET_STREAM)
|
|
||||||
self.__working = True
|
|
||||||
|
|
||||||
Thread(target = self.__command_listener).start()
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
self.__http_server.setsockopt(SOCKET_LAYER, SOCKET_REUSE_ADDRESS, 1)
|
|
||||||
self.__http_server.bind((self.__http_host, self.__http_port))
|
|
||||||
self.__http_server.listen(self.get("http_maximum_connections"))
|
|
||||||
|
|
||||||
Thread(target = self.__listen).start()
|
|
||||||
|
|
||||||
except Exception as exception:
|
|
||||||
self.exception(exception, "http_server_start_exception", {
|
|
||||||
"host" : self.__http_host,
|
|
||||||
"port" : self.__http_port,
|
|
||||||
})
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def close(self:Self) -> None:
|
|
||||||
if not self.__started:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.__started = False
|
|
||||||
self.__working = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.__http_server.close()
|
|
||||||
except Exception as exception:
|
|
||||||
self.exception(exception, "http_server_close_exception", {
|
|
||||||
"host" : self.__http_host,
|
|
||||||
"port" : self.__http_port,
|
|
||||||
})
|
|
||||||
|
|
||||||
def __command_listener(self:Self) -> None:
|
|
||||||
while self.__working:
|
|
||||||
try:
|
|
||||||
|
|
||||||
parameters:dict[str, Any|None] = {}
|
|
||||||
arguments:list[Any|None] = []
|
|
||||||
command:str = input().strip().lower()
|
|
||||||
|
|
||||||
if command:
|
|
||||||
for commands, callback in self.__commands:
|
|
||||||
if command in commands:
|
|
||||||
callback(self.get_dictionary(self.__inputs), [parameters, *arguments])
|
|
||||||
break
|
|
||||||
|
|
||||||
except Exception as exception:
|
|
||||||
self.exception(exception, "command_listener_exception", {
|
|
||||||
"command" : command
|
|
||||||
})
|
|
||||||
|
|
||||||
def __close_command(self:Self, inputs:dict[str, Any|None], *arguments:list[Any|None]) -> None:
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def get_print_type(self:Self, _type:str) -> str:
|
|
||||||
|
|
||||||
group:list[str]
|
|
||||||
|
|
||||||
for group in self.__print_types:
|
|
||||||
if _type in group:
|
|
||||||
return group[0].upper()
|
|
||||||
return self.__print_types[0][0].upper()
|
|
||||||
|
|
||||||
def print(self:Self,
|
|
||||||
_type:str,
|
|
||||||
message:str|Sequence[str],
|
|
||||||
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
|
||||||
i:int = 0
|
|
||||||
) -> None:
|
|
||||||
|
|
||||||
date:datetime = datetime.datetime.now()
|
|
||||||
own:dict[str, Any|None] = {
|
|
||||||
"raw_type" : _type,
|
|
||||||
"type" : self.get_print_type(_type),
|
|
||||||
"i18n" : self.get_texts(message),
|
|
||||||
"message" : self.i18n(message, inputs),
|
|
||||||
**self.get_dictionary(inputs),
|
|
||||||
**self.get_action_data(i + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for key in ("year", "month", "day", "hour", "minute", "second"):
|
|
||||||
|
|
||||||
k:str = "i" if key == "minute" else key[0]
|
|
||||||
|
|
||||||
own[k] = own[key] = getattr(date, key)
|
|
||||||
own[k + k] = ("00" + str(own[key]))[-2:]
|
|
||||||
|
|
||||||
own["yyyy"] = own["year"]
|
|
||||||
|
|
||||||
print(self.string_variables(self.__print_format, own) + (own["end"] if "end" in own else ""))
|
|
||||||
|
|
||||||
def exception(self:Self,
|
|
||||||
exception:Exception,
|
|
||||||
message:str|Sequence[str],
|
|
||||||
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
|
||||||
i:int = 0
|
|
||||||
) -> None:
|
|
||||||
|
|
||||||
lines:list[str] = extract_traceback(exception.__traceback__).format()
|
|
||||||
matches:REMatch = self.RE_EXCEPTION.match(lines[-1])
|
|
||||||
data:dict[str, Any|None] = {
|
|
||||||
**self.get_dictionary(inputs),
|
|
||||||
"lines" : "",
|
|
||||||
"exception_message" : str(exception),
|
|
||||||
"method" : matches.group(3),
|
|
||||||
"line" : matches.group(2),
|
|
||||||
"file" : matches.group(1)
|
|
||||||
}
|
|
||||||
block:str
|
|
||||||
j:int
|
|
||||||
|
|
||||||
for j, block in enumerate(trace_format_stack()[:-2] + lines):
|
|
||||||
if block:
|
|
||||||
data["lines"] += "\n " + str(j) + " - " + self.RE_NEW_LINE.split(block.strip())[0]
|
|
||||||
|
|
||||||
data["end"] = self.string_variables(self.__exception_format, data)
|
|
||||||
|
|
||||||
message and self.print("exception", message, data, i + 2)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fix_path(cls:type[Self], path:str) -> str:
|
|
||||||
return cls.RE_SLASHES.sub(cls.SLASH, path)
|
|
||||||
|
|
||||||
def get_absolute_path(self:Self, path:str) -> str|None:
|
|
||||||
|
|
||||||
root:str
|
|
||||||
absolute:str
|
|
||||||
|
|
||||||
for root in self.__root_paths:
|
|
||||||
absolute = self.fix_path((root + '/' if root else "") + path)
|
|
||||||
if path_exists(absolute):
|
|
||||||
return absolute
|
|
||||||
return None
|
|
||||||
|
|
||||||
def load_file(self:Self, path:str, mode:str = "r") -> str|bytes|None:
|
|
||||||
|
|
||||||
absolute_path:str = self.get_absolute_path(path)
|
|
||||||
|
|
||||||
if absolute_path:
|
|
||||||
with open(absolute_path, mode) as file:
|
|
||||||
return file.read()
|
|
||||||
return None
|
|
||||||
|
|
||||||
def load_json(self:Self, data:str|dict[str, Any|None]|list[Any|None]) -> dict[str, Any|None]|list[Any|None]|None:
|
|
||||||
if isinstance(data, str):
|
|
||||||
|
|
||||||
json:list[Any|None]|dict[str, Any|None]|None
|
|
||||||
|
|
||||||
try:
|
|
||||||
json = json_decode(data)
|
|
||||||
except Exception as exception:
|
|
||||||
self.exception(exception, "load_json_exception", {
|
|
||||||
"data" : data,
|
|
||||||
"length" : len(data)
|
|
||||||
})
|
|
||||||
|
|
||||||
if json:
|
|
||||||
return json
|
|
||||||
try:
|
|
||||||
return json_decode(self.load_file(data))
|
|
||||||
except Exception as exception:
|
|
||||||
self.exception(exception, "load_json_by_file_exception", {
|
|
||||||
"path" : data
|
|
||||||
})
|
|
||||||
return None
|
|
||||||
elif isinstance(data, (dict, list)):
|
|
||||||
return data
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_encoder(self:Self) -> str:
|
|
||||||
return self.__http_encoder
|
|
||||||
|
|
||||||
def get_http_default_code(self:Self) -> tuple[int, str]:
|
|
||||||
return self.__http_code, self.__http_message
|
|
||||||
|
|
||||||
def get(self:Self,
|
|
||||||
keys:str|Sequence[str],
|
|
||||||
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None,
|
|
||||||
default:Optional[Any] = None
|
|
||||||
) -> Any|None:
|
|
||||||
return self.get_value(keys, (inputs, self.__inputs, self.DEFAULT_SETTINGS), default)
|
|
||||||
|
|
||||||
def __get_text(self:Self, strings:str|Sequence[str]) -> str:
|
|
||||||
|
|
||||||
keys:list[str] = self.get_keys(strings := self.get_list(strings))
|
|
||||||
|
|
||||||
if len(keys):
|
|
||||||
|
|
||||||
language:str
|
|
||||||
used:list[str] = []
|
|
||||||
|
|
||||||
for language in [self.__language] + list(self.__sentences.keys()):
|
|
||||||
if language not in used and language in self.__sentences:
|
|
||||||
|
|
||||||
key:str
|
|
||||||
|
|
||||||
used.append(language)
|
|
||||||
for key in keys:
|
|
||||||
if key in self.__sentences[language]:
|
|
||||||
return self.__sentences[language][key]
|
|
||||||
return strings[0]
|
|
||||||
|
|
||||||
def i18n(self:Self,
|
|
||||||
strings:str|Sequence[str],
|
|
||||||
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
|
||||||
) -> str:
|
|
||||||
return self.string_variables(self.__get_text(strings), inputs)
|
|
||||||
|
|
||||||
def add_controllers(self:Self,
|
|
||||||
inputs:str|dict[str, dict[str, str|Callable[[Request], None]]]|Sequence[Any|None]
|
|
||||||
) -> None:
|
|
||||||
if isinstance(inputs, dict):
|
|
||||||
|
|
||||||
method:str
|
|
||||||
controllers:dict[str, str|Callable[[NucelarMonitor.Request], None]]
|
|
||||||
|
|
||||||
for method, controllers in inputs.items():
|
|
||||||
if (method := method.lower()) not in self.__controllers:
|
|
||||||
self.__controllers[method] = []
|
|
||||||
if isinstance(controllers, dict):
|
|
||||||
|
|
||||||
request:str
|
|
||||||
target:str|Callable[[NucelarMonitor.Request], None]
|
|
||||||
|
|
||||||
for request, target in controllers.items():
|
|
||||||
|
|
||||||
controller:Callable[[NucelarMonitor.Request, NucelarMonitor.Response], None]|None = None
|
|
||||||
path:str|None = None
|
|
||||||
|
|
||||||
if isinstance(target, str) and (controller := getattr(self, target, None)) is None:
|
|
||||||
path = self.get_absolute_path(target)
|
|
||||||
|
|
||||||
if callable(controller) or path is not None:
|
|
||||||
|
|
||||||
variables:list[str] = []
|
|
||||||
|
|
||||||
if path is not None:
|
|
||||||
variables.append("route")
|
|
||||||
|
|
||||||
def callback(matches:REMatch) -> str:
|
|
||||||
|
|
||||||
variables.append(matches.group(1))
|
|
||||||
|
|
||||||
return r'([^\/]+)'
|
|
||||||
|
|
||||||
self.__controllers[method].append((re_compile(r'^' + self.RE_ROUTE_KEY.sub(callback, self.to_regular_expression(
|
|
||||||
request[:-1] if request[-1] == "/" else request
|
|
||||||
)) + (r'' if path is None else r'(.*)') + r'\/?$'), tuple(variables), controller, path))
|
|
||||||
|
|
||||||
elif isinstance(inputs, (list, tuple)):
|
|
||||||
|
|
||||||
subinputs:Any|None
|
|
||||||
|
|
||||||
for subinputs in inputs:
|
|
||||||
self.add_controllers(subinputs)
|
|
||||||
|
|
||||||
elif isinstance(inputs, str):
|
|
||||||
self.add_controllers(self.load_json(inputs))
|
|
||||||
|
|
||||||
def __listen(self:Self) -> None:
|
|
||||||
while self.__working:
|
|
||||||
try:
|
|
||||||
|
|
||||||
client:Socket
|
|
||||||
address:str
|
|
||||||
port:int
|
|
||||||
|
|
||||||
client, (address, port) = self.__http_server.accept()
|
|
||||||
|
|
||||||
Thread(
|
|
||||||
target = self.__listen_client,
|
|
||||||
args = (client, address, port)
|
|
||||||
).start()
|
|
||||||
|
|
||||||
except Exception as exception:
|
|
||||||
self.exception(exception, "http_server_listen_exception", {
|
|
||||||
"host" : self.__http_host,
|
|
||||||
"port" : self.__http_port,
|
|
||||||
})
|
|
||||||
|
|
||||||
def __listen_client(self:Self, client:Socket, address:str, port:int) -> None:
|
|
||||||
|
|
||||||
data:bytes = b""
|
|
||||||
route:str = ""
|
|
||||||
method:str = "UNKN"
|
|
||||||
response:NucelarMonitor.Response = NucelarMonitor.Response(self)
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
request:NucelarMonitor.Request
|
|
||||||
variables:tuple[str, ...]
|
|
||||||
controller:Callable[[NucelarMonitor.Request], Any|None]|None
|
|
||||||
path:str|None
|
|
||||||
response_data:Any|None = None
|
|
||||||
pattern:REPattern
|
|
||||||
done:bool = False
|
|
||||||
|
|
||||||
while True:
|
|
||||||
|
|
||||||
buffer:bytes = client.recv(self.__http_buffer_size)
|
|
||||||
|
|
||||||
if not buffer:
|
|
||||||
break
|
|
||||||
data += buffer
|
|
||||||
if len(buffer) != self.__http_buffer_size:
|
|
||||||
break
|
|
||||||
|
|
||||||
for pattern, variables, controller, path in self.__controllers[
|
|
||||||
method := (request := self.Request(data, self.__http_encoder)).method
|
|
||||||
]:
|
|
||||||
|
|
||||||
matches:REMatch = pattern.match(route := request.request)
|
|
||||||
|
|
||||||
if matches is not None:
|
|
||||||
request.set_uri_variables(variables, matches)
|
|
||||||
if done := path is not None:
|
|
||||||
for index in self.__index_files:
|
|
||||||
|
|
||||||
full_path = path + "/" + route + ("" if index == "" else "/" + index)
|
|
||||||
|
|
||||||
if done := (response_data := self.load_file(full_path, "rb")) is not None:
|
|
||||||
response.set_data(
|
|
||||||
response_data,
|
|
||||||
get_mime_by_extension(full_path)[0] or "application/octet-stream"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
elif done := controller is not None:
|
|
||||||
controller(request, response)
|
|
||||||
|
|
||||||
if done:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
if not done:
|
|
||||||
response.body = b"<h1>Not Found</h1>"
|
|
||||||
response.mime = "text/html;charset=" + self.__http_encoder
|
|
||||||
response.code = "404"
|
|
||||||
response.message = "Not Found"
|
|
||||||
|
|
||||||
client.sendall(self.string_variables(self.__http_header_response, {
|
|
||||||
"http_protocol" : self.__http_protocol,
|
|
||||||
"http_version" : self.__http_version,
|
|
||||||
**response.get_parameters()
|
|
||||||
}).encode(self.__http_encoder) + response.body)
|
|
||||||
client.close()
|
|
||||||
|
|
||||||
except Exception as exception:
|
|
||||||
self.exception(exception, "http_server_client_exception", {
|
|
||||||
"host" : self.__http_host,
|
|
||||||
"port" : self.__http_port,
|
|
||||||
"client_address" : address,
|
|
||||||
"client_port" : port,
|
|
||||||
"length" : len(data),
|
|
||||||
"method" : method,
|
|
||||||
"route" : route,
|
|
||||||
"response_length" : len(response.body)
|
|
||||||
})
|
|
||||||
|
|
||||||
def __odbc_connection_autoclose(self:Self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def format_odbc_query(self:Self,
|
|
||||||
query:str,
|
|
||||||
parameters:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
|
||||||
) -> tuple[str, list[str]]:
|
|
||||||
|
|
||||||
variables:list[str] = []
|
|
||||||
|
|
||||||
def callback(matches:REMatch) -> str:
|
|
||||||
|
|
||||||
key:str = matches.group(1)
|
|
||||||
|
|
||||||
if key:
|
|
||||||
return (
|
|
||||||
"'" + str(parameters[key]).replace("'","''") + "'" if isinstance(parameters[key], str) else
|
|
||||||
str(parameters[key]) if key in parameters else matches.group(0))
|
|
||||||
|
|
||||||
key = matches.group(2)
|
|
||||||
|
|
||||||
variables.append(key)
|
|
||||||
|
|
||||||
return matches.group(0)
|
|
||||||
|
|
||||||
query = self.RE_ODBC_STRING_VARIABLE.sub(callback, query)
|
|
||||||
|
|
||||||
return (
|
|
||||||
"".join("declare @" + variable + " varchar(max)\n" for variable in variables) +
|
|
||||||
query +
|
|
||||||
"\nselect " + ", ".join("@" + variable for variable in variables)
|
|
||||||
) if len(variables) else query, variables
|
|
||||||
|
|
||||||
def odbc_query(self:Self,
|
|
||||||
query:str,
|
|
||||||
parameters:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
|
||||||
) -> dict[str, dict[str, Any|None]|list[list[list[Any|None]]]|list[list[str]]]:
|
|
||||||
|
|
||||||
response:dict[str, dict[str, Any|None]|list[list[list[Any|None]]]|list[list[str]]] = {
|
|
||||||
"tables" : [],
|
|
||||||
"columns" : [],
|
|
||||||
"variables" : {}
|
|
||||||
}
|
|
||||||
cursor:ODBCCursor|None = None
|
|
||||||
variables:list[str] = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
if self.__odbc_connection is None:
|
|
||||||
self.__odbc_connection = odbc_connect(
|
|
||||||
self.__odbc_string_connection,
|
|
||||||
autocommit = True
|
|
||||||
)
|
|
||||||
cursor = self.__odbc_connection.cursor()
|
|
||||||
query, variables = self.format_odbc_query(query, parameters)
|
|
||||||
|
|
||||||
cursor.execute(query)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
|
|
||||||
if cursor.description is not None:
|
|
||||||
response["columns"].append([column[0] for column in cursor.description])
|
|
||||||
response["tables"].append([tuple(row) for row in cursor.fetchall()])
|
|
||||||
|
|
||||||
if not cursor.nextset():
|
|
||||||
break
|
|
||||||
|
|
||||||
except Exception as exception:
|
|
||||||
self.exception(exception, "odbc_connection_exception", {
|
|
||||||
"string_connection" : self.__odbc_string_connection
|
|
||||||
})
|
|
||||||
finally:
|
|
||||||
if cursor is not None:
|
|
||||||
cursor.close()
|
|
||||||
|
|
||||||
if len(variables) and len(response["tables"]):
|
|
||||||
for i, variable in enumerate(variables):
|
|
||||||
response["variables"][variable] = response["tables"][-1][0][i]
|
|
||||||
response["tables"] = response["tables"][:-1]
|
|
||||||
response["columns"] = response["columns"][:-1]
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def debian(self:Self, request:Request, response:Response) -> None:
|
|
||||||
|
|
||||||
key:str = request.get("key")
|
|
||||||
hostnames:list[str]
|
|
||||||
domain:str|None
|
|
||||||
interfaces:list[list[int, str, bool, str, int]]
|
|
||||||
disks:list[list[str, int, int, str|None]]
|
|
||||||
iterations:int
|
|
||||||
candle_times:list[int, int]
|
|
||||||
cpu:list[float, float, float, float, float]
|
|
||||||
memory:list[int, int, int, int, int, float]
|
|
||||||
net_use:list[list[list[str, int, int, int, int, int, int]]]
|
|
||||||
|
|
||||||
hostnames, domain, interfaces, disks, iterations, candle_times, cpu, memory, net_use = json_decode(request.body)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_dictionary(cls:type[Self], *items:Sequence[Any|None]) -> dict[str, Any|None]:
|
|
||||||
|
|
||||||
dictionary:dict[str, Any|None] = {}
|
|
||||||
item:Any|None
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
if isinstance(item, dict):
|
|
||||||
dictionary.update(item)
|
|
||||||
elif isinstance(item, (list, tuple)):
|
|
||||||
|
|
||||||
subitem:Any|None
|
|
||||||
|
|
||||||
for subitem in item:
|
|
||||||
dictionary.update(cls.get_dictionary(subitem))
|
|
||||||
|
|
||||||
return dictionary
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_keys(cls:type[Self], *items:Sequence[Any|None]) -> list[str]:
|
|
||||||
|
|
||||||
keys:list[str] = []
|
|
||||||
item:Any|None
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
if isinstance(item, str):
|
|
||||||
cls.RE_KEY.match(item) and keys.append(item)
|
|
||||||
elif isinstance(item, (list, tuple)):
|
|
||||||
|
|
||||||
subitem:Any|None
|
|
||||||
|
|
||||||
for subitem in item:
|
|
||||||
keys.extend(cls.get_keys(subitem))
|
|
||||||
|
|
||||||
return keys
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_dictionaries(cls:type[Self], *items:Sequence[Any|None]) -> list[dict[str, Any|None]]:
|
|
||||||
|
|
||||||
dictionaries:list[dict[str, Any|None]] = []
|
|
||||||
item:Any|None
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
if isinstance(item, dict):
|
|
||||||
dictionaries.append(item)
|
|
||||||
elif isinstance(item, (list, tuple)):
|
|
||||||
|
|
||||||
subitem:Any|None
|
|
||||||
|
|
||||||
for subitem in item:
|
|
||||||
dictionaries.extend(cls.get_dictionaries(subitem))
|
|
||||||
|
|
||||||
return dictionaries
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_value(cls:type[Self],
|
|
||||||
keys:str|Sequence[str],
|
|
||||||
inputs:dict[str, Any|None]|Sequence[Any|None],
|
|
||||||
default:Optional[Any] = None
|
|
||||||
) -> Any|None:
|
|
||||||
if len(cls.get_keys(keys)):
|
|
||||||
|
|
||||||
dictionary:dict[str, Any|None]
|
|
||||||
|
|
||||||
for dictionary in cls.get_dictionaries(inputs):
|
|
||||||
|
|
||||||
key:str
|
|
||||||
|
|
||||||
for key in cls.get_keys(keys):
|
|
||||||
if key in dictionary:
|
|
||||||
return dictionary[key]
|
|
||||||
return default
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_list(item:Any|None) -> list[Any|None]:
|
|
||||||
return item if isinstance(item, (list, tuple)) else [item]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def string_variables(cls:type[Self],
|
|
||||||
string:str,
|
|
||||||
inputs:dict[str, Any|None]|Sequence[Any|None],
|
|
||||||
default:Optional[str] = None
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
variables:dict[str, Any|None] = cls.get_dictionary(inputs)
|
|
||||||
|
|
||||||
def callback(matches:REMatch) -> str:
|
|
||||||
|
|
||||||
key:str = matches.group(1)
|
|
||||||
|
|
||||||
return (
|
|
||||||
str(variables[key]) if key in variables else
|
|
||||||
default if default is not None else
|
|
||||||
matches.group(0))
|
|
||||||
|
|
||||||
return cls.RE_STRING_VARIABLE.sub(callback, string)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_texts(*items:list[Any|None]) -> list[str]:
|
|
||||||
|
|
||||||
texts:list[str] = []
|
|
||||||
item:Any|None
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
if isinstance(item, str):
|
|
||||||
texts.append(item)
|
|
||||||
elif isinstance(item, (list, tuple)):
|
|
||||||
|
|
||||||
subitem:Any|None
|
|
||||||
|
|
||||||
for subitem in item:
|
|
||||||
texts.extend(NucelarMonitor.get_texts(subitem))
|
|
||||||
|
|
||||||
return texts
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_action_data(i:int = 0) -> dict[str, str|int]:
|
|
||||||
|
|
||||||
stack:FrameInfo = get_stack()[i]
|
|
||||||
|
|
||||||
return {
|
|
||||||
"file" : stack.filename,
|
|
||||||
"method" : stack.function,
|
|
||||||
"line" : stack.lineno
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def to_regular_expression(cls:type[Self], string:str) -> str:
|
|
||||||
|
|
||||||
def callback(matches:REMatch) -> str:
|
|
||||||
|
|
||||||
character:str = matches.group(0)
|
|
||||||
|
|
||||||
return "\\" + (
|
|
||||||
cls.SPECIAL_REGULAR_EXPRESSION_CHARACTERS[character] if character in cls.SPECIAL_REGULAR_EXPRESSION_CHARACTERS else
|
|
||||||
character)
|
|
||||||
|
|
||||||
return cls.RE_TO_REGULAR_EXPRESSION.sub(callback, string)
|
|
||||||
21
Python/Utils/Patterns.py
Normal file
21
Python/Utils/Patterns.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from re import compile as re_compile, Pattern as REPattern, IGNORECASE as RE_IGNORE_CASE
|
||||||
|
|
||||||
|
class RE:
|
||||||
|
|
||||||
|
KEY:REPattern = re_compile(r'^[a-z_][a-z0-9_]*$', RE_IGNORE_CASE)
|
||||||
|
STRING_VARIABLE:REPattern = re_compile(r'\{([a-z_][a-z0-9_]*)\}', RE_IGNORE_CASE)
|
||||||
|
EXCEPTION:REPattern = re_compile(r'^\s*File "([^"]+)", line ([0-9]+), in ([^\n]+)(.*|[\r\n]*)*$')
|
||||||
|
NEW_LINE:REPattern = re_compile(r'\r\n|[\r\n]')
|
||||||
|
TO_SNAKE:REPattern = re_compile(r'[^a-zA-Z0-9]*([A-Z][A-Z0-9]*)|[^a-z0-9]+')
|
||||||
|
HTTP_REQUEST:REPattern = re_compile(r'^([^\s]+)\s([^\s\?\#]+)(?:\?([^#]+))?(?:\#[^\s]+)?\s([^\/]+)\/([0-9\.]+)$')
|
||||||
|
HEADER_LINE:REPattern = re_compile(r'^([^\:]+)\:(.+)$')
|
||||||
|
HTTP_BLOCKS:REPattern = re_compile(r'((?:(?!(?:(?:\r\n){2}|\n{2}|\r{2}))(?:.|[\r\n]+))+)(?:(?:(?:\r\n){2}|\n{2}|\r{2})((?:.+|[\r\n]+)*))?')
|
||||||
|
LAST_DIRECTORY:REPattern = re_compile(r'^(.*)[\/\\\\][^\/\\\\]*[\/\\\\]?$')
|
||||||
|
SLASHES:REPattern = re_compile(r'[\\\/]+')
|
||||||
|
TO_REGULAR_EXPRESSION:REPattern = re_compile(r'[\(\)\{\}\/\\\.\-\+\*\^\$\?\|\!\<\>\r\n\t]')
|
||||||
|
ROUTE_KEY:REPattern = re_compile(r'\\\{([a-z_][a-z0-9_]*)\\\}', RE_IGNORE_CASE)
|
||||||
|
ODBC_STRING_VARIABLE:REPattern = re_compile(r'\{([a-z_][a-z0-9_]*)\}|@([a-z0-9_]+)', RE_IGNORE_CASE)
|
||||||
|
ROUTE:REPattern = re_compile(r'^(?:([^\:]+)?\:)([^\s]+)\s+(?:([^\@\s]+)\@([^\s]+)|([^\s]+))(?:\s*(.+))?$', RE_IGNORE_CASE)
|
||||||
218
Python/Utils/Utils.py
Normal file
218
Python/Utils/Utils.py
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Any, Self, Optional, Sequence, Callable
|
||||||
|
from re import Match as REMatch
|
||||||
|
from inspect import FrameInfo, stack as get_stack
|
||||||
|
from Utils.Patterns import RE
|
||||||
|
from json import dumps as json_encode, loads as json_decode
|
||||||
|
|
||||||
|
class Utils:
|
||||||
|
|
||||||
|
SPECIAL_REGULAR_EXPRESSION_CHARACTERS:dict[str, str] = {
|
||||||
|
"\r" : "r",
|
||||||
|
"\n" : "n",
|
||||||
|
"\t" : "t"
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_dictionary(cls:type[Self], *items:Sequence[Any|None]) -> dict[str, Any|None]:
|
||||||
|
|
||||||
|
dictionary:dict[str, Any|None] = {}
|
||||||
|
item:Any|None
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
dictionary.update(item)
|
||||||
|
elif isinstance(item, (list, tuple)):
|
||||||
|
|
||||||
|
subitem:Any|None
|
||||||
|
|
||||||
|
for subitem in item:
|
||||||
|
dictionary.update(cls.get_dictionary(subitem))
|
||||||
|
|
||||||
|
return dictionary
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_keys(cls:type[Self], *items:Sequence[Any|None]) -> list[str]:
|
||||||
|
|
||||||
|
keys:list[str] = []
|
||||||
|
item:Any|None
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if isinstance(item, str):
|
||||||
|
RE.KEY.match(item) and keys.append(item)
|
||||||
|
elif isinstance(item, (list, tuple)):
|
||||||
|
|
||||||
|
subitem:Any|None
|
||||||
|
|
||||||
|
for subitem in item:
|
||||||
|
keys.extend(cls.get_keys(subitem))
|
||||||
|
|
||||||
|
return keys
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_dictionaries(cls:type[Self], *items:Sequence[Any|None]) -> list[dict[str, Any|None]]:
|
||||||
|
|
||||||
|
dictionaries:list[dict[str, Any|None]] = []
|
||||||
|
item:Any|None
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
dictionaries.append(item)
|
||||||
|
elif isinstance(item, (list, tuple)):
|
||||||
|
|
||||||
|
subitem:Any|None
|
||||||
|
|
||||||
|
for subitem in item:
|
||||||
|
dictionaries.extend(cls.get_dictionaries(subitem))
|
||||||
|
|
||||||
|
return dictionaries
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_value(cls:type[Self],
|
||||||
|
keys:str|Sequence[str],
|
||||||
|
inputs:dict[str, Any|None]|Sequence[Any|None],
|
||||||
|
default:Optional[Any] = None
|
||||||
|
) -> Any|None:
|
||||||
|
if len(cls.get_keys(keys)):
|
||||||
|
|
||||||
|
dictionary:dict[str, Any|None]
|
||||||
|
|
||||||
|
for dictionary in cls.get_dictionaries(inputs):
|
||||||
|
|
||||||
|
key:str
|
||||||
|
|
||||||
|
for key in cls.get_keys(keys):
|
||||||
|
if key in dictionary:
|
||||||
|
return dictionary[key]
|
||||||
|
return default
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_list(item:Any|None) -> list[Any|None]:
|
||||||
|
return item if isinstance(item, (list, tuple)) else [item]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def string_variables(cls:type[Self],
|
||||||
|
string:str,
|
||||||
|
inputs:dict[str, Any|None]|Sequence[Any|None],
|
||||||
|
default:Optional[str] = None
|
||||||
|
) -> str:
|
||||||
|
|
||||||
|
variables:dict[str, Any|None] = cls.get_dictionary(inputs)
|
||||||
|
|
||||||
|
def callback(matches:REMatch) -> str:
|
||||||
|
|
||||||
|
key:str = matches.group(1)
|
||||||
|
|
||||||
|
return (
|
||||||
|
str(variables[key]) if key in variables else
|
||||||
|
default if default is not None else
|
||||||
|
matches.group(0))
|
||||||
|
|
||||||
|
return RE.STRING_VARIABLE.sub(callback, string)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_texts(cls:type[Self], *items:list[Any|None]) -> list[str]:
|
||||||
|
|
||||||
|
texts:list[str] = []
|
||||||
|
item:Any|None
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if isinstance(item, str):
|
||||||
|
texts.append(item)
|
||||||
|
elif isinstance(item, (list, tuple)):
|
||||||
|
|
||||||
|
subitem:Any|None
|
||||||
|
|
||||||
|
for subitem in item:
|
||||||
|
texts.extend(cls.get_texts(subitem))
|
||||||
|
|
||||||
|
return texts
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_action_data(i:int = 0) -> dict[str, str|int]:
|
||||||
|
|
||||||
|
stack:FrameInfo = get_stack()[i]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"file" : stack.filename,
|
||||||
|
"method" : stack.function,
|
||||||
|
"line" : stack.lineno
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_regular_expression(cls:type[Self], string:str) -> str:
|
||||||
|
|
||||||
|
def callback(matches:REMatch) -> str:
|
||||||
|
|
||||||
|
character:str = matches.group(0)
|
||||||
|
|
||||||
|
return "\\" + (
|
||||||
|
cls.SPECIAL_REGULAR_EXPRESSION_CHARACTERS[character] if character in cls.SPECIAL_REGULAR_EXPRESSION_CHARACTERS else
|
||||||
|
character)
|
||||||
|
|
||||||
|
return RE.TO_REGULAR_EXPRESSION.sub(callback, string)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def json_decode(data:str) -> Any|None:
|
||||||
|
try:
|
||||||
|
return json_decode(data)
|
||||||
|
except Exception as _:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def json_encode(data:Sequence[Any|None]|dict[str, Any|None]) -> str|None:
|
||||||
|
try:
|
||||||
|
return json_encode(data)
|
||||||
|
except Exception as _:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_snake(string:str) -> str:
|
||||||
|
|
||||||
|
def callback(matches:REMatch) -> str:
|
||||||
|
|
||||||
|
upper:str|None = matches.group(1)
|
||||||
|
|
||||||
|
return "_" + upper.lower() if upper else "_"
|
||||||
|
|
||||||
|
return RE.TO_SNAKE.sub(callback, string).lower()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_function(string:str, _from:list[str] = []) -> Optional[Callable[[Any|None], Any|None]]:
|
||||||
|
|
||||||
|
item:Any|None
|
||||||
|
fragments:list[str] = string.split(".")
|
||||||
|
fragment:str
|
||||||
|
done:bool
|
||||||
|
|
||||||
|
for item in _from:
|
||||||
|
|
||||||
|
done = True
|
||||||
|
|
||||||
|
for fragment in fragments:
|
||||||
|
if done := hasattr(item, fragment):
|
||||||
|
item = getattr(item, fragment)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if done and callable(item):
|
||||||
|
return item
|
||||||
|
|
||||||
|
if done := fragments[0] in globals():
|
||||||
|
|
||||||
|
item = globals()[fragments[0]]
|
||||||
|
|
||||||
|
for fragment in fragments[1:]:
|
||||||
|
if done := hasattr(item, fragment):
|
||||||
|
item = getattr(item, fragment)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if done and callable(item):
|
||||||
|
return item
|
||||||
|
|
||||||
|
return None
|
||||||
@ -1,6 +1,32 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from NucelarMonitor import NucelarMonitor
|
from typing import Any
|
||||||
|
from Application.NucelarMonitor import NucelarMonitor
|
||||||
|
from Controllers.AgentsController import AgentsController
|
||||||
|
from Drivers.SQLServerDriver import SQLServerDriver
|
||||||
|
from Drivers.WebServerDriver import WebServerDriver
|
||||||
|
|
||||||
nucelar_monitor:NucelarMonitor = NucelarMonitor()
|
inputs:dict[str, dict[str, Any|None]] = {
|
||||||
|
"default_models" : {
|
||||||
|
"agents" : AgentsController,
|
||||||
|
"sql_server" : SQLServerDriver,
|
||||||
|
"web_server" : WebServerDriver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
from secrets import secrets as custom_secrets
|
||||||
|
|
||||||
|
for key, value in dict(custom_secrets).items():
|
||||||
|
if key not in inputs or isinstance(inputs[key], dict):
|
||||||
|
inputs[key] = value
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
for subkey, subvalue in value.items():
|
||||||
|
inputs[key][subkey] = subvalue
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
nucelar_monitor:NucelarMonitor = NucelarMonitor(inputs)
|
||||||
3
Tools/NucelarMonitor.sh
Executable file
3
Tools/NucelarMonitor.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cd `dirname $(readlink -f "$0")`/../Python
|
||||||
|
python3 "run.py"
|
||||||
Loading…
Reference in New Issue
Block a user