From 34595a5e5b94536cfac44b62c92ddb75f49edca9 Mon Sep 17 00:00:00 2001 From: masasibata Date: Tue, 10 Feb 2026 18:23:13 +0300 Subject: [PATCH] feat: add nodes bulk actions and revoke-only-passwords support --- remnawave/controllers/nodes.py | 12 +++++++++++- remnawave/enums/webhook.py | 1 + remnawave/models/__init__.py | 6 ++++++ remnawave/models/nodes.py | 18 ++++++++++++++++-- remnawave/models/users.py | 6 +++++- 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/remnawave/controllers/nodes.py b/remnawave/controllers/nodes.py index 12c1256..1b99623 100644 --- a/remnawave/controllers/nodes.py +++ b/remnawave/controllers/nodes.py @@ -17,11 +17,13 @@ from remnawave.models import ( RestartNodeResponseDto, UpdateNodeRequestDto, UpdateNodeResponseDto, - RestartAllNodesRequestBodyDto, + RestartAllNodesRequestBodyDto, ResetNodeTrafficRequestDto, ResetNodeTrafficResponseDto, ProfileModificationRequestDto, ProfileModificationResponseDto, + NodesBulkActionsRequestDto, + NodesBulkActionsResponseDto, ) from remnawave.rapid import BaseController, delete, get, patch, post @@ -120,4 +122,12 @@ class NodesController(BaseController): body: Annotated[ProfileModificationRequestDto, PydanticBody()], ) -> ProfileModificationResponseDto: """Modify Inbounds & Profile for many nodes""" + ... + + @post("/nodes/bulk-actions", response_class=NodesBulkActionsResponseDto) + async def nodes_bulk_actions( + self, + body: Annotated[NodesBulkActionsRequestDto, PydanticBody()], + ) -> NodesBulkActionsResponseDto: + """Perform actions for many nodes (ENABLE, DISABLE, RESTART, RESET_TRAFFIC)""" ... \ No newline at end of file diff --git a/remnawave/enums/webhook.py b/remnawave/enums/webhook.py index 68c3a5c..1ab22f4 100644 --- a/remnawave/enums/webhook.py +++ b/remnawave/enums/webhook.py @@ -35,6 +35,7 @@ TServiceEvents = Literal[ "service.panel_started", "service.login_attempt_failed", "service.login_attempt_success", + "service.subpage_config_changed", ] TErrorsEvents = Literal[ diff --git a/remnawave/models/__init__.py b/remnawave/models/__init__.py index 94104d8..c0a7ce2 100644 --- a/remnawave/models/__init__.py +++ b/remnawave/models/__init__.py @@ -196,6 +196,9 @@ from .nodes import ( ResetNodeTrafficResponseDto, ProfileModificationRequestDto, ProfileModificationResponseDto, + NodeBulkActionType, + NodesBulkActionsRequestDto, + NodesBulkActionsResponseDto, ) from .nodes_usage_history import ( GetNodeUserUsageByRangeResponseDto, @@ -488,6 +491,9 @@ __all__ = [ "ResetNodeTrafficResponseDto", "ProfileModificationRequestDto", "ProfileModificationResponseDto", + "NodeBulkActionType", + "NodesBulkActionsRequestDto", + "NodesBulkActionsResponseDto", # Hosts models "CreateHostRequestDto", "CreateHostResponseDto", diff --git a/remnawave/models/nodes.py b/remnawave/models/nodes.py index e8564f5..24e8bab 100644 --- a/remnawave/models/nodes.py +++ b/remnawave/models/nodes.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Annotated, List, Optional, Union +from typing import Annotated, List, Optional, Union, Literal from uuid import UUID from pydantic import BaseModel, Field, StringConstraints, RootModel @@ -253,4 +253,18 @@ class ProfileModificationResponseDto(ProfileModificationResponseData): # Для обратной совместимости RestartAllNodesRequestDto = RestartAllNodesRequestBodyDto -NodesResponseDto = NodeResponseDto \ No newline at end of file +NodesResponseDto = NodeResponseDto + + +NodeBulkActionType = Literal["ENABLE", "DISABLE", "RESTART", "RESET_TRAFFIC"] + + +class NodesBulkActionsRequestDto(BaseModel): + """Request for performing bulk actions on nodes""" + uuids: List[UUID] = Field(min_length=1) + action: NodeBulkActionType = Field(description="Action to perform on nodes") + + +class NodesBulkActionsResponseDto(BaseModel): + """Response after performing bulk actions on nodes""" + event_sent: bool = Field(alias="eventSent") \ No newline at end of file diff --git a/remnawave/models/users.py b/remnawave/models/users.py index 2e7ba56..80cc6b8 100644 --- a/remnawave/models/users.py +++ b/remnawave/models/users.py @@ -196,7 +196,11 @@ class RevokeUserRequestDto(BaseModel): max_length=48, pattern=r"^[a-zA-Z0-9_-]+$", ) - + revoke_only_passwords: Optional[bool] = Field( + None, + serialization_alias="revokeOnlyPasswords", + description="Optional. If true, only passwords will be revoked without changing the short UUID.", + ) class SubscriptionRequestRecord(BaseModel): """Subscription request history record"""