feat: добавить поддержку переупорядочивания профилей, внешних и внутренних отрядов, а также шаблонов подписок

This commit is contained in:
Artem 2026-02-18 01:36:31 +01:00
parent 6c9fa202ad
commit 32d99f3f2d
No known key found for this signature in database
GPG key ID: 833485276B7902CE
19 changed files with 283 additions and 5 deletions

View file

@ -11,6 +11,8 @@ from remnawave.models import (
GetAllInboundsResponseDto,
GetConfigProfileByUuidResponseDto,
GetInboundsByProfileUuidResponseDto,
ReorderConfigProfilesRequestDto,
ReorderConfigProfilesResponseDto,
UpdateConfigProfileRequestDto,
UpdateConfigProfileResponseDto,
)
@ -68,6 +70,14 @@ class ConfigProfilesController(BaseController):
"""Delete config profile"""
...
@post("/config-profiles/actions/reorder", response_class=ReorderConfigProfilesResponseDto)
async def reorder_config_profiles(
self,
body: Annotated[ReorderConfigProfilesRequestDto, PydanticBody()],
) -> ReorderConfigProfilesResponseDto:
"""Reorder config profiles"""
...
# Get computed config profile by uuid
@get("/config-profiles/{uuid}/computed-config", response_class=GetConfigProfileByUuidResponseDto)
async def get_computed_config_profile_by_uuid(

View file

@ -10,6 +10,8 @@ from remnawave.models import (
GetExternalSquadByUuidResponseDto,
GetExternalSquadsResponseDto,
RemoveUsersFromExternalSquadResponseDto,
ReorderExternalSquadsRequestDto,
ReorderExternalSquadsResponseDto,
UpdateExternalSquadRequestDto,
UpdateExternalSquadResponseDto,
)
@ -70,4 +72,11 @@ class ExternalSquadsController(BaseController):
uuid: str,
) -> RemoveUsersFromExternalSquadResponseDto:
"""Delete users from external squad"""
...
@post("/external-squads/actions/reorder", response_class=ReorderExternalSquadsResponseDto)
async def reorder_external_squads(
self,
body: Annotated[ReorderExternalSquadsRequestDto, PydanticBody()],
) -> ReorderExternalSquadsResponseDto:
"""Reorder external squads"""
...

View file

@ -13,6 +13,8 @@ from remnawave.models import (
DeleteUsersFromInternalSquadResponseDto,
GetAllInternalSquadsResponseDto,
GetInternalSquadByUuidResponseDto,
ReorderInternalSquadsRequestDto,
ReorderInternalSquadsResponseDto,
UpdateInternalSquadRequestDto,
UpdateInternalSquadResponseDto,
GetInternalSquadAccessibleNodesResponseDto,
@ -90,3 +92,11 @@ class InternalSquadsController(BaseController):
) -> GetInternalSquadAccessibleNodesResponseDto:
"""Get accessible nodes for internal squad"""
...
@post("/internal-squads/actions/reorder", response_class=ReorderInternalSquadsResponseDto)
async def reorder_internal_squads(
self,
body: Annotated[ReorderInternalSquadsRequestDto, PydanticBody()],
) -> ReorderInternalSquadsResponseDto:
"""Reorder internal squads"""
...

View file

@ -115,6 +115,14 @@ class NodesController(BaseController):
) -> ReorderNodeResponseDto:
"""Reorder Nodes"""
...
@post("/nodes/{uuid}/actions/reset-traffic", response_class=ResetNodeTrafficResponseDto)
async def reset_node_traffic(
self,
uuid: Annotated[str, Path(description="UUID of the node")],
) -> ResetNodeTrafficResponseDto:
"""Reset traffic for individual node"""
...
@post("/nodes/actions/reset-traffic", response_class=ResetNodeTrafficResponseDto)
async def reset_traffic_all_nodes(

View file

@ -1,11 +1,19 @@
from typing import Annotated
from rapid_api_client import Path, Query
from rapid_api_client.annotations import PydanticBody
from remnawave.enums import ClientType
from remnawave.models.subscription import GetRawSubscriptionByShortUuidResponseDto
from remnawave.rapid import BaseController, get
from remnawave.models import GetAllSubscriptionsResponseDto, GetSubscriptionByUsernameResponseDto, GetSubscriptionByShortUUIDResponseDto, GetSubscriptionByUUIDResponseDto
from remnawave.models import (
GetAllSubscriptionsResponseDto,
GetSubscriptionByUsernameResponseDto,
GetSubscriptionByShortUUIDResponseDto,
GetSubscriptionByUUIDResponseDto,
GetSubpageConfigByShortUuidRequestBodyDto,
GetSubpageConfigByShortUuidResponseDto,
)
class SubscriptionsController(BaseController):
@ -47,6 +55,15 @@ class SubscriptionsController(BaseController):
"""None"""
...
@get("/subscriptions/subpage-config/{short_uuid}", response_class=GetSubpageConfigByShortUuidResponseDto)
async def get_subpage_config(
self,
short_uuid: Annotated[str, Path(description="Short UUID of the subscription")],
body: Annotated[GetSubpageConfigByShortUuidRequestBodyDto, PydanticBody()],
) -> GetSubpageConfigByShortUuidResponseDto:
"""Get subscription page config by short UUID"""
...
@get("/subscriptions/by-short-uuid/{short_uuid}/raw", response_class=GetRawSubscriptionByShortUuidResponseDto)
async def get_raw_subscription(
self,

View file

@ -9,6 +9,8 @@ from remnawave.models import (
DeleteSubscriptionTemplateResponseDto,
GetTemplateResponseDto,
GetTemplatesResponseDto,
ReorderSubscriptionTemplatesRequestDto,
ReorderSubscriptionTemplatesResponseDto,
UpdateTemplateRequestDto,
UpdateTemplateResponseDto,
)
@ -51,4 +53,11 @@ class SubscriptionsTemplateController(BaseController):
uuid: Annotated[str, Path(description="Template UUID")],
) -> DeleteSubscriptionTemplateResponseDto:
"""Delete subscription template"""
...
@post("/subscription-templates/actions/reorder", response_class=ReorderSubscriptionTemplatesResponseDto)
async def reorder_templates(
self,
body: Annotated[ReorderSubscriptionTemplatesRequestDto, PydanticBody()],
) -> ReorderSubscriptionTemplatesResponseDto:
"""Reorder subscription templates"""
...

View file

@ -61,6 +61,9 @@ from .config_profiles import (
GetInboundsByProfileUuidResponseDto,
InboundDto,
NodesProfileDto,
ReorderConfigProfileItem,
ReorderConfigProfilesRequestDto,
ReorderConfigProfilesResponseDto,
UpdateConfigProfileRequestDto,
UpdateConfigProfileResponseDto,
)
@ -166,6 +169,9 @@ from .internal_squads import (
GetAllInternalSquadsResponseDto,
GetInternalSquadByUuidResponseDto,
InternalSquadDto,
ReorderInternalSquadItem,
ReorderInternalSquadsRequestDto,
ReorderInternalSquadsResponseDto,
UpdateInternalSquadRequestDto,
UpdateInternalSquadResponseDto,
GetInternalSquadAccessibleNodesResponseDto,
@ -243,6 +249,9 @@ from .subscriptions_template import (
GetTemplatesResponseDto,
TemplateInfoDto,
GetTemplateResponseDto,
ReorderTemplateItem,
ReorderSubscriptionTemplatesRequestDto,
ReorderSubscriptionTemplatesResponseDto,
TemplateResponseDto,
UpdateTemplateRequestDto,
UpdateTemplateResponseDto,
@ -406,6 +415,9 @@ from .external_squads import (
GetExternalSquadByUuidResponseDto,
GetExternalSquadsResponseDto,
RemoveUsersFromExternalSquadResponseDto,
ReorderExternalSquadItem,
ReorderExternalSquadsRequestDto,
ReorderExternalSquadsResponseDto,
TemplateType,
UpdateExternalSquadRequestDto,
UpdateExternalSquadResponseDto,
@ -445,6 +457,9 @@ from .subscription_page import (
DeleteSubscriptionPageConfigResponseDto,
GetSubscriptionPageConfigResponseDto,
GetSubscriptionPageConfigsResponseDto,
GetSubpageConfigByShortUuidRequestBodyDto,
GetSubpageConfigByShortUuidResponseDto,
SubpageConfigData,
ReorderSubscriptionPageConfigItem,
ReorderSubscriptionPageConfigsRequestDto,
ReorderSubscriptionPageConfigsResponseDto,
@ -564,6 +579,9 @@ __all__ = [
"CreateSubscriptionTemplateResponseDto",
"DeleteSubscriptionTemplateResponseDto",
"GetTemplatesResponseDto",
"ReorderTemplateItem",
"ReorderSubscriptionTemplatesRequestDto",
"ReorderSubscriptionTemplatesResponseDto",
"TemplateInfoDto",
# System models
"BandwidthStatistic",
@ -723,6 +741,9 @@ __all__ = [
"GetInboundsByProfileUuidResponseDto",
"InboundDto",
"NodesProfileDto",
"ReorderConfigProfileItem",
"ReorderConfigProfilesRequestDto",
"ReorderConfigProfilesResponseDto",
"UpdateConfigProfileRequestDto",
"UpdateConfigProfileResponseDto",
"GetAllConfigProfilesResponsePaginated",
@ -766,8 +787,12 @@ __all__ = [
"GetAllInternalSquadsResponseDto",
"GetInternalSquadByUuidResponseDto",
"InternalSquadDto",
"ReorderInternalSquadItem",
"ReorderInternalSquadsRequestDto",
"ReorderInternalSquadsResponseDto",
"UpdateInternalSquadRequestDto",
"UpdateInternalSquadResponseDto",
"GetInternalSquadAccessibleNodesResponseDto",
# Nodes usage history models
"GetNodeUserUsageByRangeResponseDto",
"GetNodesUsageByRangeResponseDto",
@ -838,6 +863,9 @@ __all__ = [
"GetExternalSquadByUuidResponseDto",
"GetExternalSquadsResponseDto",
"RemoveUsersFromExternalSquadResponseDto",
"ReorderExternalSquadItem",
"ReorderExternalSquadsRequestDto",
"ReorderExternalSquadsResponseDto",
"TemplateType",
"UpdateExternalSquadRequestDto",
"UpdateExternalSquadResponseDto",
@ -879,6 +907,9 @@ __all__ = [
"DeleteSubscriptionPageConfigResponseDto",
"GetSubscriptionPageConfigResponseDto",
"GetSubscriptionPageConfigsResponseDto",
"GetSubpageConfigByShortUuidRequestBodyDto",
"GetSubpageConfigByShortUuidResponseDto",
"SubpageConfigData",
"ReorderSubscriptionPageConfigItem",
"ReorderSubscriptionPageConfigsRequestDto",
"ReorderSubscriptionPageConfigsResponseDto",

View file

@ -73,3 +73,16 @@ class GetAllInboundsResponseDto(List[InboundDto]):
class GetInboundsByProfileUuidResponseDto(List[InboundDto]):
pass
class ReorderConfigProfileItem(BaseModel):
view_position: int = Field(serialization_alias="viewPosition")
uuid: UUID
class ReorderConfigProfilesRequestDto(BaseModel):
items: List[ReorderConfigProfileItem]
class ReorderConfigProfilesResponseDto(GetAllConfigProfilesResponsePaginated):
pass

View file

@ -106,6 +106,26 @@ class DeleteExternalSquadResponseDto(BaseModel):
is_deleted: bool = Field(alias="isDeleted")
class ReorderExternalSquadItem(BaseModel):
view_position: int = Field(serialization_alias="viewPosition")
uuid: UUID
class ReorderExternalSquadsRequestDto(BaseModel):
items: List[ReorderExternalSquadItem]
class ReorderExternalSquadsResponseDto(BaseModel):
"""Response after reordering external squads"""
total: int = Field(alias="total")
external_squads: List[ExternalSquadDto] = Field(alias="externalSquads")
class DeleteExternalSquadResponseDto(BaseModel):
"""Response after deleting external squad"""
is_deleted: bool = Field(alias="isDeleted")
class AddUsersToExternalSquadResponseDto(BaseModel):
"""Response after adding users to external squad"""
event_sent: bool = Field(alias="eventSent")

View file

@ -101,3 +101,16 @@ class AccessibleNodeDto(BaseModel):
class GetInternalSquadAccessibleNodesResponseDto(BaseModel):
squad_uuid: UUID = Field(alias="squadUuid")
accessible_nodes: List[AccessibleNodeDto] = Field(alias="accessibleNodes")
class ReorderInternalSquadItem(BaseModel):
view_position: int = Field(serialization_alias="viewPosition")
uuid: UUID
class ReorderInternalSquadsRequestDto(BaseModel):
items: List[ReorderInternalSquadItem]
class ReorderInternalSquadsResponseDto(GetAllInternalSquadsResponse):
pass

View file

@ -212,6 +212,10 @@ class RestartAllNodesResponseDto(BaseModel):
event_sent: bool = Field(alias="eventSent")
class ResetNodeTrafficResponseDto(BaseModel):
event_sent: bool = Field(alias="eventSent")
class ReorderNodeResponseDto(RootModel[List[NodeResponseDto]]):
root: List[NodeResponseDto]

View file

@ -104,4 +104,26 @@ class CloneSubscriptionPageConfigRequestDto(BaseModel):
class CloneSubscriptionPageConfigResponseDto(SubscriptionPageConfigDto):
"""Response after cloning subscription page config"""
pass
pass
class GetSubpageConfigByShortUuidRequestBodyDto(BaseModel):
"""Request body for getting subpage config by short UUID"""
model_config = ConfigDict(populate_by_name=True)
request_headers: dict[str, str] = Field(default_factory=dict, serialization_alias="requestHeaders")
class SubpageConfigData(BaseModel):
"""Data inside GetSubpageConfigByShortUuidResponseDto"""
model_config = ConfigDict(populate_by_name=True)
subpage_config_uuid: UUID | None = Field(alias="subpageConfigUuid")
webpage_allowed: bool = Field(alias="webpageAllowed")
class GetSubpageConfigByShortUuidResponseDto(BaseModel):
"""Response for getting subpage config by short UUID"""
model_config = ConfigDict(populate_by_name=True)
response: SubpageConfigData

View file

@ -65,6 +65,19 @@ class DeleteSubscriptionTemplateResponseDto(DeleteTemplateData):
pass
class ReorderTemplateItem(BaseModel):
view_position: int = Field(serialization_alias="viewPosition")
uuid: UUID
class ReorderSubscriptionTemplatesRequestDto(BaseModel):
items: List[ReorderTemplateItem]
class ReorderSubscriptionTemplatesResponseDto(GetTemplatesData):
pass
# Legacy aliases for backward compatibility
class UpdateTemplateRequestDtoLegacy(BaseModel):
template_type: TemplateType = Field(serialization_alias="templateType")

20
test_imports.py Normal file
View file

@ -0,0 +1,20 @@
#!/usr/bin/env python3
"""Quick test to verify all new imports work"""
try:
from remnawave.models import (
ReorderConfigProfilesRequestDto,
ReorderSubscriptionTemplatesRequestDto,
ReorderInternalSquadsRequestDto,
ReorderExternalSquadsRequestDto,
GetSubpageConfigByShortUuidResponseDto,
)
print("Все новые модели успешно импортируются!")
print(" - ReorderConfigProfilesRequestDto")
print(" - ReorderSubscriptionTemplatesRequestDto")
print(" - ReorderInternalSquadsRequestDto")
print(" - ReorderExternalSquadsRequestDto")
print(" - GetSubpageConfigByShortUuidResponseDto")
except ImportError as e:
print(f"❌ Ошибка импорта: {e}")
exit(1)

View file

@ -8,6 +8,9 @@ from remnawave.models import (
GetAllInboundsResponseDto,
GetConfigProfileByUuidResponseDto,
GetInboundsByProfileUuidResponseDto,
ReorderConfigProfileItem,
ReorderConfigProfilesRequestDto,
ReorderConfigProfilesResponseDto,
UpdateConfigProfileRequestDto,
UpdateConfigProfileResponseDto,
)
@ -128,6 +131,21 @@ async def test_config_profiles(remnawave) -> None:
inbounds_by_profile = await remnawave.config_profiles.get_inbounds_by_profile_uuid(profile_uuid)
assert isinstance(inbounds_by_profile, GetInboundsByProfileUuidResponseDto)
# Test reorder config profiles
all_profiles_before_reorder = await remnawave.config_profiles.get_config_profiles()
if len(all_profiles_before_reorder.config_profiles) >= 2:
items = [
ReorderConfigProfileItem(
uuid=profile.uuid,
view_position=idx
)
for idx, profile in enumerate(all_profiles_before_reorder.config_profiles)
]
reorder_result = await remnawave.config_profiles.reorder_config_profiles(
ReorderConfigProfilesRequestDto(items=items)
)
assert isinstance(reorder_result, ReorderConfigProfilesResponseDto)
# Test delete config profile
delete_profile = await remnawave.config_profiles.delete_config_profile_by_uuid(profile_uuid)
assert isinstance(delete_profile, DeleteConfigProfileResponseDto)

View file

@ -11,6 +11,9 @@ from remnawave.models import (
DeleteUsersFromInternalSquadResponseDto,
GetAllInternalSquadsResponseDto,
GetInternalSquadByUuidResponseDto,
ReorderInternalSquadItem,
ReorderInternalSquadsRequestDto,
ReorderInternalSquadsResponseDto,
UpdateInternalSquadRequestDto,
UpdateInternalSquadResponseDto,
)
@ -69,6 +72,21 @@ async def test_internal_squads(remnawave) -> None:
)
assert isinstance(remove_users, DeleteUsersFromInternalSquadResponseDto)
# Test reorder internal squads
all_squads = await remnawave.internal_squads.get_internal_squads()
if len(all_squads.internal_squads) >= 2:
items = [
ReorderInternalSquadItem(
uuid=squad.uuid,
view_position=idx
)
for idx, squad in enumerate(all_squads.internal_squads)
]
reorder_result = await remnawave.internal_squads.reorder_internal_squads(
ReorderInternalSquadsRequestDto(items=items)
)
assert isinstance(reorder_result, ReorderInternalSquadsResponseDto)
# Test delete internal squad
delete_squad = await remnawave.internal_squads.delete_internal_squad(squad_uuid)

View file

@ -11,6 +11,7 @@ from remnawave.models import (
NodesResponseDto,
ReorderNodeRequestDto,
ReorderNodeResponseDto,
ResetNodeTrafficResponseDto,
UpdateNodeRequestDto,
)
from remnawave.models.nodes import ReorderNodeItem
@ -64,6 +65,10 @@ async def test_nodes(remnawave):
assert update_node.uuid == create_node.uuid
assert update_node.name == update_name
reset_traffic = await remnawave.nodes.reset_node_traffic(uuid=string_uuid)
assert isinstance(reset_traffic, ResetNodeTrafficResponseDto)
assert reset_traffic.event_sent is True
delete_node = await remnawave.nodes.delete_node(uuid=string_uuid)
assert isinstance(delete_node, DeleteNodeResponseDto)
assert delete_node.is_deleted is True

View file

@ -6,7 +6,10 @@ from remnawave.models import (
GetSubscriptionInfoResponseDto,
GetRawSubscriptionByShortUuidResponseDto,
GetAllSubscriptionsResponseDto,
GetSubscriptionByUsernameResponseDto
GetSubscriptionByUsernameResponseDto,
GetSubpageConfigByShortUuidRequestBodyDto,
GetSubpageConfigByShortUuidResponseDto,
SubpageConfigData,
)
from tests.conftest import REMNAWAVE_SHORT_UUID, REMNAWAVE_USER_USERNAME
@ -71,4 +74,16 @@ class TestSubscriptionsManagement:
subscription_by_username = await remnawave.subscriptions.get_subscription_by_username(
username=REMNAWAVE_USER_USERNAME
)
assert isinstance(subscription_by_username, GetSubscriptionByUsernameResponseDto)
assert isinstance(subscription_by_username, GetSubscriptionByUsernameResponseDto)
@pytest.mark.asyncio
async def test_get_subpage_config(self, remnawave):
"""Тест получения конфига страницы подписки по short UUID"""
body = GetSubpageConfigByShortUuidRequestBodyDto(request_headers={})
subpage_config = await remnawave.subscriptions.get_subpage_config(
short_uuid=REMNAWAVE_SHORT_UUID,
body=body,
)
# Client auto-unwraps single "response" field → returns SubpageConfigData
assert isinstance(subpage_config, (GetSubpageConfigByShortUuidResponseDto, SubpageConfigData))
assert hasattr(subpage_config, 'webpage_allowed')

View file

@ -6,6 +6,9 @@ from remnawave.models import (
DeleteSubscriptionTemplateResponseDto,
GetTemplateResponseDto,
GetTemplatesResponseDto,
ReorderTemplateItem,
ReorderSubscriptionTemplatesRequestDto,
ReorderSubscriptionTemplatesResponseDto,
UpdateTemplateRequestDto,
UpdateTemplateResponseDto,
)
@ -88,4 +91,24 @@ async def test_delete_template(remnawave):
str(created.uuid)
)
assert isinstance(delete_response, DeleteSubscriptionTemplateResponseDto)
assert delete_response.is_deleted is True
assert delete_response.is_deleted is True
@pytest.mark.asyncio
async def test_reorder_templates(remnawave):
"""Проверка изменения порядка шаблонов"""
templates = await remnawave.subscriptions_template.get_all_templates()
assert isinstance(templates, GetTemplatesResponseDto)
if len(templates.templates) >= 2:
items = [
ReorderTemplateItem(
uuid=tmpl.uuid,
view_position=idx
)
for idx, tmpl in enumerate(templates.templates)
]
reorder_result = await remnawave.subscriptions_template.reorder_templates(
ReorderSubscriptionTemplatesRequestDto(items=items)
)
assert isinstance(reorder_result, ReorderSubscriptionTemplatesResponseDto)