skypilot-nightly 1.0.0.dev20250523__py3-none-any.whl → 1.0.0.dev20250526__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.
Files changed (95) hide show
  1. sky/__init__.py +2 -2
  2. sky/backends/backend_utils.py +62 -45
  3. sky/backends/cloud_vm_ray_backend.py +3 -1
  4. sky/check.py +335 -170
  5. sky/cli.py +56 -13
  6. sky/client/cli.py +56 -13
  7. sky/client/sdk.py +54 -10
  8. sky/clouds/gcp.py +19 -3
  9. sky/core.py +5 -2
  10. sky/dashboard/out/404.html +1 -1
  11. sky/dashboard/out/_next/static/7GEgRyZKRaSnYZCV1Jwol/_buildManifest.js +1 -0
  12. sky/dashboard/out/_next/static/chunks/25-062253ea41fb8eec.js +6 -0
  13. sky/dashboard/out/_next/static/chunks/480-5a0de8b6570ea105.js +1 -0
  14. sky/dashboard/out/_next/static/chunks/488-50d843fdb5396d32.js +15 -0
  15. sky/dashboard/out/_next/static/chunks/498-d7722313e5e5b4e6.js +21 -0
  16. sky/dashboard/out/_next/static/chunks/573-f17bd89d9f9118b3.js +66 -0
  17. sky/dashboard/out/_next/static/chunks/578-d351125af46c293f.js +6 -0
  18. sky/dashboard/out/_next/static/chunks/734-a6e01d7f98904741.js +1 -0
  19. sky/dashboard/out/_next/static/chunks/937.f97f83652028e944.js +1 -0
  20. sky/dashboard/out/_next/static/chunks/938-59956af3950b02ed.js +1 -0
  21. sky/dashboard/out/_next/static/chunks/9f96d65d-5a3e4af68c26849e.js +1 -0
  22. sky/dashboard/out/_next/static/chunks/pages/_app-96a715a6fb01e228.js +1 -0
  23. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-3b5aad09a25f64b7.js +1 -0
  24. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-9529d9e882a0e75c.js +16 -0
  25. sky/dashboard/out/_next/static/chunks/pages/clusters-9e6d1ec6e1ac5b29.js +1 -0
  26. sky/dashboard/out/_next/static/chunks/pages/infra-abb7d744ecf15109.js +1 -0
  27. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-48dc8d67d4b60be1.js +1 -0
  28. sky/dashboard/out/_next/static/chunks/pages/jobs-73d5e0c369d00346.js +16 -0
  29. sky/dashboard/out/_next/static/chunks/pages/users-b8acf6e6735323a2.js +1 -0
  30. sky/dashboard/out/_next/static/chunks/pages/workspace/new-bbf436f41381e169.js +1 -0
  31. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-7733c960685b4385.js +1 -0
  32. sky/dashboard/out/_next/static/chunks/pages/workspaces-5ed48b3201b998c8.js +1 -0
  33. sky/dashboard/out/_next/static/chunks/webpack-deda68c926e8d0bc.js +1 -0
  34. sky/dashboard/out/_next/static/css/28558d57108b05ae.css +3 -0
  35. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  36. sky/dashboard/out/clusters/[cluster].html +1 -1
  37. sky/dashboard/out/clusters.html +1 -1
  38. sky/dashboard/out/index.html +1 -1
  39. sky/dashboard/out/infra.html +1 -1
  40. sky/dashboard/out/jobs/[job].html +1 -1
  41. sky/dashboard/out/jobs.html +1 -1
  42. sky/dashboard/out/users.html +1 -0
  43. sky/dashboard/out/workspace/new.html +1 -0
  44. sky/dashboard/out/workspaces/[name].html +1 -0
  45. sky/dashboard/out/workspaces.html +1 -0
  46. sky/data/storage.py +1 -1
  47. sky/global_user_state.py +606 -543
  48. sky/jobs/constants.py +1 -1
  49. sky/jobs/server/core.py +72 -56
  50. sky/jobs/state.py +26 -5
  51. sky/jobs/utils.py +65 -13
  52. sky/optimizer.py +6 -3
  53. sky/provision/fluidstack/instance.py +1 -0
  54. sky/serve/server/core.py +9 -6
  55. sky/server/html/token_page.html +6 -1
  56. sky/server/requests/executor.py +1 -0
  57. sky/server/requests/payloads.py +28 -0
  58. sky/server/server.py +59 -5
  59. sky/setup_files/dependencies.py +1 -0
  60. sky/skylet/constants.py +4 -1
  61. sky/skypilot_config.py +107 -11
  62. sky/utils/cli_utils/status_utils.py +18 -8
  63. sky/utils/db_utils.py +53 -0
  64. sky/utils/kubernetes/config_map_utils.py +133 -0
  65. sky/utils/kubernetes/deploy_remote_cluster.py +166 -147
  66. sky/utils/kubernetes/kubernetes_deploy_utils.py +49 -5
  67. sky/utils/kubernetes/ssh-tunnel.sh +20 -28
  68. sky/utils/log_utils.py +4 -0
  69. sky/utils/schemas.py +54 -0
  70. sky/workspaces/__init__.py +0 -0
  71. sky/workspaces/core.py +295 -0
  72. sky/workspaces/server.py +62 -0
  73. {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250526.dist-info}/METADATA +2 -1
  74. {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250526.dist-info}/RECORD +79 -63
  75. sky/dashboard/out/_next/static/ECKwDNS9v9y3_IKFZ2lpp/_buildManifest.js +0 -1
  76. sky/dashboard/out/_next/static/chunks/236-1a3a9440417720eb.js +0 -6
  77. sky/dashboard/out/_next/static/chunks/312-c3c8845990db8ffc.js +0 -15
  78. sky/dashboard/out/_next/static/chunks/37-d584022b0da4ac3b.js +0 -6
  79. sky/dashboard/out/_next/static/chunks/393-e1eaa440481337ec.js +0 -1
  80. sky/dashboard/out/_next/static/chunks/480-f28cd152a98997de.js +0 -1
  81. sky/dashboard/out/_next/static/chunks/582-683f4f27b81996dc.js +0 -59
  82. sky/dashboard/out/_next/static/chunks/pages/_app-8cfab319f9fb3ae8.js +0 -1
  83. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-33bc2bec322249b1.js +0 -1
  84. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-e2fc2dd1955e6c36.js +0 -1
  85. sky/dashboard/out/_next/static/chunks/pages/clusters-3a748bd76e5c2984.js +0 -1
  86. sky/dashboard/out/_next/static/chunks/pages/infra-abf08c4384190a39.js +0 -1
  87. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-70756c2dad850a7e.js +0 -1
  88. sky/dashboard/out/_next/static/chunks/pages/jobs-ecd804b9272f4a7c.js +0 -1
  89. sky/dashboard/out/_next/static/chunks/webpack-830f59b8404e96b8.js +0 -1
  90. sky/dashboard/out/_next/static/css/7e7ce4ff31d3977b.css +0 -3
  91. /sky/dashboard/out/_next/static/{ECKwDNS9v9y3_IKFZ2lpp → 7GEgRyZKRaSnYZCV1Jwol}/_ssgManifest.js +0 -0
  92. {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250526.dist-info}/WHEEL +0 -0
  93. {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250526.dist-info}/entry_points.txt +0 -0
  94. {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250526.dist-info}/licenses/LICENSE +0 -0
  95. {skypilot_nightly-1.0.0.dev20250523.dist-info → skypilot_nightly-1.0.0.dev20250526.dist-info}/top_level.txt +0 -0
sky/cli.py CHANGED
@@ -353,12 +353,12 @@ _TASK_OPTIONS = [
353
353
  type=str,
354
354
  help='Infrastructure to use. '
355
355
  'Format: cloud, cloud/region, cloud/region/zone, '
356
- 'or kubernetes/context-name. '
356
+ 'k8s/context-name, or ssh/node-pool-name. '
357
357
  'Examples: aws, aws/us-east-1, aws/us-east-1/us-east-1a, '
358
358
  # TODO(zhwu): we have to use `\*` to make sure the docs build
359
359
  # not complaining about the `*`, but this will cause `--help`
360
360
  # to show `\*` instead of `*`.
361
- 'aws/\\*/us-east-1a, kubernetes/my-cluster-context.'),
361
+ 'aws/\\*/us-east-1a, k8s/my-context, ssh/my-nodes.'),
362
362
  click.option(
363
363
  '--cloud',
364
364
  required=False,
@@ -1781,9 +1781,13 @@ def _show_endpoint(query_clusters: Optional[List[str]],
1781
1781
  return
1782
1782
 
1783
1783
 
1784
- def _show_enabled_infra():
1784
+ def _show_enabled_infra(active_workspace: str, show_workspace: bool):
1785
1785
  """Show the enabled infrastructure."""
1786
- title = (f'{colorama.Fore.CYAN}{colorama.Style.BRIGHT}Enabled Infra:'
1786
+ workspace_str = ''
1787
+ if show_workspace:
1788
+ workspace_str = f' (workspace: {active_workspace!r})'
1789
+ title = (f'{colorama.Fore.CYAN}{colorama.Style.BRIGHT}Enabled Infra'
1790
+ f'{workspace_str}:'
1787
1791
  f'{colorama.Style.RESET_ALL} ')
1788
1792
  enabled_clouds = sdk.get(sdk.enabled_clouds())
1789
1793
  enabled_ssh_infras = []
@@ -1954,6 +1958,7 @@ def status(verbose: bool, refresh: bool, ip: bool, endpoints: bool,
1954
1958
  # status query.
1955
1959
  service_status_request_id = serve_lib.status(service_names=None)
1956
1960
 
1961
+ workspace_request_id = None
1957
1962
  if ip or show_endpoints:
1958
1963
  if refresh:
1959
1964
  raise click.UsageError(
@@ -1988,9 +1993,18 @@ def status(verbose: bool, refresh: bool, ip: bool, endpoints: bool,
1988
1993
  ('endpoint port'
1989
1994
  if show_single_endpoint else 'endpoints')))
1990
1995
  else:
1991
- _show_enabled_infra()
1992
- click.echo(f'{colorama.Fore.CYAN}{colorama.Style.BRIGHT}Clusters'
1993
- f'{colorama.Style.RESET_ALL}')
1996
+ try:
1997
+ workspace_request_id = sdk.workspaces()
1998
+ except RuntimeError:
1999
+ # Backward compatibility for API server before #5660.
2000
+ # TODO(zhwu): remove this after 0.12.0.
2001
+ logger.warning(f'{colorama.Style.DIM}SkyPilot API server is '
2002
+ 'in an old version, and may miss feature: '
2003
+ 'workspaces. Update with: sky api stop; '
2004
+ 'sky api start'
2005
+ f'{colorama.Style.RESET_ALL}')
2006
+ workspace_request_id = None
2007
+
1994
2008
  query_clusters: Optional[List[str]] = None if not clusters else clusters
1995
2009
  refresh_mode = common.StatusRefreshMode.NONE
1996
2010
  if refresh:
@@ -2013,9 +2027,20 @@ def status(verbose: bool, refresh: bool, ip: bool, endpoints: bool,
2013
2027
  else:
2014
2028
  normal_clusters.append(cluster_record)
2015
2029
 
2030
+ if workspace_request_id is not None:
2031
+ all_workspaces = sdk.get(workspace_request_id)
2032
+ else:
2033
+ all_workspaces = [constants.SKYPILOT_DEFAULT_WORKSPACE]
2034
+ active_workspace = skypilot_config.get_active_workspace()
2035
+ show_workspace = len(all_workspaces) > 1
2036
+ _show_enabled_infra(active_workspace, show_workspace)
2037
+ click.echo(f'{colorama.Fore.CYAN}{colorama.Style.BRIGHT}Clusters'
2038
+ f'{colorama.Style.RESET_ALL}')
2039
+
2016
2040
  num_pending_autostop = 0
2017
2041
  num_pending_autostop += status_utils.show_status_table(
2018
- normal_clusters + controllers, verbose, all_users, query_clusters)
2042
+ normal_clusters + controllers, verbose, all_users, query_clusters,
2043
+ show_workspace)
2019
2044
 
2020
2045
  managed_jobs_query_interrupted = False
2021
2046
  if show_managed_jobs:
@@ -3345,9 +3370,16 @@ def _down_or_stop_clusters(
3345
3370
  is_flag=True,
3346
3371
  default=False,
3347
3372
  help='Show the activated account for each cloud.')
3373
+ @click.option(
3374
+ '--workspace',
3375
+ '-w',
3376
+ type=str,
3377
+ help='The workspace to check. If None, all workspaces will be checked.')
3348
3378
  @usage_lib.entrypoint
3349
3379
  # pylint: disable=redefined-outer-name
3350
- def check(infra_list: Tuple[str], verbose: bool):
3380
+ def check(infra_list: Tuple[str],
3381
+ verbose: bool,
3382
+ workspace: Optional[str] = None):
3351
3383
  """Check which clouds are available to use.
3352
3384
 
3353
3385
  This checks access credentials for all clouds supported by SkyPilot. If a
@@ -3370,7 +3402,9 @@ def check(infra_list: Tuple[str], verbose: bool):
3370
3402
  sky check aws gcp
3371
3403
  """
3372
3404
  infra_arg = infra_list if len(infra_list) > 0 else None
3373
- request_id = sdk.check(infra_list=infra_arg, verbose=verbose)
3405
+ request_id = sdk.check(infra_list=infra_arg,
3406
+ verbose=verbose,
3407
+ workspace=workspace)
3374
3408
  sdk.stream_and_get(request_id)
3375
3409
  api_server_url = server_common.get_server_url()
3376
3410
  click.echo()
@@ -4447,7 +4481,8 @@ def jobs_cancel(name: Optional[str], job_ids: Tuple[int], all: bool, yes: bool,
4447
4481
  f'Provided {" ".join(arguments)!r}.')
4448
4482
 
4449
4483
  if not yes:
4450
- job_identity_str = (f'managed jobs with IDs {job_id_str}'
4484
+ plural = 's' if len(job_ids) > 1 else ''
4485
+ job_identity_str = (f'managed job{plural} with ID{plural} {job_id_str}'
4451
4486
  if job_ids else repr(name))
4452
4487
  if all_users:
4453
4488
  job_identity_str = 'all managed jobs FOR ALL USERS'
@@ -6169,10 +6204,14 @@ def api_status(request_ids: Optional[List[str]], all_status: bool,
6169
6204
  '-e',
6170
6205
  required=False,
6171
6206
  help='The SkyPilot API server endpoint.')
6207
+ @click.option('--get-token',
6208
+ is_flag=True,
6209
+ default=False,
6210
+ help='Force token-based login.')
6172
6211
  @usage_lib.entrypoint
6173
- def api_login(endpoint: Optional[str]):
6212
+ def api_login(endpoint: Optional[str], get_token: bool):
6174
6213
  """Logs into a SkyPilot API server."""
6175
- sdk.api_login(endpoint)
6214
+ sdk.api_login(endpoint, get_token)
6176
6215
 
6177
6216
 
6178
6217
  @api.command('info', cls=_DocumentedCodeCommand)
@@ -6184,6 +6223,10 @@ def api_info():
6184
6223
  api_server_info = sdk.api_info()
6185
6224
  user_name = os.getenv(constants.USER_ENV_VAR, getpass.getuser())
6186
6225
  user_hash = common_utils.get_user_hash()
6226
+ api_server_user = api_server_info.get('user')
6227
+ if api_server_user is not None:
6228
+ user_name = api_server_user['name']
6229
+ user_hash = api_server_user['id']
6187
6230
  dashboard_url = server_common.get_dashboard_url(url)
6188
6231
  click.echo(f'Using SkyPilot API server: {url}\n'
6189
6232
  f'{ux_utils.INDENT_SYMBOL}Status: {api_server_info["status"]}, '
sky/client/cli.py CHANGED
@@ -353,12 +353,12 @@ _TASK_OPTIONS = [
353
353
  type=str,
354
354
  help='Infrastructure to use. '
355
355
  'Format: cloud, cloud/region, cloud/region/zone, '
356
- 'or kubernetes/context-name. '
356
+ 'k8s/context-name, or ssh/node-pool-name. '
357
357
  'Examples: aws, aws/us-east-1, aws/us-east-1/us-east-1a, '
358
358
  # TODO(zhwu): we have to use `\*` to make sure the docs build
359
359
  # not complaining about the `*`, but this will cause `--help`
360
360
  # to show `\*` instead of `*`.
361
- 'aws/\\*/us-east-1a, kubernetes/my-cluster-context.'),
361
+ 'aws/\\*/us-east-1a, k8s/my-context, ssh/my-nodes.'),
362
362
  click.option(
363
363
  '--cloud',
364
364
  required=False,
@@ -1781,9 +1781,13 @@ def _show_endpoint(query_clusters: Optional[List[str]],
1781
1781
  return
1782
1782
 
1783
1783
 
1784
- def _show_enabled_infra():
1784
+ def _show_enabled_infra(active_workspace: str, show_workspace: bool):
1785
1785
  """Show the enabled infrastructure."""
1786
- title = (f'{colorama.Fore.CYAN}{colorama.Style.BRIGHT}Enabled Infra:'
1786
+ workspace_str = ''
1787
+ if show_workspace:
1788
+ workspace_str = f' (workspace: {active_workspace!r})'
1789
+ title = (f'{colorama.Fore.CYAN}{colorama.Style.BRIGHT}Enabled Infra'
1790
+ f'{workspace_str}:'
1787
1791
  f'{colorama.Style.RESET_ALL} ')
1788
1792
  enabled_clouds = sdk.get(sdk.enabled_clouds())
1789
1793
  enabled_ssh_infras = []
@@ -1954,6 +1958,7 @@ def status(verbose: bool, refresh: bool, ip: bool, endpoints: bool,
1954
1958
  # status query.
1955
1959
  service_status_request_id = serve_lib.status(service_names=None)
1956
1960
 
1961
+ workspace_request_id = None
1957
1962
  if ip or show_endpoints:
1958
1963
  if refresh:
1959
1964
  raise click.UsageError(
@@ -1988,9 +1993,18 @@ def status(verbose: bool, refresh: bool, ip: bool, endpoints: bool,
1988
1993
  ('endpoint port'
1989
1994
  if show_single_endpoint else 'endpoints')))
1990
1995
  else:
1991
- _show_enabled_infra()
1992
- click.echo(f'{colorama.Fore.CYAN}{colorama.Style.BRIGHT}Clusters'
1993
- f'{colorama.Style.RESET_ALL}')
1996
+ try:
1997
+ workspace_request_id = sdk.workspaces()
1998
+ except RuntimeError:
1999
+ # Backward compatibility for API server before #5660.
2000
+ # TODO(zhwu): remove this after 0.12.0.
2001
+ logger.warning(f'{colorama.Style.DIM}SkyPilot API server is '
2002
+ 'in an old version, and may miss feature: '
2003
+ 'workspaces. Update with: sky api stop; '
2004
+ 'sky api start'
2005
+ f'{colorama.Style.RESET_ALL}')
2006
+ workspace_request_id = None
2007
+
1994
2008
  query_clusters: Optional[List[str]] = None if not clusters else clusters
1995
2009
  refresh_mode = common.StatusRefreshMode.NONE
1996
2010
  if refresh:
@@ -2013,9 +2027,20 @@ def status(verbose: bool, refresh: bool, ip: bool, endpoints: bool,
2013
2027
  else:
2014
2028
  normal_clusters.append(cluster_record)
2015
2029
 
2030
+ if workspace_request_id is not None:
2031
+ all_workspaces = sdk.get(workspace_request_id)
2032
+ else:
2033
+ all_workspaces = [constants.SKYPILOT_DEFAULT_WORKSPACE]
2034
+ active_workspace = skypilot_config.get_active_workspace()
2035
+ show_workspace = len(all_workspaces) > 1
2036
+ _show_enabled_infra(active_workspace, show_workspace)
2037
+ click.echo(f'{colorama.Fore.CYAN}{colorama.Style.BRIGHT}Clusters'
2038
+ f'{colorama.Style.RESET_ALL}')
2039
+
2016
2040
  num_pending_autostop = 0
2017
2041
  num_pending_autostop += status_utils.show_status_table(
2018
- normal_clusters + controllers, verbose, all_users, query_clusters)
2042
+ normal_clusters + controllers, verbose, all_users, query_clusters,
2043
+ show_workspace)
2019
2044
 
2020
2045
  managed_jobs_query_interrupted = False
2021
2046
  if show_managed_jobs:
@@ -3345,9 +3370,16 @@ def _down_or_stop_clusters(
3345
3370
  is_flag=True,
3346
3371
  default=False,
3347
3372
  help='Show the activated account for each cloud.')
3373
+ @click.option(
3374
+ '--workspace',
3375
+ '-w',
3376
+ type=str,
3377
+ help='The workspace to check. If None, all workspaces will be checked.')
3348
3378
  @usage_lib.entrypoint
3349
3379
  # pylint: disable=redefined-outer-name
3350
- def check(infra_list: Tuple[str], verbose: bool):
3380
+ def check(infra_list: Tuple[str],
3381
+ verbose: bool,
3382
+ workspace: Optional[str] = None):
3351
3383
  """Check which clouds are available to use.
3352
3384
 
3353
3385
  This checks access credentials for all clouds supported by SkyPilot. If a
@@ -3370,7 +3402,9 @@ def check(infra_list: Tuple[str], verbose: bool):
3370
3402
  sky check aws gcp
3371
3403
  """
3372
3404
  infra_arg = infra_list if len(infra_list) > 0 else None
3373
- request_id = sdk.check(infra_list=infra_arg, verbose=verbose)
3405
+ request_id = sdk.check(infra_list=infra_arg,
3406
+ verbose=verbose,
3407
+ workspace=workspace)
3374
3408
  sdk.stream_and_get(request_id)
3375
3409
  api_server_url = server_common.get_server_url()
3376
3410
  click.echo()
@@ -4447,7 +4481,8 @@ def jobs_cancel(name: Optional[str], job_ids: Tuple[int], all: bool, yes: bool,
4447
4481
  f'Provided {" ".join(arguments)!r}.')
4448
4482
 
4449
4483
  if not yes:
4450
- job_identity_str = (f'managed jobs with IDs {job_id_str}'
4484
+ plural = 's' if len(job_ids) > 1 else ''
4485
+ job_identity_str = (f'managed job{plural} with ID{plural} {job_id_str}'
4451
4486
  if job_ids else repr(name))
4452
4487
  if all_users:
4453
4488
  job_identity_str = 'all managed jobs FOR ALL USERS'
@@ -6169,10 +6204,14 @@ def api_status(request_ids: Optional[List[str]], all_status: bool,
6169
6204
  '-e',
6170
6205
  required=False,
6171
6206
  help='The SkyPilot API server endpoint.')
6207
+ @click.option('--get-token',
6208
+ is_flag=True,
6209
+ default=False,
6210
+ help='Force token-based login.')
6172
6211
  @usage_lib.entrypoint
6173
- def api_login(endpoint: Optional[str]):
6212
+ def api_login(endpoint: Optional[str], get_token: bool):
6174
6213
  """Logs into a SkyPilot API server."""
6175
- sdk.api_login(endpoint)
6214
+ sdk.api_login(endpoint, get_token)
6176
6215
 
6177
6216
 
6178
6217
  @api.command('info', cls=_DocumentedCodeCommand)
@@ -6184,6 +6223,10 @@ def api_info():
6184
6223
  api_server_info = sdk.api_info()
6185
6224
  user_name = os.getenv(constants.USER_ENV_VAR, getpass.getuser())
6186
6225
  user_hash = common_utils.get_user_hash()
6226
+ api_server_user = api_server_info.get('user')
6227
+ if api_server_user is not None:
6228
+ user_name = api_server_user['name']
6229
+ user_hash = api_server_user['id']
6187
6230
  dashboard_url = server_common.get_dashboard_url(url)
6188
6231
  click.echo(f'Using SkyPilot API server: {url}\n'
6189
6232
  f'{ux_utils.INDENT_SYMBOL}Status: {api_server_info["status"]}, '
sky/client/sdk.py CHANGED
@@ -94,12 +94,15 @@ def stream_response(request_id: Optional[str],
94
94
  @server_common.check_server_healthy_or_start
95
95
  @annotations.client_api
96
96
  def check(infra_list: Optional[Tuple[str, ...]],
97
- verbose: bool) -> server_common.RequestId:
97
+ verbose: bool,
98
+ workspace: Optional[str] = None) -> server_common.RequestId:
98
99
  """Checks the credentials to enable clouds.
99
100
 
100
101
  Args:
101
102
  infra: The infra to check.
102
103
  verbose: Whether to show verbose output.
104
+ workspace: The workspace to check. If None, all workspaces will be
105
+ checked.
103
106
 
104
107
  Returns:
105
108
  The request ID of the check request.
@@ -123,7 +126,9 @@ def check(infra_list: Optional[Tuple[str, ...]],
123
126
  f'ignoring {region_zone}')
124
127
  specified_clouds.append(infra.cloud)
125
128
  clouds = tuple(specified_clouds)
126
- body = payloads.CheckBody(clouds=clouds, verbose=verbose)
129
+ body = payloads.CheckBody(clouds=clouds,
130
+ verbose=verbose,
131
+ workspace=workspace)
127
132
  response = requests.post(f'{server_common.get_server_url()}/check',
128
133
  json=json.loads(body.model_dump_json()),
129
134
  cookies=server_common.get_api_cookie_jar())
@@ -133,16 +138,23 @@ def check(infra_list: Optional[Tuple[str, ...]],
133
138
  @usage_lib.entrypoint
134
139
  @server_common.check_server_healthy_or_start
135
140
  @annotations.client_api
136
- def enabled_clouds() -> server_common.RequestId:
141
+ def enabled_clouds(workspace: Optional[str] = None) -> server_common.RequestId:
137
142
  """Gets the enabled clouds.
138
143
 
144
+ Args:
145
+ workspace: The workspace to get the enabled clouds for. If None, the
146
+ active workspace will be used.
147
+
139
148
  Returns:
140
149
  The request ID of the enabled clouds request.
141
150
 
142
151
  Request Returns:
143
152
  A list of enabled clouds in string format.
144
153
  """
145
- response = requests.get(f'{server_common.get_server_url()}/enabled_clouds',
154
+ if workspace is None:
155
+ workspace = skypilot_config.get_active_workspace()
156
+ response = requests.get((f'{server_common.get_server_url()}/enabled_clouds?'
157
+ f'workspace={workspace}'),
146
158
  cookies=server_common.get_api_cookie_jar())
147
159
  return server_common.get_request_id(response)
148
160
 
@@ -278,6 +290,13 @@ def optimize(
278
290
  return server_common.get_request_id(response)
279
291
 
280
292
 
293
+ def workspaces() -> server_common.RequestId:
294
+ """Gets the workspaces."""
295
+ response = requests.get(f'{server_common.get_server_url()}/workspaces',
296
+ cookies=server_common.get_api_cookie_jar())
297
+ return server_common.get_request_id(response)
298
+
299
+
281
300
  @usage_lib.entrypoint
282
301
  @server_common.check_server_healthy_or_start
283
302
  @annotations.client_api
@@ -1731,7 +1750,7 @@ def api_status(
1731
1750
  @usage_lib.entrypoint
1732
1751
  @server_common.check_server_healthy_or_start
1733
1752
  @annotations.client_api
1734
- def api_info() -> Dict[str, str]:
1753
+ def api_info() -> Dict[str, Any]:
1735
1754
  """Gets the server's status, commit and version.
1736
1755
 
1737
1756
  Returns:
@@ -1744,8 +1763,15 @@ def api_info() -> Dict[str, str]:
1744
1763
  'api_version': '1',
1745
1764
  'commit': 'abc1234567890',
1746
1765
  'version': '1.0.0',
1766
+ 'version_on_disk': '1.0.0',
1767
+ 'user': {
1768
+ 'name': 'test@example.com',
1769
+ 'id': '12345abcd',
1770
+ },
1747
1771
  }
1748
1772
 
1773
+ Note that user may be None if we are not using an auth proxy.
1774
+
1749
1775
  """
1750
1776
  response = requests.get(f'{server_common.get_server_url()}/api/health',
1751
1777
  cookies=server_common.get_api_cookie_jar())
@@ -1868,7 +1894,7 @@ def api_server_logs(follow: bool = True, tail: Optional[int] = None) -> None:
1868
1894
 
1869
1895
  @usage_lib.entrypoint
1870
1896
  @annotations.client_api
1871
- def api_login(endpoint: Optional[str] = None) -> None:
1897
+ def api_login(endpoint: Optional[str] = None, get_token: bool = False) -> None:
1872
1898
  """Logs into a SkyPilot API server.
1873
1899
 
1874
1900
  This sets the endpoint globally, i.e., all SkyPilot CLI and SDK calls will
@@ -1895,7 +1921,7 @@ def api_login(endpoint: Optional[str] = None) -> None:
1895
1921
  raise click.BadParameter('Endpoint must be a valid URL.')
1896
1922
 
1897
1923
  server_status = server_common.check_server_healthy(endpoint)
1898
- if server_status == server_common.ApiServerStatus.NEEDS_AUTH:
1924
+ if server_status == server_common.ApiServerStatus.NEEDS_AUTH or get_token:
1899
1925
  # We detected an auth proxy, so go through the auth proxy cookie flow.
1900
1926
  parsed_url = urlparse.urlparse(endpoint)
1901
1927
  token_url = f'{endpoint}/token'
@@ -1915,11 +1941,20 @@ def api_login(endpoint: Optional[str] = None) -> None:
1915
1941
  raise ValueError(f'Malformed token: {token}') from e
1916
1942
  logger.debug(f'Token data: {data!r}')
1917
1943
  try:
1918
- cookie_dict = json.loads(data)
1944
+ json_data = json.loads(data)
1919
1945
  except (json.JSONDecodeError, UnicodeDecodeError) as e:
1920
1946
  raise ValueError(f'Malformed token data: {data!r}') from e
1921
- if not isinstance(cookie_dict, dict):
1922
- raise ValueError(f'Malformed token JSON: {cookie_dict}')
1947
+ if not isinstance(json_data, dict):
1948
+ raise ValueError(f'Malformed token JSON: {json_data}')
1949
+
1950
+ if json_data.get('v') == 1:
1951
+ user_hash = json_data.get('user')
1952
+ cookie_dict = json_data['cookies']
1953
+ elif 'v' not in json_data:
1954
+ user_hash = None
1955
+ cookie_dict = json_data
1956
+ else:
1957
+ raise ValueError(f'Unsupported token version: {json_data.get("v")}')
1923
1958
 
1924
1959
  cookie_jar = cookiejar.MozillaCookieJar()
1925
1960
  for (name, value) in cookie_dict.items():
@@ -1962,6 +1997,15 @@ def api_login(endpoint: Optional[str] = None) -> None:
1962
1997
  server_common.get_api_cookie_jar_path())
1963
1998
  cookie_jar.save(cookie_jar_path)
1964
1999
 
2000
+ # If we have a user_hash, save it to the local file
2001
+ if user_hash is not None:
2002
+ if not common_utils.is_valid_user_hash(user_hash):
2003
+ raise ValueError(f'Invalid user hash: {user_hash}')
2004
+ with open(os.path.expanduser('~/.sky/user_hash'),
2005
+ 'w',
2006
+ encoding='utf-8') as f:
2007
+ f.write(user_hash)
2008
+
1965
2009
  # Set the endpoint in the config file
1966
2010
  config_path = pathlib.Path(
1967
2011
  skypilot_config.get_user_config_path()).expanduser()
sky/clouds/gcp.py CHANGED
@@ -997,10 +997,21 @@ class GCP(clouds.Cloud):
997
997
  return GCPIdentityType.SHARED_CREDENTIALS_FILE
998
998
 
999
999
  @classmethod
1000
- @annotations.lru_cache(scope='request',
1001
- maxsize=1) # Cache since getting identity is slow.
1002
1000
  def get_user_identities(cls) -> List[List[str]]:
1003
1001
  """Returns the email address + project id of the active user."""
1002
+ gcp_workspace_config = json.dumps(
1003
+ skypilot_config.get_workspace_cloud('gcp'), sort_keys=True)
1004
+ return cls._get_user_identities(gcp_workspace_config)
1005
+
1006
+ @classmethod
1007
+ @annotations.lru_cache(scope='request', maxsize=5)
1008
+ def _get_user_identities(
1009
+ cls, workspace_config: Optional[str]) -> List[List[str]]:
1010
+ # We add workspace_config in args to avoid caching the GCP identity
1011
+ # for when different workspace configs are used. Use json.dumps to
1012
+ # ensure the config is hashable.
1013
+ del workspace_config # Unused
1014
+
1004
1015
  try:
1005
1016
  account = _run_output('gcloud auth list --filter=status:ACTIVE '
1006
1017
  '--format="value(account)"')
@@ -1031,7 +1042,8 @@ class GCP(clouds.Cloud):
1031
1042
  f'{common_utils.format_exception(e, use_bracket=True)}'
1032
1043
  ) from e
1033
1044
  # TODO: Return a list of identities in the profile when we support
1034
- # automatic switching for GCP. Currently we only support one identity.
1045
+ # automatic switching for GCP. Currently we only support one
1046
+ # identity.
1035
1047
  return [[f'{account} [project_id={project_id}]']]
1036
1048
 
1037
1049
  @classmethod
@@ -1061,6 +1073,10 @@ class GCP(clouds.Cloud):
1061
1073
  return 'dryrun-project-id'
1062
1074
  # pylint: disable=import-outside-toplevel
1063
1075
  from google import auth # type: ignore
1076
+ config_project_id = skypilot_config.get_workspace_cloud('gcp').get(
1077
+ 'project_id', None)
1078
+ if config_project_id:
1079
+ return config_project_id
1064
1080
  _, project_id = auth.default()
1065
1081
  if project_id is None:
1066
1082
  raise exceptions.CloudUserIdentityError(
sky/core.py CHANGED
@@ -17,6 +17,7 @@ from sky import global_user_state
17
17
  from sky import models
18
18
  from sky import optimizer
19
19
  from sky import sky_logging
20
+ from sky import skypilot_config
20
21
  from sky import task as task_lib
21
22
  from sky.backends import backend_utils
22
23
  from sky.clouds import cloud as sky_cloud
@@ -1009,9 +1010,11 @@ def storage_delete(name: str) -> None:
1009
1010
  # = Catalog Observe =
1010
1011
  # ===================
1011
1012
  @usage_lib.entrypoint
1012
- def enabled_clouds() -> List[clouds.Cloud]:
1013
+ def enabled_clouds(workspace: Optional[str] = None) -> List[clouds.Cloud]:
1014
+ if workspace is None:
1015
+ workspace = skypilot_config.get_active_workspace()
1013
1016
  return global_user_state.get_cached_enabled_clouds(
1014
- sky_cloud.CloudCapability.COMPUTE)
1017
+ sky_cloud.CloudCapability.COMPUTE, workspace=workspace)
1015
1018
 
1016
1019
 
1017
1020
  @usage_lib.entrypoint
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><title>404: This page could not be found</title><meta name="next-head-count" content="3"/><link rel="preload" href="/dashboard/_next/static/css/7e7ce4ff31d3977b.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/7e7ce4ff31d3977b.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-830f59b8404e96b8.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-87d061ee6ed71b28.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-e0e2335212e72357.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-8cfab319f9fb3ae8.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-1be831200e60c5c0.js" defer=""></script><script src="/dashboard/_next/static/ECKwDNS9v9y3_IKFZ2lpp/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/ECKwDNS9v9y3_IKFZ2lpp/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"ECKwDNS9v9y3_IKFZ2lpp","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"/><title>404: This page could not be found</title><meta name="next-head-count" content="3"/><link rel="preload" href="/dashboard/_next/static/css/28558d57108b05ae.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/28558d57108b05ae.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-deda68c926e8d0bc.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-87d061ee6ed71b28.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-e0e2335212e72357.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-96a715a6fb01e228.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-1be831200e60c5c0.js" defer=""></script><script src="/dashboard/_next/static/7GEgRyZKRaSnYZCV1Jwol/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/7GEgRyZKRaSnYZCV1Jwol/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"7GEgRyZKRaSnYZCV1Jwol","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
@@ -0,0 +1 @@
1
+ self.__BUILD_MANIFEST=function(s,e,c,a,t,r,u,b,n){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/pages/index-6b0d9e5031b70c58.js"],"/_error":["static/chunks/pages/_error-1be831200e60c5c0.js"],"/clusters":[s,c,e,a,b,"static/chunks/pages/clusters-9e6d1ec6e1ac5b29.js"],"/clusters/[cluster]":[s,c,e,a,t,b,"static/chunks/pages/clusters/[cluster]-9529d9e882a0e75c.js"],"/clusters/[cluster]/[job]":[s,e,"static/chunks/pages/clusters/[cluster]/[job]-3b5aad09a25f64b7.js"],"/infra":[s,e,"static/chunks/pages/infra-abb7d744ecf15109.js"],"/jobs":[s,c,e,a,t,"static/chunks/pages/jobs-73d5e0c369d00346.js"],"/jobs/[job]":[s,e,"static/chunks/pages/jobs/[job]-48dc8d67d4b60be1.js"],"/users":[s,e,"static/chunks/pages/users-b8acf6e6735323a2.js"],"/workspace/new":[r,s,c,u,e,a,t,n,"static/chunks/pages/workspace/new-bbf436f41381e169.js"],"/workspaces":[r,s,c,u,e,a,t,"static/chunks/pages/workspaces-5ed48b3201b998c8.js"],"/workspaces/[name]":[r,s,c,u,e,a,t,n,"static/chunks/pages/workspaces/[name]-7733c960685b4385.js"],sortedPages:["/","/_app","/_error","/clusters","/clusters/[cluster]","/clusters/[cluster]/[job]","/infra","/jobs","/jobs/[job]","/users","/workspace/new","/workspaces","/workspaces/[name]"]}}("static/chunks/573-f17bd89d9f9118b3.js","static/chunks/480-5a0de8b6570ea105.js","static/chunks/488-50d843fdb5396d32.js","static/chunks/734-a6e01d7f98904741.js","static/chunks/938-59956af3950b02ed.js","static/chunks/9f96d65d-5a3e4af68c26849e.js","static/chunks/498-d7722313e5e5b4e6.js","static/chunks/578-d351125af46c293f.js","static/chunks/25-062253ea41fb8eec.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
@@ -0,0 +1,6 @@
1
+ (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[25],{1812:function(e,s,l){"use strict";l.d(s,{X:function(){return n}});var a=l(5893),t=l(7294);let r=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.charAt(0).toUpperCase()+s.slice(1)},n=e=>{let{error:s,title:l="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:r(s);return(0,a.jsx)("div",{className:"bg-red-50 border border-red-200 rounded-md p-3 mb-4",children:(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsxs)("div",{className:"flex",children:[(0,a.jsx)("div",{className:"flex-shrink-0",children:(0,a.jsx)("svg",{className:"h-5 w-5 text-red-400",viewBox:"0 0 20 20",fill:"currentColor",children:(0,a.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,a.jsx)("div",{className:"ml-3",children:(0,a.jsxs)("div",{className:"text-sm text-red-800",children:[(0,a.jsxs)("strong",{children:[l,":"]})," ",o]})})]}),(0,a.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,a.jsx)("svg",{className:"h-4 w-4",viewBox:"0 0 20 20",fill:"currentColor",children:(0,a.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"})})})]})})}},3025:function(e,s,l){"use strict";l.d(s,{I:function(){return O}});var a=l(5893),t=l(7294),r=l(1163),n=l(7324),c=l(3266),i=l(8969),o=l(9470),d=l(1664),u=l.n(d),x=l(9008),m=l.n(x),h=l(7673),f=l(803),p=l(2350);let g=t.forwardRef((e,s)=>{let{className:l,...t}=e;return(0,a.jsx)("textarea",{className:(0,p.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",l),ref:s,...t})});g.displayName="Textarea";var j=l(8799),b=l(282),y=l(3626),N=l(998);/**
2
+ * @license lucide-react v0.407.0 - ISC
3
+ *
4
+ * This source code is licensed under the ISC license.
5
+ * See the LICENSE file in the root directory of this source tree.
6
+ */let k=(0,N.Z)("Trash",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}]]),v=(0,N.Z)("Save",[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z",key:"1c8476"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7",key:"1ydtos"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7",key:"t51u73"}]]);var w=l(326);let C=e=>{let{className:s="",variant:l="default",children:t,...r}=e;return(0,a.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"}[l]," ").concat(s),...r,children:t})},L=e=>{let{className:s="",children:l,...t}=e;return(0,a.jsx)("div",{className:"text-sm leading-relaxed ".concat(s),...t,children:l})};var E=l(3850),S=l(1812),D=l(938),M=l(3320),A=l.n(M),W=l(3225);let z=e=>{let{message:s}=e;return s?(0,a.jsxs)(C,{className:"border-green-200 bg-green-50",children:[(0,a.jsx)(b.Z,{className:"h-4 w-4 text-green-600"}),(0,a.jsx)(L,{className:"text-green-800",children:s})]}):null},R=e=>{let{workspaceName:s,config:l}=e;if(!l)return null;let t="default"===s,r=0===Object.keys(l).length;if(t&&r)return(0,a.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 n=[],c=[];Object.entries(l).forEach(e=>{let[s,l]=e,t=W.MN[s.toLowerCase()];if((null==l?void 0:l.disabled)===!0)c.push(t);else if(l&&Object.keys(l).length>0){let e="";"gcp"===s.toLowerCase()&&l.project_id?e=" (Project ID: ".concat(l.project_id,")"):"aws"===s.toLowerCase()&&l.region&&(e=" (Region: ".concat(l.region,")")),n.push((0,a.jsxs)("span",{className:"block",children:[t,e," is enabled."]},"".concat(s,"-enabled")))}else n.push((0,a.jsxs)("span",{className:"block",children:[t," is enabled (using default settings)."]},"".concat(s,"-default-enabled")))});let i=[];if(c.length>0){let e=c.join(" and ");i.push((0,a.jsxs)("span",{className:"block",children:[e," ",1===c.length?"is":"are"," explicitly disabled."]},"disabled-clouds"))}return(i.push(...n),i.length>0)?(0,a.jsx)("div",{className:"text-sm text-gray-700 mb-3 p-3 bg-sky-50 rounded border border-sky-200",children:i}):!t&&r?(0,a.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};function O(e){let{workspaceName:s,isNewWorkspace:l=!1}=e,d=(0,r.useRouter)(),[x,p]=(0,t.useState)({}),[b,N]=(0,t.useState)({}),[C,L]=(0,t.useState)(""),[M,W]=(0,t.useState)(!0),[O,_]=(0,t.useState)(!1),[Z,T]=(0,t.useState)(!1),[Y,I]=(0,t.useState)(null),[P,V]=(0,t.useState)(null),[J,H]=(0,t.useState)(null),[X,B]=(0,t.useState)({showDialog:!1,deleting:!1,error:null}),[F,U]=(0,t.useState)({totalClusterCount:0,runningClusterCount:0,managedJobsCount:0,clouds:[]}),[G,q]=(0,t.useState)(!1),K=(0,t.useCallback)(async()=>{W(!0),I(null);try{let e;let l=(await (0,n.fX)())[s]||{};p(l),N(l),e=0===Object.keys(l).length?"".concat(s,":\n # Empty workspace configuration - uses all accessible infrastructure\n"):A().dump({[s]:l},{indent:2,lineWidth:-1,noRefs:!0,skipInvalid:!0,flowLevel:-1}),L(e)}catch(e){console.error("Error fetching workspace config:",e),I(e)}finally{W(!1)}},[s]),Q=(0,t.useCallback)(async()=>{if(!l){q(!0);try{let[e,l,a]=await Promise.all([(0,c.zd)(),(0,i.Vp)(),(0,n.yz)(s)]),t=e.filter(e=>(e.workspace||"default")===s),r=t.filter(e=>"RUNNING"===e.status||"LAUNCHING"===e.status),o={};e.forEach(e=>{o[e.cluster]=e.workspace||"default"});let d=l.jobs||[],u=new Set(D.x2.active),x=0;d.forEach(e=>{let l=e.cluster_name||e.resources&&e.resources.cluster_name;l&&o[l]===s&&u.has(e.status)&&x++}),U({totalClusterCount:t.length,runningClusterCount:r.length,managedJobsCount:x,clouds:Array.isArray(a)?a.sort():[]})}catch(e){console.error("Failed to fetch workspace stats:",e)}finally{q(!1)}}},[s,l]);(0,t.useEffect)(()=>{l?(W(!1),L("".concat(s,":\n # New workspace configuration\n # Leave empty to use all accessible infrastructure\n"))):(K(),Q())},[s,l,K,Q]),(0,t.useEffect)(()=>{T(JSON.stringify(x)!==JSON.stringify(b))},[x,b]);let $=e=>{L(e),H(null);try{let l=A().load(e)||{},a=Object.keys(l);if(0===a.length)p({});else if(1===a.length){let e=a[0];if(e!==s){H('Workspace name cannot be changed. Expected "'.concat(s,'" but found "').concat(e,'".'));return}let t=l[s]||{};p(t)}else H("Configuration must contain only one workspace. Found: ".concat(a.join(", ")))}catch(e){H("Invalid YAML: ".concat(e.message))}},ee=async()=>{_(!0),I(null),V(null);try{if(J)throw Error("Please fix YAML errors before saving");let e=A().load(C)||{},a=Object.keys(e);if(a.length>0&&a[0]!==s)throw Error('Workspace name cannot be changed. Expected "'.concat(s,'".'));l?(await (0,n.MB)(s,x),V("Workspace created successfully!"),setTimeout(()=>{d.push("/workspaces/".concat(s))},1500)):(await (0,n.eA)(s,x),V("Workspace updated successfully!"),N(x),Q())}catch(e){console.error("Error saving workspace:",e),I(e)}finally{_(!1)}},es=async()=>{B(e=>({...e,deleting:!0,error:null}));try{await (0,n.zl)(s),V("Workspace deleted successfully!"),setTimeout(()=>{d.push("/workspaces")},1500)}catch(e){console.error("Error deleting workspace:",e),B(s=>({...s,deleting:!1,error:e}))}},el=()=>{B({showDialog:!1,deleting:!1,error:null})},ea=async()=>{await Promise.all([K(),Q()])};if(!d.isReady)return(0,a.jsx)("div",{children:"Loading..."});let et=l?"Create New Workspace | SkyPilot Dashboard":"Workspace: ".concat(s," | SkyPilot Dashboard");return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(m(),{children:(0,a.jsx)("title",{children:et})}),(0,a.jsxs)(o.A,{highlighted:"workspaces",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between mb-4 h-5",children:[(0,a.jsxs)("div",{className:"text-base flex items-center",children:[(0,a.jsx)(u(),{href:"/workspaces",className:"text-sky-blue hover:underline",children:"Workspaces"}),(0,a.jsx)("span",{className:"mx-2 text-gray-500",children:"›"}),(0,a.jsx)(u(),{href:l?"/workspace/new":"/workspaces/".concat(s),className:"text-sky-blue hover:underline",children:l?"New Workspace":s}),Z&&(0,a.jsx)("span",{className:"ml-3 px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded",children:"Unsaved changes"})]}),(0,a.jsxs)("div",{className:"text-sm flex items-center",children:[(M||O||G)&&(0,a.jsxs)("div",{className:"flex items-center mr-4",children:[(0,a.jsx)(j.Z,{size:15,className:"mt-0"}),(0,a.jsx)("span",{className:"ml-2 text-gray-500",children:O?"Saving...":"Loading..."})]}),(0,a.jsxs)("div",{className:"flex items-center space-x-4",children:[!l&&(0,a.jsxs)("button",{onClick:ea,disabled:M||O||G,className:"text-sky-blue hover:text-sky-blue-bright font-medium inline-flex items-center",children:[(0,a.jsx)(y.Z,{className:"w-4 h-4 mr-1.5"}),"Refresh"]}),!l&&"default"!==s&&(0,a.jsxs)("button",{onClick:()=>B({...X,showDialog:!0}),disabled:X.deleting||O,className:"text-red-600 hover:text-red-700 font-medium inline-flex items-center",children:[(0,a.jsx)(k,{className:"w-4 h-4 mr-1.5"}),"Delete"]})]})]})]}),M?(0,a.jsxs)("div",{className:"flex justify-center items-center py-12",children:[(0,a.jsx)(j.Z,{size:24,className:"mr-2"}),(0,a.jsx)("span",{className:"text-gray-500",children:"Loading workspace configuration..."})]}):(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsx)(S.X,{error:Y,title:"Error",onDismiss:()=>I(null)}),(0,a.jsx)(z,{message:P}),(0,a.jsxs)("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-6",children:[!l&&(0,a.jsx)("div",{className:"lg:col-span-1",children:(0,a.jsxs)(h.Zb,{className:"h-full",children:[(0,a.jsx)(h.Ol,{children:(0,a.jsxs)(h.ll,{className:"text-base font-normal",children:[(0,a.jsx)("span",{className:"font-semibold",children:"Workspace:"})," ",s]})}),(0,a.jsxs)(h.aY,{className:"text-sm pb-2 flex-1",children:[(0,a.jsxs)("div",{className:"py-2 flex items-center justify-between",children:[(0,a.jsxs)("div",{className:"flex items-center text-gray-600",children:[(0,a.jsx)(E.QT,{className:"w-4 h-4 mr-2 text-gray-500"}),(0,a.jsx)("span",{children:"Clusters (Running / Total)"})]}),(0,a.jsx)("span",{className:"font-normal text-gray-800",children:G?"...":"".concat(F.runningClusterCount," / ").concat(F.totalClusterCount)})]}),(0,a.jsxs)("div",{className:"py-2 flex items-center justify-between border-t border-gray-100",children:[(0,a.jsxs)("div",{className:"flex items-center text-gray-600",children:[(0,a.jsx)(E.Vp,{className:"w-4 h-4 mr-2 text-gray-500"}),(0,a.jsx)("span",{children:"Managed Jobs"})]}),(0,a.jsx)("span",{className:"font-normal text-gray-800",children:G?"...":F.managedJobsCount})]})]}),(0,a.jsxs)("div",{className:"px-6 pb-6 text-sm pt-3",children:[(0,a.jsx)("h4",{className:"mb-2 text-xs text-gray-500 tracking-wider",children:"Enabled Infra"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-x-4 gap-y-1",children:G?(0,a.jsx)("span",{className:"text-gray-500",children:"Loading..."}):F.clouds.length>0?F.clouds.map(e=>(0,a.jsxs)("div",{className:"flex items-center text-gray-700",children:[(0,a.jsx)(E.Ye,{className:"w-3.5 h-3.5 mr-1.5 text-green-500"}),(0,a.jsx)("span",{children:e})]},e)):(0,a.jsx)("span",{className:"text-gray-500 italic",children:"No enabled infrastructure"})}),(0,a.jsx)("div",{className:"mt-4",children:(0,a.jsx)(R,{workspaceName:s,config:b})})]})]})}),(0,a.jsx)("div",{className:l?"lg:col-span-3":"lg:col-span-2",children:(0,a.jsxs)(h.Zb,{className:"h-full flex flex-col",children:[(0,a.jsx)(h.Ol,{children:(0,a.jsx)(h.ll,{className:"text-base font-normal",children:l?"New Workspace YAML":"Edit Workspace YAML"})}),(0,a.jsx)(h.aY,{className:"flex-1 flex flex-col",children:(0,a.jsxs)("div",{className:"space-y-4 flex-1 flex flex-col",children:[J&&(0,a.jsx)(S.X,{error:J,onDismiss:()=>H(null)}),(0,a.jsxs)("div",{className:"flex-1 flex flex-col",children:[(0,a.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,a.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,a.jsxs)("div",{className:"mb-4",children:[(0,a.jsx)("h4",{className:"text-sm font-medium text-gray-700 mb-2",children:"Example configuration:"}),(0,a.jsx)("div",{className:"p-3 bg-gray-50 border rounded-lg",children:(0,a.jsx)("pre",{className:"text-xs font-mono text-gray-600 whitespace-pre-wrap",children:"".concat(s||"my-workspace",":\n gcp:\n project_id: xxx\n aws:\n disabled: true")})})]}),(0,a.jsx)(g,{value:C,onChange:e=>$(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,a.jsx)("div",{className:"flex justify-end space-x-3 pt-3 border-gray-200",children:(0,a.jsxs)(f.z,{onClick:ee,disabled:O||J||M,className:"inline-flex items-center bg-blue-600 hover:bg-blue-700 text-white",children:[(0,a.jsx)(v,{className:"w-4 h-4 mr-1.5"}),O?"Applying...":"Apply"]})})]})]})})]})})]})]}),(0,a.jsx)(w.Vq,{open:X.showDialog,onOpenChange:el,children:(0,a.jsxs)(w.cZ,{className:"sm:max-w-md",children:[(0,a.jsxs)(w.fK,{className:"",children:[(0,a.jsx)(w.$N,{children:"Delete Workspace"}),(0,a.jsxs)(w.Be,{children:['Are you sure you want to delete workspace "',s,'"? This action cannot be undone.']})]}),X.error&&(0,a.jsx)(S.X,{error:X.error,title:"Deletion Failed",onDismiss:()=>B(e=>({...e,error:null}))}),(0,a.jsxs)(w.cN,{className:"",children:[(0,a.jsx)(f.z,{variant:"outline",onClick:el,disabled:X.deleting,children:"Cancel"}),(0,a.jsx)(f.z,{variant:"destructive",onClick:es,disabled:X.deleting,children:X.deleting?"Deleting...":"Delete"})]})]})})]})]})}},9008:function(e,s,l){e.exports=l(7219)}}]);