Added libs

This commit is contained in:
Lucas
2026-01-25 13:55:46 +10:00
parent 575c682afc
commit f70af3c4ea
229 changed files with 26983 additions and 0 deletions

2
noSys/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
__pycache__
noSys.zip

1
noSys/.mtimes.json Normal file
View File

@@ -0,0 +1 @@
{".gitignore": 1748256258.9985344, "api.http": 1750139941.0187607, "connections.py": 1757738624.594908, "dataManager.py": 1766792003.8798084, "dispatcher.py": 1757802996.961887, "events.py": 1757056638.2672198, "index.html": 1752913927.847083, "modules.py": 1757507552.9273007, "networks.py": 1757748986.1507368, "networksApiBlueprint.py": 1757583651.2527168, "noSys.py": 1757749429.9569714, "noSys.zip.bkp": 1755913936.6262422, "noSysApiBlueprint.py": 1757743519.0319538, "noSysCore.py": 1757838163.5684075, "noSysModule.py": 1757065213.3453174, "noSysModuleServer.py": 1756720592.4303212, "noSysSocketio.py": 1756798784.7306013, "peers.py": 1757802573.2807248, "servers.py": 1756114998.854464, "users.py": 1757585178.0426896, "vue\\router.js": 1753274355.3049414, "vue\\api\\noSysApi.js": 1757586249.0933578, "vue\\api\\socketEvents.js": 1753577297.9669068, "vue\\components\\ConnectionsTab.vue": 1757496047.4013255, "vue\\components\\NetworksTabs.vue": 1757748327.563559, "vue\\stores\\noSysStore.js": 1757498732.9446464, "vue\\views\\HomeView.vue": 1757495074.6380558}

24
noSys/api.http Normal file
View File

@@ -0,0 +1,24 @@
@hostname = http://127.0.0.1
@port = 5001
@hostServer = {{hostname}}:{{port}}
###
POST {{hostServer}}/users HTTP/1.1
content-type: application/json
{"password":"MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MTI=", "data":{"proof_of_work":"eYnU*@"}}
###{"password":"I0x1Y2FzR2FicmllbFZhekRvc1NhbnRvc0luYWNpbyE=", "data":{"proof_of_work":"eYnU*@"}}
### A4DZSk+TlR+4w39MbiIAQbti+N0H1QlJEhRH2DI6Iubj
###
GET {{hostServer}}/users
###
GET {{hostServer}}/users/A14%2FRek5z78U1rYC%2BWLvU%2FifnsX43o0tjnexmYdlXsjY
###
POST {{hostServer}}/users/A14%2FRek5z78U1rYC%2BWLvU%2FifnsX43o0tjnexmYdlXsjY/sign HTTP/1.1
content-type: application/json
{"data":"Test"}

70
noSys/connections.py Normal file
View File

@@ -0,0 +1,70 @@
from .peers import Peer, ConnectionState
from .events import Events, DynamicEvents
from libs.fspn.utils.observable import Event
from libs.fspn.protocol.connection import Connection, EVENTS as CONNECTION_EVENTS
from libs.app.common.logging import get_logger
logger = get_logger("connections")
class ConnectionManager:
"""Handles peer connections"""
# TODO Maybe peerManager receveid events and change status, reconnections, disconnections, etc. This class can just manage limits of connections, idk!
def __init__(self, nosys_core):
from .noSysCore import NoSysCore
self.nosys_core:NoSysCore = nosys_core
def connect(self, address, user_id, bind_address=("0.0.0.0", 0)):
peer, connect = self.create_connection(address, user_id, bind_address)
connect()
return peer
def create_connection(self, address, user_id, bind_address=("0.0.0.0", 0)):
user = self.nosys_core.users.get_user(user_id)
if not user:
raise Exception("Cannot connect, user missing")
connection = Connection(user.public_key, self.nosys_core.modules.pmc)
connection.subscribe_event(CONNECTION_EVENTS.ON_CONNECTION, self._on_connection)
connection.subscribe_event(CONNECTION_EVENTS.ON_CONNECTION_ERROR, self._on_connection_error)
connection.subscribe_event(CONNECTION_EVENTS.ON_DISCONNECTION, self._on_disconnection)
connection.subscribe_event(CONNECTION_EVENTS.ON_MESSAGE, self.nosys_core.dispatcher.on_message)
peer = self.nosys_core.peers.create_peer(connection)
self.nosys_core.peers.add_peer(peer)
def connect():
connection.connect(address, bind_address)
return peer, connect
def disconnect(self, peer_id: str):
self.nosys_core.peers.remove_peer(peer_id)
def _on_connection(self, event:Event):
connection:Connection = event.source
logger.debug(f'New connection {connection.address} ID: {connection.id}')
peer = self.nosys_core.peers.get_peer(connection.id)
peer.state = ConnectionState.CONNECTED
self.nosys_core.fire_event(Events.PEER_CONNECTED, peer=peer)
self.nosys_core.fire_event(DynamicEvents.peer_connection(peer.id), peer=peer)
logger.debug(f'Peer connected: {peer.get_my_user()} -> {peer.get_peer_user()}')
def _on_connection_error(self, event:Event):
connection:Connection = event.source
error = event.error
peer = self.nosys_core.peers.get_peer(connection.id)
peer.state = ConnectionState.DISCONNECTED
self.nosys_core.fire_event(Events.PEER_CONNECTION_ERROR, peer=peer, error=error)
self.nosys_core.fire_event(DynamicEvents.peer_connection_error(peer.id), peer=peer, error=error)
def _on_disconnection(self, event:Event):
connection:Connection = event.source
logger.debug('Disconnection', event.__dict__)
peer = self.nosys_core.peers.get_peer(connection.id)
peer.state = ConnectionState.DISCONNECTED
self.nosys_core.fire_event(Events.PEER_DISCONNECTED, peer=peer)
self.nosys_core.fire_event(DynamicEvents.peer_disconnection(peer.id), peer=peer)

145
noSys/dataManager.py Normal file
View File

@@ -0,0 +1,145 @@
from typing import Dict, List, Optional, Any
from libs.app.common.store import DataStore
from libs.fspn.utils.sha256_util import hash_string
class DataManager():
def __init__(self, nosys_core):
from .noSysCore import NoSysCore
self.nosys_core:NoSysCore = nosys_core
self.store = DataStore(path="noSys/data.json", default_data={"users":[], "rendezvous":[], "networks":[]})
# ------------------- USER CRUD -------------------
def add_user(self, user_id: str):
if self.get_user(user_id):
raise ValueError(f"User {user_id} already exists")
user = {"id": user_id, "networks":[]}
self.store.add_item("users", user, unique=True, id_field="id", id=user_id)
return user
def get_user(self, user_id: str):
return self.store.get_item("users", "id", user_id)
def list_users(self):
return self.store.list_items("users")
def delete_user(self, user_id: str) -> bool:
return self.store.remove_item("users", "id", user_id)
def update_user(self, user_id: str, updates: Dict[str, Any]) -> bool:
return self.store.update_item("users", "id", user_id, updates)
# ------------------- USER NETWORK MANAGEMENT -------------------
def user_add_network(self, user_id: str, network_id: str) -> bool:
user = self.get_user(user_id)
if not user:
return False
if network_id not in user.get("networks"):
user.setdefault("networks").append(network_id)
return self.update_user(user_id, {"networks": user["networks"]})
return False
def user_remove_network(self, user_id: str, network_id: str) -> bool:
user = self.get_user(user_id)
if not user:
return False
if network_id not in user.get("networks"):
new_networks = [n for n in user["networks"] if n != network_id]
return self.update_user(user_id, {"networks": new_networks})
return False
def user_list_networks(self, user_id: str) -> List[str]:
user = self.get_user(user_id)
return user["networks", []] if user else []
# ------------------- RENDEZVOUS CRUD -------------------
def add_rendezvous(self, rv_id: str, address: str):
if self.get_rendezvous(rv_id):
raise ValueError(f"Rendezvous {rv_id} already exists")
rv = {"id": rv_id, "address": address}
self.store.add_item("rendezvous", rv, unique=True, id_field="id", id=rv_id)
def get_rendezvous(self, rv_id: str):
return self.store.get_item("rendezvous", "id", rv_id)
def list_rendezvous(self):
return self.store.list_items("rendezvous")
def delete_rendezvous(self, rv_id: str) -> bool:
return self.store.remove_item("rendezvous", "id", rv_id)
def update_rendezvous(self, rv_id: str, updates: Dict[str, Any]) -> bool:
return self.store.update_item("rendezvous", "id", rv_id, updates)
# ------------------- NETWORK CRUD -------------------
def _default_config(self, net_type: str) -> Dict[str, Any]:
defaults = {
"test_network": {
"auto_connect": True,
"min_connections": 10,
"max_connections": 20,
"max_store_size": 1000,
"max_message_size": 64_000,
"message_ttl": 86400,
"ack_required": True,
"min_pow": 4
}
}
return defaults.get(net_type, {})
def _generate_id(self, name, net_type):
payload = f"{name}:{net_type}".encode()
return hash_string(payload)
def create_network(
self,
name: str,
description: str,
net_type: str,
modules: Optional[List[str]] = None,
config: Optional[Dict[str, Any]] = None,
):
network_id = self._generate_id(name, net_type)
if self.get_network(network_id):
raise ValueError(f"Network {network_id} already exists")
final_config = {**self._default_config(net_type), **(config or {})}
network = {
"id": network_id,
"name": name,
"description": description,
"type": net_type,
"modules": modules or [],
"peers": [],
"rendezvous": [],
"config": final_config
}
self.store.add_item("networks", network, unique=True, id_field="id", id=network_id)
return network
def get_network(self, network_id: str) -> Optional[Dict[str, Any]]:
return self.store.get_item("networks", "id", network_id)
def list_networks(self) -> List[Dict[str, Any]]:
return self.store.list_items("networks")
def delete_network(self, network_id: str) -> bool:
return self.store.remove_item("networks", "id", network_id)
def update_network(self, network_id: str, updates: Dict[str, Any]) -> bool:
return self.store.update_item("networks", "id", network_id, updates)
def network_assign_rendezvous(self, network_id: str, rv_id: str):
network = self.store.get_item("networks", "id", network_id)
if not network:
return False
if rv_id not in network.get("rendezvous", []):
network.setdefault("rendezvous", []).append(rv_id)
return self.store.update_item("networks", "id", network_id, {"rendezvous": network["rendezvous"]})
return False
# ------------------- NETWORK QUERY -------------------
def network_find_by_module(self, module_name: str) -> List[Dict[str, Any]]:
"""Return all networks that use a given module"""
return [n for n in self.list_networks() if module_name in n.get("modules", [])]

73
noSys/dispatcher.py Normal file
View File

@@ -0,0 +1,73 @@
import json
from libs.app.common.logging import get_logger
from .events import DynamicEvents
logger = get_logger("dispatcher")
class Dispatcher:
"""Responsible for sending messages/binary, broadcasting and dispatching received messages for dynamic events."""
def __init__(self, nosys_core):
from .noSysCore import NoSysCore
self.nosys_core: NoSysCore = nosys_core
# -------- Sending --------
def send_binary(self, data: bytes, peer_id, to_module: tuple[str, str], meta=None, encrypted: bool = True):
meta = meta or {}
peer = self.nosys_core.peers.get_peer(peer_id)
if peer and peer.has_module(to_module):
app_body = {
"app": {"lib": to_module[0], "module": to_module[1]},
"meta": meta,
}
peer.connection.send_binary(data=data, meta=app_body, encrypted=encrypted)
def send_message(self, message, peer_id, to_module: tuple[str, str], encrypted: bool = True):
peer = self.nosys_core.peers.get_peer(peer_id)
if peer:
if not peer.is_connected():
raise Exception(f"Cannot send message to a disconnected peer: {peer_id}")
app_body = {
"app": {"lib": to_module[0], "module": to_module[1]},
"data": message,
}
peer.connection.send_message(message=json.dumps(app_body), encrypted=encrypted)
def broadcast_message(self, message, to_module: tuple[str, str], exclude: list[str] | None = None,
encrypted: bool = True):
exclude = exclude or []
for peer in self.nosys_core.peers.get_online_peers():
if peer.peer_id not in exclude:
self.send_message(message, peer.peer_id, to_module, encrypted)
# -------- Receiving --------
def on_message(self, event):
"""Receives event from Connection and fires dynamic event `module_message_`"""
try:
if "meta" in event.message:
app_body = event.message["meta"]
module_meta = app_body.get("meta", {})
data = event.message["data"]
else:
app_body = json.loads(event.message["data"])
module_meta = {}
data = app_body["data"]
module_key = (app_body["app"]["lib"], app_body["app"]["module"])
logger.debug(
"%s receiving from %s\n Module: %s\n Meta: %s\n Data: %s",
event.source.bind_address,
event.source.address,
module_key,
module_meta,
data,
)
evt_name = DynamicEvents.module_message(module_key[0], module_key[1])
peer = self.nosys_core.peers.get_peer(event.source.id)
self.nosys_core.fire_event(evt_name, peer=peer, module=module_key, data=data, meta=module_meta)
except Exception:
logger.exception("ERROR while dispatching incoming message")

45
noSys/events.py Normal file
View File

@@ -0,0 +1,45 @@
class Events:
STARTED = "started"
START_ERROR = "error_start"
READY = "ready"
USER_ADDED = "user_added"
USER_REMOVED = "user_removed"
PEER_CONNECTED = "peer_connected"
PEER_CONNECTION_ERROR = "peer_connection_error"
PEER_DISCONNECTED = "peer_disconnected"
class DynamicEvents:
@staticmethod
def peer_connection(id:str) -> str:
return f"peer_connection_{id}"
@staticmethod
def peer_connection_error(id:str) -> str:
return f"peer_connection_error_{id}"
@staticmethod
def peer_disconnection(id:str) -> str:
return f"peer_disconnection_{id}"
@staticmethod
def module_message(lib: str, module: str) -> str:
return f"module_message_{lib}_{module}"
@staticmethod
def module_connection(lib: str, module: str) -> str:
return f"module_connection_{lib}_{module}"
@staticmethod
def module_disconnection(lib: str, module: str) -> str:
return f"module_disconnection_{lib}_{module}"
@staticmethod
def network_connection(network: str) -> str:
return f"network_connection_{network}"
@staticmethod
def network_disconnection(network: str) -> str:
return f"network_disconnection_{network}"
class ServerEvents:
TEST = "test"

179
noSys/index.html Normal file
View File

@@ -0,0 +1,179 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>NoSys</title>
<style>
body{
background-color: #2c2c2c;
color: white;
}
</style>
<script>
const noSysApiUrl = "http://127.0.0.1:5050/noSys";
var connectionClicked = null;
function connectAddress(){
const ip = document.getElementById("ip").value;
const port = document.getElementById("port").value;
const data = {"user": "A4DZSk+TlR+4w39MbiIAQbti+N0H1QlJEhRH2DI6Iubj", "address": {"ip": ip,"port": port}};
const requestOptions = {
method: 'POST',
headers: {'Content-Type': 'application/json',},
body: JSON.stringify(data),
};
fetch(noSysApiUrl+"/peers", requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
listConnections();
})
.catch(error => {
console.error
('Error:', error);
});
}
function sendMessage(){
messageElement = document.getElementById("message")
message = messageElement.value;
if (connectionClicked && message){
const data = {"message":{"action":"test", "data":message},"toModule": {"package": "noSys","module": "noSys"},"encrypted": true};
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
fetch(noSysApiUrl+"/peers/"+connectionClicked, requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log(data);
addMessage(connectionClicked, message, false)
messageElement.value = ""
})
.catch(error => {
console.error
('Error:', error);
});
}
}
function setConnectionClicked(id, address, bindAddress){
connectionClicked = id;
document.getElementById("messageTo").value = address;
document.getElementById("messageFrom").value = bindAddress;
}
function listConnections(){
const requestOptions = {
method: 'GET',
headers: {}
};
fetch(noSysApiUrl+"/peers", requestOptions)
.then(response => {
if (!response.ok){
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
uList = document.getElementById("ulConnections");
uList.replaceChildren();
data.forEach((element, index) => {
console.log(element);
var li = document.createElement("li");
li.setAttribute("id", element.id);
li.addEventListener("click", function(){
setConnectionClicked(this.id, element.address, element.bindAddress);
});
li.appendChild(document.createTextNode("ID:"+element.id+" | Connected to:"+element.address+" | Address bound:"+element.bindAddress+" | User:"+element.user+" | Status:"+element.status));
uList.appendChild(li);
});
})
.catch(error => {
console.error('Error:', error);
});
}
function addMessage(connectionId, message, from){
uList = document.getElementById("ulMessages");
var li = document.createElement("li");
const direction = from ? "FROM" : "TO";
text = " "+direction+" ConnectionId: "+connectionId+"\n"+message+"\n\n";
li.appendChild(document.createTextNode(text));
uList.appendChild(li);
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
<script type="text/javascript">
var socket = io(noSysApiUrl);
socket.on('connect', function() {
console.log('Connected to server');
});
socket.on('message', function(msg) {
console.log('Message received: ' + msg);
});
socket.on('test', function(msg) {
console.log(msg);
addMessage(msg.connectionId, msg.message, true)
});
socket.on('disconnect', function() {
console.log('Disconnected from server');
});
</script>
</head>
<body>
<div>
<input id="ip" type="text" value="n0sys.duckdns.org">
<input id="port" type="text" value="30331">
<button onclick="connectAddress()">Connect</button>
</div>
<div>
<input id="message" type="text" value="">
<button onclick="sendMessage()">Send message</button>
<label>TO</label>
<input id="messageTo" type="text" value="">
<label>FROM</label>
<input id="messageFrom" type="text" value="">
</div>
<pre>
<ul id="ulMessages">
</ul>
</pre>
<div>
<ul id="ulConnections">
</ul>
</div>
</body>
<script>
listConnections()
</script>
</html>

23
noSys/info.json Normal file
View File

@@ -0,0 +1,23 @@
{
"id": "noSys",
"version": 0.14,
"modules": [
{
"id": "noSys",
"version": 0
},
{
"id": "networks",
"version": 0
}
],
"frontend": "vue",
"dependencies": [
{
"id": "fspn"
},
{
"id": "api"
}
]
}

97
noSys/modules.py Normal file
View File

@@ -0,0 +1,97 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .noSysCore import NoSysCore
import sys
import importlib
import logging
from typing import Dict, Tuple, Any
from .events import Events
from .noSysModule import NoSysModule
from libs.app.common.logging import get_logger
from libs.fspn.protocol.security import Pmc
logger = get_logger("moduleManager", buffer=True)
class ModuleManager:
"""Loads, unloads, and manages modules dynamically."""
def __init__(self, nosys_core: "NoSysCore"):
self.nosys_core = nosys_core
self.modules: Dict[str, NoSysModule] = {}
from libs.api.api import Api
self.api:Api = None
self.pmc:Pmc = None
self.load_from_config()
def get(self, package_id, module_id) -> NoSysModule:
return self.modules.get((package_id, module_id))
def load_from_config(self):
libs = self.nosys_core.config.get("app", "libs")
for lib in libs:
if lib.get("enabled", True):
for module in self.nosys_core.config.get(lib.get("id"), "info", "modules"):
self.load(lib.get("id"), module.get("id"))
def load(self, package_id: str, module_id: str):
"""Dynamically load a module and initialize it."""
logger.debug(f"Importing module: {package_id}.{module_id}")
key = (package_id, module_id)
if key in self.modules:
return self.modules[key]
path = f"libs.{package_id}.{module_id}"
try:
if path in sys.modules:
module = importlib.reload(sys.modules[path])
else:
module = importlib.import_module(path)
# Prefer explicit "get_module_class" function if provided
if hasattr(module, "get_module_class"):
clss = module.get_module_class()
else:
# Fallback to naming convention: Uppercase the first letter of the module
class_name = module_id[:1].upper()+module_id[1:]
clss = getattr(module, class_name)
if not issubclass(clss, NoSysModule):
raise TypeError(f"{clss} must inherit from NoSysModule")
instance = clss(self.nosys_core)
self.modules[key] = instance
logger.debug(f"Module loaded: {path} ({clss.__name__})")
return instance
except Exception as e:
logger.exception(f"Failed to load module {path}: {e}")
return None
def unload(self, package_id: str, module_id: str):
"""Unload a module and call its teardown hook if available."""
key = (package_id, module_id)
if key not in self.modules:
return
module:NoSysModule = self.modules.pop(key)
try:
module.teardown_module()
except Exception as e:
logger.warning(f"Error during teardown of {key}: {e}")
logger.debug(f"Module unloaded: {key}")
def reload(self, package_id: str, module_id: str):
"""Reload a module (unload + load)."""
self.unload(package_id, module_id)
return self.load(package_id, module_id)
def setup(self):
for module in self.modules.values():
module.setup()

217
noSys/networks.py Normal file
View File

@@ -0,0 +1,217 @@
from typing import Dict, List, Optional, Any
import time
import random
import threading
from libs.fspn.utils.wrapper_util import threaded
from .noSysModule import NoSysModule
from libs.app.common.store import DataStore
from libs.app.common.logging import get_logger
from libs.fspn.utils.sha256_util import hash_string
from libs.rendezvous.rendezvousClient import RendezvousClient, RendezvousClientEvents as RendezvousEvents
from .peers import Peer
from .events import Events, DynamicEvents
from .networksApiBlueprint import Blueprint
from enum import Enum
class NetworkStatus(Enum):
IDLE = "idle"
CONNECTING = "connecting"
HEALTHY = "healthy"
DEGRADED = "degraded"
FAILED = "failed"
logger = get_logger("networks")
class Networks(NoSysModule):
""" SOMETHING HERE."""
def __init__(self, nosys_core):
super().__init__(nosys_core)
self.nosys_core.subscribe_event(Events.USER_ADDED, self.on_user_added)
# self.nosys_core.subscribe_event(Events.PEER_CONNECTED, self.on_peer_connected)
# self.nosys_core.subscribe_event(Events.PEER_DISCONNECTED, self.on_peer_disconnected)
# self.nosys_core.subscribe_event(Events.PEER_CONNECTION_ERROR, self.on_peer_connection_error)
self.network_states = {}
def setup(self):
self.rendezvous_client:RendezvousClient = self.nosys_core.modules.get("rendezvous", "rendezvousClient")
self.rendezvous_client.subscribe_event(RendezvousEvents.SERVER_CONNECTED, self.on_server_connected)
self.rendezvous_client.subscribe_event(RendezvousEvents.SERVER_DISCONNECTED, self.on_server_disconnected)
self.rendezvous_client.subscribe_event(RendezvousEvents.SERVER_CONNECTION_ERROR, self.on_server_connection_error)
self.nosys_core.modules.api.register_blueprint(Blueprint(self).blueprint)
# self.socketio = HandlerSocketio(self)
# self.nosys_core.modules.api.register_socketio(self.socketio)
def user_add_network(self, user_id, network_id):
self.nosys_core.data.user_add_network(user_id, network_id)
def user_remove_network(self, user_id, network_id):
self.nosys_core.data.user_remove_network(user_id, network_id)
def on_user_added(self, event):
user_id:str = event.user_id
user_data = self.nosys_core.data.get_user(user_id)
for network_id in user_data.get("networks", []):
self.network_states.get(network_id)["users"].append(user_id)
def on_nosys_ready(self, event):
self.manage_networks()
def set_network(self, network):
network_id = network["id"]
if not network:
raise ValueError(f"Network {network_id} not found")
if network_id not in self.network_states:
self.nosys_core.subscribe_event(DynamicEvents.network_connection(network_id), self.on_network_connection)
self.nosys_core.subscribe_event(DynamicEvents.network_disconnection(network_id), self.on_network_disconnection)
self.network_states[network_id] = {
"id": network_id,
"users" : [],
"peers": [],
"rendezvous": [],
"status": NetworkStatus.IDLE.value,
"managed": self.is_network_auto_start(network_id),
"tries": 0
}
return self.network_states[network_id]
def is_network_auto_start(self, network_id):
network = self.nosys_core.data.get_network(network_id)
return network.get("config", {}).get("auto_connect", False)
def set_managed(self, network_id, value:bool):
state = self.network_states[network_id]
state["managed"] = value
@threaded
def manage_networks(self):
for network in self.networks:
self.set_network(network)
while True:
for network_id, state in self.network_states.items():
if state["managed"]:
self.manage_network(network_id)
time.sleep(30)
def manage_network(self, network_id):
state = self.network_states[network_id]
network = self.nosys_core.data.get_network(network_id)
if state["status"] == NetworkStatus.IDLE.value:
print(NetworkStatus.IDLE.value)
self.connect_network_rendezvous_servers(network)
elif state["status"] == NetworkStatus.CONNECTING.value:
print(NetworkStatus.CONNECTING.value)
elif state["status"] == NetworkStatus.DEGRADED.value:
print(NetworkStatus.DEGRADED.value)
self.request_new_peer(network_id=network_id)
elif state["status"] == NetworkStatus.HEALTHY.value:
print(NetworkStatus.HEALTHY.value)
elif state["status"] == NetworkStatus.FAILED.value:
print(NetworkStatus.FAILED.value)
self.set_managed(network_id, False)
def connect_network_rendezvous_servers(self, network):
network_id = network.get("id")
state = self.network_states.get(network_id)
if state["users"]:
for rendezvous_id in network.get("rendezvous"):
rendezvous = self.nosys_core.data.get_rendezvous(rendezvous_id)
host_port = rendezvous.get("address").split(':')
address = (host_port[0], int(host_port[1])) if len(host_port) > 1 else (host_port[0], 0)
peer = self.rendezvous_client.connect_to_server(address, random.choice(state["users"]))
state["rendezvous"].append(peer.id)
else:
logger.debug(f"User missing to connect to the rendezvous servers of network {network_id}")
def on_server_connected(self, event):
peer:Peer = event.peer
peer_id = peer.id
for network_id, state in self.network_states.items():
if peer_id in state["rendezvous"]:
print(f"Rendezvous {peer_id} connected for network {network_id}")
def on_server_connection_error(self, event):
peer:Peer = event.peer
peer_id = peer.id
for network_id, state in self.network_states.items():
if peer_id in state["rendezvous"]:
state["rendezvous"].remove(peer_id)
print(f"Rendezvous {peer_id} error for network {network_id}")
if not state["rendezvous"]:
print(f"All rendezvous failed")
state["tries"]+=1
self._recalc_network_status(network_id)
def on_server_disconnected(self, event):
peer:Peer = event.peer
peer_id = peer.id
for network_id, state in self.network_states.items():
if peer_id in state["rendezvous"]:
state["rendezvous"].remove(peer_id)
print(f"Rendezvous {peer_id} disconnected for network {network_id}")
def on_network_connection(self, event):
network_id:str = event.network_id
peer:Peer = event.peer
self.network_states[network_id]["peers"].append(peer.id)
self._recalc_network_status(network_id)
def on_network_disconnection(self, event):
network_id:str = event.network_id
peer:Peer = event.peer
network_states = self.network_states.get(network_id)
network_states["peers"].remove(peer.id)
self._recalc_network_status(network_id)
def request_new_peer(self, network_id, peer_id=None):
network_states = self.network_states[network_id]
if not peer_id:
peer_id = random.choice(network_states["peers"])
self.rendezvous_client.send_get_random_peer(peer_id, network_id)
def _set_status(self, network_id, status: NetworkStatus, details=None):
state = self.network_states.get(network_id, {})
state["status"] = status.value
if details:
state.update(details)
def _recalc_network_status(self, network_id):
state = self.network_states.get(network_id)
peers = state.get("peers", [])
network = self.nosys_core.data.get_network(network_id)
min_conn = network["config"]["min_connections"]
if len(peers) >= min_conn:
self._set_status(network_id, NetworkStatus.HEALTHY)
elif peers:
self._set_status(network_id, NetworkStatus.DEGRADED)
elif state["rendezvous"]:
self._set_status(network_id, NetworkStatus.CONNECTING)
elif state["tries"]>=3:
self._set_status(network_id, NetworkStatus.FAILED)
else:
self._set_status(network_id, NetworkStatus.IDLE)
if state["managed"] == True:
self.manage_network(network_id)
def is_network_healthy(self, network_id):
return self.network_states.get(network_id, {}).get("status") == NetworkStatus.HEALTHY.value

View File

@@ -0,0 +1,51 @@
from flask import Blueprint, make_response, request, jsonify, abort
from flask_socketio import SocketIO, emit, join_room, leave_room
import os, signal, json, time
import logging
from threading import Thread
from libs.api.apiBlueprint import ApiBlueprint
class Blueprint(ApiBlueprint):
def routes(self):
from .networks import Networks
self.module:Networks = self.module
@self.blueprint.route('/')
def show():
return self.module.name
@self.blueprint.route('/networks')
def networks():
if request.method == "GET":
networks = []
for network_id, state in self.module.network_states.items():
obj = {"data":self.module.nosys_core.data.get_network(network_id), "state":state}
networks.append(obj)
return jsonify(networks)
elif request.method == "POST":
content:dict = request.json
user_id = content["user_id"]
network_id = content["network_id"]
self.module.user_add_network(user_id, network_id)
return jsonify({"network_id":network_id})
@self.blueprint.route("/networks/<path:network_id>", methods=["GET", "PUT", "DELETE"])
def network(network_id):
state = self.module.network_states.get(network_id)
if request.method == "GET":
return jsonify(state)
elif request.method == "PUT":
# TODO Edit network config
return jsonify({"networkId":network_id})
elif request.method == "DELETE":
content:dict = request.json
user_id = content["user_id"]
self.module.user_remove_network(user_id, network_id)
return jsonify({"networkId":network_id})

70
noSys/noSys.py Normal file
View File

@@ -0,0 +1,70 @@
import time
from .noSysModule import NoSysModule
from .peers import Peer
from .events import Events, DynamicEvents
from libs.noSys.noSysApiBlueprint import Blueprint
from libs.noSys.noSysSocketio import HandlerSocketio
class NoSys(NoSysModule):
"""Core module of NoSys that handles system-level messages."""
def __init__(self, nosys_core):
super().__init__(nosys_core)
self.nosys_core.subscribe_event(Events.USER_ADDED, self.on_user_added)
self.nosys_core.subscribe_event(Events.PEER_CONNECTED, self.on_peer_connected)
def setup(self):
self.nosys_core.modules.api.register_blueprint(Blueprint(self).blueprint)
self.socketio = HandlerSocketio(self)
self.nosys_core.modules.api.register_socketio(self.socketio)
def on_nosys_ready(self, event):
pass
def on_user_added(self, event):
user_id:str = event.user_id
user_data = self.nosys_core.data.get_user(user_id)
if not user_data:
user_data = self.nosys_core.data.add_user(user_id)
def on_peer_connected(self, event):
peer:Peer = event.peer
self.send_info(peer.id)
def on_module_message(self, event):
handler_action = getattr(self, 'on_'+event.data.get("action"))
handler_action(event)
def send_info(self, peer_id):
modules = []
for key, module in self.nosys_core.modules.modules.items():
modules.append({"package":module.package_id, "module":module.module_id})
networks = []
for network in self.nosys_core.data.list_networks():
networks.append({"id":network.get("id")})
self.nosys_core.dispatcher.send_message({'action':'info','modules':modules, 'networks':networks}, peer_id, self.id)
def on_info(self, event):
peer: Peer = event.peer
modules = event.data['modules']
for module in modules:
lib = module.get("package")
module = module.get("module")
peer.modules[(lib, module)] = module
self.nosys_core.fire_event(DynamicEvents.module_connection(lib, module), peer=event.peer)
networks = event.data['networks']
for network in networks:
network_id = network.get("id")
peer.networks[network_id] = network
my_net = self.nosys_core.data.get_network(network_id)
if my_net:
self.nosys_core.fire_event(DynamicEvents.network_connection(network_id), network_id=network_id, peer=event.peer)
def on_module_connection(self, event):
pass

BIN
noSys/noSys.zip.bkp Normal file

Binary file not shown.

103
noSys/noSysApiBlueprint.py Normal file
View File

@@ -0,0 +1,103 @@
from flask import Blueprint, make_response, request, jsonify, abort
from flask_socketio import SocketIO, emit, join_room, leave_room
import os, signal, json, time
import logging
from threading import Thread
from libs.api.apiBlueprint import ApiBlueprint
class Blueprint(ApiBlueprint):
def routes(self):
from .noSys import NoSys
self.nosys:NoSys = self.module
from .noSysCore import NoSysCore
self.nosys_core = self.nosys.nosys_core
@self.blueprint.route('/')
def show():
return self.nosys.name
@self.blueprint.route('/users')
def users():
if request.method == "GET":
users = []
for user in self.nosys_core.users.users.values():
user_data = self.nosys.nosys_core.data.get_user(user.id)
users.append(user_data)
return jsonify(users)
@self.blueprint.route("/users/<path:user_id>", methods=["GET", "POST", "DELETE"])
def user(user_id):
if request.method == "GET":
user_data = self.nosys.nosys_core.data.get_user(user_id)
return jsonify(user_data)
@self.blueprint.route("/users/<path:user_id>/networks", methods=["GET", "POST", "DELETE"])
def user_networks(user_id):
if request.method == "GET":
user_networks = self.nosys.nosys_core.data.get_user(user_id)["networks"]
return jsonify(user_networks)
elif request.method == "POST":
content:dict = request.json
return jsonify()
@self.blueprint.route("/peers", methods=["GET", "POST"])
def peers():
if request.method == "GET":
response = []
for peer in self.nosys_core.peers.peers.values():
response.append({
"id":peer.id, "address":f"{peer.connection.address[0]}:{peer.connection.address[1]}",
"bindAddress":f"{peer.connection.bind_address[0]}:{peer.connection.bind_address[1]}",
"user":peer.connection.security.user, "status":peer.connection.status.name
})
return jsonify(response)
elif request.method == "POST":
content:dict = request.json
address = (content["address"]["ip"], int(content["address"]["port"]))
bind_address = ("0.0.0.0", 0)
if content.get("bindAddress"):
bind_address = (content["bindAddress"]["ip"], int(content["bindAddress"]["port"]))
user_pk = content["user"]
connection_id = self.nosys_core.connections.connect(address=address, user_id=user_pk, bind_address=bind_address)
return jsonify({"connectionId":connection_id})
@self.blueprint.route("/peers/<path:connection_id>", methods=["GET", "POST", "DELETE"])
def peer(connection_id):
peer = self.nosys_core.peers.peers.get(connection_id)
if not peer:
return jsonify({})
if request.method == "GET":
return jsonify({
"id":peer.id, "address":f"{peer.connection.address[0]}:{peer.connection.address[1]}",
"bindAddress":f"{peer.connection.bind_address[0]}:{peer.connection.bind_address[1]}",
"user":peer.connection.security.user, "status":peer.connection.status.name
})
elif request.method == "POST":
content:dict = request.json
message = content.get("message")
to_module = (content.get("toModule")["package"],content.get("toModule")["module"])
encrypted = content.get("encrypted")
self.nosys_core.dispatcher.send_message(message, peer.id, to_module, encrypted)
return jsonify({"connectionId":connection_id})
elif request.method == "DELETE":
peer.connection.close_connection()
return jsonify({"connectionId":connection_id})
@self.blueprint.route('/restart')
def restart():
if request.method == "GET":
self.nosys_core.restart_app()
return jsonify({})
@self.blueprint.route('/test')
def test():
if request.method == "GET":
self.nosys_core.test()
return jsonify({})

142
noSys/noSysCore.py Normal file
View File

@@ -0,0 +1,142 @@
from libs.fspn.utils.wrapper_util import *
from libs.fspn.utils.observable import Observable, Event
from libs.fspn.protocol.server import Server
from libs.fspn.protocol.connection import Connection, EVENTS as CONNECTION_EVENTS, STATUS as CONNECTION_STATUS
from libs.fspn.protocol.security import Security, Pmc
from libs.app.updater import Updater
# from libs.api.api import Api
# from .zechoSocketio import HandlerSocketio
import pathlib
from enum import Enum, auto
import importlib
import logging, traceback
import json
import random, time
import secrets
import os
import sys
from libs.app.common.logging import get_logger, get_logger_buffer
from libs.app.common.paths import ROOT_DIR
from .peers import PeerManager
from .users import UserManager
from .modules import ModuleManager
from libs.app.common.config import Config
from .events import Events
from .connections import ConnectionManager
from .dispatcher import Dispatcher
from .servers import ServerManager
from .dataManager import DataManager
from .networks import Networks
logger = get_logger(buffer=True)
class NoSysCore(Observable):
def __init__(self):
super().__init__()
def start(self):
logger.info(f"--------------- Starting NoSys ---------------")
self.peers = PeerManager(self)
self.users = UserManager(self)
self.config = Config()
self.data = DataManager(self)
self.connections = ConnectionManager(self)
self.dispatcher = Dispatcher(self)
self.servers = ServerManager(self)
self.modules = ModuleManager(self)
self.modules.setup()
self.fire_event(Events.READY)
# self.test()
def restart_app(self):
logger.warning("Restarting...")
os.execv(sys.executable, [sys.executable] + sys.argv)
# ------------------------------------------------------------------------------------------
def wait_user(self):
while True:
if self.users.users:
user_id = random.choice(list(self.users.users.keys()))
break
time.sleep(1)
return user_id
def test_connection(self, user_id):
p1= random.randint(46600, 49000)
p2 = random.randint(46600, 49000)
c1 = self.connections.connect(("127.0.0.1", p1), user_id, ("127.0.0.1", p2))
c2 = self.connections.connect(("127.0.0.1", p2), user_id, ("127.0.0.1", p1))
p1 = self.peers.get_peer(c1)
p2 = self.peers.get_peer(c2)
return p1,p2
def test_rendezvous(self, user_id):
from libs.rendezvous.rendezvousServer import RendezvousServer
rendezvous_server : RendezvousServer = self.modules.get("rendezvous", "rendezvousServer")
rendezvous_server.run_server(user_id)
time.sleep(2)
from libs.rendezvous.rendezvousClient import RendezvousClient
rendezvous_client : RendezvousClient = self.modules.get("rendezvous", "rendezvousClient")
rendezvous_client.connect_to_server(("localhost", 40441), user_id)
rendezvous_client.connect_to_server(("localhost", 40441), user_id)
def test_nosys(self):
pass
def test(self):
from .noSys import NoSys
nosys : NoSys = self.modules.get("noSys", "noSys")
# try:
# self.data.add_rendezvous("r1", "127.0.0.1:40441")
# except:
# pass
# try:
# net = self.data.create_network("test", "network for tests", "open_forum", [nosys.name])
# except:
# pass
# self.data.network_assign_rendezvous(net.get("id"), "r1")
user_id = self.wait_user()
from libs.rendezvous.rendezvousServer import RendezvousServer
rendezvous_server : RendezvousServer = self.modules.get("rendezvous", "rendezvousServer")
rendezvous_server.run_server(user_id)
# time.sleep(2)
# self.data.user_add_network(user_id, "629985fd999a675072a2e3f22f74e60c62e8c5b8efb21478c9b04e7ff2fe9779")
# peer1, peer2 = self.test_connection(user_id)
# from libs.fileTransfer.fileTransfer import FileTransfer
# file_transfer:FileTransfer = self.modules[("fileTransfer", "fileTransfer")]
# file_transfer.subscribe_module_file_events(self.module_id, self.on_file_event)
# file_transfer.send_file(r"C:\Users\lucas\Videos\Captures\Call of Duty® 2025-07-25 10-23-55.mp4", c2, self.module_id)
# def on_file_event(self, event):
# from libs.fileTransfer.fileTransfer import File, FileEvents
# file:File = event.file
# file.subscribe_event(FileEvents.ON_FILE_COMPLETED.name, self.on_file_done)
# file.subscribe_event(FileEvents.ON_FILE_ERROR.name, self.on_file_done)
# file.subscribe_event(FileEvents.ON_FILE_UPDATE.name, self.on_file_update)
# file.approve_transfer(True)
# def on_file_done(self, event):
# logger.warning(f"FILE COMPLETED, DO SOMETHING {event.source.__dict__}")
# def on_file_update(self, event):
# file = event.source
# total_true = sum(bool(x) for x in file.parts)
# logger.warning(f"FILE UPDATE, {total_true}|{len(file.parts)}")

64
noSys/noSysModule.py Normal file
View File

@@ -0,0 +1,64 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .noSysCore import NoSysCore
from libs.fspn.utils.observable import Observable
from .events import Events, DynamicEvents
class NoSysModule(Observable):
"""Base class for all modules in NoSys."""
def __init__(self, nosys_core:"NoSysCore"):
super().__init__()
self.nosys_core:NoSysCore = nosys_core
self.id = self._get_module_id()
self.config = self.nosys_core.config.get(self.package_id)
self.networks = self._get_module_networks()
self.nosys_core.subscribe_event(Events.READY, self.on_nosys_ready)
self.nosys_core.subscribe_event(DynamicEvents.module_connection(self.package_id, self.module_id), self.on_module_connection)
self.nosys_core.subscribe_event(DynamicEvents.module_disconnection(self.package_id, self.module_id), self.on_module_disconnection)
self.nosys_core.subscribe_event(DynamicEvents.module_message(self.package_id, self.module_id), self.on_module_message)
for network in self.networks:
self.nosys_core.subscribe_event(DynamicEvents.network_connection(network.get("id")), self.on_network_connection)
self.nosys_core.subscribe_event(DynamicEvents.network_disconnection(network.get("id")), self.on_network_disconnection)
def setup(self):
pass
def on_nosys_ready(self, event):
pass
def on_module_connection(self, event):
pass
def on_module_disconnection(self, event):
pass
def on_module_message(self, event):
pass
def on_network_connection(self, event):
pass
def on_network_disconnection(self, event):
pass
def teardown_module(self):
pass
def _get_module_id(self):
module_path = self.__class__.__module__
parts = module_path.split(".")
self.package_id = parts[1]
self.module_id = parts[2]
self.name = self.package_id+"_"+self.module_id
return (self.package_id, self.module_id)
def _get_module_networks(self):
return self.nosys_core.data.network_find_by_module(self.name)

View File

@@ -0,0 +1,57 @@
from .noSysModule import NoSysModule
from .events import Events
from .peers import Peer
from libs.fspn.protocol.server import Server
from libs.fspn.protocol.connection import Connection
class NoSysModuleServer(NoSysModule, Server):
def __init__(self, nosys_core):
NoSysModule.__init__(self, nosys_core)
Server.__init__(self)
self.host = "0.0.0.0"
self.port = 0
self.clients : dict[str, Peer] = {}
def setup(self):
self.nosys_core.subscribe_event(Events.PEER_CONNECTED, self._on_peer_connected)
self.nosys_core.subscribe_event(Events.PEER_DISCONNECTED, self._on_peer_disconnected)
def run_server(self, user_id):
self.nosys_core.servers.run_server(self, user_id, (self.host, self.port))
def on_server_connection(self, event):
print("CONNECTION RAW")
connection: Connection = event.source
peer = Peer(connection.id, connection)
self.nosys_core.peers.add_peer(peer)
self.clients[peer.id] = peer
self.nosys_core.connections._on_connection(event)
def on_server_disconnection(self, event):
print("DISCONNECTION RAW")
self.nosys_core.connections._on_disconnection(event)
def on_server_message(self, event):
print("MESSAGE RAW")
self.nosys_core.dispatcher.on_message(event)
def _on_peer_connected(self, event):
peer:Peer = event.peer
print(peer.id, self.clients)
if peer.id in self.clients:
print("PEER CONNECTED")
self.on_connection(peer)
def _on_peer_disconnected(self, event):
peer:Peer = event.peer
if peer.id in self.clients:
print("PEER DISCONNECTED")
self.on_disconnection(peer)
def on_connection(self, peer:Peer):
pass
def on_disconnection(self, peer:Peer):
pass

30
noSys/noSysSocketio.py Normal file
View File

@@ -0,0 +1,30 @@
from flask import Blueprint, make_response, request, jsonify, session, request
from flask_socketio import SocketIO, emit, join_room, leave_room
from libs.api.eventsSocketio import EventsSocketio
import os, json
import logging
class HandlerSocketio(EventsSocketio):
def events(self):
@self.socketio.on("message", namespace=self.namespace)
def on_message(*args, **kwargs):
print('NoSys Message',args, kwargs)
self.emit("message", f"Message from Nosys {args[0]}")
@self.socketio.on("ola", namespace=self.namespace)
def on_test(*args, **kwargs):
print('Zecho ola',args, kwargs)
self.emit("ola",f"Ola from Nosys {args[0]}")
def send_test(self, message):
self.emit("test", message)
def emit_new_user(self, user):
self.emit("newUser", user)
def emit_new_peer(self, peer):
self.emit("newPeer", peer.id)

84
noSys/peers.py Normal file
View File

@@ -0,0 +1,84 @@
import enum
class ConnectionState(enum.Enum):
DISCONNECTED = 0
CONNECTING = 1
CONNECTED = 2
from libs.fspn.protocol.connection import Connection
class Peer:
"""Represents a peer in the network."""
def __init__(self, id: str, connection: Connection):
self.id = id
self.connection:Connection = connection
self.state = ConnectionState.DISCONNECTED
self.modules = {}
self.networks = {}
def has_module(self, module_id:str):
return module_id in self.modules
def has_network(self, network_id:str):
return network_id in self.networks
def is_connected(self):
return self.state == ConnectionState.CONNECTED
def get_my_user(self):
return self.connection.security.user
def get_peer_user(self):
return self.connection.security.peer_user
class PeerManager:
"""Handles connection and storage of peers."""
def __init__(self, nosys_core):
from .noSysCore import NoSysCore
self.nosys_core:NoSysCore = nosys_core
self.peers:dict[str, Peer] = {}
def create_peer(self, connection:Connection):
return Peer(connection.id, connection)
def add_peer(self, peer: Peer):
self.peers[peer.id] = peer
def get_peer(self, peer_id:str) -> Peer:
return self.peers.get(peer_id, None)
def remove_peer(self, peer_id: str):
self.peers.pop(peer_id, None)
def get_online_peers(self) -> list[Peer]:
return self.get_peers_in_state(ConnectionState.CONNECTED)
def get_connecting_peers(self) -> list[Peer]:
return self.get_peers_in_state(ConnectionState.CONNECTING)
def get_offline_peers(self) -> list[Peer]:
return self.get_peers_in_state(ConnectionState.DISCONNECTED)
def get_peers_in_state(self, state):
peers:list[Peer] = []
for id, peer in self.peers.items():
if peer.state == state:
peers.append(peer)
return peers
def get_by_peer_user_id(self, user_id):
peers:list[Peer] = []
for id, peer in self.peers.items():
if peer.get_peer_user() == user_id and peer.is_connected():
peers.append(peer)
return peers
def get_by_network_id(self, networks):
peers:list[Peer] = []
for id, peer in self.peers.items():
for peer_network_id in peer.networks:
if peer_network_id in networks:
peers.append(peer)
return peers

18
noSys/servers.py Normal file
View File

@@ -0,0 +1,18 @@
from .noSysModuleServer import NoSysModuleServer
class ServerManager:
"""Handles running Server modules."""
def __init__(self, nosys_core):
from .noSysCore import NoSysCore
self.nosys_core:NoSysCore = nosys_core
self.servers: dict[tuple[str, int], NoSysModuleServer] = {}
def run_server(self, module:NoSysModuleServer, user_id=None, bind_address=None):
user = self.nosys_core.users.get_user(user_id)
if not user:
raise Exception("Cannot run server, user missing")
module.run(user.public_key, self.nosys_core.modules.pmc, bind_address)
self.servers[module.bind_address] = module

26
noSys/users.py Normal file
View File

@@ -0,0 +1,26 @@
from .events import Events
class User:
"""Represents a single user in the system."""
def __init__(self, public_key, networks=[]):
self.id = public_key
self.public_key = public_key
self.networks = networks
class UserManager:
"""Handles user creation, storage, and retrieval."""
def __init__(self, nosys_core):
from .noSysCore import NoSysCore
self.nosys_core: NoSysCore = nosys_core
self.users: dict[str, User] = {}
def add_user(self, user: User):
if user.public_key not in self.users:
self.users[user.public_key] = user
self.nosys_core.fire_event(Events.USER_ADDED, user_id = user.id)
def get_user(self, user_id: str):
return self.users.get(user_id)

126
noSys/vue/api/noSysApi.js Normal file
View File

@@ -0,0 +1,126 @@
import { useNoSysStore } from '../stores/noSysStore'
const noSysApiUrl = "/api/noSys";
const noSysStore = useNoSysStore()
export const noSysApi = {
async getUsers(){
let users = {}
try {
const response = await fetch(noSysApiUrl+'/users')
if (!response.ok) throw new Error("Error fetching users:")
users = await response.json()
return users;
} catch (error) {
console.error("Error fetching users:", error);
throw error;
}
},
async getUser(userId){
let user = {}
try {
const response = await fetch(noSysApiUrl+'/users/'+userId)
if (!response.ok) throw new Error("Error fetching users:")
user = await response.json()
return user;
} catch (error) {
console.error("Error fetching users:", error);
throw error;
}
},
async getConfigs(){
try {
const response = await fetch(noSysApiUrl+'/configs')
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Error fetching configs: ${response.status} - ${errorText}`);
}
const result = await response.json()
return result;
} catch (error) {
console.error("Error fetching configs:", error);
throw error;
}
},
async connectAddress(user, ip, port){
const data = {"user": user, "address": {"ip": ip,"port": port}};
const requestOptions = {
method: 'POST',
headers: {'Content-Type': 'application/json',},
body: JSON.stringify(data),
};
try {
const response = await fetch(noSysApiUrl+"/peers", requestOptions);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Error fetching users: ${response.status} - ${errorText}`);
}
const result = await response.json();
return result;
} catch (error) {
console.error("Error fetching users:", error);
throw error;
}
},
async listPeers(){
try {
const response = await fetch(noSysApiUrl+'/peers')
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Error fetching peers: ${response.status} - ${errorText}`);
}
const result = await response.json()
return result;
} catch (error) {
console.error("Error fetching peers:", error);
throw error;
}
},
async disconnectPeer(connectionId){
const data = {};
const requestOptions = {
method: 'DELETE',
headers: {'Content-Type': 'application/json',},
body: JSON.stringify(data),
};
try {
const response = await fetch(noSysApiUrl+"/peers/"+connectionId, requestOptions);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Error fetching users: ${response.status} - ${errorText}`);
}
const result = await response.json();
return result;
} catch (error) {
console.error("Error fetching users:", error);
throw error;
}
},
async listNetworks(){
try {
const response = await fetch(noSysApiUrl+'/networks')
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Error fetching networks: ${response.status} - ${errorText}`);
}
const result = await response.json()
return result;
} catch (error) {
console.error("Error fetching networks:", error);
throw error;
}
},
}

View File

@@ -0,0 +1,15 @@
import { useNoSysStore } from '../stores/noSysStore'
const noSysStore = useNoSysStore()
export default function registerSocketEvents(socket) {
socket.on('connect', (data) => {
console.log('Connected noSys')
}),
socket.on('newUser', (data) => {
noSysStore.addUser({publicKey:data})
console.log('New user', data)
})
}

View File

@@ -0,0 +1,282 @@
<script setup>
import Button from '@/components/buttons/Button.vue';
import InputText from '@/components/inputs/InputText.vue';
import Label from '@/components/labels/Label.vue';
import { ref, onMounted, onUnmounted, onActivated, onDeactivated } from 'vue';
import { noSysApi } from '../api/noSysApi';
import InputComboBox from '@/components/inputs/InputComboBox.vue';
import { useNoSysStore } from '../stores/noSysStore'
import { getSocket } from '@/plugins/socketioManager'
import Card from '@/components/cards/Card.vue';
import { ArrowPathIcon, EllipsisHorizontalIcon, GlobeAltIcon, LinkIcon, MapIcon, WifiIcon, XMarkIcon } from '@heroicons/vue/24/solid';
import { TabPanel} from '@headlessui/vue'
var socket = null
const noSysStore = useNoSysStore()
const ip = ref("n0sys.duckdns.org")
const port = ref("30331")
const user = ref()
const peers = ref([])
async function listUsers(){
noSysStore.users.value = await noSysApi.getUsers()
}
async function connect(){
const response = await noSysApi.connectAddress(user.value.user, ip.value, port.value)
}
async function reconnect(connectionId){
// const response = await noSysApi.connectAddress(user.value.user, ip.value, port.value)
}
async function disconnect(connectionId){
const response = await noSysApi.disconnectPeer(connectionId)
}
async function listPeers(){
peers.value = await noSysApi.listPeers()
}
onActivated(async () => {
socket = await getSocket("noSys_noSys")
socket.on('tempEvent', onTemporaryEvent)
listUsers()
listPeers()
});
onDeactivated(() => {
socket.off('tempEvent')
});
function onTemporaryEvent(data) {
console.log('Received temporary event in HomeView:', data)
}
// function sendMessage(){
// messageElement = document.getElementById("message")
// message = messageElement.value;
// if (connectionClicked && message){
// const data = {"message":{"action":"test", "data":message},"toModule": {"package": "noSys","module": "noSys"},"encrypted": true};
// const requestOptions = {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify(data),
// };
// fetch(noSysApiUrl+"/peers/"+connectionClicked, requestOptions)
// .then(response => {
// if (!response.ok) {
// throw new Error('Network response was not ok');
// }
// return response.json();
// })
// .then(data => {
// console.log(data);
// addMessage(connectionClicked, message, false)
// messageElement.value = ""
// })
// .catch(error => {
// console.error
// ('Error:', error);
// });
// }
// }
</script>
<template>
<TabPanel>
<div class="container mx-auto px-6 py-1 space-y-8">
<div class="grid md:grid-cols-4 gap-6">
<Card class="border-yellow-400/20 p-6">
<div class="flex items-center space-x-3">
<div class="w-12 h-12 bg-green-500/20 rounded-lg flex items-center justify-center">
<WifiIcon class="text-green-400 w-6 h-6"/>
</div>
<div>
<p class="text-2xl font-bold text-white">
{{peers.filter((p) => p.status === "CONNECTED").length}}
</p>
<p class="text-gray-400 text-sm">Connected</p>
</div>
</div>
</Card>
<Card class=" border-yellow-400/20 p-6">
<div class="flex items-center space-x-3">
<div class="w-12 h-12 bg-yellow-500/20 rounded-lg flex items-center justify-center">
<EllipsisHorizontalIcon class="text-yellow-400 h-6 w-6" size={24} />
</div>
<div>
<p class="text-2xl font-bold text-white">
{{peers.filter((p) => p.status === "CONNECTING").length}}
</p>
<p class="text-gray-400 text-sm">Connecting</p>
</div>
</div>
</Card>
<Card class=" border-yellow-400/20 p-6">
<div class="flex items-center space-x-3">
<div class="w-12 h-12 bg-red-500/20 rounded-lg flex items-center justify-center">
<XMarkIcon class="text-red-400 h-6 w-6"/>
</div>
<div>
<p class="text-2xl font-bold text-white">
{{peers.filter((p) => p.status === "ERROR").length}}
</p>
<p class="text-gray-400 text-sm">Failed</p>
</div>
</div>
</Card>
<Card class=" border-yellow-400/20 p-6">
<div class="flex items-center space-x-3">
<div class="w-12 h-12 bg-blue-500/20 rounded-lg flex items-center justify-center">
<LinkIcon class="text-blue-400 h-6 w-6"/>
</div>
<div>
<p class="text-2xl font-bold text-white">{{peers.length}}</p>
<p class="text-gray-400 text-sm">Total</p>
</div>
</div>
</Card>
</div>
<Card class="bg-black border-yellow-400/20 p-8">
<div class="flex items-center space-x-3 mb-6">
<LinkIcon class="text-yellow-400 h-6 w-6" />
<h2 class="text-2xl font-bold text-yellow-400">Connect to Peer</h2>
</div>
<div class="grid md:grid-cols-4 gap-6">
<div>
<Label class="text-gray-300 mb-2 block">IP Address</Label>
<InputText id="ip" type="text" v-model="ip" placeholder="" class="border-yellow-400/30 w-full"/>
</div>
<div>
<Label class="text-gray-300 mb-2 block">Port</Label>
<InputText id="port" type="text" v-model="port" placeholder="" class="border-yellow-400/30 w-full"/>
</div>
<div>
<Label class="text-gray-300 mb-2 block">User</Label>
<InputComboBox v-model="user" :items="noSysStore.users.value" labelKey="user" keyField="user" class="border-yellow-400/30"></InputComboBox>
</div>
<div class="flex items-end">
<Button @click="connect()" class="w-full bg-yellow-400 text-black hover:bg-yellow-300 font-bold">Connect</Button>
</div>
</div>
</Card>
<Card class=" border-yellow-400/20 p-8">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center space-x-3">
<GlobeAltIcon class="text-yellow-400 h-6 w-6" />
<h2 class="text-2xl font-bold text-yellow-400">Active Connections</h2>
</div>
</div>
<div v-if="peers.length === 0" class="text-center py-12">
<WifiIcon class="text-gray-600 mx-auto mb-4 h-7 w-7" />
<p class="text-gray-400 text-lg">No connections established</p>
<p class="text-gray-500 text-sm">Connect to peers to start building the decentralized network</p>
</div>
<div v-else class="space-y-4">
<div v-for="peer in peers" :key="peer.id" class="bg-black border border-yellow-400/20 rounded-lg p-6">
<div class="grid lg:grid-cols-7 gap-4 items-center">
<div>
<Label class="text-gray-400 text-xs">Connection ID</Label>
<p class="text-white font-mono text-sm">{{ peer.id }}</p>
</div>
<div>
<Label class="text-gray-400 text-xs">Address</Label>
<p class="text-white text-sm">
{{peer.address}}
</p>
</div>
<div>
<Label class="text-gray-400 text-xs">Bind Address</Label>
<p class="text-white text-sm">
{{peer.bindAddress}}
</p>
</div>
<div>
<Label class="text-gray-400 text-xs">Status</Label>
<div class="flex items-center space-x-2"
:class="{
'text-green-400': peer.status === 'CONNECTED',
'text-blue-400': peer.status === 'CONNECTING',
'text-yellow-400': peer.status === 'HANDSHAKING',
'text-red-400': peer.status === 'DISCONNECTED',
'text-gray-300': !['CONNECTED', 'CONNECTING', 'HANDSHAKING', 'DISCONNECTED'].includes(peer.status)
}">
{{peer.status}}
</div>
</div>
<div>
<Label class="text-gray-400 text-xs">Users</Label>
<p class="text-sm">
<span class="text-yellow-400">{{peer.user}}</span>
<!-- TODO MyUser, PeerUser/ProofOfWork -->
<span class="text-gray-400"> </span>
<span class="text-blue-400">{{peer.user}}</span>
</p>
</div>
<div>
<Label class="text-gray-400 text-xs">Last Activity</Label>
<!-- TODO Last Activity -->
<p class="text-white text-sm">{{ new Date().toLocaleTimeString() }}</p>
</div>
<div class="flex gap-2">
<Button v-if="peer.status === 'CONNECTED'"
@click="disconnect(peer.id)"
class="bg-red-500 hover:bg-red-600 text-white"
>
<XMarkIcon class="mr-1 h-4 w-4" />
Close
</Button>
<Button v-if="peer.status === 'DISCONNECTED'"
@click="reconnect(peer.id)"
class="bg-green-500 hover:bg-green-600 text-white"
>
<ArrowPathIcon class="mr-1 h-4 w-4" />
Reconnect
</Button>
</div>
</div>
<div class="mt-4 pt-4 border-t border-yellow-400/10">
<div class="flex justify-between text-xs text-gray-400">
<!-- Connected At -->
<span>Connected: {{new Date(peer.connectedAt).toLocaleString()}}</span>
<span>
Duration: {{Math.floor((Date.now() - new Date(peer.connectedAt).getTime()) / 60000)}}{" "}
minutes
</span>
</div>
</div>
</div>
</div>
</Card>
</div>
</TabPanel>
</template>

View File

@@ -0,0 +1,183 @@
<script setup>
import Button from '@/components/buttons/Button.vue';
import Card from '@/components/cards/Card.vue';
import InputComboBox from '@/components/inputs/InputComboBox.vue';
import InputText from '@/components/inputs/InputText.vue';
import Label from '@/components/labels/Label.vue';
import { TabPanel} from '@headlessui/vue'
import { CogIcon, MagnifyingGlassIcon, PlusIcon, TrashIcon } from '@heroicons/vue/24/solid';
import { noSysApi } from '../api/noSysApi';
import { useNoSysStore } from '../stores/noSysStore';
import { onActivated, onDeactivated } from 'vue';
const noSysStore = useNoSysStore()
async function listNetworks(){
noSysStore.networks.value = await noSysApi.listNetworks()
}
onActivated(()=>{
listNetworks()
})
onDeactivated(()=>{
})
</script>
<template>
<TabPanel>
<Card class="border-yellow-400/20 p-8 mb-8">
<div class="flex items-center space-x-3 mb-6">
<PlusIcon class="text-yellow-400 w-6 h-6"/>
<h2 class="text-2xl font-bold text-yellow-400">Add Network</h2>
</div>
<div class="grid md:grid-cols-2 gap-6">
<div>
<Label class="text-gray-300 mb-2 block">Network ID</Label>
<InputText
class="bg-black border-yellow-400/30 text-white"
placeholder="ID"
/>
</div>
<Button class="mt-6 bg-yellow-400 text-black hover:bg-yellow-300 font-bold w-full">
<MagnifyingGlassIcon class="w-4 h-4 mr-2 text-black" />
<p class="text-black">Search Network</p>
</Button>
<div>
<Label class="text-gray-300 mb-2 block">Network Name</Label>
<InputText
class="bg-black border-yellow-400/30 text-white"
placeholder="Name"
/>
</div>
<div>
<Label class="text-gray-300 mb-2 block">Network Description</Label>
<InputText
class="bg-black border-yellow-400/30 text-white"
placeholder="Description"
/>
</div>
<div class="space-y-4">
<Label class="text-gray-300 mb-2 block">Modules</Label>
<InputComboBox
class="bg-black border-yellow-400/30 text-white"
/>
</div>
<div class="space-y-4">
<Label class="text-gray-300 mb-2 block">Network Type</Label>
<InputComboBox
class="bg-black border-yellow-400/30 text-white"
/>
</div>
</div>
<div class="flex flex-row gap-x-3">
<Button class="mt-6 bg-yellow-400 text-black hover:bg-yellow-300 font-bold w-full">
<PlusIcon class="w-4 h-4 mr-2 text-black" />
<p class="text-black">Add Network</p>
</Button>
</div>
</Card>
<Card class="border-yellow-400/20 p-8">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center space-x-3">
<!-- <Network class="text-yellow-400" size={24} /> -->
<h2 class="text-2xl font-bold text-yellow-400">Networks</h2>
</div>
<div class="text-sm text-gray-400">{networks.length} networks</div>
</div>
<div v-if="!noSysStore.networks.value" class="text-center py-12">
<Network size={48} class="text-gray-600 mx-auto mb-4" />
<p class="text-gray-400 text-lg">No networks created</p>
<p class="text-gray-500 text-sm">Create a network to start connecting with peers</p>
</div>
<div v-else class="space-y-4">
<div v-for="network in noSysStore.networks.value" key={network.id} class="bg-black border border-yellow-400/20 rounded-lg p-6">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center space-x-3 mb-3">
<h3 class="text-xl font-bold text-white">{{network.data.name}}</h3>
<span class="bg-yellow-400/20 text-yellow-400 px-2 py-1 rounded text-sm">
{{network.data.id}}
</span>
<span
class="px-2 py-1 rounded text-sm"
>
</span>
</div>
<div class="grid md:grid-cols-4 gap-4 mb-4">
<div>
<Label class="text-gray-400 text-xs">Total Connections</Label>
<p class="text-2xl font-bold text-green-400">{{network.state.peers.length}}</p>
</div>
<div>
<Label class="text-gray-400 text-xs">Status</Label>
<p
class=""
>
{{network.state.status}}
</p>
</div>
<div>
<Label class="text-gray-400 text-xs">Auto Fill</Label>
<p class="">
{{network.state.managed}}
</p>
</div>
<div>
<Label class="text-gray-400 text-xs">Users</Label>
<p class="">
{{network.state.users.length}}
</p>
</div>
</div>
<div class="mb-4">
<Label class="text-gray-400 text-xs">Modules</Label>
<div class="flex flex-row gap-2">
<div v-for="module in network.data.modules" class="flex flex-wrap mt-1">
<span class="bg-blue-500/20 text-blue-400 px-2 py-1 rounded text-sm">
{{module}}
</span>
</div>
</div>
</div>
</div>
<div class="flex space-x-2">
<Button
class="border-yellow-400 text-yellow-400 hover:bg-yellow-400 hover:text-black bg-transparent"
>
<CogIcon class="h-4 w-4 mr-1" />
Configure
</Button>
<Button
class="bg-red-500 hover:bg-red-600 text-white"
>
<TrashIcon class="h-4 w-4mr-1" />
Quit
</Button>
</div>
</div>
</div>
</div>
</Card>
</TabPanel>
</template>

7
noSys/vue/router.js Normal file
View File

@@ -0,0 +1,7 @@
import HomeView from "./views/HomeView.vue";
const routes = [
{path: '/', name:'noSys', component: HomeView},
]
export {routes};

View File

@@ -0,0 +1,57 @@
import { defineStore } from 'pinia'
export const useNoSysStore = defineStore('noSysStore', {
state: () => ({
users: [], // [{ publicKey }]
connections: [], // [{ publicKey, connectedAt, network }]
networks: [],
}),
getters: {
getUserByKey: (state) => (key) => {
return state.users.find(u => u.publicKey === key)
},
getConnectionsByNetwork: (state) => (network) => {
return state.connections.filter(c => c.network === network)
}
},
actions: {
addUser(user) {
if (!this.users.find(u => u.publicKey === user.publicKey)) {
this.users.push(user)
}
},
removeUser(publicKey) {
this.users = this.users.filter(u => u.publicKey !== publicKey)
},
updateUser(updated) {
const index = this.users.findIndex(u => u.publicKey === updated.publicKey)
if (index !== -1) this.users[index] = { ...this.users[index], ...updated }
},
addConnection(conn) {
const exists = this.connections.find(
c => c.publicKey === conn.publicKey && c.network === conn.network
)
if (!exists) {
this.connections.push({
...conn,
connectedAt: conn.connectedAt || new Date().toISOString(),
})
}
},
removeConnection(publicKey, network = null) {
this.connections = this.connections.filter(
c => c.publicKey !== publicKey || (network && c.network !== network)
)
},
clearConnections() {
this.connections = []
}
}
})

View File

@@ -0,0 +1,59 @@
<script setup>
import Button from '@/components/buttons/Button.vue';
import InputText from '@/components/inputs/InputText.vue';
import Label from '@/components/labels/Label.vue';
import { ref, onMounted, onUnmounted, onActivated, onDeactivated } from 'vue';
import { noSysApi } from '../api/noSysApi';
import InputComboBox from '@/components/inputs/InputComboBox.vue';
import { useNoSysStore } from '../stores/noSysStore'
import { getSocket } from '@/plugins/socketioManager'
import Card from '@/components/cards/Card.vue';
import { ArrowPathIcon, EllipsisHorizontalIcon, GlobeAltIcon, LinkIcon, MapIcon, WifiIcon, XMarkIcon } from '@heroicons/vue/24/solid';
import ConnectionsTab from '../components/ConnectionsTab.vue';
import NetworksTabs from '../components/NetworksTabs.vue';
import { TabGroup, TabList, Tab, TabPanels} from '@headlessui/vue'
const tabItems = [
{label:"Connections", icon:LinkIcon, tabComponent:ConnectionsTab},
{label:"Networks", icon:MapIcon, tabComponent:NetworksTabs},
]
</script>
<template>
<div class="min-h-screen bg-black text-yellow-400">
<header class="border-b border-yellow-400/20 py-6">
<div class="container mx-auto px-6">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<div class="text-3xl font-black">
<span class="text-yellow-400">NO</span>
<span class="text-white">SYS</span>
</div>
<div class="w-1 h-8 bg-yellow-400"></div>
<span class="text-gray-400">Peer-to-Peer Network Connection</span>
</div>
</div>
</div>
</header>
<div className="container mx-auto px-6">
<TabGroup>
<TabList class="flex flex-wrap gap-2 border-b border-yellow-400/20">
<Tab v-for="tab in tabItems" as="template" :key="tab" v-slot="{ selected }">
<button class="flex items-center space-x-2 px-6 py-3 font-semibold transition-all duration-300 border-b-2"
:class="{ 'text-yellow-400 border-yellow-400': selected, 'text-gray-400 border-transparent hover:text-yellow-400 hover:border-yellow-400/50': !selected }">
<component :is="tab.icon" class="h-4 w-4"></component>
<span>{{ tab.label }}</span>
</button>
</Tab>
</TabList>
<TabPanels class="mt-6 space-y-6" v-for="tab in tabItems" :key="tab.label">
<component :is="tab.tabComponent"></component>
</TabPanels>
</TabGroup>
</div>
</div>
</template>