plato-sdk-v2 2.7.1__py3-none-any.whl → 2.7.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plato/v1/cli/pm.py +77 -27
- plato/v2/utils/db_cleanup.py +7 -9
- plato/v2/utils/gateway_tunnel.py +221 -0
- {plato_sdk_v2-2.7.1.dist-info → plato_sdk_v2-2.7.3.dist-info}/METADATA +1 -1
- {plato_sdk_v2-2.7.1.dist-info → plato_sdk_v2-2.7.3.dist-info}/RECORD +7 -6
- {plato_sdk_v2-2.7.1.dist-info → plato_sdk_v2-2.7.3.dist-info}/WHEEL +0 -0
- {plato_sdk_v2-2.7.1.dist-info → plato_sdk_v2-2.7.3.dist-info}/entry_points.txt +0 -0
plato/v1/cli/pm.py
CHANGED
|
@@ -544,9 +544,19 @@ def review_base(
|
|
|
544
544
|
console.print(f"[bold red]📋 Most Recent Base Review: REJECTED[/bold red] ({timestamp})")
|
|
545
545
|
else:
|
|
546
546
|
console.print(f"[bold green]📋 Most Recent Base Review: PASSED[/bold green] ({timestamp})")
|
|
547
|
-
comments
|
|
548
|
-
|
|
549
|
-
|
|
547
|
+
# Handle both old 'comments' field and new 'sim_comments' structure
|
|
548
|
+
sim_comments = recent_review.get("sim_comments")
|
|
549
|
+
if sim_comments:
|
|
550
|
+
console.print("[yellow]Reviewer Comments:[/yellow]")
|
|
551
|
+
for i, item in enumerate(sim_comments, 1):
|
|
552
|
+
comment_text = item.get("comment", "")
|
|
553
|
+
if comment_text:
|
|
554
|
+
console.print(f" {i}. {comment_text}")
|
|
555
|
+
else:
|
|
556
|
+
# Fallback to old comments field
|
|
557
|
+
comments = recent_review.get("comments")
|
|
558
|
+
if comments:
|
|
559
|
+
console.print(f"[yellow]Reviewer Comments:[/yellow] {comments}")
|
|
550
560
|
|
|
551
561
|
console.print()
|
|
552
562
|
|
|
@@ -752,8 +762,6 @@ def review_data(
|
|
|
752
762
|
simulator, artifact, require_artifact=False, command_name="review data"
|
|
753
763
|
)
|
|
754
764
|
|
|
755
|
-
# Determine target URL based on simulator
|
|
756
|
-
target_url = f"https://{simulator_name}.web.plato.so"
|
|
757
765
|
console.print(f"[cyan]Simulator:[/cyan] {simulator_name}")
|
|
758
766
|
|
|
759
767
|
# Fetch simulator config and get artifact ID if not provided
|
|
@@ -802,21 +810,21 @@ def review_data(
|
|
|
802
810
|
is_installed = "site-packages" in str(package_dir)
|
|
803
811
|
|
|
804
812
|
if is_installed:
|
|
805
|
-
extension_source_path = package_dir / "extensions" / "
|
|
813
|
+
extension_source_path = package_dir / "extensions" / "data-review"
|
|
806
814
|
else:
|
|
807
815
|
repo_root = package_dir.parent.parent.parent # plato-client/
|
|
808
|
-
extension_source_path = repo_root / "extensions" / "
|
|
816
|
+
extension_source_path = repo_root / "extensions" / "data-review"
|
|
809
817
|
|
|
810
818
|
# Fallback to env var
|
|
811
819
|
if not extension_source_path.exists():
|
|
812
820
|
plato_client_dir_env = os.getenv("PLATO_CLIENT_DIR")
|
|
813
821
|
if plato_client_dir_env:
|
|
814
|
-
env_path = Path(plato_client_dir_env) / "extensions" / "
|
|
822
|
+
env_path = Path(plato_client_dir_env) / "extensions" / "data-review"
|
|
815
823
|
if env_path.exists():
|
|
816
824
|
extension_source_path = env_path
|
|
817
825
|
|
|
818
826
|
if not extension_source_path.exists():
|
|
819
|
-
console.print("[red]❌
|
|
827
|
+
console.print("[red]❌ Data Review extension not found[/red]")
|
|
820
828
|
console.print(f"\n[yellow]Expected location:[/yellow] {extension_source_path}")
|
|
821
829
|
raise typer.Exit(1)
|
|
822
830
|
|
|
@@ -829,14 +837,44 @@ def review_data(
|
|
|
829
837
|
console.print(f"[green]✅ Extension copied to: {extension_path}[/green]")
|
|
830
838
|
|
|
831
839
|
async def _review_data():
|
|
840
|
+
base_url = _get_base_url()
|
|
841
|
+
plato = AsyncPlato(api_key=api_key, base_url=base_url)
|
|
842
|
+
session = None
|
|
832
843
|
playwright = None
|
|
833
844
|
browser = None
|
|
834
845
|
|
|
835
846
|
try:
|
|
847
|
+
# Check if we have an artifact ID to create a session
|
|
848
|
+
if not artifact_id:
|
|
849
|
+
console.print("[red]❌ No artifact ID available. Cannot create session.[/red]")
|
|
850
|
+
console.print("[yellow]Specify artifact with: plato pm review data -s simulator:artifact_id[/yellow]")
|
|
851
|
+
raise typer.Exit(1)
|
|
852
|
+
|
|
853
|
+
# Create session with artifact
|
|
854
|
+
console.print(f"[cyan]Creating {simulator_name} environment with artifact {artifact_id}...[/cyan]")
|
|
855
|
+
session = await plato.sessions.create(
|
|
856
|
+
envs=[Env.artifact(artifact_id)],
|
|
857
|
+
timeout=300,
|
|
858
|
+
)
|
|
859
|
+
console.print(f"[green]✅ Session created: {session.session_id}[/green]")
|
|
860
|
+
|
|
861
|
+
# Reset environment
|
|
862
|
+
console.print("[cyan]Resetting environment...[/cyan]")
|
|
863
|
+
await session.reset()
|
|
864
|
+
console.print("[green]✅ Environment reset complete![/green]")
|
|
865
|
+
|
|
866
|
+
# Get public URL
|
|
867
|
+
public_urls = await session.get_public_url()
|
|
868
|
+
first_alias = session.envs[0].alias if session.envs else None
|
|
869
|
+
public_url = public_urls.get(first_alias) if first_alias else None
|
|
870
|
+
if not public_url and public_urls:
|
|
871
|
+
public_url = list(public_urls.values())[0]
|
|
872
|
+
console.print(f"[cyan]Public URL:[/cyan] {public_url}")
|
|
873
|
+
|
|
836
874
|
user_data_dir = Path.home() / ".plato" / "chrome-data"
|
|
837
875
|
user_data_dir.mkdir(parents=True, exist_ok=True)
|
|
838
876
|
|
|
839
|
-
console.print("[cyan]Launching Chrome with
|
|
877
|
+
console.print("[cyan]Launching Chrome with Data Review extension...[/cyan]")
|
|
840
878
|
|
|
841
879
|
from playwright.async_api import async_playwright
|
|
842
880
|
|
|
@@ -877,13 +915,14 @@ def review_data(
|
|
|
877
915
|
else:
|
|
878
916
|
console.print("[yellow]⚠️ Could not find extension ID. Please set API key manually.[/yellow]")
|
|
879
917
|
|
|
880
|
-
#
|
|
881
|
-
console.print(
|
|
918
|
+
# Navigate to public URL (user logs in manually with displayed credentials)
|
|
919
|
+
console.print("[cyan]Opening environment...[/cyan]")
|
|
882
920
|
main_page = await browser.new_page()
|
|
883
|
-
|
|
884
|
-
|
|
921
|
+
if public_url:
|
|
922
|
+
await main_page.goto(public_url)
|
|
923
|
+
console.print(f"[green]✅ Loaded: {public_url}[/green]")
|
|
885
924
|
|
|
886
|
-
#
|
|
925
|
+
# Use options page to set API key
|
|
887
926
|
if extension_id:
|
|
888
927
|
options_page = await browser.new_page()
|
|
889
928
|
try:
|
|
@@ -907,17 +946,16 @@ def review_data(
|
|
|
907
946
|
await options_page.close()
|
|
908
947
|
|
|
909
948
|
# Bring main page to front
|
|
910
|
-
|
|
949
|
+
if main_page:
|
|
950
|
+
await main_page.bring_to_front()
|
|
911
951
|
|
|
912
952
|
console.print()
|
|
913
953
|
console.print("[bold]Instructions:[/bold]")
|
|
914
|
-
console.print(" 1. Click the
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
console.print(" 3. Use the extension to record and submit reviews")
|
|
920
|
-
console.print(" 4. When done, press Control-C to exit")
|
|
954
|
+
console.print(" 1. Click the Data Review extension icon to open the sidebar")
|
|
955
|
+
console.print(f" 2. Enter '{simulator_name}' as the simulator name and click Start Review")
|
|
956
|
+
console.print(" 3. Take screenshots and add comments for any issues")
|
|
957
|
+
console.print(" 4. Select Pass or Reject and submit the review")
|
|
958
|
+
console.print(" 5. When done, press Control-C to exit")
|
|
921
959
|
|
|
922
960
|
# Show recent review if available
|
|
923
961
|
if recent_review:
|
|
@@ -930,10 +968,20 @@ def review_data(
|
|
|
930
968
|
else:
|
|
931
969
|
console.print(f"[bold green]📋 Most Recent Data Review: PASSED[/bold green] ({timestamp})")
|
|
932
970
|
|
|
933
|
-
comments
|
|
934
|
-
|
|
971
|
+
# Handle both old 'comments' field and new 'sim_comments' structure
|
|
972
|
+
sim_comments = recent_review.get("sim_comments")
|
|
973
|
+
if sim_comments:
|
|
935
974
|
console.print("\n[yellow]Reviewer Comments:[/yellow]")
|
|
936
|
-
|
|
975
|
+
for i, item in enumerate(sim_comments, 1):
|
|
976
|
+
comment_text = item.get("comment", "")
|
|
977
|
+
if comment_text:
|
|
978
|
+
console.print(f" {i}. {comment_text}")
|
|
979
|
+
else:
|
|
980
|
+
# Fallback to old comments field
|
|
981
|
+
comments = recent_review.get("comments")
|
|
982
|
+
if comments:
|
|
983
|
+
console.print("\n[yellow]Reviewer Comments:[/yellow]")
|
|
984
|
+
console.print(f" {comments}")
|
|
937
985
|
console.print("=" * 60)
|
|
938
986
|
|
|
939
987
|
console.print()
|
|
@@ -954,6 +1002,8 @@ def review_data(
|
|
|
954
1002
|
|
|
955
1003
|
finally:
|
|
956
1004
|
try:
|
|
1005
|
+
if session:
|
|
1006
|
+
await session.close()
|
|
957
1007
|
if browser:
|
|
958
1008
|
await browser.close()
|
|
959
1009
|
if playwright:
|
|
@@ -961,7 +1011,7 @@ def review_data(
|
|
|
961
1011
|
if temp_ext_dir.exists():
|
|
962
1012
|
shutil.rmtree(temp_ext_dir, ignore_errors=True)
|
|
963
1013
|
except Exception as e:
|
|
964
|
-
console.print(f"[yellow]⚠️
|
|
1014
|
+
console.print(f"[yellow]⚠️ Cleanup error: {e}[/yellow]")
|
|
965
1015
|
|
|
966
1016
|
handle_async(_review_data())
|
|
967
1017
|
|
plato/v2/utils/db_cleanup.py
CHANGED
|
@@ -12,6 +12,7 @@ from sqlalchemy.ext.asyncio import create_async_engine
|
|
|
12
12
|
|
|
13
13
|
from plato._generated.api.v1.simulator import get_db_config
|
|
14
14
|
from plato._generated.models import DbConfigResponse
|
|
15
|
+
from plato.v2.utils.gateway_tunnel import GatewayTunnel, find_free_port
|
|
15
16
|
from plato.v2.utils.models import (
|
|
16
17
|
ApiCleanupResult,
|
|
17
18
|
DatabaseCleanupResult,
|
|
@@ -19,12 +20,10 @@ from plato.v2.utils.models import (
|
|
|
19
20
|
EnvironmentInfo,
|
|
20
21
|
SessionCleanupResult,
|
|
21
22
|
)
|
|
22
|
-
from plato.v2.utils.proxy_tunnel import
|
|
23
|
+
from plato.v2.utils.proxy_tunnel import make_db_url
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
26
|
|
|
26
|
-
TEMP_PASSWORD = "newpass"
|
|
27
|
-
|
|
28
27
|
|
|
29
28
|
class DatabaseCleaner:
|
|
30
29
|
"""Handles database audit_log cleanup operations."""
|
|
@@ -145,14 +144,13 @@ class DatabaseCleaner:
|
|
|
145
144
|
config: DbConfigResponse,
|
|
146
145
|
local_port: int,
|
|
147
146
|
) -> DatabaseCleanupResult:
|
|
148
|
-
"""Connect to a single DB via tunnel and truncate audit_log tables."""
|
|
147
|
+
"""Connect to a single DB via gateway tunnel and truncate audit_log tables."""
|
|
149
148
|
db_port = config.db_port
|
|
150
149
|
|
|
151
|
-
tunnel =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
host_port=local_port,
|
|
150
|
+
tunnel = GatewayTunnel(
|
|
151
|
+
job_id=job_id,
|
|
152
|
+
remote_port=db_port,
|
|
153
|
+
local_port=local_port,
|
|
156
154
|
)
|
|
157
155
|
|
|
158
156
|
try:
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""TLS + SNI gateway tunnel for database connections.
|
|
2
|
+
|
|
3
|
+
Routes traffic through gateway.plato.so:443 using SNI-based routing,
|
|
4
|
+
replacing the deprecated HTTP CONNECT proxy approach.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import socket
|
|
13
|
+
import ssl
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# Default gateway configuration
|
|
18
|
+
DEFAULT_GATEWAY_HOST = "gateway.plato.so"
|
|
19
|
+
DEFAULT_GATEWAY_PORT = 443
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_gateway_config() -> tuple[str, int]:
|
|
23
|
+
"""Get gateway host and port from environment or defaults.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Tuple of (host, port) for the gateway.
|
|
27
|
+
"""
|
|
28
|
+
host = os.environ.get("PLATO_GATEWAY_HOST", DEFAULT_GATEWAY_HOST)
|
|
29
|
+
port = int(os.environ.get("PLATO_GATEWAY_PORT", str(DEFAULT_GATEWAY_PORT)))
|
|
30
|
+
return host, port
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def find_free_port(start_port: int = 55432) -> int:
|
|
34
|
+
"""Find the first available TCP port starting from start_port."""
|
|
35
|
+
port = start_port
|
|
36
|
+
while port < 65535:
|
|
37
|
+
try:
|
|
38
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
39
|
+
s.bind(("127.0.0.1", port))
|
|
40
|
+
return port
|
|
41
|
+
except OSError:
|
|
42
|
+
port += 1
|
|
43
|
+
raise RuntimeError(f"No free port found starting from {start_port}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class GatewayTunnel:
|
|
47
|
+
"""Async TLS + SNI gateway tunnel for database connections.
|
|
48
|
+
|
|
49
|
+
Routes local connections through gateway.plato.so using SNI-based routing.
|
|
50
|
+
This replaces the deprecated HTTP CONNECT proxy tunnel.
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
tunnel = GatewayTunnel(job_id="abc123", remote_port=5432, local_port=55432)
|
|
54
|
+
await tunnel.start()
|
|
55
|
+
# Connect to localhost:55432 to reach the VM's port 5432
|
|
56
|
+
await tunnel.stop()
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
job_id: str,
|
|
62
|
+
remote_port: int,
|
|
63
|
+
local_port: int,
|
|
64
|
+
gateway_host: str | None = None,
|
|
65
|
+
gateway_port: int | None = None,
|
|
66
|
+
verify_ssl: bool = True,
|
|
67
|
+
):
|
|
68
|
+
"""Initialize the gateway tunnel.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
job_id: The job/environment ID to connect to.
|
|
72
|
+
remote_port: Port on the VM to forward to.
|
|
73
|
+
local_port: Local port to listen on.
|
|
74
|
+
gateway_host: Gateway hostname (default: from env or gateway.plato.so).
|
|
75
|
+
gateway_port: Gateway port (default: from env or 443).
|
|
76
|
+
verify_ssl: Whether to verify SSL certificates.
|
|
77
|
+
"""
|
|
78
|
+
self.job_id = job_id
|
|
79
|
+
self.remote_port = remote_port
|
|
80
|
+
self.local_port = local_port
|
|
81
|
+
self.verify_ssl = verify_ssl
|
|
82
|
+
|
|
83
|
+
# Get gateway config
|
|
84
|
+
default_host, default_port = get_gateway_config()
|
|
85
|
+
self.gateway_host = gateway_host or default_host
|
|
86
|
+
self.gateway_port = gateway_port or default_port
|
|
87
|
+
|
|
88
|
+
# SNI for routing: {job_id}--{port}.gateway.plato.so
|
|
89
|
+
self.sni = f"{job_id}--{remote_port}.{self.gateway_host}"
|
|
90
|
+
|
|
91
|
+
self._server: asyncio.AbstractServer | None = None
|
|
92
|
+
self._client_tasks: set[asyncio.Task] = set()
|
|
93
|
+
|
|
94
|
+
async def _open_gateway_connection(
|
|
95
|
+
self,
|
|
96
|
+
timeout: float = 30.0,
|
|
97
|
+
) -> tuple[asyncio.StreamReader, asyncio.StreamWriter]:
|
|
98
|
+
"""Open a TLS connection to the gateway with SNI routing.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Tuple of (reader, writer) for the gateway connection.
|
|
102
|
+
"""
|
|
103
|
+
# Create SSL context
|
|
104
|
+
ssl_ctx = ssl.create_default_context()
|
|
105
|
+
if not self.verify_ssl:
|
|
106
|
+
ssl_ctx.check_hostname = False
|
|
107
|
+
ssl_ctx.verify_mode = ssl.CERT_NONE
|
|
108
|
+
|
|
109
|
+
# Connect with TLS, using SNI for routing
|
|
110
|
+
reader, writer = await asyncio.wait_for(
|
|
111
|
+
asyncio.open_connection(
|
|
112
|
+
self.gateway_host,
|
|
113
|
+
self.gateway_port,
|
|
114
|
+
ssl=ssl_ctx,
|
|
115
|
+
server_hostname=self.sni, # SNI determines which VM/port to route to
|
|
116
|
+
),
|
|
117
|
+
timeout=timeout,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Enable TCP keepalive
|
|
121
|
+
sock = writer.get_extra_info("socket")
|
|
122
|
+
if isinstance(sock, socket.socket):
|
|
123
|
+
try:
|
|
124
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
125
|
+
# macOS/BSD keepalive idle time
|
|
126
|
+
TCP_KEEPALIVE = getattr(socket, "TCP_KEEPALIVE", 0x10)
|
|
127
|
+
sock.setsockopt(socket.IPPROTO_TCP, TCP_KEEPALIVE, 30)
|
|
128
|
+
except OSError:
|
|
129
|
+
pass # Best effort
|
|
130
|
+
|
|
131
|
+
return reader, writer
|
|
132
|
+
|
|
133
|
+
async def _pipe(
|
|
134
|
+
self,
|
|
135
|
+
src: asyncio.StreamReader,
|
|
136
|
+
dst: asyncio.StreamWriter,
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Forward data from src to dst until EOF."""
|
|
139
|
+
try:
|
|
140
|
+
while True:
|
|
141
|
+
data = await src.read(65536)
|
|
142
|
+
if not data:
|
|
143
|
+
break
|
|
144
|
+
dst.write(data)
|
|
145
|
+
await dst.drain()
|
|
146
|
+
except (ConnectionResetError, BrokenPipeError, OSError):
|
|
147
|
+
pass
|
|
148
|
+
finally:
|
|
149
|
+
try:
|
|
150
|
+
dst.close()
|
|
151
|
+
await dst.wait_closed()
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
async def _handle_client(
|
|
156
|
+
self,
|
|
157
|
+
client_reader: asyncio.StreamReader,
|
|
158
|
+
client_writer: asyncio.StreamWriter,
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Handle a single client connection by forwarding through gateway."""
|
|
161
|
+
task = asyncio.current_task()
|
|
162
|
+
if task:
|
|
163
|
+
self._client_tasks.add(task)
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# Connect to gateway via TLS with SNI
|
|
167
|
+
gateway_reader, gateway_writer = await self._open_gateway_connection()
|
|
168
|
+
|
|
169
|
+
# Bidirectional forwarding
|
|
170
|
+
await asyncio.gather(
|
|
171
|
+
self._pipe(client_reader, gateway_writer),
|
|
172
|
+
self._pipe(gateway_reader, client_writer),
|
|
173
|
+
)
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.warning(f"Gateway tunnel error: {e}")
|
|
176
|
+
try:
|
|
177
|
+
client_writer.close()
|
|
178
|
+
await client_writer.wait_closed()
|
|
179
|
+
except Exception:
|
|
180
|
+
pass
|
|
181
|
+
finally:
|
|
182
|
+
if task:
|
|
183
|
+
self._client_tasks.discard(task)
|
|
184
|
+
|
|
185
|
+
async def start(self) -> None:
|
|
186
|
+
"""Start the gateway tunnel server."""
|
|
187
|
+
logger.info(
|
|
188
|
+
f"Starting gateway tunnel: localhost:{self.local_port} -> "
|
|
189
|
+
f"{self.job_id}:{self.remote_port} via {self.gateway_host}"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
self._server = await asyncio.start_server(
|
|
193
|
+
self._handle_client,
|
|
194
|
+
host="127.0.0.1",
|
|
195
|
+
port=self.local_port,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Small delay to ensure binding is settled
|
|
199
|
+
await asyncio.sleep(0.1)
|
|
200
|
+
|
|
201
|
+
if not self._server.sockets:
|
|
202
|
+
raise RuntimeError("Gateway tunnel failed to start: no listening sockets")
|
|
203
|
+
|
|
204
|
+
logger.info(f"Gateway tunnel established on port {self.local_port}")
|
|
205
|
+
|
|
206
|
+
async def stop(self) -> None:
|
|
207
|
+
"""Stop the gateway tunnel server."""
|
|
208
|
+
if self._server is not None:
|
|
209
|
+
logger.info("Stopping gateway tunnel")
|
|
210
|
+
|
|
211
|
+
# Stop accepting new connections
|
|
212
|
+
self._server.close()
|
|
213
|
+
await self._server.wait_closed()
|
|
214
|
+
self._server = None
|
|
215
|
+
|
|
216
|
+
# Cancel active client tasks
|
|
217
|
+
for t in list(self._client_tasks):
|
|
218
|
+
t.cancel()
|
|
219
|
+
self._client_tasks.clear()
|
|
220
|
+
|
|
221
|
+
logger.info("Gateway tunnel stopped")
|
|
@@ -425,7 +425,7 @@ plato/v1/cli/__init__.py,sha256=om4b7PxgsoI7rEwuQelmQkqPdhMVn53_5qEN8kvksYw,105
|
|
|
425
425
|
plato/v1/cli/agent.py,sha256=r5Eh2e2-rUIGjK5uevnGKqScABtFK-Spomrrytj-3og,44053
|
|
426
426
|
plato/v1/cli/chronos.py,sha256=lzFY0nomP1AY14i8oc8OvWOdq9ydCiE3dN2XrSupvA4,27827
|
|
427
427
|
plato/v1/cli/main.py,sha256=Yqy1vn4sGyAWKNpDVcLl9pbzkMn89tYVBIxFU30ZtPk,6905
|
|
428
|
-
plato/v1/cli/pm.py,sha256=
|
|
428
|
+
plato/v1/cli/pm.py,sha256=Q6HFTb8ZO_aB0EtAO6-OOsnVv3SoC8hL7UHpawBz46Y,55520
|
|
429
429
|
plato/v1/cli/proxy.py,sha256=WmCt0R9Gos1q0FZTQSsbloNC3-Cnx6Yb60RZF1BzC18,12178
|
|
430
430
|
plato/v1/cli/sandbox.py,sha256=SQb5XCdYvTHEyZxOv9ECtafTdkxpjfq45pYd-m1z7k0,101506
|
|
431
431
|
plato/v1/cli/ssh.py,sha256=9ypjn5kQuaTcVjsWMDIUDyehXRH9fauk_z-C3mXzYJ8,2381
|
|
@@ -494,7 +494,8 @@ plato/v2/sync/environment.py,sha256=WnDzbyEHpwCSEP8XnfNSjIYS7rt7lYR4HGJjzprZmTQ,
|
|
|
494
494
|
plato/v2/sync/flow_executor.py,sha256=N41-WCWIJVcCR2UmPUEiK7roNacYoeONkRXpR7lUgT8,13941
|
|
495
495
|
plato/v2/sync/session.py,sha256=okXqF-CjMmA82WRy2zPXaGidbovgjAENSqiuvE4_jKE,30420
|
|
496
496
|
plato/v2/utils/__init__.py,sha256=XLeFFsjXkm9g2raMmo7Wt4QN4hhCrNZDJKnpffJ4LtM,38
|
|
497
|
-
plato/v2/utils/db_cleanup.py,sha256=
|
|
497
|
+
plato/v2/utils/db_cleanup.py,sha256=JMzAAJz0ZnoUXtd8F4jpQmBpJpos2__RkgN_cuEearg,8692
|
|
498
|
+
plato/v2/utils/gateway_tunnel.py,sha256=eWgwf4VV8-jx6iCuHFgCISsAOVmNOOjCB56EuZLsnOA,7171
|
|
498
499
|
plato/v2/utils/models.py,sha256=PwehSSnIRG-tM3tWL1PzZEH77ZHhIAZ9R0UPs6YknbM,1441
|
|
499
500
|
plato/v2/utils/proxy_tunnel.py,sha256=8ZTd0jCGSfIHMvSv1fgEyacuISWnGPHLPbDglWroTzY,10463
|
|
500
501
|
plato/worlds/README.md,sha256=XFOkEA3cNNcrWkk-Cxnsl-zn-y0kvUENKQRSqFKpdqw,5479
|
|
@@ -503,7 +504,7 @@ plato/worlds/base.py,sha256=-RR71bSxEFI5yydtrtq-AAbuw98CIjvmrbztqzB9oIc,31041
|
|
|
503
504
|
plato/worlds/build_hook.py,sha256=KSoW0kqa5b7NyZ7MYOw2qsZ_2FkWuz0M3Ru7AKOP7Qw,3486
|
|
504
505
|
plato/worlds/config.py,sha256=O1lUXzxp-Z_M7izslT8naXgE6XujjzwYFFrDDzUOueI,12736
|
|
505
506
|
plato/worlds/runner.py,sha256=r9B2BxBae8_dM7y5cJf9xhThp_I1Qvf_tlPq2rs8qC8,4013
|
|
506
|
-
plato_sdk_v2-2.7.
|
|
507
|
-
plato_sdk_v2-2.7.
|
|
508
|
-
plato_sdk_v2-2.7.
|
|
509
|
-
plato_sdk_v2-2.7.
|
|
507
|
+
plato_sdk_v2-2.7.3.dist-info/METADATA,sha256=XmAi4ZSPth7U15myiaNzNL98AIPMnCJjtkTsWWXMUjI,8652
|
|
508
|
+
plato_sdk_v2-2.7.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
509
|
+
plato_sdk_v2-2.7.3.dist-info/entry_points.txt,sha256=upGMbJCx6YWUTKrPoYvYUYfFCqYr75nHDwhA-45m6p8,136
|
|
510
|
+
plato_sdk_v2-2.7.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|