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.
- sky/__init__.py +2 -2
- sky/adaptors/nebius.py +99 -16
- sky/authentication.py +54 -7
- sky/backends/backend_utils.py +35 -22
- sky/backends/cloud_vm_ray_backend.py +30 -15
- sky/check.py +1 -1
- sky/cli.py +20 -8
- sky/client/cli.py +20 -8
- sky/client/oauth.py +82 -0
- sky/client/sdk.py +60 -10
- sky/clouds/nebius.py +55 -14
- sky/clouds/service_catalog/data_fetchers/fetch_gcp.py +3 -3
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/Q32Bxr2Pby5tFDW-y5TNg/_buildManifest.js +1 -0
- sky/dashboard/out/_next/static/chunks/236-ca00738e2f58ea65.js +6 -0
- sky/dashboard/out/_next/static/chunks/37-64efcd0e9c54bff6.js +6 -0
- sky/dashboard/out/_next/static/chunks/{173-7db8607cefc20f70.js → 614-3d29f98e0634b179.js} +2 -2
- sky/dashboard/out/_next/static/chunks/682-f3f1443ed2fba42f.js +6 -0
- sky/dashboard/out/_next/static/chunks/798-c0525dc3f21e488d.js +1 -0
- sky/dashboard/out/_next/static/chunks/843-786c36624d5ff61f.js +11 -0
- sky/dashboard/out/_next/static/chunks/856-02e34c9fc5945066.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-42d3656aba9d2e78.js +6 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-20835df7b0c4599c.js +6 -0
- sky/dashboard/out/_next/static/chunks/pages/{clusters-943992b84fd6f4ee.js → clusters-f37ff20f0af29aae.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{config-41738d1896fc02fe.js → config-3c6a2dabf56e8cd6.js} +2 -2
- sky/dashboard/out/_next/static/chunks/pages/infra/[context]-342bc15bb78ab2e5.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/infra-7b4b8e7fa9fa0827.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-258decb65e95f520.js +11 -0
- sky/dashboard/out/_next/static/chunks/pages/{jobs-a4efc09e61988f8d.js → jobs-78a6c5ba3e24c0cf.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{users-b2634885d67c49a6.js → users-89f9212b81d8897e.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/workspace/{new-579b3203c7c19d84.js → new-198b6e00d7d724c5.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-9388e38fac73ee8f.js → [name]-2ce792183b03c341.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/workspaces-17d41826537196e7.js +1 -0
- sky/dashboard/out/_next/static/chunks/webpack-f27c9a32aa3d9c6d.js +1 -0
- sky/dashboard/out/_next/static/css/5411b9fb0a783c1c.css +3 -0
- sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
- sky/dashboard/out/clusters/[cluster].html +1 -1
- sky/dashboard/out/clusters.html +1 -1
- sky/dashboard/out/config.html +1 -1
- sky/dashboard/out/index.html +1 -1
- sky/dashboard/out/infra/[context].html +1 -0
- sky/dashboard/out/infra.html +1 -1
- sky/dashboard/out/jobs/[job].html +1 -1
- sky/dashboard/out/jobs.html +1 -1
- sky/dashboard/out/users.html +1 -1
- sky/dashboard/out/workspace/new.html +1 -1
- sky/dashboard/out/workspaces/[name].html +1 -1
- sky/dashboard/out/workspaces.html +1 -1
- sky/exceptions.py +11 -1
- sky/global_user_state.py +149 -1
- sky/jobs/client/sdk.py +1 -0
- sky/jobs/constants.py +3 -1
- sky/jobs/controller.py +3 -5
- sky/jobs/recovery_strategy.py +148 -102
- sky/jobs/scheduler.py +23 -8
- sky/jobs/server/core.py +16 -0
- sky/jobs/state.py +153 -39
- sky/jobs/utils.py +33 -5
- sky/provision/kubernetes/utils.py +2 -1
- sky/provision/provisioner.py +15 -10
- sky/resources.py +16 -1
- sky/serve/controller.py +10 -7
- sky/serve/replica_managers.py +22 -18
- sky/serve/service.py +5 -4
- sky/server/common.py +11 -4
- sky/server/html/token_page.html +32 -6
- sky/server/server.py +3 -1
- sky/server/stream_utils.py +21 -0
- sky/setup_files/dependencies.py +7 -1
- sky/skylet/constants.py +1 -1
- sky/task.py +26 -0
- sky/templates/jobs-controller.yaml.j2 +2 -1
- sky/templates/kubernetes-ray.yml.j2 +19 -1
- sky/utils/common_utils.py +66 -0
- sky/utils/rich_utils.py +5 -0
- sky/utils/schemas.py +32 -1
- {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/METADATA +3 -1
- {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/RECORD +84 -81
- sky/dashboard/out/_next/static/Mx1iAbDQn1jMHh3UHmK3R/_buildManifest.js +0 -1
- sky/dashboard/out/_next/static/chunks/236-d6900c828331f664.js +0 -6
- sky/dashboard/out/_next/static/chunks/320-afea3ddcc5bd1c6c.js +0 -6
- sky/dashboard/out/_next/static/chunks/578-9146658cead92981.js +0 -6
- sky/dashboard/out/_next/static/chunks/843-256ec920f6d5f41f.js +0 -11
- sky/dashboard/out/_next/static/chunks/856-62b87c68917b08ed.js +0 -1
- sky/dashboard/out/_next/static/chunks/9f96d65d-5a3e4af68c26849e.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-159bffb2fa34ed54.js +0 -6
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-9506c00257d10dbd.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/infra-881fcd902fbbd0e5.js +0 -6
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-2c29e97a6aa50dd4.js +0 -6
- sky/dashboard/out/_next/static/chunks/pages/workspaces-610c49ae3619ee85.js +0 -1
- sky/dashboard/out/_next/static/chunks/webpack-deda68c926e8d0bc.js +0 -1
- sky/dashboard/out/_next/static/css/ffd1cd601648c303.css +0 -3
- /sky/dashboard/out/_next/static/{Mx1iAbDQn1jMHh3UHmK3R → Q32Bxr2Pby5tFDW-y5TNg}/_ssgManifest.js +0 -0
- /sky/dashboard/out/_next/static/chunks/pages/{_app-a631df412d8172de.js → _app-f19ea34b91c33950.js} +0 -0
- {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250528.dist-info → skypilot_nightly-1.0.0.dev20250530.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
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(
|
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(
|
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: {
|
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(
|
5320
|
-
|
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
|
-
|
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(
|
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(
|
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: {
|
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(
|
5320
|
-
|
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
|
-
|
1930
|
-
|
1931
|
-
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
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.
|
307
|
-
f'{_INDENT_PREFIX} or generate
|
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.
|
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.
|
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
|
-
|
361
|
-
for
|
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
|
-
|
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
|
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
|
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:
|
sky/dashboard/out/404.html
CHANGED
@@ -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/
|
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,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";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"})]})}}}]);
|