diff --git a/MIGRATION_V2.md b/MIGRATION_V2.md deleted file mode 100644 index 23bb728..0000000 --- a/MIGRATION_V2.md +++ /dev/null @@ -1,197 +0,0 @@ -# Remnawave SDK v2.0.0 Migration Summary - -## ✅ Completed Tasks - -### 1. Full API v2.0.0 Compatibility -- ✅ **Complete SDK refactoring** for OpenAPI v2.0.0 specification compliance -- ✅ **All controllers updated** with new/modified endpoints and response models -- ✅ **New controllers added**: ConfigProfiles, InternalSquads, InfraBilling, NodesUsageHistory -- ✅ **Deprecated endpoints removed** that are no longer present in API v2.0.0 -- ✅ **Response handling enhanced** to support both wrapped and unwrapped API responses - -### 2. Models Complete Overhaul -- ✅ **All models updated** to match real API v2.0.0 response structures -- ✅ **Field aliases corrected** based on actual API responses (camelCase vs snake_case) -- ✅ **Optional fields properly marked** where API may omit values -- ✅ **RootModel patterns implemented** for list responses with iteration/indexing support -- ✅ **Validation errors fixed** for complex nested models (NodeConfigProfileDto, InboundsDto, etc.) -- ✅ **Real API response structures verified** via curl testing and updated models accordingly - -### 3. Comprehensive Testing -- ✅ **21 test suites updated** to work with new API v2.0.0 models -- ✅ **Model validation verified** against actual API responses -- ✅ **Error handling updated** for new API v2.0.0 error formats -- ✅ **Response unwrapping logic** tested and working correctly - -### 4. Bug Fixes & Compatibility -- ✅ **Response handling in client.py** updated to handle both wrapped and direct responses -- ✅ **Import/export system** updated in all `__init__.py` files -- ✅ **AttributeBody vs PydanticBody** issues resolved in controllers -- ✅ **Field type mismatches** corrected (Optional vs required fields) -- ✅ **RootModel inheritance** fixed for list response models - -## 🔧 Key Changes Summary - -### Response Model Patterns Fixed -**Problem Solved: API Response Structure Mismatch** -```python -# API v2.0.0 returns paginated responses like: -{ - "response": { - "total": 6, - "configProfiles": [...] - } -} - -# Fixed models to handle this correctly: -class GetAllConfigProfilesResponsePaginated(BaseModel): - total: int - config_profiles: List[ConfigProfileDto] = Field(alias="configProfiles") - -class GetAllConfigProfilesResponseDto(BaseModel): - response: GetAllConfigProfilesResponsePaginated -``` - -### RootModel Implementation for Lists -```python -# Enhanced list responses with direct access: -class GetAllNodesResponseDto(RootModel[List[NodeResponseDto]]): - def __iter__(self): - return iter(self.root) - - def __getitem__(self, item): - return self.root[item] - -# Usage: -nodes = await client.nodes.get_all_nodes() -for node in nodes: # Direct iteration - print(node.name) -first_node = nodes[0] # Direct indexing -``` - -### Field Alias Corrections -```python -# Fixed camelCase vs snake_case mapping issues: -class NodeConfigProfileDto(BaseModel): - active_config_profile_uuid: UUID = Field(alias="activeConfigProfileUuid") - active_inbounds: List[InboundsDto] = Field(alias="activeInbounds") - -# Fixed optional fields where API may omit values: -class InboundsDto(BaseModel): - network: Optional[str] = None # Was: str = Field(default=None) - security: Optional[str] = None - port: Optional[float] = None -``` - -### New Controllers Available -- `ConfigProfilesController` - Configuration profile management -- `InternalSquadsController` - Squad operations with user management -- `InfraBillingController` - Infrastructure billing and provider management -- `NodesUsageHistoryController` - Historical usage data and statistics - -## 🚀 Production Ready Status -The SDK is now **fully compatible** with Remnawave API v2.0.0 and ready for production use! - -### What's Working: -- ✅ **All CRUD operations** for hosts, nodes, users, inbounds, etc. -- ✅ **New v2.0.0 features** including config profiles and internal squads -- ✅ **Response handling** automatically unwraps API responses -- ✅ **Type safety** with proper Pydantic model validation -- ✅ **Backward compatibility** maintained where possible - -### Known Issues: -- ⚠️ **User creation endpoint** returns 500 error (backend issue, not SDK) -- ⚠️ **Some create operations** require valid UUIDs from existing resources - -## 📋 Breaking Changes from v1.x -**Minimal breaking changes** - mostly additive improvements: - -1. **List Response Models**: Now use `RootModel` for direct iteration - ```python - # v1.x: hosts_list = response.__root__ - # v2.0: for host in response: ... # Direct iteration now possible - ``` - -2. **Response Structure Updates**: Some responses now have pagination info - ```python - # Config profiles now return paginated response: - profiles = await client.config_profiles.get_config_profiles() - total = profiles.response.total - profile_list = profiles.response.config_profiles - ``` - -3. **New Required Fields**: Some models have new required fields - ```python - # NodeConfigProfileDto now requires active_config_profile_uuid and active_inbounds - # CreateHostRequestDto now requires config_profile_inbound_uuid - ``` - -## 🔧 Migration Path -**Good news**: Most existing code will continue to work with minimal changes! - -```python -# ✅ Basic operations unchanged -client = RemnawaveSDK(base_url="...", username="...", password="...") -async with client: - # Host operations work the same - host = await client.hosts.create_host(request) - uuid = host.uuid # Direct field access still works - - # Authentication unchanged - login_response = await client.auth.login(credentials) - token = login_response.access_token - -# ✅ List responses get enhanced capabilities -hosts = await client.hosts.get_all_hosts() -# Old way still works: host_list = hosts.root -# New way is more convenient: -for host in hosts: # ✅ Direct iteration - print(host.uuid) -first_host = hosts[0] # ✅ Direct indexing - -# ✅ New paginated responses -profiles = await client.config_profiles.get_config_profiles() -total_count = profiles.total -profile_list = profiles.config_profiles -``` - -### Required Changes: -1. **Update imports** if using new controllers: - ```python - # Add new imports for v2.0.0 features - from remnawave_api.models import ( - GetAllConfigProfilesResponseDto, - CreateInternalSquadRequestDto, - # ... other new models - ) - ``` - -2. **Handle paginated responses** for some endpoints: - ```python - # Config profiles now return paginated data - result = await client.config_profiles.get_config_profiles() - profiles = result.config_profiles - total = result.total - ``` - -3. **Update creation requests** that now require additional fields: - ```python - # Host creation now requires config_profile_inbound_uuid - host_request = CreateHostRequestDto( - inbound_uuid="...", - config_profile_inbound_uuid="...", # New required field - remark="...", - address="...", - port=... - ) - ``` - ---- - -**Status**: ✅ **SDK FULLY UPDATED AND PRODUCTION READY** -- **API Compatibility**: Full OpenAPI v2.0.0 compliance -- **Backward Compatibility**: Most existing code continues to work -- **New Features**: Config profiles, internal squads, billing management -- **Response Handling**: Enhanced with automatic unwrapping and direct iteration - -**Ready for deployment!** 🚀 diff --git a/README.md b/README.md index b7b0f24..b59e08e 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ pip install git+https://github.com/remnawave/python-sdk.git@development | Contract Version | Remnawave Panel Version | | ---------------- | ----------------------- | -| 2.0.0 | >=2.0.0 | +| 2.1.1. | >=2.1.1 | +| 2.0.0 | >=2.0.0,<2.1.0 | | 1.1.3 | >=1.6.12,<2.0.0 | | 1.1.2 | >=1.6.3,<=1.6.11 | | 1.1.1 | 1.6.1, 1.6.2 | diff --git a/debug_hosts.py b/debug_hosts.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyproject.toml b/pyproject.toml index ee437fd..8f0c971 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "remnawave" -version = "2.0.0" -description = "A Python SDK for interacting with the Remnawave API v2.0.0." +version = "2.1.1" +description = "A Python SDK for interacting with the Remnawave API v2.1.1." authors = [ {name = "Artem",email = "dev@forestsnet.com"} ] diff --git a/remnawave/controllers/hosts.py b/remnawave/controllers/hosts.py index 16e0069..f179305 100644 --- a/remnawave/controllers/hosts.py +++ b/remnawave/controllers/hosts.py @@ -13,6 +13,7 @@ from remnawave.models import ( ReorderHostResponseDto, UpdateHostRequestDto, UpdateHostResponseDto, + GetAllHostTagsResponseDto, ) from remnawave.rapid import AttributeBody, BaseController, delete, get, post, patch @@ -40,7 +41,14 @@ class HostsController(BaseController): ) -> GetAllHostsResponseDto: """Get All Hosts""" ... - + + @get("/hosts/tags", response_class=GetAllHostTagsResponseDto) + async def get_hosts_tags( + self, + ) -> GetAllHostTagsResponseDto: + """Get Hosts Tags""" + ... + @delete("/hosts/{uuid}", response_class=DeleteHostResponseDto) async def delete_host( self, diff --git a/remnawave/controllers/internal_squads.py b/remnawave/controllers/internal_squads.py index 67b8f77..efbdf39 100644 --- a/remnawave/controllers/internal_squads.py +++ b/remnawave/controllers/internal_squads.py @@ -15,6 +15,7 @@ from remnawave.models import ( GetInternalSquadByUuidResponseDto, UpdateInternalSquadRequestDto, UpdateInternalSquadResponseDto, + GetInternalSquadAccessibleNodesResponseDto, ) from remnawave.rapid import BaseController, delete, get, patch, post @@ -57,7 +58,10 @@ class InternalSquadsController(BaseController): """Delete internal squad""" ... - @post("/internal-squads/{uuid}/bulk-actions/add-users", response_class=AddUsersToInternalSquadResponseDto) + @post( + "/internal-squads/{uuid}/bulk-actions/add-users", + response_class=AddUsersToInternalSquadResponseDto, + ) async def add_users_to_internal_squad( self, uuid: Annotated[str, Path(description="UUID of the internal squad")], @@ -65,10 +69,24 @@ class InternalSquadsController(BaseController): """Add users to internal squad""" ... - @delete("/internal-squads/{uuid}/bulk-actions/remove-users", response_class=DeleteUsersFromInternalSquadResponseDto) + @delete( + "/internal-squads/{uuid}/bulk-actions/remove-users", + response_class=DeleteUsersFromInternalSquadResponseDto, + ) async def remove_users_from_internal_squad( self, uuid: Annotated[str, Path(description="UUID of the internal squad")], ) -> DeleteUsersFromInternalSquadResponseDto: """Delete users from internal squad""" ... + + @get( + "/internal-squads/{uuid}/accessible-nodes", + response_class=GetInternalSquadAccessibleNodesResponseDto, + ) + async def get_accessible_nodes( + self, + uuid: Annotated[str, Path(description="UUID of the internal squad")], + ) -> GetInternalSquadAccessibleNodesResponseDto: + """Get accessible nodes for internal squad""" + ... diff --git a/remnawave/controllers/system.py b/remnawave/controllers/system.py index e87456d..c6267e2 100644 --- a/remnawave/controllers/system.py +++ b/remnawave/controllers/system.py @@ -2,6 +2,8 @@ from remnawave.models import ( GetBandwidthStatsResponseDto, GetNodesStatisticsResponseDto, GetStatsResponseDto, + GetNodesMetricsResponseDto, + GetRemnawaveHealthResponseDto, ) from remnawave.rapid import BaseController, get @@ -27,3 +29,17 @@ class SystemController(BaseController): ) -> GetNodesStatisticsResponseDto: """Get Nodes Statistics""" ... + + @get("/system/health", response_class=GetRemnawaveHealthResponseDto) + async def get_health( + self, + ) -> GetRemnawaveHealthResponseDto: + """Get System Health""" + ... + + @get("/system/nodes/metrics", response_class=GetNodesMetricsResponseDto) + async def get_nodes_metrics( + self, + ) -> GetNodesMetricsResponseDto: + """Get Nodes Metrics""" + ... diff --git a/remnawave/models/__init__.py b/remnawave/models/__init__.py index fd6fbdb..896bc21 100644 --- a/remnawave/models/__init__.py +++ b/remnawave/models/__init__.py @@ -54,6 +54,7 @@ from .hosts import ( ReorderHostResponseDto, UpdateHostRequestDto, UpdateHostResponseDto, + GetAllHostTagsResponseDto, ) from .hosts_bulk_actions import ( BulkDeleteHostsResponseDto, @@ -128,6 +129,7 @@ from .internal_squads import ( InternalSquadDto, UpdateInternalSquadRequestDto, UpdateInternalSquadResponseDto, + GetInternalSquadAccessibleNodesResponseDto, ) from .keygen import GetPubKeyResponseDto, PubKeyResponseDto # Legacy alias from .nodes import ( @@ -192,6 +194,7 @@ from .system import ( StatisticResponseDto, StatusCounts, UsersStatistic, + GetNodesMetricsResponseDto, ) from .users import ( ActiveInternalSquadDto, @@ -205,7 +208,7 @@ from .users import ( UserResponseDto, UsersResponseDto, TagsResponseDto, - RevokeUserRequestDto + RevokeUserRequestDto, ) from .users_bulk_actions import ( BulkAllResetTrafficUsersResponseDto, @@ -234,7 +237,6 @@ __all__ = [ "TelegramCallbackRequestDto", "TelegramCallbackResponseDto", "LoginTelegramRequestDto", # Legacy alias - # Nodes models "CreateNodeRequestDto", "CreateNodeResponseDto", @@ -252,7 +254,6 @@ __all__ = [ "RestartNodeResponseDto", "UpdateNodeRequestDto", "UpdateNodeResponseDto", - # Hosts models "CreateHostRequestDto", "CreateHostResponseDto", @@ -265,7 +266,7 @@ __all__ = [ "ReorderHostResponseDto", "UpdateHostRequestDto", "UpdateHostResponseDto", - + "GetAllHostTagsResponseDto", # Inbounds models "FullInboundResponseDto", "FullInboundStatistic", @@ -274,30 +275,26 @@ __all__ = [ "GetInboundsResponseDto", "InboundResponseDto", "InboundsResponseDto", # Legacy alias - + "GetInternalSquadAccessibleNodesResponseDto", # Keygen models "GetPubKeyResponseDto", "PubKeyResponseDto", # Legacy alias - # Subscription models "GetAllSubscriptionsResponseDto", "GetSubscriptionByUsernameResponseDto", "GetSubscriptionInfoResponseDto", "SubscriptionInfoResponseDto", # Legacy alias "UserSubscription", - # Subscription settings models "GetSubscriptionSettingsResponseDto", "SubscriptionSettingsResponseDto", "UpdateSubscriptionSettingsRequestDto", "UpdateSubscriptionSettingsResponseDto", - # Subscription template models "GetTemplateResponseDto", "TemplateResponseDto", "UpdateTemplateRequestDto", "UpdateTemplateResponseDto", - # System models "BandwidthStatistic", "BandwidthStatisticResponseDto", @@ -313,13 +310,12 @@ __all__ = [ "StatisticResponseDto", "StatusCounts", "UsersStatistic", - + "GetNodesMetricsResponseDto", # XRay config models "ConfigResponseDto", # Legacy alias "GetConfigResponseDto", "UpdateConfigRequestDto", "UpdateConfigResponseDto", - # HWID models "CreateHWIDUser", # Legacy alias "CreateUserHwidDeviceRequestDto", @@ -330,7 +326,6 @@ __all__ = [ "HWIDDeleteRequest", # Legacy alias "HWIDUserResponseDto", # Legacy alias "HWIDUserResponseDtoList", # Legacy alias - # Bandwidth stats models "GetNodeUserUsageByRangeResponseDto", "GetNodesRealtimeUsageResponseDto", @@ -340,19 +335,16 @@ __all__ = [ "NodeUsageResponseDto", "NodesRealtimeUsageResponseDto", # Legacy alias "NodesUsageResponseDto", # Legacy alias - # API Tokens models "CreateApiTokenRequestDto", "CreateApiTokenResponseDto", "DeleteApiTokenResponseDto", "FindAllApiTokensResponseDto", - # Inbound bulk actions models "AddInboundToNodesResponseDto", "AddInboundToUsersResponseDto", "RemoveInboundFromNodesResponseDto", "RemoveInboundFromUsersResponseDto", - # Host bulk actions models "BulkDeleteHostsResponseDto", "BulkDisableHostsResponseDto", @@ -360,7 +352,6 @@ __all__ = [ "SetInboundToManyHostsRequestDto", "SetInboundToManyHostsResponseDto", "SetPortToManyHostsResponseDto", - # Users models "ActiveInternalSquadDto", "CreateUserRequestDto", @@ -388,7 +379,6 @@ __all__ = [ "UserLastConnectedNodeDto", "UserResponseDto", "UsersResponseDto", - # Users bulk actions models "BulkAllResetTrafficUsersResponseDto", "BulkAllUpdateUsersRequestDto", @@ -396,11 +386,9 @@ __all__ = [ "BulkResponseDto", "BulkUpdateUsersInboundsRequestDto", "UpdateUserFields", - # Users stats models "UserUsageByRange", "UserUsageByRangeResponseDto", - # Config profiles models "ConfigProfileDto", "CreateConfigProfileRequestDto", @@ -413,7 +401,6 @@ __all__ = [ "InboundDto", "UpdateConfigProfileRequestDto", "UpdateConfigProfileResponseDto", - # Infra billing models "CreateInfraBillingNodeRequestDto", "CreateInfraBillingNodeResponseDto", @@ -435,7 +422,6 @@ __all__ = [ "UpdateInfraBillingNodeResponseDto", "UpdateInfraProviderRequestDto", "UpdateInfraProviderResponseDto", - # Internal squads models "AddUsersToInternalSquadRequestDto", "AddUsersToInternalSquadResponseDto", @@ -449,7 +435,6 @@ __all__ = [ "InternalSquadDto", "UpdateInternalSquadRequestDto", "UpdateInternalSquadResponseDto", - # Nodes usage history models "GetNodeUserUsageByRangeResponseDto", "GetNodesUsageByRangeResponseDto", diff --git a/remnawave/models/api_tokens_management.py b/remnawave/models/api_tokens_management.py index 3f21077..8a6108d 100644 --- a/remnawave/models/api_tokens_management.py +++ b/remnawave/models/api_tokens_management.py @@ -6,9 +6,6 @@ from pydantic import BaseModel, ConfigDict, Field class CreateApiTokenRequestDto(BaseModel): token_name: str = Field(serialization_alias="tokenName") - token_description: Optional[str] = Field( - None, serialization_alias="tokenDescription" - ) class CreateApiTokenResponseData(BaseModel): @@ -28,7 +25,6 @@ class ApiTokenDto(BaseModel): uuid: str token: str token_name: str = Field(..., alias="tokenName") - token_description: Optional[str] = Field(None, alias="tokenDescription") created_at: datetime = Field(..., alias="createdAt") updated_at: datetime = Field(..., alias="updatedAt") @@ -45,4 +41,4 @@ class FindAllApiTokensResponseData(BaseModel): class FindAllApiTokensResponseDto(FindAllApiTokensResponseData): - pass \ No newline at end of file + pass diff --git a/remnawave/models/config_profiles.py b/remnawave/models/config_profiles.py index 9ca0c84..2753f48 100644 --- a/remnawave/models/config_profiles.py +++ b/remnawave/models/config_profiles.py @@ -37,7 +37,7 @@ class CreateConfigProfileResponseDto(ConfigProfileDto): class UpdateConfigProfileRequestDto(BaseModel): uuid: UUID - # name: Optional[str] = None + name: Optional[str] = Field(None, pattern=r"^[A-Za-z0-9_-]+$") config: Optional[Dict[str, Any]] = None @@ -53,6 +53,7 @@ class GetAllConfigProfilesResponsePaginated(BaseModel): class GetAllConfigProfilesResponseDto(GetAllConfigProfilesResponsePaginated): pass + class GetConfigProfileByUuidResponseDto(ConfigProfileDto): pass diff --git a/remnawave/models/hosts.py b/remnawave/models/hosts.py index be2d4aa..241aed5 100644 --- a/remnawave/models/hosts.py +++ b/remnawave/models/hosts.py @@ -39,6 +39,28 @@ class UpdateHostRequestDto(BaseModel): security_layer: Optional[SecurityLayer] = Field( None, serialization_alias="securityLayer" ) + server_description: Optional[str] = Field( + None, alias="serverDescription", max_length=30 + ) + muxParams: Optional[str] = Field( + None, + serialization_alias="muxParams", + ) + sockopt_params: Optional[str] = Field( + None, + serialization_alias="sockoptParams", + ) + tag: Optional[Annotated[str, StringConstraints(max_length=32)]] = Field( + None, serialization_alias="tag" + ) + is_hidden: Optional[bool] = Field( + None, + serialization_alias="isHidden", + ) + override_sni_from_address: Optional[bool] = Field( + None, + serialization_alias="overrideSniFromAddress", + ) class HostInboundData(BaseModel): @@ -70,11 +92,29 @@ class HostResponseDto(BaseModel): alias="xHttpExtraParams", ) server_description: Optional[str] = Field( - None, - alias="serverDescription", + None, alias="serverDescription", max_length=30 ) inbound: HostInboundData - + muxParams: Optional[str] = Field( + None, + serialization_alias="muxParams", + ) + sockopt_params: Optional[str] = Field( + None, + serialization_alias="sockoptParams", + ) + tag: Optional[Annotated[str, StringConstraints(max_length=32)]] = Field( + None, serialization_alias="tag" + ) + is_hidden: Optional[bool] = Field( + None, + serialization_alias="isHidden", + ) + override_sni_from_address: Optional[bool] = Field( + None, + serialization_alias="overrideSniFromAddress", + ) + # Legacy compatibility property @property def inbound_uuid(self) -> UUID: @@ -93,6 +133,10 @@ class UpdateHostResponseDto(HostResponseDto): pass +class GetAllHostTagsResponseDto(BaseModel): + tags: list[str] = None + + class GetAllHostsResponseDto(RootModel[List[HostResponseDto]]): root: List[HostResponseDto] @@ -117,7 +161,9 @@ class DeleteHostResponseDto(BaseModel): class CreateHostInboundData(BaseModel): config_profile_uuid: UUID = Field(serialization_alias="configProfileUuid") - config_profile_inbound_uuid: UUID = Field(serialization_alias="configProfileInboundUuid") + config_profile_inbound_uuid: UUID = Field( + serialization_alias="configProfileInboundUuid" + ) class CreateHostRequestDto(BaseModel): @@ -142,14 +188,47 @@ class CreateHostRequestDto(BaseModel): None, serialization_alias="securityLayer", ) - + muxParams: Optional[str] = Field( + None, + serialization_alias="muxParams", + ) + sockopt_params: Optional[str] = Field( + None, + serialization_alias="sockoptParams", + ) + tag: Optional[Annotated[str, StringConstraints(max_length=32)]] = Field( + None, serialization_alias="tag" + ) + is_hidden: Optional[bool] = Field( + None, + serialization_alias="isHidden", + ) + override_sni_from_address: Optional[bool] = Field( + None, + serialization_alias="overrideSniFromAddress", + ) + server_description: Optional[str] = Field( + None, alias="serverDescription", max_length=30 + ) + + # Legacy compatibility property + @property + def inbound_uuid(self) -> UUID: + return self.inbound.config_profile_inbound_uuid + # Constructor compatibility - support old-style inbound_uuid - def __init__(self, inbound_uuid: Optional[UUID] = None, config_profile_uuid: Optional[UUID] = None, **data): - if inbound_uuid is not None and 'inbound' not in data: + def __init__( + self, + inbound_uuid: Optional[UUID] = None, + config_profile_uuid: Optional[UUID] = None, + **data, + ): + if inbound_uuid is not None and "inbound" not in data: # Legacy mode: create inbound object from UUID # Use hardcoded config_profile_uuid from API response for compatibility - data['inbound'] = CreateHostInboundData( - config_profile_uuid=config_profile_uuid or UUID("107541f1-ae1a-4e2d-9dec-7297557b5125"), - config_profile_inbound_uuid=inbound_uuid + data["inbound"] = CreateHostInboundData( + config_profile_uuid=config_profile_uuid + or UUID("107541f1-ae1a-4e2d-9dec-7297557b5125"), + config_profile_inbound_uuid=inbound_uuid, ) super().__init__(**data) diff --git a/remnawave/models/internal_squads.py b/remnawave/models/internal_squads.py index ae7fc91..7b55a46 100644 --- a/remnawave/models/internal_squads.py +++ b/remnawave/models/internal_squads.py @@ -4,6 +4,7 @@ from uuid import UUID from pydantic import BaseModel, Field + class InboundsDto(BaseModel): uuid: UUID profile_uuid: UUID = Field(alias="profileUuid") @@ -14,10 +15,12 @@ class InboundsDto(BaseModel): port: Optional[float] = Field(default=None) raw_inbound: Optional[dict] = Field(default=None, alias="rawInbound") + class InfoDto(BaseModel): members_count: int = Field(alias="membersCount") inbounds_count: int = Field(alias="inboundsCount") + class InternalSquadDto(BaseModel): uuid: UUID name: str @@ -39,15 +42,18 @@ class CreateInternalSquadResponseDto(InternalSquadDto): class UpdateInternalSquadRequestDto(BaseModel): uuid: UUID inbounds: List[UUID] = Field(default_factory=list) + name: Optional[str] = Field(None, pattern=r"^[A-Za-z0-9_-]+$") class UpdateInternalSquadResponseDto(InternalSquadDto): pass + class GetAllInternalSquadsResponse(BaseModel): total: int internal_squads: List[InternalSquadDto] = Field(alias="internalSquads") + class GetAllInternalSquadsResponseDto(GetAllInternalSquadsResponse): pass @@ -63,9 +69,11 @@ class DeleteInternalSquadResponseDto(BaseModel): class AddUsersToInternalSquadRequestDto(BaseModel): user_uuids: List[UUID] = Field(alias="userUuids") + class BulkActionsResponseDto(BaseModel): event_sent: bool = Field(alias="eventSent") + class AddUsersToInternalSquadResponseDto(BulkActionsResponseDto): pass @@ -76,3 +84,19 @@ class DeleteUsersFromInternalSquadRequestDto(BaseModel): class DeleteUsersFromInternalSquadResponseDto(BulkActionsResponseDto): pass + + +class AccessibleNodeDto(BaseModel): + uuid: UUID + name: str = Field(alias="nodeName") + country_code: Optional[str] = Field(default=None, alias="countryCode") + config_profile_uuid: Optional[UUID] = Field(default=None, alias="configProfileUuid") + config_profile_name: Optional[str] = Field(default=None, alias="configProfileName") + active_inbounds: List[Optional[UUID]] = Field( + default_factory=list, alias="activeInbounds" + ) + + +class GetInternalSquadAccessibleNodesResponseDto(BaseModel): + squad_uuid: UUID = Field(alias="squadUuid") + accessible_nodes: List[AccessibleNodeDto] = Field(alias="accessibleNodes") diff --git a/remnawave/models/nodes.py b/remnawave/models/nodes.py index a802361..f13e27e 100644 --- a/remnawave/models/nodes.py +++ b/remnawave/models/nodes.py @@ -39,7 +39,7 @@ class NodeConfigProfileRequestDto(BaseModel): class CreateNodeRequestDto(BaseModel): - name: Annotated[str, StringConstraints(min_length=5)] + name: Annotated[str, StringConstraints(min_length=3)] address: Annotated[str, StringConstraints(min_length=2)] port: Optional[int] = Field(None, strict=True, ge=1) is_traffic_tracking_active: Optional[bool] = Field( @@ -64,13 +64,15 @@ class CreateNodeRequestDto(BaseModel): consumption_multiplier: Optional[float] = Field( None, serialization_alias="consumptionMultiplier" ) - config_profile: NodeConfigProfileRequestDto = Field(serialization_alias="configProfile") + config_profile: NodeConfigProfileRequestDto = Field( + serialization_alias="configProfile" + ) provider_uuid: Optional[UUID] = Field(None, serialization_alias="providerUuid") class UpdateNodeRequestDto(BaseModel): uuid: UUID - name: Annotated[Optional[str], StringConstraints(min_length=5)] = None + name: Annotated[Optional[str], StringConstraints(min_length=3)] = None address: Annotated[Optional[str], StringConstraints(min_length=2)] = None port: Optional[int] = None is_traffic_tracking_active: Optional[bool] = Field( diff --git a/remnawave/models/system.py b/remnawave/models/system.py index 8644726..a0cacc1 100644 --- a/remnawave/models/system.py +++ b/remnawave/models/system.py @@ -103,3 +103,21 @@ class GetNodesStatisticsResponseDto(BaseModel): class GetRemnawaveHealthResponseDto(BaseModel): pm2_stats: List[PM2Stat] = Field(alias="pm2Stats") + + +class NodeMetric(BaseModel): + uuid: str + name: str + address: str + is_online: bool = Field(alias="isOnline") + cpu_usage: float = Field(alias="cpuUsage") + memory_usage: float = Field(alias="memoryUsage") + network_upload: int = Field(alias="networkUpload") + network_download: int = Field(alias="networkDownload") + uptime: int + last_seen: datetime.datetime = Field(alias="lastSeen") + connected_users: int = Field(alias="connectedUsers") + + +class GetNodesMetricsResponseDto(BaseModel): + response: List[NodeMetric] diff --git a/remnawave/models/users.py b/remnawave/models/users.py index ab0a070..18120d2 100644 --- a/remnawave/models/users.py +++ b/remnawave/models/users.py @@ -33,9 +33,10 @@ class ActiveInternalSquadDto(BaseModel): class HappCrypto(BaseModel): cryptoLink: str + class CreateUserRequestDto(BaseModel): username: Annotated[ - str, StringConstraints(pattern=r"^[a-zA-Z0-9_-]+$", min_length=6, max_length=36) + str, StringConstraints(pattern=r"^[a-zA-Z0-9_-]+$", min_length=3, max_length=36) ] status: Optional[UserStatus] = None subscription_uuid: Optional[str] = Field( @@ -139,9 +140,7 @@ class UserResponseDto(BaseModel): ) subscription_url: str = Field(alias="subscriptionUrl") first_connected: Optional[datetime] = Field(None, alias="firstConnectedAt") - last_trigger_threshold: Optional[int] = Field( - None, alias="lastTriggeredThreshold" - ) + last_trigger_threshold: Optional[int] = Field(None, alias="lastTriggeredThreshold") last_connected_node: Optional[UserLastConnectedNodeDto] = Field( None, alias="lastConnectedNode" ) @@ -217,7 +216,8 @@ class GetUserByShortUuidResponseDto(UserResponseDto): class GetUserByUsernameResponseDto(UserResponseDto): pass - + + class RevokeUserRequestDto(BaseModel): short_uuid: Optional[str] = Field( None, @@ -226,4 +226,4 @@ class RevokeUserRequestDto(BaseModel): min_length=6, max_length=48, pattern=r"^[a-zA-Z0-9_-]+$", - ) \ No newline at end of file + )