platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
788
selective-vpn-gui/api/models.py
Normal file
788
selective-vpn-gui/api/models.py
Normal file
@@ -0,0 +1,788 @@
|
||||
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"]
|
||||
Reference in New Issue
Block a user