skypilot-nightly 1.0.0.dev2024053101__py3-none-any.whl → 1.0.0.dev2025022801__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 (299) hide show
  1. sky/__init__.py +64 -32
  2. sky/adaptors/aws.py +23 -6
  3. sky/adaptors/azure.py +432 -15
  4. sky/adaptors/cloudflare.py +5 -5
  5. sky/adaptors/common.py +19 -9
  6. sky/adaptors/do.py +20 -0
  7. sky/adaptors/gcp.py +3 -2
  8. sky/adaptors/kubernetes.py +122 -88
  9. sky/adaptors/nebius.py +100 -0
  10. sky/adaptors/oci.py +39 -1
  11. sky/adaptors/vast.py +29 -0
  12. sky/admin_policy.py +101 -0
  13. sky/authentication.py +117 -98
  14. sky/backends/backend.py +52 -20
  15. sky/backends/backend_utils.py +669 -557
  16. sky/backends/cloud_vm_ray_backend.py +1099 -808
  17. sky/backends/local_docker_backend.py +14 -8
  18. sky/backends/wheel_utils.py +38 -20
  19. sky/benchmark/benchmark_utils.py +22 -23
  20. sky/check.py +76 -27
  21. sky/cli.py +1586 -1139
  22. sky/client/__init__.py +1 -0
  23. sky/client/cli.py +5683 -0
  24. sky/client/common.py +345 -0
  25. sky/client/sdk.py +1765 -0
  26. sky/cloud_stores.py +283 -19
  27. sky/clouds/__init__.py +7 -2
  28. sky/clouds/aws.py +303 -112
  29. sky/clouds/azure.py +185 -179
  30. sky/clouds/cloud.py +115 -37
  31. sky/clouds/cudo.py +29 -22
  32. sky/clouds/do.py +313 -0
  33. sky/clouds/fluidstack.py +44 -54
  34. sky/clouds/gcp.py +206 -65
  35. sky/clouds/ibm.py +26 -21
  36. sky/clouds/kubernetes.py +345 -91
  37. sky/clouds/lambda_cloud.py +40 -29
  38. sky/clouds/nebius.py +297 -0
  39. sky/clouds/oci.py +129 -90
  40. sky/clouds/paperspace.py +22 -18
  41. sky/clouds/runpod.py +53 -34
  42. sky/clouds/scp.py +28 -24
  43. sky/clouds/service_catalog/__init__.py +19 -13
  44. sky/clouds/service_catalog/aws_catalog.py +29 -12
  45. sky/clouds/service_catalog/azure_catalog.py +33 -6
  46. sky/clouds/service_catalog/common.py +95 -75
  47. sky/clouds/service_catalog/constants.py +3 -3
  48. sky/clouds/service_catalog/cudo_catalog.py +13 -3
  49. sky/clouds/service_catalog/data_fetchers/fetch_aws.py +36 -21
  50. sky/clouds/service_catalog/data_fetchers/fetch_azure.py +31 -4
  51. sky/clouds/service_catalog/data_fetchers/fetch_cudo.py +8 -117
  52. sky/clouds/service_catalog/data_fetchers/fetch_fluidstack.py +197 -44
  53. sky/clouds/service_catalog/data_fetchers/fetch_gcp.py +224 -36
  54. sky/clouds/service_catalog/data_fetchers/fetch_lambda_cloud.py +44 -24
  55. sky/clouds/service_catalog/data_fetchers/fetch_vast.py +147 -0
  56. sky/clouds/service_catalog/data_fetchers/fetch_vsphere.py +1 -1
  57. sky/clouds/service_catalog/do_catalog.py +111 -0
  58. sky/clouds/service_catalog/fluidstack_catalog.py +2 -2
  59. sky/clouds/service_catalog/gcp_catalog.py +16 -2
  60. sky/clouds/service_catalog/ibm_catalog.py +2 -2
  61. sky/clouds/service_catalog/kubernetes_catalog.py +192 -70
  62. sky/clouds/service_catalog/lambda_catalog.py +8 -3
  63. sky/clouds/service_catalog/nebius_catalog.py +116 -0
  64. sky/clouds/service_catalog/oci_catalog.py +31 -4
  65. sky/clouds/service_catalog/paperspace_catalog.py +2 -2
  66. sky/clouds/service_catalog/runpod_catalog.py +2 -2
  67. sky/clouds/service_catalog/scp_catalog.py +2 -2
  68. sky/clouds/service_catalog/vast_catalog.py +104 -0
  69. sky/clouds/service_catalog/vsphere_catalog.py +2 -2
  70. sky/clouds/utils/aws_utils.py +65 -0
  71. sky/clouds/utils/azure_utils.py +91 -0
  72. sky/clouds/utils/gcp_utils.py +5 -9
  73. sky/clouds/utils/oci_utils.py +47 -5
  74. sky/clouds/utils/scp_utils.py +4 -3
  75. sky/clouds/vast.py +280 -0
  76. sky/clouds/vsphere.py +22 -18
  77. sky/core.py +361 -107
  78. sky/dag.py +41 -28
  79. sky/data/data_transfer.py +37 -0
  80. sky/data/data_utils.py +211 -32
  81. sky/data/mounting_utils.py +182 -30
  82. sky/data/storage.py +2118 -270
  83. sky/data/storage_utils.py +126 -5
  84. sky/exceptions.py +179 -8
  85. sky/execution.py +158 -85
  86. sky/global_user_state.py +150 -34
  87. sky/jobs/__init__.py +12 -10
  88. sky/jobs/client/__init__.py +0 -0
  89. sky/jobs/client/sdk.py +302 -0
  90. sky/jobs/constants.py +49 -11
  91. sky/jobs/controller.py +161 -99
  92. sky/jobs/dashboard/dashboard.py +171 -25
  93. sky/jobs/dashboard/templates/index.html +572 -60
  94. sky/jobs/recovery_strategy.py +157 -156
  95. sky/jobs/scheduler.py +307 -0
  96. sky/jobs/server/__init__.py +1 -0
  97. sky/jobs/server/core.py +598 -0
  98. sky/jobs/server/dashboard_utils.py +69 -0
  99. sky/jobs/server/server.py +190 -0
  100. sky/jobs/state.py +627 -122
  101. sky/jobs/utils.py +615 -206
  102. sky/models.py +27 -0
  103. sky/optimizer.py +142 -83
  104. sky/provision/__init__.py +20 -5
  105. sky/provision/aws/config.py +124 -42
  106. sky/provision/aws/instance.py +130 -53
  107. sky/provision/azure/__init__.py +7 -0
  108. sky/{skylet/providers → provision}/azure/azure-config-template.json +19 -7
  109. sky/provision/azure/config.py +220 -0
  110. sky/provision/azure/instance.py +1012 -37
  111. sky/provision/common.py +31 -3
  112. sky/provision/constants.py +25 -0
  113. sky/provision/cudo/__init__.py +2 -1
  114. sky/provision/cudo/cudo_utils.py +112 -0
  115. sky/provision/cudo/cudo_wrapper.py +37 -16
  116. sky/provision/cudo/instance.py +28 -12
  117. sky/provision/do/__init__.py +11 -0
  118. sky/provision/do/config.py +14 -0
  119. sky/provision/do/constants.py +10 -0
  120. sky/provision/do/instance.py +287 -0
  121. sky/provision/do/utils.py +301 -0
  122. sky/provision/docker_utils.py +82 -46
  123. sky/provision/fluidstack/fluidstack_utils.py +57 -125
  124. sky/provision/fluidstack/instance.py +15 -43
  125. sky/provision/gcp/config.py +19 -9
  126. sky/provision/gcp/constants.py +7 -1
  127. sky/provision/gcp/instance.py +55 -34
  128. sky/provision/gcp/instance_utils.py +339 -80
  129. sky/provision/gcp/mig_utils.py +210 -0
  130. sky/provision/instance_setup.py +172 -133
  131. sky/provision/kubernetes/__init__.py +1 -0
  132. sky/provision/kubernetes/config.py +104 -90
  133. sky/provision/kubernetes/constants.py +8 -0
  134. sky/provision/kubernetes/instance.py +680 -325
  135. sky/provision/kubernetes/manifests/smarter-device-manager-daemonset.yaml +3 -0
  136. sky/provision/kubernetes/network.py +54 -20
  137. sky/provision/kubernetes/network_utils.py +70 -21
  138. sky/provision/kubernetes/utils.py +1370 -251
  139. sky/provision/lambda_cloud/__init__.py +11 -0
  140. sky/provision/lambda_cloud/config.py +10 -0
  141. sky/provision/lambda_cloud/instance.py +265 -0
  142. sky/{clouds/utils → provision/lambda_cloud}/lambda_utils.py +24 -23
  143. sky/provision/logging.py +1 -1
  144. sky/provision/nebius/__init__.py +11 -0
  145. sky/provision/nebius/config.py +11 -0
  146. sky/provision/nebius/instance.py +285 -0
  147. sky/provision/nebius/utils.py +318 -0
  148. sky/provision/oci/__init__.py +15 -0
  149. sky/provision/oci/config.py +51 -0
  150. sky/provision/oci/instance.py +436 -0
  151. sky/provision/oci/query_utils.py +681 -0
  152. sky/provision/paperspace/constants.py +6 -0
  153. sky/provision/paperspace/instance.py +4 -3
  154. sky/provision/paperspace/utils.py +2 -0
  155. sky/provision/provisioner.py +207 -130
  156. sky/provision/runpod/__init__.py +1 -0
  157. sky/provision/runpod/api/__init__.py +3 -0
  158. sky/provision/runpod/api/commands.py +119 -0
  159. sky/provision/runpod/api/pods.py +142 -0
  160. sky/provision/runpod/instance.py +64 -8
  161. sky/provision/runpod/utils.py +239 -23
  162. sky/provision/vast/__init__.py +10 -0
  163. sky/provision/vast/config.py +11 -0
  164. sky/provision/vast/instance.py +247 -0
  165. sky/provision/vast/utils.py +162 -0
  166. sky/provision/vsphere/common/vim_utils.py +1 -1
  167. sky/provision/vsphere/instance.py +8 -18
  168. sky/provision/vsphere/vsphere_utils.py +1 -1
  169. sky/resources.py +247 -102
  170. sky/serve/__init__.py +9 -9
  171. sky/serve/autoscalers.py +361 -299
  172. sky/serve/client/__init__.py +0 -0
  173. sky/serve/client/sdk.py +366 -0
  174. sky/serve/constants.py +12 -3
  175. sky/serve/controller.py +106 -36
  176. sky/serve/load_balancer.py +63 -12
  177. sky/serve/load_balancing_policies.py +84 -2
  178. sky/serve/replica_managers.py +42 -34
  179. sky/serve/serve_state.py +62 -32
  180. sky/serve/serve_utils.py +271 -160
  181. sky/serve/server/__init__.py +0 -0
  182. sky/serve/{core.py → server/core.py} +271 -90
  183. sky/serve/server/server.py +112 -0
  184. sky/serve/service.py +52 -16
  185. sky/serve/service_spec.py +95 -32
  186. sky/server/__init__.py +1 -0
  187. sky/server/common.py +430 -0
  188. sky/server/constants.py +21 -0
  189. sky/server/html/log.html +174 -0
  190. sky/server/requests/__init__.py +0 -0
  191. sky/server/requests/executor.py +472 -0
  192. sky/server/requests/payloads.py +487 -0
  193. sky/server/requests/queues/__init__.py +0 -0
  194. sky/server/requests/queues/mp_queue.py +76 -0
  195. sky/server/requests/requests.py +567 -0
  196. sky/server/requests/serializers/__init__.py +0 -0
  197. sky/server/requests/serializers/decoders.py +192 -0
  198. sky/server/requests/serializers/encoders.py +166 -0
  199. sky/server/server.py +1106 -0
  200. sky/server/stream_utils.py +141 -0
  201. sky/setup_files/MANIFEST.in +2 -5
  202. sky/setup_files/dependencies.py +159 -0
  203. sky/setup_files/setup.py +14 -125
  204. sky/sky_logging.py +59 -14
  205. sky/skylet/autostop_lib.py +2 -2
  206. sky/skylet/constants.py +183 -50
  207. sky/skylet/events.py +22 -10
  208. sky/skylet/job_lib.py +403 -258
  209. sky/skylet/log_lib.py +111 -71
  210. sky/skylet/log_lib.pyi +6 -0
  211. sky/skylet/providers/command_runner.py +6 -8
  212. sky/skylet/providers/ibm/node_provider.py +2 -2
  213. sky/skylet/providers/scp/config.py +11 -3
  214. sky/skylet/providers/scp/node_provider.py +8 -8
  215. sky/skylet/skylet.py +3 -1
  216. sky/skylet/subprocess_daemon.py +69 -17
  217. sky/skypilot_config.py +119 -57
  218. sky/task.py +205 -64
  219. sky/templates/aws-ray.yml.j2 +37 -7
  220. sky/templates/azure-ray.yml.j2 +27 -82
  221. sky/templates/cudo-ray.yml.j2 +7 -3
  222. sky/templates/do-ray.yml.j2 +98 -0
  223. sky/templates/fluidstack-ray.yml.j2 +7 -4
  224. sky/templates/gcp-ray.yml.j2 +26 -6
  225. sky/templates/ibm-ray.yml.j2 +3 -2
  226. sky/templates/jobs-controller.yaml.j2 +46 -11
  227. sky/templates/kubernetes-ingress.yml.j2 +7 -0
  228. sky/templates/kubernetes-loadbalancer.yml.j2 +7 -0
  229. sky/templates/{kubernetes-port-forward-proxy-command.sh.j2 → kubernetes-port-forward-proxy-command.sh} +51 -7
  230. sky/templates/kubernetes-ray.yml.j2 +292 -25
  231. sky/templates/lambda-ray.yml.j2 +30 -40
  232. sky/templates/nebius-ray.yml.j2 +79 -0
  233. sky/templates/oci-ray.yml.j2 +18 -57
  234. sky/templates/paperspace-ray.yml.j2 +10 -6
  235. sky/templates/runpod-ray.yml.j2 +26 -4
  236. sky/templates/scp-ray.yml.j2 +3 -2
  237. sky/templates/sky-serve-controller.yaml.j2 +12 -1
  238. sky/templates/skypilot-server-kubernetes-proxy.sh +36 -0
  239. sky/templates/vast-ray.yml.j2 +70 -0
  240. sky/templates/vsphere-ray.yml.j2 +8 -3
  241. sky/templates/websocket_proxy.py +64 -0
  242. sky/usage/constants.py +10 -1
  243. sky/usage/usage_lib.py +130 -37
  244. sky/utils/accelerator_registry.py +35 -51
  245. sky/utils/admin_policy_utils.py +147 -0
  246. sky/utils/annotations.py +51 -0
  247. sky/utils/cli_utils/status_utils.py +81 -23
  248. sky/utils/cluster_utils.py +356 -0
  249. sky/utils/command_runner.py +452 -89
  250. sky/utils/command_runner.pyi +77 -3
  251. sky/utils/common.py +54 -0
  252. sky/utils/common_utils.py +319 -108
  253. sky/utils/config_utils.py +204 -0
  254. sky/utils/control_master_utils.py +48 -0
  255. sky/utils/controller_utils.py +548 -266
  256. sky/utils/dag_utils.py +93 -32
  257. sky/utils/db_utils.py +18 -4
  258. sky/utils/env_options.py +29 -7
  259. sky/utils/kubernetes/create_cluster.sh +8 -60
  260. sky/utils/kubernetes/deploy_remote_cluster.sh +243 -0
  261. sky/utils/kubernetes/exec_kubeconfig_converter.py +73 -0
  262. sky/utils/kubernetes/generate_kubeconfig.sh +336 -0
  263. sky/utils/kubernetes/gpu_labeler.py +4 -4
  264. sky/utils/kubernetes/k8s_gpu_labeler_job.yaml +4 -3
  265. sky/utils/kubernetes/kubernetes_deploy_utils.py +228 -0
  266. sky/utils/kubernetes/rsync_helper.sh +24 -0
  267. sky/utils/kubernetes/ssh_jump_lifecycle_manager.py +1 -1
  268. sky/utils/log_utils.py +240 -33
  269. sky/utils/message_utils.py +81 -0
  270. sky/utils/registry.py +127 -0
  271. sky/utils/resources_utils.py +94 -22
  272. sky/utils/rich_utils.py +247 -18
  273. sky/utils/schemas.py +284 -64
  274. sky/{status_lib.py → utils/status_lib.py} +12 -7
  275. sky/utils/subprocess_utils.py +212 -46
  276. sky/utils/timeline.py +12 -7
  277. sky/utils/ux_utils.py +168 -15
  278. skypilot_nightly-1.0.0.dev2025022801.dist-info/METADATA +363 -0
  279. skypilot_nightly-1.0.0.dev2025022801.dist-info/RECORD +352 -0
  280. {skypilot_nightly-1.0.0.dev2024053101.dist-info → skypilot_nightly-1.0.0.dev2025022801.dist-info}/WHEEL +1 -1
  281. sky/clouds/cloud_registry.py +0 -31
  282. sky/jobs/core.py +0 -330
  283. sky/skylet/providers/azure/__init__.py +0 -2
  284. sky/skylet/providers/azure/azure-vm-template.json +0 -301
  285. sky/skylet/providers/azure/config.py +0 -170
  286. sky/skylet/providers/azure/node_provider.py +0 -466
  287. sky/skylet/providers/lambda_cloud/__init__.py +0 -2
  288. sky/skylet/providers/lambda_cloud/node_provider.py +0 -320
  289. sky/skylet/providers/oci/__init__.py +0 -2
  290. sky/skylet/providers/oci/node_provider.py +0 -488
  291. sky/skylet/providers/oci/query_helper.py +0 -383
  292. sky/skylet/providers/oci/utils.py +0 -21
  293. sky/utils/cluster_yaml_utils.py +0 -24
  294. sky/utils/kubernetes/generate_static_kubeconfig.sh +0 -137
  295. skypilot_nightly-1.0.0.dev2024053101.dist-info/METADATA +0 -315
  296. skypilot_nightly-1.0.0.dev2024053101.dist-info/RECORD +0 -275
  297. {skypilot_nightly-1.0.0.dev2024053101.dist-info → skypilot_nightly-1.0.0.dev2025022801.dist-info}/LICENSE +0 -0
  298. {skypilot_nightly-1.0.0.dev2024053101.dist-info → skypilot_nightly-1.0.0.dev2025022801.dist-info}/entry_points.txt +0 -0
  299. {skypilot_nightly-1.0.0.dev2024053101.dist-info → skypilot_nightly-1.0.0.dev2025022801.dist-info}/top_level.txt +0 -0
@@ -5,44 +5,372 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
7
  <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
8
- <title>SkyPilot Dashboard</title>
8
+ <title>SkyPilot Managed Jobs</title>
9
9
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
10
10
  integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
11
11
  <style>
12
- .table-no-stripes tbody tr:nth-of-type(even) {
13
- background-color: transparent;
12
+ :root {
13
+ --secondary-color: #6c757d;
14
+ --success-color: #198754;
15
+ --warning-color: #ffc107;
16
+ --info-color: #0dcaf0;
17
+ --light-color: #f8f9fa;
14
18
  }
15
19
 
16
- .table-hover-selected tbody tr:hover {
17
- background-color: #f5f5f5;
20
+ body {
21
+ margin-top: 0;
22
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
23
+ background-color: white;
18
24
  }
19
25
 
20
- .footer {
21
- font-size: 14px;
22
- color: #777;
23
- margin-top: 20px;
26
+ .container {
27
+ max-width: 100%;
28
+ width: 100%;
29
+ background-color: white;
30
+ border-radius: 0;
31
+ box-shadow: none;
32
+ padding: 2rem;
33
+ margin-bottom: 0;
24
34
  }
25
35
 
26
- body {
27
- margin-top: 20px;
36
+ header {
37
+ position: sticky;
38
+ top: 0;
39
+ background: white;
40
+ z-index: 1000;
41
+ padding: 1.5rem 2rem;
42
+ margin: -2rem -2rem 1.5rem -2rem; /* Negative margins to match container padding */
43
+ border-bottom: 1px solid #dee2e6;
28
44
  }
29
45
 
30
- .bg-light {
31
- color: #212529;
32
- /* for some reason not in bootstrap? */
46
+ /* Add shadow when header is sticky */
47
+ header.sticky {
48
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
33
49
  }
34
50
 
35
- .fixed-header-table thead {
36
- position: sticky;
37
- top: 0;
38
- background-color: #f8f9fa;
39
- /* Replace with your desired background color */
40
- color: #000000;
41
- /* Replace with your desired text color */
42
- z-index: 1;
51
+ h1 {
52
+ color: var(--primary-color);
53
+ font-weight: 600;
54
+ font-size: 2rem;
55
+ }
56
+
57
+ .table {
58
+ width: 100%; /* Ensure table takes full container width */
59
+ border-radius: 8px;
60
+ overflow: hidden;
61
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.05);
62
+ table-layout: auto; /* Allow table to adjust column widths automatically */
63
+ }
64
+
65
+ .fixed-header-table thead th,
66
+ .fixed-header-table thead td { /* Added td selector */
67
+ background-color: var(--light-color);
68
+ padding: 1rem;
69
+ font-weight: 600;
70
+ border-bottom: 2px solid #dee2e6;
71
+ }
72
+
73
+ .table th:nth-child(2), /* ID column */
74
+ .table td:nth-child(2) {
75
+ min-width: 30px; /* Reduced from 100px */
76
+ max-width: 60px; /* Added max-width */
77
+ overflow: hidden; /* Handle overflow */
78
+ text-overflow: ellipsis; /* Show ellipsis for overflow */
79
+ white-space: nowrap; /* Keep text on one line */
80
+ }
81
+
82
+ .table th:nth-child(4), /* Name column */
83
+ .table td:nth-child(4) {
84
+ min-width: 150px;
85
+ }
86
+
87
+ .table th:nth-child(10), /* Status column */
88
+ .table td:nth-child(10) {
89
+ min-width: 120px;
90
+ }
91
+
92
+ .table th,
93
+ .table td {
94
+ padding: 0.8rem 1rem;
95
+ white-space: nowrap; /* Prevent text wrapping in cells */
96
+ }
97
+
98
+ /* Allow Details column to wrap */
99
+ .table th:nth-child(12), /* Details column */
100
+
101
+ .table td:nth-child(12) {
102
+ max-width: 250px; /* Limit width */
103
+ overflow: hidden; /* Hide overflow */
104
+ text-overflow: ellipsis; /* Show ellipsis for overflow */
105
+ position: relative; /* For tooltip positioning */
106
+ cursor: pointer !important; /* Force show pointer cursor */
107
+ padding-right: 24px; /* Make room for the arrow */
108
+ }
109
+
110
+ .table td:nth-child(12)::after {
111
+ content: '▼';
112
+ position: absolute;
113
+ right: 8px;
114
+ top: 12px;
115
+ font-size: 0.6em;
116
+ opacity: 0.5;
117
+ display: none; /* Hide by default */
118
+ }
119
+
120
+ .table td:nth-child(12).expandable::after {
121
+ display: block; /* Only show for expandable content */
122
+ }
123
+
124
+ .table td:nth-child(12).expanded::after {
125
+ transform: rotate(180deg);
126
+ }
127
+
128
+ .table td:nth-child(12).expanded {
129
+ max-width: none;
130
+ white-space: normal;
131
+ word-wrap: break-word;
132
+ }
133
+
134
+ .badge {
135
+ padding: 0.5em 0.8em;
136
+ font-weight: 500;
137
+ border-radius: 6px;
138
+ }
139
+
140
+ .btn-outline-secondary {
141
+ transition: all 0.2s;
142
+ }
143
+
144
+ .btn-outline-secondary:hover {
145
+ background-color: var(--secondary-color);
146
+ color: white;
147
+ transform: translateY(-1px);
148
+ }
149
+
150
+ .filter-controls {
151
+ background-color: var(--light-color);
152
+ padding: 1rem;
153
+ border-radius: 8px;
154
+ margin: 1rem 0;
155
+ }
156
+
157
+ .form-select {
158
+ border-radius: 6px;
159
+ border: 1px solid #dee2e6;
160
+ padding: 0.5rem 2rem 0.5rem 1rem;
161
+ transition: all 0.2s;
162
+ }
163
+
164
+ .form-select:hover {
165
+ border-color: var(--primary-color);
166
+ }
167
+
168
+ .form-check-input {
169
+ cursor: pointer;
43
170
  }
171
+
44
172
  .clickable {
45
- cursor: pointer; /* This makes the cursor a pointer when hovering over the element */
173
+ transition: color 0.2s;
174
+ }
175
+
176
+ .clickable:hover {
177
+ color: var(--primary-color);
178
+ }
179
+
180
+ /* Status badge animations */
181
+ .badge {
182
+ animation: fadeIn 0.3s ease-in;
183
+ }
184
+
185
+ @keyframes fadeIn {
186
+ from { opacity: 0; transform: translateY(-2px); }
187
+ to { opacity: 1; transform: translateY(0); }
188
+ }
189
+
190
+ /* Loading indicator for auto-refresh */
191
+ .refresh-indicator {
192
+ display: inline-block;
193
+ margin-left: 8px;
194
+ font-size: 12px;
195
+ color: var(--secondary-color);
196
+ }
197
+
198
+ .refresh-spinner {
199
+ display: none;
200
+ width: 12px;
201
+ height: 12px;
202
+ border: 2px solid #f3f3f3;
203
+ border-top: 2px solid var(--primary-color);
204
+ border-radius: 50%;
205
+ animation: spin 1s linear infinite;
206
+ }
207
+
208
+ @keyframes spin {
209
+ 0% { transform: rotate(0deg); }
210
+ 100% { transform: rotate(360deg); }
211
+ }
212
+
213
+ .status-container {
214
+ cursor: help;
215
+ position: relative;
216
+ display: inline-block;
217
+ }
218
+
219
+ .status-container:hover::after {
220
+ content: attr(data-tooltip);
221
+ position: absolute;
222
+ left: 0;
223
+ top: 100%;
224
+ transform: translateY(8px);
225
+ z-index: 1001;
226
+ background-color: rgba(33, 37, 41, 0.9);
227
+ color: white;
228
+ padding: 0.75rem 1rem;
229
+ border-radius: 6px;
230
+ font-size: 0.875rem;
231
+ white-space: pre;
232
+ min-width: 500px;
233
+ max-width: 800px;
234
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
235
+ pointer-events: none;
236
+ opacity: 0;
237
+ animation: tooltipFadeIn 0.2s ease-in-out forwards;
238
+ }
239
+
240
+ /* Update the arrow position */
241
+ .status-container:hover::before {
242
+ content: '';
243
+ position: absolute;
244
+ left: 20px;
245
+ top: 100%; /* Changed from bottom: 100% */
246
+ transform: none;
247
+ border: 8px solid transparent;
248
+ border-bottom-color: rgba(33, 37, 41, 0.9); /* Changed from border-top-color */
249
+ z-index: 1001;
250
+ pointer-events: none;
251
+ opacity: 0;
252
+ animation: tooltipFadeIn 0.2s ease-in-out forwards;
253
+ }
254
+
255
+ @keyframes tooltipFadeIn {
256
+ to {
257
+ opacity: 1;
258
+ transform: translateY(0); /* Simplified animation */
259
+ }
260
+ }
261
+
262
+ /* Ensure the table doesn't cut off tooltips */
263
+ .table {
264
+ overflow: visible !important;
265
+ }
266
+
267
+ .fixed-header-table {
268
+ overflow: visible !important;
269
+ }
270
+
271
+ /* Add horizontal scroll for very small screens */
272
+ @media (max-width: 1200px) {
273
+ .table-responsive {
274
+ overflow-x: auto;
275
+ }
276
+ }
277
+
278
+ /* Update the timestamp hover styles */
279
+ #last-updated {
280
+ position: relative;
281
+ text-decoration: none;
282
+ border-bottom: 1px solid transparent;
283
+ transition: border-bottom-color 0.2s;
284
+ cursor: help;
285
+ }
286
+
287
+ #last-updated:hover {
288
+ border-bottom-color: var(--secondary-color);
289
+ }
290
+
291
+ #last-updated:hover::after {
292
+ content: attr(data-tooltip);
293
+ position: absolute;
294
+ top: 100%; /* Changed from bottom: 100% to top: 100% */
295
+ left: 50%;
296
+ transform: translateX(-50%);
297
+ padding: 0.5rem 1rem;
298
+ background-color: rgba(33, 37, 41, 0.9);
299
+ color: white;
300
+ border-radius: 6px;
301
+ font-size: 0.875rem;
302
+ white-space: nowrap;
303
+ z-index: 1000;
304
+ margin-top: 8px; /* Changed from margin-bottom to margin-top */
305
+ }
306
+
307
+ #last-updated:hover::before {
308
+ content: '';
309
+ position: absolute;
310
+ top: 100%; /* Changed from bottom: 100% to top: 100% */
311
+ left: 50%;
312
+ transform: translateX(-50%);
313
+ border: 8px solid transparent;
314
+ border-bottom-color: rgba(33, 37, 41, 0.9); /* Changed from border-top-color to border-bottom-color */
315
+ margin-top: -8px; /* Changed from margin-bottom to margin-top */
316
+ z-index: 1000;
317
+ }
318
+
319
+ .clickable-badge {
320
+ cursor: pointer;
321
+ transition: transform 0.2s, opacity 0.2s;
322
+ opacity: 0.4;
323
+ }
324
+
325
+ .clickable-badge:hover {
326
+ transform: scale(1.05);
327
+ opacity: 1;
328
+ }
329
+
330
+ .clickable-badge.selected-filter {
331
+ opacity: 1;
332
+ box-shadow: 0 0 0 2px #fff, 0 0 0 4px currentColor;
333
+ }
334
+
335
+ /* Ensure tooltips appear above the sticky header */
336
+ .status-container:hover::after,
337
+ .status-container:hover::before,
338
+ #last-updated:hover::after,
339
+ #last-updated:hover::before {
340
+ z-index: 1001;
341
+ }
342
+
343
+ /* Add tooltip styles for refresh label */
344
+ .refresh-label {
345
+ position: relative;
346
+ cursor: help;
347
+ }
348
+
349
+ .refresh-label:hover::after {
350
+ content: attr(data-tooltip);
351
+ position: absolute;
352
+ left: 50%;
353
+ top: 100%;
354
+ transform: translateX(-50%) translateY(8px);
355
+ z-index: 1001;
356
+ background-color: rgba(33, 37, 41, 0.9);
357
+ color: white;
358
+ padding: 0.5rem 1rem;
359
+ border-radius: 6px;
360
+ font-size: 0.875rem;
361
+ white-space: nowrap;
362
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
363
+ }
364
+
365
+ .refresh-label:hover::before {
366
+ content: '';
367
+ position: absolute;
368
+ left: 50%;
369
+ top: 100%;
370
+ transform: translateX(-50%);
371
+ border: 8px solid transparent;
372
+ border-bottom-color: rgba(33, 37, 41, 0.9);
373
+ z-index: 1001;
46
374
  }
47
375
  </style>
48
376
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
@@ -54,20 +382,84 @@
54
382
  <body>
55
383
  <div class="container">
56
384
  <header>
57
- <h1>Managed jobs</h1>
58
- <p class="text-muted mt-4" id="last-updated"></p>
59
- <div class="form-check form-switch">
60
- <input class="form-check-input" type="checkbox" id="refresh-toggle" checked>
61
- <label class="form-check-label" for="refresh-toggle">Auto-refresh (every 30s)</label>
385
+ <div class="d-flex justify-content-between align-items-center">
386
+ <h1>SkyPilot Managed Jobs</h1>
387
+ <div class="d-flex align-items-center">
388
+ <div class="form-check form-switch me-3">
389
+ <input class="form-check-input" type="checkbox" id="refresh-toggle" checked>
390
+ <label class="form-check-label refresh-label" for="refresh-toggle" data-tooltip="Refreshes every 30 seconds">
391
+ Auto-refresh
392
+ <span class="refresh-indicator">
393
+ <span class="refresh-spinner" id="refresh-spinner"></span>
394
+ </span>
395
+ </label>
396
+ </div>
397
+ <p class="text-muted mb-0" id="last-updated" data-tooltip="{{ utcTimestamp }}"></p>
398
+ </div>
62
399
  </div>
63
400
  </header>
64
401
 
402
+ <!-- Hidden status filter -->
403
+ <select id="status-filter" style="display: none;">
404
+ <option value="">All</option>
405
+ {% for status in status_values %}
406
+ <option value="{{ status }}">{{ status }}</option>
407
+ {% endfor %}
408
+ </select>
409
+
410
+ {% if rows %}
411
+ <p>Filter by status:
412
+ <span class="badge bg-secondary clickable-badge selected-filter me-2" data-status="ALL">All</span>
413
+ {% set status_dict = {} %}
414
+ {% for row in rows %}
415
+ {% set status = row[9].split()[0] %}
416
+ {% if status not in status_dict %}
417
+ {% set _ = status_dict.update({status: 1}) %}
418
+ {% else %}
419
+ {% set _ = status_dict.update({status: status_dict[status] + 1}) %}
420
+ {% endif %}
421
+ {% endfor %}
422
+ {% for status, count in status_dict|dictsort %}
423
+ <span class="me-2">
424
+ <span class="me-1">| {{ count }}</span>
425
+ {% if status.startswith('RUNNING') %}
426
+ <span class="badge bg-primary clickable-badge" data-status="{{ status }}">{{ status }}</span>
427
+ {% elif status.startswith('PENDING') or status.startswith('SUBMITTED') %}
428
+ <span class="badge bg-light clickable-badge" data-status="{{ status }}">{{ status }}</span>
429
+ {% elif status.startswith('RECOVERING') or status.startswith('CANCELLING') or status.startswith('STARTING') %}
430
+ <span class="badge bg-info clickable-badge" data-status="{{ status }}">{{ status }}</span>
431
+ {% elif status.startswith('SUCCEEDED') %}
432
+ <span class="badge bg-success clickable-badge" data-status="{{ status }}">{{ status }}</span>
433
+ {% elif status.startswith('CANCELLED') %}
434
+ <span class="badge bg-secondary clickable-badge" data-status="{{ status }}">{{ status }}</span>
435
+ {% elif status.startswith('FAILED') %}
436
+ <span class="badge bg-danger clickable-badge" data-status="{{ status }}">{{ status }}</span>
437
+ {% else %}
438
+ <span class="clickable-badge" data-status="{{ status }}">{{ status }}</span>
439
+ {% endif %}
440
+ </span>
441
+ {% endfor %}
442
+ </p>
443
+ {% else %}
444
+ <p>No jobs found.</p>
445
+ {% endif %}
446
+
65
447
  <table class="table table-hover table-hover-selected fixed-header-table" id="jobs-table">
66
448
  <thead>
67
449
  <tr>
68
- {% for column in columns %}
69
- <th>{{ column }}</th>
70
- {% endfor %}
450
+ <td></td>
451
+ <th>ID</th>
452
+ <th>Task</th>
453
+ <th>Name</th>
454
+ <th>Total Duration</th>
455
+ <th>Job Duration</th>
456
+ <th>Status</th>
457
+ <th>Resources</th>
458
+ <th>Cluster</th>
459
+ <th>Region</th>
460
+ <th>Recoveries</th>
461
+ <th>Details</th>
462
+ <th>Actions</th>
71
463
  </tr>
72
464
  </thead>
73
465
  <tbody>
@@ -77,33 +469,41 @@
77
469
  <td>{{ row[1]|string|replace(' \u21B3', '') }}</td>
78
470
  <td>{{ row[2] }}</td>
79
471
  <td>{{ row[3] }}</td>
80
- <td>{{ row[4] }}</td>
81
- <td>{{ row[5] }}</td>
82
472
  <td>{{ row[6] }}</td>
83
473
  <td>{{ row[7] }}</td>
84
- <td>{{ row[8] }}</td>
85
474
  <td>
86
- <!-- https://getbootstrap.com/docs/4.0/components/badge/ -->
87
- {% if row[9].startswith('RUNNING') %}
88
- <span class="badge bg-primary">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
89
- {% elif row[9].startswith('PENDING') or row[9].startswith('SUBMITTED') %}
90
- <span class="badge bg-light">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
91
- {% elif row[9].startswith('RECOVERING') or row[9].startswith('CANCELLING') or row[9].startswith('STARTING') %}
92
- <span class="badge bg-info">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
93
- {% elif row[9].startswith('SUCCEEDED') %}
94
- <span class="badge bg-success">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
95
- {% elif row[9].startswith('CANCELLED') %}
96
- <span class="badge bg-secondary">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
97
- {% elif row[9].startswith('FAILED') %}
98
- <span class="badge bg-warning">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
99
- {% else %}
100
- {{ row[9] }}
475
+ <!-- Status column with tooltip -->
476
+ <div class="status-container" style="position: relative;" data-tooltip="{{ row[14] }}">
477
+ {% if row[9].startswith('RUNNING') %}
478
+ <span class="badge bg-primary">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
479
+ {% elif row[9].startswith('PENDING') or row[9].startswith('SUBMITTED') %}
480
+ <span class="badge bg-warning">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
481
+ {% elif row[9].startswith('RECOVERING') or row[9].startswith('CANCELLING') or row[9].startswith('STARTING') %}
482
+ <span class="badge bg-info">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
483
+ {% elif row[9].startswith('SUCCEEDED') %}
484
+ <span class="badge bg-success">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
485
+ {% elif row[9].startswith('CANCELLED') %}
486
+ <span class="badge bg-secondary">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
487
+ {% elif row[9].startswith('FAILED') %}
488
+ <span class="badge bg-danger">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
489
+ {% else %}
490
+ {{ row[9] }}
491
+ {% endif %}
492
+ </div>
493
+ </td>
494
+ <td>{{ row[4] }}</td> {# Resources #}
495
+ <td>{{ row[11] }}</td> {# Cluster #}
496
+ <td>{{ row[12] }}</td> {# Region #}
497
+ <td>{{ row[8] }}</td> {# Recoveries #}
498
+ <td data-full-text="{{ row[13] }}">{{ row[13] }}</td> {# Details #}
499
+ <td>
500
+ {% if row[1]|string|replace(' \u21B3', '') and row[1]|string|replace(' \u21B3', '') != '-' %}
501
+ <a href="{{ url_for('download_log', job_id=row[1]|string|replace(' \u21B3', '')) }}"
502
+ class="btn btn-sm btn-outline-secondary">
503
+ controller log
504
+ </a>
101
505
  {% endif %}
102
506
  </td>
103
- <td>{{ row[10] }}</td>
104
- <td>{{ row[11] }}</td>
105
- <td>{{ row[12] }}</td>
106
- <td>{{ row[13] }}</td>
107
507
  </tr>
108
508
  {% endfor %}
109
509
  </tbody>
@@ -172,7 +572,9 @@
172
572
  document.addEventListener("DOMContentLoaded", function () {
173
573
  var timestamp = "{{ last_updated_timestamp }}"; // Get the UTC timestamp from the template
174
574
  var localTimestamp = moment.utc(timestamp).tz(moment.tz.guess()).format('YYYY-MM-DD HH:mm:ss z');
575
+ var utcTimestamp = moment.utc(timestamp).format('YYYY-MM-DD HH:mm:ss UTC');
175
576
  document.getElementById("last-updated").textContent = "Last updated: " + localTimestamp;
577
+ document.getElementById("last-updated").setAttribute('data-tooltip', utcTimestamp);
176
578
  });
177
579
  </script>
178
580
  <script>
@@ -185,6 +587,7 @@
185
587
  } else {
186
588
  refreshToggle.checked = false;
187
589
  }
590
+
188
591
  // Add event listener to the toggle switch
189
592
  function handleAutoRefresh() {
190
593
  localStorage.setItem("refreshState", refreshToggle.checked);
@@ -192,6 +595,9 @@
192
595
  if (refreshToggle.checked) {
193
596
  // Auto-refresh is enabled
194
597
  refreshInterval = setInterval(function () {
598
+ // Store current filter before reload
599
+ var currentFilter = document.getElementById("status-filter").value;
600
+ localStorage.setItem("statusFilter", currentFilter);
195
601
  location.reload();
196
602
  }, 30000); // 30 seconds in milliseconds
197
603
  } else {
@@ -199,24 +605,130 @@
199
605
  clearInterval(refreshInterval);
200
606
  }
201
607
  }
608
+
609
+ // Restore filter state after page load
610
+ document.addEventListener("DOMContentLoaded", function() {
611
+ var savedFilter = localStorage.getItem("statusFilter");
612
+ if (savedFilter) {
613
+ var statusFilter = document.getElementById("status-filter");
614
+ statusFilter.value = savedFilter;
615
+ filterStatus(savedFilter);
616
+ }
617
+ });
618
+
202
619
  refreshToggle.addEventListener("change", handleAutoRefresh);
203
620
  handleAutoRefresh();
204
621
  </script>
205
622
  <script>
206
623
  function filterStatus(status) {
207
- var rows = document.querySelectorAll("#spot-jobs-table tbody tr");
208
- rows.forEach(function (row) {
209
- var statusCell = row.querySelector("td:nth-child(9)");
624
+ var rows = document.querySelectorAll("#jobs-table tbody tr");
625
+ rows.forEach(function(row) {
626
+ var statusCell = row.querySelector("td:nth-child(7)"); // Status is now in the 7th column
627
+ if (statusCell) {
628
+ var statusText = statusCell.textContent.trim().split(' ')[0]; // Get first word of status
210
629
 
211
- if (status === '' || statusCell.textContent === status) {
212
- row.style.display = "";
213
- } else {
214
- row.style.display = "none";
630
+ if (status === '' || statusText === status) {
631
+ row.style.display = "";
632
+ } else {
633
+ row.style.display = "none";
634
+ }
215
635
  }
216
636
  });
217
637
  }
638
+
639
+ // Add event listener for the status filter
640
+ document.addEventListener("DOMContentLoaded", function() {
641
+ document.getElementById("status-filter").addEventListener("change", function() {
642
+ filterStatus(this.value);
643
+ });
644
+ });
645
+ </script>
646
+ <script>
647
+ // Show loading spinner during refresh
648
+ window.addEventListener('beforeunload', function() {
649
+ document.getElementById('refresh-spinner').style.display = 'inline-block';
650
+ });
651
+ </script>
652
+ <script>
653
+ // Update column indices for job table
654
+ const JOB_TABLE_COLUMNS = [
655
+ '', 'ID', 'Task', 'Name', 'Total Duration',
656
+ 'Job Duration', 'Status', 'Resources', 'Cluster', 'Region', 'Recoveries', 'Details',
657
+ 'Actions'
658
+ ];
659
+ </script>
660
+ <script>
661
+ // Replace the existing click handler for status badges with this updated version
662
+ document.addEventListener("DOMContentLoaded", function() {
663
+ const statusFilter = document.getElementById('status-filter');
664
+ const badges = document.querySelectorAll('.clickable-badge');
665
+
666
+ // Set initial state
667
+ const savedFilter = localStorage.getItem("statusFilter") || '';
668
+ updateSelectedBadge(savedFilter);
669
+
670
+ badges.forEach(function(badge) {
671
+ badge.addEventListener('click', function() {
672
+ const status = this.dataset.status;
673
+ const currentFilter = statusFilter.value;
674
+
675
+ // If clicking the already selected filter, clear it (show all)
676
+ const newStatus = (status === currentFilter || (status === 'ALL' && currentFilter === '')) ? '' :
677
+ (status === 'ALL' ? '' : status);
678
+
679
+ // Update filter and UI
680
+ statusFilter.value = newStatus;
681
+ filterStatus(newStatus);
682
+ localStorage.setItem("statusFilter", newStatus);
683
+ updateSelectedBadge(newStatus);
684
+ });
685
+ });
686
+
687
+ function updateSelectedBadge(selectedStatus) {
688
+ badges.forEach(badge => {
689
+ badge.classList.remove('selected-filter');
690
+ if ((selectedStatus === '' && badge.dataset.status === 'ALL') ||
691
+ badge.dataset.status === selectedStatus) {
692
+ badge.classList.add('selected-filter');
693
+ }
694
+ });
695
+ }
696
+ });
218
697
  </script>
698
+ <script>
699
+ // Add scroll event listener to handle sticky header shadow
700
+ document.addEventListener("DOMContentLoaded", function() {
701
+ const header = document.querySelector('header');
702
+ const container = document.querySelector('.container');
219
703
 
704
+ window.addEventListener('scroll', function() {
705
+ if (container.getBoundingClientRect().top < 0) {
706
+ header.classList.add('sticky');
707
+ } else {
708
+ header.classList.remove('sticky');
709
+ }
710
+ });
711
+ });
712
+ </script>
713
+ <script>
714
+ // Add click handler for Details cells
715
+ document.addEventListener('DOMContentLoaded', function() {
716
+ const detailsCells = document.querySelectorAll('#jobs-table td:nth-child(12)');
717
+
718
+ detailsCells.forEach(cell => {
719
+ // Check if content is truncated
720
+ if (cell.scrollWidth > cell.clientWidth) {
721
+ cell.classList.add('expandable');
722
+ }
723
+
724
+ cell.addEventListener('click', function() {
725
+ if (this.scrollWidth > this.clientWidth || this.classList.contains('expanded')) {
726
+ this.classList.toggle('expanded');
727
+ }
728
+ });
729
+ });
730
+ });
731
+ </script>
220
732
  </body>
221
733
 
222
734
  </html>