skypilot-nightly 1.0.0.dev20250528__py3-none-any.whl → 1.0.0.dev20250530__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 (98) hide show
  1. sky/__init__.py +2 -2
  2. sky/adaptors/nebius.py +99 -16
  3. sky/authentication.py +54 -7
  4. sky/backends/backend_utils.py +35 -22
  5. sky/backends/cloud_vm_ray_backend.py +30 -15
  6. sky/check.py +1 -1
  7. sky/cli.py +20 -8
  8. sky/client/cli.py +20 -8
  9. sky/client/oauth.py +82 -0
  10. sky/client/sdk.py +60 -10
  11. sky/clouds/nebius.py +55 -14
  12. sky/clouds/service_catalog/data_fetchers/fetch_gcp.py +3 -3
  13. sky/dashboard/out/404.html +1 -1
  14. sky/dashboard/out/_next/static/Q32Bxr2Pby5tFDW-y5TNg/_buildManifest.js +1 -0
  15. sky/dashboard/out/_next/static/chunks/236-ca00738e2f58ea65.js +6 -0
  16. sky/dashboard/out/_next/static/chunks/37-64efcd0e9c54bff6.js +6 -0
  17. sky/dashboard/out/_next/static/chunks/{173-7db8607cefc20f70.js → 614-3d29f98e0634b179.js} +2 -2
  18. sky/dashboard/out/_next/static/chunks/682-f3f1443ed2fba42f.js +6 -0
  19. sky/dashboard/out/_next/static/chunks/798-c0525dc3f21e488d.js +1 -0
  20. sky/dashboard/out/_next/static/chunks/843-786c36624d5ff61f.js +11 -0
  21. sky/dashboard/out/_next/static/chunks/856-02e34c9fc5945066.js +1 -0
  22. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-42d3656aba9d2e78.js +6 -0
  23. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-20835df7b0c4599c.js +6 -0
  24. sky/dashboard/out/_next/static/chunks/pages/{clusters-943992b84fd6f4ee.js → clusters-f37ff20f0af29aae.js} +1 -1
  25. sky/dashboard/out/_next/static/chunks/pages/{config-41738d1896fc02fe.js → config-3c6a2dabf56e8cd6.js} +2 -2
  26. sky/dashboard/out/_next/static/chunks/pages/infra/[context]-342bc15bb78ab2e5.js +1 -0
  27. sky/dashboard/out/_next/static/chunks/pages/infra-7b4b8e7fa9fa0827.js +1 -0
  28. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-258decb65e95f520.js +11 -0
  29. sky/dashboard/out/_next/static/chunks/pages/{jobs-a4efc09e61988f8d.js → jobs-78a6c5ba3e24c0cf.js} +1 -1
  30. sky/dashboard/out/_next/static/chunks/pages/{users-b2634885d67c49a6.js → users-89f9212b81d8897e.js} +1 -1
  31. sky/dashboard/out/_next/static/chunks/pages/workspace/{new-579b3203c7c19d84.js → new-198b6e00d7d724c5.js} +1 -1
  32. sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-9388e38fac73ee8f.js → [name]-2ce792183b03c341.js} +1 -1
  33. sky/dashboard/out/_next/static/chunks/pages/workspaces-17d41826537196e7.js +1 -0
  34. sky/dashboard/out/_next/static/chunks/webpack-f27c9a32aa3d9c6d.js +1 -0
  35. sky/dashboard/out/_next/static/css/5411b9fb0a783c1c.css +3 -0
  36. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  37. sky/dashboard/out/clusters/[cluster].html +1 -1
  38. sky/dashboard/out/clusters.html +1 -1
  39. sky/dashboard/out/config.html +1 -1
  40. sky/dashboard/out/index.html +1 -1
  41. sky/dashboard/out/infra/[context].html +1 -0
  42. sky/dashboard/out/infra.html +1 -1
  43. sky/dashboard/out/jobs/[job].html +1 -1
  44. sky/dashboard/out/jobs.html +1 -1
  45. sky/dashboard/out/users.html +1 -1
  46. sky/dashboard/out/workspace/new.html +1 -1
  47. sky/dashboard/out/workspaces/[name].html +1 -1
  48. sky/dashboard/out/workspaces.html +1 -1
  49. sky/exceptions.py +11 -1
  50. sky/global_user_state.py +149 -1
  51. sky/jobs/client/sdk.py +1 -0
  52. sky/jobs/constants.py +3 -1
  53. sky/jobs/controller.py +3 -5
  54. sky/jobs/recovery_strategy.py +148 -102
  55. sky/jobs/scheduler.py +23 -8
  56. sky/jobs/server/core.py +16 -0
  57. sky/jobs/state.py +153 -39
  58. sky/jobs/utils.py +33 -5
  59. sky/provision/kubernetes/utils.py +2 -1
  60. sky/provision/provisioner.py +15 -10
  61. sky/resources.py +16 -1
  62. sky/serve/controller.py +10 -7
  63. sky/serve/replica_managers.py +22 -18
  64. sky/serve/service.py +5 -4
  65. sky/server/common.py +11 -4
  66. sky/server/html/token_page.html +32 -6
  67. sky/server/server.py +3 -1
  68. sky/server/stream_utils.py +21 -0
  69. sky/setup_files/dependencies.py +7 -1
  70. sky/skylet/constants.py +1 -1
  71. sky/task.py +26 -0
  72. sky/templates/jobs-controller.yaml.j2 +2 -1
  73. sky/templates/kubernetes-ray.yml.j2 +19 -1
  74. sky/utils/common_utils.py +66 -0
  75. sky/utils/rich_utils.py +5 -0
  76. sky/utils/schemas.py +32 -1
  77. {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/METADATA +3 -1
  78. {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/RECORD +84 -81
  79. sky/dashboard/out/_next/static/Mx1iAbDQn1jMHh3UHmK3R/_buildManifest.js +0 -1
  80. sky/dashboard/out/_next/static/chunks/236-d6900c828331f664.js +0 -6
  81. sky/dashboard/out/_next/static/chunks/320-afea3ddcc5bd1c6c.js +0 -6
  82. sky/dashboard/out/_next/static/chunks/578-9146658cead92981.js +0 -6
  83. sky/dashboard/out/_next/static/chunks/843-256ec920f6d5f41f.js +0 -11
  84. sky/dashboard/out/_next/static/chunks/856-62b87c68917b08ed.js +0 -1
  85. sky/dashboard/out/_next/static/chunks/9f96d65d-5a3e4af68c26849e.js +0 -1
  86. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-159bffb2fa34ed54.js +0 -6
  87. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-9506c00257d10dbd.js +0 -1
  88. sky/dashboard/out/_next/static/chunks/pages/infra-881fcd902fbbd0e5.js +0 -6
  89. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-2c29e97a6aa50dd4.js +0 -6
  90. sky/dashboard/out/_next/static/chunks/pages/workspaces-610c49ae3619ee85.js +0 -1
  91. sky/dashboard/out/_next/static/chunks/webpack-deda68c926e8d0bc.js +0 -1
  92. sky/dashboard/out/_next/static/css/ffd1cd601648c303.css +0 -3
  93. /sky/dashboard/out/_next/static/{Mx1iAbDQn1jMHh3UHmK3R → Q32Bxr2Pby5tFDW-y5TNg}/_ssgManifest.js +0 -0
  94. /sky/dashboard/out/_next/static/chunks/pages/{_app-a631df412d8172de.js → _app-f19ea34b91c33950.js} +0 -0
  95. {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/WHEEL +0 -0
  96. {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/entry_points.txt +0 -0
  97. {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/licenses/LICENSE +0 -0
  98. {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/top_level.txt +0 -0
sky/cli.py CHANGED
@@ -862,6 +862,7 @@ def _make_task_or_dag_from_entrypoint_with_overrides(
862
862
  field_to_ignore: Optional[List[str]] = None,
863
863
  # job launch specific
864
864
  job_recovery: Optional[str] = None,
865
+ priority: Optional[int] = None,
865
866
  config_override: Optional[Dict[str, Any]] = None,
866
867
  ) -> Union[sky.Task, sky.Dag]:
867
868
  """Creates a task or a dag from an entrypoint with overrides.
@@ -939,6 +940,9 @@ def _make_task_or_dag_from_entrypoint_with_overrides(
939
940
  task.num_nodes = num_nodes
940
941
  if name is not None:
941
942
  task.name = name
943
+ # job launch specific.
944
+ if priority is not None:
945
+ task.set_job_priority(priority)
942
946
  return task
943
947
 
944
948
 
@@ -4170,6 +4174,12 @@ def jobs():
4170
4174
  default=None,
4171
4175
  type=str,
4172
4176
  help='Recovery strategy to use for managed jobs.')
4177
+ @click.option('--priority',
4178
+ type=click.IntRange(0, 1000),
4179
+ default=None,
4180
+ show_default=True,
4181
+ help=('Job priority from 0 to 1000. A lower number is higher '
4182
+ 'priority. Default is 500.'))
4173
4183
  @click.option(
4174
4184
  '--detach-run',
4175
4185
  '-d',
@@ -4207,6 +4217,7 @@ def jobs_launch(
4207
4217
  disk_size: Optional[int],
4208
4218
  disk_tier: Optional[str],
4209
4219
  ports: Tuple[str],
4220
+ priority: Optional[int],
4210
4221
  detach_run: bool,
4211
4222
  yes: bool,
4212
4223
  async_call: bool,
@@ -4253,6 +4264,7 @@ def jobs_launch(
4253
4264
  disk_tier=disk_tier,
4254
4265
  ports=ports,
4255
4266
  job_recovery=job_recovery,
4267
+ priority=priority,
4256
4268
  config_override=config_override,
4257
4269
  )
4258
4270
 
@@ -4335,8 +4347,6 @@ def jobs_queue(verbose: bool, refresh: bool, skip_finished: bool,
4335
4347
  - ``PENDING``: Job is waiting for a free slot on the jobs controller to be
4336
4348
  accepted.
4337
4349
 
4338
- - ``SUBMITTED``: Job is submitted to and accepted by the jobs controller.
4339
-
4340
4350
  - ``STARTING``: Job is starting (provisioning a cluster for the job).
4341
4351
 
4342
4352
  - ``RUNNING``: Job is running.
@@ -4553,7 +4563,7 @@ def jobs_logs(name: Optional[str], job_id: Optional[int], follow: bool,
4553
4563
  @usage_lib.entrypoint
4554
4564
  def jobs_dashboard():
4555
4565
  """Opens a dashboard for managed jobs."""
4556
- managed_jobs.dashboard()
4566
+ sdk.dashboard(starting_page='jobs')
4557
4567
 
4558
4568
 
4559
4569
  @cli.command(cls=_DocumentedCodeCommand)
@@ -5293,7 +5303,8 @@ def serve_logs(
5293
5303
 
5294
5304
 
5295
5305
  @ux_utils.print_exception_no_traceback()
5296
- def _get_candidate_configs(yaml_path: str) -> Optional[List[Dict[str, str]]]:
5306
+ def _get_candidate_configs(
5307
+ entrypoint_yaml_path: str) -> Optional[List[Dict[str, str]]]:
5297
5308
  """Gets benchmark candidate configs from a YAML file.
5298
5309
 
5299
5310
  Benchmark candidates are configured in the YAML file as a list of
@@ -5307,17 +5318,18 @@ def _get_candidate_configs(yaml_path: str) -> Optional[List[Dict[str, str]]]:
5307
5318
  - {instance_type: g4dn.2xlarge}
5308
5319
  - {cloud: gcp, accelerators: V100} # overrides cloud
5309
5320
  """
5310
- config = common_utils.read_yaml(os.path.expanduser(yaml_path))
5321
+ config = common_utils.read_yaml(os.path.expanduser(entrypoint_yaml_path))
5311
5322
  if not isinstance(config, dict):
5312
- raise ValueError(f'Invalid YAML file: {yaml_path}. '
5323
+ raise ValueError(f'Invalid YAML file: {entrypoint_yaml_path}. '
5313
5324
  'The YAML file should be parsed into a dictionary.')
5314
5325
  if config.get('resources') is None:
5315
5326
  return None
5316
5327
 
5317
5328
  resources = config['resources']
5318
5329
  if not isinstance(resources, dict):
5319
- raise ValueError(f'Invalid resources configuration in {yaml_path}. '
5320
- 'Resources must be a dictionary.')
5330
+ raise ValueError(
5331
+ f'Invalid resources configuration in {entrypoint_yaml_path}. '
5332
+ 'Resources must be a dictionary.')
5321
5333
  if resources.get('candidates') is None:
5322
5334
  return None
5323
5335
 
sky/client/cli.py CHANGED
@@ -862,6 +862,7 @@ def _make_task_or_dag_from_entrypoint_with_overrides(
862
862
  field_to_ignore: Optional[List[str]] = None,
863
863
  # job launch specific
864
864
  job_recovery: Optional[str] = None,
865
+ priority: Optional[int] = None,
865
866
  config_override: Optional[Dict[str, Any]] = None,
866
867
  ) -> Union[sky.Task, sky.Dag]:
867
868
  """Creates a task or a dag from an entrypoint with overrides.
@@ -939,6 +940,9 @@ def _make_task_or_dag_from_entrypoint_with_overrides(
939
940
  task.num_nodes = num_nodes
940
941
  if name is not None:
941
942
  task.name = name
943
+ # job launch specific.
944
+ if priority is not None:
945
+ task.set_job_priority(priority)
942
946
  return task
943
947
 
944
948
 
@@ -4170,6 +4174,12 @@ def jobs():
4170
4174
  default=None,
4171
4175
  type=str,
4172
4176
  help='Recovery strategy to use for managed jobs.')
4177
+ @click.option('--priority',
4178
+ type=click.IntRange(0, 1000),
4179
+ default=None,
4180
+ show_default=True,
4181
+ help=('Job priority from 0 to 1000. A lower number is higher '
4182
+ 'priority. Default is 500.'))
4173
4183
  @click.option(
4174
4184
  '--detach-run',
4175
4185
  '-d',
@@ -4207,6 +4217,7 @@ def jobs_launch(
4207
4217
  disk_size: Optional[int],
4208
4218
  disk_tier: Optional[str],
4209
4219
  ports: Tuple[str],
4220
+ priority: Optional[int],
4210
4221
  detach_run: bool,
4211
4222
  yes: bool,
4212
4223
  async_call: bool,
@@ -4253,6 +4264,7 @@ def jobs_launch(
4253
4264
  disk_tier=disk_tier,
4254
4265
  ports=ports,
4255
4266
  job_recovery=job_recovery,
4267
+ priority=priority,
4256
4268
  config_override=config_override,
4257
4269
  )
4258
4270
 
@@ -4335,8 +4347,6 @@ def jobs_queue(verbose: bool, refresh: bool, skip_finished: bool,
4335
4347
  - ``PENDING``: Job is waiting for a free slot on the jobs controller to be
4336
4348
  accepted.
4337
4349
 
4338
- - ``SUBMITTED``: Job is submitted to and accepted by the jobs controller.
4339
-
4340
4350
  - ``STARTING``: Job is starting (provisioning a cluster for the job).
4341
4351
 
4342
4352
  - ``RUNNING``: Job is running.
@@ -4553,7 +4563,7 @@ def jobs_logs(name: Optional[str], job_id: Optional[int], follow: bool,
4553
4563
  @usage_lib.entrypoint
4554
4564
  def jobs_dashboard():
4555
4565
  """Opens a dashboard for managed jobs."""
4556
- managed_jobs.dashboard()
4566
+ sdk.dashboard(starting_page='jobs')
4557
4567
 
4558
4568
 
4559
4569
  @cli.command(cls=_DocumentedCodeCommand)
@@ -5293,7 +5303,8 @@ def serve_logs(
5293
5303
 
5294
5304
 
5295
5305
  @ux_utils.print_exception_no_traceback()
5296
- def _get_candidate_configs(yaml_path: str) -> Optional[List[Dict[str, str]]]:
5306
+ def _get_candidate_configs(
5307
+ entrypoint_yaml_path: str) -> Optional[List[Dict[str, str]]]:
5297
5308
  """Gets benchmark candidate configs from a YAML file.
5298
5309
 
5299
5310
  Benchmark candidates are configured in the YAML file as a list of
@@ -5307,17 +5318,18 @@ def _get_candidate_configs(yaml_path: str) -> Optional[List[Dict[str, str]]]:
5307
5318
  - {instance_type: g4dn.2xlarge}
5308
5319
  - {cloud: gcp, accelerators: V100} # overrides cloud
5309
5320
  """
5310
- config = common_utils.read_yaml(os.path.expanduser(yaml_path))
5321
+ config = common_utils.read_yaml(os.path.expanduser(entrypoint_yaml_path))
5311
5322
  if not isinstance(config, dict):
5312
- raise ValueError(f'Invalid YAML file: {yaml_path}. '
5323
+ raise ValueError(f'Invalid YAML file: {entrypoint_yaml_path}. '
5313
5324
  'The YAML file should be parsed into a dictionary.')
5314
5325
  if config.get('resources') is None:
5315
5326
  return None
5316
5327
 
5317
5328
  resources = config['resources']
5318
5329
  if not isinstance(resources, dict):
5319
- raise ValueError(f'Invalid resources configuration in {yaml_path}. '
5320
- 'Resources must be a dictionary.')
5330
+ raise ValueError(
5331
+ f'Invalid resources configuration in {entrypoint_yaml_path}. '
5332
+ 'Resources must be a dictionary.')
5321
5333
  if resources.get('candidates') is None:
5322
5334
  return None
5323
5335
 
sky/client/oauth.py ADDED
@@ -0,0 +1,82 @@
1
+ """Client-side OAuth module."""
2
+ from http.server import BaseHTTPRequestHandler
3
+ from http.server import HTTPServer
4
+ import threading
5
+ import time
6
+ from typing import Dict, Optional
7
+
8
+ AUTH_TIMEOUT = 300 # 5 minutes
9
+
10
+
11
+ class _AuthCallbackHandler(BaseHTTPRequestHandler):
12
+ """HTTP request handler for OAuth callback."""
13
+
14
+ def __init__(self, token_container: Dict[str, Optional[str]],
15
+ remote_endpoint: str, *args, **kwargs):
16
+ self.token_container = token_container
17
+ self.remote_endpoint = remote_endpoint
18
+ super().__init__(*args, **kwargs)
19
+
20
+ def do_POST(self): # pylint: disable=invalid-name
21
+ """Handle POST request for OAuth callback."""
22
+ data = self.rfile.read(int(self.headers['Content-Length']))
23
+
24
+ if data:
25
+ token = data.decode('utf-8')
26
+ self.token_container['token'] = token
27
+
28
+ # Send success response
29
+ self.send_response(200)
30
+ self.send_header('Content-type', 'text/html')
31
+ self.send_header('Access-Control-Allow-Origin',
32
+ self.remote_endpoint)
33
+ self.end_headers()
34
+ else:
35
+ # Send error response
36
+ self.send_response(400)
37
+ self.send_header('Content-type', 'text/html')
38
+ self.send_header('Access-Control-Allow-Origin',
39
+ self.remote_endpoint)
40
+ self.end_headers()
41
+
42
+ def log_message(self, *args): # pylint: disable=unused-argument
43
+ """Suppress default HTTP server logging."""
44
+ pass
45
+
46
+
47
+ def start_local_auth_server(port: int,
48
+ token_store: Dict[str, Optional[str]],
49
+ remote_endpoint: str,
50
+ timeout: int = AUTH_TIMEOUT) -> HTTPServer:
51
+ """Start a local HTTP server to handle OAuth callback.
52
+
53
+ Args:
54
+ port: Port to bind the server to.
55
+ token_container: Dict to store the received token.
56
+ remote_endpoint: The endpoint of the SkyPilot API server that will send
57
+ the token, needed for CORS.
58
+ timeout: Timeout in seconds to wait for the callback.
59
+
60
+ Returns:
61
+ The HTTP server instance.
62
+ """
63
+
64
+ def handler_factory(*args, **kwargs):
65
+ return _AuthCallbackHandler(token_store, remote_endpoint, *args,
66
+ **kwargs)
67
+
68
+ server = HTTPServer(('localhost', port), handler_factory)
69
+ server.timeout = timeout
70
+
71
+ def serve_until_token():
72
+ """Serve requests until token is received or timeout."""
73
+ start_time = time.time()
74
+ while (token_store['token'] is None and
75
+ time.time() - start_time < timeout):
76
+ server.handle_request()
77
+
78
+ # Start server in a separate thread
79
+ server_thread = threading.Thread(target=serve_until_token, daemon=True)
80
+ server_thread.start()
81
+
82
+ return server
sky/client/sdk.py CHANGED
@@ -36,6 +36,7 @@ from sky import sky_logging
36
36
  from sky import skypilot_config
37
37
  from sky.adaptors import common as adaptors_common
38
38
  from sky.client import common as client_common
39
+ from sky.client import oauth as oauth_lib
39
40
  from sky.server import common as server_common
40
41
  from sky.server.requests import payloads
41
42
  from sky.server.requests import requests as requests_lib
@@ -341,10 +342,11 @@ def validate(
341
342
  @usage_lib.entrypoint
342
343
  @server_common.check_server_healthy_or_start
343
344
  @annotations.client_api
344
- def dashboard() -> None:
345
+ def dashboard(starting_page: Optional[str] = None) -> None:
345
346
  """Starts the dashboard for SkyPilot."""
346
347
  api_server_url = server_common.get_server_url()
347
- url = server_common.get_dashboard_url(api_server_url)
348
+ url = server_common.get_dashboard_url(api_server_url,
349
+ starting_page=starting_page)
348
350
  logger.info(f'Opening dashboard in browser: {url}')
349
351
  webbrowser.open(url)
350
352
 
@@ -1908,6 +1910,7 @@ def api_login(endpoint: Optional[str] = None, get_token: bool = False) -> None:
1908
1910
  Args:
1909
1911
  endpoint: The endpoint of the SkyPilot API server, e.g.,
1910
1912
  http://1.2.3.4:46580 or https://skypilot.mydomain.com.
1913
+ get_token: Whether to force getting a new token even if not needed.
1911
1914
 
1912
1915
  Returns:
1913
1916
  None
@@ -1926,14 +1929,60 @@ def api_login(endpoint: Optional[str] = None, get_token: bool = False) -> None:
1926
1929
  server_status = server_common.check_server_healthy(endpoint)
1927
1930
  if server_status == server_common.ApiServerStatus.NEEDS_AUTH or get_token:
1928
1931
  # We detected an auth proxy, so go through the auth proxy cookie flow.
1929
- parsed_url = urlparse.urlparse(endpoint)
1930
- token_url = f'{endpoint}/token'
1931
- click.echo('Authentication is needed. Please visit this URL setup up '
1932
- f'the token:{colorama.Style.BRIGHT}\n\n{token_url}'
1933
- f'\n{colorama.Style.RESET_ALL}')
1934
- if webbrowser.open(token_url):
1935
- click.echo('Opening browser...')
1936
- token: str = click.prompt('Paste the token')
1932
+ token: Optional[str] = None
1933
+ server: Optional[oauth_lib.HTTPServer] = None
1934
+ try:
1935
+ callback_port = common_utils.find_free_port(8000)
1936
+
1937
+ token_container: Dict[str, Optional[str]] = {'token': None}
1938
+ logger.debug('Starting local authentication server...')
1939
+ server = oauth_lib.start_local_auth_server(callback_port,
1940
+ token_container,
1941
+ endpoint)
1942
+
1943
+ token_url = (f'{endpoint}/token?local_port={callback_port}')
1944
+ if webbrowser.open(token_url):
1945
+ click.echo(f'{colorama.Fore.GREEN}A web browser has been '
1946
+ f'opened at {token_url}. Please continue the login '
1947
+ f'in the web browser.{colorama.Style.RESET_ALL}\n'
1948
+ f'{colorama.Style.DIM}To manually copy the token, '
1949
+ f'press ctrl+c.{colorama.Style.RESET_ALL}')
1950
+ else:
1951
+ raise ValueError('Failed to open browser.')
1952
+
1953
+ start_time = time.time()
1954
+
1955
+ while (token_container['token'] is None and
1956
+ time.time() - start_time < oauth_lib.AUTH_TIMEOUT):
1957
+ time.sleep(1)
1958
+
1959
+ if token_container['token'] is None:
1960
+ click.echo(f'{colorama.Fore.YELLOW}Authentication timed out '
1961
+ f'after {oauth_lib.AUTH_TIMEOUT} seconds.')
1962
+ else:
1963
+ token = token_container['token']
1964
+
1965
+ except (Exception, KeyboardInterrupt) as e: # pylint: disable=broad-except
1966
+ logger.debug(f'Automatic authentication failed: {e}, '
1967
+ 'falling back to manual token entry.')
1968
+ if isinstance(e, KeyboardInterrupt):
1969
+ click.echo(f'\n{colorama.Style.DIM}Interrupted. Press ctrl+c '
1970
+ f'again to exit.{colorama.Style.RESET_ALL}')
1971
+ # Fall back to manual token entry
1972
+ token_url = f'{endpoint}/token'
1973
+ click.echo('Authentication is needed. Please visit this URL '
1974
+ f'to set up the token:{colorama.Style.BRIGHT}\n\n'
1975
+ f'{token_url}\n{colorama.Style.RESET_ALL}')
1976
+ token = click.prompt('Paste the token')
1977
+ finally:
1978
+ if server is not None:
1979
+ try:
1980
+ server.server_close()
1981
+ except Exception: # pylint: disable=broad-except
1982
+ pass
1983
+ if not token:
1984
+ with ux_utils.print_exception_no_traceback():
1985
+ raise ValueError('Authentication failed.')
1937
1986
 
1938
1987
  # Parse the token.
1939
1988
  # b64decode will ignore invalid characters, but does some length and
@@ -1959,6 +2008,7 @@ def api_login(endpoint: Optional[str] = None, get_token: bool = False) -> None:
1959
2008
  else:
1960
2009
  raise ValueError(f'Unsupported token version: {json_data.get("v")}')
1961
2010
 
2011
+ parsed_url = urlparse.urlparse(endpoint)
1962
2012
  cookie_jar = cookiejar.MozillaCookieJar()
1963
2013
  for (name, value) in cookie_dict.items():
1964
2014
  # dict keys in JSON must be strings
sky/clouds/nebius.py CHANGED
@@ -1,9 +1,11 @@
1
1
  """ Nebius Cloud. """
2
+ import json
2
3
  import os
3
4
  import typing
4
5
  from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
5
6
 
6
7
  from sky import clouds
8
+ from sky import exceptions
7
9
  from sky import skypilot_config
8
10
  from sky.adaptors import nebius
9
11
  from sky.clouds import service_catalog
@@ -14,13 +16,6 @@ from sky.utils import resources_utils
14
16
  if typing.TYPE_CHECKING:
15
17
  from sky import resources as resources_lib
16
18
 
17
- _CREDENTIAL_FILES = [
18
- # credential files for Nebius
19
- nebius.NEBIUS_TENANT_ID_FILENAME,
20
- nebius.NEBIUS_IAM_TOKEN_FILENAME,
21
- nebius.NEBIUS_CREDENTIALS_FILENAME
22
- ]
23
-
24
19
  _INDENT_PREFIX = ' '
25
20
 
26
21
 
@@ -296,20 +291,19 @@ class Nebius(clouds.Cloud):
296
291
  fuzzy_candidate_list, None)
297
292
 
298
293
  @classmethod
299
- @annotations.lru_cache(scope='request')
300
294
  def _check_compute_credentials(
301
295
  cls) -> Tuple[bool, Optional[Union[str, Dict[str, str]]]]:
302
296
  """Checks if the user has access credentials to
303
297
  Nebius's compute service."""
304
298
  token_cred_msg = (
305
299
  f'{_INDENT_PREFIX}Credentials can be set up by running: \n'
306
- f'{_INDENT_PREFIX} $ nebius iam get-access-token > {nebius.NEBIUS_IAM_TOKEN_PATH} \n' # pylint: disable=line-too-long
307
- f'{_INDENT_PREFIX} or generate ~/.nebius/credentials.json \n')
300
+ f'{_INDENT_PREFIX} $ nebius iam get-access-token > {nebius.iam_token_path()} \n' # pylint: disable=line-too-long
301
+ f'{_INDENT_PREFIX} or generate {nebius.credentials_path()} \n')
308
302
 
309
303
  tenant_msg = (f'{_INDENT_PREFIX} Copy your tenat ID from the web console and save it to file \n' # pylint: disable=line-too-long
310
- f'{_INDENT_PREFIX} $ echo $NEBIUS_TENANT_ID_PATH > {nebius.NEBIUS_TENANT_ID_PATH} \n' # pylint: disable=line-too-long
304
+ f'{_INDENT_PREFIX} $ echo $NEBIUS_TENANT_ID_PATH > {nebius.tenant_id_path()} \n' # pylint: disable=line-too-long
311
305
  f'{_INDENT_PREFIX} Or if you have 1 tenant you can run:\n' # pylint: disable=line-too-long
312
- f'{_INDENT_PREFIX} $ nebius --format json iam whoami|jq -r \'.user_profile.tenants[0].tenant_id\' > {nebius.NEBIUS_TENANT_ID_PATH} \n') # pylint: disable=line-too-long
306
+ f'{_INDENT_PREFIX} $ nebius --format json iam whoami|jq -r \'.user_profile.tenants[0].tenant_id\' > {nebius.tenant_id_path()} \n') # pylint: disable=line-too-long
313
307
  if not nebius.is_token_or_cred_file_exist():
314
308
  return False, f'{token_cred_msg}'
315
309
  sdk = nebius.sdk()
@@ -357,8 +351,8 @@ class Nebius(clouds.Cloud):
357
351
 
358
352
  def get_credential_file_mounts(self) -> Dict[str, str]:
359
353
  credential_file_mounts = {
360
- f'~/.nebius/{filename}': f'~/.nebius/{filename}'
361
- for filename in _CREDENTIAL_FILES
354
+ filepath: filepath
355
+ for filepath in nebius.get_credential_file_paths()
362
356
  }
363
357
  if nebius_profile_in_aws_cred_and_config():
364
358
  credential_file_mounts['~/.aws/credentials'] = '~/.aws/credentials'
@@ -378,3 +372,50 @@ class Nebius(clouds.Cloud):
378
372
  return service_catalog.validate_region_zone(region,
379
373
  zone,
380
374
  clouds='nebius')
375
+
376
+ @classmethod
377
+ def get_user_identities(cls) -> Optional[List[List[str]]]:
378
+ """Returns the email address + project id of the active user."""
379
+ nebius_workspace_config = json.dumps(
380
+ skypilot_config.get_workspace_cloud('nebius'), sort_keys=True)
381
+ return cls._get_user_identities(nebius_workspace_config)
382
+
383
+ @classmethod
384
+ @annotations.lru_cache(scope='request', maxsize=5)
385
+ def _get_user_identities(
386
+ cls, workspace_config: Optional[str]) -> Optional[List[List[str]]]:
387
+ # We add workspace_config in args to avoid caching the identity for when
388
+ # different workspace configs are used.
389
+ del workspace_config # Unused
390
+ sdk = nebius.sdk()
391
+ profile_client = nebius.iam().ProfileServiceClient(sdk)
392
+ profile = profile_client.get(nebius.iam().GetProfileRequest()).wait()
393
+ if profile.user_profile is not None:
394
+ if profile.user_profile.attributes is None:
395
+ raise exceptions.CloudUserIdentityError(
396
+ 'Nebius profile is a UserProfile, but has no attributes: '
397
+ f'{profile.user_profile}')
398
+ if profile.user_profile.attributes.email is None:
399
+ raise exceptions.CloudUserIdentityError(
400
+ 'Nebius profile is a UserProfile, but has no email: '
401
+ f'{profile.user_profile}')
402
+ return [[profile.user_profile.attributes.email]]
403
+ if profile.service_account_profile is not None:
404
+ if profile.service_account_profile.info is None:
405
+ raise exceptions.CloudUserIdentityError(
406
+ 'Nebius profile is a ServiceAccountProfile, but has no '
407
+ f'info: {profile.service_account_profile}')
408
+ if profile.service_account_profile.info.metadata is None:
409
+ raise exceptions.CloudUserIdentityError(
410
+ 'Nebius profile is a ServiceAccountProfile, but has no '
411
+ f'metadata: {profile.service_account_profile}')
412
+ if profile.service_account_profile.info.metadata.name is None:
413
+ raise exceptions.CloudUserIdentityError(
414
+ 'Nebius profile is a ServiceAccountProfile, but has no '
415
+ f'name: {profile.service_account_profile}')
416
+ return [[profile.service_account_profile.info.metadata.name]]
417
+ if profile.anonymous_profile is not None:
418
+ return None
419
+ unknown_profile_type = profile.which_field_in_oneof('profile')
420
+ raise exceptions.CloudUserIdentityError(
421
+ f'Nebius profile is of an unknown type - {unknown_profile_type}')
@@ -179,7 +179,7 @@ TPU_V4_HOST_DF = pd.read_csv(
179
179
  # TODO(woosuk): Make this more robust.
180
180
  # Refer to: https://github.com/skypilot-org/skypilot/issues/1006
181
181
  # Unsupported Series: 'f1', 'm2'
182
- SERIES_TO_DISCRIPTION = {
182
+ SERIES_TO_DESCRIPTION = {
183
183
  'a2': 'A2 Instance',
184
184
  'a3': 'A3 Instance',
185
185
  # TODO(zhwu): GCP does not have A4 instance in SKUs API yet. We keep it here
@@ -338,7 +338,7 @@ def get_vm_df(skus: List[Dict[str, Any]], region_prefix: str) -> 'pd.DataFrame':
338
338
 
339
339
  # Drop the unsupported series.
340
340
  df = df[df['InstanceType'].str.startswith(
341
- tuple(f'{series}-' for series in SERIES_TO_DISCRIPTION))]
341
+ tuple(f'{series}-' for series in SERIES_TO_DESCRIPTION))]
342
342
  df = df[~df['AvailabilityZone'].str.startswith(tuple(TPU_V4_ZONES))]
343
343
 
344
344
  # TODO(woosuk): Make this more efficient.
@@ -356,7 +356,7 @@ def get_vm_df(skus: List[Dict[str, Any]], region_prefix: str) -> 'pd.DataFrame':
356
356
 
357
357
  # Check if the SKU is for the correct series.
358
358
  description = sku['description']
359
- if SERIES_TO_DISCRIPTION[series].lower() not in description.lower():
359
+ if SERIES_TO_DESCRIPTION[series].lower() not in description.lower():
360
360
  continue
361
361
  # Special check for M1 instances.
362
362
  if series == 'm1' and 'M3' in description:
@@ -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/ffd1cd601648c303.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/ffd1cd601648c303.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-a631df412d8172de.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-1be831200e60c5c0.js" defer=""></script><script src="/dashboard/_next/static/Mx1iAbDQn1jMHh3UHmK3R/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/Mx1iAbDQn1jMHh3UHmK3R/_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":"Mx1iAbDQn1jMHh3UHmK3R","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/5411b9fb0a783c1c.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/5411b9fb0a783c1c.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-f27c9a32aa3d9c6d.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-f19ea34b91c33950.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-1be831200e60c5c0.js" defer=""></script><script src="/dashboard/_next/static/Q32Bxr2Pby5tFDW-y5TNg/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/Q32Bxr2Pby5tFDW-y5TNg/_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":"Q32Bxr2Pby5tFDW-y5TNg","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
@@ -0,0 +1 @@
1
+ self.__BUILD_MANIFEST=function(s,c,e,a,t,r,n,f,u,i,j){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/pages/index-6b0d9e5031b70c58.js"],"/_error":["static/chunks/pages/_error-1be831200e60c5c0.js"],"/clusters":[s,t,r,c,e,a,n,u,"static/chunks/pages/clusters-f37ff20f0af29aae.js"],"/clusters/[cluster]":[s,t,r,c,e,a,n,f,u,"static/chunks/pages/clusters/[cluster]-20835df7b0c4599c.js"],"/clusters/[cluster]/[job]":[s,c,"static/chunks/pages/clusters/[cluster]/[job]-42d3656aba9d2e78.js"],"/config":[s,t,c,e,"static/chunks/pages/config-3c6a2dabf56e8cd6.js"],"/infra":[s,c,e,a,i,"static/chunks/pages/infra-7b4b8e7fa9fa0827.js"],"/infra/[context]":[s,c,e,a,i,"static/chunks/pages/infra/[context]-342bc15bb78ab2e5.js"],"/jobs":[s,r,c,e,a,n,f,"static/chunks/pages/jobs-78a6c5ba3e24c0cf.js"],"/jobs/[job]":[s,t,c,"static/chunks/pages/jobs/[job]-258decb65e95f520.js"],"/users":[s,c,e,a,"static/chunks/pages/users-89f9212b81d8897e.js"],"/workspace/new":[s,t,r,c,e,a,n,f,j,"static/chunks/pages/workspace/new-198b6e00d7d724c5.js"],"/workspaces":[s,t,r,c,e,a,n,f,"static/chunks/pages/workspaces-17d41826537196e7.js"],"/workspaces/[name]":[s,t,r,c,e,a,n,f,j,"static/chunks/pages/workspaces/[name]-2ce792183b03c341.js"],sortedPages:["/","/_app","/_error","/clusters","/clusters/[cluster]","/clusters/[cluster]/[job]","/config","/infra","/infra/[context]","/jobs","/jobs/[job]","/users","/workspace/new","/workspaces","/workspaces/[name]"]}}("static/chunks/614-3d29f98e0634b179.js","static/chunks/470-4d003c441839094d.js","static/chunks/293-351268365226d251.js","static/chunks/856-02e34c9fc5945066.js","static/chunks/798-c0525dc3f21e488d.js","static/chunks/121-8f55ee3fa6301784.js","static/chunks/973-1a09cac61cfcc1e1.js","static/chunks/236-ca00738e2f58ea65.js","static/chunks/37-64efcd0e9c54bff6.js","static/chunks/682-f3f1443ed2fba42f.js","static/chunks/843-786c36624d5ff61f.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
@@ -0,0 +1,6 @@
1
+ "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[236],{8236:function(e,s,t){t.d(s,{L4:function(){return D},Nk:function(){return I},x2:function(){return R}});var n=t(5893),r=t(7294),a=t(1163),l=t(1664),i=t.n(l),c=t(8799),o=t(803),d=t(7673),h=t(8764),x=t(6989),u=t(8969),m=t(3266),p=t(7324),j=t(9470),f=t(3626),g=t(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 w=(0,g.Z)("RefreshCcw",[["path",{d:"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"14sxne"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16",key:"1hlbsb"}],["path",{d:"M16 16h5v5",key:"ccwih5"}]]),N=(0,g.Z)("FileSearch",[["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M4.268 21a2 2 0 0 0 1.727 1H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3",key:"ms7g94"}],["path",{d:"m9 18-1.5-1.5",key:"1j6qii"}],["circle",{cx:"5",cy:"14",r:"3",key:"ufru5t"}]]),b=(0,g.Z)("MonitorPlay",[["path",{d:"M10 7.75a.75.75 0 0 1 1.142-.638l3.664 2.249a.75.75 0 0 1 0 1.278l-3.664 2.25a.75.75 0 0 1-1.142-.64z",key:"1pctta"}],["path",{d:"M12 17v4",key:"1riwvh"}],["path",{d:"M8 21h8",key:"1ev6f3"}],["rect",{x:"2",y:"3",width:"20",height:"14",rx:"2",key:"x3v2xh"}]]);var y=t(9284),v=t(4545),k=t(9307),C=t(3001),S=t(8950),E=t(6378),L=t(6856);let R={active:["PENDING","RUNNING","RECOVERING","SUBMITTED","STARTING","CANCELLING"],finished:["SUCCEEDED","FAILED","CANCELLED","FAILED_SETUP","FAILED_PRECHECKS","FAILED_NO_RESOURCE","FAILED_CONTROLLER"]},M="__ALL_WORKSPACES__",_=e=>{if(!e)return"-";let s=(0,x.GV)(e);if(r.isValidElement(s)&&s.props&&s.props.children&&(s=r.isValidElement(s.props.children)&&s.props.children.props&&s.props.children.props.children?s.props.children.props.children:s.props.children),"string"!=typeof s)return s;if("just now"===s)return"now";let t=s.match(/^About\s+(\d+)\s+(\w+)\s+ago$/);if(t){let e=t[1],s=t[2],n={second:"sec",seconds:"secs",minute:"min",minutes:"mins",hour:"hr",hours:"hrs",day:"d",days:"d",month:"mo",months:"mos",year:"yr",years:"yrs"};if(n[s])return"~ ".concat(e," ").concat(n[s]," ago")}let n=s.match(/^a[n]?\s+(\w+)\s+ago$/);if(n){let e=n[1],s={second:"sec",minute:"min",hour:"hr",day:"d",month:"mo",year:"yr"};if(s[e])return"1 ".concat(s[e]," ago")}let a=s.match(/^(\d+)\s+(\w+)\s+ago$/);if(a){let e=a[1],s=a[2],t={seconds:"secs",minutes:"mins",hours:"hrs",days:"d",months:"mo",years:"yr"};if(t[s])return"".concat(e," ").concat(t[s]," ago")}return s};function I(){let e=(0,a.useRouter)(),[s,t]=(0,r.useState)(!1),l=r.useRef(null),[o,d]=(0,r.useState)({isOpen:!1,title:"",message:"",onConfirm:null}),h=(0,C.X)(),[g,w]=(0,r.useState)(M),[N,b]=(0,r.useState)([]);return(0,r.useEffect)(()=>{e.isReady&&e.query.workspace&&w(Array.isArray(e.query.workspace)?e.query.workspace[0]:e.query.workspace)},[e.isReady,e.query.workspace]),(0,r.useEffect)(()=>{(async()=>{try{await L.ZP.preloadForPage("jobs");let e=await E.ZP.get(p.fX),s=Object.keys(e),t=(await E.ZP.get(u.getManagedJobs)).jobs||[],n=[...new Set(t.map(e=>e.workspace||"default").filter(e=>e))],r=new Set(s);n.forEach(e=>r.add(e)),b(Array.from(r).sort())}catch(e){console.error("Error fetching data for workspace filter:",e),b(["default"])}})()},[]),(0,n.jsxs)(j.A,{highlighted:"jobs",children:[(0,n.jsxs)("div",{className:"flex items-center justify-between mb-4 h-5",children:[(0,n.jsxs)("div",{className:"text-base flex items-center",children:[(0,n.jsx)(i(),{href:"/jobs",className:"text-sky-blue hover:underline leading-none",children:"Managed Jobs"}),(0,n.jsxs)(S.Ph,{value:g,onValueChange:w,children:[(0,n.jsx)(S.i4,{className:"h-8 w-48 ml-4 mr-2 text-sm border-none focus:ring-0 focus:outline-none",children:(0,n.jsx)(S.ki,{placeholder:"Filter by workspace...",children:g===M?"All Workspaces":g})}),(0,n.jsxs)(S.Bw,{children:[(0,n.jsx)(S.Ql,{value:M,children:"All Workspaces"}),N.map(e=>(0,n.jsx)(S.Ql,{value:e,children:e},e))]})]})]}),(0,n.jsxs)("div",{className:"flex items-center space-x-2",children:[s&&(0,n.jsxs)("div",{className:"flex items-center mr-2",children:[(0,n.jsx)(c.Z,{size:15,className:"mt-0"}),(0,n.jsx)("span",{className:"ml-2 text-gray-500 text-sm",children:"Loading..."})]}),(0,n.jsxs)("button",{onClick:()=>{E.ZP.invalidate(u.getManagedJobs),E.ZP.invalidate(m.getClusters),E.ZP.invalidate(p.fX),l.current&&l.current()},disabled:s,className:"text-sky-blue hover:text-sky-blue-bright flex items-center",title:"Refresh",children:[(0,n.jsx)(f.Z,{className:"h-4 w-4 mr-1.5"}),!h&&(0,n.jsx)("span",{children:"Refresh"})]})]})]}),(0,n.jsx)(O,{refreshInterval:x.yc,setLoading:t,refreshDataRef:l,workspaceFilter:g}),(0,n.jsx)(y.cV,{isOpen:o.isOpen,onClose:()=>d({...o,isOpen:!1}),onConfirm:o.onConfirm,title:o.title,message:o.message})]})}function O(e){let{refreshInterval:s,setLoading:t,refreshDataRef:a,workspaceFilter:l}=e,[p,j]=(0,r.useState)([]),[f,g]=(0,r.useState)({key:null,direction:"ascending"}),[N,b]=(0,r.useState)(!1),[C,S]=(0,r.useState)(!0),[L,I]=(0,r.useState)(1),[O,D]=(0,r.useState)(10),[z,F]=(0,r.useState)(null),W=(0,r.useRef)(null),[U,q]=(0,r.useState)([]),[T,V]=(0,r.useState)({}),[B,J]=(0,r.useState)(!1),[G,H]=(0,r.useState)(!1),[X,$]=(0,r.useState)(!1),[K,Q]=(0,r.useState)("active"),[Y,ee]=(0,r.useState)(!0),[es,et]=(0,r.useState)({isOpen:!1,title:"",message:"",onConfirm:null}),en=async()=>{et({isOpen:!0,title:"Restart Controller",message:"Are you sure you want to restart the controller? This will temporarily interrupt job management.",onConfirm:async()=>{try{$(!0),b(!0),await (0,u.Ce)("restartcontroller"),await er()}catch(e){console.error("Error restarting controller:",e)}finally{$(!1),b(!1)}}})},er=r.useCallback(async()=>{b(!0),t(!0);try{let[e,s]=await Promise.all([E.ZP.get(u.getManagedJobs),E.ZP.get(m.getClusters)]),{jobs:t,controllerStopped:n}=e,r=s.find(e=>(0,v.Ym)(e.cluster)),a=r?r.status:"NOT_FOUND",l=!1;"STOPPED"==a&&n&&(l=!0),"LAUNCHING"==a?H(!0):H(!1),j(t),J(l)}catch(e){console.error("Error fetching data:",e),j([])}finally{b(!1),t(!1),S(!1)}},[t]);r.useEffect(()=>{a&&(a.current=er)},[a,er]),(0,r.useEffect)(()=>{j([]);let e=!0;er();let t=setInterval(()=>{e&&er()},s);return()=>{e=!1,clearInterval(t)}},[s,er]),(0,r.useEffect)(()=>{I(1)},[K,p.length]),(0,r.useEffect)(()=>{q([]),ee(!0)},[K]);let ea=e=>{let s="ascending";f.key===e&&"ascending"===f.direction&&(s="descending"),g({key:e,direction:s})},el=e=>f.key===e?"ascending"===f.direction?" ↑":" ↓":"";r.useMemo(()=>({active:p.filter(e=>R.active.includes(e.status)).length,finished:p.filter(e=>R.finished.includes(e.status)).length}),[p]);let ei=e=>U.length>0?U.includes(e):R[K].includes(e),ec=r.useMemo(()=>{let e=l&&l!==M?p.filter(e=>(e.workspace||"default").toLowerCase()===l.toLowerCase()):p;return U.length>0?e.filter(e=>U.includes(e.status)):Y?e.filter(e=>R[K].includes(e.status)):[]},[p,K,U,Y,R,l]),eo=r.useMemo(()=>f.key?[...ec].sort((e,s)=>e[f.key]<s[f.key]?"ascending"===f.direction?-1:1:e[f.key]>s[f.key]?"ascending"===f.direction?1:-1:0):ec,[ec,f]),ed=Math.ceil(eo.length/O),eh=(L-1)*O,ex=eh+O,eu=eo.slice(eh,ex),em=e=>{if(U.includes(e)){let s=U.filter(s=>s!==e);0===s.length?(ee(!0),q([])):(q(s),ee(!1))}else q([...U,e]),ee(!1)};return(0,r.useEffect)(()=>{V(p.reduce((e,s)=>(e[s.status]=(e[s.status]||0)+1,e),{}))},[p]),(0,n.jsxs)("div",{className:"relative",children:[(0,n.jsx)("div",{className:"flex flex-col space-y-1 mb-1",children:(0,n.jsxs)("div",{className:"flex flex-wrap items-center text-sm mb-1",children:[(0,n.jsx)("span",{className:"mr-2 text-sm font-medium",children:"Statuses:"}),(0,n.jsxs)("div",{className:"flex flex-wrap gap-2 items-center",children:[!N&&0===p.length&&!C&&(0,n.jsx)("span",{className:"text-gray-500 mr-2",children:"No jobs found"}),Object.entries(T).map(e=>{let[s,t]=e;return(0,n.jsxs)("button",{onClick:()=>em(s),className:"px-3 py-1 rounded-full flex items-center space-x-2 ".concat(ei(s)||U.includes(s)?(0,k.Cl)(s):"bg-gray-50 text-gray-600 hover:bg-gray-100"),children:[(0,n.jsx)("span",{children:s}),(0,n.jsx)("span",{className:"text-xs ".concat(ei(s)||U.includes(s)?"bg-white/50":"bg-gray-200"," px-1.5 py-0.5 rounded"),children:t})]},s)}),p.length>0&&(0,n.jsxs)("div",{className:"flex items-center ml-2 gap-2",children:[(0,n.jsx)("span",{className:"text-gray-500",children:"("}),(0,n.jsx)("button",{onClick:()=>{Q("active"),q([]),ee(!0)},className:"text-sm font-medium ".concat("active"===K&&Y?"text-green-700 underline":"text-gray-600 hover:text-green-700 hover:underline"),children:"show all active jobs"}),(0,n.jsx)("span",{className:"text-gray-500 mx-1",children:"|"}),(0,n.jsx)("button",{onClick:()=>{Q("finished"),q([]),ee(!0)},className:"text-sm font-medium ".concat("finished"===K&&Y?"text-blue-700 underline":"text-gray-600 hover:text-blue-700 hover:underline"),children:"show all finished jobs"}),(0,n.jsx)("span",{className:"text-gray-500",children:")"})]})]})]})}),(0,n.jsx)(d.Zb,{children:(0,n.jsxs)(h.iA,{children:[(0,n.jsx)(h.xD,{children:(0,n.jsxs)(h.SC,{children:[(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("id"),children:["ID",el("id")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("name"),children:["Name",el("name")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("user"),children:["User",el("user")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("workspace"),children:["Workspace",el("workspace")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("submitted_at"),children:["Submitted",el("submitted_at")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("job_duration"),children:["Duration",el("job_duration")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("status"),children:["Status",el("status")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("priority"),children:["Priority",el("priority")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("resources_str"),children:["Requested",el("resources_str")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("infra"),children:["Infra",el("infra")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("cluster"),children:["Resources",el("cluster")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>ea("recoveries"),children:["Recoveries",el("recoveries")]}),(0,n.jsx)(h.ss,{children:"Details"}),(0,n.jsx)(h.ss,{children:"Logs"})]})}),(0,n.jsx)(h.RM,{children:N&&C?(0,n.jsx)(h.SC,{children:(0,n.jsx)(h.pj,{colSpan:13,className:"text-center py-6 text-gray-500",children:(0,n.jsxs)("div",{className:"flex justify-center items-center",children:[(0,n.jsx)(c.Z,{size:20,className:"mr-2"}),(0,n.jsx)("span",{children:"Loading..."})]})})}):eu.length>0?(0,n.jsx)(n.Fragment,{children:eu.map(e=>(0,n.jsxs)(r.Fragment,{children:[(0,n.jsxs)(h.SC,{children:[(0,n.jsx)(h.pj,{children:(0,n.jsx)(i(),{href:"/jobs/".concat(e.id),className:"text-blue-600",children:e.id})}),(0,n.jsx)(h.pj,{children:(0,n.jsx)(i(),{href:"/jobs/".concat(e.id),className:"text-blue-600",children:e.name})}),(0,n.jsx)(h.pj,{children:e.user}),(0,n.jsx)(h.pj,{children:(0,n.jsx)(i(),{href:"/workspaces",className:"text-blue-600 hover:underline",children:e.workspace||"default"})}),(0,n.jsx)(h.pj,{children:_(e.submitted_at)}),(0,n.jsx)(h.pj,{children:(0,x.LU)(e.job_duration)}),(0,n.jsx)(h.pj,{children:(0,n.jsx)(k.OE,{status:e.status})}),(0,n.jsx)(h.pj,{children:e.priority}),(0,n.jsx)(h.pj,{children:e.requested_resources}),(0,n.jsx)(h.pj,{children:e.infra&&"-"!==e.infra?(0,n.jsx)(x.Md,{content:e.full_infra||e.infra,className:"text-sm text-muted-foreground",children:(0,n.jsxs)("span",{children:[(0,n.jsx)(i(),{href:"/infra",className:"text-blue-600 hover:underline",children:e.cloud||e.infra.split("(")[0].trim()}),e.infra.includes("(")&&(0,n.jsx)("span",{children:" "+e.infra.substring(e.infra.indexOf("("))})]})}):(0,n.jsx)("span",{children:e.infra||"-"})}),(0,n.jsx)(h.pj,{children:(0,n.jsx)(x.Md,{content:e.resources_str_full||e.resources_str,className:"text-sm text-muted-foreground",children:(0,n.jsx)("span",{children:e.resources_str})})}),(0,n.jsx)(h.pj,{children:e.recoveries}),(0,n.jsx)(h.pj,{children:e.details?(0,n.jsx)(Z,{text:e.details,rowId:e.id,expandedRowId:z,setExpandedRowId:F}):"-"}),(0,n.jsx)(h.pj,{children:(0,n.jsx)(P,{jobParent:"/jobs",jobId:e.id,managed:!0})})]}),z===e.id&&(0,n.jsx)(A,{text:e.details,colSpan:13,innerRef:W})]},e.id))}):(0,n.jsx)(h.SC,{children:(0,n.jsx)(h.pj,{colSpan:13,className:"text-center py-6",children:(0,n.jsxs)("div",{className:"flex flex-col items-center space-y-4",children:[G&&(0,n.jsxs)("div",{className:"flex flex-col items-center space-y-2",children:[(0,n.jsx)("p",{className:"text-gray-700",children:"The managed job controller is launching. Please wait for it to be ready."}),(0,n.jsxs)("div",{className:"flex items-center",children:[(0,n.jsx)(c.Z,{size:12,className:"mr-2"}),(0,n.jsx)("span",{className:"text-gray-500",children:"Launching..."})]})]}),!B&&!G&&(0,n.jsx)("p",{className:"text-gray-500",children:"No active jobs"}),B&&(0,n.jsxs)("div",{className:"flex flex-col items-center space-y-2",children:[(0,n.jsx)("p",{className:"text-gray-700",children:"The managed job controller has been stopped. Please restart it to check the latest job status."}),(0,n.jsx)(o.z,{variant:"outline",size:"sm",onClick:en,className:"text-sky-blue hover:text-sky-blue-bright",disabled:N||X,children:X?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(c.Z,{size:12,className:"mr-2"}),"Restarting..."]}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(w,{className:"h-4 w-4 mr-2"}),"Restart Controller"]})})]})]})})})})]})}),eo.length>0&&(0,n.jsx)("div",{className:"flex justify-end items-center py-2 px-4 text-sm text-gray-700",children:(0,n.jsxs)("div",{className:"flex items-center space-x-4",children:[(0,n.jsxs)("div",{className:"flex items-center",children:[(0,n.jsx)("span",{className:"mr-2",children:"Rows per page:"}),(0,n.jsxs)("div",{className:"relative inline-block",children:[(0,n.jsxs)("select",{value:O,onChange:e=>{D(parseInt(e.target.value,10)),I(1)},className:"py-1 pl-2 pr-6 appearance-none outline-none cursor-pointer border-none bg-transparent",style:{minWidth:"40px"},children:[(0,n.jsx)("option",{value:10,children:"10"}),(0,n.jsx)("option",{value:30,children:"30"}),(0,n.jsx)("option",{value:50,children:"50"}),(0,n.jsx)("option",{value:100,children:"100"}),(0,n.jsx)("option",{value:200,children:"200"})]}),(0,n.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-4 w-4 text-gray-500 absolute right-0 top-1/2 transform -translate-y-1/2 pointer-events-none",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:(0,n.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]})]}),(0,n.jsxs)("div",{children:[eh+1," – ",Math.min(ex,eo.length)," of"," ",eo.length]}),(0,n.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,n.jsx)(o.z,{variant:"ghost",size:"icon",onClick:()=>{I(e=>Math.max(e-1,1))},disabled:1===L,className:"text-gray-500 h-8 w-8 p-0",children:(0,n.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"chevron-left",children:(0,n.jsx)("path",{d:"M15 18l-6-6 6-6"})})}),(0,n.jsx)(o.z,{variant:"ghost",size:"icon",onClick:()=>{I(e=>Math.min(e+1,ed))},disabled:L===ed||0===ed,className:"text-gray-500 h-8 w-8 p-0",children:(0,n.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"chevron-right",children:(0,n.jsx)("path",{d:"M9 18l6-6-6-6"})})})]})]})}),(0,n.jsx)(y.cV,{isOpen:es.isOpen,onClose:()=>et({...es,isOpen:!1}),onConfirm:es.onConfirm,title:es.title,message:es.message})]})}function P(e){let{withLabel:s=!1,jobParent:t,jobId:r,managed:l}=e,i=(0,a.useRouter)(),c=(e,s)=>{e.preventDefault(),e.stopPropagation(),i.push({pathname:"".concat(t,"/").concat(r),query:{tab:s}})};return(0,n.jsxs)("div",{className:"flex items-center space-x-4",children:[(0,n.jsx)(x.WH,{content:"View Job Logs",className:"capitalize text-sm text-muted-foreground",children:(0,n.jsxs)("button",{onClick:e=>c(e,"logs"),className:"text-sky-blue hover:text-sky-blue-bright font-medium inline-flex items-center h-8",children:[(0,n.jsx)(N,{className:"w-4 h-4"}),s&&(0,n.jsx)("span",{className:"ml-1.5",children:"Logs"})]})},"logs"),l&&(0,n.jsx)(x.WH,{content:"View Controller Logs",className:"capitalize text-sm text-muted-foreground",children:(0,n.jsxs)("button",{onClick:e=>c(e,"controllerlogs"),className:"text-sky-blue hover:text-sky-blue-bright font-medium inline-flex items-center h-8",children:[(0,n.jsx)(b,{className:"w-4 h-4"}),s&&(0,n.jsx)("span",{className:"ml-2",children:"Controller Logs"})]})},"controllerlogs")]})}function D(e){let{clusterName:s,clusterJobData:t,loading:a}=e,[l,u]=(0,r.useState)(null),[m,p]=(0,r.useState)({key:null,direction:"ascending"}),[j,f]=(0,r.useState)(1),[g,w]=(0,r.useState)(10),N=(0,r.useRef)(null),[b,y]=(0,r.useState)(null);(0,r.useEffect)(()=>{let e=e=>{l&&N.current&&!N.current.contains(e.target)&&u(null)};return document.addEventListener("mousedown",e),()=>{document.removeEventListener("mousedown",e)}},[l]);let v=r.useMemo(()=>t||[],[t]);(0,r.useEffect)(()=>{JSON.stringify(t)!==JSON.stringify(b)&&y(t)},[t,b]);let C=r.useMemo(()=>m.key?[...v].sort((e,s)=>e[m.key]<s[m.key]?"ascending"===m.direction?-1:1:e[m.key]>s[m.key]?"ascending"===m.direction?1:-1:0):v,[v,m]),S=e=>{let s="ascending";m.key===e&&"ascending"===m.direction&&(s="descending"),p({key:e,direction:s})},E=e=>m.key===e?"ascending"===m.direction?" ↑":" ↓":"",L=Math.ceil(C.length/g),R=(j-1)*g,M=R+g,I=C.slice(R,M);return(0,n.jsxs)("div",{className:"relative",children:[(0,n.jsxs)(d.Zb,{children:[(0,n.jsxs)("div",{className:"flex items-center justify-between p-4",children:[(0,n.jsx)("h3",{className:"text-lg font-semibold",children:"Cluster Jobs"}),a&&(0,n.jsxs)("div",{className:"flex items-center mr-2",children:[(0,n.jsx)(c.Z,{size:15,className:"mt-0"}),(0,n.jsx)("span",{className:"ml-2 text-gray-500 text-sm",children:"Loading..."})]})]}),(0,n.jsxs)(h.iA,{children:[(0,n.jsx)(h.xD,{children:(0,n.jsxs)(h.SC,{children:[(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>S("id"),children:["ID",E("id")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>S("job"),children:["Name",E("job")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>S("user"),children:["User",E("user")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>S("workspace"),children:["Workspace",E("workspace")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>S("submitted_at"),children:["Submitted",E("submitted_at")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>S("job_duration"),children:["Duration",E("job_duration")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>S("status"),children:["Status",E("status")]}),(0,n.jsxs)(h.ss,{className:"sortable whitespace-nowrap",onClick:()=>S("resources"),children:["Resources",E("resources")]}),(0,n.jsx)(h.ss,{className:"whitespace-nowrap",children:"Logs"})]})}),(0,n.jsx)(h.RM,{children:I.length>0?I.map(e=>(0,n.jsxs)(r.Fragment,{children:[(0,n.jsxs)(h.SC,{className:l===e.id?"selected-row":"",children:[(0,n.jsx)(h.pj,{children:(0,n.jsx)(i(),{href:"/clusters/".concat(s,"/").concat(e.id),className:"text-blue-600",children:e.id})}),(0,n.jsx)(h.pj,{children:(0,n.jsx)(i(),{href:"/clusters/".concat(s,"/").concat(e.id),className:"text-blue-600",children:(0,n.jsx)(Z,{text:e.job||"Unnamed job",rowId:e.id,expandedRowId:l,setExpandedRowId:u})})}),(0,n.jsx)(h.pj,{children:e.user}),(0,n.jsx)(h.pj,{children:(0,n.jsx)(i(),{href:"/workspaces",className:"text-blue-600 hover:underline",children:e.workspace||"default"})}),(0,n.jsx)(h.pj,{children:_(e.submitted_at)}),(0,n.jsx)(h.pj,{children:(0,x.LU)(e.job_duration)}),(0,n.jsx)(h.pj,{children:(0,n.jsx)(k.OE,{status:e.status})}),(0,n.jsx)(h.pj,{children:e.resources}),(0,n.jsx)(h.pj,{className:"flex content-center items-center",children:(0,n.jsx)(P,{jobParent:"/clusters/".concat(s),jobId:e.id,managed:!1})})]}),l===e.id&&(0,n.jsx)(A,{text:e.job||"Unnamed job",colSpan:9,innerRef:N})]},e.id)):(0,n.jsx)(h.SC,{children:(0,n.jsx)(h.pj,{colSpan:8,className:"text-center py-6 text-gray-500",children:"No jobs found"})})})]})]}),C.length>0&&(0,n.jsx)("div",{className:"flex justify-end items-center py-2 px-4 text-sm text-gray-700",children:(0,n.jsxs)("div",{className:"flex items-center space-x-4",children:[(0,n.jsxs)("div",{className:"flex items-center",children:[(0,n.jsx)("span",{className:"mr-2",children:"Rows per page:"}),(0,n.jsxs)("div",{className:"relative inline-block",children:[(0,n.jsxs)("select",{value:g,onChange:e=>{w(parseInt(e.target.value,10)),f(1)},className:"py-1 pl-2 pr-6 appearance-none outline-none cursor-pointer border-none bg-transparent",style:{minWidth:"40px"},children:[(0,n.jsx)("option",{value:5,children:"5"}),(0,n.jsx)("option",{value:10,children:"10"}),(0,n.jsx)("option",{value:20,children:"20"}),(0,n.jsx)("option",{value:50,children:"50"})]}),(0,n.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-4 w-4 text-gray-500 absolute right-0 top-1/2 transform -translate-y-1/2 pointer-events-none",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:(0,n.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]})]}),(0,n.jsxs)("div",{children:[R+1," – ",Math.min(M,C.length)," of"," ",C.length]}),(0,n.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,n.jsx)(o.z,{variant:"ghost",size:"icon",onClick:()=>{f(e=>Math.max(e-1,1))},disabled:1===j,className:"text-gray-500 h-8 w-8 p-0",children:(0,n.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"chevron-left",children:(0,n.jsx)("path",{d:"M15 18l-6-6 6-6"})})}),(0,n.jsx)(o.z,{variant:"ghost",size:"icon",onClick:()=>{f(e=>Math.min(e+1,L))},disabled:j===L||0===L,className:"text-gray-500 h-8 w-8 p-0",children:(0,n.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"chevron-right",children:(0,n.jsx)("path",{d:"M9 18l6-6-6-6"})})})]})]})})]})}function A(e){let{text:s,colSpan:t,innerRef:r}=e;return(0,n.jsx)(h.SC,{className:"expanded-details",children:(0,n.jsx)(h.pj,{colSpan:t,children:(0,n.jsx)("div",{className:"p-4 bg-gray-50 rounded-md border border-gray-200",ref:r,children:(0,n.jsx)("div",{className:"flex justify-between items-start",children:(0,n.jsxs)("div",{className:"flex-1",children:[(0,n.jsx)("p",{className:"text-sm font-medium text-gray-900",children:"Full Details"}),(0,n.jsx)("p",{className:"mt-1 text-sm text-gray-700",style:{whiteSpace:"pre-wrap"},children:s})]})})})})})}function Z(e){let{text:s,rowId:t,expandedRowId:a,setExpandedRowId:l}=e,i=s.length>50,c=a===t,o=i?"".concat(s.substring(0,50)):s,d=(0,r.useRef)(null);return(0,n.jsxs)("div",{className:"truncated-details relative max-w-full flex items-center",children:[(0,n.jsx)("span",{className:"truncate",children:o}),i&&(0,n.jsx)("button",{ref:d,type:"button",onClick:e=>{e.preventDefault(),e.stopPropagation(),l(c?null:t)},className:"text-blue-600 hover:text-blue-800 font-medium ml-1 flex-shrink-0","data-button-type":"show-more-less",children:c?"... show less":"... show more"})]})}}}]);