skypilot-nightly 1.0.0.dev20250922__py3-none-any.whl → 1.0.0.dev20250925__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 (111) hide show
  1. sky/__init__.py +2 -2
  2. sky/backends/backend.py +10 -0
  3. sky/backends/backend_utils.py +194 -69
  4. sky/backends/cloud_vm_ray_backend.py +37 -13
  5. sky/backends/local_docker_backend.py +9 -0
  6. sky/client/cli/command.py +104 -53
  7. sky/client/sdk.py +13 -5
  8. sky/client/sdk_async.py +4 -2
  9. sky/clouds/kubernetes.py +2 -1
  10. sky/clouds/runpod.py +20 -7
  11. sky/core.py +7 -53
  12. sky/dashboard/out/404.html +1 -1
  13. sky/dashboard/out/_next/static/{KP6HCNMqb_bnJB17oplgW → bn-NHt5qTzeTN2PefXuDA}/_buildManifest.js +1 -1
  14. sky/dashboard/out/_next/static/chunks/1121-b911fc0a0b4742f0.js +1 -0
  15. sky/dashboard/out/_next/static/chunks/6856-2b3600ff2854d066.js +1 -0
  16. sky/dashboard/out/_next/static/chunks/8969-d8bc3a2b9cf839a9.js +1 -0
  17. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-2cb9b15e09cda628.js +16 -0
  18. sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-9525660179df3605.js → [cluster]-e052384df65ef200.js} +1 -1
  19. sky/dashboard/out/_next/static/chunks/{webpack-26167a9e6d91fa51.js → webpack-16ba1d7187d2e3b1.js} +1 -1
  20. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  21. sky/dashboard/out/clusters/[cluster].html +1 -1
  22. sky/dashboard/out/clusters.html +1 -1
  23. sky/dashboard/out/config.html +1 -1
  24. sky/dashboard/out/index.html +1 -1
  25. sky/dashboard/out/infra/[context].html +1 -1
  26. sky/dashboard/out/infra.html +1 -1
  27. sky/dashboard/out/jobs/[job].html +1 -1
  28. sky/dashboard/out/jobs/pools/[pool].html +1 -1
  29. sky/dashboard/out/jobs.html +1 -1
  30. sky/dashboard/out/users.html +1 -1
  31. sky/dashboard/out/volumes.html +1 -1
  32. sky/dashboard/out/workspace/new.html +1 -1
  33. sky/dashboard/out/workspaces/[name].html +1 -1
  34. sky/dashboard/out/workspaces.html +1 -1
  35. sky/data/mounting_utils.py +19 -10
  36. sky/execution.py +4 -2
  37. sky/global_user_state.py +217 -36
  38. sky/jobs/client/sdk.py +10 -1
  39. sky/jobs/controller.py +7 -7
  40. sky/jobs/server/core.py +3 -3
  41. sky/jobs/server/server.py +15 -11
  42. sky/jobs/utils.py +1 -1
  43. sky/logs/agent.py +30 -3
  44. sky/logs/aws.py +9 -19
  45. sky/provision/__init__.py +2 -1
  46. sky/provision/aws/instance.py +2 -1
  47. sky/provision/azure/instance.py +2 -1
  48. sky/provision/cudo/instance.py +2 -2
  49. sky/provision/do/instance.py +2 -2
  50. sky/provision/docker_utils.py +41 -19
  51. sky/provision/fluidstack/instance.py +2 -2
  52. sky/provision/gcp/instance.py +2 -1
  53. sky/provision/hyperbolic/instance.py +2 -1
  54. sky/provision/instance_setup.py +1 -1
  55. sky/provision/kubernetes/instance.py +134 -8
  56. sky/provision/lambda_cloud/instance.py +2 -1
  57. sky/provision/nebius/instance.py +2 -1
  58. sky/provision/oci/instance.py +2 -1
  59. sky/provision/paperspace/instance.py +2 -2
  60. sky/provision/primeintellect/instance.py +2 -2
  61. sky/provision/provisioner.py +1 -0
  62. sky/provision/runpod/instance.py +2 -2
  63. sky/provision/scp/instance.py +2 -2
  64. sky/provision/seeweb/instance.py +2 -1
  65. sky/provision/vast/instance.py +2 -1
  66. sky/provision/vsphere/instance.py +6 -5
  67. sky/schemas/api/responses.py +2 -1
  68. sky/serve/autoscalers.py +2 -0
  69. sky/serve/client/impl.py +45 -19
  70. sky/serve/replica_managers.py +12 -5
  71. sky/serve/serve_utils.py +5 -7
  72. sky/serve/server/core.py +9 -6
  73. sky/serve/server/impl.py +78 -25
  74. sky/serve/server/server.py +4 -5
  75. sky/serve/service_spec.py +33 -0
  76. sky/server/constants.py +1 -1
  77. sky/server/daemons.py +2 -3
  78. sky/server/requests/executor.py +56 -6
  79. sky/server/requests/payloads.py +31 -8
  80. sky/server/requests/preconditions.py +2 -3
  81. sky/server/rest.py +2 -0
  82. sky/server/server.py +28 -19
  83. sky/server/stream_utils.py +34 -12
  84. sky/setup_files/dependencies.py +4 -1
  85. sky/setup_files/setup.py +44 -44
  86. sky/templates/kubernetes-ray.yml.j2 +16 -15
  87. sky/usage/usage_lib.py +3 -0
  88. sky/utils/cli_utils/status_utils.py +4 -5
  89. sky/utils/context.py +104 -29
  90. sky/utils/controller_utils.py +7 -6
  91. sky/utils/kubernetes/create_cluster.sh +13 -28
  92. sky/utils/kubernetes/delete_cluster.sh +10 -7
  93. sky/utils/kubernetes/generate_kind_config.py +6 -66
  94. sky/utils/kubernetes/kubernetes_deploy_utils.py +170 -37
  95. sky/utils/kubernetes_enums.py +5 -0
  96. sky/utils/ux_utils.py +35 -1
  97. sky/utils/yaml_utils.py +9 -0
  98. sky/volumes/client/sdk.py +44 -8
  99. sky/volumes/server/server.py +33 -7
  100. sky/volumes/volume.py +22 -14
  101. {skypilot_nightly-1.0.0.dev20250922.dist-info → skypilot_nightly-1.0.0.dev20250925.dist-info}/METADATA +40 -35
  102. {skypilot_nightly-1.0.0.dev20250922.dist-info → skypilot_nightly-1.0.0.dev20250925.dist-info}/RECORD +107 -107
  103. sky/dashboard/out/_next/static/chunks/1121-4ff1ec0dbc5792ab.js +0 -1
  104. sky/dashboard/out/_next/static/chunks/6856-9a2538f38c004652.js +0 -1
  105. sky/dashboard/out/_next/static/chunks/8969-a39efbadcd9fde80.js +0 -1
  106. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-1e9248ddbddcd122.js +0 -16
  107. /sky/dashboard/out/_next/static/{KP6HCNMqb_bnJB17oplgW → bn-NHt5qTzeTN2PefXuDA}/_ssgManifest.js +0 -0
  108. {skypilot_nightly-1.0.0.dev20250922.dist-info → skypilot_nightly-1.0.0.dev20250925.dist-info}/WHEEL +0 -0
  109. {skypilot_nightly-1.0.0.dev20250922.dist-info → skypilot_nightly-1.0.0.dev20250925.dist-info}/entry_points.txt +0 -0
  110. {skypilot_nightly-1.0.0.dev20250922.dist-info → skypilot_nightly-1.0.0.dev20250925.dist-info}/licenses/LICENSE +0 -0
  111. {skypilot_nightly-1.0.0.dev20250922.dist-info → skypilot_nightly-1.0.0.dev20250925.dist-info}/top_level.txt +0 -0
sky/client/cli/command.py CHANGED
@@ -127,6 +127,7 @@ def _get_cluster_records_and_set_ssh_config(
127
127
  clusters: Optional[List[str]],
128
128
  refresh: common.StatusRefreshMode = common.StatusRefreshMode.NONE,
129
129
  all_users: bool = False,
130
+ verbose: bool = False,
130
131
  ) -> List[responses.StatusResponse]:
131
132
  """Returns a list of clusters that match the glob pattern.
132
133
 
@@ -144,7 +145,8 @@ def _get_cluster_records_and_set_ssh_config(
144
145
  request_id = sdk.status(clusters,
145
146
  refresh=refresh,
146
147
  all_users=all_users,
147
- _include_credentials=True)
148
+ _include_credentials=True,
149
+ _summary_response=not verbose)
148
150
  cluster_records = sdk.stream_and_get(request_id)
149
151
  # Update the SSH config for all clusters
150
152
  for record in cluster_records:
@@ -1858,7 +1860,7 @@ def status(verbose: bool, refresh: bool, ip: bool, endpoints: bool,
1858
1860
 
1859
1861
  # Phase 3: Get cluster records and handle special cases
1860
1862
  cluster_records = _get_cluster_records_and_set_ssh_config(
1861
- query_clusters, refresh_mode, all_users)
1863
+ query_clusters, refresh_mode, all_users, verbose)
1862
1864
 
1863
1865
  # TOOD(zhwu): setup the ssh config for status
1864
1866
  if ip or show_endpoints:
@@ -4184,6 +4186,13 @@ def volumes_apply(
4184
4186
 
4185
4187
  logger.debug(f'Volume config: {volume.to_yaml_config()}')
4186
4188
 
4189
+ # TODO(kevin): remove the try block in v0.13.0
4190
+ try:
4191
+ volumes_sdk.validate(volume)
4192
+ except exceptions.APINotSupportedError:
4193
+ # Do best-effort client-side validation.
4194
+ volume.validate(skip_cloud_compatibility=True)
4195
+
4187
4196
  if not yes:
4188
4197
  click.confirm(f'Proceed to create volume {volume.name!r}?',
4189
4198
  default=True,
@@ -4780,7 +4789,7 @@ def pool():
4780
4789
  @pool.command('apply', cls=_DocumentedCodeCommand)
4781
4790
  @flags.config_option(expose_value=False)
4782
4791
  @click.argument('pool_yaml',
4783
- required=True,
4792
+ required=False,
4784
4793
  type=str,
4785
4794
  nargs=-1,
4786
4795
  **_get_shell_complete_args(_complete_file_name))
@@ -4799,13 +4808,18 @@ def pool():
4799
4808
  'with rolling update. If "blue_green", cluster pool will '
4800
4809
  'be updated with blue-green update. This option is only '
4801
4810
  'valid when the pool is already running.'))
4811
+ @click.option('--workers',
4812
+ default=None,
4813
+ type=int,
4814
+ required=False,
4815
+ help='Can be used to update the number of workers in the pool.')
4802
4816
  @_add_click_options(flags.TASK_OPTIONS + flags.EXTRA_RESOURCES_OPTIONS +
4803
4817
  flags.COMMON_OPTIONS)
4804
4818
  @flags.yes_option()
4805
4819
  @timeline.event
4806
4820
  @usage_lib.entrypoint
4807
4821
  def jobs_pool_apply(
4808
- pool_yaml: Tuple[str, ...],
4822
+ pool_yaml: Optional[Tuple[str, ...]],
4809
4823
  pool: Optional[str], # pylint: disable=redefined-outer-name
4810
4824
  workdir: Optional[str],
4811
4825
  infra: Optional[str],
@@ -4827,60 +4841,80 @@ def jobs_pool_apply(
4827
4841
  disk_tier: Optional[str],
4828
4842
  network_tier: Optional[str],
4829
4843
  mode: str,
4844
+ workers: Optional[int],
4830
4845
  yes: bool,
4831
4846
  async_call: bool,
4832
4847
  ):
4833
- """Apply a config to a cluster pool for managed jobs submission.
4834
-
4835
- If the pool is already running, the config will be applied to the pool.
4836
- Otherwise, a new pool will be created.
4837
-
4838
- POOL_YAML must point to a valid YAML file.
4848
+ """Either apply a config to a cluster pool for managed jobs submission
4849
+ or update the number of workers in the pool. One of POOL_YAML or --workers
4850
+ must be provided.
4851
+ Config:
4852
+ If the pool is already running, the config will be applied to the pool.
4853
+ Otherwise, a new pool will be created.
4854
+ Workers:
4855
+ The --workers option can be used to override the number of workers
4856
+ specified in the YAML file, or to update workers without a YAML file.
4857
+ Example:
4858
+ sky jobs pool apply -p my-pool --workers 5
4839
4859
  """
4840
4860
  cloud, region, zone = _handle_infra_cloud_region_zone_options(
4841
4861
  infra, cloud, region, zone)
4842
- if pool is None:
4843
- pool = serve_lib.generate_service_name(pool=True)
4862
+ if workers is not None and pool_yaml is not None and len(pool_yaml) > 0:
4863
+ raise click.UsageError(
4864
+ 'Cannot specify both --workers and POOL_YAML. Please use one of '
4865
+ 'them.')
4844
4866
 
4845
- task = _generate_task_with_service(
4846
- service_name=pool,
4847
- service_yaml_args=pool_yaml,
4848
- workdir=workdir,
4849
- cloud=cloud,
4850
- region=region,
4851
- zone=zone,
4852
- gpus=gpus,
4853
- cpus=cpus,
4854
- memory=memory,
4855
- instance_type=instance_type,
4856
- num_nodes=num_nodes,
4857
- use_spot=use_spot,
4858
- image_id=image_id,
4859
- env_file=env_file,
4860
- env=env,
4861
- secret=secret,
4862
- disk_size=disk_size,
4863
- disk_tier=disk_tier,
4864
- network_tier=network_tier,
4865
- ports=ports,
4866
- not_supported_cmd='sky jobs pool up',
4867
- pool=True,
4868
- )
4869
- assert task.service is not None
4870
- if not task.service.pool:
4871
- raise click.UsageError('The YAML file needs a `pool` section.')
4872
- click.secho('Pool spec:', fg='cyan')
4873
- click.echo(task.service)
4874
- serve_lib.validate_service_task(task, pool=True)
4867
+ if pool_yaml is None or len(pool_yaml) == 0:
4868
+ if pool is None:
4869
+ raise click.UsageError(
4870
+ 'A pool name must be provided to update the number of workers.')
4871
+ task = None
4872
+ click.secho(f'Attempting to update {pool} to have {workers} workers',
4873
+ fg='cyan')
4874
+ else:
4875
+ if pool is None:
4876
+ pool = serve_lib.generate_service_name(pool=True)
4877
+
4878
+ task = _generate_task_with_service(
4879
+ service_name=pool,
4880
+ service_yaml_args=pool_yaml,
4881
+ workdir=workdir,
4882
+ cloud=cloud,
4883
+ region=region,
4884
+ zone=zone,
4885
+ gpus=gpus,
4886
+ cpus=cpus,
4887
+ memory=memory,
4888
+ instance_type=instance_type,
4889
+ num_nodes=num_nodes,
4890
+ use_spot=use_spot,
4891
+ image_id=image_id,
4892
+ env_file=env_file,
4893
+ env=env,
4894
+ secret=secret,
4895
+ disk_size=disk_size,
4896
+ disk_tier=disk_tier,
4897
+ network_tier=network_tier,
4898
+ ports=ports,
4899
+ not_supported_cmd='sky jobs pool up',
4900
+ pool=True,
4901
+ )
4902
+ assert task.service is not None
4903
+ if not task.service.pool:
4904
+ raise click.UsageError('The YAML file needs a `pool` section.')
4905
+ click.secho('Pool spec:', fg='cyan')
4906
+ click.echo(task.service)
4907
+ serve_lib.validate_service_task(task, pool=True)
4875
4908
 
4876
- click.secho(
4877
- 'Each pool worker will use the following resources (estimated):',
4878
- fg='cyan')
4879
- with dag_lib.Dag() as dag:
4880
- dag.add(task)
4909
+ click.secho(
4910
+ 'Each pool worker will use the following resources (estimated):',
4911
+ fg='cyan')
4912
+ with dag_lib.Dag() as dag:
4913
+ dag.add(task)
4881
4914
 
4882
4915
  request_id = managed_jobs.pool_apply(task,
4883
4916
  pool,
4917
+ workers=workers,
4884
4918
  mode=serve_lib.UpdateMode(mode),
4885
4919
  _need_confirmation=not yes)
4886
4920
  _async_call_or_wait(request_id, async_call, 'sky.jobs.pool_apply')
@@ -5487,6 +5521,8 @@ def serve_update(
5487
5521
  sky serve update --mode blue_green sky-service-16aa new_service.yaml
5488
5522
 
5489
5523
  """
5524
+ # TODO(lloyd-brown): Add a way to update number of replicas for serve
5525
+ # the way we did for pools.
5490
5526
  cloud, region, zone = _handle_infra_cloud_region_zone_options(
5491
5527
  infra, cloud, region, zone)
5492
5528
  task = _generate_task_with_service(
@@ -5868,7 +5904,13 @@ def local():
5868
5904
  '--context-name',
5869
5905
  type=str,
5870
5906
  required=False,
5871
- help='Name to use for the kubeconfig context. Defaults to "default".')
5907
+ help='Name to use for the kubeconfig context. Defaults to "default". '
5908
+ 'Used with the ip list.')
5909
+ @click.option(
5910
+ '--name',
5911
+ type=str,
5912
+ required=False,
5913
+ help='Name of the cluster. Defaults to "skypilot". Used without ip list.')
5872
5914
  @click.option('--password',
5873
5915
  type=str,
5874
5916
  required=False,
@@ -5879,7 +5921,7 @@ def local():
5879
5921
  @_add_click_options(flags.COMMON_OPTIONS)
5880
5922
  @usage_lib.entrypoint
5881
5923
  def local_up(gpus: bool, ips: str, ssh_user: str, ssh_key_path: str,
5882
- cleanup: bool, context_name: Optional[str],
5924
+ cleanup: bool, context_name: Optional[str], name: Optional[str],
5883
5925
  password: Optional[str], async_call: bool):
5884
5926
  """Creates a local or remote cluster."""
5885
5927
 
@@ -5926,17 +5968,26 @@ def local_up(gpus: bool, ips: str, ssh_user: str, ssh_key_path: str,
5926
5968
  f'Failed to read SSH key file {ssh_key_path}: {str(e)}')
5927
5969
 
5928
5970
  request_id = sdk.local_up(gpus, ip_list, ssh_user, ssh_key, cleanup,
5929
- context_name, password)
5971
+ context_name, name, password)
5930
5972
  _async_call_or_wait(request_id, async_call, request_name='local up')
5931
5973
 
5932
5974
 
5975
+ @click.option('--name',
5976
+ type=str,
5977
+ required=False,
5978
+ help='Name of the cluster to down. Defaults to "skypilot".')
5933
5979
  @local.command('down', cls=_DocumentedCodeCommand)
5934
5980
  @flags.config_option(expose_value=False)
5935
5981
  @_add_click_options(flags.COMMON_OPTIONS)
5936
5982
  @usage_lib.entrypoint
5937
- def local_down(async_call: bool):
5938
- """Deletes a local cluster."""
5939
- request_id = sdk.local_down()
5983
+ def local_down(name: Optional[str], async_call: bool):
5984
+ """Deletes a local cluster.
5985
+
5986
+ This will only delete a local cluster started without the ip list.
5987
+ To clean up the local cluster started with a ip list, use `sky local up`
5988
+ with the cleanup flag.
5989
+ """
5990
+ request_id = sdk.local_down(name)
5940
5991
  _async_call_or_wait(request_id, async_call, request_name='sky.local.down')
5941
5992
 
5942
5993
 
sky/client/sdk.py CHANGED
@@ -1429,6 +1429,7 @@ def status(
1429
1429
  all_users: bool = False,
1430
1430
  *,
1431
1431
  _include_credentials: bool = False,
1432
+ _summary_response: bool = False,
1432
1433
  ) -> server_common.RequestId[List[responses.StatusResponse]]:
1433
1434
  """Gets cluster statuses.
1434
1435
 
@@ -1514,6 +1515,7 @@ def status(
1514
1515
  refresh=refresh,
1515
1516
  all_users=all_users,
1516
1517
  include_credentials=_include_credentials,
1518
+ summary_response=_summary_response,
1517
1519
  )
1518
1520
  response = server_common.make_authenticated_request(
1519
1521
  'POST', '/status', json=json.loads(body.model_dump_json()))
@@ -1675,6 +1677,7 @@ def local_up(gpus: bool,
1675
1677
  ssh_key: Optional[str],
1676
1678
  cleanup: bool,
1677
1679
  context_name: Optional[str] = None,
1680
+ name: Optional[str] = None,
1678
1681
  password: Optional[str] = None) -> server_common.RequestId[None]:
1679
1682
  """Launches a Kubernetes cluster on local machines.
1680
1683
 
@@ -1686,8 +1689,8 @@ def local_up(gpus: bool,
1686
1689
  # TODO: move this check to server.
1687
1690
  if not server_common.is_api_server_local():
1688
1691
  with ux_utils.print_exception_no_traceback():
1689
- raise ValueError(
1690
- 'sky local up is only supported when running SkyPilot locally.')
1692
+ raise ValueError('`sky local up` is only supported when '
1693
+ 'running SkyPilot locally.')
1691
1694
 
1692
1695
  body = payloads.LocalUpBody(gpus=gpus,
1693
1696
  ips=ips,
@@ -1695,6 +1698,7 @@ def local_up(gpus: bool,
1695
1698
  ssh_key=ssh_key,
1696
1699
  cleanup=cleanup,
1697
1700
  context_name=context_name,
1701
+ name=name,
1698
1702
  password=password)
1699
1703
  response = server_common.make_authenticated_request(
1700
1704
  'POST', '/local_up', json=json.loads(body.model_dump_json()))
@@ -1704,16 +1708,19 @@ def local_up(gpus: bool,
1704
1708
  @usage_lib.entrypoint
1705
1709
  @server_common.check_server_healthy_or_start
1706
1710
  @annotations.client_api
1707
- def local_down() -> server_common.RequestId[None]:
1711
+ def local_down(name: Optional[str]) -> server_common.RequestId[None]:
1708
1712
  """Tears down the Kubernetes cluster started by local_up."""
1709
1713
  # We do not allow local up when the API server is running remotely since it
1710
1714
  # will modify the kubeconfig.
1711
1715
  # TODO: move this check to remote server.
1712
1716
  if not server_common.is_api_server_local():
1713
1717
  with ux_utils.print_exception_no_traceback():
1714
- raise ValueError('sky local down is only supported when running '
1718
+ raise ValueError('`sky local down` is only supported when running '
1715
1719
  'SkyPilot locally.')
1716
- response = server_common.make_authenticated_request('POST', '/local_down')
1720
+
1721
+ body = payloads.LocalDownBody(name=name)
1722
+ response = server_common.make_authenticated_request(
1723
+ 'POST', '/local_down', json=json.loads(body.model_dump_json()))
1717
1724
  return server_common.get_request_id(response)
1718
1725
 
1719
1726
 
@@ -2083,6 +2090,7 @@ def stream_and_get(
2083
2090
  return stream_response(request_id,
2084
2091
  response,
2085
2092
  output_stream,
2093
+ resumable=True,
2086
2094
  get_result=follow)
2087
2095
 
2088
2096
 
sky/client/sdk_async.py CHANGED
@@ -661,13 +661,14 @@ async def local_up(
661
661
  ssh_key: Optional[str],
662
662
  cleanup: bool,
663
663
  context_name: Optional[str] = None,
664
+ name: Optional[str] = None,
664
665
  password: Optional[str] = None,
665
666
  stream_logs: Optional[StreamConfig] = DEFAULT_STREAM_CONFIG) -> None:
666
667
  """Async version of local_up() that launches a Kubernetes cluster on
667
668
  local machines."""
668
669
  request_id = await context_utils.to_thread(sdk.local_up, gpus, ips,
669
670
  ssh_user, ssh_key, cleanup,
670
- context_name, password)
671
+ context_name, name, password)
671
672
  if stream_logs is not None:
672
673
  return await _stream_and_get(request_id, stream_logs)
673
674
  else:
@@ -677,10 +678,11 @@ async def local_up(
677
678
  @usage_lib.entrypoint
678
679
  @annotations.client_api
679
680
  async def local_down(
681
+ name: Optional[str] = None,
680
682
  stream_logs: Optional[StreamConfig] = DEFAULT_STREAM_CONFIG) -> None:
681
683
  """Async version of local_down() that tears down the Kubernetes cluster
682
684
  started by local_up."""
683
- request_id = await context_utils.to_thread(sdk.local_down)
685
+ request_id = await context_utils.to_thread(sdk.local_down, name)
684
686
  if stream_logs is not None:
685
687
  return await _stream_and_get(request_id, stream_logs)
686
688
  else:
sky/clouds/kubernetes.py CHANGED
@@ -62,6 +62,7 @@ class Kubernetes(clouds.Cloud):
62
62
  _SUPPORTS_SERVICE_ACCOUNT_ON_REMOTE = True
63
63
 
64
64
  _DEFAULT_NUM_VCPUS = 2
65
+ _DEFAULT_NUM_VCPUS_WITH_GPU = 4
65
66
  _DEFAULT_MEMORY_CPU_RATIO = 1
66
67
  _DEFAULT_MEMORY_CPU_RATIO_WITH_GPU = 4 # Allocate more memory for GPU tasks
67
68
  _REPR = 'Kubernetes'
@@ -842,7 +843,7 @@ class Kubernetes(clouds.Cloud):
842
843
 
843
844
  gpu_task_cpus = k8s_instance_type.cpus
844
845
  if resources.cpus is None:
845
- gpu_task_cpus = gpu_task_cpus * acc_count
846
+ gpu_task_cpus = self._DEFAULT_NUM_VCPUS_WITH_GPU * acc_count
846
847
  # Special handling to bump up memory multiplier for GPU instances
847
848
  gpu_task_memory = (float(resources.memory.strip('+')) if
848
849
  resources.memory is not None else gpu_task_cpus *
sky/clouds/runpod.py CHANGED
@@ -286,14 +286,16 @@ class RunPod(clouds.Cloud):
286
286
  @classmethod
287
287
  def _check_credentials(cls) -> Tuple[bool, Optional[str]]:
288
288
  """Verify that the user has valid credentials for RunPod. """
289
- dependency_error_msg = ('Failed to import runpod. '
290
- 'To install, run: pip install skypilot[runpod]')
289
+ dependency_error_msg = ('Failed to import runpod or TOML parser. '
290
+ 'Install: pip install "skypilot[runpod]".')
291
291
  try:
292
292
  runpod_spec = import_lib_util.find_spec('runpod')
293
293
  if runpod_spec is None:
294
294
  return False, dependency_error_msg
295
- toml_spec = import_lib_util.find_spec('toml')
296
- if toml_spec is None:
295
+ # Prefer stdlib tomllib (Python 3.11+); fallback to tomli
296
+ tomllib_spec = import_lib_util.find_spec('tomllib')
297
+ tomli_spec = import_lib_util.find_spec('tomli')
298
+ if tomllib_spec is None and tomli_spec is None:
297
299
  return False, dependency_error_msg
298
300
  except ValueError:
299
301
  # docstring of importlib_util.find_spec:
@@ -322,9 +324,20 @@ class RunPod(clouds.Cloud):
322
324
  if not os.path.exists(credential_file):
323
325
  return False, '~/.runpod/config.toml does not exist.'
324
326
 
325
- # we don't need to import toml here if config.toml does not exist,
326
- # wait until we know the cred file exists.
327
- import tomli as toml # pylint: disable=import-outside-toplevel
327
+ # We don't need to import TOML parser if config.toml does not exist.
328
+ # When needed, prefer stdlib tomllib (py>=3.11); otherwise use tomli.
329
+ # TODO(andy): remove this fallback after dropping Python 3.10 support.
330
+ try:
331
+ try:
332
+ import tomllib as toml # pylint: disable=import-outside-toplevel
333
+ except ModuleNotFoundError: # py<3.11
334
+ import tomli as toml # pylint: disable=import-outside-toplevel
335
+ except ModuleNotFoundError:
336
+ # Should never happen. We already installed proper dependencies for
337
+ # different Python versions in setup_files/dependencies.py.
338
+ return False, (
339
+ '~/.runpod/config.toml exists but no TOML parser is available. '
340
+ 'Install tomli for Python < 3.11: pip install tomli.')
328
341
 
329
342
  # Check for default api_key
330
343
  try:
sky/core.py CHANGED
@@ -1,6 +1,4 @@
1
1
  """SDK functions for cluster/job management."""
2
- import os
3
- import shlex
4
2
  import typing
5
3
  from typing import Any, Dict, List, Optional, Tuple, Union
6
4
 
@@ -9,7 +7,6 @@ import colorama
9
7
  from sky import admin_policy
10
8
  from sky import backends
11
9
  from sky import catalog
12
- from sky import check as sky_check
13
10
  from sky import clouds
14
11
  from sky import dag as dag_lib
15
12
  from sky import data
@@ -31,7 +28,6 @@ from sky.schemas.api import responses
31
28
  from sky.skylet import autostop_lib
32
29
  from sky.skylet import constants
33
30
  from sky.skylet import job_lib
34
- from sky.skylet import log_lib
35
31
  from sky.usage import usage_lib
36
32
  from sky.utils import admin_policy_utils
37
33
  from sky.utils import common
@@ -102,6 +98,7 @@ def status(
102
98
  refresh: common.StatusRefreshMode = common.StatusRefreshMode.NONE,
103
99
  all_users: bool = False,
104
100
  include_credentials: bool = False,
101
+ summary_response: bool = False,
105
102
  ) -> List[responses.StatusResponse]:
106
103
  # NOTE(dev): Keep the docstring consistent between the Python API and CLI.
107
104
  """Gets cluster statuses.
@@ -181,7 +178,8 @@ def status(
181
178
  refresh=refresh,
182
179
  cluster_names=cluster_names,
183
180
  all_users=all_users,
184
- include_credentials=include_credentials)
181
+ include_credentials=include_credentials,
182
+ summary_response=summary_response)
185
183
 
186
184
  status_responses = []
187
185
  for cluster in clusters:
@@ -1301,6 +1299,7 @@ def local_up(gpus: bool,
1301
1299
  ssh_key: Optional[str],
1302
1300
  cleanup: bool,
1303
1301
  context_name: Optional[str] = None,
1302
+ name: Optional[str] = None,
1304
1303
  password: Optional[str] = None) -> None:
1305
1304
  """Creates a local or remote cluster."""
1306
1305
 
@@ -1331,57 +1330,12 @@ def local_up(gpus: bool,
1331
1330
  password)
1332
1331
  else:
1333
1332
  # Run local deployment (kind) if no remote args are specified
1334
- kubernetes_deploy_utils.deploy_local_cluster(gpus)
1333
+ kubernetes_deploy_utils.deploy_local_cluster(name, gpus)
1335
1334
 
1336
1335
 
1337
- def local_down() -> None:
1336
+ def local_down(name: Optional[str] = None) -> None:
1338
1337
  """Tears down the Kubernetes cluster started by local_up."""
1339
- cluster_removed = False
1340
-
1341
- path_to_package = os.path.dirname(__file__)
1342
- down_script_path = os.path.join(path_to_package, 'utils/kubernetes',
1343
- 'delete_cluster.sh')
1344
-
1345
- cwd = os.path.dirname(os.path.abspath(down_script_path))
1346
- run_command = shlex.split(down_script_path)
1347
-
1348
- # Setup logging paths
1349
- run_timestamp = sky_logging.get_run_timestamp()
1350
- log_path = os.path.join(constants.SKY_LOGS_DIRECTORY, run_timestamp,
1351
- 'local_down.log')
1352
-
1353
- with rich_utils.safe_status(
1354
- ux_utils.spinner_message('Removing local cluster',
1355
- log_path=log_path,
1356
- is_local=True)):
1357
-
1358
- returncode, stdout, stderr = log_lib.run_with_log(cmd=run_command,
1359
- log_path=log_path,
1360
- require_outputs=True,
1361
- stream_logs=False,
1362
- cwd=cwd)
1363
- stderr = stderr.replace('No kind clusters found.\n', '')
1364
-
1365
- if returncode == 0:
1366
- cluster_removed = True
1367
- elif returncode == 100:
1368
- logger.info(ux_utils.error_message('Local cluster does not exist.'))
1369
- else:
1370
- with ux_utils.print_exception_no_traceback():
1371
- raise RuntimeError('Failed to create local cluster. '
1372
- f'Stdout: {stdout}'
1373
- f'\nError: {stderr}')
1374
- if cluster_removed:
1375
- # Run sky check
1376
- with rich_utils.safe_status(
1377
- ux_utils.spinner_message('Running sky check...')):
1378
- sky_check.check_capability(sky_cloud.CloudCapability.COMPUTE,
1379
- clouds=['kubernetes'],
1380
- quiet=True)
1381
- logger.info(
1382
- ux_utils.finishing_message('Local cluster removed.',
1383
- log_path=log_path,
1384
- is_local=True))
1338
+ kubernetes_deploy_utils.teardown_local_cluster(name)
1385
1339
 
1386
1340
 
1387
1341
  @usage_lib.entrypoint
@@ -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-26167a9e6d91fa51.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/KP6HCNMqb_bnJB17oplgW/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/KP6HCNMqb_bnJB17oplgW/_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":"KP6HCNMqb_bnJB17oplgW","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-16ba1d7187d2e3b1.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/bn-NHt5qTzeTN2PefXuDA/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/bn-NHt5qTzeTN2PefXuDA/_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":"bn-NHt5qTzeTN2PefXuDA","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,n,b,o,j,i,r,d){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,n,j,b,i,"static/chunks/6856-9a2538f38c004652.js",r,d,"static/chunks/9037-472ee1222cb1e158.js","static/chunks/pages/clusters/[cluster]-9525660179df3605.js"],"/clusters/[cluster]/[job]":[s,c,a,f,e,t,b,"static/chunks/pages/clusters/[cluster]/[job]-1e9248ddbddcd122.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,n,"static/chunks/pages/jobs/pools/[pool]-07349868f7905d37.js"],"/jobs/[job]":[s,c,a,f,u,o,e,t,n,b,"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-7528cc0ef8c522c5.js"],"/workspaces/[name]":[s,c,a,f,u,"static/chunks/1836-37fede578e2da5f8.js",e,t,n,j,b,i,r,d,"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-a39efbadcd9fde80.js","static/chunks/6135-4b4d5e824b7f9d3c.js","static/chunks/754-d0da8ab45f9509e9.js","static/chunks/6990-f6818c84ed8f1c86.js","static/chunks/1121-4ff1ec0dbc5792ab.js","static/chunks/6601-06114c982db410b6.js","static/chunks/3015-88c7c8d69b0b6dba.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
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-2b3600ff2854d066.js",r,k,"static/chunks/9037-472ee1222cb1e158.js","static/chunks/pages/clusters/[cluster]-e052384df65ef200.js"],"/clusters/[cluster]/[job]":[s,c,a,f,e,t,n,"static/chunks/pages/clusters/[cluster]/[job]-2cb9b15e09cda628.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-7528cc0ef8c522c5.js"],"/workspaces/[name]":[s,c,a,f,u,"static/chunks/1836-37fede578e2da5f8.js",e,t,b,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-d8bc3a2b9cf839a9.js","static/chunks/6135-4b4d5e824b7f9d3c.js","static/chunks/754-d0da8ab45f9509e9.js","static/chunks/6990-f6818c84ed8f1c86.js","static/chunks/1121-b911fc0a0b4742f0.js","static/chunks/6601-06114c982db410b6.js","static/chunks/3015-88c7c8d69b0b6dba.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
@@ -0,0 +1 @@
1
+ "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1121],{50326:function(e,t,a){a.d(t,{$N:function(){return m},Be:function(){return h},Vq:function(){return c},cN:function(){return _},cZ:function(){return d},fK:function(){return f}});var r=a(85893),s=a(67294),o=a(6327),n=a(32350),l=a(43767);let c=o.fC;o.xz;let u=o.h_;o.x8;let i=s.forwardRef((e,t)=>{let{className:a,...s}=e;return(0,r.jsx)(o.aV,{ref:t,className:(0,n.cn)("fixed inset-0 z-50 bg-black/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",a),...s})});i.displayName=o.aV.displayName;let d=s.forwardRef((e,t)=>{let{className:a,children:s,...c}=e;return(0,r.jsxs)(u,{children:[(0,r.jsx)(i,{}),(0,r.jsxs)(o.VY,{ref:t,className:(0,n.cn)("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-gray-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",a),...c,children:[s,(0,r.jsxs)(o.x8,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-gray-100 data-[state=open]:text-gray-500",children:[(0,r.jsx)(l.Z,{className:"h-4 w-4"}),(0,r.jsx)("span",{className:"sr-only",children:"Close"})]})]})]})});d.displayName=o.VY.displayName;let f=e=>{let{className:t,...a}=e;return(0,r.jsx)("div",{className:(0,n.cn)("flex flex-col space-y-1.5 text-center sm:text-left",t),...a})};f.displayName="DialogHeader";let _=e=>{let{className:t,...a}=e;return(0,r.jsx)("div",{className:(0,n.cn)("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",t),...a})};_.displayName="DialogFooter";let m=s.forwardRef((e,t)=>{let{className:a,...s}=e;return(0,r.jsx)(o.Dx,{ref:t,className:(0,n.cn)("text-lg font-semibold leading-none tracking-tight",a),...s})});m.displayName=o.Dx.displayName;let h=s.forwardRef((e,t)=>{let{className:a,...s}=e;return(0,r.jsx)(o.dk,{ref:t,className:(0,n.cn)("text-sm text-gray-500",a),...s})});h.displayName=o.dk.displayName},23266:function(e,t,a){a.d(t,{GH:function(){return f},QL:function(){return m},Sl:function(){return d},getClusters:function(){return u},uR:function(){return i}});var r=a(67294),s=a(15821),o=a(47145),n=a(93225),l=a(6378);let c={UP:"RUNNING",STOPPED:"STOPPED",INIT:"LAUNCHING",null:"TERMINATED"};async function u(){let{clusterNames:e=null}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};try{return(await o.x.fetch("/status",{cluster_names:e,all_users:!0,include_credentials:!1,summary_response:null==e})).map(e=>{let t="",a=t=e.zone?e.zone:e.region;return t&&t.length>25&&(t=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:15;if(!e||e.length<=t)return e;if(t<=3)return"...";let a=Math.floor((t-3)/2),r=a+(t-3)%2;return 0===a?e.substring(0,r)+"...":e.substring(0,r)+"..."+e.substring(e.length-a)}(t,25)),{status:c[e.status],cluster:e.name,user:e.user_name,user_hash:e.user_hash,cluster_hash:e.cluster_hash,cloud:e.cloud,region:e.region,infra:t?e.cloud+" ("+t+")":e.cloud,full_infra:a?"".concat(e.cloud," (").concat(a,")"):e.cloud,cpus:e.cpus,mem:e.memory,gpus:e.accelerators,resources_str:e.resources_str,resources_str_full:e.resources_str_full,time:new Date(1e3*e.launched_at),num_nodes:e.nodes,workspace:e.workspace,autostop:e.autostop,last_event:e.last_event,to_down:e.to_down,cluster_name_on_cloud:e.cluster_name_on_cloud,jobs:[],command:e.last_creation_command||e.last_use,task_yaml:e.last_creation_yaml||"{}",events:[{time:new Date(1e3*e.launched_at),event:"Cluster created."}]}})}catch(e){return console.error("Error fetching clusters:",e),[]}}async function i(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;try{let t={days:30,dashboard_summary_response:!0};e&&(t.cluster_hashes=[e]);let a=await o.x.fetch("/cost_report",t);console.log("Raw cluster history data:",a);let r=a.map(e=>{let t="Unknown";e.cloud?t=e.cloud:e.resources&&e.resources.cloud&&(t=e.resources.cloud);let a=e.user_name||"-";return{status:e.status?c[e.status]:"TERMINATED",cluster:e.name,user:a,user_hash:e.user_hash,cluster_hash:e.cluster_hash,cloud:t,region:"",infra:t,full_infra:t,resources_str:e.resources_str,resources_str_full:e.resources_str_full,time:e.launched_at?new Date(1e3*e.launched_at):null,num_nodes:e.num_nodes||1,duration:e.duration,total_cost:e.total_cost,workspace:e.workspace||"default",autostop:-1,last_event:e.last_event,to_down:!1,cluster_name_on_cloud:null,usage_intervals:e.usage_intervals,command:e.last_creation_command||"",task_yaml:e.last_creation_yaml||"{}",events:[{time:e.launched_at?new Date(1e3*e.launched_at):new Date,event:"Cluster created."}]}});return console.log("Processed cluster history data:",r),r}catch(e){return console.error("Error fetching cluster history:",e),[]}}async function d(e){let{clusterName:t,jobId:a,onNewLog:r,workspace:n}=e;try{await o.x.stream("/logs",{follow:!1,cluster_name:t,job_id:a,tail:1e4,override_skypilot_config:{active_workspace:n||"default"}},r)}catch(e){console.error("Error in streamClusterJobLogs:",e),(0,s.C)("Error in streamClusterJobLogs: ".concat(e.message),"error")}}async function f(e){let{clusterName:t,jobIds:a=null,workspace:r}=e;try{let e=await o.x.fetch("/download_logs",{cluster_name:t,job_ids:a?a.map(String):null,override_skypilot_config:{active_workspace:r||"default"}}),l=Object.values(e||{});if(!l.length){(0,s.C)("No logs found to download.","warning");return}let c=window.location.origin,u="".concat(c).concat(n.f4,"/download"),i=await fetch("".concat(u,"?relative=items"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({folder_paths:l})});if(!i.ok){let e=await i.text();throw Error("Download failed: ".concat(i.status," ").concat(e))}let d=await i.blob(),f=window.URL.createObjectURL(d),_=document.createElement("a"),m=new Date().toISOString().replace(/[:.]/g,"-"),h=a&&1===a.length?"job-".concat(a[0]):"jobs";_.href=f,_.download="".concat(t,"-").concat(h,"-logs-").concat(m,".zip"),document.body.appendChild(_),_.click(),_.remove(),window.URL.revokeObjectURL(f)}catch(e){console.error("Error downloading logs:",e),(0,s.C)("Error downloading logs: ".concat(e.message),"error")}}async function _(e){let{clusterName:t,workspace:a}=e;try{return(await o.x.fetch("/queue",{cluster_name:t,all_users:!0,override_skypilot_config:{active_workspace:a}})).map(e=>{var r;let s=e.end_at?e.end_at:Date.now()/1e3,o=0,n=0;return e.submitted_at&&(o=s-e.submitted_at),e.start_at&&(n=s-e.start_at),{id:e.job_id,status:e.status,job:e.job_name,user:e.username,user_hash:e.user_hash,gpus:e.accelerators||{},submitted_at:e.submitted_at?new Date(1e3*e.submitted_at):null,resources:e.resources,cluster:t,total_duration:o,job_duration:n,infra:"",logs:"",workspace:a||"default",git_commit:(null===(r=e.metadata)||void 0===r?void 0:r.git_commit)||"-"}})}catch(e){return console.error("Error fetching cluster jobs:",e),[]}}function m(e){let{cluster:t,job:a=null}=e,[s,o]=(0,r.useState)(null),[n,c]=(0,r.useState)(null),[i,d]=(0,r.useState)(!0),[f,m]=(0,r.useState)(!0),h=(0,r.useCallback)(async()=>{if(t)try{d(!0);let e=await l.default.get(u,[{clusterNames:[t]}]);return o(e[0]),e[0]}catch(e){console.error("Error fetching cluster data:",e)}finally{d(!1)}return null},[t]),g=(0,r.useCallback)(async e=>{if(t)try{m(!0);let a=await l.default.get(_,[{clusterName:t,workspace:e||"default"}]);c(a)}catch(e){console.error("Error fetching cluster job data:",e)}finally{m(!1)}},[t]),p=(0,r.useCallback)(async()=>{l.default.invalidate(u,[{clusterNames:[t]}]);let e=await h();e&&(l.default.invalidate(_,[{clusterName:t,workspace:e.workspace||"default"}]),await g(e.workspace))},[h,g,t]),w=(0,r.useCallback)(async()=>{s&&(l.default.invalidate(_,[{clusterName:t,workspace:s.workspace||"default"}]),await g(s.workspace))},[g,s,t]);return(0,r.useEffect)(()=>{(async()=>{let e=await h();e&&g(e.workspace)})()},[t,a,h,g]),{clusterData:s,clusterJobData:n,loading:i,clusterDetailsLoading:i,clusterJobsLoading:f,refreshData:p,refreshClusterJobsOnly:w}}},53081:function(e,t,a){a.d(t,{R:function(){return s}}),a(23266),a(68969);var r=a(47145);async function s(){try{let e=await r.x.get("/users");if(!e.ok)throw Error("HTTP error! status: ".concat(e.status));return(await e.json()).map(e=>({userId:e.id,username:e.name,role:e.role,created_at:e.created_at}))||[]}catch(e){return console.error("Failed to fetch users:",e),[]}}}}]);
@@ -0,0 +1 @@
1
+ "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[6856],{32045:function(e,t,o){o.d(t,{Cu:function(){return s},R8:function(){return u},Xg:function(){return c},ef:function(){return n}});var r=o(93225),a=o(47145);async function n(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=(await Promise.resolve().then(o.bind(o,6378))).default,{getClusters:n}=await Promise.resolve().then(o.bind(o,23266)),{getManagedJobs:s}=await Promise.resolve().then(o.bind(o,68969));try{let o=await t.get(s,[{allUsers:!0}]),c=(null==o?void 0:o.jobs)||[],l=await t.get(n),i=[];try{if(e){console.log("Force refreshing clouds by running sky check...");try{let e=await a.x.post("/check",{}),t=e.headers.get("X-Skypilot-Request-ID")||e.headers.get("X-Request-ID"),o=await a.x.get("/api/get?request_id=".concat(t)),r=await o.json();console.log("Sky check completed:",r)}catch(e){console.error("Error running sky check:",e)}}let t=await a.x.get("/enabled_clouds"),o=t.headers.get("X-Skypilot-Request-ID")||t.headers.get("X-Request-ID"),r=await a.x.get("/api/get?request_id=".concat(o)),n=await r.json();i=n.return_value?JSON.parse(n.return_value):[],console.log("Enabled clouds:",i)}catch(e){console.error("Error fetching enabled clouds:",e),i=[]}let u={};r.$m.forEach(e=>{let t=i.includes(e.toLowerCase());u[e]={name:e,clusters:0,jobs:0,enabled:t}}),(l||[]).forEach(e=>{if(e.cloud){let t=e.cloud;u[t]&&(u[t].clusters+=1,u[t].enabled=!0)}}),c.forEach(e=>{if(e.cloud){let t=e.cloud;u[t]&&(u[t].jobs+=1,u[t].enabled=!0)}});let d=r.$m.length,g=Object.values(u).filter(e=>e.enabled).length;return{clouds:Object.values(u).filter(e=>e.enabled).sort((e,t)=>e.name.localeCompare(t.name)),totalClouds:d,enabledClouds:g}}catch(e){return console.error("Error fetching cloud infrastructure:",e),{clouds:[],totalClouds:r.$m.length,enabledClouds:0}}}async function s(){return await c()}async function c(){try{console.log("[DEBUG] Starting workspace-aware infrastructure fetch");let{getWorkspaces:e}=await Promise.resolve().then(o.bind(o,17324));console.log("[DEBUG] About to call getWorkspaces()");let t=await e();if(console.log("[DEBUG] Workspaces data received:",t),console.log("[DEBUG] Number of accessible workspaces:",Object.keys(t||{}).length),console.log("[DEBUG] Workspace names:",Object.keys(t||{})),!t||0===Object.keys(t).length)return console.log("[DEBUG] No accessible workspaces found - returning empty result"),{workspaces:{},allContextNames:[],allGPUs:[],perContextGPUs:[],perNodeGPUs:[],contextStats:{},contextWorkspaceMap:{}};let{getEnabledClouds:r}=await Promise.resolve().then(o.bind(o,17324)),a={},n=[],s={};for(let[e,o]of Object.entries(t)){console.log("Fetching infrastructure for workspace: ".concat(e));try{console.log("[DEBUG] Fetching enabled clouds for workspace: ".concat(e));let t=await r(e,!0);console.log("[DEBUG] Expanded clouds for ".concat(e,":"),t),a[e]={config:o,clouds:t,contexts:[]},console.log("[DEBUG] Processing expandedClouds for ".concat(e,":"),t),t&&Array.isArray(t)?t.forEach(t=>{if(console.log("[DEBUG] Processing infraItem: ".concat(t)),t.toLowerCase().startsWith("kubernetes/")){let o=t.replace(/^kubernetes\//i,"");console.log("[DEBUG] Extracted kubernetes context: ".concat(o)),n.push(o),s[o]||(s[o]=[]),s[o].includes(e)||s[o].push(e),a[e].contexts.push(o)}else if(t.toLowerCase().startsWith("ssh/")){let o=t.replace(/^ssh\//i,""),r="ssh-".concat(o);console.log("[DEBUG] Extracted SSH context: ".concat(r)),n.push(r),s[r]||(s[r]=[]),s[r].includes(e)||s[r].push(e),a[e].contexts.push(r)}}):console.log("[DEBUG] No expanded clouds or not an array for ".concat(e))}catch(t){console.error("Failed to fetch infrastructure for workspace ".concat(e,":"),t),a[e]={config:o,clouds:[],contexts:[],error:t.message}}}let{getClusters:c}=await Promise.resolve().then(o.bind(o,23266)),i=(await Promise.resolve().then(o.bind(o,6378))).default,u=await i.get(c),g=await d(u||[]),f=n.filter(e=>e&&"string"==typeof e),p=await l(f),h={workspaces:a,allContextNames:[...new Set(n)].sort(),allGPUs:p.allGPUs||[],perContextGPUs:p.perContextGPUs||[],perNodeGPUs:p.perNodeGPUs||[],contextStats:g,contextWorkspaceMap:s};return console.log("[DEBUG] Final result:",h),console.log("[DEBUG] All contexts found:",n),console.log("[DEBUG] Context workspace map:",s),h}catch(e){return console.error("[DEBUG] Failed to fetch workspace infrastructure:",e),console.error("[DEBUG] Error stack:",e.stack),{workspaces:{},allContextNames:[],allGPUs:[],perContextGPUs:[],perNodeGPUs:[],contextStats:{},contextWorkspaceMap:{},error:e.message}}}async function l(e){try{var t,o,r,a,n,s;if(!e||0===e.length)return{allGPUs:[],perContextGPUs:[],perNodeGPUs:[]};let c={},l={},u={},d=await Promise.all(e.map(e=>i(e))),g={};for(let t=0;t<e.length;t++)g[e[t]]=d[t];for(let r of e){let e=g[r]||{};if(e&&Object.keys(e).length>0){let a={};for(let n in e){let s=e[n];if(!s){console.warn("No node data for node ".concat(n," in context ").concat(r));continue}let c=s.accelerator_type,l=(null===(t=s.total)||void 0===t?void 0:t.accelerator_count)||0,i=(null===(o=s.free)||void 0===o?void 0:o.accelerators_available)||0;l>0&&(a[c]||(a[c]={gpu_name:c,gpu_requestable_qty_per_node:0,gpu_total:0,gpu_free:0,context:r}),a[c].gpu_total+=l,a[c].gpu_free+=i,a[c].gpu_requestable_qty_per_node=l)}for(let e in l[r]=Object.values(a),a)e in c?(c[e].gpu_total+=a[e].gpu_total,c[e].gpu_free+=a[e].gpu_free):c[e]={gpu_total:a[e].gpu_total,gpu_free:a[e].gpu_free,gpu_name:e}}else l[r]=[]}for(let t of e){let e=g[t];if(e&&Object.keys(e).length>0)for(let o in e){let i=e[o];if(!i){console.warn("No node data for node ".concat(o," in context ").concat(t));continue}let d=i.accelerator_type||"-",g=null!==(n=null===(r=i.total)||void 0===r?void 0:r.accelerator_count)&&void 0!==n?n:0,f=null!==(s=null===(a=i.free)||void 0===a?void 0:a.accelerators_available)&&void 0!==s?s:0;u["".concat(t,"/").concat(o)]={node_name:i.name||o,gpu_name:d,gpu_total:g,gpu_free:f,ip_address:i.ip_address||null,context:t},"-"===d||!l[t]||l[t].some(e=>e.gpu_name===d)||(d in c||(c[d]={gpu_total:0,gpu_free:0,gpu_name:d}),l[t].find(e=>e.gpu_name===d)||l[t].push({gpu_name:d,gpu_requestable_qty_per_node:"-",gpu_total:0,gpu_free:0,context:t}))}}return{allGPUs:Object.values(c).sort((e,t)=>e.gpu_name.localeCompare(t.gpu_name)),perContextGPUs:Object.values(l).flat().sort((e,t)=>e.context.localeCompare(t.context)||e.gpu_name.localeCompare(t.gpu_name)),perNodeGPUs:Object.values(u).sort((e,t)=>e.context.localeCompare(t.context)||e.node_name.localeCompare(t.node_name)||e.gpu_name.localeCompare(t.gpu_name))}}catch(e){return console.error("[infra.jsx] Error in getKubernetesGPUsFromContexts:",e),{allGPUs:[],perContextGPUs:[],perNodeGPUs:[]}}}async function i(e){try{let t=await a.x.post("/kubernetes_node_info",{context:e}),o=t.headers.get("X-Skypilot-Request-ID")||t.headers.get("x-request-id"),r=await a.x.get("/api/get?request_id=".concat(o));if(500===r.status){try{let t=await r.json();if(t.detail&&t.detail.error)try{let o=JSON.parse(t.detail.error);console.warn("[infra.jsx] Context ".concat(e," unavailable:"),o.message)}catch(e){console.error("Error parsing JSON:",e)}}catch(e){console.error("Error parsing JSON:",e)}return{}}let n=await r.json();return(n.return_value?JSON.parse(n.return_value):{}).node_info_dict||{}}catch(t){return console.warn("[infra.jsx] Context ".concat(e," unavailable or timed out:"),t.message),{}}}async function u(e){try{let t={};return e.forEach(e=>{let o=null;if("Kubernetes"===e.cloud)(o=e.region)&&(o="kubernetes/".concat(o));else if("SSH"===e.cloud&&(o=e.region)){let e=o.startsWith("ssh-")?o.substring(4):o;o="ssh/".concat(e)}o&&(t[o]||(t[o]={clusters:0,jobs:0}),t[o].jobs+=1)}),t}catch(e){return console.error("=== Error in getContextJobs ===",e),{}}}async function d(e){try{let t={};return e.forEach(e=>{let o=null;if("Kubernetes"===e.cloud)(o=e.region)&&(o="kubernetes/".concat(o));else if("SSH"===e.cloud&&(o=e.region)){let e=o.startsWith("ssh-")?o.substring(4):o;o="ssh/".concat(e)}o&&(t[o]||(t[o]={clusters:0,jobs:0}),t[o].clusters+=1)}),t}catch(e){return console.error("=== Error in getContextClusters ===",e),{}}}},29326:function(e,t,o){o.d(t,{IS:function(){return d},It:function(){return n},MV:function(){return c},Ri:function(){return s},_x:function(){return i},ez:function(){return u},hY:function(){return l},mF:function(){return f},wJ:function(){return g}});var r=o(93225),a=o(15821);async function n(){try{let e=await fetch("".concat(r.f4,"/ssh_node_pools"),{method:"GET",headers:{"Content-Type":"application/json"}});if(!e.ok)throw Error("HTTP error! status: ".concat(e.status));return await e.json()}catch(e){return console.error("Error fetching SSH Node Pools:",e),{}}}async function s(e){try{let t=await fetch("".concat(r.f4,"/ssh_node_pools"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok)throw Error("HTTP error! status: ".concat(t.status));return await t.json()}catch(e){throw console.error("Error updating SSH Node Pools:",e),e}}async function c(e){try{let t=await fetch("".concat(r.f4,"/ssh_node_pools/").concat(e),{method:"DELETE",headers:{"Content-Type":"application/json"}});if(!t.ok)throw Error("HTTP error! status: ".concat(t.status));return await t.json()}catch(e){throw console.error("Error deleting SSH Node Pool:",e),e}}async function l(e,t){try{let o=new FormData;o.append("key_name",e),o.append("key_file",t);let a=await fetch("".concat(r.f4,"/ssh_node_pools/keys"),{method:"POST",body:o});if(!a.ok)throw Error("HTTP error! status: ".concat(a.status));return await a.json()}catch(e){throw console.error("Error uploading SSH key:",e),e}}async function i(e){try{let t=await fetch("".concat(r.f4,"/ssh_node_pools/").concat(e,"/deploy"),{method:"POST",headers:{"Content-Type":"application/json"}});if(!t.ok)throw Error("HTTP error! status: ".concat(t.status));return await t.json()}catch(e){throw console.error("Error deploying SSH Node Pool:",e),e}}async function u(e){try{let t=await fetch("".concat(r.f4,"/ssh_node_pools/").concat(e,"/down"),{method:"POST",headers:{"Content-Type":"application/json"}});if(!t.ok)throw Error("HTTP error! status: ".concat(t.status));return await t.json()}catch(e){throw console.error("Error tearing down SSH Node Pool:",e),e}}async function d(e){try{let t=await fetch("".concat(r.f4,"/ssh_node_pools/").concat(e,"/status"),{method:"GET",headers:{"Content-Type":"application/json"}});if(!t.ok)throw Error("HTTP error! status: ".concat(t.status));return await t.json()}catch(e){throw console.error("Error fetching SSH Node Pool status:",e),e}}async function g(e){let t,{requestId:o,signal:n,onNewLog:s}=e,c=Date.now(),l=new Promise(e=>{let o=()=>{let r=Date.now()-c;r>=3e5?e({timeout:!0}):t=setTimeout(o,3e5-r)};t=setTimeout(o,3e5)}),i=(async()=>{try{let e=await fetch("".concat(r.f4,"/api/stream?request_id=").concat(o,"&format=plain&tail=").concat(1e3,"&follow=true"),{method:"GET",headers:{"Content-Type":"application/json"},...n?{signal:n}:{}});if(!e.ok)throw Error("HTTP error! status: ".concat(e.status));let a=e.body.getReader();try{for(;;){let{done:e,value:t}=await a.read();if(e)break;c=Date.now();let o=new TextDecoder().decode(t);s(o)}}finally{a.cancel(),t&&clearTimeout(t)}return{timeout:!1}}catch(e){if(t&&clearTimeout(t),"AbortError"===e.name)return{timeout:!1};throw e}})(),u=await Promise.race([i,l]);if(t&&clearTimeout(t),u.timeout){(0,a.C)("SSH deployment log stream timed out after ".concat(300,"s of inactivity"),"warning");return}}async function f(e){let t,{requestId:o,signal:n,onNewLog:s,operationType:c="operation"}=e,l=Date.now(),i=new Promise(e=>{let o=()=>{let r=Date.now()-l;r>=3e5?e({timeout:!0}):t=setTimeout(o,3e5-r)};t=setTimeout(o,3e5)}),u=(async()=>{try{let e=await fetch("".concat(r.f4,"/api/stream?request_id=").concat(o,"&format=plain&tail=").concat(1e3,"&follow=true"),{method:"GET",headers:{"Content-Type":"application/json"},...n?{signal:n}:{}});if(!e.ok)throw Error("HTTP error! status: ".concat(e.status));let a=e.body.getReader();try{for(;;){let{done:e,value:t}=await a.read();if(e)break;l=Date.now();let o=new TextDecoder().decode(t);s(o)}}finally{a.cancel(),t&&clearTimeout(t)}return{timeout:!1}}catch(e){if(t&&clearTimeout(t),"AbortError"===e.name)return{timeout:!1};throw e}})(),d=await Promise.race([u,i]);if(t&&clearTimeout(t),d.timeout){(0,a.C)("SSH ".concat(c," log stream timed out after ").concat(300,"s of inactivity"),"warning");return}}},19238:function(e,t,o){o.d(t,{C:function(){return a},w:function(){return n}});var r=o(47145);async function a(){try{return(await r.x.fetch("/volumes",{},"GET")).map(e=>{var t,o,r;let a=e.cloud||"";return e.region&&(a+="/".concat(e.region)),e.zone&&(a+="/".concat(e.zone)),{name:e.name,launched_at:e.launched_at,user_hash:e.user_hash,user_name:e.user_name||"-",workspace:e.workspace||"-",last_attached_at:e.last_attached_at,status:e.status,type:e.type,cloud:e.cloud,region:e.region,zone:e.zone,infra:a,size:"".concat(e.size,"Gi"),config:e.config,storage_class:(null===(t=e.config)||void 0===t?void 0:t.storage_class_name)||"-",access_mode:(null===(o=e.config)||void 0===o?void 0:o.access_mode)||"-",namespace:(null===(r=e.config)||void 0===r?void 0:r.namespace)||"-",name_on_cloud:e.name_on_cloud,usedby_pods:e.usedby_pods,usedby_clusters:e.usedby_clusters}})||[]}catch(e){return console.error("Failed to fetch volumes:",e),[]}}async function n(e){let t="";try{let o=await r.x.post("/volumes/delete",{names:[e]}),a=o.headers.get("X-SkyPilot-Request-ID")||o.headers.get("X-Request-ID"),n=await r.x.get("/api/get?request_id=".concat(a));if(500===n.status){try{let e=await n.json();if(e.detail&&e.detail.error)try{t=JSON.parse(e.detail.error).message}catch(e){console.error("Error parsing JSON:",e)}}catch(e){console.error("Error parsing JSON:",e)}return{success:!1,msg:t}}return{success:!0}}catch(e){return console.error("Failed to delete volume:",e),{success:!1,msg:e.message}}}},36856:function(e,t,o){var r=o(6378),a=o(23266),n=o(68969),s=o(17324),c=o(53081),l=o(19238),i=o(32045),u=o(29326);let d={base:{getClusters:{fn:a.getClusters,args:[]},getManagedJobs:{fn:n.aT,args:[{allUsers:!0}]},getWorkspaces:{fn:s.getWorkspaces,args:[]},getUsers:{fn:c.R,args:[]},getGPUs:{fn:i.Cu,args:[]},getCloudInfrastructure:{fn:i.ef,args:[!1]},getSSHNodePools:{fn:u.It,args:[]},getVolumes:{fn:l.C,args:[]}},dynamic:{getEnabledClouds:{fn:s.getEnabledClouds,requiresWorkspaces:!0}},pages:{clusters:["getClusters","getWorkspaces"],jobs:["getManagedJobs","getClusters","getWorkspaces","getUsers"],infra:["getClusters","getManagedJobs","getGPUs","getCloudInfrastructure","getSSHNodePools"],workspaces:["getWorkspaces","getClusters","getManagedJobs","getEnabledClouds"],users:["getUsers","getClusters","getManagedJobs"],volumes:["getVolumes"]}};class g{async preloadForPage(e,t){let{backgroundPreload:o=!0,force:r=!1}=t||{};if(!d.pages[e]){console.warn("Unknown page: ".concat(e));return}console.log("[CachePreloader] Preloading cache for page: ".concat(e));try{await this._loadPageData(e,r),o&&this._backgroundPreloadOtherPages(e)}catch(t){console.error("[CachePreloader] Error preloading for page ".concat(e,":"),t)}}async _loadPageData(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],o=d.pages[e],a=[];for(let e of o)if(d.base[e]){let{fn:o,args:n}=d.base[e];t&&r.default.invalidate(o,n),a.push(r.default.get(o,n).then(e=>(this._markAsPreloaded(o,n),e)))}else"getEnabledClouds"===e&&a.push(this._loadEnabledCloudsForAllWorkspaces(t));await Promise.allSettled(a),console.log("[CachePreloader] Loaded data for page: ".concat(e))}async _loadEnabledCloudsForAllWorkspaces(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];try{e&&r.default.invalidate(s.getWorkspaces);let t=await r.default.get(s.getWorkspaces),o=Object.keys(t||{}).map(t=>(e&&r.default.invalidate(s.getEnabledClouds,[t]),r.default.get(s.getEnabledClouds,[t])));await Promise.allSettled(o)}catch(e){console.error("[CachePreloader] Error loading enabled clouds:",e)}}_backgroundPreloadOtherPages(e){if(this.isPreloading)return;this.isPreloading=!0;let t=new Set(d.pages[e]),o=new Set;Object.keys(d.pages).filter(t=>t!==e).forEach(e=>{d.pages[e].forEach(e=>{t.has(e)||o.add(e)})}),console.log("[CachePreloader] Background preloading ".concat(o.size," unique functions: ").concat(Array.from(o).join(", "))),Promise.allSettled(Array.from(o).map(async e=>{try{if(d.base[e]){let{fn:t,args:o}=d.base[e];await r.default.get(t,o),this._markAsPreloaded(t,o)}else"getEnabledClouds"===e&&await this._loadEnabledCloudsForAllWorkspaces(!1);console.log("[CachePreloader] Background loaded function: ".concat(e))}catch(t){console.error("[CachePreloader] Background load failed for function ".concat(e,":"),t)}})).then(()=>{this.isPreloading=!1,console.log("[CachePreloader] Background preloading complete")})}async preloadBaseFunctions(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];console.log("[CachePreloader] Preloading all base functions");let t=Object.entries(d.base).map(t=>{let[o,{fn:a,args:n}]=t;return e&&r.default.invalidate(a,n),r.default.get(a,n).catch(e=>{console.error("[CachePreloader] Failed to preload ".concat(o,":"),e)})});await Promise.allSettled(t),console.log("[CachePreloader] Base functions preloaded")}getCacheStats(){return{...r.default.getStats(),isPreloading:this.isPreloading}}wasRecentlyPreloaded(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],o=this._generateKey(e,t),r=this.recentlyPreloaded.get(o);if(!r)return!1;let a=Date.now()-r<this.PRELOAD_GRACE_PERIOD;return a||this.recentlyPreloaded.delete(o),a}_markAsPreloaded(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],o=this._generateKey(e,t);this.recentlyPreloaded.set(o,Date.now())}_generateKey(e,t){let o=e.toString(),r=this._simpleHash(o),a=t.length>0?JSON.stringify(t):"";return"".concat(r,"_").concat(a)}_simpleHash(e){let t=5381;for(let o=0;o<e.length;o++)t=(t<<5)+t+e.charCodeAt(o);return t>>>0}clearCache(){r.default.clear(),this.isPreloading=!1,this.preloadPromises.clear(),this.recentlyPreloaded.clear(),console.log("[CachePreloader] Cache cleared")}constructor(){this.isPreloading=!1,this.preloadPromises=new Map,this.recentlyPreloaded=new Map,this.PRELOAD_GRACE_PERIOD=5e3}}let f=new g;r.default.setPreloader(f),t.ZP=f}}]);