skypilot-nightly 1.0.0.dev20250912__py3-none-any.whl → 1.0.0.dev20250913__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.

Files changed (72) hide show
  1. sky/__init__.py +4 -2
  2. sky/adaptors/seeweb.py +103 -0
  3. sky/authentication.py +38 -0
  4. sky/backends/backend_utils.py +24 -9
  5. sky/backends/cloud_vm_ray_backend.py +382 -151
  6. sky/catalog/data_fetchers/fetch_aws.py +0 -36
  7. sky/catalog/data_fetchers/fetch_seeweb.py +329 -0
  8. sky/catalog/seeweb_catalog.py +184 -0
  9. sky/clouds/__init__.py +2 -0
  10. sky/clouds/kubernetes.py +2 -0
  11. sky/clouds/seeweb.py +463 -0
  12. sky/core.py +46 -12
  13. sky/dashboard/out/404.html +1 -1
  14. sky/dashboard/out/_next/static/{DAiq7V2xJnO1LSfmunZl6 → Y0Q7LyrxiFoWWbTdwb5nh}/_buildManifest.js +1 -1
  15. sky/dashboard/out/_next/static/chunks/1141-159df2d4c441a9d1.js +1 -0
  16. sky/dashboard/out/_next/static/chunks/3015-2ea98b57e318bd6e.js +1 -0
  17. sky/dashboard/out/_next/static/chunks/3294.03e02ae73455f48e.js +6 -0
  18. sky/dashboard/out/_next/static/chunks/3785.0fa442e16dd3f00e.js +1 -0
  19. sky/dashboard/out/_next/static/chunks/5339.c033b29835da0f35.js +51 -0
  20. sky/dashboard/out/_next/static/chunks/6856-e0754534b3015377.js +1 -0
  21. sky/dashboard/out/_next/static/chunks/6990-11c8e9b982e8ffec.js +1 -0
  22. sky/dashboard/out/_next/static/chunks/9037-f9800e64eb05dd1c.js +6 -0
  23. sky/dashboard/out/_next/static/chunks/{webpack-e8a0c4c3c6f408fb.js → webpack-d1e29b3aa66bf4cf.js} +1 -1
  24. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  25. sky/dashboard/out/clusters/[cluster].html +1 -1
  26. sky/dashboard/out/clusters.html +1 -1
  27. sky/dashboard/out/config.html +1 -1
  28. sky/dashboard/out/index.html +1 -1
  29. sky/dashboard/out/infra/[context].html +1 -1
  30. sky/dashboard/out/infra.html +1 -1
  31. sky/dashboard/out/jobs/[job].html +1 -1
  32. sky/dashboard/out/jobs/pools/[pool].html +1 -1
  33. sky/dashboard/out/jobs.html +1 -1
  34. sky/dashboard/out/users.html +1 -1
  35. sky/dashboard/out/volumes.html +1 -1
  36. sky/dashboard/out/workspace/new.html +1 -1
  37. sky/dashboard/out/workspaces/[name].html +1 -1
  38. sky/dashboard/out/workspaces.html +1 -1
  39. sky/exceptions.py +5 -0
  40. sky/global_user_state.py +41 -26
  41. sky/jobs/utils.py +61 -13
  42. sky/provision/__init__.py +1 -0
  43. sky/provision/kubernetes/utils.py +14 -3
  44. sky/provision/seeweb/__init__.py +11 -0
  45. sky/provision/seeweb/config.py +13 -0
  46. sky/provision/seeweb/instance.py +806 -0
  47. sky/schemas/generated/jobsv1_pb2.py +86 -0
  48. sky/schemas/generated/jobsv1_pb2.pyi +252 -0
  49. sky/schemas/generated/jobsv1_pb2_grpc.py +542 -0
  50. sky/setup_files/dependencies.py +8 -1
  51. sky/skylet/constants.py +2 -1
  52. sky/skylet/job_lib.py +128 -10
  53. sky/skylet/log_lib.py +3 -3
  54. sky/skylet/services.py +203 -0
  55. sky/skylet/skylet.py +4 -0
  56. sky/templates/seeweb-ray.yml.j2 +108 -0
  57. sky/utils/controller_utils.py +11 -5
  58. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/METADATA +39 -34
  59. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/RECORD +64 -53
  60. sky/dashboard/out/_next/static/chunks/1141-943efc7aff0f0c06.js +0 -1
  61. sky/dashboard/out/_next/static/chunks/3015-86cabed5d4669ad0.js +0 -1
  62. sky/dashboard/out/_next/static/chunks/3294.ba6586f9755b0edb.js +0 -6
  63. sky/dashboard/out/_next/static/chunks/3785.4872a2f3aa489880.js +0 -1
  64. sky/dashboard/out/_next/static/chunks/5339.3fda4a4010ff4e06.js +0 -51
  65. sky/dashboard/out/_next/static/chunks/6856-6e2bc8a6fd0867af.js +0 -1
  66. sky/dashboard/out/_next/static/chunks/6990-08b2a1cae076a943.js +0 -1
  67. sky/dashboard/out/_next/static/chunks/9037-fa1737818d0a0969.js +0 -6
  68. /sky/dashboard/out/_next/static/{DAiq7V2xJnO1LSfmunZl6 → Y0Q7LyrxiFoWWbTdwb5nh}/_ssgManifest.js +0 -0
  69. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/WHEEL +0 -0
  70. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/entry_points.txt +0 -0
  71. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/licenses/LICENSE +0 -0
  72. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/top_level.txt +0 -0
sky/clouds/seeweb.py ADDED
@@ -0,0 +1,463 @@
1
+ """Seeweb Cloud
2
+
3
+ History:
4
+ @ Aug 6, 2025: Initial version of the integration.
5
+ - Francesco Massa
6
+ - Marco Cristofanilli (marco.cATseeweb.it)
7
+
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import typing
13
+ from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
14
+
15
+ from sky import catalog
16
+ from sky import clouds
17
+ from sky.adaptors import seeweb as seeweb_adaptor
18
+ from sky.provision import seeweb as seeweb_provision
19
+ from sky.utils import registry
20
+ from sky.utils import resources_utils
21
+ from sky.utils import ux_utils
22
+
23
+ if typing.TYPE_CHECKING:
24
+ from sky import resources as resources_lib
25
+ from sky.utils import status_lib
26
+ from sky.utils import volume as volume_lib
27
+
28
+ # ---------- key file path -----------------
29
+ _SEEWEB_KEY_FILE = '~/.seeweb_cloud/seeweb_keys'
30
+ # (content: ini-like)
31
+ # api_key = <TOKEN>
32
+
33
+
34
+ @registry.CLOUD_REGISTRY.register
35
+ class Seeweb(clouds.Cloud):
36
+ """Seeweb GPU Cloud."""
37
+
38
+ _REPR = 'Seeweb'
39
+ # Define unsupported features to provide clear error messages
40
+ # This helps users understand what Seeweb can and cannot do
41
+ _CLOUD_UNSUPPORTED_FEATURES = {
42
+ clouds.CloudImplementationFeatures.MULTI_NODE:
43
+ ('Multi-node not supported. '
44
+ 'Seeweb does not support multi-node clusters.'),
45
+ clouds.CloudImplementationFeatures.CUSTOM_DISK_TIER:
46
+ ('Custom disk tiers not supported. '
47
+ 'Seeweb does not support custom disk tiers.'),
48
+ clouds.CloudImplementationFeatures.STORAGE_MOUNTING:
49
+ ('Storage mounting not supported. '
50
+ 'Seeweb does not support storage mounting.'),
51
+ clouds.CloudImplementationFeatures.HIGH_AVAILABILITY_CONTROLLERS:
52
+ ('High availability controllers not supported. '
53
+ 'Seeweb does not support high availability controllers.'),
54
+ clouds.CloudImplementationFeatures.SPOT_INSTANCE:
55
+ ('Spot instances not supported. '
56
+ 'Seeweb does not support spot instances.'),
57
+ clouds.CloudImplementationFeatures.CLONE_DISK_FROM_CLUSTER:
58
+ ('Disk cloning not supported. '
59
+ 'Seeweb does not support disk cloning.'),
60
+ clouds.CloudImplementationFeatures.DOCKER_IMAGE:
61
+ ('Docker images not supported. '
62
+ 'Seeweb does not support Docker images.'),
63
+ clouds.CloudImplementationFeatures.IMAGE_ID:
64
+ ('Custom image IDs not supported. '
65
+ 'Seeweb does not support custom image IDs.'),
66
+ clouds.CloudImplementationFeatures.CUSTOM_NETWORK_TIER:
67
+ ('Custom network tiers not supported. '
68
+ 'Seeweb does not support custom network tiers.'),
69
+ clouds.CloudImplementationFeatures.HOST_CONTROLLERS:
70
+ ('Host controllers not supported. '
71
+ 'Seeweb does not support host controllers.'),
72
+ clouds.CloudImplementationFeatures.CUSTOM_MULTI_NETWORK:
73
+ ('Custom multi-network not supported. '
74
+ 'Seeweb does not support custom multi-network.'),
75
+ }
76
+ _MAX_CLUSTER_NAME_LEN_LIMIT = 120
77
+ _regions: List[clouds.Region] = []
78
+
79
+ PROVISIONER_VERSION = clouds.ProvisionerVersion.SKYPILOT
80
+ STATUS_VERSION = clouds.StatusVersion.SKYPILOT
81
+
82
+ # Enable port support with updatable version
83
+ OPEN_PORTS_VERSION = clouds.OpenPortsVersion.UPDATABLE
84
+
85
+ @classmethod
86
+ def _unsupported_features_for_resources(
87
+ cls, resources: 'resources_lib.Resources'
88
+ ) -> Dict[clouds.CloudImplementationFeatures, str]:
89
+ return cls._CLOUD_UNSUPPORTED_FEATURES
90
+
91
+ @classmethod
92
+ def max_cluster_name_length(cls) -> Optional[int]:
93
+ return cls._MAX_CLUSTER_NAME_LEN_LIMIT
94
+
95
+ @classmethod
96
+ def regions(cls) -> List['clouds.Region']:
97
+ """Return available regions for Seeweb."""
98
+ # Get regions from the catalog system
99
+ # This reads from the CSV files generated by fetch_seeweb.py
100
+ regions = catalog.regions(clouds='seeweb')
101
+ return regions
102
+
103
+ @classmethod
104
+ def regions_with_offering(
105
+ cls,
106
+ instance_type: str,
107
+ accelerators: Optional[Dict[str, int]],
108
+ use_spot: bool,
109
+ region: Optional[str],
110
+ zone: Optional[str],
111
+ ) -> List[clouds.Region]:
112
+ assert zone is None, 'Seeweb does not support zones.'
113
+ del zone
114
+ if use_spot:
115
+ return []
116
+
117
+ # Get regions from catalog based on instance type
118
+ # This will read the CSV and return only regions
119
+ # where the instance type exists
120
+ regions = catalog.get_region_zones_for_instance_type(
121
+ instance_type, use_spot, 'seeweb')
122
+
123
+ if region is not None:
124
+ regions = [r for r in regions if r.name == region]
125
+
126
+ return regions
127
+
128
+ @classmethod
129
+ def zones_provision_loop(
130
+ cls,
131
+ *,
132
+ region: str,
133
+ num_nodes: int,
134
+ instance_type: str,
135
+ accelerators: Optional[Dict[str, int]] = None,
136
+ use_spot: bool = False,
137
+ ) -> Iterator[None]:
138
+ del num_nodes
139
+ regions = cls.regions_with_offering(instance_type,
140
+ accelerators,
141
+ use_spot,
142
+ region=region,
143
+ zone=None)
144
+ for r in regions:
145
+ assert r.zones is None, r
146
+ yield r.zones
147
+
148
+ @classmethod
149
+ def get_zone_shell_cmd(cls) -> Optional[str]:
150
+ """Seeweb doesn't support zones."""
151
+ return None
152
+
153
+ def instance_type_to_hourly_cost(
154
+ self,
155
+ instance_type: str,
156
+ use_spot: bool,
157
+ region: Optional[str],
158
+ zone: Optional[str],
159
+ ) -> float:
160
+ cost = catalog.get_hourly_cost(instance_type,
161
+ use_spot=use_spot,
162
+ region=region,
163
+ zone=zone,
164
+ clouds='seeweb')
165
+ return cost
166
+
167
+ def accelerators_to_hourly_cost(
168
+ self,
169
+ accelerators: Dict[str, int],
170
+ use_spot: bool,
171
+ region: Optional[str],
172
+ zone: Optional[str],
173
+ ) -> float:
174
+
175
+ return 0.0
176
+
177
+ def get_egress_cost(self, num_gigabytes: float):
178
+ return 0.0
179
+
180
+ def make_deploy_resources_variables(
181
+ self,
182
+ resources: 'resources_lib.Resources',
183
+ cluster_name: resources_utils.ClusterName,
184
+ region: 'clouds.Region',
185
+ zones: Optional[List['clouds.Zone']],
186
+ num_nodes: int,
187
+ dryrun: bool = False,
188
+ volume_mounts: Optional[List['volume_lib.VolumeMount']] = None,
189
+ ) -> Dict[str, Any]:
190
+ """Create deployment variables for Seeweb."""
191
+
192
+ # Note: Spot instances and multi-node are automatically handled by
193
+ # the framework via _CLOUD_UNSUPPORTED_FEATURES
194
+
195
+ resources = resources.assert_launchable()
196
+
197
+ acc_dict = self.get_accelerators_from_instance_type(
198
+ resources.instance_type)
199
+
200
+ # Standard custom_resources string for Ray
201
+ custom_resources = resources_utils.make_ray_custom_resources_str(
202
+ acc_dict)
203
+
204
+ # Seeweb-specific GPU configuration for the provisioner
205
+ # This tells the provisioner how to configure GPU resources
206
+ seeweb_gpu_config = None
207
+ if resources.accelerators:
208
+ # If the instance has accelerators, prepare GPU configuration
209
+ accelerator_name = list(resources.accelerators.keys())[0]
210
+ accelerator_count = resources.accelerators[accelerator_name]
211
+ seeweb_gpu_config = {
212
+ 'gpu': accelerator_count,
213
+ 'gpu_label': accelerator_name,
214
+ }
215
+
216
+ # Seeweb uses pre-configured images based on instance type
217
+ # Determine image based on whether the instance type name contains "GPU"
218
+ if resources.instance_type and 'GPU' in resources.instance_type.upper():
219
+ # GPU instance - use image with NVIDIA drivers
220
+ if resources.instance_type in ['ECS1GPU10', 'ECS2GPU10']:
221
+ # H200 GPU instance - use UEFI image with NVIDIA drivers
222
+ image_id = 'ubuntu-2204-uefi-nvidia-driver'
223
+ else:
224
+ # Other GPU instance - use standard image with NVIDIA drivers
225
+ image_id = 'ubuntu-2204-nvidia-driver'
226
+ else:
227
+ # CPU-only instance - use standard Ubuntu image
228
+ image_id = 'ubuntu-2204'
229
+
230
+ result = {
231
+ 'instance_type': resources.instance_type,
232
+ 'region': region.name,
233
+ 'cluster_name': cluster_name,
234
+ 'custom_resources': custom_resources,
235
+ 'seeweb_gpu_config': seeweb_gpu_config,
236
+ 'image_id': image_id,
237
+ }
238
+ return result
239
+
240
+ @classmethod
241
+ def get_vcpus_mem_from_instance_type(
242
+ cls, instance_type: str) -> Tuple[Optional[float], Optional[float]]:
243
+ result = catalog.get_vcpus_mem_from_instance_type(instance_type,
244
+ clouds='seeweb')
245
+ return result
246
+
247
+ @classmethod
248
+ def get_accelerators_from_instance_type(
249
+ cls,
250
+ instance_type: str,
251
+ ) -> Optional[Dict[str, Union[int, float]]]:
252
+ result = catalog.get_accelerators_from_instance_type(instance_type,
253
+ clouds='seeweb')
254
+ return result
255
+
256
+ @classmethod
257
+ def get_default_instance_type(
258
+ cls,
259
+ cpus: Optional[str] = None,
260
+ memory: Optional[str] = None,
261
+ disk_tier: Optional[resources_utils.DiskTier] = None,
262
+ region: Optional[str] = None,
263
+ zone: Optional[str] = None,
264
+ ) -> Optional[str]:
265
+ result = catalog.get_default_instance_type(cpus=cpus,
266
+ memory=memory,
267
+ disk_tier=disk_tier,
268
+ clouds='seeweb')
269
+ return result
270
+
271
+ def _get_feasible_launchable_resources(
272
+ self, resources: 'resources_lib.Resources'
273
+ ) -> 'resources_utils.FeasibleResources':
274
+ """Get feasible resources for Seeweb."""
275
+ if resources.use_spot:
276
+ return resources_utils.FeasibleResources(
277
+ [], [], 'Spot instances not supported on Seeweb')
278
+
279
+ if resources.accelerators and len(resources.accelerators) > 1:
280
+ return resources_utils.FeasibleResources(
281
+ [], [], 'Multiple accelerator types not supported on Seeweb')
282
+
283
+ # If no instance_type is specified, try to get a default one
284
+ if not resources.instance_type:
285
+ # If accelerators are specified, try to find instance
286
+ # type forthat accelerator
287
+ if resources.accelerators:
288
+ # Get the first accelerator
289
+ # (we already checked there's only one)
290
+ acc_name, acc_count = list(resources.accelerators.items())[0]
291
+
292
+ # Use catalog to find instance type for this accelerator
293
+ # This leverages the catalog system to find suitable instances
294
+ (
295
+ instance_types,
296
+ fuzzy_candidates,
297
+ ) = catalog.get_instance_type_for_accelerator(
298
+ acc_name=acc_name,
299
+ acc_count=acc_count,
300
+ cpus=resources.cpus,
301
+ memory=resources.memory,
302
+ use_spot=resources.use_spot,
303
+ region=resources.region,
304
+ zone=resources.zone,
305
+ clouds='seeweb',
306
+ )
307
+
308
+ if instance_types and len(instance_types) > 0:
309
+ # Use the first (cheapest) instance type
310
+ selected_instance_type = instance_types[0]
311
+ resources = resources.copy(
312
+ instance_type=selected_instance_type)
313
+ else:
314
+ return resources_utils.FeasibleResources(
315
+ [],
316
+ fuzzy_candidates,
317
+ f'No instance type found for accelerator'
318
+ f'{acc_name}:{acc_count} on Seeweb',
319
+ )
320
+ else:
321
+ # No accelerators specified, use default instance type
322
+ default_instance_type = self.get_default_instance_type(
323
+ cpus=resources.cpus,
324
+ memory=resources.memory,
325
+ region=resources.region,
326
+ zone=resources.zone,
327
+ )
328
+
329
+ if default_instance_type:
330
+ # Create new resources with the default instance type
331
+ resources = resources.copy(
332
+ instance_type=default_instance_type)
333
+ else:
334
+ return resources_utils.FeasibleResources(
335
+ [],
336
+ [],
337
+ f'No suitable instance type found for'
338
+ f'cpus={resources.cpus}, memory={resources.memory}',
339
+ )
340
+
341
+ # Check if instance type exists
342
+ if resources.instance_type:
343
+ exists = catalog.instance_type_exists(resources.instance_type,
344
+ clouds='seeweb')
345
+ if not exists:
346
+ return resources_utils.FeasibleResources(
347
+ [],
348
+ [],
349
+ f'Instance type {resources.instance_type}'
350
+ f' not available on Seeweb',
351
+ )
352
+
353
+ # Set the cloud if not already set
354
+ if not resources.cloud:
355
+ resources = resources.copy(cloud=self)
356
+
357
+ # Return the resources as feasible
358
+ return resources_utils.FeasibleResources([resources], [], None)
359
+
360
+ @classmethod
361
+ def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
362
+ """Check Seeweb compute credentials."""
363
+ try:
364
+ result = seeweb_adaptor.check_compute_credentials()
365
+ return result, None
366
+ except Exception as e: # pylint: disable=broad-except
367
+ return False, str(e)
368
+
369
+ @classmethod
370
+ def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
371
+ """Check Seeweb storage credentials."""
372
+ try:
373
+ result = seeweb_adaptor.check_storage_credentials()
374
+ return result, None
375
+ except Exception as e: # pylint: disable=broad-except
376
+ return False, str(e)
377
+
378
+ @classmethod
379
+ def get_user_identities(cls) -> Optional[List[List[str]]]:
380
+ # Seeweb doesn't have user identity concept
381
+ return None
382
+
383
+ @classmethod
384
+ def query_status(
385
+ cls,
386
+ name: str,
387
+ tag_filters: Dict[str, str],
388
+ region: Optional[str],
389
+ zone: Optional[str],
390
+ **kwargs,
391
+ ) -> List['status_lib.ClusterStatus']:
392
+ """Query the status of Seeweb cluster instances."""
393
+ cluster_name_on_cloud = name
394
+
395
+ result = seeweb_provision.instance.query_instances(
396
+ cluster_name=name,
397
+ cluster_name_on_cloud=cluster_name_on_cloud,
398
+ provider_config={},
399
+ non_terminated_only=True)
400
+ # Convert Dict[str, Tuple[Optional[ClusterStatus],
401
+ # Optional[str]]] to List[ClusterStatus]
402
+ return [status for status, _ in result.values() if status is not None]
403
+
404
+ def get_credential_file_mounts(self) -> Dict[str, str]:
405
+ """Returns the credential files to mount."""
406
+ # Mount the Seeweb API key file to the remote instance
407
+ # This allows the provisioner to authenticate with Seeweb API
408
+ result = {
409
+ _SEEWEB_KEY_FILE: _SEEWEB_KEY_FILE,
410
+ }
411
+ return result
412
+
413
+ def instance_type_exists(self, instance_type: str) -> bool:
414
+ """Returns whether the instance type exists for Seeweb."""
415
+ result = catalog.instance_type_exists(instance_type, clouds='seeweb')
416
+ return result
417
+
418
+ @classmethod
419
+ def get_image_size(cls, image_id: str, region: Optional[str]) -> float:
420
+ """Seeweb doesn't support custom images."""
421
+ del image_id, region
422
+ with ux_utils.print_exception_no_traceback():
423
+ raise ValueError(f'Custom images are not supported on {cls._REPR}. '
424
+ 'Seeweb clusters use pre-configured images only.')
425
+
426
+ # Image-related methods (not supported)
427
+ @classmethod
428
+ def create_image_from_cluster(
429
+ cls,
430
+ cluster_name: resources_utils.ClusterName,
431
+ region: Optional[str],
432
+ zone: Optional[str],
433
+ ) -> str:
434
+ del cluster_name, region, zone # unused
435
+ with ux_utils.print_exception_no_traceback():
436
+ raise ValueError(
437
+ f'Creating images from clusters is not supported on'
438
+ f' {cls._REPR}. Seeweb does not support custom'
439
+ f' image creation.')
440
+
441
+ @classmethod
442
+ def maybe_move_image(
443
+ cls,
444
+ image_id: str,
445
+ source_region: str,
446
+ target_region: str,
447
+ source_zone: Optional[str],
448
+ target_zone: Optional[str],
449
+ ) -> str:
450
+ del image_id, source_region, target_region, source_zone, target_zone
451
+ with ux_utils.print_exception_no_traceback():
452
+ raise ValueError(
453
+ f'Moving images between regions is not supported on'
454
+ f' {cls._REPR}. '
455
+ 'Seeweb does not support custom images.')
456
+
457
+ @classmethod
458
+ def delete_image(cls, image_id: str, region: Optional[str]) -> None:
459
+ del image_id, region
460
+ with ux_utils.print_exception_no_traceback():
461
+ raise ValueError(
462
+ f'Deleting images is not supported on {cls._REPR}. '
463
+ 'Seeweb does not support custom image management.')
sky/core.py CHANGED
@@ -20,7 +20,9 @@ from sky import optimizer
20
20
  from sky import sky_logging
21
21
  from sky import skypilot_config
22
22
  from sky import task as task_lib
23
+ from sky.adaptors import common as adaptors_common
23
24
  from sky.backends import backend_utils
25
+ from sky.backends import cloud_vm_ray_backend
24
26
  from sky.clouds import cloud as sky_cloud
25
27
  from sky.jobs.server import core as managed_jobs_core
26
28
  from sky.provision.kubernetes import constants as kubernetes_constants
@@ -44,6 +46,9 @@ from sky.utils.kubernetes import kubernetes_deploy_utils
44
46
 
45
47
  if typing.TYPE_CHECKING:
46
48
  from sky import resources as resources_lib
49
+ from sky.schemas.generated import jobsv1_pb2
50
+ else:
51
+ jobsv1_pb2 = adaptors_common.LazyImport('sky.schemas.generated.jobsv1_pb2')
47
52
 
48
53
  logger = sky_logging.init_logger(__name__)
49
54
 
@@ -811,7 +816,6 @@ def queue(cluster_name: str,
811
816
  user_hash = None
812
817
  else:
813
818
  user_hash = common_utils.get_current_user().id
814
- code = job_lib.JobLibCodeGen.get_job_queue(user_hash, all_jobs)
815
819
 
816
820
  handle = backend_utils.check_cluster_available(
817
821
  cluster_name,
@@ -819,17 +823,47 @@ def queue(cluster_name: str,
819
823
  )
820
824
  backend = backend_utils.get_backend_from_handle(handle)
821
825
 
822
- returncode, jobs_payload, stderr = backend.run_on_head(handle,
823
- code,
824
- require_outputs=True,
825
- separate_stderr=True)
826
- subprocess_utils.handle_returncode(
827
- returncode,
828
- command=code,
829
- error_msg=f'Failed to get job queue on cluster {cluster_name}.',
830
- stderr=f'{jobs_payload + stderr}',
831
- stream_logs=True)
832
- jobs = job_lib.load_job_queue(jobs_payload)
826
+ use_legacy = not handle.is_grpc_enabled_with_flag
827
+
828
+ if handle.is_grpc_enabled_with_flag:
829
+ try:
830
+ request = jobsv1_pb2.GetJobQueueRequest(user_hash=user_hash,
831
+ all_jobs=all_jobs)
832
+ response = backend_utils.invoke_skylet_with_retries(
833
+ lambda: cloud_vm_ray_backend.SkyletClient(
834
+ handle.get_grpc_channel()).get_job_queue(request))
835
+ jobs = []
836
+ for job_info in response.jobs:
837
+ job_dict = {
838
+ 'job_id': job_info.job_id,
839
+ 'job_name': job_info.job_name,
840
+ 'submitted_at': job_info.submitted_at,
841
+ 'status': job_lib.JobStatus.from_protobuf(job_info.status),
842
+ 'run_timestamp': job_info.run_timestamp,
843
+ 'start_at': job_info.start_at,
844
+ 'end_at': job_info.end_at,
845
+ 'resources': job_info.resources,
846
+ 'log_path': job_info.log_path,
847
+ 'user_hash': job_info.username,
848
+ }
849
+ # Copied from job_lib.load_job_queue.
850
+ user = global_user_state.get_user(job_dict['user_hash'])
851
+ job_dict['username'] = user.name if user is not None else None
852
+ jobs.append(job_dict)
853
+ except exceptions.SkyletMethodNotImplementedError:
854
+ use_legacy = True
855
+
856
+ if use_legacy:
857
+ code = job_lib.JobLibCodeGen.get_job_queue(user_hash, all_jobs)
858
+ returncode, jobs_payload, stderr = backend.run_on_head(
859
+ handle, code, require_outputs=True, separate_stderr=True)
860
+ subprocess_utils.handle_returncode(
861
+ returncode,
862
+ command=code,
863
+ error_msg=f'Failed to get job queue on cluster {cluster_name}.',
864
+ stderr=f'{jobs_payload + stderr}',
865
+ stream_logs=True)
866
+ jobs = job_lib.load_job_queue(jobs_payload)
833
867
  return jobs
834
868
 
835
869
 
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/4614e06482d7309e.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/4614e06482d7309e.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-e8a0c4c3c6f408fb.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-cf60a09ccd051a10.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-f15ccb73239a3bf1.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-ce361c6959bc2001.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js" defer=""></script><script src="/dashboard/_next/static/DAiq7V2xJnO1LSfmunZl6/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/DAiq7V2xJnO1LSfmunZl6/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"DAiq7V2xJnO1LSfmunZl6","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/dashboard/_next/static/css/4614e06482d7309e.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/4614e06482d7309e.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-d1e29b3aa66bf4cf.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-cf60a09ccd051a10.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-f15ccb73239a3bf1.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-ce361c6959bc2001.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js" defer=""></script><script src="/dashboard/_next/static/Y0Q7LyrxiFoWWbTdwb5nh/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/Y0Q7LyrxiFoWWbTdwb5nh/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"Y0Q7LyrxiFoWWbTdwb5nh","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
@@ -1 +1 @@
1
- self.__BUILD_MANIFEST=function(s,c,a,e,t,f,u,b,n,o,j,i,r,k){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/pages/index-444f1804401f04ea.js"],"/_error":["static/chunks/pages/_error-c66a4e8afc46f17b.js"],"/clusters":["static/chunks/pages/clusters-469814d711d63b1b.js"],"/clusters/[cluster]":[s,c,a,f,u,"static/chunks/4676-9da7fdbde90b5549.js",o,e,t,b,j,n,i,"static/chunks/6856-6e2bc8a6fd0867af.js",r,k,"static/chunks/9037-fa1737818d0a0969.js","static/chunks/pages/clusters/[cluster]-0b4b35dc1dfe046c.js"],"/clusters/[cluster]/[job]":[s,c,a,f,e,t,n,"static/chunks/pages/clusters/[cluster]/[job]-1cbba24bd1bd35f8.js"],"/config":["static/chunks/pages/config-dfb9bf07b13045f4.js"],"/infra":["static/chunks/pages/infra-aabba60d57826e0f.js"],"/infra/[context]":["static/chunks/pages/infra/[context]-6563820e094f68ca.js"],"/jobs":["static/chunks/pages/jobs-1f70d9faa564804f.js"],"/jobs/pools/[pool]":[s,c,a,u,o,e,t,b,"static/chunks/pages/jobs/pools/[pool]-07349868f7905d37.js"],"/jobs/[job]":[s,c,a,f,u,o,e,t,b,n,"static/chunks/pages/jobs/[job]-dd64309c3fe67ed2.js"],"/users":["static/chunks/pages/users-018bf31cda52e11b.js"],"/volumes":["static/chunks/pages/volumes-739726d6b823f532.js"],"/workspace/new":["static/chunks/pages/workspace/new-3f88a1c7e86a3f86.js"],"/workspaces":["static/chunks/pages/workspaces-7598c33a746cdc91.js"],"/workspaces/[name]":[s,c,a,f,u,"static/chunks/1836-37fede578e2da5f8.js",e,t,b,j,n,i,r,k,"static/chunks/1141-943efc7aff0f0c06.js","static/chunks/pages/workspaces/[name]-af76bb06dbb3954f.js"],sortedPages:["/","/_app","/_error","/clusters","/clusters/[cluster]","/clusters/[cluster]/[job]","/config","/infra","/infra/[context]","/jobs","/jobs/pools/[pool]","/jobs/[job]","/users","/volumes","/workspace/new","/workspaces","/workspaces/[name]"]}}("static/chunks/616-3d59f75e2ccf9321.js","static/chunks/6130-2be46d70a38f1e82.js","static/chunks/5739-d67458fcb1386c92.js","static/chunks/6989-01359c57e018caa4.js","static/chunks/3850-ff4a9a69d978632b.js","static/chunks/7411-b15471acd2cba716.js","static/chunks/1272-1ef0bf0237faccdb.js","static/chunks/8969-0487dfbf149d9e53.js","static/chunks/6135-4b4d5e824b7f9d3c.js","static/chunks/754-d0da8ab45f9509e9.js","static/chunks/6990-08b2a1cae076a943.js","static/chunks/1121-408ed10b2f9fce17.js","static/chunks/6601-06114c982db410b6.js","static/chunks/3015-86cabed5d4669ad0.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
1
+ self.__BUILD_MANIFEST=function(s,c,e,a,t,f,b,u,n,o,j,i,r,k){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/pages/index-444f1804401f04ea.js"],"/_error":["static/chunks/pages/_error-c66a4e8afc46f17b.js"],"/clusters":["static/chunks/pages/clusters-469814d711d63b1b.js"],"/clusters/[cluster]":[s,c,e,f,b,"static/chunks/4676-9da7fdbde90b5549.js",o,a,t,u,j,n,i,"static/chunks/6856-e0754534b3015377.js",r,k,"static/chunks/9037-f9800e64eb05dd1c.js","static/chunks/pages/clusters/[cluster]-0b4b35dc1dfe046c.js"],"/clusters/[cluster]/[job]":[s,c,e,f,a,t,n,"static/chunks/pages/clusters/[cluster]/[job]-1cbba24bd1bd35f8.js"],"/config":["static/chunks/pages/config-dfb9bf07b13045f4.js"],"/infra":["static/chunks/pages/infra-aabba60d57826e0f.js"],"/infra/[context]":["static/chunks/pages/infra/[context]-6563820e094f68ca.js"],"/jobs":["static/chunks/pages/jobs-1f70d9faa564804f.js"],"/jobs/pools/[pool]":[s,c,e,b,o,a,t,u,"static/chunks/pages/jobs/pools/[pool]-07349868f7905d37.js"],"/jobs/[job]":[s,c,e,f,b,o,a,t,u,n,"static/chunks/pages/jobs/[job]-dd64309c3fe67ed2.js"],"/users":["static/chunks/pages/users-018bf31cda52e11b.js"],"/volumes":["static/chunks/pages/volumes-739726d6b823f532.js"],"/workspace/new":["static/chunks/pages/workspace/new-3f88a1c7e86a3f86.js"],"/workspaces":["static/chunks/pages/workspaces-7598c33a746cdc91.js"],"/workspaces/[name]":[s,c,e,f,b,"static/chunks/1836-37fede578e2da5f8.js",a,t,u,j,n,i,r,k,"static/chunks/1141-159df2d4c441a9d1.js","static/chunks/pages/workspaces/[name]-af76bb06dbb3954f.js"],sortedPages:["/","/_app","/_error","/clusters","/clusters/[cluster]","/clusters/[cluster]/[job]","/config","/infra","/infra/[context]","/jobs","/jobs/pools/[pool]","/jobs/[job]","/users","/volumes","/workspace/new","/workspaces","/workspaces/[name]"]}}("static/chunks/616-3d59f75e2ccf9321.js","static/chunks/6130-2be46d70a38f1e82.js","static/chunks/5739-d67458fcb1386c92.js","static/chunks/6989-01359c57e018caa4.js","static/chunks/3850-ff4a9a69d978632b.js","static/chunks/7411-b15471acd2cba716.js","static/chunks/1272-1ef0bf0237faccdb.js","static/chunks/8969-0487dfbf149d9e53.js","static/chunks/6135-4b4d5e824b7f9d3c.js","static/chunks/754-d0da8ab45f9509e9.js","static/chunks/6990-11c8e9b982e8ffec.js","static/chunks/1121-408ed10b2f9fce17.js","static/chunks/6601-06114c982db410b6.js","static/chunks/3015-2ea98b57e318bd6e.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
@@ -0,0 +1 @@
1
+ "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1141],{1812:function(e,s,r){r.d(s,{X:function(){return n}});var l=r(85893),t=r(67294);let a=e=>{if(!(null==e?void 0:e.message))return"An unexpected error occurred.";let s=e.message;return s.includes("failed:")&&(s=s.split("failed:")[1].trim()),s},n=e=>{let{error:s,title:r="Error",onDismiss:n}=e,[c,i]=(0,t.useState)(!1);if((0,t.useEffect)(()=>{s&&i(!1)},[s]),!s||c)return null;let o="string"==typeof s?s:a(s);return(0,l.jsx)("div",{className:"bg-red-50 border border-red-200 rounded-md p-3 mb-4",children:(0,l.jsxs)("div",{className:"flex items-center justify-between",children:[(0,l.jsxs)("div",{className:"flex",children:[(0,l.jsx)("div",{className:"flex-shrink-0",children:(0,l.jsx)("svg",{className:"h-5 w-5 text-red-400",viewBox:"0 0 20 20",fill:"currentColor",children:(0,l.jsx)("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z",clipRule:"evenodd"})})}),(0,l.jsx)("div",{className:"ml-3",children:(0,l.jsxs)("div",{className:"text-sm text-red-800",children:[(0,l.jsxs)("strong",{children:[r,":"]})," ",o]})})]}),(0,l.jsx)("button",{onClick:()=>{i(!0),n&&n()},className:"flex-shrink-0 ml-4 text-red-400 hover:text-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 focus:ring-offset-red-50 rounded","aria-label":"Dismiss error",children:(0,l.jsx)("svg",{className:"h-4 w-4",viewBox:"0 0 20 20",fill:"currentColor",children:(0,l.jsx)("path",{fillRule:"evenodd",d:"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z",clipRule:"evenodd"})})})]})})}},69123:function(e,s,r){r.d(s,{g:function(){return n}});var l=r(85893),t=r(67294),a=r(32350);let n=t.forwardRef((e,s)=>{let{className:r,...t}=e;return(0,l.jsx)("textarea",{className:(0,a.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",r),ref:s,...t})});n.displayName="Textarea"},11141:function(e,s,r){r.r(s),r.d(s,{WorkspaceEditor:function(){return _}});var l=r(85893),t=r(67294),a=r(11163),n=r(17324),c=r(23266),i=r(68969);r(6135);var o=r(41664),d=r.n(o),u=r(9008),x=r.n(u),m=r(37673),h=r(30803),f=r(69123),g=r(55739),p=r(70282),b=r(6021),j=r(13626),y=r(98418),N=r(99333),w=r(50326);let v=e=>{let{className:s="",variant:r="default",children:t,...a}=e;return(0,l.jsx)("div",{role:"alert",className:"".concat("relative w-full rounded-lg border p-4 flex items-start space-x-2"," ").concat({default:"bg-blue-50 border-blue-200 text-blue-800",destructive:"bg-red-50 border-red-200 text-red-800"}[r]," ").concat(s),...a,children:t})},k=e=>{let{className:s="",children:r,...t}=e;return(0,l.jsx)("div",{className:"text-sm leading-relaxed ".concat(s),...t,children:r})};var C=r(53850),L=r(1812),E=r(23015),S=r(1272),W=r(93225),A=r(53081);let D=e=>{let{message:s}=e;return s?(0,l.jsxs)(v,{className:"border-green-200 bg-green-50",children:[(0,l.jsx)(p.Z,{className:"h-4 w-4 text-green-600"}),(0,l.jsx)(k,{className:"text-green-800",children:s})]}):null},P=e=>{let{workspaceName:s,config:r,enabledClouds:t=[]}=e;if(!r)return null;let a="default"===s,n=0===Object.keys(r).length;if(a&&n)return(0,l.jsx)("div",{className:"text-sm text-gray-500 mb-3 italic p-3 bg-sky-50 rounded border border-sky-200",children:"Workspace 'default' can use all accessible infrastructure."});let c=[],i=[],o=[],d=new Set(t.map(e=>e.toLowerCase()));Object.entries(r).forEach(e=>{let[s,r]=e;if("private"===s||"allowed_users"===s)return;let t=W.Z2[s.toLowerCase()]||s.toUpperCase(),a=null==t?void 0:t.toLowerCase(),n=d.has(a)||Array.from(d).some(e=>e.startsWith(a+"/")),u=()=>"kubernetes"===s.toLowerCase()?Array.from(d).filter(e=>e.startsWith(a+"/")).map(e=>e.split("/")[1]):[];if((null==r?void 0:r.disabled)===!0)i.push(t);else if(r&&Object.keys(r).length>0){let e="";if("gcp"===s.toLowerCase()&&r.project_id)e=" (Project ID: ".concat(r.project_id,")");else if("aws"===s.toLowerCase()&&r.region)e=" (Region: ".concat(r.region,")");else if("kubernetes"===s.toLowerCase()){let s=u();s.length>0&&(e=" (Contexts: ".concat(s.join(", "),")"))}n?c.push((0,l.jsxs)("span",{className:"block",children:[t,e," is enabled."]},"".concat(s,"-enabled"))):o.push((0,l.jsxs)("span",{className:"block text-amber-700",children:[t,e," is configured but not currently available."]},"".concat(s,"-configured-not-enabled")))}else if(n){let e="";if("kubernetes"===s.toLowerCase()){let s=u();s.length>0&&(e=" (Contexts: ".concat(s.join(", "),")"))}c.push((0,l.jsxs)("span",{className:"block",children:[t,e," is enabled (using default settings)."]},"".concat(s,"-default-enabled")))}else o.push((0,l.jsxs)("span",{className:"block text-amber-700",children:[t," is configured but not currently available."]},"".concat(s,"-default-not-enabled")))});let u=[];if(i.length>0){let e=i.join(" and ");u.push((0,l.jsxs)("span",{className:"block",children:[e," ",1===i.length?"is":"are"," explicitly disabled."]},"disabled-clouds"))}return(u.push(...c),u.push(...o),u.length>0)?(0,l.jsx)("div",{className:"text-sm text-gray-700 mb-3 p-3 bg-sky-50 rounded border border-sky-200",children:u}):!a&&n?(0,l.jsx)("div",{className:"text-sm text-gray-500 mb-3 italic p-3 bg-sky-50 rounded border border-sky-200",children:"This workspace has no specific cloud resource configurations and can use all accessible infrastructure."}):null},R=e=>{let{isPrivate:s}=e;return s?(0,l.jsx)("span",{className:"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-700 border border-gray-300",children:"Private"}):(0,l.jsx)("span",{className:"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-700 border border-green-300",children:"Public"})},Z=e=>{let{workspaceConfig:s,allUsers:r}=e;if(!s.private)return null;let t=s.allowed_users||[],a=(r||[]).filter(e=>"admin"===e.role).map(e=>e.username),n=[...new Set([...t,...a])];return 0===n.length?(0,l.jsxs)("div",{className:"mt-4",children:[(0,l.jsx)("h4",{className:"mb-2 text-xs text-gray-500 tracking-wider",children:"Allowed Users (0)"}),(0,l.jsx)("div",{className:"text-amber-600 text-xs italic p-2 bg-amber-50 rounded border border-amber-200",children:"No users configured (workspace may be inaccessible)"})]}):(0,l.jsxs)("div",{className:"mt-4",children:[(0,l.jsxs)("h4",{className:"mb-2 text-xs text-gray-500 tracking-wider",children:["Allowed Users (",n.length,")"]}),(0,l.jsx)("div",{className:"space-y-1 max-h-48 overflow-y-auto border border-gray-200 rounded",children:n.map(e=>{let s=a.includes(e);return(0,l.jsxs)("div",{className:"flex items-center justify-between text-xs p-2 bg-gray-50 hover:bg-gray-100 border-b border-gray-100 last:border-b-0",children:[(0,l.jsx)("span",{className:"font-medium text-gray-700",children:e}),s?(0,l.jsxs)("span",{className:"inline-flex items-center text-blue-600",children:[(0,l.jsx)(C.r7,{className:"w-3 h-3 mr-1"}),"Admin"]}):(0,l.jsxs)("span",{className:"inline-flex items-center text-gray-600",children:[(0,l.jsx)(b.Z,{className:"w-3 h-3 mr-1"}),"User"]})]},e)})})]})};function _(e){let{workspaceName:s,isNewWorkspace:r=!1}=e,o=(0,a.useRouter)(),[u,p]=(0,t.useState)({}),[b,v]=(0,t.useState)({}),[k,W]=(0,t.useState)(""),[_,M]=(0,t.useState)(!0),[O,z]=(0,t.useState)(!1),[U,Y]=(0,t.useState)(!1),[J,T]=(0,t.useState)(null),[I,F]=(0,t.useState)(null),[B,X]=(0,t.useState)(null),[G,H]=(0,t.useState)([]),[V,q]=(0,t.useState)({showDialog:!1,deleting:!1,error:null}),[K,Q]=(0,t.useState)({totalClusterCount:0,runningClusterCount:0,managedJobsCount:0,clouds:[]}),[$,ee]=(0,t.useState)(!1),es=(0,t.useCallback)(async()=>{M(!0),T(null);try{let e;let[r,l]=await Promise.all([(0,n.getWorkspaces)(),(0,A.R)()]),t=r[s]||{};p(t),v(t),H(l||[]),e=0===Object.keys(t).length?"".concat(s,":\n # Empty workspace configuration - uses all accessible infrastructure\n"):S.ZP.dump({[s]:t},{indent:2,lineWidth:-1,noRefs:!0,skipInvalid:!0,flowLevel:-1}),W(e)}catch(e){console.error("Error fetching workspace config:",e),T(e)}finally{M(!1)}},[s]),er=(0,t.useCallback)(async()=>{if(!r){ee(!0);try{let[e,r,l]=await Promise.all([(0,c.getClusters)(),(0,i.getManagedJobs)({allUsers:!0}),(0,n.getEnabledClouds)(s,!0)]),t=e.filter(e=>(e.workspace||"default")===s),a=t.filter(e=>"RUNNING"===e.status||"LAUNCHING"===e.status),o={};e.forEach(e=>{o[e.cluster]=e.workspace||"default"});let d=r.jobs||[],u=new Set(E.statusGroups.active),x=0;d.forEach(e=>{let r=e.cluster_name||e.resources&&e.resources.cluster_name;r&&o[r]===s&&u.has(e.status)&&x++}),Q({totalClusterCount:t.length,runningClusterCount:a.length,managedJobsCount:x,clouds:Array.isArray(l)?l:[]})}catch(e){console.error("Failed to fetch workspace stats:",e)}finally{ee(!1)}}},[s,r]);(0,t.useEffect)(()=>{r?(M(!1),W("".concat(s,":\n # New workspace configuration\n # Leave empty to use all accessible infrastructure\n"))):(es(),er())},[s,r,es,er]),(0,t.useEffect)(()=>{Y(JSON.stringify(u)!==JSON.stringify(b))},[u,b]);let el=e=>{W(e),X(null);try{let r=S.ZP.load(e)||{},l=Object.keys(r);if(0===l.length)p({});else if(1===l.length){let e=l[0];if(e!==s){X('Workspace name cannot be changed. Expected "'.concat(s,'" but found "').concat(e,'".'));return}let t=r[s]||{};p(t)}else X("Configuration must contain only one workspace. Found: ".concat(l.join(", ")))}catch(e){X("Invalid YAML: ".concat(e.message))}},et=async()=>{z(!0),T(null),F(null);try{if(B)throw Error("Please fix YAML errors before saving");let e=S.ZP.load(k)||{},l=Object.keys(e);if(l.length>0&&l[0]!==s)throw Error('Workspace name cannot be changed. Expected "'.concat(s,'".'));r?(await (0,n.MB)(s,u),F("Workspace created successfully!"),setTimeout(()=>{o.push("/workspaces/".concat(s))},1500)):(await (0,n.eA)(s,u),F("Workspace updated successfully!"),v(u),er())}catch(e){console.error("Error saving workspace:",e),T(e)}finally{z(!1)}},ea=async()=>{q(e=>({...e,deleting:!0,error:null}));try{await (0,n.zl)(s),F("Workspace deleted successfully!"),setTimeout(()=>{o.push("/workspaces")},1500)}catch(e){console.error("Error deleting workspace:",e),q(s=>({...s,deleting:!1,error:e}))}},en=()=>{q({showDialog:!1,deleting:!1,error:null})},ec=async()=>{await Promise.all([es(),er()])};if(!o.isReady)return(0,l.jsx)("div",{children:"Loading..."});let ei=r?"Create New Workspace | SkyPilot Dashboard":"Workspace: ".concat(s," | SkyPilot Dashboard");return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(x(),{children:(0,l.jsx)("title",{children:ei})}),(0,l.jsxs)(l.Fragment,{children:[(0,l.jsxs)("div",{className:"flex items-center justify-between mb-4 h-5",children:[(0,l.jsxs)("div",{className:"text-base flex items-center",children:[(0,l.jsx)(d(),{href:"/workspaces",className:"text-sky-blue hover:underline",children:"Workspaces"}),(0,l.jsx)("span",{className:"mx-2 text-gray-500",children:"›"}),(0,l.jsx)(d(),{href:r?"/workspace/new":"/workspaces/".concat(s),className:"text-sky-blue hover:underline",children:r?"New Workspace":s}),U&&(0,l.jsx)("span",{className:"ml-3 px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded",children:"Unsaved changes"})]}),(0,l.jsxs)("div",{className:"text-sm flex items-center",children:[(_||O||$)&&(0,l.jsxs)("div",{className:"flex items-center mr-4",children:[(0,l.jsx)(g.Z,{size:15,className:"mt-0"}),(0,l.jsx)("span",{className:"ml-2 text-gray-500",children:O?"Saving...":"Loading..."})]}),(0,l.jsxs)("div",{className:"flex items-center space-x-4",children:[!r&&(0,l.jsxs)("button",{onClick:ec,disabled:_||O||$,className:"text-sky-blue hover:text-sky-blue-bright font-medium inline-flex items-center",children:[(0,l.jsx)(j.Z,{className:"w-4 h-4 mr-1.5"}),"Refresh"]}),!r&&"default"!==s&&(0,l.jsxs)("button",{onClick:()=>q({...V,showDialog:!0}),disabled:V.deleting||O,className:"text-red-600 hover:text-red-700 font-medium inline-flex items-center",children:[(0,l.jsx)(y.Z,{className:"w-4 h-4 mr-1.5"}),"Delete"]})]})]})]}),_?(0,l.jsxs)("div",{className:"flex justify-center items-center py-12",children:[(0,l.jsx)(g.Z,{size:24,className:"mr-2"}),(0,l.jsx)("span",{className:"text-gray-500",children:"Loading workspace configuration..."})]}):(0,l.jsxs)("div",{className:"space-y-6",children:[(0,l.jsx)(L.X,{error:J,title:"Error",onDismiss:()=>T(null)}),(0,l.jsx)(D,{message:I}),(0,l.jsxs)("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-6",children:[!r&&(0,l.jsx)("div",{className:"lg:col-span-1",children:(0,l.jsxs)(m.Zb,{className:"h-full",children:[(0,l.jsx)(m.Ol,{children:(0,l.jsx)(m.ll,{className:"text-base font-normal",children:(0,l.jsxs)("div",{className:"flex items-center justify-between",children:[(0,l.jsxs)("div",{children:[(0,l.jsx)("span",{className:"font-semibold",children:"Workspace:"})," ",s]}),(0,l.jsx)(R,{isPrivate:!0===b.private})]})})}),(0,l.jsxs)(m.aY,{className:"text-sm pb-2 flex-1",children:[(0,l.jsxs)("div",{className:"py-2 flex items-center justify-between",children:[(0,l.jsxs)("div",{className:"flex items-center text-gray-600",children:[(0,l.jsx)(C.QT,{className:"w-4 h-4 mr-2 text-gray-500"}),(0,l.jsx)("span",{children:"Clusters (Running / Total)"})]}),(0,l.jsx)("span",{className:"font-normal text-gray-800",children:$?"...":"".concat(K.runningClusterCount," / ").concat(K.totalClusterCount)})]}),(0,l.jsxs)("div",{className:"py-2 flex items-center justify-between border-t border-gray-100",children:[(0,l.jsxs)("div",{className:"flex items-center text-gray-600",children:[(0,l.jsx)(C.Vp,{className:"w-4 h-4 mr-2 text-gray-500"}),(0,l.jsx)("span",{children:"Managed Jobs"})]}),(0,l.jsx)("span",{className:"font-normal text-gray-800",children:$?"...":K.managedJobsCount})]})]}),(0,l.jsxs)("div",{className:"px-6 pb-6 text-sm pt-3",children:[(0,l.jsx)("h4",{className:"mb-2 text-xs text-gray-500 tracking-wider",children:"Enabled Infra"}),(0,l.jsx)("div",{className:"flex flex-wrap gap-x-4 gap-y-1",children:$?(0,l.jsx)("span",{className:"text-gray-500",children:"Loading..."}):K.clouds.length>0?K.clouds.map(e=>(0,l.jsxs)("div",{className:"flex items-center text-gray-700",children:[(0,l.jsx)(C.Ye,{className:"w-3.5 h-3.5 mr-1.5 text-green-500"}),(0,l.jsx)("span",{children:e})]},e)):(0,l.jsx)("span",{className:"text-gray-500 italic",children:"No enabled infrastructure"})}),(0,l.jsx)("div",{className:"mt-4",children:(0,l.jsx)(P,{workspaceName:s,config:b,enabledClouds:K.clouds})}),(0,l.jsx)(Z,{workspaceConfig:b,allUsers:G})]})]})}),(0,l.jsx)("div",{className:r?"lg:col-span-3":"lg:col-span-2",children:(0,l.jsxs)(m.Zb,{className:"h-full flex flex-col",children:[(0,l.jsx)(m.Ol,{children:(0,l.jsx)(m.ll,{className:"text-base font-normal",children:r?"New Workspace YAML":"Edit Workspace YAML"})}),(0,l.jsx)(m.aY,{className:"flex-1 flex flex-col",children:(0,l.jsxs)("div",{className:"space-y-4 flex-1 flex flex-col",children:[B&&(0,l.jsx)(L.X,{error:B,onDismiss:()=>X(null)}),(0,l.jsxs)("div",{className:"flex-1 flex flex-col",children:[(0,l.jsxs)("p",{className:"text-sm text-gray-600 mb-3",children:["Configure infra-specific settings for this workspace. Leave empty to use all accessible infrastructure. Refer to"," ",(0,l.jsx)("a",{href:"https://docs.skypilot.co/en/latest/admin/workspaces.html#configuration",target:"_blank",rel:"noopener noreferrer",className:"text-blue-600",children:"SkyPilot Docs"})," ","for more details."]}),(0,l.jsxs)("div",{className:"mb-4",children:[(0,l.jsx)("h4",{className:"text-sm font-medium text-gray-700 mb-2",children:"Example configuration:"}),(0,l.jsx)("div",{className:"p-3 bg-gray-50 border rounded-lg",children:(0,l.jsx)("pre",{className:"text-xs font-mono text-gray-600 whitespace-pre-wrap",children:"".concat(s||"my-workspace",":\n private: true\n allowed_users:\n - user1@mydomain.com\n - user2@mydomain.com\n gcp:\n project_id: xxx\n disabled: false\n kubernetes:\n allowed_contexts:\n - context-1")})})]}),(0,l.jsx)(f.g,{value:k,onChange:e=>el(e.target.value),className:"font-mono text-sm flex-1 resize-none",style:{minHeight:"350px"},spellCheck:!1,placeholder:"# Enter workspace configuration in YAML format"}),(0,l.jsx)("div",{className:"flex justify-end space-x-3 pt-3 border-gray-200",children:(0,l.jsxs)(h.z,{onClick:et,disabled:O||B||_,className:"inline-flex items-center bg-sky-600 hover:bg-sky-700 text-white",children:[(0,l.jsx)(N.Z,{className:"w-4 h-4 mr-1.5"}),O?"Applying...":"Apply"]})})]})]})})]})})]})]}),(0,l.jsx)(w.Vq,{open:V.showDialog,onOpenChange:en,children:(0,l.jsxs)(w.cZ,{className:"sm:max-w-md",children:[(0,l.jsxs)(w.fK,{className:"",children:[(0,l.jsx)(w.$N,{children:"Delete Workspace"}),(0,l.jsxs)(w.Be,{children:['Are you sure you want to delete workspace "',s,'"? This action cannot be undone.']})]}),V.error&&(0,l.jsx)(L.X,{error:V.error,title:"Deletion Failed",onDismiss:()=>q(e=>({...e,error:null}))}),(0,l.jsxs)(w.cN,{className:"",children:[(0,l.jsx)(h.z,{variant:"outline",onClick:en,disabled:V.deleting,children:"Cancel"}),(0,l.jsx)(h.z,{variant:"destructive",onClick:ea,disabled:V.deleting,children:V.deleting?"Deleting...":"Delete"})]})]})})]})]})}}}]);