789 lines
14 KiB
Python
789 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, Dict, List, Literal, Optional
|
|
|
|
# ---------------------------
|
|
# Models (UI-friendly)
|
|
# ---------------------------
|
|
|
|
@dataclass(frozen=True)
|
|
class Status:
|
|
timestamp: str
|
|
ip_count: int
|
|
domain_count: int
|
|
iface: str
|
|
table: str
|
|
mark: str
|
|
# NOTE: backend uses omitempty for these, so they may be absent.
|
|
policy_route_ok: Optional[bool]
|
|
route_ok: Optional[bool]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CmdResult:
|
|
ok: bool
|
|
message: str
|
|
exit_code: Optional[int] = None
|
|
stdout: str = ""
|
|
stderr: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LoginState:
|
|
state: str
|
|
email: str
|
|
msg: str
|
|
# backend may also provide UI-ready fields
|
|
text: str
|
|
color: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class UnitState:
|
|
state: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RoutesTimerState:
|
|
enabled: bool
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficModeStatus:
|
|
mode: str
|
|
desired_mode: str
|
|
applied_mode: str
|
|
preferred_iface: str
|
|
advanced_active: bool
|
|
auto_local_bypass: bool
|
|
auto_local_active: bool
|
|
ingress_reply_bypass: bool
|
|
ingress_reply_active: bool
|
|
bypass_candidates: int
|
|
force_vpn_subnets: List[str]
|
|
force_vpn_uids: List[str]
|
|
force_vpn_cgroups: List[str]
|
|
force_direct_subnets: List[str]
|
|
force_direct_uids: List[str]
|
|
force_direct_cgroups: List[str]
|
|
overrides_applied: int
|
|
cgroup_resolved_uids: int
|
|
cgroup_warning: str
|
|
active_iface: str
|
|
iface_reason: str
|
|
rule_mark: bool
|
|
rule_full: bool
|
|
ingress_rule_present: bool
|
|
ingress_nft_active: bool
|
|
table_default: bool
|
|
probe_ok: bool
|
|
probe_message: str
|
|
healthy: bool
|
|
message: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficInterfaces:
|
|
interfaces: List[str]
|
|
preferred_iface: str
|
|
active_iface: str
|
|
iface_reason: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficAppMarksStatus:
|
|
vpn_count: int
|
|
direct_count: int
|
|
message: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficAppMarksResult:
|
|
ok: bool
|
|
message: str
|
|
op: str = ""
|
|
target: str = ""
|
|
cgroup: str = ""
|
|
cgroup_id: int = 0
|
|
timeout_sec: int = 0
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficAppMarkItem:
|
|
id: int
|
|
target: str # vpn|direct
|
|
cgroup: str
|
|
cgroup_rel: str
|
|
level: int
|
|
unit: str
|
|
command: str
|
|
app_key: str
|
|
added_at: str
|
|
expires_at: str
|
|
remaining_sec: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficAppProfile:
|
|
id: str
|
|
name: str
|
|
app_key: str
|
|
command: str
|
|
target: str # vpn|direct
|
|
ttl_sec: int
|
|
vpn_profile: str
|
|
created_at: str
|
|
updated_at: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficAppProfileSaveResult:
|
|
ok: bool
|
|
message: str
|
|
profile: Optional[TrafficAppProfile] = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficAudit:
|
|
ok: bool
|
|
message: str
|
|
now: str
|
|
pretty: str
|
|
issues: List[str]
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportClientHealth:
|
|
last_check: str
|
|
latency_ms: int
|
|
last_error: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportClient:
|
|
id: str
|
|
name: str
|
|
kind: str
|
|
enabled: bool
|
|
status: str
|
|
iface: str
|
|
routing_table: str
|
|
mark_hex: str
|
|
priority_base: int
|
|
capabilities: List[str]
|
|
health: TransportClientHealth
|
|
config: Dict[str, Any]
|
|
updated_at: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportHealthRefreshItem:
|
|
client_id: str
|
|
status: str
|
|
queued: bool
|
|
reason: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportHealthRefreshResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
count: int
|
|
queued: int
|
|
skipped: int
|
|
items: List[TransportHealthRefreshItem]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportClientHealthSnapshot:
|
|
client_id: str
|
|
status: str
|
|
latency_ms: int
|
|
last_error: str
|
|
last_check: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportPolicyIntent:
|
|
selector_type: str
|
|
selector_value: str
|
|
client_id: str
|
|
priority: int = 100
|
|
mode: str = "strict"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportPolicy:
|
|
revision: int
|
|
intents: List[TransportPolicyIntent]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportConflict:
|
|
key: str
|
|
type: str
|
|
severity: str
|
|
owners: List[str]
|
|
reason: str
|
|
suggested_resolution: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportPolicyValidateSummary:
|
|
block_count: int
|
|
warn_count: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportPolicyDiff:
|
|
added: int
|
|
changed: int
|
|
removed: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportPolicyValidateResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
valid: bool
|
|
base_revision: int
|
|
confirm_token: str
|
|
summary: TransportPolicyValidateSummary
|
|
conflicts: List[TransportConflict]
|
|
diff: TransportPolicyDiff
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportPolicyApplyResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
policy_revision: int
|
|
current_revision: int
|
|
apply_id: str
|
|
rollback_available: bool
|
|
conflicts: List[TransportConflict]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportConflicts:
|
|
has_blocking: bool
|
|
items: List[TransportConflict]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportCapabilities:
|
|
clients: Dict[str, Dict[str, bool]]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportInterfaceItem:
|
|
id: str
|
|
name: str
|
|
mode: str
|
|
runtime_iface: str
|
|
netns_name: str
|
|
routing_table: str
|
|
client_ids: List[str]
|
|
client_count: int
|
|
up_count: int
|
|
updated_at: str
|
|
config: Dict[str, Any]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportInterfacesSnapshot:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
count: int
|
|
items: List[TransportInterfaceItem]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportOwnershipRecord:
|
|
key: str
|
|
selector_type: str
|
|
selector_value: str
|
|
client_id: str
|
|
client_kind: str
|
|
owner_scope: str
|
|
owner_status: str
|
|
lock_active: bool
|
|
iface_id: str
|
|
routing_table: str
|
|
mark_hex: str
|
|
priority_base: int
|
|
mode: str
|
|
priority: int
|
|
updated_at: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportOwnershipSnapshot:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
policy_revision: int
|
|
plan_digest: str
|
|
count: int
|
|
lock_count: int
|
|
items: List[TransportOwnershipRecord]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportOwnerLockRecord:
|
|
destination_ip: str
|
|
client_id: str
|
|
client_kind: str
|
|
iface_id: str
|
|
mark_hex: str
|
|
proto: str
|
|
updated_at: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportOwnerLocksSnapshot:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
policy_revision: int
|
|
count: int
|
|
items: List[TransportOwnerLockRecord]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportOwnerLocksClearResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
base_revision: int
|
|
confirm_required: bool
|
|
confirm_token: str
|
|
match_count: int
|
|
cleared_count: int
|
|
remaining_count: int
|
|
items: List[TransportOwnerLockRecord]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportClientActionResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
client_id: str
|
|
kind: str
|
|
action: str
|
|
status_before: str
|
|
status_after: str
|
|
last_error: str
|
|
exit_code: Optional[int] = None
|
|
stdout: str = ""
|
|
stderr: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportNetnsToggleItem:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
client_id: str
|
|
kind: str
|
|
status_before: str
|
|
status_after: str
|
|
netns_enabled: bool
|
|
config_updated: bool
|
|
provisioned: bool
|
|
restarted: bool
|
|
stdout: str = ""
|
|
stderr: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TransportNetnsToggleResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
enabled: bool
|
|
count: int
|
|
success_count: int
|
|
failure_count: int
|
|
items: List[TransportNetnsToggleItem]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SingBoxProfileIssue:
|
|
field: str
|
|
severity: str
|
|
code: str
|
|
message: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SingBoxProfileRenderDiff:
|
|
added: int
|
|
changed: int
|
|
removed: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SingBoxProfileValidateResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
profile_id: str
|
|
profile_revision: int
|
|
valid: bool
|
|
errors: List[SingBoxProfileIssue]
|
|
warnings: List[SingBoxProfileIssue]
|
|
render_digest: str
|
|
diff: SingBoxProfileRenderDiff
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SingBoxProfileApplyResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
profile_id: str
|
|
client_id: str
|
|
config_path: str
|
|
profile_revision: int
|
|
render_revision: int
|
|
last_applied_at: str
|
|
render_path: str
|
|
render_digest: str
|
|
rollback_available: bool
|
|
valid: bool
|
|
errors: List[SingBoxProfileIssue]
|
|
warnings: List[SingBoxProfileIssue]
|
|
diff: SingBoxProfileRenderDiff
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SingBoxProfile:
|
|
id: str
|
|
name: str
|
|
mode: str
|
|
protocol: str
|
|
enabled: bool
|
|
schema_version: int
|
|
profile_revision: int
|
|
render_revision: int
|
|
last_validated_at: str
|
|
last_applied_at: str
|
|
last_error: str
|
|
typed: Dict[str, Any]
|
|
raw_config: Dict[str, Any]
|
|
meta: Dict[str, Any]
|
|
has_secrets: bool
|
|
secrets_masked: Dict[str, str]
|
|
created_at: str
|
|
updated_at: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SingBoxProfilesState:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
count: int
|
|
active_profile_id: str
|
|
items: List[SingBoxProfile]
|
|
item: Optional[SingBoxProfile] = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SingBoxProfileRenderResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
profile_id: str
|
|
profile_revision: int
|
|
render_revision: int
|
|
render_path: str
|
|
render_digest: str
|
|
changed: bool
|
|
valid: bool
|
|
errors: List[SingBoxProfileIssue]
|
|
warnings: List[SingBoxProfileIssue]
|
|
diff: SingBoxProfileRenderDiff
|
|
config: Dict[str, Any]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SingBoxProfileRollbackResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
profile_id: str
|
|
client_id: str
|
|
config_path: str
|
|
history_id: str
|
|
profile_revision: int
|
|
last_applied_at: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SingBoxProfileHistoryEntry:
|
|
id: str
|
|
at: str
|
|
profile_id: str
|
|
action: str
|
|
status: str
|
|
code: str
|
|
message: str
|
|
profile_revision: int
|
|
render_revision: int
|
|
render_digest: str
|
|
render_path: str
|
|
client_id: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SingBoxProfileHistoryResult:
|
|
ok: bool
|
|
message: str
|
|
code: str
|
|
profile_id: str
|
|
count: int
|
|
items: List[SingBoxProfileHistoryEntry]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficCandidateSubnet:
|
|
cidr: str
|
|
dev: str
|
|
kind: str
|
|
linkdown: bool
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficCandidateUnit:
|
|
unit: str
|
|
description: str
|
|
cgroup: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficCandidateUID:
|
|
uid: int
|
|
user: str
|
|
examples: List[str]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrafficCandidates:
|
|
generated_at: str
|
|
subnets: List[TrafficCandidateSubnet]
|
|
units: List[TrafficCandidateUnit]
|
|
uids: List[TrafficCandidateUID]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DnsUpstreams:
|
|
default1: str
|
|
default2: str
|
|
meta1: str
|
|
meta2: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DNSBenchmarkUpstream:
|
|
addr: str
|
|
enabled: bool = True
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DNSBenchmarkResult:
|
|
upstream: str
|
|
attempts: int
|
|
ok: int
|
|
fail: int
|
|
nxdomain: int
|
|
timeout: int
|
|
temporary: int
|
|
other: int
|
|
avg_ms: int
|
|
p95_ms: int
|
|
score: float
|
|
color: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DNSBenchmarkResponse:
|
|
results: List[DNSBenchmarkResult]
|
|
domains_used: List[str]
|
|
timeout_ms: int
|
|
attempts_per_domain: int
|
|
profile: str
|
|
recommended_default: List[str]
|
|
recommended_meta: List[str]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DNSUpstreamPoolState:
|
|
items: List[DNSBenchmarkUpstream]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SmartdnsServiceState:
|
|
state: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DNSStatus:
|
|
via_smartdns: bool
|
|
smartdns_addr: str
|
|
mode: str
|
|
unit_state: str
|
|
runtime_nftset: bool
|
|
wildcard_source: str
|
|
runtime_config_path: str
|
|
runtime_config_error: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SmartdnsRuntimeState:
|
|
enabled: bool
|
|
applied_enabled: bool
|
|
wildcard_source: str
|
|
unit_state: str
|
|
config_path: str
|
|
changed: bool = False
|
|
restarted: bool = False
|
|
message: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DomainsTable:
|
|
lines: List[str]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DomainsFile:
|
|
name: str
|
|
content: str
|
|
source: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class VpnAutoloopStatus:
|
|
raw_text: str
|
|
status_word: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class VpnStatus:
|
|
desired_location: str
|
|
status_word: str
|
|
raw_text: str
|
|
unit_state: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class VpnLocation:
|
|
label: str
|
|
iso: str
|
|
target: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class VpnLocationsState:
|
|
locations: List[VpnLocation]
|
|
updated_at: str
|
|
stale: bool
|
|
refresh_in_progress: bool
|
|
last_error: str
|
|
next_retry_at: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class EgressIdentity:
|
|
scope: str
|
|
source: str
|
|
source_id: str
|
|
ip: str
|
|
country_code: str
|
|
country_name: str
|
|
updated_at: str
|
|
stale: bool
|
|
refresh_in_progress: bool
|
|
last_error: str
|
|
next_retry_at: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class EgressIdentityRefreshItem:
|
|
scope: str
|
|
status: str
|
|
queued: bool
|
|
reason: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class EgressIdentityRefreshResult:
|
|
ok: bool
|
|
message: str
|
|
count: int
|
|
queued: int
|
|
skipped: int
|
|
items: List[EgressIdentityRefreshItem]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TraceDump:
|
|
lines: List[str]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Event:
|
|
id: int
|
|
kind: str
|
|
ts: str
|
|
data: Any
|
|
|
|
# ---------------------------
|
|
# AdGuard VPN interactive login-session (PTY)
|
|
# ---------------------------
|
|
|
|
@dataclass(frozen=True)
|
|
class LoginSessionStart:
|
|
ok: bool
|
|
phase: str
|
|
level: str
|
|
pid: Optional[int] = None
|
|
email: str = ""
|
|
error: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LoginSessionState:
|
|
ok: bool
|
|
phase: str
|
|
level: str
|
|
alive: bool
|
|
url: str
|
|
email: str
|
|
cursor: int
|
|
lines: List[str]
|
|
can_open: bool
|
|
can_check: bool
|
|
can_cancel: bool
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LoginSessionAction:
|
|
ok: bool
|
|
phase: str = ""
|
|
level: str = ""
|
|
error: str = ""
|
|
|
|
TraceMode = Literal["full", "gui", "smartdns"]
|
|
ServiceAction = Literal["start", "stop", "restart"]
|
|
TransportClientAction = Literal["provision", "start", "stop", "restart"]
|