skypilot-nightly 1.0.0.dev20250909__py3-none-any.whl → 1.0.0.dev20250912__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.
Potentially problematic release.
This version of skypilot-nightly might be problematic. Click here for more details.
- sky/__init__.py +2 -2
- sky/authentication.py +19 -4
- sky/backends/backend_utils.py +160 -23
- sky/backends/cloud_vm_ray_backend.py +226 -74
- sky/catalog/__init__.py +7 -0
- sky/catalog/aws_catalog.py +4 -0
- sky/catalog/common.py +18 -0
- sky/catalog/data_fetchers/fetch_aws.py +13 -1
- sky/client/cli/command.py +2 -71
- sky/client/sdk.py +20 -0
- sky/client/sdk_async.py +23 -18
- sky/clouds/aws.py +26 -6
- sky/clouds/cloud.py +8 -0
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/chunks/3294.ba6586f9755b0edb.js +6 -0
- sky/dashboard/out/_next/static/chunks/{webpack-d4fabc08788e14af.js → webpack-e8a0c4c3c6f408fb.js} +1 -1
- sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
- sky/dashboard/out/clusters/[cluster].html +1 -1
- sky/dashboard/out/clusters.html +1 -1
- sky/dashboard/out/config.html +1 -1
- sky/dashboard/out/index.html +1 -1
- sky/dashboard/out/infra/[context].html +1 -1
- sky/dashboard/out/infra.html +1 -1
- sky/dashboard/out/jobs/[job].html +1 -1
- sky/dashboard/out/jobs/pools/[pool].html +1 -1
- sky/dashboard/out/jobs.html +1 -1
- sky/dashboard/out/users.html +1 -1
- sky/dashboard/out/volumes.html +1 -1
- sky/dashboard/out/workspace/new.html +1 -1
- sky/dashboard/out/workspaces/[name].html +1 -1
- sky/dashboard/out/workspaces.html +1 -1
- sky/data/storage.py +5 -1
- sky/execution.py +21 -14
- sky/global_user_state.py +34 -0
- sky/jobs/client/sdk_async.py +4 -2
- sky/jobs/constants.py +3 -0
- sky/jobs/controller.py +734 -310
- sky/jobs/recovery_strategy.py +251 -129
- sky/jobs/scheduler.py +247 -174
- sky/jobs/server/core.py +20 -4
- sky/jobs/server/utils.py +2 -2
- sky/jobs/state.py +709 -508
- sky/jobs/utils.py +90 -40
- sky/logs/agent.py +10 -2
- sky/provision/aws/config.py +4 -1
- sky/provision/gcp/config.py +6 -1
- sky/provision/kubernetes/config.py +7 -2
- sky/provision/kubernetes/instance.py +84 -41
- sky/provision/kubernetes/utils.py +17 -8
- sky/provision/provisioner.py +1 -0
- sky/provision/vast/instance.py +1 -1
- sky/schemas/db/global_user_state/008_skylet_ssh_tunnel_metadata.py +34 -0
- sky/serve/replica_managers.py +0 -7
- sky/serve/serve_utils.py +5 -0
- sky/serve/server/impl.py +1 -2
- sky/serve/service.py +0 -2
- sky/server/common.py +8 -3
- sky/server/config.py +55 -27
- sky/server/constants.py +1 -0
- sky/server/daemons.py +7 -11
- sky/server/metrics.py +41 -8
- sky/server/requests/executor.py +41 -4
- sky/server/requests/serializers/encoders.py +1 -1
- sky/server/server.py +9 -1
- sky/server/uvicorn.py +11 -5
- sky/setup_files/dependencies.py +4 -2
- sky/skylet/attempt_skylet.py +1 -0
- sky/skylet/constants.py +14 -7
- sky/skylet/events.py +2 -10
- sky/skylet/log_lib.py +11 -0
- sky/skylet/log_lib.pyi +9 -0
- sky/task.py +62 -0
- sky/templates/kubernetes-ray.yml.j2 +120 -3
- sky/utils/accelerator_registry.py +3 -1
- sky/utils/command_runner.py +35 -11
- sky/utils/command_runner.pyi +25 -3
- sky/utils/common_utils.py +11 -1
- sky/utils/context_utils.py +15 -2
- sky/utils/controller_utils.py +5 -0
- sky/utils/db/db_utils.py +31 -2
- sky/utils/db/migration_utils.py +1 -1
- sky/utils/git.py +559 -1
- sky/utils/resource_checker.py +8 -7
- sky/utils/rich_utils.py +3 -1
- sky/utils/subprocess_utils.py +9 -0
- sky/volumes/volume.py +2 -0
- sky/workspaces/core.py +57 -21
- {skypilot_nightly-1.0.0.dev20250909.dist-info → skypilot_nightly-1.0.0.dev20250912.dist-info}/METADATA +38 -36
- {skypilot_nightly-1.0.0.dev20250909.dist-info → skypilot_nightly-1.0.0.dev20250912.dist-info}/RECORD +95 -95
- sky/client/cli/git.py +0 -549
- sky/dashboard/out/_next/static/chunks/3294.c80326aec9bfed40.js +0 -6
- /sky/dashboard/out/_next/static/{eWytLgin5zvayQw3Xk46m → DAiq7V2xJnO1LSfmunZl6}/_buildManifest.js +0 -0
- /sky/dashboard/out/_next/static/{eWytLgin5zvayQw3Xk46m → DAiq7V2xJnO1LSfmunZl6}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20250909.dist-info → skypilot_nightly-1.0.0.dev20250912.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20250909.dist-info → skypilot_nightly-1.0.0.dev20250912.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250909.dist-info → skypilot_nightly-1.0.0.dev20250912.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20250909.dist-info → skypilot_nightly-1.0.0.dev20250912.dist-info}/top_level.txt +0 -0
sky/client/cli/git.py
DELETED
|
@@ -1,549 +0,0 @@
|
|
|
1
|
-
"""Git utilities for SkyPilot."""
|
|
2
|
-
import enum
|
|
3
|
-
import os
|
|
4
|
-
import re
|
|
5
|
-
from typing import List, Optional, Union
|
|
6
|
-
|
|
7
|
-
import git
|
|
8
|
-
import requests
|
|
9
|
-
|
|
10
|
-
from sky import exceptions
|
|
11
|
-
from sky import sky_logging
|
|
12
|
-
|
|
13
|
-
logger = sky_logging.init_logger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class GitRefType(enum.Enum):
|
|
17
|
-
"""Type of git reference."""
|
|
18
|
-
|
|
19
|
-
BRANCH = 'branch'
|
|
20
|
-
TAG = 'tag'
|
|
21
|
-
COMMIT = 'commit'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class GitUrlInfo:
|
|
25
|
-
"""Information extracted from a git URL."""
|
|
26
|
-
|
|
27
|
-
def __init__(self,
|
|
28
|
-
host: str,
|
|
29
|
-
path: str,
|
|
30
|
-
protocol: str,
|
|
31
|
-
user: Optional[str] = None,
|
|
32
|
-
port: Optional[int] = None):
|
|
33
|
-
self.host = host
|
|
34
|
-
# Repository path (e.g., 'user/repo' or 'org/subgroup/repo').
|
|
35
|
-
# The path is the part after the host.
|
|
36
|
-
self.path = path
|
|
37
|
-
# 'https', 'ssh'
|
|
38
|
-
self.protocol = protocol
|
|
39
|
-
# SSH username
|
|
40
|
-
self.user = user
|
|
41
|
-
self.port = port
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class GitCloneInfo:
|
|
45
|
-
"""Information about a git clone."""
|
|
46
|
-
|
|
47
|
-
def __init__(self,
|
|
48
|
-
url: str,
|
|
49
|
-
envs: Optional[dict] = None,
|
|
50
|
-
token: Optional[str] = None,
|
|
51
|
-
ssh_key: Optional[str] = None):
|
|
52
|
-
self.url = url
|
|
53
|
-
self.envs = envs
|
|
54
|
-
self.token = token
|
|
55
|
-
self.ssh_key = ssh_key
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class GitRepo:
|
|
59
|
-
"""Git utilities for SkyPilot."""
|
|
60
|
-
|
|
61
|
-
def __init__(self,
|
|
62
|
-
repo_url: str,
|
|
63
|
-
ref: str = 'main',
|
|
64
|
-
git_token: Optional[str] = None,
|
|
65
|
-
git_ssh_key_path: Optional[str] = None):
|
|
66
|
-
"""Initialize Git utility.
|
|
67
|
-
|
|
68
|
-
Args:
|
|
69
|
-
repo_url: Git repository URL.
|
|
70
|
-
ref: Git reference (branch, tag, or commit hash).
|
|
71
|
-
git_token: GitHub token for private repositories.
|
|
72
|
-
git_ssh_key_path: Path to SSH private key for authentication.
|
|
73
|
-
"""
|
|
74
|
-
self.repo_url = repo_url
|
|
75
|
-
self.ref = ref
|
|
76
|
-
self.git_token = git_token
|
|
77
|
-
self.git_ssh_key_path = git_ssh_key_path
|
|
78
|
-
|
|
79
|
-
# Parse URL during initialization to catch format errors early
|
|
80
|
-
self._parsed_url = self._parse_git_url(self.repo_url)
|
|
81
|
-
|
|
82
|
-
def _parse_git_url(self, url: str) -> GitUrlInfo:
|
|
83
|
-
"""Parse git URL into components.
|
|
84
|
-
|
|
85
|
-
Supports various git URL formats:
|
|
86
|
-
- HTTPS: https://github.com/user/repo.git
|
|
87
|
-
- SSH: git@github.com:user/repo.git (SCP-like)
|
|
88
|
-
- SSH full: ssh://git@github.com/user/repo.git
|
|
89
|
-
- SSH with port: ssh://git@github.com:2222/user/repo.git
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
url: Git repository URL in any supported format.
|
|
93
|
-
|
|
94
|
-
Returns:
|
|
95
|
-
GitUrlInfo with parsed components.
|
|
96
|
-
|
|
97
|
-
Raises:
|
|
98
|
-
exceptions.GitError: If URL format is not supported.
|
|
99
|
-
"""
|
|
100
|
-
# Remove trailing .git if present
|
|
101
|
-
clean_url = url.rstrip('/')
|
|
102
|
-
if clean_url.endswith('.git'):
|
|
103
|
-
clean_url = clean_url[:-4]
|
|
104
|
-
|
|
105
|
-
# Pattern for HTTPS/HTTP URLs
|
|
106
|
-
https_pattern = r'^(https?)://(?:([^@]+)@)?([^:/]+)(?::(\d+))?/(.+)$'
|
|
107
|
-
https_match = re.match(https_pattern, clean_url)
|
|
108
|
-
|
|
109
|
-
if https_match:
|
|
110
|
-
protocol, user, host, port_str, path = https_match.groups()
|
|
111
|
-
port = int(port_str) if port_str else None
|
|
112
|
-
|
|
113
|
-
# Validate that path is not empty
|
|
114
|
-
if not path or path == '/':
|
|
115
|
-
raise exceptions.GitError(
|
|
116
|
-
f'Invalid repository path in URL: {url}')
|
|
117
|
-
|
|
118
|
-
return GitUrlInfo(host=host,
|
|
119
|
-
path=path,
|
|
120
|
-
protocol=protocol,
|
|
121
|
-
user=user,
|
|
122
|
-
port=port)
|
|
123
|
-
|
|
124
|
-
# Pattern for SSH URLs (full format)
|
|
125
|
-
ssh_full_pattern = r'^ssh://(?:([^@]+)@)?([^:/]+)(?::(\d+))?/(.+)$'
|
|
126
|
-
ssh_full_match = re.match(ssh_full_pattern, clean_url)
|
|
127
|
-
|
|
128
|
-
if ssh_full_match:
|
|
129
|
-
user, host, port_str, path = ssh_full_match.groups()
|
|
130
|
-
port = int(port_str) if port_str else None
|
|
131
|
-
|
|
132
|
-
# Validate that path is not empty
|
|
133
|
-
if not path or path == '/':
|
|
134
|
-
raise exceptions.GitError(
|
|
135
|
-
f'Invalid repository path in SSH URL: {url}')
|
|
136
|
-
|
|
137
|
-
return GitUrlInfo(host=host,
|
|
138
|
-
path=path,
|
|
139
|
-
protocol='ssh',
|
|
140
|
-
user=user,
|
|
141
|
-
port=port)
|
|
142
|
-
|
|
143
|
-
# Pattern for SSH SCP-like format (exclude URLs with ://)
|
|
144
|
-
scp_pattern = r'^(?:([^@]+)@)?([^:/]+):(.+)$'
|
|
145
|
-
scp_match = re.match(scp_pattern, clean_url)
|
|
146
|
-
|
|
147
|
-
# Make sure it's not a URL with protocol (should not contain ://)
|
|
148
|
-
if scp_match and '://' not in clean_url:
|
|
149
|
-
user, host, path = scp_match.groups()
|
|
150
|
-
|
|
151
|
-
# Validate that path is not empty
|
|
152
|
-
if not path:
|
|
153
|
-
raise exceptions.GitError(
|
|
154
|
-
f'Invalid repository path in SSH URL: {url}')
|
|
155
|
-
|
|
156
|
-
return GitUrlInfo(host=host,
|
|
157
|
-
path=path,
|
|
158
|
-
protocol='ssh',
|
|
159
|
-
user=user,
|
|
160
|
-
port=None)
|
|
161
|
-
|
|
162
|
-
raise exceptions.GitError(
|
|
163
|
-
f'Unsupported git URL format: {url}. '
|
|
164
|
-
'Supported formats: https://host/owner/repo, '
|
|
165
|
-
'ssh://user@host/owner/repo, user@host:owner/repo')
|
|
166
|
-
|
|
167
|
-
def get_https_url(self, with_token: bool = False) -> str:
|
|
168
|
-
"""Get HTTPS URL for the repository.
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
with_token: If True, includes token in URL for authentication
|
|
172
|
-
|
|
173
|
-
Returns:
|
|
174
|
-
HTTPS URL string.
|
|
175
|
-
"""
|
|
176
|
-
port_str = f':{self._parsed_url.port}' if self._parsed_url.port else ''
|
|
177
|
-
path = self._parsed_url.path
|
|
178
|
-
# Remove .git suffix if present (but not individual characters)
|
|
179
|
-
if path.endswith('.git'):
|
|
180
|
-
path = path[:-4]
|
|
181
|
-
|
|
182
|
-
if with_token and self.git_token:
|
|
183
|
-
return f'https://{self.git_token}@{self._parsed_url.host}' \
|
|
184
|
-
f'{port_str}/{path}.git'
|
|
185
|
-
return f'https://{self._parsed_url.host}{port_str}/{path}.git'
|
|
186
|
-
|
|
187
|
-
def get_ssh_url(self) -> str:
|
|
188
|
-
"""Get SSH URL for the repository in full format.
|
|
189
|
-
|
|
190
|
-
Returns:
|
|
191
|
-
SSH URL string in full format.
|
|
192
|
-
"""
|
|
193
|
-
# Use original user from URL, or default to 'git'
|
|
194
|
-
ssh_user = self._parsed_url.user or 'git'
|
|
195
|
-
port_str = f':{self._parsed_url.port}' if self._parsed_url.port else ''
|
|
196
|
-
path = self._parsed_url.path
|
|
197
|
-
# Remove .git suffix if present (but not individual characters)
|
|
198
|
-
if path.endswith('.git'):
|
|
199
|
-
path = path[:-4]
|
|
200
|
-
return f'ssh://{ssh_user}@{self._parsed_url.host}{port_str}/{path}.git'
|
|
201
|
-
|
|
202
|
-
def get_repo_clone_info(self) -> GitCloneInfo:
|
|
203
|
-
"""Validate the repository access with comprehensive authentication
|
|
204
|
-
and return the appropriate clone info.
|
|
205
|
-
|
|
206
|
-
This method implements a sequential validation approach:
|
|
207
|
-
1. Try public access (no authentication)
|
|
208
|
-
2. If has token and URL is https, try token access
|
|
209
|
-
3. If URL is ssh, try ssh access with user provided ssh key or
|
|
210
|
-
default ssh credential
|
|
211
|
-
|
|
212
|
-
Returns:
|
|
213
|
-
GitCloneInfo instance with successful access method.
|
|
214
|
-
|
|
215
|
-
Raises:
|
|
216
|
-
exceptions.GitError: If the git URL format is invalid or
|
|
217
|
-
the repository cannot be accessed.
|
|
218
|
-
"""
|
|
219
|
-
logger.debug(f'Validating access to {self._parsed_url.host}'
|
|
220
|
-
f'/{self._parsed_url.path}')
|
|
221
|
-
|
|
222
|
-
# Step 1: Try public access first (most common case)
|
|
223
|
-
try:
|
|
224
|
-
https_url = self.get_https_url()
|
|
225
|
-
logger.debug(f'Trying public HTTPS access to {https_url}')
|
|
226
|
-
|
|
227
|
-
# Use /info/refs endpoint to check public access.
|
|
228
|
-
# This is more reliable than git ls-remote as it doesn't
|
|
229
|
-
# use local git config.
|
|
230
|
-
stripped_url = https_url.rstrip('/')
|
|
231
|
-
info_refs_url = f'{stripped_url}/info/refs?service=git-upload-pack'
|
|
232
|
-
|
|
233
|
-
# Make a simple HTTP request without any authentication
|
|
234
|
-
response = requests.get(
|
|
235
|
-
info_refs_url,
|
|
236
|
-
timeout=10,
|
|
237
|
-
allow_redirects=True,
|
|
238
|
-
# Ensure no local credentials are used
|
|
239
|
-
auth=None)
|
|
240
|
-
|
|
241
|
-
if response.status_code == 200:
|
|
242
|
-
logger.info(
|
|
243
|
-
f'Successfully validated repository {https_url} access '
|
|
244
|
-
'using public access')
|
|
245
|
-
return GitCloneInfo(url=https_url)
|
|
246
|
-
except Exception as e: # pylint: disable=broad-except
|
|
247
|
-
logger.debug(f'Public access failed: {str(e)}')
|
|
248
|
-
|
|
249
|
-
# Step 2: Try with token if provided
|
|
250
|
-
if self.git_token and self._parsed_url.protocol == 'https':
|
|
251
|
-
try:
|
|
252
|
-
https_url = self.get_https_url()
|
|
253
|
-
auth_url = self.get_https_url(with_token=True)
|
|
254
|
-
logger.debug(f'Trying token authentication to {https_url}')
|
|
255
|
-
git_cmd = git.cmd.Git()
|
|
256
|
-
git_cmd.ls_remote(auth_url)
|
|
257
|
-
logger.info(
|
|
258
|
-
f'Successfully validated repository {https_url} access '
|
|
259
|
-
'using token authentication')
|
|
260
|
-
return GitCloneInfo(url=https_url, token=self.git_token)
|
|
261
|
-
except Exception as e:
|
|
262
|
-
logger.info(f'Token access failed: {str(e)}')
|
|
263
|
-
raise exceptions.GitError(
|
|
264
|
-
f'Failed to access repository {self.repo_url} using token '
|
|
265
|
-
'authentication. Please verify your token and repository '
|
|
266
|
-
f'access permissions. Original error: {str(e)}') from e
|
|
267
|
-
|
|
268
|
-
# Step 3: Try SSH access with available keys
|
|
269
|
-
if self._parsed_url.protocol == 'ssh':
|
|
270
|
-
try:
|
|
271
|
-
ssh_url = self.get_ssh_url()
|
|
272
|
-
|
|
273
|
-
# Get SSH key info using the combined method
|
|
274
|
-
ssh_key_info = self._get_ssh_key_info()
|
|
275
|
-
|
|
276
|
-
if ssh_key_info:
|
|
277
|
-
key_path, key_content = ssh_key_info
|
|
278
|
-
git_ssh_command = f'ssh -F none -i {key_path} ' \
|
|
279
|
-
'-o StrictHostKeyChecking=no ' \
|
|
280
|
-
'-o UserKnownHostsFile=/dev/null ' \
|
|
281
|
-
'-o IdentitiesOnly=yes'
|
|
282
|
-
ssh_env = {'GIT_SSH_COMMAND': git_ssh_command}
|
|
283
|
-
|
|
284
|
-
logger.debug(f'Trying SSH authentication to {ssh_url} '
|
|
285
|
-
f'with {key_path}')
|
|
286
|
-
git_cmd = git.cmd.Git()
|
|
287
|
-
git_cmd.update_environment(**ssh_env)
|
|
288
|
-
git_cmd.ls_remote(ssh_url)
|
|
289
|
-
logger.info(
|
|
290
|
-
f'Successfully validated repository {ssh_url} access '
|
|
291
|
-
f'using SSH key: {key_path}')
|
|
292
|
-
return GitCloneInfo(url=ssh_url,
|
|
293
|
-
ssh_key=key_content,
|
|
294
|
-
envs=ssh_env)
|
|
295
|
-
else:
|
|
296
|
-
raise exceptions.GitError(
|
|
297
|
-
f'No SSH keys found for {self.repo_url}.')
|
|
298
|
-
except Exception as e: # pylint: disable=broad-except
|
|
299
|
-
raise exceptions.GitError(
|
|
300
|
-
f'Failed to access repository {self.repo_url} using '
|
|
301
|
-
'SSH key authentication. Please verify your SSH key and '
|
|
302
|
-
'repository access permissions. '
|
|
303
|
-
f'Original error: {str(e)}') from e
|
|
304
|
-
|
|
305
|
-
# If we get here, no authentication methods are available
|
|
306
|
-
raise exceptions.GitError(
|
|
307
|
-
f'Failed to access repository {self.repo_url}. '
|
|
308
|
-
'If this is a private repository, please provide authentication'
|
|
309
|
-
f' using either: GIT_TOKEN for token-based access, or'
|
|
310
|
-
f' GIT_SSH_KEY_PATH for SSH access.')
|
|
311
|
-
|
|
312
|
-
def _parse_ssh_config(self) -> Optional[str]:
|
|
313
|
-
"""Parse SSH config file to find IdentityFile for the target host.
|
|
314
|
-
|
|
315
|
-
Returns:
|
|
316
|
-
Path to SSH private key specified in config, or None if not found.
|
|
317
|
-
"""
|
|
318
|
-
ssh_config_path = os.path.expanduser('~/.ssh/config')
|
|
319
|
-
if not os.path.exists(ssh_config_path):
|
|
320
|
-
logger.debug('SSH config file ~/.ssh/config does not exist')
|
|
321
|
-
return None
|
|
322
|
-
|
|
323
|
-
try:
|
|
324
|
-
# Try to use paramiko's SSH config parser if available
|
|
325
|
-
try:
|
|
326
|
-
import paramiko # pylint: disable=import-outside-toplevel
|
|
327
|
-
ssh_config = paramiko.SSHConfig()
|
|
328
|
-
with open(ssh_config_path, 'r', encoding='utf-8') as f:
|
|
329
|
-
ssh_config.parse(f)
|
|
330
|
-
# Get config for the target host
|
|
331
|
-
host_config = ssh_config.lookup(self._parsed_url.host)
|
|
332
|
-
|
|
333
|
-
# Look for identity files in the config
|
|
334
|
-
identity_files: Union[str, List[str]] = host_config.get(
|
|
335
|
-
'identityfile', [])
|
|
336
|
-
if not isinstance(identity_files, list):
|
|
337
|
-
identity_files = [identity_files]
|
|
338
|
-
|
|
339
|
-
# Find the first existing identity file
|
|
340
|
-
for identity_file in identity_files:
|
|
341
|
-
key_path = os.path.expanduser(identity_file)
|
|
342
|
-
if os.path.exists(key_path):
|
|
343
|
-
logger.debug(f'Found SSH key in config for '
|
|
344
|
-
f'{self._parsed_url.host}: {key_path}')
|
|
345
|
-
return key_path
|
|
346
|
-
|
|
347
|
-
logger.debug(f'No valid SSH keys found in config for host: '
|
|
348
|
-
f'{self._parsed_url.host}')
|
|
349
|
-
return None
|
|
350
|
-
|
|
351
|
-
except ImportError:
|
|
352
|
-
logger.debug('paramiko not available')
|
|
353
|
-
return None
|
|
354
|
-
|
|
355
|
-
except Exception as e: # pylint: disable=broad-except
|
|
356
|
-
logger.debug(f'Error parsing SSH config: {str(e)}')
|
|
357
|
-
return None
|
|
358
|
-
|
|
359
|
-
def _get_ssh_key_info(self) -> Optional[tuple]:
|
|
360
|
-
"""Get SSH key path and content using comprehensive strategy.
|
|
361
|
-
|
|
362
|
-
Strategy:
|
|
363
|
-
1. Check provided git_ssh_key_path if given
|
|
364
|
-
2. Check SSH config for host-specific IdentityFile
|
|
365
|
-
3. Search for common SSH key types in ~/.ssh/ directory
|
|
366
|
-
|
|
367
|
-
Returns:
|
|
368
|
-
Tuple of (key_path, key_content) if found, None otherwise.
|
|
369
|
-
"""
|
|
370
|
-
# Step 1: Check provided SSH key path first
|
|
371
|
-
if self.git_ssh_key_path:
|
|
372
|
-
try:
|
|
373
|
-
key_path = os.path.expanduser(self.git_ssh_key_path)
|
|
374
|
-
|
|
375
|
-
# Validate SSH key before using it
|
|
376
|
-
if not os.path.exists(key_path):
|
|
377
|
-
raise exceptions.GitError(
|
|
378
|
-
f'SSH key not found at path: {self.git_ssh_key_path}')
|
|
379
|
-
|
|
380
|
-
# Check key permissions
|
|
381
|
-
key_stat = os.stat(key_path)
|
|
382
|
-
if key_stat.st_mode & 0o077:
|
|
383
|
-
logger.warning(
|
|
384
|
-
f'SSH key {key_path} has too open permissions. '
|
|
385
|
-
f'Recommended: chmod 600 {key_path}')
|
|
386
|
-
|
|
387
|
-
# Check if it's a valid private key and read content
|
|
388
|
-
with open(key_path, 'r', encoding='utf-8') as f:
|
|
389
|
-
key_content = f.read()
|
|
390
|
-
if not (key_content.startswith('-----BEGIN') and
|
|
391
|
-
'PRIVATE KEY' in key_content):
|
|
392
|
-
raise exceptions.GitError(
|
|
393
|
-
f'SSH key {key_path} is invalid.')
|
|
394
|
-
|
|
395
|
-
logger.debug(f'Using provided SSH key: {key_path}')
|
|
396
|
-
return (key_path, key_content)
|
|
397
|
-
except Exception as e: # pylint: disable=broad-except
|
|
398
|
-
raise exceptions.GitError(
|
|
399
|
-
f'Validate provided SSH key error: {str(e)}') from e
|
|
400
|
-
|
|
401
|
-
# Step 2: Check SSH config for host-specific configuration
|
|
402
|
-
config_key_path = self._parse_ssh_config()
|
|
403
|
-
if config_key_path:
|
|
404
|
-
try:
|
|
405
|
-
with open(config_key_path, 'r', encoding='utf-8') as f:
|
|
406
|
-
key_content = f.read()
|
|
407
|
-
logger.debug(f'Using SSH key from config: {config_key_path}')
|
|
408
|
-
return (config_key_path, key_content)
|
|
409
|
-
except Exception as e: # pylint: disable=broad-except
|
|
410
|
-
logger.debug(f'Could not read SSH key: {str(e)}')
|
|
411
|
-
|
|
412
|
-
# Step 3: Search for default SSH keys
|
|
413
|
-
ssh_dir = os.path.expanduser('~/.ssh')
|
|
414
|
-
if not os.path.exists(ssh_dir):
|
|
415
|
-
logger.debug('SSH directory ~/.ssh does not exist')
|
|
416
|
-
return None
|
|
417
|
-
|
|
418
|
-
# Common SSH key file names in order of preference
|
|
419
|
-
key_candidates = [
|
|
420
|
-
'id_rsa', # Most common
|
|
421
|
-
'id_ed25519', # Modern, recommended
|
|
422
|
-
]
|
|
423
|
-
|
|
424
|
-
for key_name in key_candidates:
|
|
425
|
-
private_key_path = os.path.join(ssh_dir, key_name)
|
|
426
|
-
|
|
427
|
-
# Check if both private and public keys exist
|
|
428
|
-
if not os.path.exists(private_key_path):
|
|
429
|
-
continue
|
|
430
|
-
|
|
431
|
-
# Check private key permissions
|
|
432
|
-
try:
|
|
433
|
-
key_stat = os.stat(private_key_path)
|
|
434
|
-
if key_stat.st_mode & 0o077:
|
|
435
|
-
logger.warning(
|
|
436
|
-
f'SSH key {private_key_path} has too open permissions. '
|
|
437
|
-
f'Consider: chmod 600 {private_key_path}')
|
|
438
|
-
|
|
439
|
-
# Validate private key format and read content
|
|
440
|
-
with open(private_key_path, 'r', encoding='utf-8') as f:
|
|
441
|
-
key_content = f.read()
|
|
442
|
-
if not (key_content.startswith('-----BEGIN') and
|
|
443
|
-
'PRIVATE KEY' in key_content):
|
|
444
|
-
logger.debug(f'SSH key {private_key_path} is invalid.')
|
|
445
|
-
continue
|
|
446
|
-
|
|
447
|
-
logger.debug(f'Discovered default SSH key: {private_key_path}')
|
|
448
|
-
return (private_key_path, key_content)
|
|
449
|
-
|
|
450
|
-
except Exception as e: # pylint: disable=broad-except
|
|
451
|
-
logger.debug(
|
|
452
|
-
f'Error checking SSH key {private_key_path}: {str(e)}')
|
|
453
|
-
continue
|
|
454
|
-
|
|
455
|
-
logger.debug('No suitable SSH keys found')
|
|
456
|
-
return None
|
|
457
|
-
|
|
458
|
-
def get_ref_type(self) -> GitRefType:
|
|
459
|
-
"""Get the type of the reference.
|
|
460
|
-
|
|
461
|
-
Returns:
|
|
462
|
-
GitRefType.COMMIT if it's a commit hash,
|
|
463
|
-
GitRefType.BRANCH if it's a branch,
|
|
464
|
-
GitRefType.TAG if it's a tag.
|
|
465
|
-
|
|
466
|
-
Raises:
|
|
467
|
-
exceptions.GitError: If the reference is invalid.
|
|
468
|
-
"""
|
|
469
|
-
clone_info = self.get_repo_clone_info()
|
|
470
|
-
git_cmd = git.cmd.Git()
|
|
471
|
-
if clone_info.envs:
|
|
472
|
-
git_cmd.update_environment(**clone_info.envs)
|
|
473
|
-
|
|
474
|
-
try:
|
|
475
|
-
# Get all remote refs
|
|
476
|
-
refs = git_cmd.ls_remote(clone_info.url).split('\n')
|
|
477
|
-
|
|
478
|
-
# Collect all commit hashes from refs
|
|
479
|
-
all_commit_hashes = set()
|
|
480
|
-
|
|
481
|
-
# Check if it's a branch or tag name
|
|
482
|
-
for ref in refs:
|
|
483
|
-
if not ref:
|
|
484
|
-
continue
|
|
485
|
-
hash_val, ref_name = ref.split('\t')
|
|
486
|
-
|
|
487
|
-
# Store the commit hash for later validation
|
|
488
|
-
all_commit_hashes.add(hash_val)
|
|
489
|
-
|
|
490
|
-
# Check if it's a branch
|
|
491
|
-
if ref_name.startswith(
|
|
492
|
-
'refs/heads/') and ref_name[11:] == self.ref:
|
|
493
|
-
return GitRefType.BRANCH
|
|
494
|
-
|
|
495
|
-
# Check if it's a tag
|
|
496
|
-
if ref_name.startswith(
|
|
497
|
-
'refs/tags/') and ref_name[10:] == self.ref:
|
|
498
|
-
return GitRefType.TAG
|
|
499
|
-
|
|
500
|
-
# If we get here, it's not a branch or tag name
|
|
501
|
-
# Check if it looks like a commit hash (hex string)
|
|
502
|
-
if len(self.ref) >= 4 and all(
|
|
503
|
-
c in '0123456789abcdef' for c in self.ref.lower()):
|
|
504
|
-
# First check if it's a complete match with any known commit
|
|
505
|
-
if self.ref in all_commit_hashes:
|
|
506
|
-
logger.debug(f'Found exact commit hash match: {self.ref}')
|
|
507
|
-
return GitRefType.COMMIT
|
|
508
|
-
|
|
509
|
-
# Check if it's a prefix match with any known commit
|
|
510
|
-
matching_commits = [
|
|
511
|
-
h for h in all_commit_hashes if h.startswith(self.ref)
|
|
512
|
-
]
|
|
513
|
-
if len(matching_commits) == 1:
|
|
514
|
-
logger.debug(
|
|
515
|
-
f'Found commit hash prefix match: {self.ref} -> '
|
|
516
|
-
f'{matching_commits[0]}')
|
|
517
|
-
return GitRefType.COMMIT
|
|
518
|
-
elif len(matching_commits) > 1:
|
|
519
|
-
# Multiple matches - ambiguous
|
|
520
|
-
raise exceptions.GitError(
|
|
521
|
-
f'Ambiguous commit hash {self.ref!r}. '
|
|
522
|
-
f'Multiple commits match: '
|
|
523
|
-
f'{", ".join(matching_commits[:5])}...')
|
|
524
|
-
|
|
525
|
-
# If no match found in ls-remote output, we can't verify
|
|
526
|
-
# the commit exists. This could be a valid commit that's
|
|
527
|
-
# not at the tip of any branch/tag. We'll assume it's valid
|
|
528
|
-
# if it looks like a commit hash and let git handle validation
|
|
529
|
-
# during clone.
|
|
530
|
-
logger.debug(f'Commit hash not found in ls-remote output, '
|
|
531
|
-
f'assuming valid: {self.ref}')
|
|
532
|
-
logger.warning(
|
|
533
|
-
f'Cannot verify commit {self.ref} exists - it may be a '
|
|
534
|
-
'commit in history not at any branch/tag tip')
|
|
535
|
-
return GitRefType.COMMIT
|
|
536
|
-
|
|
537
|
-
# If it's not a branch, tag, or hex string, it's invalid
|
|
538
|
-
raise exceptions.GitError(
|
|
539
|
-
f'Git reference {self.ref!r} not found. '
|
|
540
|
-
'Please provide a valid branch, tag, or commit hash.')
|
|
541
|
-
|
|
542
|
-
except git.exc.GitCommandError as e:
|
|
543
|
-
if not (self.git_token or self.git_ssh_key_path):
|
|
544
|
-
raise exceptions.GitError(
|
|
545
|
-
'Failed to check repository. If this is a private '
|
|
546
|
-
'repository, please provide authentication using either '
|
|
547
|
-
'GIT_TOKEN or GIT_SSH_KEY_PATH.') from e
|
|
548
|
-
raise exceptions.GitError(
|
|
549
|
-
f'Failed to check git reference: {str(e)}') from e
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3294],{98418:function(e,s,t){t.d(s,{Z:function(){return a}});/**
|
|
2
|
-
* @license lucide-react v0.407.0 - ISC
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the ISC license.
|
|
5
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/let a=(0,t(60998).Z)("Trash",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}]])},53294:function(e,s,t){t.r(s),t.d(s,{ContextDetails:function(){return U},GPUs:function(){return E},InfrastructureSection:function(){return I}});var a=t(85893),l=t(67294),r=t(55739);t(6135);var n=t(26409),o=t(98418),i=t(13626),d=t(23001),c=t(17853),m=t(32045),u=t(23266),x=t(68969),h=t(29326),g=t(50326),p=t(30803),f=t(42557),y=t(29749),j=t(69123);function b(e){let{isOpen:s,onClose:t,onSave:n,poolData:o=null,isLoading:i=!1}=e,[d,c]=(0,l.useState)(""),[m,u]=(0,l.useState)(""),[x,b]=(0,l.useState)("ubuntu"),[N,v]=(0,l.useState)(null),[w,S]=(0,l.useState)(""),[C,_]=(0,l.useState)({}),k=null!==o;(0,l.useEffect)(()=>{if(k&&o){var e,s,t;c(o.name||""),u(((null===(e=o.config)||void 0===e?void 0:e.hosts)||[]).join("\n")),b((null===(s=o.config)||void 0===s?void 0:s.user)||"ubuntu"),S((null===(t=o.config)||void 0===t?void 0:t.password)||"")}else c(""),u(""),b("ubuntu"),v(null),S("");_({})},[k,o]);let P=()=>{let e={};return d.trim()||(e.poolName="Pool name is required"),m.trim()||(e.hosts="At least one host is required"),x.trim()||(e.sshUser="SSH user is required"),N||w||(e.auth="Either SSH key file or password is required"),_(e),0===Object.keys(e).length},D=async()=>{if(!P())return;let e={hosts:m.split("\n").map(e=>e.trim()).filter(e=>e.length>0),user:x};try{if(N){let s=N.name;await (0,h.hY)(s,N),e.identity_file="~/.sky/ssh_keys/".concat(s)}w&&(e.password=w),n(d,e)}catch(e){console.error("Failed to upload SSH key:",e),_({...C,keyUpload:"Failed to upload SSH key"})}},I=()=>{i||t()};return(0,a.jsx)(g.Vq,{open:s,onOpenChange:I,children:(0,a.jsxs)(g.cZ,{className:"max-w-2xl max-h-[80vh] overflow-y-auto",children:[(0,a.jsx)(g.fK,{children:(0,a.jsx)(g.$N,{children:k?"Edit SSH Node Pool: ".concat(null==o?void 0:o.name):"Add SSH Node Pool"})}),(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsx)(y._,{htmlFor:"poolName",children:"Pool Name"}),(0,a.jsx)(f.I,{id:"poolName",placeholder:"my-ssh-cluster",value:d,onChange:e=>c(e.target.value),disabled:k,className:"placeholder:text-gray-500 ".concat(C.poolName?"border-red-500":"")}),C.poolName&&(0,a.jsx)("p",{className:"text-sm text-red-500",children:C.poolName})]}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsx)(y._,{htmlFor:"hosts",children:"Hosts (one per line)"}),(0,a.jsx)(j.g,{id:"hosts",placeholder:"192.168.1.10\n192.168.1.11\nhostname.example.com",value:m,onChange:e=>u(e.target.value),rows:6,className:"placeholder:text-gray-500 ".concat(C.hosts?"border-red-500":"")}),C.hosts&&(0,a.jsx)("p",{className:"text-sm text-red-500",children:C.hosts})]}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsx)(y._,{htmlFor:"sshUser",children:"SSH User"}),(0,a.jsx)(f.I,{id:"sshUser",placeholder:"ubuntu",value:x,onChange:e=>b(e.target.value),className:"placeholder:text-gray-500 ".concat(C.sshUser?"border-red-500":"")}),C.sshUser&&(0,a.jsx)("p",{className:"text-sm text-red-500",children:C.sshUser})]}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsx)(y._,{htmlFor:"keyFile",children:"SSH Private Key File"}),(0,a.jsx)(f.I,{id:"keyFile",type:"file",accept:".pem,.key,id_rsa,id_ed25519",onChange:e=>{var s;return v((null===(s=e.target.files)||void 0===s?void 0:s[0])||null)},className:"border-0 bg-transparent p-0 shadow-none focus:ring-0 file:mr-2 file:text-sm file:py-1 file:px-3 file:border file:border-gray-300 file:rounded file:bg-gray-50 hover:file:bg-gray-100 file:cursor-pointer"}),C.keyUpload&&(0,a.jsx)("p",{className:"text-sm text-red-500",children:C.keyUpload})]}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsx)(y._,{htmlFor:"password",children:"Password (optional, if sudo requires a password)"}),(0,a.jsx)(f.I,{id:"password",type:"password",placeholder:"Leave empty if using passwordless sudo",value:w,onChange:e=>S(e.target.value),className:"placeholder:text-gray-500"})]}),C.auth&&(0,a.jsx)("p",{className:"text-sm text-red-500",children:C.auth})]}),(0,a.jsxs)(g.cN,{children:[(0,a.jsx)(p.z,{variant:"outline",onClick:I,disabled:i,children:"Cancel"}),(0,a.jsx)(p.z,{onClick:D,disabled:i,className:"bg-blue-600 hover:bg-blue-700 text-white disabled:bg-gray-300 disabled:text-gray-500",children:i?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.Z,{size:16,className:"mr-2"}),"Saving..."]}):k?"Update Pool":"Create Pool"})]})]})})}var N=t(6378),v=t(36856),w=t(51214),S=t(11163),C=t(41664),_=t.n(C),k=t(36989);t(37673);let P=w.nb.REFRESH_INTERVAL,D=w.MO.NAME_TRUNCATE_LENGTH;function I(e){let{title:s,isLoading:t,isDataLoaded:l,contexts:n,gpus:o,groupedPerContextGPUs:i,groupedPerNodeGPUs:d,handleContextClick:c,contextStats:m={},jobsData:u={},isJobsDataLoading:x=!0,isSSH:h=!1,actionButton:g=null}=e,p=n||[];return t&&0===p.length?(0,a.jsx)("div",{className:"rounded-lg border bg-card text-card-foreground shadow-sm mb-6",children:(0,a.jsxs)("div",{className:"p-5",children:[(0,a.jsx)("h3",{className:"text-lg font-semibold mb-4",children:s}),(0,a.jsxs)("div",{className:"flex items-center justify-center py-6",children:[(0,a.jsx)(r.Z,{size:24,className:"mr-3"}),(0,a.jsxs)("span",{className:"text-gray-500",children:["Loading ",s,"..."]})]})]})}):l&&0===p.length?(0,a.jsx)("div",{className:"rounded-lg border bg-card text-card-foreground shadow-sm mb-6",children:(0,a.jsxs)("div",{className:"p-5",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between mb-4",children:[(0,a.jsx)("h3",{className:"text-lg font-semibold",children:s}),g]}),(0,a.jsxs)("p",{className:"text-sm text-gray-500",children:["No ",s," found or ",s," is not configured."]})]})}):p.length>0?(0,a.jsx)("div",{className:"rounded-lg border bg-card text-card-foreground shadow-sm mb-6",children:(0,a.jsxs)("div",{className:"p-5",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between mb-4",children:[(0,a.jsxs)("div",{className:"flex items-center",children:[(0,a.jsx)("h3",{className:"text-lg font-semibold",children:s}),(0,a.jsxs)("span",{className:"ml-2 px-2 py-0.5 bg-blue-100 text-blue-800 rounded-full text-xs font-medium",children:[p.length," ",1===p.length?h?"pool":"context":h?"pools":"contexts"]})]}),g]}),(0,a.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-6",children:[(0,a.jsx)("div",{children:(0,a.jsx)("div",{className:"overflow-x-auto rounded-md border border-gray-200 shadow-sm bg-white",children:(0,a.jsxs)("table",{className:"min-w-full text-sm",children:[(0,a.jsx)("thead",{className:"bg-gray-50",children:(0,a.jsxs)("tr",{children:[(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-1/4",children:h?"Node Pool":"Context"}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-1/8",children:"Clusters"}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-1/8",children:"Jobs"}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-1/8",children:"Nodes"}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-1/4",children:"GPU Types"}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-1/8",children:"#GPUs"})]})}),(0,a.jsx)("tbody",{className:"bg-white divide-y divide-gray-200 ".concat(p.length>5?"max-h-[250px] overflow-y-auto block":""),children:p.map(e=>{var s,t;let n=i[e]||[],o=d[e]||[],g=n.reduce((e,s)=>e+(s.gpu_total||0),0),p=h?"ssh/".concat(e.replace(/^ssh-/,"")):"kubernetes/".concat(e),f=m[p]||{clusters:0,jobs:0},y=Object.keys(m).length>0&&m[p]||l,j=i&&Object.keys(i).length>0||l,b=d&&Object.keys(d).length>0||l,N=j?Object.keys(n.reduce((e,s)=>(e[s.gpu_name]=(e[s.gpu_name]||0)+(s.gpu_total||0),e),{})).join(", "):null,v=h?e.replace(/^ssh-/,""):e;return(0,a.jsxs)("tr",{className:"hover:bg-gray-50",children:[(0,a.jsx)("td",{className:"p-3",children:(0,a.jsx)(k.Md,{content:v,className:"text-sm text-muted-foreground",children:(0,a.jsx)("span",{className:"text-blue-600 hover:underline cursor-pointer",onClick:()=>c(e),children:v.length>D?"".concat(v.substring(0,Math.floor((D-3)/2)),"...").concat(v.substring(v.length-Math.ceil((D-3)/2))):v})})}),(0,a.jsx)("td",{className:"p-3",children:y?f.clusters>0?(0,a.jsx)("span",{className:"px-2 py-0.5 bg-blue-100 text-blue-800 rounded text-xs font-medium",children:f.clusters}):(0,a.jsx)("span",{className:"px-2 py-0.5 bg-gray-100 text-gray-500 rounded text-xs font-medium",children:"0"}):(0,a.jsx)("div",{className:"flex items-center justify-center",children:(0,a.jsx)(r.Z,{size:12})})}),(0,a.jsx)("td",{className:"p-3",children:x?(0,a.jsx)("span",{className:"px-2 py-0.5 bg-gray-100 text-gray-500 rounded text-xs font-medium",children:(0,a.jsx)(r.Z,{size:12})}):(null===(s=u[p])||void 0===s?void 0:s.jobs)?(0,a.jsx)("span",{className:"px-2 py-0.5 bg-green-100 text-green-800 rounded text-xs font-medium",children:null===(t=u[p])||void 0===t?void 0:t.jobs}):(0,a.jsx)("span",{className:"px-2 py-0.5 bg-gray-100 text-gray-500 rounded text-xs font-medium",children:"0"})}),(0,a.jsx)("td",{className:"p-3",children:b?o.length:(0,a.jsx)("div",{className:"flex items-center justify-center",children:(0,a.jsx)(r.Z,{size:16})})}),(0,a.jsx)("td",{className:"p-3",children:j?N||"-":(0,a.jsx)("div",{className:"flex items-center justify-center",children:(0,a.jsx)(r.Z,{size:16})})}),(0,a.jsx)("td",{className:"p-3",children:j?g:(0,a.jsx)("div",{className:"flex items-center justify-center",children:(0,a.jsx)(r.Z,{size:16})})})]},e)})})]})})}),o&&o.length>0&&(0,a.jsx)("div",{children:(0,a.jsx)("div",{className:"overflow-x-auto rounded-md border border-gray-200 shadow-sm bg-white",children:(0,a.jsxs)("table",{className:"min-w-full text-sm",children:[(0,a.jsx)("thead",{className:"bg-gray-50",children:(0,a.jsxs)("tr",{children:[(0,a.jsxs)("th",{className:"p-3 text-left font-medium text-gray-600 w-1/4 whitespace-nowrap",children:["GPU",(0,a.jsxs)("span",{className:"ml-2 px-2 py-0.5 bg-green-100 text-green-800 rounded-full text-xs font-medium whitespace-nowrap",children:[o.reduce((e,s)=>e+s.gpu_free,0)," ","of"," ",o.reduce((e,s)=>e+s.gpu_total,0)," ","free"]})]}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-1/4",children:"Requestable"}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-1/2",children:(0,a.jsx)("div",{className:"flex items-center",children:(0,a.jsx)("span",{children:"Utilization"})})})]})}),(0,a.jsx)("tbody",{className:"bg-white divide-y divide-gray-200 ".concat(o.length>5?"max-h-[250px] overflow-y-auto block":""),children:o.map(e=>{let s=e.gpu_total-e.gpu_free,t=e.gpu_total>0?e.gpu_free/e.gpu_total*100:0,l=e.gpu_total>0?s/e.gpu_total*100:0,r=i?Object.values(i).flat().filter(s=>s.gpu_name===e.gpu_name&&(h?s.context.startsWith("ssh-"):!s.context.startsWith("ssh-"))).map(e=>e.gpu_requestable_qty_per_node).filter((e,s,t)=>t.indexOf(e)===s).join(", "):"-";return(0,a.jsxs)("tr",{children:[(0,a.jsx)("td",{className:"p-3 font-medium w-24 whitespace-nowrap",children:e.gpu_name}),(0,a.jsxs)("td",{className:"p-3 text-xs text-gray-600",children:[r||"-"," / node"]}),(0,a.jsx)("td",{className:"p-3 w-2/3",children:(0,a.jsx)("div",{className:"flex items-center gap-3",children:(0,a.jsxs)("div",{className:"flex-1 bg-gray-100 rounded-md h-5 flex overflow-hidden shadow-sm min-w-[100px] w-full",children:[l>0&&(0,a.jsx)("div",{style:{width:"".concat(l,"%")},className:"bg-yellow-500 h-full flex items-center justify-center text-white text-xs font-medium",children:l>15&&"".concat(s," used")}),t>0&&(0,a.jsx)("div",{style:{width:"".concat(t,"%")},className:"bg-green-700 h-full flex items-center justify-center text-white text-xs font-medium",children:t>15&&"".concat(e.gpu_free," free")})]})})})]},e.gpu_name)})})]})})})]})]})}):null}function U(e){let{contextName:s,gpusInContext:t,nodesInContext:n}=e,o=s.startsWith("ssh-"),[i,d]=(0,l.useState)([]),[m,u]=(0,l.useState)("$__all"),[x,h]=(0,l.useState)({from:"now-1h",to:"now"}),[g,p]=(0,l.useState)(!1),[f,y]=(0,l.useState)(!1);(0,l.useEffect)(()=>{(async()=>{y(await (0,c.TO)())})()},[]);let j=(0,l.useCallback)(async()=>{if(f){p(!0);try{let e=(0,c.ki)(),t="in-cluster"===s?"^$":s,a="query="+encodeURIComponent('group by (node) (DCGM_FI_DEV_GPU_TEMP{cluster=~"'.concat(t,'"})')),l="/api/datasources/proxy/1/api/v1/query?".concat(a);try{let s=await fetch("".concat(e).concat(l),{method:"GET",credentials:"include",headers:{Accept:"application/json"}});if(s.ok){let e=await s.json();if(e.data&&e.data.result&&e.data.result.length>0){let s=e.data.result.map(e=>e.metric.node).filter(Boolean).sort();d(s),console.log("Successfully fetched hosts for cluster ".concat(t||"in-cluster",":"),s)}else console.log("No nodes found for this cluster"),d([])}else console.log("HTTP ".concat(s.status," from ").concat(l,": ").concat(s.statusText)),d([])}catch(e){console.log("Failed to fetch from ".concat(l,":"),e),d([])}}catch(e){console.error("Error fetching available hosts:",e),d([])}finally{p(!1)}}},[f,s]);(0,l.useEffect)(()=>{f&&n&&n.length>0&&j()},[n,f,j]);let b=e=>{let t=(0,c.ki)(),a="in-cluster"===s?"^$":s;return"".concat(t,"/d-solo/skypilot-dcgm-cluster-dashboard/skypilot-dcgm-kubernetes-cluster-dashboard?orgId=1&timezone=browser&var-datasource=prometheus&var-host=").concat(encodeURIComponent(m),"&var-gpu=$__all&var-cluster=").concat(encodeURIComponent(a),"&refresh=5s&theme=light&from=").concat(encodeURIComponent(x.from),"&to=").concat(encodeURIComponent(x.to),"&panelId=").concat(e,"&__feature.dashboardSceneSolo")},N=e=>{h({"15m":{from:"now-15m",to:"now"},"1h":{from:"now-1h",to:"now"},"6h":{from:"now-6h",to:"now"},"24h":{from:"now-24h",to:"now"},"7d":{from:"now-7d",to:"now"}}[e])};return(0,a.jsx)("div",{className:"mb-4",children:(0,a.jsx)("div",{className:"rounded-lg border bg-card text-card-foreground shadow-sm h-full",children:(0,a.jsxs)("div",{className:"p-5",children:[(0,a.jsx)("div",{className:"flex items-center justify-between mb-4",children:(0,a.jsx)("h4",{className:"text-lg font-semibold",children:"Available GPUs"})}),(0,a.jsx)("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6",children:t.map(e=>{let s=e.gpu_total-e.gpu_free,t=e.gpu_total>0?e.gpu_free/e.gpu_total*100:0,l=e.gpu_total>0?s/e.gpu_total*100:0;return(0,a.jsxs)("div",{className:"p-3 bg-gray-50 rounded-md border border-gray-200 shadow-sm",children:[(0,a.jsxs)("div",{className:"flex justify-between items-center mb-1.5 flex-wrap",children:[(0,a.jsxs)("div",{className:"font-medium text-gray-800 text-sm",children:[e.gpu_name,(0,a.jsxs)("span",{className:"text-xs text-gray-500 ml-2",children:["(Requestable: ",e.gpu_requestable_qty_per_node," / node)"]})]}),(0,a.jsxs)("span",{className:"text-xs font-medium",children:[e.gpu_free," free / ",e.gpu_total," total"]})]}),(0,a.jsxs)("div",{className:"w-full bg-gray-100 rounded-md h-4 flex overflow-hidden shadow-sm",children:[l>0&&(0,a.jsx)("div",{style:{width:"".concat(l,"%")},className:"bg-yellow-500 h-full flex items-center justify-center text-white text-xs",children:l>15&&"".concat(s," used")}),t>0&&(0,a.jsx)("div",{style:{width:"".concat(t,"%")},className:"bg-green-700 h-full flex items-center justify-center text-white text-xs",children:t>15&&"".concat(e.gpu_free," free")})]})]},e.gpu_name)})}),n&&n.length>0&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("h4",{className:"text-lg font-semibold mb-4",children:"Nodes"}),(0,a.jsx)("div",{className:"overflow-x-auto rounded-md border border-gray-200 shadow-sm",children:(0,a.jsxs)("table",{className:"min-w-full text-sm",children:[(0,a.jsx)("thead",{className:"bg-gray-100",children:(0,a.jsxs)("tr",{children:[(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600",children:"Node"}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600",children:"IP Address"}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600",children:"GPU"}),(0,a.jsx)("th",{className:"p-3 text-right font-medium text-gray-600",children:"Availability"})]})}),(0,a.jsx)("tbody",{className:"bg-white divide-y divide-gray-200",children:n.map((e,s)=>(0,a.jsxs)("tr",{className:"hover:bg-gray-50",children:[(0,a.jsx)("td",{className:"p-3 whitespace-nowrap text-gray-700",children:e.node_name}),(0,a.jsx)("td",{className:"p-3 whitespace-nowrap text-gray-700",children:e.ip_address||"-"}),(0,a.jsx)("td",{className:"p-3 whitespace-nowrap text-gray-700",children:e.gpu_name}),(0,a.jsx)("td",{className:"p-3 whitespace-nowrap text-right text-gray-700",children:"".concat(e.gpu_free," of ").concat(e.gpu_total," free")})]},"".concat(e.node_name,"-").concat(s)))})]})})]}),f&&t&&t.length>0&&!o&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("h4",{className:"text-lg font-semibold mb-4 mt-6",children:"GPU Metrics"}),(0,a.jsxs)("div",{className:"mb-4 p-4 bg-gray-50 rounded-md border border-gray-200",children:[(0,a.jsxs)("div",{className:"flex flex-col sm:flex-row gap-4 items-start sm:items-center",children:[n&&n.length>0&&(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)("label",{htmlFor:"host-select",className:"text-sm font-medium text-gray-700 whitespace-nowrap",children:"Node:"}),(0,a.jsxs)("select",{id:"host-select",value:m,onChange:e=>{u(e.target.value)},disabled:g,className:"px-3 py-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-sky-blue focus:border-transparent",children:[(0,a.jsx)("option",{value:"$__all",children:"All Nodes"}),i.map(e=>(0,a.jsx)("option",{value:e,children:e},e))]}),g&&(0,a.jsx)("div",{className:"ml-2",children:(0,a.jsx)(r.Z,{size:16})})]}),(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)("label",{className:"text-sm font-medium text-gray-700 whitespace-nowrap",children:"Time Range:"}),(0,a.jsx)("div",{className:"flex gap-1",children:[{label:"15m",value:"15m"},{label:"1h",value:"1h"},{label:"6h",value:"6h"},{label:"24h",value:"24h"},{label:"7d",value:"7d"}].map(e=>(0,a.jsx)("button",{onClick:()=>N(e.value),className:"px-2 py-1 text-xs font-medium rounded border transition-colors ".concat(x.from==="now-".concat(e.value)&&"now"===x.to?"bg-sky-blue text-white border-sky-blue":"bg-white text-gray-600 border-gray-300 hover:bg-gray-50"),children:e.label},e.value))})]})]}),(0,a.jsx)("div",{className:"mt-2 text-xs text-gray-500",children:n&&n.length>0?(0,a.jsxs)(a.Fragment,{children:["Showing:"," ","$__all"===m?"All nodes":m," ","• Time: ",x.from," to ",x.to,i.length>0&&(0,a.jsxs)("span",{children:[" ","• ",i.length," nodes available"]})]}):(0,a.jsxs)(a.Fragment,{children:["Cluster:"," ",o?s.replace(/^ssh-/,""):s," ","• Time: ",x.from," to ",x.to," • Showing metrics for all nodes in cluster"]})})]}),(0,a.jsxs)("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-4",children:[(0,a.jsx)("div",{className:"bg-white rounded-md border border-gray-200 shadow-sm",children:(0,a.jsx)("div",{className:"p-2",children:(0,a.jsx)("iframe",{src:b("6"),width:"100%",height:"400",frameBorder:"0",title:"GPU Utilization",className:"rounded"},"gpu-util-".concat(m,"-").concat(x.from,"-").concat(x.to))})}),(0,a.jsx)("div",{className:"bg-white rounded-md border border-gray-200 shadow-sm",children:(0,a.jsx)("div",{className:"p-2",children:(0,a.jsx)("iframe",{src:b("18"),width:"100%",height:"400",frameBorder:"0",title:"GPU Memory",className:"rounded"},"gpu-memory-".concat(m,"-").concat(x.from,"-").concat(x.to))})}),(0,a.jsx)("div",{className:"bg-white rounded-md border border-gray-200 shadow-sm",children:(0,a.jsx)("div",{className:"p-2",children:(0,a.jsx)("iframe",{src:b("10"),width:"100%",height:"400",frameBorder:"0",title:"GPU Power Consumption",className:"rounded"},"gpu-power-".concat(m,"-").concat(x.from,"-").concat(x.to))})})]})]})]})})})}function H(e){var s;let{poolName:t,gpusInContext:i,nodesInContext:d,handleDeploySSHPool:c,handleEditSSHPool:m,handleDeleteSSHPool:u,poolConfig:x}=e,[f,y]=(0,l.useState)(null),[j,b]=(0,l.useState)(!0),[N,v]=(0,l.useState)({isOpen:!1,action:null,loading:!1}),[w,S]=(0,l.useState)({isOpen:!1,logs:"",isStreaming:!1,deploymentComplete:!1,deploymentSuccess:!1,requestId:null});(0,l.useEffect)(()=>{(async()=>{try{b(!0);let e=await (0,h.IS)(t);y(e)}catch(e){console.error("Failed to fetch SSH Node Pool status:",e),y({pool_name:t,status:"Error",reason:"Failed to fetch status"})}finally{b(!1)}})()},[t]);let{deployDisabled:C}=(()=>{if(!f)return{deployDisabled:!0};let e=f.status;return"Ready"===e?{deployDisabled:!0}:"Error"===e?{deployDisabled:!0}:{deployDisabled:!1}})(),_=async()=>{v({...N,loading:!0});try{if("deploy"===N.action){v({isOpen:!1,action:null,loading:!1}),S({isOpen:!0,logs:"",isStreaming:!0,deploymentComplete:!1,deploymentSuccess:!1,requestId:null});try{let e=(await c(t)).request_id;S(s=>({...s,requestId:e}));let s=new AbortController;await (0,h.wJ)({requestId:e,signal:s.signal,onNewLog:e=>{S(s=>({...s,logs:s.logs+e}))}}),S(e=>({...e,isStreaming:!1,deploymentComplete:!0,deploymentSuccess:!0})),setTimeout(async()=>{(async()=>{try{let e=await (0,h.IS)(t);y(e)}catch(e){console.error("Failed to fetch SSH Node Pool status after deployment:",e)}})()},1e3)}catch(e){console.error("Deployment failed:",e),S(s=>({...s,isStreaming:!1,deploymentComplete:!0,deploymentSuccess:!1,logs:s.logs+"\nDeployment failed: ".concat(e.message)}))}}else if("delete"===N.action){v({isOpen:!1,action:null,loading:!1}),S({isOpen:!0,logs:"",isStreaming:!0,deploymentComplete:!1,deploymentSuccess:!1,requestId:null});try{let e=(await (0,h.ez)(t)).request_id;S(s=>({...s,requestId:e})),e&&await (0,h.mF)({requestId:e,signal:null,onNewLog:e=>{S(s=>({...s,logs:s.logs+e}))},operationType:"down"}),await u(t),S(e=>({...e,isStreaming:!1,deploymentComplete:!0,deploymentSuccess:!0,logs:e.logs+"\nSSH Node Pool teardown completed successfully."}))}catch(e){console.error("Down operation failed:",e),S(s=>({...s,isStreaming:!1,deploymentComplete:!0,deploymentSuccess:!1,logs:s.logs+"\nTeardown failed: ".concat(e.message)}))}}}catch(e){console.error("Action failed:",e),v({...N,loading:!1})}},k=()=>{v({isOpen:!1,action:null,loading:!1})},P=()=>{S({isOpen:!1,logs:"",isStreaming:!1,deploymentComplete:!1,deploymentSuccess:!1,requestId:null}),w.deploymentComplete&&setTimeout(()=>{(async()=>{try{let e=await (0,h.IS)(t);y(e)}catch(e){console.error("Failed to refresh status:",e)}})()},1e3)},D="deploy"===N.action?{title:"Deploy SSH Node Pool",description:'Are you sure you want to deploy SSH Node Pool "'.concat(t,'"?'),details:["• Set up SkyPilot runtime on the configured SSH hosts","• Install required components and dependencies","• Make the node pool available for workloads","","This process may take a few minutes to complete."]}:{title:"Delete SSH Node Pool",description:'Are you sure you want to delete SSH Node Pool "'.concat(t,'"?'),details:["• Clean up any deployed resources","• Remove the SSH Node Pool configuration"]};return(0,a.jsxs)("div",{children:[(0,a.jsx)("div",{className:"mb-6",children:(0,a.jsxs)("div",{className:"rounded-lg border bg-card text-card-foreground shadow-sm",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between px-4 pt-4",children:[(0,a.jsx)("h3",{className:"text-lg font-semibold",children:"SSH Node Pool Details"}),(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsxs)("button",{className:"px-3 py-1 text-sm border rounded flex items-center ".concat(C?"border-gray-300 bg-gray-100 text-gray-400 cursor-not-allowed":"border-green-300 bg-green-50 text-green-700 hover:bg-green-100"),onClick:C?void 0:()=>{v({isOpen:!0,action:"deploy",loading:!1})},disabled:C,children:[(0,a.jsx)(n.Z,{className:"w-4 h-4 mr-2"}),"Deploy"]}),(0,a.jsxs)("button",{className:"px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50 flex items-center text-red-600 hover:text-red-700",onClick:()=>{v({isOpen:!0,action:"delete",loading:!1})},children:[(0,a.jsx)(o.Z,{className:"w-4 h-4 mr-2"}),"Delete"]})]})]}),(0,a.jsx)("div",{className:"p-4",children:(0,a.jsxs)("div",{className:"grid grid-cols-2 gap-6",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("div",{className:"text-gray-600 font-medium text-base",children:"Pool Name"}),(0,a.jsx)("div",{className:"text-base mt-1",children:t})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("div",{className:"text-gray-600 font-medium text-base",children:"Nodes"}),(0,a.jsx)("div",{className:"text-base mt-1",children:d?d.length:0})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("div",{className:"text-gray-600 font-medium text-base",children:"Status"}),(0,a.jsx)("div",{className:"text-base mt-1",children:j?(0,a.jsxs)("div",{className:"flex items-center",children:[(0,a.jsx)(r.Z,{size:16,className:"mr-2"}),(0,a.jsx)("span",{className:"text-gray-500",children:"Loading..."})]}):f?(0,a.jsx)(e=>{let{status:s,reason:t}=e,l="Ready"===s,r="Not Ready"===s?"Click Deploy to set up this node pool":t;return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,a.jsx)("span",{className:"px-2 py-0.5 rounded text-xs font-medium ".concat(l?"bg-green-100":"bg-red-100"," ").concat(l?"text-green-800":"text-red-800"),children:s}),!l&&r&&(0,a.jsxs)("span",{className:"text-sm text-gray-600",children:["(",r,")"]})]})},{status:f.status,reason:f.reason}):(0,a.jsx)("span",{className:"text-gray-500",children:"Unknown"})})]})]})})]})}),(0,a.jsx)(U,{contextName:"ssh-".concat(t),gpusInContext:i,nodesInContext:d}),(0,a.jsx)(g.Vq,{open:N.isOpen,onOpenChange:k,children:(0,a.jsxs)(g.cZ,{className:"sm:max-w-md",children:[(0,a.jsxs)(g.fK,{className:"",children:[(0,a.jsx)(g.$N,{className:"",children:D.title}),(0,a.jsx)(g.Be,{className:"",children:D.description})]}),(0,a.jsx)("div",{className:"py-4",children:(0,a.jsxs)("div",{className:"text-sm text-gray-600 space-y-1",children:[(0,a.jsx)("p",{className:"font-medium mb-2",children:"This will:"}),D.details.map((e,s)=>(0,a.jsx)("p",{className:""===e?"pt-2":"",children:e},s))]})}),(0,a.jsxs)(g.cN,{className:"",children:[(0,a.jsx)(p.z,{variant:"outline",onClick:k,disabled:N.loading,className:"",children:"Cancel"}),(0,a.jsx)(p.z,{onClick:_,disabled:N.loading,className:"deploy"===N.action?"bg-green-600 hover:bg-green-700 text-white":"bg-red-600 hover:bg-red-700 text-white",children:N.loading?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.Z,{size:16,className:"mr-2"}),"deploy"===N.action?"Deploying...":"Deleting..."]}):"deploy"===N.action?"Deploy":"Delete"})]})]})}),(0,a.jsx)(g.Vq,{open:w.isOpen,onOpenChange:w.isStreaming?void 0:P,children:(0,a.jsxs)(g.cZ,{className:"sm:max-w-4xl max-h-[80vh]",children:[(0,a.jsxs)(g.fK,{className:"",children:[(0,a.jsxs)(g.$N,{className:"",children:["Deploying SSH Node Pool: ",t]}),(0,a.jsx)(g.Be,{className:"",children:w.isStreaming?"Deployment in progress. Do not close this dialog.":w.deploymentSuccess?"Deployment completed successfully!":"Deployment completed with errors."})]}),(0,a.jsx)("div",{className:"py-4",children:(0,a.jsxs)("div",{className:"bg-black text-green-400 p-4 rounded-md font-mono text-sm max-h-96 overflow-y-auto",children:[(0,a.jsx)("pre",{className:"whitespace-pre-wrap",children:(s=w.logs)?s.split("\n").map(e=>(e=e.replace(/\x1b\[[0-9;]*m/g,"")).match(/^D \d{2}-\d{2} \d{2}:\d{2}:\d{2}/)?null:e=(e=e.replace(/├──/g,"├─")).replace(/└──/g,"└─")).filter(e=>null!==e&&""!==e.trim()).join("\n"):""}),w.isStreaming&&(0,a.jsxs)("div",{className:"flex items-center mt-2",children:[(0,a.jsx)(r.Z,{size:16,className:"mr-2 text-green-400"}),(0,a.jsx)("span",{className:"text-green-400",children:"Streaming logs..."})]})]})}),(0,a.jsx)(g.cN,{className:"",children:(0,a.jsx)(p.z,{onClick:P,disabled:w.isStreaming,className:w.deploymentSuccess?"bg-green-600 hover:bg-green-700 text-white":w.deploymentComplete&&!w.deploymentSuccess?"bg-red-600 hover:bg-red-700 text-white":"bg-gray-600 hover:bg-gray-700 text-white",children:w.isStreaming?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.Z,{size:16,className:"mr-2"}),"Deploying..."]}):"Close"})})]})})]})}function E(){let[e,s]=(0,l.useState)(!0),[t,n]=(0,l.useState)(!0),[o,c]=(0,l.useState)(!0),g=l.useRef(null),p=(0,d.X)(),[f,y]=(0,l.useState)(!1),[j,w]=(0,l.useState)(!1),C=(0,S.useRouter)(),[k,D]=(0,l.useState)([]),[E,F]=(0,l.useState)([]),[R,A]=(0,l.useState)([]),[q,z]=(0,l.useState)([]),[Z,M]=(0,l.useState)([]),[O,L]=(0,l.useState)(0),[T,G]=(0,l.useState)(0),[W,K]=(0,l.useState)({}),[J,V]=(0,l.useState)({}),[$,B]=(0,l.useState)(!1),[X,Y]=(0,l.useState)(null),[Q,ee]=(0,l.useState)(!1),[es,et]=(0,l.useState)(!0),[ea,el]=(0,l.useState)({}),[er,en]=(0,l.useState)(null),eo=l.useCallback(async function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{showLoadingIndicators:!0},{showLoadingIndicators:t=!0,forceRefresh:a=!1}=e;t&&(s(!0),n(!0),ee(!0),et(!0));try{async function l(e){await ei(e),await em(e)}await Promise.all([l(a),ec(a),ed(a)])}catch(e){console.error("Error in fetchData:",e),D([]),F([]),A([]),z([]),K({}),y(!0),s(!1),M([]),L(0),G(0),w(!0),V({}),ee(!1),el({}),et(!1)}finally{t&&(s(!1),n(!1),ee(!1),et(!1)),o&&t&&c(!1)}},[o]),ei=async e=>{try{let t=e?await (0,m.Cu)():await N.default.get(m.Cu);if(t){let{allContextNames:e,allGPUs:a,perContextGPUs:l,perNodeGPUs:r,contextStats:n}=t;D(e||[]),F(a||[]),A(l||[]),z(r||[]),K(n||{}),y(!0),s(!1)}else null===t&&(D([]),F([]),A([]),z([]),K({}),y(!0),s(!1))}catch(e){console.error("Error in fetchKubernetesData:",e),D([]),F([]),A([]),z([]),K({}),y(!0),s(!1)}},ed=async e=>{try{let s=e?await (0,x.getManagedJobs)({allUsers:!0}):await N.default.get(x.getManagedJobs,[{allUsers:!0}]),t=(null==s?void 0:s.jobs)||[];el(await (0,m.R8)(t)),et(!1)}catch(e){console.error("Error in fetchManagedJobsData:",e),el({}),et(!1)}},ec=async e=>{try{let s=e?await (0,m.ef)():await N.default.get(m.ef,[e]);s?(M(s.clouds||[]),L(s.totalClouds||0),G(s.enabledClouds||0),w(!0)):null===s&&(M([]),L(0),G(0),w(!0))}catch(e){console.error("Error in fetchCloudData:",e),M([]),L(0),G(0),w(!0)}},em=async e=>{try{let s=e?await (0,h.It)():await N.default.get(h.It);V(s),ee(!1)}catch(e){console.error("Failed to fetch SSH Node Pools:",e),V({}),ee(!1)}},eu=(e,s)=>{Y({name:e,config:s}),B(!0)},ex=async e=>{try{await (0,h.MV)(e),await em(),en(null),C.push("/infra")}catch(e){throw console.error("Failed to delete SSH Node Pool:",e),e}},eh=async e=>{try{await (0,h._x)(e)}catch(e){throw console.error("Failed to deploy SSH Node Pool:",e),e}},eg=async(e,s)=>{ee(!0);try{let t={...J};t[e]=s,await (0,h.Ri)(t),await em(),B(!1)}catch(e){console.error("Failed to save SSH Node Pool:",e),alert("Failed to save SSH Node Pool. Please try again.")}finally{ee(!1)}};(0,l.useEffect)(()=>{g.current=eo},[eo]),(0,l.useEffect)(()=>{(async()=>{await v.ZP.preloadForPage("infra"),eo({showLoadingIndicators:!0})})()},[eo]),(0,l.useEffect)(()=>{let e=!0,s=setInterval(()=>{e&&g.current&&g.current({showLoadingIndicators:!1})},P);return()=>{e=!1,clearInterval(s)}},[]),(0,l.useEffect)(()=>()=>{y(!1),w(!1),ee(!1),c(!0),et(!1)},[]);let ep=()=>{N.default.invalidate(u.getClusters),N.default.invalidate(x.getManagedJobs,[{allUsers:!0}]),N.default.invalidate(m.Cu),N.default.invalidate(m.ef,[!1]),N.default.invalidate(h.It),g.current&&g.current({showLoadingIndicators:!0,forceRefresh:!0})};(0,l.useEffect)(()=>{let e=e=>{(e.metaKey||e.ctrlKey)&&"r"===e.key&&(e.preventDefault(),ep())};return window.addEventListener("keydown",e),()=>{window.removeEventListener("keydown",e)}},[]),(E||[]).length,(E||[]).reduce((e,s)=>e+s.gpu_total,0),(E||[]).reduce((e,s)=>e+s.gpu_free,0);let ef=l.useMemo(()=>R?R.reduce((e,s)=>{let{context:t}=s;return e[t]||(e[t]=[]),e[t].push(s),e},{}):{},[R]),ey=l.useMemo(()=>k&&Array.isArray(k)?k.filter(e=>e.startsWith("ssh-")):[],[k]),ej=l.useMemo(()=>k&&Array.isArray(k)?k.filter(e=>!e.startsWith("ssh-")):[],[k]),eb=l.useMemo(()=>{if(!R||!E)return[];let e=new Set;return R.forEach(s=>{s.context.startsWith("ssh-")&&e.add(s.gpu_name)}),E.filter(s=>e.has(s.gpu_name))},[E,R]),eN=l.useMemo(()=>{if(!R||!E)return[];let e=new Set;return R.forEach(s=>{s.context.startsWith("ssh-")||e.add(s.gpu_name)}),E.filter(s=>e.has(s.gpu_name))},[E,R]),ev=l.useMemo(()=>q?q.reduce((e,s)=>{let{context:t}=s;return e[t]||(e[t]=[]),e[t].push(s),e},{}):{},[q]);(0,l.useEffect)(()=>{C.isReady&&C.query.context&&en(decodeURIComponent(Array.isArray(C.query.context)?C.query.context[0]:C.query.context))},[C.isReady,C.query.context]);let ew=e=>{en(e),C.push("/infra/".concat(encodeURIComponent(e)))},eS=s=>{let t=ef[s]||[],l=ev[s]||[];if(e&&!f)return(0,a.jsxs)("div",{className:"flex flex-col items-center justify-center h-64",children:[(0,a.jsx)(r.Z,{size:32,className:"mb-4"}),(0,a.jsx)("span",{className:"text-gray-500 text-lg",children:"Loading Context..."})]});if(s.startsWith("ssh-")){let e=s.replace(/^ssh-/,"");return(0,a.jsx)(H,{poolName:e,gpusInContext:t,nodesInContext:l,handleDeploySSHPool:eh,handleEditSSHPool:eu,handleDeleteSSHPool:ex,poolConfig:J[e]})}return(0,a.jsx)(U,{contextName:s,gpusInContext:t,nodesInContext:l})},eC=()=>t&&(!Z||0===Z.length)?(0,a.jsx)("div",{className:"rounded-lg border bg-card text-card-foreground shadow-sm mb-6",children:(0,a.jsxs)("div",{className:"p-5",children:[(0,a.jsx)("h3",{className:"text-lg font-semibold mb-4",children:"Cloud"}),(0,a.jsxs)("div",{className:"flex items-center justify-center py-6",children:[(0,a.jsx)(r.Z,{size:24,className:"mr-3"}),(0,a.jsx)("span",{className:"text-gray-500",children:"Loading Cloud..."})]})]})}):(0,a.jsx)("div",{className:"rounded-lg border bg-card text-card-foreground shadow-sm mb-6",children:(0,a.jsxs)("div",{className:"p-5",children:[(0,a.jsxs)("div",{className:"flex items-center mb-4",children:[(0,a.jsx)("h3",{className:"text-lg font-semibold",children:"Cloud"}),(0,a.jsxs)("span",{className:"ml-2 px-2 py-0.5 bg-blue-100 text-blue-800 rounded-full text-xs font-medium",children:[T," of ",O," enabled"]})]}),Z&&0!==Z.length?(0,a.jsx)("div",{className:"overflow-x-auto rounded-md border border-gray-200 shadow-sm bg-white",children:(0,a.jsxs)("table",{className:"min-w-full text-sm",children:[(0,a.jsx)("thead",{className:"bg-gray-50",children:(0,a.jsxs)("tr",{children:[(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-32",children:"Cloud"}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-24",children:"Clusters"}),(0,a.jsx)("th",{className:"p-3 text-left font-medium text-gray-600 w-24",children:"Jobs"})]})}),(0,a.jsx)("tbody",{className:"bg-white divide-y divide-gray-200",children:Z.map(e=>{let s=j&&void 0!==e.clusters&&void 0!==e.jobs;return(0,a.jsxs)("tr",{className:"hover:bg-gray-50",children:[(0,a.jsx)("td",{className:"p-3 font-medium text-gray-700",children:e.name}),(0,a.jsx)("td",{className:"p-3",children:s?e.clusters>0?(0,a.jsx)("span",{className:"px-2 py-0.5 bg-blue-100 text-blue-800 rounded text-xs font-medium",children:e.clusters}):(0,a.jsx)("span",{className:"px-2 py-0.5 bg-gray-100 text-gray-500 rounded text-xs font-medium",children:"0"}):(0,a.jsx)("div",{className:"flex items-center justify-center",children:(0,a.jsx)(r.Z,{size:16})})}),(0,a.jsx)("td",{className:"p-3",children:s?e.jobs>0?(0,a.jsx)("span",{className:"px-2 py-0.5 bg-green-100 text-green-800 rounded text-xs font-medium",children:e.jobs}):(0,a.jsx)("span",{className:"px-2 py-0.5 bg-gray-100 text-gray-500 rounded text-xs font-medium",children:"0"}):(0,a.jsx)("div",{className:"flex items-center justify-center",children:(0,a.jsx)(r.Z,{size:16})})})]},e.name)})})]})}):(0,a.jsx)("p",{className:"text-sm text-gray-500",children:"No enabled clouds available."})]})}),e_=()=>(0,a.jsx)(I,{title:"SSH Node Pool",isLoading:e,isDataLoaded:f,contexts:ey,gpus:eb,groupedPerContextGPUs:ef,groupedPerNodeGPUs:ev,handleContextClick:ew,contextStats:W,jobsData:ea,isJobsDataLoading:es,isSSH:!0,actionButton:null}),ek=()=>(0,a.jsx)(I,{title:"Kubernetes",isLoading:e,isDataLoaded:f,contexts:ej,gpus:eN,groupedPerContextGPUs:ef,groupedPerNodeGPUs:ev,handleContextClick:ew,contextStats:W,jobsData:ea,isJobsDataLoading:es,isSSH:!1}),eP=e||t;return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)("div",{className:"flex items-center justify-between mb-4 h-5",children:[(0,a.jsxs)("div",{className:"text-base flex items-center",children:[(0,a.jsx)(_(),{href:"/infra",className:"text-sky-blue hover:underline ".concat(er?"":"cursor-default"),children:"Infrastructure"}),er&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("span",{className:"mx-2 text-gray-500",children:"›"}),er.startsWith("ssh-")?(0,a.jsx)(_(),{href:"/infra",className:"text-sky-blue hover:underline cursor-pointer",children:"SSH Node Pool"}):(0,a.jsx)(_(),{href:"/infra",className:"text-sky-blue hover:underline cursor-pointer",children:"Kubernetes"}),(0,a.jsx)("span",{className:"mx-2 text-gray-500",children:"›"}),(0,a.jsx)("span",{className:"text-sky-blue",children:er.startsWith("ssh-")?er.replace(/^ssh-/,""):er})]})]}),(0,a.jsxs)("div",{className:"flex items-center",children:[eP&&(0,a.jsxs)("div",{className:"flex items-center mr-2",children:[(0,a.jsx)(r.Z,{size:15,className:"mt-0"}),(0,a.jsx)("span",{className:"ml-2 text-gray-500",children:"Loading..."})]}),(0,a.jsxs)("button",{onClick:ep,disabled:eP,className:"text-sky-blue hover:text-sky-blue-bright flex items-center",children:[(0,a.jsx)(i.Z,{className:"h-4 w-4 mr-1.5"}),!p&&"Refresh"]})]})]}),(()=>{if(er)return e&&!f?(0,a.jsxs)("div",{className:"flex flex-col items-center justify-center h-64",children:[(0,a.jsx)(r.Z,{size:32,className:"mb-4"}),(0,a.jsx)("span",{className:"text-gray-500 text-lg",children:"Loading Context..."})]}):eS(er);let s=[];s.push({name:"Kubernetes",render:ek,hasActivity:ej.length>0,priority:1}),s.push({name:"Cloud",render:eC,hasActivity:T>0,priority:2}),s.push({name:"SSH Node Pool",render:e_,hasActivity:ey.length>0&&function(e){let s=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return e.some(e=>{let t=W[s?"ssh/".concat(e.replace(/^ssh-/,"")):"kubernetes/".concat(e)]||{clusters:0,jobs:0};return t.clusters>0||t.jobs>0})}(ey,!0),priority:3});let t=s.sort((e,s)=>e.hasActivity!==s.hasActivity?e.hasActivity?-1:1:e.priority-s.priority);return(0,a.jsx)(a.Fragment,{children:t.map((e,s)=>(0,a.jsx)(l.Fragment,{children:e.render()},s))})})(),(0,a.jsx)(b,{isOpen:$,onClose:()=>B(!1),onSave:eg,poolData:X,isLoading:Q})]})}},42557:function(e,s,t){t.d(s,{I:function(){return n}});var a=t(85893),l=t(67294),r=t(32350);let n=l.forwardRef((e,s)=>{let{className:t,type:l,...n}=e;return(0,a.jsx)("input",{type:l,className:(0,r.cn)("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",t),ref:s,...n})});n.displayName="Input"},29749:function(e,s,t){t.d(s,{_:function(){return c}});var a=t(85893),l=t(67294),r=t(75320),n=l.forwardRef((e,s)=>(0,a.jsx)(r.WV.label,{...e,ref:s,onMouseDown:s=>{s.target.closest("button, input, select, textarea")||(e.onMouseDown?.(s),!s.defaultPrevented&&s.detail>1&&s.preventDefault())}}));n.displayName="Label";var o=t(12003),i=t(32350);let d=(0,o.j)("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"),c=l.forwardRef((e,s)=>{let{className:t,...l}=e;return(0,a.jsx)(n,{ref:s,className:(0,i.cn)(d(),t),...l})});c.displayName=n.displayName},69123:function(e,s,t){t.d(s,{g:function(){return n}});var a=t(85893),l=t(67294),r=t(32350);let n=l.forwardRef((e,s)=>{let{className:t,...l}=e;return(0,a.jsx)("textarea",{className:(0,r.cn)("flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",t),ref:s,...l})});n.displayName="Textarea"},17853:function(e,s,t){t.d(s,{TO:function(){return r},ki:function(){return n}});let a=null,l=null,r=async()=>null!==a?a:l||(l=(async()=>{try{let e="".concat(window.location.origin,"/grafana"),s=await fetch("".concat(e,"/api/health"),{method:"GET",credentials:"include",headers:{Accept:"application/json"},signal:AbortSignal.timeout(5e3)});return 200==s.status}catch(e){return console.debug("Grafana availability check failed:",e),a=!1,!1}finally{l=null}})()),n=()=>"".concat(window.location.origin,"/grafana")}}]);
|
/sky/dashboard/out/_next/static/{eWytLgin5zvayQw3Xk46m → DAiq7V2xJnO1LSfmunZl6}/_buildManifest.js
RENAMED
|
File without changes
|
/sky/dashboard/out/_next/static/{eWytLgin5zvayQw3Xk46m → DAiq7V2xJnO1LSfmunZl6}/_ssgManifest.js
RENAMED
|
File without changes
|