diff --git a/README.md b/README.md index 5993d35..6e369b1 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ pip install git+https://github.com/remnawave/python-sdk.git@development | Contract Version | Remnawave Panel Version | | ---------------- | ----------------------- | +| 2.6.1 | >=2.6.0 | | 2.4.4 | >=2.4.0 | | 2.3.2 | >=2.3.0, <2.4.0 | | 2.3.0 | >=2.3.0, <2.3.2 | diff --git a/pyproject.toml b/pyproject.toml index e450fe4..b95c373 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "remnawave" -version = "2.4.4" -description = "A Python SDK for interacting with the Remnawave API v2.4.4." +version = "2.6.1" +description = "A Python SDK for interacting with the Remnawave API v2.6.1." authors = [ {name = "Artem",email = "dev@forestsnet.com"} ] diff --git a/remnawave/controllers/nodes.py b/remnawave/controllers/nodes.py index 1b99623..746a42e 100644 --- a/remnawave/controllers/nodes.py +++ b/remnawave/controllers/nodes.py @@ -10,6 +10,7 @@ from remnawave.models import ( DisableNodeResponseDto, EnableNodeResponseDto, GetAllNodesResponseDto, + GetAllNodesTagsResponseDto, GetOneNodeResponseDto, ReorderNodeRequestDto, ReorderNodeResponseDto, @@ -29,6 +30,13 @@ from remnawave.rapid import BaseController, delete, get, patch, post class NodesController(BaseController): + @get("/nodes/tags", response_class=GetAllNodesTagsResponseDto) + async def get_all_nodes_tags( + self, + ) -> GetAllNodesTagsResponseDto: + """Get all nodes tags""" + ... + @post("/nodes", response_class=CreateNodeResponseDto) async def create_node( self, diff --git a/remnawave/controllers/passkeys.py b/remnawave/controllers/passkeys.py index 55e53db..82c88e9 100644 --- a/remnawave/controllers/passkeys.py +++ b/remnawave/controllers/passkeys.py @@ -7,10 +7,12 @@ from remnawave.models import ( DeletePasskeyResponseDto, GetAllPasskeysResponseDto, GetPasskeyRegistrationOptionsResponseDto, + UpdatePasskeyRequestDto, + UpdatePasskeyResponseDto, VerifyPasskeyRegistrationRequestDto, VerifyPasskeyRegistrationResponseDto, ) -from remnawave.rapid import BaseController, delete, get, post +from remnawave.rapid import BaseController, delete, get, patch, post class PasskeysController(BaseController): @@ -42,4 +44,12 @@ class PasskeysController(BaseController): body: Annotated[DeletePasskeyRequestDto, PydanticBody()], ) -> DeletePasskeyResponseDto: """Delete a passkey by ID""" + ... + + @patch("/passkeys", response_class=UpdatePasskeyResponseDto) + async def update_passkey( + self, + body: Annotated[UpdatePasskeyRequestDto, PydanticBody()], + ) -> UpdatePasskeyResponseDto: + """Update a passkey name""" ... \ No newline at end of file diff --git a/remnawave/enums/auth.py b/remnawave/enums/auth.py index cc18cd6..ac62047 100644 --- a/remnawave/enums/auth.py +++ b/remnawave/enums/auth.py @@ -4,4 +4,5 @@ class OAuth2Provider(StrEnum): """OAuth2 Provider enum""" GITHUB = "github" POCKETID = "pocketid" - YANDEX = "yandex" \ No newline at end of file + YANDEX = "yandex" + KEYCLOAK = "keycloak" \ No newline at end of file diff --git a/remnawave/models/__init__.py b/remnawave/models/__init__.py index c0a7ce2..126fcc4 100644 --- a/remnawave/models/__init__.py +++ b/remnawave/models/__init__.py @@ -179,6 +179,7 @@ from .nodes import ( EnableNodeResponseDto, ExcludedInbounds, GetAllNodesResponseDto, + GetAllNodesTagsResponseDto, GetOneNodeResponseDto, NodeConfigProfileDto, NodeConfigProfileRequestDto, @@ -388,6 +389,8 @@ from .passkeys import ( GetAllPasskeysResponseDto, GetPasskeyRegistrationOptionsResponseDto, PasskeyDto, + UpdatePasskeyRequestDto, + UpdatePasskeyResponseDto, VerifyPasskeyRegistrationRequestDto, VerifyPasskeyRegistrationResponseDto, ) @@ -422,6 +425,8 @@ from .remnawave_settings import ( BrandingSettings, GetRemnawaveSettingsResponseDto, GitHubOAuth2Settings, + GenericOAuth2Settings, + KeycloakOAuth2Settings, OAuth2Settings, PasskeySettings, PasswordSettings, @@ -474,6 +479,7 @@ __all__ = [ "EnableNodeResponseDto", "ExcludedInbounds", "GetAllNodesResponseDto", + "GetAllNodesTagsResponseDto", "GetOneNodeResponseDto", "NodeResponseDto", "NodesResponseDto", # Legacy alias @@ -815,6 +821,8 @@ __all__ = [ "GetAllPasskeysResponseDto", "GetPasskeyRegistrationOptionsResponseDto", "PasskeyDto", + "UpdatePasskeyRequestDto", + "UpdatePasskeyResponseDto", "VerifyPasskeyRegistrationRequestDto", "VerifyPasskeyRegistrationResponseDto", @@ -850,7 +858,9 @@ __all__ = [ "BrandingSettings", "GetRemnawaveSettingsResponseDto", + "GenericOAuth2Settings", "GitHubOAuth2Settings", + "KeycloakOAuth2Settings", "OAuth2Settings", "PasskeySettings", "PasswordSettings", diff --git a/remnawave/models/config_profiles.py b/remnawave/models/config_profiles.py index 46371fc..f3625ef 100644 --- a/remnawave/models/config_profiles.py +++ b/remnawave/models/config_profiles.py @@ -12,7 +12,7 @@ class InboundDto(BaseModel): type: str network: Optional[str] = None security: Optional[str] = None - port: Optional[int] = None + port: Optional[float] = None raw_inbound: Optional[Any] = Field(None, alias="rawInbound") class NodesProfileDto(BaseModel): @@ -23,6 +23,7 @@ class NodesProfileDto(BaseModel): class ConfigProfileDto(BaseModel): uuid: UUID name: str + view_position: int = Field(alias="viewPosition") config: Dict[str, Any] inbounds: List[InboundDto] nodes: List[NodesProfileDto] = [] diff --git a/remnawave/models/external_squads.py b/remnawave/models/external_squads.py index ee579d3..bae2d41 100644 --- a/remnawave/models/external_squads.py +++ b/remnawave/models/external_squads.py @@ -49,6 +49,7 @@ class ExternalSquadHostOverridesDto(BaseModel): class ExternalSquadDto(BaseModel): """External squad data model""" uuid: UUID + view_position: int = Field(alias="viewPosition") name: str info: ExternalSquadInfoDto templates: List[ExternalSquadTemplateDto] diff --git a/remnawave/models/hosts.py b/remnawave/models/hosts.py index f456f17..3795fef 100644 --- a/remnawave/models/hosts.py +++ b/remnawave/models/hosts.py @@ -65,18 +65,18 @@ class HostResponseDto(BaseModel): remark: str address: str port: int - path: Optional[str] = None - sni: Optional[str] = None - host: Optional[str] = None - alpn: Optional[str] = None - fingerprint: Optional[str] = None - x_http_extra_params: Optional[Dict[str, Any]] = Field(None, alias="xHttpExtraParams") - mux_params: Optional[Dict[str, Any]] = Field(None, alias="muxParams") - sockopt_params: Optional[Dict[str, Any]] = Field(None, alias="sockoptParams") + path: str | None = Field(alias="path") + sni: str | None = Field(alias="sni") + 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") + mux_params: Dict[str, Any] | None = Field(alias="muxParams") + sockopt_params: Dict[str, Any] | None = Field(alias="sockoptParams") inbound: HostInboundData - server_description: Optional[str] = Field(None, alias="serverDescription") - tag: Optional[str] = None - vless_route_id: Optional[int] = Field(None, alias="vlessRouteId") + server_description: str | None = Field(alias="serverDescription") + tag: str | None = Field(alias="tag") + vless_route_id: int | None = Field(alias="vlessRouteId") shuffle_host: bool = Field(alias="shuffleHost") mihomo_x25519: bool = Field(alias="mihomoX25519") nodes: List[UUID] @@ -86,7 +86,7 @@ class HostResponseDto(BaseModel): override_sni_from_address: bool = Field(False, alias="overrideSniFromAddress") keep_blank_sni: bool = Field(False, alias="keepBlankSni") allow_insecure: bool = Field(False, alias="allowInsecure") - xray_json_template_uuid: Optional[UUID] = Field(None, alias="xrayJsonTemplateUuid") + xray_json_template_uuid: UUID | None = Field(alias="xrayJsonTemplateUuid") excluded_internal_squads: List[UUID] = Field(default_factory=list, alias="excludedInternalSquads") @property diff --git a/remnawave/models/internal_squads.py b/remnawave/models/internal_squads.py index 7b55a46..5034dcf 100644 --- a/remnawave/models/internal_squads.py +++ b/remnawave/models/internal_squads.py @@ -10,10 +10,10 @@ class InboundsDto(BaseModel): profile_uuid: UUID = Field(alias="profileUuid") tag: str type: str - network: Optional[str] = Field(default=None) - security: Optional[str] = Field(default=None) - port: Optional[float] = Field(default=None) - raw_inbound: Optional[dict] = Field(default=None, alias="rawInbound") + network: Optional[str] = None + security: Optional[str] = None + port: Optional[float] = None + raw_inbound: Optional[dict] = Field(None, alias="rawInbound") class InfoDto(BaseModel): @@ -23,6 +23,7 @@ class InfoDto(BaseModel): class InternalSquadDto(BaseModel): uuid: UUID + view_position: int = Field(alias="viewPosition") name: str info: Optional[InfoDto] = Field(default=None) inbounds: List[InboundsDto] = Field(default_factory=list) diff --git a/remnawave/models/nodes.py b/remnawave/models/nodes.py index 24e8bab..9f2383d 100644 --- a/remnawave/models/nodes.py +++ b/remnawave/models/nodes.py @@ -28,6 +28,11 @@ class ReorderNodeItem(BaseModel): uuid: UUID +class GetAllNodesTagsResponseDto(BaseModel): + """Response with all nodes tags""" + tags: List[str] + + class NodeProviderDto(BaseModel): """Node provider information""" uuid: UUID @@ -79,6 +84,11 @@ class CreateNodeRequestDto(BaseModel): serialization_alias="configProfile" ) provider_uuid: Optional[UUID] = Field(None, serialization_alias="providerUuid") + tags: Optional[List[Annotated[str, StringConstraints(max_length=36, pattern=r'^[A-Z0-9_:]+$')]]] = Field( + None, + serialization_alias="tags", + max_length=10 + ) class UpdateNodeRequestDto(BaseModel): @@ -111,6 +121,11 @@ class UpdateNodeRequestDto(BaseModel): None, serialization_alias="configProfile" ) provider_uuid: Optional[UUID] = Field(None, serialization_alias="providerUuid") + tags: Optional[List[Annotated[str, StringConstraints(max_length=36, pattern=r'^[A-Z0-9_:]+$')]]] = Field( + None, + serialization_alias="tags", + max_length=10 + ) class ReorderNodeRequestDto(BaseModel): @@ -147,6 +162,7 @@ class NodeResponseDto(BaseModel): config_profile: NodeConfigProfileDto = Field(alias="configProfile") provider_uuid: Optional[UUID] = Field(None, alias="providerUuid") provider: Optional[NodeProviderDto] = None + tags: List[str] = Field(default_factory=list, alias="tags") class CreateNodeResponseDto(NodeResponseDto): diff --git a/remnawave/models/passkeys.py b/remnawave/models/passkeys.py index 45d6586..1f1725c 100644 --- a/remnawave/models/passkeys.py +++ b/remnawave/models/passkeys.py @@ -43,4 +43,15 @@ class DeletePasskeyRequestDto(BaseModel): class DeletePasskeyResponseDto(BaseModel): """Response with updated passkeys list after deletion""" - passkeys: List[PasskeyDto] \ No newline at end of file + passkeys: List[PasskeyDto] + + +class UpdatePasskeyRequestDto(BaseModel): + """Request to update a passkey""" + id: str + name: str + + +class UpdatePasskeyResponseDto(BaseModel): + """Response with updated passkey information""" + passkey: PasskeyDto \ No newline at end of file diff --git a/remnawave/models/remnawave_settings.py b/remnawave/models/remnawave_settings.py index 5f04010..76fcad4 100644 --- a/remnawave/models/remnawave_settings.py +++ b/remnawave/models/remnawave_settings.py @@ -6,32 +6,55 @@ from pydantic import BaseModel, Field, HttpUrl class PasskeySettings(BaseModel): """Passkey authentication settings""" enabled: bool - rp_id: Optional[str] = Field(None, alias="rpId") - origin: Optional[str] = None + rp_id: str | None = Field(alias="rpId") + origin: str | None class GitHubOAuth2Settings(BaseModel): """GitHub OAuth2 settings""" enabled: bool - client_id: Optional[str] = Field(None, alias="clientId") - client_secret: Optional[str] = Field(None, alias="clientSecret") + client_id: str | None = Field(alias="clientId") + client_secret: str | None = Field(alias="clientSecret") allowed_emails: List[str] = Field(alias="allowedEmails") class PocketIdOAuth2Settings(BaseModel): """PocketID OAuth2 settings""" enabled: bool - client_id: Optional[str] = Field(None, alias="clientId") - client_secret: Optional[str] = Field(None, alias="clientSecret") - plain_domain: Optional[str] = Field(None, alias="plainDomain") + client_id: str | None = Field(alias="clientId") + client_secret: str | None = Field(alias="clientSecret") + plain_domain: str | None = Field(alias="plainDomain") allowed_emails: List[str] = Field(alias="allowedEmails") class YandexOAuth2Settings(BaseModel): """Yandex OAuth2 settings""" enabled: bool - client_id: Optional[str] = Field(None, alias="clientId") - client_secret: Optional[str] = Field(None, alias="clientSecret") + client_id: str | None = Field(alias="clientId") + client_secret: str | None = Field(alias="clientSecret") + allowed_emails: List[str] = Field(alias="allowedEmails") + + +class KeycloakOAuth2Settings(BaseModel): + """Keycloak OAuth2 settings""" + enabled: bool + realm: str | None + client_id: str | None = Field(alias="clientId") + client_secret: str | None = Field(alias="clientSecret") + frontend_domain: str | None = Field(alias="frontendDomain") + keycloak_domain: str | None = Field(alias="keycloakDomain") + allowed_emails: List[str] = Field(alias="allowedEmails") + + +class GenericOAuth2Settings(BaseModel): + """Generic OAuth2 settings""" + enabled: bool + client_id: str | None = Field(alias="clientId") + client_secret: str | None = Field(alias="clientSecret") + with_pkce: bool = Field(alias="withPkce") + authorization_url: str | None = Field(alias="authorizationUrl") + token_url: str | None = Field(alias="tokenUrl") + frontend_domain: str | None = Field(alias="frontendDomain") allowed_emails: List[str] = Field(alias="allowedEmails") @@ -40,12 +63,14 @@ class OAuth2Settings(BaseModel): github: GitHubOAuth2Settings pocketid: PocketIdOAuth2Settings yandex: YandexOAuth2Settings + keycloak: KeycloakOAuth2Settings + generic: GenericOAuth2Settings class TelegramAuthSettings(BaseModel): """Telegram authentication settings""" enabled: bool - bot_token: Optional[str] = Field(None, alias="botToken") + bot_token: str | None = Field(alias="botToken") admin_ids: List[str] = Field(alias="adminIds") @@ -62,9 +87,9 @@ class BrandingSettings(BaseModel): class RemnawaveSettingsData(BaseModel): """Remnawave settings data""" - passkey_settings: Optional[PasskeySettings] = Field(None, alias="passkeySettings") - oauth2_settings: Optional[OAuth2Settings] = Field(None, alias="oauth2Settings") - tg_auth_settings: Optional[TelegramAuthSettings] = Field(None, alias="tgAuthSettings") + passkey_settings: PasskeySettings | None = Field(alias="passkeySettings") + oauth2_settings: OAuth2Settings | None = Field(alias="oauth2Settings") + tg_auth_settings: TelegramAuthSettings | None = Field(alias="tgAuthSettings") password_settings: Optional[PasswordSettings] = Field(None, alias="passwordSettings") branding_settings: Optional[BrandingSettings] = Field(None, alias="brandingSettings") diff --git a/remnawave/models/subscription.py b/remnawave/models/subscription.py index d75878d..37a892f 100644 --- a/remnawave/models/subscription.py +++ b/remnawave/models/subscription.py @@ -182,7 +182,7 @@ class UserSubscription(BaseModel): lifetime_traffic_used: str = Field(alias="lifetimeTrafficUsed") traffic_used_bytes: str = Field(alias="trafficUsedBytes") traffic_limit_bytes: str = Field(alias="trafficLimitBytes") - lifetime_traffic_used_bytes: int = Field(alias="lifetimeTrafficUsedBytes") + lifetime_traffic_used_bytes: str = Field(alias="lifetimeTrafficUsedBytes") traffic_limit_strategy: TrafficLimitStrategy = Field(alias="trafficLimitStrategy") expires_at: datetime = Field(alias="expiresAt") user_status: UserStatus = Field(alias="userStatus") diff --git a/remnawave/models/subscription_page.py b/remnawave/models/subscription_page.py index 7d8ff73..eccc716 100644 --- a/remnawave/models/subscription_page.py +++ b/remnawave/models/subscription_page.py @@ -11,7 +11,7 @@ class SubscriptionPageConfigDto(BaseModel): uuid: UUID view_position: int = Field(alias="viewPosition") name: str - config: Optional[Any] = None + config: Any | None class GetSubscriptionPageConfigsData(BaseModel): @@ -25,9 +25,14 @@ class GetSubscriptionPageConfigsResponseDto(GetSubscriptionPageConfigsData): pass -class GetSubscriptionPageConfigResponseDto(SubscriptionPageConfigDto): +class GetSubscriptionPageConfigResponseDto(BaseModel): """Response with single subscription page config""" - pass + model_config = ConfigDict(populate_by_name=True) + + uuid: UUID + view_position: int = Field(alias="viewPosition") + name: str + config: Any class CreateSubscriptionPageConfigRequestDto(BaseModel): diff --git a/remnawave/models/subscriptions_template.py b/remnawave/models/subscriptions_template.py index 1ff1102..254d7b9 100644 --- a/remnawave/models/subscriptions_template.py +++ b/remnawave/models/subscriptions_template.py @@ -9,15 +9,17 @@ from remnawave.enums import TemplateType class TemplateResponseDto(BaseModel): uuid: UUID name: str + view_position: int = Field(alias="viewPosition") template_type: TemplateType = Field(alias="templateType") - template_json: Optional[Any] = Field(None, alias="templateJson") - encoded_template_yaml: Optional[str] = Field(None, alias="encodedTemplateYaml") + template_json: Any | None = Field(alias="templateJson") + encoded_template_yaml: str | None = Field(alias="encodedTemplateYaml") class TemplateInfoDto(BaseModel): """Template info without content - used in list responses""" uuid: UUID name: str + view_position: int = Field(alias="viewPosition") template_type: TemplateType = Field(alias="templateType") template_json: Optional[Any] = Field(None, alias="templateJson") encoded_template_yaml: Optional[str] = Field(None, alias="encodedTemplateYaml")