mirror of
https://github.com/remnawave/python-sdk.git
synced 2026-08-04 06:35:09 +00:00
Merge pull request #56 from remnawave/development
Some checks failed
Publish Python Package / publish (push) Has been cancelled
Some checks failed
Publish Python Package / publish (push) Has been cancelled
Upgrade SDK to Remnawave API v2.8.0
This commit is contained in:
commit
eee9be821a
26 changed files with 753 additions and 140 deletions
|
|
@ -50,6 +50,7 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
|
|||
|
||||
| Contract Version | Remnawave Panel Version |
|
||||
| ---------------- | ----------------------- |
|
||||
| 2.8.0 | >=2.8.0 |
|
||||
| 2.7.0 | >=2.7.0 |
|
||||
| 2.6.3 | >=2.6.3 |
|
||||
| 2.6.2 | >=2.6.2 |
|
||||
|
|
|
|||
4
poetry.lock
generated
4
poetry.lock
generated
|
|
@ -894,5 +894,5 @@ typing-extensions = ">=4.12.0"
|
|||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.11,<4.0"
|
||||
content-hash = "2e64664ad5ef0cd863e8a3898493f4cdcf0544ada35d9ecad6aa686b32bdb1ac"
|
||||
python-versions = ">=3.11,<3.15"
|
||||
content-hash = "7b2d287bb0036b4254e0589beb3d86856735c088d39dcbf1bb2c965ce04a045f"
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
[project]
|
||||
name = "remnawave"
|
||||
version = "2.7.1"
|
||||
description = "A Python SDK for interacting with the Remnawave API v2.7.1."
|
||||
version = "2.8.0"
|
||||
description = "A Python SDK for interacting with the Remnawave API v2.8.0."
|
||||
authors = [
|
||||
{name = "Artem",email = "dev@forestsnet.com"}
|
||||
]
|
||||
license = { text = "MIT" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11,<3.14"
|
||||
requires-python = ">=3.11,<3.15"
|
||||
dependencies = [
|
||||
"rapid-api-client (==0.6.0)",
|
||||
"orjson (>=3.10.15,<4.0.0)",
|
||||
"httpx (>=0.27.2,<0.28.0)",
|
||||
"pydantic[email]>=2.9.2,<3.0.0",
|
||||
"pydantic-core>=2.33.1,<2.34.0",
|
||||
"pydantic>=2.9.2,<3.0.0",
|
||||
"cryptography (>=46.0.3,<47.0.0)",
|
||||
]
|
||||
keywords = ["remnawave", "api", "sdk", "proxy", "httpx", "async", "xray"]
|
||||
|
|
@ -26,6 +24,7 @@ classifiers = [
|
|||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
"Framework :: AsyncIO",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from remnawave.models import (
|
|||
CreateApiTokenResponseDto,
|
||||
DeleteApiTokenResponseDto,
|
||||
FindAllApiTokensResponseDto,
|
||||
GetApiTokenScopesResponseDto,
|
||||
)
|
||||
from remnawave.rapid import BaseController, delete, get, post
|
||||
|
||||
|
|
@ -36,3 +37,10 @@ class APITokensManagementController(BaseController):
|
|||
) -> FindAllApiTokensResponseDto:
|
||||
"""Get all API tokens"""
|
||||
...
|
||||
|
||||
@get("/tokens/scopes", response_class=GetApiTokenScopesResponseDto)
|
||||
async def get_scopes(
|
||||
self,
|
||||
) -> GetApiTokenScopesResponseDto:
|
||||
"""Get available API token scopes"""
|
||||
...
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Annotated
|
||||
|
||||
from rapid_api_client import Path, Query
|
||||
from rapid_api_client.annotations import PydanticBody
|
||||
|
||||
from remnawave.models.bandwidthstats import (
|
||||
GetLegacyStatsNodesUsersUsageResponseDto,
|
||||
|
|
@ -11,10 +12,12 @@ from remnawave.models.bandwidthstats import (
|
|||
GetStatsNodesRealtimeUsageResponseDto,
|
||||
GetStatsNodesUsageResponseDto,
|
||||
GetStatsNodeUsersUsageResponseDto,
|
||||
GetStatsNodesUsersUsageRequestDto,
|
||||
GetStatsNodesUsersUsageResponseDto,
|
||||
GetStatsUserUsageResponseDto,
|
||||
GetUserUsageByRangeResponseDto,
|
||||
)
|
||||
from remnawave.rapid import BaseController, get
|
||||
from remnawave.rapid import BaseController, get, post
|
||||
|
||||
|
||||
class BandWidthStatsController(BaseController):
|
||||
|
|
@ -70,6 +73,17 @@ class BandWidthStatsController(BaseController):
|
|||
"""Get Node Users Usage by Node UUID"""
|
||||
...
|
||||
|
||||
@post("/bandwidth-stats/nodes/users", response_class=GetStatsNodesUsersUsageResponseDto)
|
||||
async def get_stats_nodes_users_usage(
|
||||
self,
|
||||
body: Annotated[GetStatsNodesUsersUsageRequestDto, PydanticBody()],
|
||||
top_users_limit: Annotated[int, Query(description="Limit of top users to return", alias="topUsersLimit")],
|
||||
start: Annotated[str, Query(description="Start date (YYYY-MM-DD)")],
|
||||
end: Annotated[str, Query(description="End date (YYYY-MM-DD)")],
|
||||
) -> GetStatsNodesUsersUsageResponseDto:
|
||||
"""Get Nodes Users Usage by Nodes UUIDs"""
|
||||
...
|
||||
|
||||
@get("/bandwidth-stats/users/{uuid}", response_class=GetStatsUserUsageResponseDto)
|
||||
async def get_stats_user_usage(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -7,12 +7,10 @@ from remnawave.models import (
|
|||
BulkDeleteHostsResponseDto,
|
||||
BulkDisableHostsResponseDto,
|
||||
BulkEnableHostsResponseDto,
|
||||
SetInboundToManyHostsRequestDto,
|
||||
SetInboundToManyHostsResponseDto,
|
||||
SetPortToManyHostsResponseDto,
|
||||
SetPortToManyHostsRequestDto
|
||||
UpdateManyHostsRequestDto,
|
||||
UpdateManyHostsResponseDto,
|
||||
)
|
||||
from remnawave.rapid import AttributeBody, BaseController, post
|
||||
from remnawave.rapid import AttributeBody, BaseController, patch, post
|
||||
|
||||
|
||||
class HostsBulkActionsController(BaseController):
|
||||
|
|
@ -40,21 +38,10 @@ class HostsBulkActionsController(BaseController):
|
|||
"""Enable many hosts"""
|
||||
...
|
||||
|
||||
@post(
|
||||
"/hosts/bulk/set-inbound",
|
||||
response_class=SetInboundToManyHostsResponseDto,
|
||||
)
|
||||
async def set_inbound_to_hosts(
|
||||
@patch("/hosts/bulk/update", response_class=UpdateManyHostsResponseDto)
|
||||
async def update_hosts(
|
||||
self,
|
||||
body: Annotated[SetInboundToManyHostsRequestDto, PydanticBody()],
|
||||
) -> SetInboundToManyHostsResponseDto:
|
||||
"""Set inbound to many hosts"""
|
||||
...
|
||||
|
||||
@post("/hosts/bulk/set-port", response_class=SetPortToManyHostsResponseDto)
|
||||
async def set_port_to_hosts(
|
||||
self,
|
||||
body: Annotated[SetPortToManyHostsRequestDto, PydanticBody()],
|
||||
) -> SetPortToManyHostsResponseDto:
|
||||
"""Set port to many hosts"""
|
||||
body: Annotated[UpdateManyHostsRequestDto, PydanticBody()],
|
||||
) -> UpdateManyHostsResponseDto:
|
||||
"""Update many hosts"""
|
||||
...
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from remnawave.models import (
|
|||
UpdateNodeRequestDto,
|
||||
UpdateNodeResponseDto,
|
||||
RestartAllNodesRequestBodyDto,
|
||||
RestartNodeRequestBodyDto,
|
||||
ResetNodeTrafficRequestDto,
|
||||
ResetNodeTrafficResponseDto,
|
||||
ProfileModificationRequestDto,
|
||||
|
|
@ -98,6 +99,7 @@ class NodesController(BaseController):
|
|||
async def restart_node(
|
||||
self,
|
||||
uuid: Annotated[str, Path(description="Node UUID")],
|
||||
body: Annotated[RestartNodeRequestBodyDto | None, PydanticBody()] = None,
|
||||
) -> RestartNodeResponseDto:
|
||||
"""Restart Node"""
|
||||
...
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ from remnawave.models import (
|
|||
GetNodesMetricsResponseDto,
|
||||
GetRemnawaveHealthResponseDto,
|
||||
GetX25519KeyPairResponseDto,
|
||||
EncryptHappCryptoLinkRequestDto,
|
||||
EncryptHappCryptoLinkResponseDto,
|
||||
DebugSrrMatcherRequestDto,
|
||||
DebugSrrMatcherResponseDto,
|
||||
GetMetadataResponseDto,
|
||||
|
|
@ -67,14 +65,6 @@ class SystemController(BaseController):
|
|||
"""Get X25519 Key Pair"""
|
||||
...
|
||||
|
||||
@post("/system/tools/happ/encrypt", response_class=EncryptHappCryptoLinkResponseDto)
|
||||
async def encrypt_happ_crypto_link(
|
||||
self,
|
||||
body: Annotated[EncryptHappCryptoLinkRequestDto, PydanticBody()],
|
||||
) -> EncryptHappCryptoLinkResponseDto:
|
||||
"""Encrypt Happ Crypto Link"""
|
||||
...
|
||||
|
||||
@post("/system/testers/srr-matcher", response_class=DebugSrrMatcherResponseDto)
|
||||
async def debug_srr_matcher(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from remnawave.models import (
|
|||
GetUserByUuidResponseDto,
|
||||
GetUserAccessibleNodesResponseDto,
|
||||
GetUserSubscriptionRequestHistoryResponseDto,
|
||||
GetUsersStreamResponseDto,
|
||||
TelegramUserResponseDto,
|
||||
EmailUserResponseDto,
|
||||
TagUserResponseDto,
|
||||
|
|
@ -56,13 +57,31 @@ class UsersController(BaseController):
|
|||
Query(default=None, description="Offset for pagination")
|
||||
] = None,
|
||||
size: Annotated[
|
||||
Optional[int],
|
||||
Optional[int],
|
||||
Query(default=None, description="Page size for pagination")
|
||||
] = None,
|
||||
) -> GetAllUsersResponseDto:
|
||||
"""Get all users"""
|
||||
...
|
||||
|
||||
@get("/users/stream", response_class=GetUsersStreamResponseDto)
|
||||
async def get_users_stream(
|
||||
self,
|
||||
size: Annotated[
|
||||
Optional[int],
|
||||
Query(default=None, description="Page size, no more than 1000 (default 250)"),
|
||||
] = None,
|
||||
cursor: Annotated[
|
||||
Optional[str],
|
||||
Query(
|
||||
default=None,
|
||||
description="Cursor from the previous response (nextCursor). Omit on the first request",
|
||||
),
|
||||
] = None,
|
||||
) -> GetUsersStreamResponseDto:
|
||||
"""Get all users using cursor-based (keyset) pagination"""
|
||||
...
|
||||
|
||||
@delete("/users/{uuid}", response_class=DeleteUserResponseDto)
|
||||
async def delete_user(
|
||||
self,
|
||||
|
|
@ -75,7 +94,7 @@ class UsersController(BaseController):
|
|||
async def revoke_user_subscription(
|
||||
self,
|
||||
uuid: Annotated[str, Path(description="UUID of the user")],
|
||||
body: Optional[Annotated[RevokeUserRequestDto, PydanticBody()]] = None,
|
||||
body: Annotated[Optional[RevokeUserRequestDto], PydanticBody()] = None,
|
||||
) -> RevokeUserSubscriptionResponseDto:
|
||||
"""Revoke User Subscription"""
|
||||
...
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ from .alpn import ALPN
|
|||
from .client_type import ClientType
|
||||
from .error_code import ErrorCode
|
||||
from .fingerprint import Fingerprint
|
||||
from .mihomo import MihomoIpVersion
|
||||
from .scopes import Scope
|
||||
from .security_layer import SecurityLayer
|
||||
from .template_type import TemplateType
|
||||
from .users import TrafficLimitStrategy, UserStatus
|
||||
|
|
@ -25,6 +27,8 @@ __all__ = [
|
|||
"ClientType",
|
||||
"ALPN",
|
||||
"Fingerprint",
|
||||
"MihomoIpVersion",
|
||||
"Scope",
|
||||
"SecurityLayer",
|
||||
"TemplateType",
|
||||
"ResponseRuleConditionOperator",
|
||||
|
|
|
|||
9
remnawave/enums/mihomo.py
Normal file
9
remnawave/enums/mihomo.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from enum import StrEnum
|
||||
|
||||
|
||||
class MihomoIpVersion(StrEnum):
|
||||
DUAL = "dual"
|
||||
IPV4 = "ipv4"
|
||||
IPV6 = "ipv6"
|
||||
IPV4_PREFER = "ipv4-prefer"
|
||||
IPV6_PREFER = "ipv6-prefer"
|
||||
279
remnawave/enums/scopes.py
Normal file
279
remnawave/enums/scopes.py
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
from enum import StrEnum
|
||||
|
||||
|
||||
class Scope(StrEnum):
|
||||
"""API token scopes available in Remnawave API v2.8.0.
|
||||
|
||||
Mirrors the catalog returned by ``GET /api/tokens/scopes``. Use these members
|
||||
when creating API tokens, e.g. ``CreateApiTokenRequestDto(scopes=[Scope.USERS_READ])``.
|
||||
|
||||
Coarse scopes per resource: ``<resource>:*`` (``*_ALL``), ``<resource>:read``,
|
||||
``<resource>:write``. Fine-grained per-endpoint scopes are also available.
|
||||
"""
|
||||
|
||||
WILDCARD = "*"
|
||||
|
||||
# Users
|
||||
USERS_ALL = "users:*"
|
||||
USERS_READ = "users:read"
|
||||
USERS_WRITE = "users:write"
|
||||
USERS_CREATE = "users:create"
|
||||
USERS_UPDATE = "users:update"
|
||||
USERS_DELETE = "users:delete"
|
||||
USERS_LIST = "users:list"
|
||||
USERS_STREAM = "users:stream"
|
||||
USERS_LIST_TAGS = "users:list-tags"
|
||||
USERS_ACCESSIBLE_NODES = "users:accessible-nodes"
|
||||
USERS_SUBSCRIPTION_REQUEST_HISTORY = "users:subscription-request-history"
|
||||
USERS_BY_SHORT_UUID = "users:by-short-uuid"
|
||||
USERS_BY_UUID = "users:by-uuid"
|
||||
USERS_BY_USERNAME = "users:by-username"
|
||||
USERS_BY_ID = "users:by-id"
|
||||
USERS_BY_TELEGRAM_ID = "users:by-telegram-id"
|
||||
USERS_BY_EMAIL = "users:by-email"
|
||||
USERS_BY_TAG = "users:by-tag"
|
||||
USERS_REVOKE_SUBSCRIPTION = "users:revoke-subscription"
|
||||
USERS_DISABLE = "users:disable"
|
||||
USERS_ENABLE = "users:enable"
|
||||
USERS_RESET_TRAFFIC = "users:reset-traffic"
|
||||
USERS_RESOLVE = "users:resolve"
|
||||
USERS_BULK_DELETE_BY_STATUS = "users:bulk-delete-by-status"
|
||||
USERS_BULK_DELETE = "users:bulk-delete"
|
||||
USERS_BULK_REVOKE_SUBSCRIPTION = "users:bulk-revoke-subscription"
|
||||
USERS_BULK_RESET_TRAFFIC = "users:bulk-reset-traffic"
|
||||
USERS_BULK_UPDATE_USERS = "users:bulk-update-users"
|
||||
USERS_BULK_UPDATE_SQUADS = "users:bulk-update-squads"
|
||||
USERS_BULK_EXTEND_EXPIRATION_DATE = "users:bulk-extend-expiration-date"
|
||||
USERS_BULK_ALL_UPDATE_USERS = "users:bulk-all-update-users"
|
||||
USERS_BULK_ALL_RESET_TRAFFIC = "users:bulk-all-reset-traffic"
|
||||
USERS_BULK_ALL_EXTEND_EXPIRATION_DATE = "users:bulk-all-extend-expiration-date"
|
||||
|
||||
# Hwid User Devices
|
||||
HWID_USER_DEVICES_ALL = "hwid-user-devices:*"
|
||||
HWID_USER_DEVICES_READ = "hwid-user-devices:read"
|
||||
HWID_USER_DEVICES_WRITE = "hwid-user-devices:write"
|
||||
HWID_USER_DEVICES_LIST = "hwid-user-devices:list"
|
||||
HWID_USER_DEVICES_CREATE = "hwid-user-devices:create"
|
||||
HWID_USER_DEVICES_DELETE = "hwid-user-devices:delete"
|
||||
HWID_USER_DEVICES_DELETE_ALL = "hwid-user-devices:delete-all"
|
||||
HWID_USER_DEVICES_STATS = "hwid-user-devices:stats"
|
||||
HWID_USER_DEVICES_TOP_USERS = "hwid-user-devices:top-users"
|
||||
HWID_USER_DEVICES_LIST_BY_USER = "hwid-user-devices:list-by-user"
|
||||
|
||||
# Subscriptions
|
||||
SUBSCRIPTIONS_ALL = "subscriptions:*"
|
||||
SUBSCRIPTIONS_READ = "subscriptions:read"
|
||||
SUBSCRIPTIONS_WRITE = "subscriptions:write"
|
||||
SUBSCRIPTIONS_LIST = "subscriptions:list"
|
||||
SUBSCRIPTIONS_BY_USERNAME = "subscriptions:by-username"
|
||||
SUBSCRIPTIONS_BY_SHORT_UUID_PROTECTED = "subscriptions:by-short-uuid-protected"
|
||||
SUBSCRIPTIONS_BY_UUID = "subscriptions:by-uuid"
|
||||
SUBSCRIPTIONS_RAW = "subscriptions:raw"
|
||||
SUBSCRIPTIONS_SUBPAGE_CONFIG = "subscriptions:subpage-config"
|
||||
SUBSCRIPTIONS_CONNECTION_KEYS = "subscriptions:connection-keys"
|
||||
|
||||
# Nodes
|
||||
NODES_ALL = "nodes:*"
|
||||
NODES_READ = "nodes:read"
|
||||
NODES_WRITE = "nodes:write"
|
||||
NODES_LIST_TAGS = "nodes:list-tags"
|
||||
NODES_CREATE = "nodes:create"
|
||||
NODES_LIST = "nodes:list"
|
||||
NODES_GET = "nodes:get"
|
||||
NODES_ENABLE = "nodes:enable"
|
||||
NODES_DISABLE = "nodes:disable"
|
||||
NODES_DELETE = "nodes:delete"
|
||||
NODES_UPDATE = "nodes:update"
|
||||
NODES_RESTART = "nodes:restart"
|
||||
NODES_RESET_TRAFFIC = "nodes:reset-traffic"
|
||||
NODES_RESTART_ALL = "nodes:restart-all"
|
||||
NODES_REORDER = "nodes:reorder"
|
||||
NODES_BULK_PROFILE_MODIFICATION = "nodes:bulk-profile-modification"
|
||||
NODES_BULK_ACTIONS = "nodes:bulk-actions"
|
||||
NODES_BULK_UPDATE = "nodes:bulk-update"
|
||||
|
||||
# Node Plugins
|
||||
NODE_PLUGINS_ALL = "node-plugins:*"
|
||||
NODE_PLUGINS_READ = "node-plugins:read"
|
||||
NODE_PLUGINS_WRITE = "node-plugins:write"
|
||||
NODE_PLUGINS_TORRENT_BLOCKER_REPORTS = "node-plugins:torrent-blocker-reports"
|
||||
NODE_PLUGINS_TORRENT_BLOCKER_STATS = "node-plugins:torrent-blocker-stats"
|
||||
NODE_PLUGINS_TRUNCATE = "node-plugins:truncate"
|
||||
NODE_PLUGINS_LIST = "node-plugins:list"
|
||||
NODE_PLUGINS_GET = "node-plugins:get"
|
||||
NODE_PLUGINS_UPDATE = "node-plugins:update"
|
||||
NODE_PLUGINS_DELETE = "node-plugins:delete"
|
||||
NODE_PLUGINS_CREATE = "node-plugins:create"
|
||||
NODE_PLUGINS_REORDER = "node-plugins:reorder"
|
||||
NODE_PLUGINS_CLONE = "node-plugins:clone"
|
||||
NODE_PLUGINS_EXECUTOR = "node-plugins:executor"
|
||||
|
||||
# Bandwidth Stats
|
||||
BANDWIDTH_STATS_ALL = "bandwidth-stats:*"
|
||||
BANDWIDTH_STATS_READ = "bandwidth-stats:read"
|
||||
BANDWIDTH_STATS_WRITE = "bandwidth-stats:write"
|
||||
BANDWIDTH_STATS_NODE_USERS_USAGE_LEGACY = "bandwidth-stats:node-users-usage-legacy"
|
||||
BANDWIDTH_STATS_NODE_USERS_USAGE = "bandwidth-stats:node-users-usage"
|
||||
BANDWIDTH_STATS_NODES_USERS_USAGE = "bandwidth-stats:nodes-users-usage"
|
||||
BANDWIDTH_STATS_USER_USAGE_LEGACY = "bandwidth-stats:user-usage-legacy"
|
||||
BANDWIDTH_STATS_USER_USAGE = "bandwidth-stats:user-usage"
|
||||
BANDWIDTH_STATS_NODES_USAGE = "bandwidth-stats:nodes-usage"
|
||||
|
||||
# Ip Control
|
||||
IP_CONTROL_ALL = "ip-control:*"
|
||||
IP_CONTROL_READ = "ip-control:read"
|
||||
IP_CONTROL_WRITE = "ip-control:write"
|
||||
IP_CONTROL_FETCH_IPS = "ip-control:fetch-ips"
|
||||
IP_CONTROL_FETCH_IPS_RESULT = "ip-control:fetch-ips-result"
|
||||
IP_CONTROL_DROP_CONNECTIONS = "ip-control:drop-connections"
|
||||
IP_CONTROL_FETCH_USERS_IPS = "ip-control:fetch-users-ips"
|
||||
IP_CONTROL_FETCH_USERS_IPS_RESULT = "ip-control:fetch-users-ips-result"
|
||||
|
||||
# Config Profiles
|
||||
CONFIG_PROFILES_ALL = "config-profiles:*"
|
||||
CONFIG_PROFILES_READ = "config-profiles:read"
|
||||
CONFIG_PROFILES_WRITE = "config-profiles:write"
|
||||
CONFIG_PROFILES_LIST = "config-profiles:list"
|
||||
CONFIG_PROFILES_LIST_INBOUNDS = "config-profiles:list-inbounds"
|
||||
CONFIG_PROFILES_LIST_PROFILE_INBOUNDS = "config-profiles:list-profile-inbounds"
|
||||
CONFIG_PROFILES_GET = "config-profiles:get"
|
||||
CONFIG_PROFILES_GET_COMPUTED = "config-profiles:get-computed"
|
||||
CONFIG_PROFILES_DELETE = "config-profiles:delete"
|
||||
CONFIG_PROFILES_CREATE = "config-profiles:create"
|
||||
CONFIG_PROFILES_UPDATE = "config-profiles:update"
|
||||
CONFIG_PROFILES_REORDER = "config-profiles:reorder"
|
||||
|
||||
# Internal Squads
|
||||
INTERNAL_SQUADS_ALL = "internal-squads:*"
|
||||
INTERNAL_SQUADS_READ = "internal-squads:read"
|
||||
INTERNAL_SQUADS_WRITE = "internal-squads:write"
|
||||
INTERNAL_SQUADS_LIST = "internal-squads:list"
|
||||
INTERNAL_SQUADS_GET = "internal-squads:get"
|
||||
INTERNAL_SQUADS_CREATE = "internal-squads:create"
|
||||
INTERNAL_SQUADS_ACCESSIBLE_NODES = "internal-squads:accessible-nodes"
|
||||
INTERNAL_SQUADS_UPDATE = "internal-squads:update"
|
||||
INTERNAL_SQUADS_DELETE = "internal-squads:delete"
|
||||
INTERNAL_SQUADS_ADD_USERS = "internal-squads:add-users"
|
||||
INTERNAL_SQUADS_REMOVE_USERS = "internal-squads:remove-users"
|
||||
INTERNAL_SQUADS_REORDER = "internal-squads:reorder"
|
||||
|
||||
# External Squads
|
||||
EXTERNAL_SQUADS_ALL = "external-squads:*"
|
||||
EXTERNAL_SQUADS_READ = "external-squads:read"
|
||||
EXTERNAL_SQUADS_WRITE = "external-squads:write"
|
||||
EXTERNAL_SQUADS_LIST = "external-squads:list"
|
||||
EXTERNAL_SQUADS_GET = "external-squads:get"
|
||||
EXTERNAL_SQUADS_CREATE = "external-squads:create"
|
||||
EXTERNAL_SQUADS_UPDATE = "external-squads:update"
|
||||
EXTERNAL_SQUADS_DELETE = "external-squads:delete"
|
||||
EXTERNAL_SQUADS_ADD_USERS = "external-squads:add-users"
|
||||
EXTERNAL_SQUADS_REMOVE_USERS = "external-squads:remove-users"
|
||||
EXTERNAL_SQUADS_REORDER = "external-squads:reorder"
|
||||
|
||||
# Hosts
|
||||
HOSTS_ALL = "hosts:*"
|
||||
HOSTS_READ = "hosts:read"
|
||||
HOSTS_WRITE = "hosts:write"
|
||||
HOSTS_LIST_TAGS = "hosts:list-tags"
|
||||
HOSTS_CREATE = "hosts:create"
|
||||
HOSTS_UPDATE = "hosts:update"
|
||||
HOSTS_LIST = "hosts:list"
|
||||
HOSTS_GET = "hosts:get"
|
||||
HOSTS_REORDER = "hosts:reorder"
|
||||
HOSTS_DELETE = "hosts:delete"
|
||||
HOSTS_BULK_DELETE = "hosts:bulk-delete"
|
||||
HOSTS_BULK_DISABLE = "hosts:bulk-disable"
|
||||
HOSTS_BULK_ENABLE = "hosts:bulk-enable"
|
||||
HOSTS_BULK_UPDATE = "hosts:bulk-update"
|
||||
|
||||
# Subscription Template
|
||||
SUBSCRIPTION_TEMPLATE_ALL = "subscription-template:*"
|
||||
SUBSCRIPTION_TEMPLATE_READ = "subscription-template:read"
|
||||
SUBSCRIPTION_TEMPLATE_WRITE = "subscription-template:write"
|
||||
SUBSCRIPTION_TEMPLATE_LIST = "subscription-template:list"
|
||||
SUBSCRIPTION_TEMPLATE_GET = "subscription-template:get"
|
||||
SUBSCRIPTION_TEMPLATE_UPDATE = "subscription-template:update"
|
||||
SUBSCRIPTION_TEMPLATE_DELETE = "subscription-template:delete"
|
||||
SUBSCRIPTION_TEMPLATE_CREATE = "subscription-template:create"
|
||||
SUBSCRIPTION_TEMPLATE_REORDER = "subscription-template:reorder"
|
||||
|
||||
# Subscription Settings
|
||||
SUBSCRIPTION_SETTINGS_ALL = "subscription-settings:*"
|
||||
SUBSCRIPTION_SETTINGS_READ = "subscription-settings:read"
|
||||
SUBSCRIPTION_SETTINGS_WRITE = "subscription-settings:write"
|
||||
SUBSCRIPTION_SETTINGS_GET = "subscription-settings:get"
|
||||
SUBSCRIPTION_SETTINGS_UPDATE = "subscription-settings:update"
|
||||
|
||||
# Infra Billing
|
||||
INFRA_BILLING_ALL = "infra-billing:*"
|
||||
INFRA_BILLING_READ = "infra-billing:read"
|
||||
INFRA_BILLING_WRITE = "infra-billing:write"
|
||||
INFRA_BILLING_LIST_PROVIDERS = "infra-billing:list-providers"
|
||||
INFRA_BILLING_GET_PROVIDER = "infra-billing:get-provider"
|
||||
INFRA_BILLING_DELETE_PROVIDER = "infra-billing:delete-provider"
|
||||
INFRA_BILLING_CREATE_PROVIDER = "infra-billing:create-provider"
|
||||
INFRA_BILLING_UPDATE_PROVIDER = "infra-billing:update-provider"
|
||||
INFRA_BILLING_CREATE_BILL_RECORD = "infra-billing:create-bill-record"
|
||||
INFRA_BILLING_LIST_BILL_RECORDS = "infra-billing:list-bill-records"
|
||||
INFRA_BILLING_DELETE_BILL_RECORD = "infra-billing:delete-bill-record"
|
||||
INFRA_BILLING_LIST_BILLING_NODES = "infra-billing:list-billing-nodes"
|
||||
INFRA_BILLING_UPDATE_BILLING_NODE = "infra-billing:update-billing-node"
|
||||
INFRA_BILLING_CREATE_BILLING_NODE = "infra-billing:create-billing-node"
|
||||
INFRA_BILLING_DELETE_BILLING_NODE = "infra-billing:delete-billing-node"
|
||||
|
||||
# System
|
||||
SYSTEM_ALL = "system:*"
|
||||
SYSTEM_READ = "system:read"
|
||||
SYSTEM_WRITE = "system:write"
|
||||
SYSTEM_METADATA = "system:metadata"
|
||||
SYSTEM_STATS = "system:stats"
|
||||
SYSTEM_BANDWIDTH_STATS = "system:bandwidth-stats"
|
||||
SYSTEM_NODES_STATISTICS = "system:nodes-statistics"
|
||||
SYSTEM_REMNAWAVE_HEALTH = "system:remnawave-health"
|
||||
SYSTEM_NODES_METRICS = "system:nodes-metrics"
|
||||
SYSTEM_GENERATE_X25519 = "system:generate-x25519"
|
||||
SYSTEM_TEST_SRR_MATCHER = "system:test-srr-matcher"
|
||||
SYSTEM_RECAP = "system:recap"
|
||||
|
||||
# Keygen
|
||||
KEYGEN_ALL = "keygen:*"
|
||||
KEYGEN_READ = "keygen:read"
|
||||
KEYGEN_WRITE = "keygen:write"
|
||||
KEYGEN_GET = "keygen:get"
|
||||
|
||||
# Subscription Request History
|
||||
SUBSCRIPTION_REQUEST_HISTORY_ALL = "subscription-request-history:*"
|
||||
SUBSCRIPTION_REQUEST_HISTORY_READ = "subscription-request-history:read"
|
||||
SUBSCRIPTION_REQUEST_HISTORY_WRITE = "subscription-request-history:write"
|
||||
SUBSCRIPTION_REQUEST_HISTORY_LIST = "subscription-request-history:list"
|
||||
SUBSCRIPTION_REQUEST_HISTORY_STATS = "subscription-request-history:stats"
|
||||
|
||||
# Snippets
|
||||
SNIPPETS_ALL = "snippets:*"
|
||||
SNIPPETS_READ = "snippets:read"
|
||||
SNIPPETS_WRITE = "snippets:write"
|
||||
SNIPPETS_LIST = "snippets:list"
|
||||
SNIPPETS_DELETE = "snippets:delete"
|
||||
SNIPPETS_CREATE = "snippets:create"
|
||||
SNIPPETS_UPDATE = "snippets:update"
|
||||
|
||||
# Subscription Page Configs
|
||||
SUBSCRIPTION_PAGE_CONFIGS_ALL = "subscription-page-configs:*"
|
||||
SUBSCRIPTION_PAGE_CONFIGS_READ = "subscription-page-configs:read"
|
||||
SUBSCRIPTION_PAGE_CONFIGS_WRITE = "subscription-page-configs:write"
|
||||
SUBSCRIPTION_PAGE_CONFIGS_LIST = "subscription-page-configs:list"
|
||||
SUBSCRIPTION_PAGE_CONFIGS_GET = "subscription-page-configs:get"
|
||||
SUBSCRIPTION_PAGE_CONFIGS_UPDATE = "subscription-page-configs:update"
|
||||
SUBSCRIPTION_PAGE_CONFIGS_DELETE = "subscription-page-configs:delete"
|
||||
SUBSCRIPTION_PAGE_CONFIGS_CREATE = "subscription-page-configs:create"
|
||||
SUBSCRIPTION_PAGE_CONFIGS_REORDER = "subscription-page-configs:reorder"
|
||||
SUBSCRIPTION_PAGE_CONFIGS_CLONE = "subscription-page-configs:clone"
|
||||
|
||||
# Metadata
|
||||
METADATA_ALL = "metadata:*"
|
||||
METADATA_READ = "metadata:read"
|
||||
METADATA_WRITE = "metadata:write"
|
||||
METADATA_GET_USER = "metadata:get-user"
|
||||
METADATA_UPSERT_USER = "metadata:upsert-user"
|
||||
METADATA_GET_NODE = "metadata:get-node"
|
||||
METADATA_UPSERT_NODE = "metadata:upsert-node"
|
||||
|
|
@ -23,12 +23,10 @@ TUserEvents = Literal[
|
|||
"user.limited",
|
||||
"user.expired",
|
||||
"user.traffic_reset",
|
||||
"user.expires_in_72_hours",
|
||||
"user.expires_in_48_hours",
|
||||
"user.expires_in_24_hours",
|
||||
"user.expired_24_hours_ago",
|
||||
"user.first_connected",
|
||||
"user.bandwidth_usage_threshold_reached",
|
||||
"user.not_connected",
|
||||
"user.expiration",
|
||||
]
|
||||
|
||||
TServiceEvents = Literal[
|
||||
|
|
@ -36,6 +34,8 @@ TServiceEvents = Literal[
|
|||
"service.login_attempt_failed",
|
||||
"service.login_attempt_success",
|
||||
"service.subpage_config_changed",
|
||||
"service.api_token_created",
|
||||
"service.api_token_deleted",
|
||||
]
|
||||
|
||||
TErrorsEvents = Literal[
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
from .api_tokens_management import (
|
||||
ApiTokenDto,
|
||||
ApiTokenScopeEndpointDto,
|
||||
ApiTokenScopeResourceDto,
|
||||
CreateApiTokenRequestDto,
|
||||
CreateApiTokenResponseDto,
|
||||
DeleteApiTokenResponseDto,
|
||||
DocsInfoDto,
|
||||
FindAllApiTokensResponseDto,
|
||||
GetApiTokenScopesResponseDto,
|
||||
)
|
||||
from .auth import (
|
||||
GetStatusResponseDto,
|
||||
|
|
@ -36,6 +41,8 @@ from .bandwidthstats import (
|
|||
GetStatsNodesRealtimeUsageResponseDto,
|
||||
GetStatsNodesUsageResponseDto,
|
||||
GetStatsNodeUsersUsageResponseDto,
|
||||
GetStatsNodesUsersUsageRequestDto,
|
||||
GetStatsNodesUsersUsageResponseDto,
|
||||
GetStatsUserUsageResponseDto,
|
||||
|
||||
# Data Models
|
||||
|
|
@ -44,9 +51,11 @@ from .bandwidthstats import (
|
|||
NodeRealtimeUsageItem,
|
||||
TopNodeItem,
|
||||
TopUserItem,
|
||||
TopNodesUserItem,
|
||||
NodeSeriesItem,
|
||||
StatsNodesUsageData,
|
||||
StatsNodeUsersUsageData,
|
||||
StatsNodesUsersUsageData,
|
||||
StatsUserUsageData,
|
||||
)
|
||||
from .config_profiles import (
|
||||
|
|
@ -88,10 +97,8 @@ from .hosts_bulk_actions import (
|
|||
BulkDeleteHostsResponseDto,
|
||||
BulkDisableHostsResponseDto,
|
||||
BulkEnableHostsResponseDto,
|
||||
SetInboundToManyHostsRequestDto,
|
||||
SetInboundToManyHostsResponseDto,
|
||||
SetPortToManyHostsResponseDto,
|
||||
SetPortToManyHostsRequestDto
|
||||
UpdateManyHostsRequestDto,
|
||||
UpdateManyHostsResponseDto,
|
||||
)
|
||||
from .hwid import (
|
||||
CreateHWIDUser, # Legacy alias
|
||||
|
|
@ -199,6 +206,7 @@ from .nodes import (
|
|||
UpdateNodeResponseDto,
|
||||
RestartAllNodesRequestDto, # Legacy alias,
|
||||
RestartAllNodesRequestBodyDto,
|
||||
RestartNodeRequestBodyDto,
|
||||
ResetNodeTrafficRequestDto,
|
||||
ResetNodeTrafficResponseDto,
|
||||
ProfileModificationRequestDto,
|
||||
|
|
@ -281,8 +289,6 @@ from .system import (
|
|||
X25519KeyPair,
|
||||
DebugSrrMatcherRequestDto,
|
||||
DebugSrrMatcherResponseDto,
|
||||
EncryptHappCryptoLinkRequestDto,
|
||||
EncryptHappCryptoLinkResponseDto,
|
||||
GetMetadataResponseDto,
|
||||
GetRecapResponseDto,
|
||||
RecapThisMonth,
|
||||
|
|
@ -313,6 +319,8 @@ from .users import (
|
|||
GetAllUsersResponseDto,
|
||||
GetAllTagsResponseDto,
|
||||
GetUserSubscriptionRequestHistoryResponseDto,
|
||||
GetUsersStreamResponseDto,
|
||||
UsersStreamData,
|
||||
|
||||
# Response DTOs - Arrays (RootModel)
|
||||
TelegramUserResponseDto,
|
||||
|
|
@ -589,6 +597,7 @@ __all__ = [
|
|||
"NodeConfigProfileRequestDto",
|
||||
"RestartAllNodesRequestDto", # Legacy alias
|
||||
"RestartAllNodesRequestBodyDto",
|
||||
"RestartNodeRequestBodyDto",
|
||||
"ResetNodeTrafficRequestDto",
|
||||
"ResetNodeTrafficResponseDto",
|
||||
"ProfileModificationRequestDto",
|
||||
|
|
@ -689,8 +698,6 @@ __all__ = [
|
|||
"X25519KeyPair",
|
||||
"DebugSrrMatcherRequestDto",
|
||||
"DebugSrrMatcherResponseDto",
|
||||
"EncryptHappCryptoLinkRequestDto",
|
||||
"EncryptHappCryptoLinkResponseDto",
|
||||
"GetMetadataResponseDto",
|
||||
"GetRecapResponseDto",
|
||||
"RecapThisMonth",
|
||||
|
|
@ -729,21 +736,30 @@ __all__ = [
|
|||
"GetStatsNodesRealtimeUsageResponseDto",
|
||||
"GetStatsNodesUsageResponseDto",
|
||||
"GetStatsNodeUsersUsageResponseDto",
|
||||
"GetStatsNodesUsersUsageRequestDto",
|
||||
"GetStatsNodesUsersUsageResponseDto",
|
||||
"GetStatsUserUsageResponseDto",
|
||||
"LegacyUserUsageItem",
|
||||
"LegacyNodeUserUsageItem",
|
||||
"NodeRealtimeUsageItem",
|
||||
"TopNodeItem",
|
||||
"TopUserItem",
|
||||
"TopNodesUserItem",
|
||||
"NodeSeriesItem",
|
||||
"StatsNodesUsageData",
|
||||
"StatsNodeUsersUsageData",
|
||||
"StatsNodesUsersUsageData",
|
||||
"StatsUserUsageData",
|
||||
# API Tokens models
|
||||
"ApiTokenDto",
|
||||
"ApiTokenScopeEndpointDto",
|
||||
"ApiTokenScopeResourceDto",
|
||||
"CreateApiTokenRequestDto",
|
||||
"CreateApiTokenResponseDto",
|
||||
"DeleteApiTokenResponseDto",
|
||||
"DocsInfoDto",
|
||||
"FindAllApiTokensResponseDto",
|
||||
"GetApiTokenScopesResponseDto",
|
||||
# Inbound bulk actions models
|
||||
"AddInboundToNodesResponseDto",
|
||||
"AddInboundToUsersResponseDto",
|
||||
|
|
@ -753,10 +769,8 @@ __all__ = [
|
|||
"BulkDeleteHostsResponseDto",
|
||||
"BulkDisableHostsResponseDto",
|
||||
"BulkEnableHostsResponseDto",
|
||||
"SetInboundToManyHostsRequestDto",
|
||||
"SetInboundToManyHostsResponseDto",
|
||||
"SetPortToManyHostsResponseDto",
|
||||
"SetPortToManyHostsRequestDto",
|
||||
"UpdateManyHostsRequestDto",
|
||||
"UpdateManyHostsResponseDto",
|
||||
# Users models
|
||||
"CreateUserRequestDto",
|
||||
"UpdateUserRequestDto",
|
||||
|
|
@ -777,6 +791,8 @@ __all__ = [
|
|||
"GetAllUsersResponseDto",
|
||||
"GetAllTagsResponseDto",
|
||||
"GetUserSubscriptionRequestHistoryResponseDto",
|
||||
"GetUsersStreamResponseDto",
|
||||
"UsersStreamData",
|
||||
"TelegramUserResponseDto",
|
||||
"EmailUserResponseDto",
|
||||
"TagUserResponseDto",
|
||||
|
|
|
|||
|
|
@ -1,16 +1,37 @@
|
|||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from typing import Annotated, List, Literal, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import BaseModel, Field, StringConstraints
|
||||
|
||||
from remnawave.enums import Scope
|
||||
|
||||
|
||||
class CreateApiTokenRequestDto(BaseModel):
|
||||
token_name: str = Field(serialization_alias="tokenName")
|
||||
name: Annotated[str, StringConstraints(min_length=2, max_length=30)] = Field(
|
||||
serialization_alias="name"
|
||||
)
|
||||
expires_in_days: float = Field(serialization_alias="expiresInDays", ge=1)
|
||||
scopes: List[str] = Field(
|
||||
default_factory=lambda: [Scope.WILDCARD],
|
||||
description='API token scopes. Pass :class:`remnawave.enums.Scope` members (or raw strings). Defaults to ["*"] (full access). See GET /api/tokens/scopes for the catalog.',
|
||||
)
|
||||
|
||||
def __init__(self, **data):
|
||||
# Backward compatibility: `token_name` was renamed to `name` in v2.8.0
|
||||
if "token_name" in data and "name" not in data:
|
||||
data["name"] = data.pop("token_name")
|
||||
super().__init__(**data)
|
||||
|
||||
|
||||
class CreateApiTokenResponseData(BaseModel):
|
||||
uuid: UUID
|
||||
name: str
|
||||
expire_at: datetime = Field(alias="expireAt")
|
||||
scopes: List[str]
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
token: str
|
||||
uuid: str
|
||||
|
||||
|
||||
class CreateApiTokenResponseDto(CreateApiTokenResponseData):
|
||||
|
|
@ -22,23 +43,62 @@ class DeleteApiTokenResponseDto(BaseModel):
|
|||
|
||||
|
||||
class ApiTokenDto(BaseModel):
|
||||
uuid: str
|
||||
token: str
|
||||
token_name: str = Field(..., alias="tokenName")
|
||||
uuid: UUID
|
||||
name: str
|
||||
expire_at: datetime = Field(..., alias="expireAt")
|
||||
scopes: List[str] = Field(default_factory=list)
|
||||
created_at: datetime = Field(..., alias="createdAt")
|
||||
updated_at: datetime = Field(..., alias="updatedAt")
|
||||
|
||||
@property
|
||||
def token_name(self) -> str:
|
||||
"""Backward compatibility property (renamed to `name` in v2.8.0)"""
|
||||
return self.name
|
||||
|
||||
|
||||
class DocsInfoDto(BaseModel):
|
||||
is_docs_enabled: bool = Field(..., alias="isDocsEnabled")
|
||||
enabled: bool = Field(..., alias="enabled")
|
||||
scalar_path: Optional[str] = Field(None, alias="scalarPath")
|
||||
swagger_path: Optional[str] = Field(None, alias="swaggerPath")
|
||||
|
||||
@property
|
||||
def is_docs_enabled(self) -> bool:
|
||||
"""Backward compatibility property (renamed to `enabled` in v2.8.0)"""
|
||||
return self.enabled
|
||||
|
||||
|
||||
class FindAllApiTokensResponseData(BaseModel):
|
||||
api_keys: List[ApiTokenDto] = Field(..., alias="apiKeys")
|
||||
tokens: List[ApiTokenDto] = Field(..., alias="tokens")
|
||||
docs: DocsInfoDto
|
||||
|
||||
@property
|
||||
def api_keys(self) -> List[ApiTokenDto]:
|
||||
"""Backward compatibility property (renamed to `tokens` in v2.8.0)"""
|
||||
return self.tokens
|
||||
|
||||
|
||||
class FindAllApiTokensResponseDto(FindAllApiTokensResponseData):
|
||||
pass
|
||||
|
||||
|
||||
class ApiTokenScopeEndpointDto(BaseModel):
|
||||
key: str
|
||||
kind: Literal["read", "write"]
|
||||
method: str
|
||||
path: str
|
||||
description: str
|
||||
|
||||
|
||||
class ApiTokenScopeResourceDto(BaseModel):
|
||||
resource: str
|
||||
resource_scopes: List[str] = Field(..., alias="resourceScopes")
|
||||
endpoints: List[ApiTokenScopeEndpointDto]
|
||||
|
||||
|
||||
class GetApiTokenScopesResponseData(BaseModel):
|
||||
wildcard: str
|
||||
resources: List[ApiTokenScopeResourceDto]
|
||||
|
||||
|
||||
class GetApiTokenScopesResponseDto(GetApiTokenScopesResponseData):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -277,6 +277,34 @@ class GetStatsNodeUsersUsageResponseDto(RootModel[StatsNodeUsersUsageData]):
|
|||
return self.root
|
||||
|
||||
|
||||
# Stats Nodes Users Usage by Nodes UUIDs (POST /bandwidth-stats/nodes/users)
|
||||
|
||||
class GetStatsNodesUsersUsageRequestDto(BaseModel):
|
||||
"""Request for nodes users usage by nodes UUIDs"""
|
||||
nodes_uuids: List[UUID] = Field(serialization_alias="nodesUuids", min_length=1)
|
||||
|
||||
|
||||
class TopNodesUserItem(BaseModel):
|
||||
"""Top user item for nodes users usage"""
|
||||
color: str
|
||||
username: str
|
||||
total: float
|
||||
|
||||
|
||||
class StatsNodesUsersUsageData(BaseModel):
|
||||
"""Stats nodes users usage data"""
|
||||
categories: List[str]
|
||||
sparkline_data: List[float] = Field(alias="sparklineData")
|
||||
top_users: List[TopNodesUserItem] = Field(alias="topUsers")
|
||||
|
||||
|
||||
class GetStatsNodesUsersUsageResponseDto(RootModel[StatsNodesUsersUsageData]):
|
||||
"""Response for stats nodes users usage by nodes UUIDs"""
|
||||
@property
|
||||
def response(self) -> StatsNodesUsersUsageData:
|
||||
return self.root
|
||||
|
||||
|
||||
# Stats User Usage (with charts)
|
||||
|
||||
class StatsUserUsageData(BaseModel):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ from uuid import UUID
|
|||
|
||||
from pydantic import BaseModel, Field, StringConstraints, RootModel
|
||||
|
||||
from remnawave.enums import ALPN, Fingerprint, SecurityLayer, SubscriptionType
|
||||
from remnawave.enums import ALPN, MihomoIpVersion, SecurityLayer, SubscriptionType
|
||||
|
||||
# Tag for a single host tag entry: uppercase alphanumeric, underscores and colons, max 36 chars
|
||||
HostTag = Annotated[str, StringConstraints(max_length=36, pattern=r"^[A-Z0-9_:]+$")]
|
||||
|
||||
|
||||
class ReorderHostItem(BaseModel):
|
||||
|
|
@ -35,21 +38,24 @@ class UpdateHostRequestDto(BaseModel):
|
|||
sni: Optional[str] = None
|
||||
host: Optional[str] = None
|
||||
alpn: Optional[ALPN] = None
|
||||
fingerprint: Optional[Fingerprint] = None
|
||||
allow_insecure: Optional[bool] = Field(None, serialization_alias="allowInsecure")
|
||||
fingerprint: Optional[str] = None
|
||||
is_disabled: Optional[bool] = Field(None, serialization_alias="isDisabled")
|
||||
security_layer: Optional[SecurityLayer] = Field(None, serialization_alias="securityLayer")
|
||||
server_description: Optional[str] = Field(None, serialization_alias="serverDescription", max_length=30)
|
||||
tag: Optional[Annotated[str, StringConstraints(max_length=32, pattern=r"^[A-Z0-9_:]+$")]] = None
|
||||
tags: Optional[List[HostTag]] = Field(None, serialization_alias="tags", max_length=10)
|
||||
is_hidden: Optional[bool] = Field(None, serialization_alias="isHidden")
|
||||
override_sni_from_address: Optional[bool] = Field(None, serialization_alias="overrideSniFromAddress")
|
||||
keep_blank_sni: Optional[bool] = Field(None, serialization_alias="keepSniBlank")
|
||||
vless_route_id: Optional[int] = Field(None, serialization_alias="vlessRouteId", ge=0, le=65535)
|
||||
pinned_peer_cert_sha256: Optional[str] = Field(None, serialization_alias="pinnedPeerCertSha256")
|
||||
verify_peer_cert_by_name: Optional[str] = Field(None, serialization_alias="verifyPeerCertByName")
|
||||
shuffle_host: Optional[bool] = Field(None, serialization_alias="shuffleHost")
|
||||
mihomo_x25519: Optional[bool] = Field(None, serialization_alias="mihomoX25519")
|
||||
x_http_extra_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="xHttpExtraParams")
|
||||
mihomo_ip_version: Optional[MihomoIpVersion] = Field(None, serialization_alias="mihomoIpVersion")
|
||||
xhttp_extra_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="xhttpExtraParams")
|
||||
mux_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="muxParams")
|
||||
sockopt_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="sockoptParams")
|
||||
final_mask: Optional[Any] = Field(None, serialization_alias="finalMask")
|
||||
nodes: Optional[List[UUID]] = None
|
||||
xray_json_template_uuid: Optional[UUID] = Field(None, serialization_alias="xrayJsonTemplateUuid")
|
||||
excluded_internal_squads: Optional[List[UUID]] = Field(None, serialization_alias="excludedInternalSquads")
|
||||
|
|
@ -59,6 +65,28 @@ class UpdateHostRequestDto(BaseModel):
|
|||
description="Subscription types from which this host will be excluded.",
|
||||
)
|
||||
|
||||
def __init__(self, **data):
|
||||
# Backward compatibility: `tag` (single value) was replaced by `tags` (list) in v2.8.0
|
||||
if "tag" in data and "tags" not in data:
|
||||
tag = data.pop("tag")
|
||||
data["tags"] = [tag] if tag is not None else None
|
||||
# Backward compatibility: `allow_insecure` was removed in v2.8.0 (use security_layer instead)
|
||||
data.pop("allow_insecure", None)
|
||||
# Backward compatibility: `xHttpExtraParams` alias was renamed to `xhttpExtraParams`
|
||||
if "x_http_extra_params" in data and "xhttp_extra_params" not in data:
|
||||
data["xhttp_extra_params"] = data.pop("x_http_extra_params")
|
||||
super().__init__(**data)
|
||||
|
||||
@property
|
||||
def x_http_extra_params(self) -> Optional[Dict[str, Any]]:
|
||||
"""Backward compatibility property (renamed to xhttp_extra_params in v2.8.0)"""
|
||||
return self.xhttp_extra_params
|
||||
|
||||
@property
|
||||
def tag(self) -> Optional[str]:
|
||||
"""Backward compatibility property (replaced by `tags` in v2.8.0)"""
|
||||
return self.tags[0] if self.tags else None
|
||||
|
||||
@property
|
||||
def inbound_uuid(self) -> Optional[UUID]:
|
||||
return self.inbound.config_profile_inbound_uuid if self.inbound else None
|
||||
|
|
@ -75,22 +103,25 @@ class HostResponseDto(BaseModel):
|
|||
host: str | None = Field(alias="host")
|
||||
alpn: str | None = Field(alias="alpn")
|
||||
fingerprint: str | None = Field(alias="fingerprint")
|
||||
x_http_extra_params: Dict[str, Any] | None = Field(alias="xHttpExtraParams")
|
||||
xhttp_extra_params: Dict[str, Any] | None = Field(None, alias="xhttpExtraParams")
|
||||
mux_params: Dict[str, Any] | None = Field(alias="muxParams")
|
||||
sockopt_params: Dict[str, Any] | None = Field(alias="sockoptParams")
|
||||
final_mask: Any | None = Field(None, alias="finalMask")
|
||||
inbound: HostInboundData
|
||||
server_description: str | None = Field(alias="serverDescription")
|
||||
tag: str | None = Field(alias="tag")
|
||||
tags: List[str] = Field(default_factory=list, alias="tags")
|
||||
vless_route_id: int | None = Field(alias="vlessRouteId")
|
||||
pinned_peer_cert_sha256: str | None = Field(None, alias="pinnedPeerCertSha256")
|
||||
verify_peer_cert_by_name: str | None = Field(None, alias="verifyPeerCertByName")
|
||||
shuffle_host: bool = Field(alias="shuffleHost")
|
||||
mihomo_x25519: bool = Field(alias="mihomoX25519")
|
||||
mihomo_ip_version: str | None = Field(None, alias="mihomoIpVersion")
|
||||
nodes: List[UUID]
|
||||
is_disabled: bool = Field(False, alias="isDisabled")
|
||||
security_layer: SecurityLayer = Field(SecurityLayer.DEFAULT, alias="securityLayer")
|
||||
is_hidden: bool = Field(False, alias="isHidden")
|
||||
override_sni_from_address: bool = Field(False, alias="overrideSniFromAddress")
|
||||
keep_blank_sni: bool = Field(False, alias="keepSniBlank")
|
||||
allow_insecure: bool = Field(False, alias="allowInsecure")
|
||||
xray_json_template_uuid: UUID | None = Field(alias="xrayJsonTemplateUuid")
|
||||
excluded_internal_squads: List[UUID] = Field(default_factory=list, alias="excludedInternalSquads")
|
||||
exclude_from_subscription_types: List[SubscriptionType] = Field(
|
||||
|
|
@ -103,6 +134,21 @@ class HostResponseDto(BaseModel):
|
|||
def inbound_uuid(self) -> Optional[UUID]:
|
||||
return self.inbound.config_profile_inbound_uuid
|
||||
|
||||
@property
|
||||
def x_http_extra_params(self) -> Dict[str, Any] | None:
|
||||
"""Backward compatibility property (renamed to xhttp_extra_params in v2.8.0)"""
|
||||
return self.xhttp_extra_params
|
||||
|
||||
@property
|
||||
def tag(self) -> str | None:
|
||||
"""Backward compatibility property (replaced by `tags` in v2.8.0)"""
|
||||
return self.tags[0] if self.tags else None
|
||||
|
||||
@property
|
||||
def allow_insecure(self) -> bool:
|
||||
"""Backward compatibility property (removed in v2.8.0, derived from security_layer)"""
|
||||
return self.security_layer == SecurityLayer.NONE
|
||||
|
||||
|
||||
class CreateHostRequestDto(BaseModel):
|
||||
inbound: CreateHostInboundData
|
||||
|
|
@ -113,17 +159,20 @@ class CreateHostRequestDto(BaseModel):
|
|||
sni: Optional[str] = None
|
||||
host: Optional[str] = None
|
||||
alpn: Optional[ALPN] = None
|
||||
fingerprint: Optional[Fingerprint] = None
|
||||
x_http_extra_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="xHttpExtraParams")
|
||||
fingerprint: Optional[str] = None
|
||||
xhttp_extra_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="xhttpExtraParams")
|
||||
mux_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="muxParams")
|
||||
sockopt_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="sockoptParams")
|
||||
final_mask: Optional[Any] = Field(None, serialization_alias="finalMask")
|
||||
server_description: Optional[str] = Field(None, serialization_alias="serverDescription", max_length=30)
|
||||
tag: Optional[Annotated[str, StringConstraints(max_length=32, pattern=r"^[A-Z0-9_:]+$")]] = None
|
||||
tags: Optional[List[HostTag]] = Field(None, serialization_alias="tags", max_length=10)
|
||||
vless_route_id: Optional[int] = Field(None, serialization_alias="vlessRouteId", ge=0, le=65535)
|
||||
pinned_peer_cert_sha256: Optional[str] = Field(None, serialization_alias="pinnedPeerCertSha256")
|
||||
verify_peer_cert_by_name: Optional[str] = Field(None, serialization_alias="verifyPeerCertByName")
|
||||
shuffle_host: bool = Field(False, serialization_alias="shuffleHost")
|
||||
mihomo_x25519: bool = Field(False, serialization_alias="mihomoX25519")
|
||||
mihomo_ip_version: Optional[MihomoIpVersion] = Field(None, serialization_alias="mihomoIpVersion")
|
||||
nodes: List[UUID] = Field(default_factory=list)
|
||||
allow_insecure: bool = Field(False, serialization_alias="allowInsecure")
|
||||
is_disabled: bool = Field(False, serialization_alias="isDisabled")
|
||||
security_layer: SecurityLayer = Field(SecurityLayer.DEFAULT, serialization_alias="securityLayer")
|
||||
is_hidden: bool = Field(False, serialization_alias="isHidden")
|
||||
|
|
@ -141,6 +190,16 @@ class CreateHostRequestDto(BaseModel):
|
|||
def inbound_uuid(self) -> Optional[UUID]:
|
||||
return self.inbound.config_profile_inbound_uuid
|
||||
|
||||
@property
|
||||
def x_http_extra_params(self) -> Optional[Dict[str, Any]]:
|
||||
"""Backward compatibility property (renamed to xhttp_extra_params in v2.8.0)"""
|
||||
return self.xhttp_extra_params
|
||||
|
||||
@property
|
||||
def tag(self) -> Optional[str]:
|
||||
"""Backward compatibility property (replaced by `tags` in v2.8.0)"""
|
||||
return self.tags[0] if self.tags else None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
inbound_uuid: Optional[UUID] = None,
|
||||
|
|
@ -157,6 +216,17 @@ class CreateHostRequestDto(BaseModel):
|
|||
or UUID("107541f1-ae1a-4e2d-9dec-7297557b5125"),
|
||||
config_profile_inbound_uuid=inbound_uuid,
|
||||
)
|
||||
|
||||
# Backward compatibility: `tag` (single value) was replaced by `tags` (list) in v2.8.0
|
||||
if "tag" in data and "tags" not in data:
|
||||
tag = data.pop("tag")
|
||||
data["tags"] = [tag] if tag is not None else None
|
||||
# Backward compatibility: `allow_insecure` was removed in v2.8.0 (use security_layer instead)
|
||||
data.pop("allow_insecure", None)
|
||||
# Backward compatibility: `xHttpExtraParams` alias was renamed to `xhttpExtraParams`
|
||||
if "x_http_extra_params" in data and "xhttp_extra_params" not in data:
|
||||
data["xhttp_extra_params"] = data.pop("x_http_extra_params")
|
||||
|
||||
super().__init__(**data)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,36 +1,80 @@
|
|||
from typing import List
|
||||
from typing import Annotated, Any, Dict, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, RootModel, StringConstraints
|
||||
|
||||
from remnawave.enums import ALPN, MihomoIpVersion, SecurityLayer, SubscriptionType
|
||||
from remnawave.models import HostResponseDto
|
||||
from remnawave.models.hosts import CreateHostInboundData, HostTag
|
||||
|
||||
|
||||
class SetInboundToManyHostsRequestDto(BaseModel):
|
||||
uuids: List[UUID]
|
||||
config_profile_uuid: UUID = Field(serialization_alias="configProfileUuid")
|
||||
config_profile_inbound_uuid: UUID = Field(
|
||||
serialization_alias="configProfileInboundUuid"
|
||||
class _HostListResponse(RootModel[List[HostResponseDto]]):
|
||||
"""Base for bulk host responses that return a plain list of hosts."""
|
||||
root: List[HostResponseDto]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.root)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class BulkDeleteHostsResponseDto(_HostListResponse):
|
||||
pass
|
||||
|
||||
|
||||
class BulkDisableHostsResponseDto(_HostListResponse):
|
||||
pass
|
||||
|
||||
|
||||
class BulkEnableHostsResponseDto(_HostListResponse):
|
||||
pass
|
||||
|
||||
|
||||
class UpdateManyHostsRequestDto(BaseModel):
|
||||
"""Request to update many hosts at once (PATCH /hosts/bulk/update)."""
|
||||
uuids: List[UUID] = Field(min_length=1)
|
||||
inbound: Optional[CreateHostInboundData] = None
|
||||
remark: Annotated[Optional[str], StringConstraints(max_length=40)] = None
|
||||
address: Optional[str] = None
|
||||
port: Optional[int] = None
|
||||
path: Optional[str] = None
|
||||
sni: Optional[str] = None
|
||||
host: Optional[str] = None
|
||||
alpn: Optional[ALPN] = None
|
||||
fingerprint: Optional[str] = None
|
||||
is_disabled: Optional[bool] = Field(None, serialization_alias="isDisabled")
|
||||
security_layer: Optional[SecurityLayer] = Field(None, serialization_alias="securityLayer")
|
||||
server_description: Optional[str] = Field(None, serialization_alias="serverDescription", max_length=30)
|
||||
tags: Optional[List[HostTag]] = Field(None, serialization_alias="tags", max_length=10)
|
||||
is_hidden: Optional[bool] = Field(None, serialization_alias="isHidden")
|
||||
override_sni_from_address: Optional[bool] = Field(None, serialization_alias="overrideSniFromAddress")
|
||||
keep_blank_sni: Optional[bool] = Field(None, serialization_alias="keepSniBlank")
|
||||
vless_route_id: Optional[int] = Field(None, serialization_alias="vlessRouteId", ge=0, le=65535)
|
||||
pinned_peer_cert_sha256: Optional[str] = Field(None, serialization_alias="pinnedPeerCertSha256")
|
||||
verify_peer_cert_by_name: Optional[str] = Field(None, serialization_alias="verifyPeerCertByName")
|
||||
shuffle_host: Optional[bool] = Field(None, serialization_alias="shuffleHost")
|
||||
mihomo_x25519: Optional[bool] = Field(None, serialization_alias="mihomoX25519")
|
||||
mihomo_ip_version: Optional[MihomoIpVersion] = Field(None, serialization_alias="mihomoIpVersion")
|
||||
xhttp_extra_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="xhttpExtraParams")
|
||||
mux_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="muxParams")
|
||||
sockopt_params: Optional[Dict[str, Any]] = Field(None, serialization_alias="sockoptParams")
|
||||
final_mask: Optional[Any] = Field(None, serialization_alias="finalMask")
|
||||
nodes: Optional[List[UUID]] = None
|
||||
xray_json_template_uuid: Optional[UUID] = Field(None, serialization_alias="xrayJsonTemplateUuid")
|
||||
excluded_internal_squads: Optional[List[UUID]] = Field(None, serialization_alias="excludedInternalSquads")
|
||||
exclude_from_subscription_types: Optional[List[SubscriptionType]] = Field(
|
||||
None,
|
||||
serialization_alias="excludeFromSubscriptionTypes",
|
||||
description="Subscription types from which the hosts will be excluded.",
|
||||
)
|
||||
|
||||
class SetPortToManyHostsRequestDto(BaseModel):
|
||||
uuids: List[UUID]
|
||||
port: int = Field(ge=1, le=65535)
|
||||
|
||||
class BulkDeleteHostsResponseDto(List[HostResponseDto]):
|
||||
pass
|
||||
|
||||
|
||||
class BulkDisableHostsResponseDto(List[HostResponseDto]):
|
||||
pass
|
||||
|
||||
|
||||
class BulkEnableHostsResponseDto(List[HostResponseDto]):
|
||||
pass
|
||||
|
||||
|
||||
class SetInboundToManyHostsResponseDto(List[HostResponseDto]):
|
||||
pass
|
||||
|
||||
class SetPortToManyHostsResponseDto(List[HostResponseDto]):
|
||||
class UpdateManyHostsResponseDto(_HostListResponse):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class CreateUserHwidDeviceRequestDto(BaseModel):
|
|||
os_version: Optional[str] = Field(None, serialization_alias="osVersion")
|
||||
device_model: Optional[str] = Field(None, serialization_alias="deviceModel")
|
||||
user_agent: Optional[str] = Field(None, serialization_alias="userAgent")
|
||||
request_ip: Optional[str] = Field(None, serialization_alias="requestIp")
|
||||
|
||||
|
||||
class DeleteUserHwidDeviceRequestDto(BaseModel):
|
||||
|
|
|
|||
|
|
@ -146,9 +146,10 @@ class DeleteInfraBillingHistoryRecordByUuidResponseDto(BaseModel):
|
|||
|
||||
# Billing Nodes models
|
||||
class CreateInfraBillingNodeRequestDto(BaseModel):
|
||||
node_uuid: UUID = Field(serialization_alias="nodeUuid")
|
||||
provider_uuid: UUID = Field(serialization_alias="providerUuid")
|
||||
next_billing_at: Optional[datetime] = Field(None, serialization_alias="nextBillingAt")
|
||||
node_uuid: Optional[UUID] = Field(None, serialization_alias="nodeUuid")
|
||||
name: Optional[str] = Field(None, serialization_alias="name", min_length=1, max_length=255)
|
||||
next_billing_at: datetime = Field(serialization_alias="nextBillingAt")
|
||||
|
||||
|
||||
# ИСПРАВЛЕНО: API возвращает список всех billing nodes после создания, а не один созданный
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from datetime import datetime
|
|||
from typing import Annotated, List, Optional, Union, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, StringConstraints, RootModel
|
||||
from pydantic import BaseModel, ConfigDict, Field, StringConstraints, RootModel
|
||||
|
||||
from remnawave.models.internal_squads import InboundsDto
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ class CreateNodeRequestDto(BaseModel):
|
|||
False,
|
||||
serialization_alias="isTrafficTrackingActive",
|
||||
)
|
||||
traffic_limit_bytes: Optional[int] = Field(
|
||||
traffic_limit_bytes: Optional[float] = Field(
|
||||
None, serialization_alias="trafficLimitBytes", ge=0
|
||||
)
|
||||
notify_percent: Optional[int] = Field(
|
||||
|
|
@ -74,11 +74,20 @@ class CreateNodeRequestDto(BaseModel):
|
|||
None, serialization_alias="excludedInbounds"
|
||||
)
|
||||
country_code: Annotated[Optional[str], StringConstraints(max_length=2)] = Field(
|
||||
"XX",
|
||||
"XX",
|
||||
serialization_alias="countryCode"
|
||||
)
|
||||
consumption_multiplier: Optional[float] = Field(
|
||||
None, serialization_alias="consumptionMultiplier", ge=0.1
|
||||
None, serialization_alias="consumptionMultiplier", ge=0, le=100
|
||||
)
|
||||
node_consumption_multiplier: Optional[float] = Field(
|
||||
None, serialization_alias="nodeConsumptionMultiplier", ge=0, le=100
|
||||
)
|
||||
note: Optional[str] = Field(None, serialization_alias="note", max_length=255)
|
||||
proxy_url: Optional[str] = Field(
|
||||
None,
|
||||
serialization_alias="proxyUrl",
|
||||
pattern=r"^socks5://(?:[^:@/\s]+(?::[^@/\s]*)?@)?[^:@/\s]+:\d{1,5}$",
|
||||
)
|
||||
config_profile: NodeConfigProfileRequestDto = Field(
|
||||
serialization_alias="configProfile"
|
||||
|
|
@ -118,7 +127,16 @@ class UpdateNodeRequestDto(BaseModel):
|
|||
None, serialization_alias="countryCode"
|
||||
)
|
||||
consumption_multiplier: Optional[float] = Field(
|
||||
None, serialization_alias="consumptionMultiplier", ge=0.1
|
||||
None, serialization_alias="consumptionMultiplier", ge=0, le=100
|
||||
)
|
||||
node_consumption_multiplier: Optional[float] = Field(
|
||||
None, serialization_alias="nodeConsumptionMultiplier", ge=0, le=100
|
||||
)
|
||||
note: Optional[str] = Field(None, serialization_alias="note", max_length=255)
|
||||
proxy_url: Optional[str] = Field(
|
||||
None,
|
||||
serialization_alias="proxyUrl",
|
||||
pattern=r"^socks5://(?:[^:@/\s]+(?::[^@/\s]*)?@)?[^:@/\s]+:\d{1,5}$",
|
||||
)
|
||||
config_profile: Optional[NodeConfigProfileRequestDto] = Field(
|
||||
None, serialization_alias="configProfile"
|
||||
|
|
@ -160,6 +178,9 @@ class NodeResponseDto(BaseModel):
|
|||
view_position: int = Field(alias="viewPosition")
|
||||
country_code: str = Field(alias="countryCode")
|
||||
consumption_multiplier: float = Field(alias="consumptionMultiplier")
|
||||
node_consumption_multiplier: Optional[float] = Field(None, alias="nodeConsumptionMultiplier")
|
||||
note: Optional[str] = Field(None, alias="note")
|
||||
proxy_url: Optional[str] = Field(None, alias="proxyUrl")
|
||||
cpu_count: Optional[int] = Field(None, alias="cpuCount")
|
||||
cpu_model: Optional[str] = Field(None, alias="cpuModel")
|
||||
total_ram: Optional[str] = Field(None, alias="totalRam")
|
||||
|
|
@ -249,8 +270,16 @@ class DeleteNodeResponseDto(BaseModel):
|
|||
|
||||
|
||||
class RestartAllNodesRequestBodyDto(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
|
||||
force_restart: bool = Field(default=False, alias="forceRestart")
|
||||
|
||||
|
||||
|
||||
class RestartNodeRequestBodyDto(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
|
||||
force_restart: bool = Field(default=False, alias="forceRestart")
|
||||
|
||||
class ResetNodeTrafficRequestDto(BaseModel):
|
||||
uuid: Union[str, UUID] = Field(alias="uuid")
|
||||
|
||||
|
|
|
|||
|
|
@ -198,18 +198,6 @@ class GetX25519KeyPairResponseDto(BaseModel):
|
|||
GenerateX25519ResponseDto = GetX25519KeyPairResponseDto
|
||||
|
||||
|
||||
class EncryptHappCryptoLinkRequestDto(BaseModel):
|
||||
link_to_encrypt: str = Field(serialization_alias="linkToEncrypt")
|
||||
|
||||
|
||||
class EncryptHappCryptoLinkData(BaseModel):
|
||||
encrypted_link: str = Field(alias="encryptedLink")
|
||||
|
||||
|
||||
class EncryptHappCryptoLinkResponseDto(EncryptHappCryptoLinkData):
|
||||
pass
|
||||
|
||||
|
||||
class DebugSrrMatcherRequestDto(BaseModel):
|
||||
response_rules: ResponseRules = Field(serialization_alias="responseRules")
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class CreateUserRequestDto(BaseModel):
|
|||
ss_password: Optional[Annotated[str, StringConstraints(min_length=8, max_length=32)]] = Field(
|
||||
None, serialization_alias="ssPassword"
|
||||
)
|
||||
traffic_limit_bytes: Optional[int] = Field(
|
||||
traffic_limit_bytes: Optional[float] = Field(
|
||||
None, serialization_alias="trafficLimitBytes", ge=0
|
||||
)
|
||||
created_at: Optional[datetime] = Field(None, serialization_alias="createdAt")
|
||||
|
|
@ -97,7 +97,7 @@ class UpdateUserRequestDto(BaseModel):
|
|||
hwid_device_limit: Optional[int] = Field(None, serialization_alias="hwidDeviceLimit", ge=0)
|
||||
tag: Optional[Annotated[str, StringConstraints(max_length=16, pattern=r"^[A-Z0-9_]+$")]] = None
|
||||
telegram_id: Optional[int] = Field(None, serialization_alias="telegramId")
|
||||
traffic_limit_bytes: Optional[int] = Field(None, serialization_alias="trafficLimitBytes", ge=0)
|
||||
traffic_limit_bytes: Optional[float] = Field(None, serialization_alias="trafficLimitBytes", ge=0)
|
||||
traffic_limit_strategy: Optional[TrafficLimitStrategy] = Field(
|
||||
None, serialization_alias="trafficLimitStrategy"
|
||||
)
|
||||
|
|
@ -123,7 +123,7 @@ class UserResponseDto(BaseModel):
|
|||
short_uuid: str = Field(alias="shortUuid")
|
||||
username: str
|
||||
status: UserStatus = Field(default=UserStatus.ACTIVE)
|
||||
traffic_limit_bytes: int = Field(0, alias="trafficLimitBytes")
|
||||
traffic_limit_bytes: float = Field(0, alias="trafficLimitBytes")
|
||||
traffic_limit_strategy: TrafficLimitStrategy = Field(
|
||||
TrafficLimitStrategy.NO_RESET, alias="trafficLimitStrategy"
|
||||
)
|
||||
|
|
@ -300,6 +300,22 @@ class GetAllUsersResponseDto(UsersResponseDto):
|
|||
pass
|
||||
|
||||
|
||||
class UsersStreamData(BaseModel):
|
||||
"""Cursor-based (keyset) users stream page"""
|
||||
users: list[UserResponseDto]
|
||||
next_cursor: Optional[str] = Field(
|
||||
None,
|
||||
alias="nextCursor",
|
||||
description="Cursor to fetch the next page, or null if there are no more results",
|
||||
)
|
||||
has_more: bool = Field(alias="hasMore", description="Whether there are more results to fetch")
|
||||
|
||||
|
||||
class GetUsersStreamResponseDto(UsersStreamData):
|
||||
"""Response for get all users using cursor-based (keyset) pagination"""
|
||||
pass
|
||||
|
||||
|
||||
class TagsResponseDto(BaseModel):
|
||||
"""Tags collection response"""
|
||||
tags: list[str]
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ class BulkResetTrafficUsersRequestDto(BaseModel):
|
|||
class UpdateUserFields(BaseModel):
|
||||
"""Fields to update for users"""
|
||||
status: Optional[UserStatus] = None
|
||||
traffic_limit_bytes: Optional[int] = Field(
|
||||
None,
|
||||
serialization_alias="trafficLimitBytes",
|
||||
traffic_limit_bytes: Optional[float] = Field(
|
||||
None,
|
||||
serialization_alias="trafficLimitBytes",
|
||||
ge=0,
|
||||
description="Traffic limit in bytes. 0 - unlimited"
|
||||
)
|
||||
|
|
@ -98,7 +98,7 @@ class BulkExtendExpirationDateRequestDto(BaseModel):
|
|||
class BulkAllUpdateUsersRequestDto(BaseModel):
|
||||
"""Request to update all users"""
|
||||
status: Optional[UserStatus] = Field(default=UserStatus.ACTIVE)
|
||||
traffic_limit_bytes: Optional[int] = Field(
|
||||
traffic_limit_bytes: Optional[float] = Field(
|
||||
None,
|
||||
serialization_alias="trafficLimitBytes",
|
||||
ge=0,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import inspect
|
|||
from remnawave.controllers.users import UsersController
|
||||
from remnawave.controllers.system import SystemController
|
||||
from remnawave.controllers.ip_control import IpControlController
|
||||
from remnawave.controllers.api_tokens_management import APITokensManagementController
|
||||
from remnawave.controllers.hosts_bulk_actions import HostsBulkActionsController
|
||||
from remnawave.controllers.bandwidthstats import BandWidthStatsController
|
||||
|
||||
|
||||
class TestUsersControllerEndpoints:
|
||||
|
|
@ -66,6 +69,10 @@ class TestUsersControllerEndpoints:
|
|||
def test_has_get_user_subscription_request_history(self):
|
||||
assert hasattr(UsersController, "get_user_subscription_request_history")
|
||||
|
||||
def test_has_get_users_stream(self):
|
||||
assert hasattr(UsersController, "get_users_stream")
|
||||
assert callable(getattr(UsersController, "get_users_stream"))
|
||||
|
||||
|
||||
class TestSystemControllerEndpoints:
|
||||
def test_has_get_recap(self):
|
||||
|
|
@ -93,12 +100,39 @@ class TestSystemControllerEndpoints:
|
|||
def test_has_get_x25519_key_pair(self):
|
||||
assert hasattr(SystemController, "get_x25519_key_pair")
|
||||
|
||||
def test_has_encrypt_happ_crypto_link(self):
|
||||
assert hasattr(SystemController, "encrypt_happ_crypto_link")
|
||||
|
||||
def test_has_debug_srr_matcher(self):
|
||||
assert hasattr(SystemController, "debug_srr_matcher")
|
||||
|
||||
def test_no_encrypt_happ_crypto_link(self):
|
||||
# Removed in Remnawave API v2.8.0 (use client-side happ link generation instead)
|
||||
assert not hasattr(SystemController, "encrypt_happ_crypto_link")
|
||||
|
||||
|
||||
class TestApiTokensControllerEndpoints:
|
||||
def test_has_get_scopes(self):
|
||||
assert hasattr(APITokensManagementController, "get_scopes")
|
||||
assert callable(getattr(APITokensManagementController, "get_scopes"))
|
||||
|
||||
|
||||
class TestHostsBulkActionsControllerEndpoints:
|
||||
def test_has_update_hosts(self):
|
||||
assert hasattr(HostsBulkActionsController, "update_hosts")
|
||||
assert callable(getattr(HostsBulkActionsController, "update_hosts"))
|
||||
|
||||
def test_no_set_inbound_to_hosts(self):
|
||||
# Removed in Remnawave API v2.8.0 (replaced by update_hosts)
|
||||
assert not hasattr(HostsBulkActionsController, "set_inbound_to_hosts")
|
||||
|
||||
def test_no_set_port_to_hosts(self):
|
||||
# Removed in Remnawave API v2.8.0 (replaced by update_hosts)
|
||||
assert not hasattr(HostsBulkActionsController, "set_port_to_hosts")
|
||||
|
||||
|
||||
class TestBandwidthStatsControllerEndpoints:
|
||||
def test_has_get_stats_nodes_users_usage(self):
|
||||
assert hasattr(BandWidthStatsController, "get_stats_nodes_users_usage")
|
||||
assert callable(getattr(BandWidthStatsController, "get_stats_nodes_users_usage"))
|
||||
|
||||
|
||||
class TestIpControlControllerEndpoints:
|
||||
def test_has_fetch_user_ips(self):
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import pytest
|
|||
from datetime import datetime, timezone
|
||||
from uuid import uuid4
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from remnawave.models import (
|
||||
# Users
|
||||
ResolveUserRequestBodyDto,
|
||||
|
|
@ -180,12 +182,13 @@ class TestCreateInfraBillingHistoryRecordRequestDto:
|
|||
|
||||
|
||||
class TestCreateInfraBillingNodeRequestDto:
|
||||
def test_next_billing_at_optional(self):
|
||||
dto = CreateInfraBillingNodeRequestDto(
|
||||
node_uuid=uuid4(),
|
||||
provider_uuid=uuid4(),
|
||||
)
|
||||
assert dto.next_billing_at is None
|
||||
def test_next_billing_at_required(self):
|
||||
"""next_billing_at became required in Remnawave API v2.8.0."""
|
||||
with pytest.raises(ValidationError):
|
||||
CreateInfraBillingNodeRequestDto(
|
||||
node_uuid=uuid4(),
|
||||
provider_uuid=uuid4(),
|
||||
)
|
||||
|
||||
def test_next_billing_at_provided(self):
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
|
|
@ -196,6 +199,17 @@ class TestCreateInfraBillingNodeRequestDto:
|
|||
)
|
||||
assert dto.next_billing_at == now
|
||||
|
||||
def test_name_supported_and_node_uuid_optional(self):
|
||||
"""name was added and node_uuid became nullable in Remnawave API v2.8.0."""
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
dto = CreateInfraBillingNodeRequestDto(
|
||||
provider_uuid=uuid4(),
|
||||
name="My server",
|
||||
next_billing_at=now,
|
||||
)
|
||||
assert dto.name == "My server"
|
||||
assert dto.node_uuid is None
|
||||
|
||||
|
||||
class TestResponseRulesSettings:
|
||||
def test_settings_field_exists(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue