skypilot-nightly 1.0.0.dev20250202__py3-none-any.whl → 1.0.0.dev20250204__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/check.py +2 -2
- sky/jobs/dashboard/dashboard.py +156 -20
- sky/jobs/dashboard/templates/index.html +493 -79
- sky/provision/lambda_cloud/instance.py +17 -1
- sky/serve/serve_state.py +23 -21
- {skypilot_nightly-1.0.0.dev20250202.dist-info → skypilot_nightly-1.0.0.dev20250204.dist-info}/METADATA +1 -1
- {skypilot_nightly-1.0.0.dev20250202.dist-info → skypilot_nightly-1.0.0.dev20250204.dist-info}/RECORD +12 -12
- {skypilot_nightly-1.0.0.dev20250202.dist-info → skypilot_nightly-1.0.0.dev20250204.dist-info}/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20250202.dist-info → skypilot_nightly-1.0.0.dev20250204.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20250202.dist-info → skypilot_nightly-1.0.0.dev20250204.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250202.dist-info → skypilot_nightly-1.0.0.dev20250204.dist-info}/top_level.txt +0 -0
sky/__init__.py
CHANGED
@@ -5,7 +5,7 @@ from typing import Optional
|
|
5
5
|
import urllib.request
|
6
6
|
|
7
7
|
# Replaced with the current commit when building the wheels.
|
8
|
-
_SKYPILOT_COMMIT_SHA = '
|
8
|
+
_SKYPILOT_COMMIT_SHA = 'e4ad98c907a819d9a6b7f6872445ca497401b8c9'
|
9
9
|
|
10
10
|
|
11
11
|
def _get_git_commit():
|
@@ -35,7 +35,7 @@ def _get_git_commit():
|
|
35
35
|
|
36
36
|
|
37
37
|
__commit__ = _get_git_commit()
|
38
|
-
__version__ = '1.0.0.
|
38
|
+
__version__ = '1.0.0.dev20250204'
|
39
39
|
__root_dir__ = os.path.dirname(os.path.abspath(__file__))
|
40
40
|
|
41
41
|
|
sky/check.py
CHANGED
@@ -245,10 +245,10 @@ def _format_enabled_cloud(cloud_name: str) -> str:
|
|
245
245
|
# here we are using rich. We should migrate this file to
|
246
246
|
# use colorama as we do in the rest of the codebase.
|
247
247
|
symbol = ('└── ' if i == len(existing_contexts) - 1 else '├── ')
|
248
|
-
contexts_formatted.append(f'\n
|
248
|
+
contexts_formatted.append(f'\n {symbol}{context}')
|
249
249
|
context_info = f'Allowed contexts:{"".join(contexts_formatted)}'
|
250
250
|
else:
|
251
251
|
context_info = f'Active context: {existing_contexts[0]}'
|
252
252
|
|
253
|
-
return f'{cloud_name}[/green][dim]\n
|
253
|
+
return f'{cloud_name}[/green][dim]\n {context_info}[/dim][green]'
|
254
254
|
return cloud_name
|
sky/jobs/dashboard/dashboard.py
CHANGED
@@ -6,13 +6,17 @@ https://github.com/ray-project/ray/tree/master/dashboard/client/src) and/or get
|
|
6
6
|
rid of the SSH port-forwarding business (see cli.py's job_dashboard()
|
7
7
|
comment).
|
8
8
|
"""
|
9
|
+
import collections
|
9
10
|
import datetime
|
11
|
+
import enum
|
12
|
+
import os
|
10
13
|
import pathlib
|
11
14
|
|
12
15
|
import flask
|
13
16
|
import yaml
|
14
17
|
|
15
18
|
from sky import jobs as managed_jobs
|
19
|
+
from sky.jobs import constants as managed_job_constants
|
16
20
|
from sky.utils import common_utils
|
17
21
|
from sky.utils import controller_utils
|
18
22
|
|
@@ -41,6 +45,92 @@ def _is_running_on_jobs_controller() -> bool:
|
|
41
45
|
return False
|
42
46
|
|
43
47
|
|
48
|
+
# Column indices for job table
|
49
|
+
class JobTableColumns(enum.IntEnum):
|
50
|
+
"""Column indices for the jobs table in the dashboard.
|
51
|
+
|
52
|
+
- DROPDOWN (0): Column for expandable dropdown arrow
|
53
|
+
- ID (1): Job ID column
|
54
|
+
- TASK (2): Task name/number column
|
55
|
+
- NAME (3): Job name column
|
56
|
+
- RESOURCES (4): Resources used by job
|
57
|
+
- SUBMITTED (5): Job submission timestamp
|
58
|
+
- TOTAL_DURATION (6): Total time since job submission
|
59
|
+
- JOB_DURATION (7): Actual job runtime
|
60
|
+
- RECOVERIES (8): Number of job recoveries
|
61
|
+
- STATUS (9): Current job status
|
62
|
+
- STARTED (10): Job start timestamp
|
63
|
+
- CLUSTER (11): Cluster name
|
64
|
+
- REGION (12): Cloud region
|
65
|
+
- FAILOVER (13): Job failover history
|
66
|
+
- DETAILS (14): Job details
|
67
|
+
- ACTIONS (15): Available actions column
|
68
|
+
"""
|
69
|
+
DROPDOWN = 0
|
70
|
+
ID = 1
|
71
|
+
TASK = 2
|
72
|
+
NAME = 3
|
73
|
+
RESOURCES = 4
|
74
|
+
SUBMITTED = 5
|
75
|
+
TOTAL_DURATION = 6
|
76
|
+
JOB_DURATION = 7
|
77
|
+
RECOVERIES = 8
|
78
|
+
STATUS = 9
|
79
|
+
STARTED = 10
|
80
|
+
CLUSTER = 11
|
81
|
+
REGION = 12
|
82
|
+
DETAILS = 13
|
83
|
+
FAILOVER = 14
|
84
|
+
ACTIONS = 15
|
85
|
+
|
86
|
+
|
87
|
+
# Column headers matching the indices above
|
88
|
+
JOB_TABLE_COLUMNS = [
|
89
|
+
'', 'ID', 'Task', 'Name', 'Resources', 'Submitted', 'Total Duration',
|
90
|
+
'Job Duration', 'Status', 'Started', 'Cluster', 'Region', 'Failover',
|
91
|
+
'Recoveries', 'Details', 'Actions'
|
92
|
+
]
|
93
|
+
|
94
|
+
|
95
|
+
def _extract_launch_history(log_content: str) -> str:
|
96
|
+
"""Extract launch history from log content.
|
97
|
+
|
98
|
+
Args:
|
99
|
+
log_content: Content of the log file.
|
100
|
+
Returns:
|
101
|
+
A formatted string containing the launch history.
|
102
|
+
"""
|
103
|
+
launches = []
|
104
|
+
current_launch = None
|
105
|
+
|
106
|
+
for line in log_content.splitlines():
|
107
|
+
if 'Launching on' in line:
|
108
|
+
try:
|
109
|
+
parts = line.split(']')
|
110
|
+
if len(parts) >= 2:
|
111
|
+
timestamp = parts[0].split()[1:3]
|
112
|
+
message = parts[1].replace('[0m⚙︎', '').strip()
|
113
|
+
formatted_line = f'{" ".join(timestamp)} {message}'
|
114
|
+
if current_launch:
|
115
|
+
prev_time, prev_target = current_launch.rsplit(
|
116
|
+
' Launching on ', 1)
|
117
|
+
launches.append(
|
118
|
+
f'{prev_time} Tried to launch on {prev_target}')
|
119
|
+
|
120
|
+
# Store the current launch
|
121
|
+
current_launch = formatted_line
|
122
|
+
except IndexError:
|
123
|
+
launches.append(line.strip())
|
124
|
+
|
125
|
+
# Add the final (successful) launch at the beginning
|
126
|
+
if current_launch:
|
127
|
+
result = [current_launch]
|
128
|
+
result.extend(launches)
|
129
|
+
return '\n'.join(result)
|
130
|
+
|
131
|
+
return 'No launch history found'
|
132
|
+
|
133
|
+
|
44
134
|
@app.route('/')
|
45
135
|
def home():
|
46
136
|
if not _is_running_on_jobs_controller():
|
@@ -54,38 +144,84 @@ def home():
|
|
54
144
|
rows = managed_jobs.format_job_table(all_managed_jobs,
|
55
145
|
show_all=True,
|
56
146
|
return_rows=True)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
147
|
+
|
148
|
+
status_counts = collections.defaultdict(int)
|
149
|
+
for task in all_managed_jobs:
|
150
|
+
if not task['status'].is_terminal():
|
151
|
+
status_counts[task['status'].value] += 1
|
152
|
+
|
153
|
+
# Add an empty column for the dropdown button and actions column
|
154
|
+
rows = [[''] + row + [''] + [''] for row in rows
|
155
|
+
] # Add empty cell for failover and actions column
|
156
|
+
|
157
|
+
# Add log content as failover history for each job
|
158
|
+
for row in rows:
|
159
|
+
job_id = str(row[JobTableColumns.ID]).strip().replace(' ⤳', '')
|
160
|
+
if job_id and job_id != '-':
|
161
|
+
try:
|
162
|
+
log_path = os.path.join(
|
163
|
+
os.path.expanduser(
|
164
|
+
managed_job_constants.JOBS_CONTROLLER_LOGS_DIR),
|
165
|
+
f'{job_id}.log')
|
166
|
+
if os.path.exists(log_path):
|
167
|
+
with open(log_path, 'r', encoding='utf-8') as f:
|
168
|
+
log_content = f.read()
|
169
|
+
row[JobTableColumns.FAILOVER] = _extract_launch_history(
|
170
|
+
log_content)
|
171
|
+
else:
|
172
|
+
row[JobTableColumns.FAILOVER] = 'Log file not found'
|
173
|
+
except (IOError, OSError) as e:
|
174
|
+
row[JobTableColumns.FAILOVER] = f'Error reading log: {str(e)}'
|
175
|
+
app.logger.error('All managed jobs:')
|
176
|
+
|
177
|
+
# Validate column count
|
178
|
+
if rows and len(rows[0]) != len(JOB_TABLE_COLUMNS):
|
69
179
|
raise RuntimeError(
|
70
|
-
'Dashboard code and managed job queue code are out of sync.'
|
180
|
+
f'Dashboard code and managed job queue code are out of sync. '
|
181
|
+
f'Expected {(JOB_TABLE_COLUMNS)} columns, got {(rows[0])}')
|
71
182
|
|
72
|
-
# Fix STATUS color codes: '\x1b[33mCANCELLED\x1b[0m' -> 'CANCELLED'
|
183
|
+
# Fix STATUS color codes: '\x1b[33mCANCELLED\x1b[0m' -> 'CANCELLED'
|
73
184
|
for row in rows:
|
74
|
-
row[
|
75
|
-
|
76
|
-
|
185
|
+
row[JobTableColumns.STATUS] = common_utils.remove_color(
|
186
|
+
row[JobTableColumns.STATUS])
|
187
|
+
|
188
|
+
# Remove filler rows ([''], ..., ['-'])
|
189
|
+
rows = [
|
190
|
+
row for row in rows
|
191
|
+
if ''.join(map(str, row[:JobTableColumns.ACTIONS])) != ''
|
192
|
+
]
|
193
|
+
|
194
|
+
# Get all unique status values
|
195
|
+
status_values = sorted(
|
196
|
+
list(set(row[JobTableColumns.STATUS] for row in rows)))
|
77
197
|
|
78
|
-
# Get all unique status values.
|
79
|
-
status_values = sorted(list(set(row[-5] for row in rows)))
|
80
198
|
rendered_html = flask.render_template(
|
81
199
|
'index.html',
|
82
|
-
columns=
|
200
|
+
columns=JOB_TABLE_COLUMNS,
|
83
201
|
rows=rows,
|
84
202
|
last_updated_timestamp=timestamp,
|
85
203
|
status_values=status_values,
|
204
|
+
status_counts=status_counts,
|
86
205
|
)
|
87
206
|
return rendered_html
|
88
207
|
|
89
208
|
|
209
|
+
@app.route('/download_log/<job_id>')
|
210
|
+
def download_log(job_id):
|
211
|
+
try:
|
212
|
+
log_path = os.path.join(
|
213
|
+
os.path.expanduser(managed_job_constants.JOBS_CONTROLLER_LOGS_DIR),
|
214
|
+
f'{job_id}.log')
|
215
|
+
if not os.path.exists(log_path):
|
216
|
+
flask.abort(404)
|
217
|
+
return flask.send_file(log_path,
|
218
|
+
mimetype='text/plain',
|
219
|
+
as_attachment=True,
|
220
|
+
download_name=f'job_{job_id}.log')
|
221
|
+
except (IOError, OSError) as e:
|
222
|
+
app.logger.error(f'Error downloading log for job {job_id}: {str(e)}')
|
223
|
+
flask.abort(500)
|
224
|
+
|
225
|
+
|
90
226
|
if __name__ == '__main__':
|
91
227
|
app.run()
|
@@ -5,61 +5,314 @@
|
|
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
|
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
|
-
|
13
|
-
|
12
|
+
:root {
|
13
|
+
--primary-color: #0d6efd;
|
14
|
+
--secondary-color: #6c757d;
|
15
|
+
--success-color: #198754;
|
16
|
+
--warning-color: #ffc107;
|
17
|
+
--info-color: #0dcaf0;
|
18
|
+
--light-color: #f8f9fa;
|
14
19
|
}
|
15
20
|
|
16
|
-
|
17
|
-
|
21
|
+
body {
|
22
|
+
margin-top: 0;
|
23
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
24
|
+
background-color: white;
|
18
25
|
}
|
19
26
|
|
20
|
-
.
|
21
|
-
|
22
|
-
|
23
|
-
|
27
|
+
.container {
|
28
|
+
max-width: 100%;
|
29
|
+
width: 100%;
|
30
|
+
background-color: white;
|
31
|
+
border-radius: 0;
|
32
|
+
box-shadow: none;
|
33
|
+
padding: 2rem;
|
34
|
+
margin-bottom: 0;
|
24
35
|
}
|
25
36
|
|
26
|
-
|
27
|
-
|
37
|
+
header {
|
38
|
+
position: sticky;
|
39
|
+
top: 0;
|
40
|
+
background: white;
|
41
|
+
z-index: 1000;
|
42
|
+
padding: 1.5rem 2rem;
|
43
|
+
margin: -2rem -2rem 1.5rem -2rem; /* Negative margins to match container padding */
|
44
|
+
border-bottom: 1px solid #dee2e6;
|
28
45
|
}
|
29
46
|
|
30
|
-
|
31
|
-
|
32
|
-
|
47
|
+
/* Add shadow when header is sticky */
|
48
|
+
header.sticky {
|
49
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
33
50
|
}
|
34
51
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
/* Replace with your desired background color */
|
40
|
-
color: #000000;
|
41
|
-
/* Replace with your desired text color */
|
42
|
-
z-index: 1;
|
52
|
+
h1 {
|
53
|
+
color: var(--primary-color);
|
54
|
+
font-weight: 600;
|
55
|
+
font-size: 2rem;
|
43
56
|
}
|
44
|
-
|
45
|
-
|
57
|
+
|
58
|
+
.table {
|
59
|
+
width: 100%; /* Ensure table takes full container width */
|
60
|
+
border-radius: 8px;
|
61
|
+
overflow: hidden;
|
62
|
+
box-shadow: 0 0 8px rgba(0, 0, 0, 0.05);
|
63
|
+
table-layout: auto; /* Allow table to adjust column widths automatically */
|
64
|
+
}
|
65
|
+
|
66
|
+
.fixed-header-table thead th,
|
67
|
+
.fixed-header-table thead td { /* Added td selector */
|
68
|
+
background-color: var(--light-color);
|
69
|
+
padding: 1rem;
|
70
|
+
font-weight: 600;
|
71
|
+
border-bottom: 2px solid #dee2e6;
|
72
|
+
}
|
73
|
+
|
74
|
+
.table th:nth-child(2), /* ID column */
|
75
|
+
.table td:nth-child(2) {
|
76
|
+
min-width: 30px; /* Reduced from 100px */
|
77
|
+
max-width: 60px; /* Added max-width */
|
78
|
+
overflow: hidden; /* Handle overflow */
|
79
|
+
text-overflow: ellipsis; /* Show ellipsis for overflow */
|
80
|
+
white-space: nowrap; /* Keep text on one line */
|
81
|
+
}
|
82
|
+
|
83
|
+
.table th:nth-child(4), /* Name column */
|
84
|
+
.table td:nth-child(4) {
|
85
|
+
min-width: 150px;
|
86
|
+
}
|
87
|
+
|
88
|
+
.table th:nth-child(10), /* Status column */
|
89
|
+
.table td:nth-child(10) {
|
90
|
+
min-width: 120px;
|
91
|
+
}
|
92
|
+
|
93
|
+
.table th,
|
94
|
+
.table td {
|
95
|
+
padding: 0.8rem 1rem;
|
96
|
+
white-space: nowrap; /* Prevent text wrapping in cells */
|
97
|
+
}
|
98
|
+
|
99
|
+
/* Allow Details column to wrap */
|
100
|
+
.table th:nth-child(12), /* Details column */
|
101
|
+
.table td:nth-child(12) {
|
102
|
+
white-space: normal; /* Allow text wrapping */
|
103
|
+
max-width: 250px; /* Limit width to prevent excessive stretching */
|
104
|
+
word-wrap: break-word; /* Break long words if needed */
|
105
|
+
}
|
106
|
+
|
107
|
+
.badge {
|
108
|
+
padding: 0.5em 0.8em;
|
109
|
+
font-weight: 500;
|
110
|
+
border-radius: 6px;
|
111
|
+
}
|
112
|
+
|
113
|
+
.btn-outline-secondary {
|
114
|
+
transition: all 0.2s;
|
115
|
+
}
|
116
|
+
|
117
|
+
.btn-outline-secondary:hover {
|
118
|
+
background-color: var(--secondary-color);
|
119
|
+
color: white;
|
120
|
+
transform: translateY(-1px);
|
46
121
|
}
|
47
122
|
|
48
123
|
.filter-controls {
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
margin
|
124
|
+
background-color: var(--light-color);
|
125
|
+
padding: 1rem;
|
126
|
+
border-radius: 8px;
|
127
|
+
margin: 1rem 0;
|
128
|
+
}
|
129
|
+
|
130
|
+
.form-select {
|
131
|
+
border-radius: 6px;
|
132
|
+
border: 1px solid #dee2e6;
|
133
|
+
padding: 0.5rem 2rem 0.5rem 1rem;
|
134
|
+
transition: all 0.2s;
|
135
|
+
}
|
136
|
+
|
137
|
+
.form-select:hover {
|
138
|
+
border-color: var(--primary-color);
|
139
|
+
}
|
140
|
+
|
141
|
+
.form-check-input {
|
142
|
+
cursor: pointer;
|
143
|
+
}
|
144
|
+
|
145
|
+
.clickable {
|
146
|
+
transition: color 0.2s;
|
147
|
+
}
|
148
|
+
|
149
|
+
.clickable:hover {
|
150
|
+
color: var(--primary-color);
|
151
|
+
}
|
152
|
+
|
153
|
+
/* Status badge animations */
|
154
|
+
.badge {
|
155
|
+
animation: fadeIn 0.3s ease-in;
|
156
|
+
}
|
157
|
+
|
158
|
+
@keyframes fadeIn {
|
159
|
+
from { opacity: 0; transform: translateY(-2px); }
|
160
|
+
to { opacity: 1; transform: translateY(0); }
|
161
|
+
}
|
162
|
+
|
163
|
+
/* Loading indicator for auto-refresh */
|
164
|
+
.refresh-indicator {
|
165
|
+
display: inline-block;
|
166
|
+
margin-left: 8px;
|
167
|
+
font-size: 12px;
|
168
|
+
color: var(--secondary-color);
|
169
|
+
}
|
170
|
+
|
171
|
+
.refresh-spinner {
|
172
|
+
display: none;
|
173
|
+
width: 12px;
|
174
|
+
height: 12px;
|
175
|
+
border: 2px solid #f3f3f3;
|
176
|
+
border-top: 2px solid var(--primary-color);
|
177
|
+
border-radius: 50%;
|
178
|
+
animation: spin 1s linear infinite;
|
179
|
+
}
|
180
|
+
|
181
|
+
@keyframes spin {
|
182
|
+
0% { transform: rotate(0deg); }
|
183
|
+
100% { transform: rotate(360deg); }
|
184
|
+
}
|
185
|
+
|
186
|
+
.status-container {
|
187
|
+
cursor: help;
|
53
188
|
position: relative;
|
54
|
-
|
189
|
+
display: inline-block;
|
190
|
+
}
|
191
|
+
|
192
|
+
.status-container:hover::after {
|
193
|
+
content: attr(title);
|
194
|
+
position: absolute;
|
195
|
+
left: 0; /* Changed from 50% */
|
196
|
+
bottom: 100%;
|
197
|
+
transform: translateY(-8px); /* Removed translateX */
|
198
|
+
z-index: 1001;
|
199
|
+
background-color: rgba(33, 37, 41, 0.9);
|
200
|
+
color: white;
|
201
|
+
padding: 0.75rem 1rem;
|
202
|
+
border-radius: 6px;
|
203
|
+
font-size: 0.875rem;
|
204
|
+
white-space: pre;
|
205
|
+
min-width: 500px;
|
206
|
+
max-width: 800px;
|
207
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
208
|
+
pointer-events: none;
|
209
|
+
opacity: 0;
|
210
|
+
animation: tooltipFadeIn 0.2s ease-in-out forwards;
|
211
|
+
}
|
212
|
+
|
213
|
+
/* Update the arrow position */
|
214
|
+
.status-container:hover::before {
|
215
|
+
content: '';
|
216
|
+
position: absolute;
|
217
|
+
left: 20px; /* Changed from 50% */
|
218
|
+
bottom: 100%;
|
219
|
+
transform: none; /* Removed transform */
|
220
|
+
border: 8px solid transparent;
|
221
|
+
border-top-color: rgba(33, 37, 41, 0.9);
|
222
|
+
z-index: 1001;
|
223
|
+
pointer-events: none;
|
224
|
+
opacity: 0;
|
225
|
+
animation: tooltipFadeIn 0.2s ease-in-out forwards;
|
55
226
|
}
|
56
227
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
228
|
+
@keyframes tooltipFadeIn {
|
229
|
+
to {
|
230
|
+
opacity: 1;
|
231
|
+
transform: translateY(0); /* Simplified animation */
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
/* Ensure the table doesn't cut off tooltips */
|
236
|
+
.table {
|
237
|
+
overflow: visible !important;
|
238
|
+
}
|
239
|
+
|
240
|
+
.fixed-header-table {
|
241
|
+
overflow: visible !important;
|
61
242
|
}
|
62
243
|
|
244
|
+
/* Add horizontal scroll for very small screens */
|
245
|
+
@media (max-width: 1200px) {
|
246
|
+
.table-responsive {
|
247
|
+
overflow-x: auto;
|
248
|
+
}
|
249
|
+
}
|
250
|
+
|
251
|
+
/* Update the timestamp hover styles */
|
252
|
+
#last-updated {
|
253
|
+
position: relative;
|
254
|
+
text-decoration: none;
|
255
|
+
border-bottom: 1px solid transparent;
|
256
|
+
transition: border-bottom-color 0.2s;
|
257
|
+
cursor: help;
|
258
|
+
}
|
259
|
+
|
260
|
+
#last-updated:hover {
|
261
|
+
border-bottom-color: var(--secondary-color);
|
262
|
+
}
|
263
|
+
|
264
|
+
#last-updated:hover::after {
|
265
|
+
content: attr(title);
|
266
|
+
position: absolute;
|
267
|
+
bottom: 100%;
|
268
|
+
left: 50%;
|
269
|
+
transform: translateX(-50%);
|
270
|
+
padding: 0.5rem 1rem;
|
271
|
+
background-color: rgba(33, 37, 41, 0.9);
|
272
|
+
color: white;
|
273
|
+
border-radius: 6px;
|
274
|
+
font-size: 0.875rem;
|
275
|
+
white-space: nowrap;
|
276
|
+
z-index: 1000;
|
277
|
+
margin-bottom: 8px;
|
278
|
+
}
|
279
|
+
|
280
|
+
#last-updated:hover::before {
|
281
|
+
content: '';
|
282
|
+
position: absolute;
|
283
|
+
bottom: 100%;
|
284
|
+
left: 50%;
|
285
|
+
transform: translateX(-50%);
|
286
|
+
border: 8px solid transparent;
|
287
|
+
border-top-color: rgba(33, 37, 41, 0.9);
|
288
|
+
margin-bottom: -8px;
|
289
|
+
z-index: 1000;
|
290
|
+
}
|
291
|
+
|
292
|
+
.clickable-badge {
|
293
|
+
cursor: pointer;
|
294
|
+
transition: transform 0.2s, opacity 0.2s;
|
295
|
+
opacity: 0.4;
|
296
|
+
}
|
297
|
+
|
298
|
+
.clickable-badge:hover {
|
299
|
+
transform: scale(1.1);
|
300
|
+
opacity: 1;
|
301
|
+
}
|
302
|
+
|
303
|
+
.clickable-badge.selected-filter {
|
304
|
+
transform: scale(1.1);
|
305
|
+
opacity: 1;
|
306
|
+
box-shadow: 0 0 0 2px #fff, 0 0 0 4px currentColor;
|
307
|
+
}
|
308
|
+
|
309
|
+
/* Ensure tooltips appear above the sticky header */
|
310
|
+
.status-container:hover::after,
|
311
|
+
.status-container:hover::before,
|
312
|
+
#last-updated:hover::after,
|
313
|
+
#last-updated:hover::before {
|
314
|
+
z-index: 1001;
|
315
|
+
}
|
63
316
|
</style>
|
64
317
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
|
65
318
|
<script
|
@@ -70,29 +323,84 @@
|
|
70
323
|
<body>
|
71
324
|
<div class="container">
|
72
325
|
<header>
|
73
|
-
<
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
</
|
326
|
+
<div class="d-flex justify-content-between align-items-center">
|
327
|
+
<h1>SkyPilot Managed Jobs</h1>
|
328
|
+
<div class="d-flex align-items-center">
|
329
|
+
<div class="form-check form-switch me-3">
|
330
|
+
<input class="form-check-input" type="checkbox" id="refresh-toggle" checked>
|
331
|
+
<label class="form-check-label" for="refresh-toggle">
|
332
|
+
Auto-refresh
|
333
|
+
<span class="refresh-indicator">
|
334
|
+
<span class="refresh-spinner" id="refresh-spinner"></span>
|
335
|
+
</span>
|
336
|
+
</label>
|
337
|
+
</div>
|
338
|
+
<p class="text-muted mb-0" id="last-updated"></p>
|
339
|
+
</div>
|
87
340
|
</div>
|
88
341
|
</header>
|
89
342
|
|
343
|
+
<!-- Hidden status filter -->
|
344
|
+
<select id="status-filter" style="display: none;">
|
345
|
+
<option value="">All</option>
|
346
|
+
{% for status in status_values %}
|
347
|
+
<option value="{{ status }}">{{ status }}</option>
|
348
|
+
{% endfor %}
|
349
|
+
</select>
|
350
|
+
|
351
|
+
{% if rows %}
|
352
|
+
<p>Filter By :
|
353
|
+
<span class="badge bg-secondary clickable-badge selected-filter me-2" data-status="ALL">All</span>
|
354
|
+
{% set status_dict = {} %}
|
355
|
+
{% for row in rows %}
|
356
|
+
{% set status = row[9].split()[0] %}
|
357
|
+
{% if status not in status_dict %}
|
358
|
+
{% set _ = status_dict.update({status: 1}) %}
|
359
|
+
{% else %}
|
360
|
+
{% set _ = status_dict.update({status: status_dict[status] + 1}) %}
|
361
|
+
{% endif %}
|
362
|
+
{% endfor %}
|
363
|
+
{% for status, count in status_dict|dictsort %}
|
364
|
+
<span class="me-2">
|
365
|
+
<span class="me-1">; {{ count }}</span>
|
366
|
+
{% if status.startswith('RUNNING') %}
|
367
|
+
<span class="badge bg-primary clickable-badge" data-status="{{ status }}">{{ status }}</span>
|
368
|
+
{% elif status.startswith('PENDING') or status.startswith('SUBMITTED') %}
|
369
|
+
<span class="badge bg-light clickable-badge" data-status="{{ status }}">{{ status }}</span>
|
370
|
+
{% elif status.startswith('RECOVERING') or status.startswith('CANCELLING') or status.startswith('STARTING') %}
|
371
|
+
<span class="badge bg-info clickable-badge" data-status="{{ status }}">{{ status }}</span>
|
372
|
+
{% elif status.startswith('SUCCEEDED') %}
|
373
|
+
<span class="badge bg-success clickable-badge" data-status="{{ status }}">{{ status }}</span>
|
374
|
+
{% elif status.startswith('CANCELLED') %}
|
375
|
+
<span class="badge bg-secondary clickable-badge" data-status="{{ status }}">{{ status }}</span>
|
376
|
+
{% elif status.startswith('FAILED') %}
|
377
|
+
<span class="badge bg-danger clickable-badge" data-status="{{ status }}">{{ status }}</span>
|
378
|
+
{% else %}
|
379
|
+
<span class="clickable-badge" data-status="{{ status }}">{{ status }}</span>
|
380
|
+
{% endif %}
|
381
|
+
</span>
|
382
|
+
{% endfor %}
|
383
|
+
</p>
|
384
|
+
{% else %}
|
385
|
+
<p>No jobs found.</p>
|
386
|
+
{% endif %}
|
387
|
+
|
90
388
|
<table class="table table-hover table-hover-selected fixed-header-table" id="jobs-table">
|
91
389
|
<thead>
|
92
390
|
<tr>
|
93
|
-
|
94
|
-
<th>
|
95
|
-
|
391
|
+
<td></td>
|
392
|
+
<th>ID</th>
|
393
|
+
<th>Task</th>
|
394
|
+
<th>Name</th>
|
395
|
+
<th>Total Duration</th>
|
396
|
+
<th>Job Duration</th>
|
397
|
+
<th>Status</th>
|
398
|
+
<th>Resources</th>
|
399
|
+
<th>Cluster</th>
|
400
|
+
<th>Region</th>
|
401
|
+
<th>Recoveries</th>
|
402
|
+
<th>Details</th>
|
403
|
+
<th>Actions</th>
|
96
404
|
</tr>
|
97
405
|
</thead>
|
98
406
|
<tbody>
|
@@ -102,33 +410,41 @@
|
|
102
410
|
<td>{{ row[1]|string|replace(' \u21B3', '') }}</td>
|
103
411
|
<td>{{ row[2] }}</td>
|
104
412
|
<td>{{ row[3] }}</td>
|
105
|
-
<td>{{ row[4] }}</td>
|
106
|
-
<td>{{ row[5] }}</td>
|
107
413
|
<td>{{ row[6] }}</td>
|
108
414
|
<td>{{ row[7] }}</td>
|
109
|
-
<td>{{ row[8] }}</td>
|
110
415
|
<td>
|
111
|
-
<!--
|
112
|
-
{
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
416
|
+
<!-- Status column with tooltip -->
|
417
|
+
<div class="status-container" style="position: relative;" title="{{ row[14] }}">
|
418
|
+
{% if row[9].startswith('RUNNING') %}
|
419
|
+
<span class="badge bg-primary">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
|
420
|
+
{% elif row[9].startswith('PENDING') or row[9].startswith('SUBMITTED') %}
|
421
|
+
<span class="badge bg-warning">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
|
422
|
+
{% elif row[9].startswith('RECOVERING') or row[9].startswith('CANCELLING') or row[9].startswith('STARTING') %}
|
423
|
+
<span class="badge bg-info">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
|
424
|
+
{% elif row[9].startswith('SUCCEEDED') %}
|
425
|
+
<span class="badge bg-success">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
|
426
|
+
{% elif row[9].startswith('CANCELLED') %}
|
427
|
+
<span class="badge bg-secondary">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
|
428
|
+
{% elif row[9].startswith('FAILED') %}
|
429
|
+
<span class="badge bg-danger">{{ row[9].split()[0] }}</span>{{ row[9][row[9].split()[0]|length:] }}
|
430
|
+
{% else %}
|
431
|
+
{{ row[9] }}
|
432
|
+
{% endif %}
|
433
|
+
</div>
|
434
|
+
</td>
|
435
|
+
<td>{{ row[4] }}</td> {# Resources #}
|
436
|
+
<td>{{ row[11] }}</td> {# Cluster #}
|
437
|
+
<td>{{ row[12] }}</td> {# Region #}
|
438
|
+
<td>{{ row[8] }}</td> {# Recoveries #}
|
439
|
+
<td>{{ row[13] }}</td> {# Details #}
|
440
|
+
<td>
|
441
|
+
{% if row[1]|string|replace(' \u21B3', '') and row[1]|string|replace(' \u21B3', '') != '-' %}
|
442
|
+
<a href="{{ url_for('download_log', job_id=row[1]|string|replace(' \u21B3', '')) }}"
|
443
|
+
class="btn btn-sm btn-outline-secondary">
|
444
|
+
Controller Log
|
445
|
+
</a>
|
126
446
|
{% endif %}
|
127
447
|
</td>
|
128
|
-
<td>{{ row[10] }}</td>
|
129
|
-
<td>{{ row[11] }}</td>
|
130
|
-
<td>{{ row[12] }}</td>
|
131
|
-
<td>{{ row[13] }}</td>
|
132
448
|
</tr>
|
133
449
|
{% endfor %}
|
134
450
|
</tbody>
|
@@ -197,7 +513,9 @@
|
|
197
513
|
document.addEventListener("DOMContentLoaded", function () {
|
198
514
|
var timestamp = "{{ last_updated_timestamp }}"; // Get the UTC timestamp from the template
|
199
515
|
var localTimestamp = moment.utc(timestamp).tz(moment.tz.guess()).format('YYYY-MM-DD HH:mm:ss z');
|
516
|
+
var utcTimestamp = moment.utc(timestamp).format('YYYY-MM-DD HH:mm:ss UTC');
|
200
517
|
document.getElementById("last-updated").textContent = "Last updated: " + localTimestamp;
|
518
|
+
document.getElementById("last-updated").title = utcTimestamp;
|
201
519
|
});
|
202
520
|
</script>
|
203
521
|
<script>
|
@@ -210,6 +528,7 @@
|
|
210
528
|
} else {
|
211
529
|
refreshToggle.checked = false;
|
212
530
|
}
|
531
|
+
|
213
532
|
// Add event listener to the toggle switch
|
214
533
|
function handleAutoRefresh() {
|
215
534
|
localStorage.setItem("refreshState", refreshToggle.checked);
|
@@ -217,6 +536,9 @@
|
|
217
536
|
if (refreshToggle.checked) {
|
218
537
|
// Auto-refresh is enabled
|
219
538
|
refreshInterval = setInterval(function () {
|
539
|
+
// Store current filter before reload
|
540
|
+
var currentFilter = document.getElementById("status-filter").value;
|
541
|
+
localStorage.setItem("statusFilter", currentFilter);
|
220
542
|
location.reload();
|
221
543
|
}, 30000); // 30 seconds in milliseconds
|
222
544
|
} else {
|
@@ -224,6 +546,17 @@
|
|
224
546
|
clearInterval(refreshInterval);
|
225
547
|
}
|
226
548
|
}
|
549
|
+
|
550
|
+
// Restore filter state after page load
|
551
|
+
document.addEventListener("DOMContentLoaded", function() {
|
552
|
+
var savedFilter = localStorage.getItem("statusFilter");
|
553
|
+
if (savedFilter) {
|
554
|
+
var statusFilter = document.getElementById("status-filter");
|
555
|
+
statusFilter.value = savedFilter;
|
556
|
+
filterStatus(savedFilter);
|
557
|
+
}
|
558
|
+
});
|
559
|
+
|
227
560
|
refreshToggle.addEventListener("change", handleAutoRefresh);
|
228
561
|
handleAutoRefresh();
|
229
562
|
</script>
|
@@ -231,13 +564,15 @@
|
|
231
564
|
function filterStatus(status) {
|
232
565
|
var rows = document.querySelectorAll("#jobs-table tbody tr");
|
233
566
|
rows.forEach(function(row) {
|
234
|
-
var statusCell = row.querySelector("td:nth-child(
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
567
|
+
var statusCell = row.querySelector("td:nth-child(7)"); // Status is now in the 7th column
|
568
|
+
if (statusCell) {
|
569
|
+
var statusText = statusCell.textContent.trim().split(' ')[0]; // Get first word of status
|
570
|
+
|
571
|
+
if (status === '' || statusText === status) {
|
572
|
+
row.style.display = "";
|
573
|
+
} else {
|
574
|
+
row.style.display = "none";
|
575
|
+
}
|
241
576
|
}
|
242
577
|
});
|
243
578
|
}
|
@@ -249,6 +584,85 @@
|
|
249
584
|
});
|
250
585
|
});
|
251
586
|
</script>
|
587
|
+
<script>
|
588
|
+
// Show loading spinner during refresh
|
589
|
+
window.addEventListener('beforeunload', function() {
|
590
|
+
document.getElementById('refresh-spinner').style.display = 'inline-block';
|
591
|
+
});
|
592
|
+
|
593
|
+
// Enhance table row hover effect
|
594
|
+
document.querySelectorAll('tbody tr').forEach(row => {
|
595
|
+
row.addEventListener('mouseenter', function() {
|
596
|
+
this.style.transform = 'scale(1.002)';
|
597
|
+
this.style.transition = 'transform 0.2s';
|
598
|
+
});
|
599
|
+
|
600
|
+
row.addEventListener('mouseleave', function() {
|
601
|
+
this.style.transform = 'scale(1)';
|
602
|
+
});
|
603
|
+
});
|
604
|
+
</script>
|
605
|
+
<script>
|
606
|
+
// Update column indices for job table
|
607
|
+
const JOB_TABLE_COLUMNS = [
|
608
|
+
'', 'ID', 'Task', 'Name', 'Total Duration',
|
609
|
+
'Job Duration', 'Status', 'Resources', 'Cluster', 'Region', 'Recoveries', 'Details',
|
610
|
+
'Actions'
|
611
|
+
];
|
612
|
+
</script>
|
613
|
+
<script>
|
614
|
+
// Replace the existing click handler for status badges with this updated version
|
615
|
+
document.addEventListener("DOMContentLoaded", function() {
|
616
|
+
const statusFilter = document.getElementById('status-filter');
|
617
|
+
const badges = document.querySelectorAll('.clickable-badge');
|
618
|
+
|
619
|
+
// Set initial state
|
620
|
+
const savedFilter = localStorage.getItem("statusFilter") || '';
|
621
|
+
updateSelectedBadge(savedFilter);
|
622
|
+
|
623
|
+
badges.forEach(function(badge) {
|
624
|
+
badge.addEventListener('click', function() {
|
625
|
+
const status = this.dataset.status;
|
626
|
+
const currentFilter = statusFilter.value;
|
627
|
+
|
628
|
+
// If clicking the already selected filter, clear it (show all)
|
629
|
+
const newStatus = (status === currentFilter || (status === 'ALL' && currentFilter === '')) ? '' :
|
630
|
+
(status === 'ALL' ? '' : status);
|
631
|
+
|
632
|
+
// Update filter and UI
|
633
|
+
statusFilter.value = newStatus;
|
634
|
+
filterStatus(newStatus);
|
635
|
+
localStorage.setItem("statusFilter", newStatus);
|
636
|
+
updateSelectedBadge(newStatus);
|
637
|
+
});
|
638
|
+
});
|
639
|
+
|
640
|
+
function updateSelectedBadge(selectedStatus) {
|
641
|
+
badges.forEach(badge => {
|
642
|
+
badge.classList.remove('selected-filter');
|
643
|
+
if ((selectedStatus === '' && badge.dataset.status === 'ALL') ||
|
644
|
+
badge.dataset.status === selectedStatus) {
|
645
|
+
badge.classList.add('selected-filter');
|
646
|
+
}
|
647
|
+
});
|
648
|
+
}
|
649
|
+
});
|
650
|
+
</script>
|
651
|
+
<script>
|
652
|
+
// Add scroll event listener to handle sticky header shadow
|
653
|
+
document.addEventListener("DOMContentLoaded", function() {
|
654
|
+
const header = document.querySelector('header');
|
655
|
+
const container = document.querySelector('.container');
|
656
|
+
|
657
|
+
window.addEventListener('scroll', function() {
|
658
|
+
if (container.getBoundingClientRect().top < 0) {
|
659
|
+
header.classList.add('sticky');
|
660
|
+
} else {
|
661
|
+
header.classList.remove('sticky');
|
662
|
+
}
|
663
|
+
});
|
664
|
+
});
|
665
|
+
</script>
|
252
666
|
|
253
667
|
</body>
|
254
668
|
|
@@ -64,6 +64,21 @@ def _get_ssh_key_name(prefix: str = '') -> str:
|
|
64
64
|
return name
|
65
65
|
|
66
66
|
|
67
|
+
def _get_private_ip(instance_info: Dict[str, Any], single_node: bool) -> str:
|
68
|
+
private_ip = instance_info.get('private_ip')
|
69
|
+
if private_ip is None:
|
70
|
+
if single_node:
|
71
|
+
# The Lambda cloud API may return an instance info without
|
72
|
+
# private IP. It does not align with their docs, but we still
|
73
|
+
# allow single-node cluster to proceed with provisioning, by using
|
74
|
+
# 127.0.0.1, as private IP is not critical for single-node case.
|
75
|
+
return '127.0.0.1'
|
76
|
+
msg = f'Failed to retrieve private IP for instance {instance_info}.'
|
77
|
+
logger.error(msg)
|
78
|
+
raise RuntimeError(msg)
|
79
|
+
return private_ip
|
80
|
+
|
81
|
+
|
67
82
|
def run_instances(region: str, cluster_name_on_cloud: str,
|
68
83
|
config: common.ProvisionConfig) -> common.ProvisionRecord:
|
69
84
|
"""Runs instances for the given cluster"""
|
@@ -197,13 +212,14 @@ def get_cluster_info(
|
|
197
212
|
) -> common.ClusterInfo:
|
198
213
|
del region # unused
|
199
214
|
running_instances = _filter_instances(cluster_name_on_cloud, ['active'])
|
215
|
+
single_node = len(running_instances) == 1
|
200
216
|
instances: Dict[str, List[common.InstanceInfo]] = {}
|
201
217
|
head_instance_id = None
|
202
218
|
for instance_id, instance_info in running_instances.items():
|
203
219
|
instances[instance_id] = [
|
204
220
|
common.InstanceInfo(
|
205
221
|
instance_id=instance_id,
|
206
|
-
internal_ip=instance_info
|
222
|
+
internal_ip=_get_private_ip(instance_info, single_node),
|
207
223
|
external_ip=instance_info['ip'],
|
208
224
|
ssh_port=22,
|
209
225
|
tags={},
|
sky/serve/serve_state.py
CHANGED
@@ -55,33 +55,35 @@ def create_table(cursor: 'sqlite3.Cursor', conn: 'sqlite3.Connection') -> None:
|
|
55
55
|
PRIMARY KEY (service_name, replica_id))""")
|
56
56
|
cursor.execute("""\
|
57
57
|
CREATE TABLE IF NOT EXISTS version_specs (
|
58
|
-
version INTEGER,
|
58
|
+
version INTEGER,
|
59
59
|
service_name TEXT,
|
60
60
|
spec BLOB,
|
61
61
|
PRIMARY KEY (service_name, version))""")
|
62
62
|
conn.commit()
|
63
63
|
|
64
|
+
# Backward compatibility.
|
65
|
+
db_utils.add_column_to_table(cursor, conn, 'services',
|
66
|
+
'requested_resources_str', 'TEXT')
|
67
|
+
# Deprecated: switched to `active_versions` below for the version
|
68
|
+
# considered active by the load balancer. The
|
69
|
+
# authscaler/replica_manager version can be found in the
|
70
|
+
# version_specs table.
|
71
|
+
db_utils.add_column_to_table(
|
72
|
+
cursor, conn, 'services', 'current_version',
|
73
|
+
f'INTEGER DEFAULT {constants.INITIAL_VERSION}')
|
74
|
+
# The versions that is activated for the service. This is a list
|
75
|
+
# of integers in json format.
|
76
|
+
db_utils.add_column_to_table(cursor, conn, 'services', 'active_versions',
|
77
|
+
f'TEXT DEFAULT {json.dumps([])!r}')
|
78
|
+
db_utils.add_column_to_table(cursor, conn, 'services',
|
79
|
+
'load_balancing_policy', 'TEXT DEFAULT NULL')
|
80
|
+
# Whether the service's load balancer is encrypted with TLS.
|
81
|
+
db_utils.add_column_to_table(cursor, conn, 'services', 'tls_encrypted',
|
82
|
+
'INTEGER DEFAULT 0')
|
83
|
+
conn.commit()
|
84
|
+
|
64
85
|
|
65
|
-
|
66
|
-
# Backward compatibility.
|
67
|
-
db_utils.add_column_to_table(_DB.cursor, _DB.conn, 'services',
|
68
|
-
'requested_resources_str', 'TEXT')
|
69
|
-
# Deprecated: switched to `active_versions` below for the version considered
|
70
|
-
# active by the load balancer. The authscaler/replica_manager version can be
|
71
|
-
# found in the version_specs table.
|
72
|
-
db_utils.add_column_to_table(_DB.cursor, _DB.conn, 'services',
|
73
|
-
'current_version',
|
74
|
-
f'INTEGER DEFAULT {constants.INITIAL_VERSION}')
|
75
|
-
# The versions that is activated for the service. This is a list of integers in
|
76
|
-
# json format.
|
77
|
-
db_utils.add_column_to_table(_DB.cursor, _DB.conn, 'services',
|
78
|
-
'active_versions',
|
79
|
-
f'TEXT DEFAULT {json.dumps([])!r}')
|
80
|
-
db_utils.add_column_to_table(_DB.cursor, _DB.conn, 'services',
|
81
|
-
'load_balancing_policy', 'TEXT DEFAULT NULL')
|
82
|
-
# Whether the service's load balancer is encrypted with TLS.
|
83
|
-
db_utils.add_column_to_table(_DB.cursor, _DB.conn, 'services', 'tls_encrypted',
|
84
|
-
'INTEGER DEFAULT 0')
|
86
|
+
db_utils.SQLiteConn(_DB_PATH, create_table)
|
85
87
|
_UNIQUE_CONSTRAINT_FAILED_ERROR_MSG = 'UNIQUE constraint failed: services.name'
|
86
88
|
|
87
89
|
|
{skypilot_nightly-1.0.0.dev20250202.dist-info → skypilot_nightly-1.0.0.dev20250204.dist-info}/RECORD
RENAMED
@@ -1,7 +1,7 @@
|
|
1
|
-
sky/__init__.py,sha256=
|
1
|
+
sky/__init__.py,sha256=wD_DitkbMqx2kv8G3wQz7yRYrWtCMbhBQoeM6oaT1KQ,5529
|
2
2
|
sky/admin_policy.py,sha256=hPo02f_A32gCqhUueF0QYy1fMSSKqRwYEg_9FxScN_s,3248
|
3
3
|
sky/authentication.py,sha256=LXUDABKP1FJCS256xTTDJa40WXwHKF5x49S-4hZbD1M,21501
|
4
|
-
sky/check.py,sha256=
|
4
|
+
sky/check.py,sha256=xzLlxUkBCrzpOho8lw65EvKLPl_b9lA2nteF5MSYbDQ,10885
|
5
5
|
sky/cli.py,sha256=B-YWYiKnfSGdSOXtAY8SRGOGhneUeNPBjXFZ0FuLZ8w,214131
|
6
6
|
sky/cloud_stores.py,sha256=PcLT57_8SZy7o6paAluElfBynaLkbaOq3l-8dNg1AVM,23672
|
7
7
|
sky/core.py,sha256=fE1rn4Ku94S0XmWTO5-6t6eT6aaJImNczRqEnTe8v7Q,38742
|
@@ -104,9 +104,9 @@ sky/jobs/recovery_strategy.py,sha256=m-EA-MWXPFrgx2CYFPr6MmgeUoDTEBmY2xruD2PRSGY
|
|
104
104
|
sky/jobs/scheduler.py,sha256=WAvNb8-vBk8q1zFordFdpH7gxqWDjPHDGZZay6aodOk,12028
|
105
105
|
sky/jobs/state.py,sha256=bvBNZMg3DzPfS4eHNzMqYaMui2cqnWoWGDIaiOpaXSk,40770
|
106
106
|
sky/jobs/utils.py,sha256=9tCKeY2x1lOgFQdaxqx6tZd2zd2e3pdUOQGvgvbf1Rk,52682
|
107
|
-
sky/jobs/dashboard/dashboard.py,sha256=
|
107
|
+
sky/jobs/dashboard/dashboard.py,sha256=lLXAt755bkOh0XNjl5eQbu5vys7zYpvEoJgM97DkpeM,7706
|
108
108
|
sky/jobs/dashboard/static/favicon.ico,sha256=uYlvgxSM7gjBmXpZ8wydvZUPAbJiiix-rc2Xe5mma9s,15086
|
109
|
-
sky/jobs/dashboard/templates/index.html,sha256=
|
109
|
+
sky/jobs/dashboard/templates/index.html,sha256=nBiIjYDgZdRyO4pNt_2qEj201QvCiiO2n2kuVuSgKeI,26289
|
110
110
|
sky/provision/__init__.py,sha256=rnuwL9x3qQGhX3EYW6KyZWfw_yBqGSiFBDz86T5xus4,6339
|
111
111
|
sky/provision/common.py,sha256=E8AlSUFcn0FYQq1erNmoVfMAdsF9tP2yxfyk-9PLvQU,10286
|
112
112
|
sky/provision/constants.py,sha256=oc_XDUkcoLQ_lwDy5yMeMSWviKS0j0s1c0pjlvpNeWY,800
|
@@ -154,7 +154,7 @@ sky/provision/kubernetes/manifests/smarter-device-manager-configmap.yaml,sha256=
|
|
154
154
|
sky/provision/kubernetes/manifests/smarter-device-manager-daemonset.yaml,sha256=RtTq4F1QUmR2Uunb6zuuRaPhV7hpesz4saHjn3Ncsb4,2010
|
155
155
|
sky/provision/lambda_cloud/__init__.py,sha256=6EEvSgtUeEiup9ivIFevHmgv0GqleroO2X0K7TRa2nE,612
|
156
156
|
sky/provision/lambda_cloud/config.py,sha256=jq1iLzp4Up61r4JGxvtpVbJlgXnea3LHYQhCQyyl7ik,272
|
157
|
-
sky/provision/lambda_cloud/instance.py,sha256=
|
157
|
+
sky/provision/lambda_cloud/instance.py,sha256=_gVFNfaXdqYEmCtCwgaCkvU8BwxZarpyetSGGA_Dj64,9681
|
158
158
|
sky/provision/lambda_cloud/lambda_utils.py,sha256=H2Qx4xdJyyEu2IXaj5AyppuPJW385nF5_KXFOk8j9RI,9858
|
159
159
|
sky/provision/oci/__init__.py,sha256=5E6EUtTK3mqGVREw5TuVl5DxteBYTZigIii7c8gHExU,612
|
160
160
|
sky/provision/oci/config.py,sha256=diSDTyHLokcuXGB2XgZCHFvsXa8bah1PP2XuMouW_UU,1650
|
@@ -195,7 +195,7 @@ sky/serve/core.py,sha256=ANjALyYiQUmcpWjQ1YJor2rqHJypQpzuQxuIPnDyEk0,35788
|
|
195
195
|
sky/serve/load_balancer.py,sha256=2nkMPRvy-h7hJL4Qq__tkT8nIAVC_nmjyXf8mMGYEFk,13658
|
196
196
|
sky/serve/load_balancing_policies.py,sha256=XVj76qBgqh7h6wfx53RKQFzBefDWTE4TCdCEtFLLtI4,5398
|
197
197
|
sky/serve/replica_managers.py,sha256=SW7k2iivUZ6dw_YMgGYOHOGD9_yyV4byfKa8e5t8_HE,57587
|
198
|
-
sky/serve/serve_state.py,sha256=
|
198
|
+
sky/serve/serve_state.py,sha256=Do-MITtijw1z8dDUyJZMAGnjZCgdUFtJ2nwhhcjpOGI,20056
|
199
199
|
sky/serve/serve_utils.py,sha256=m1Zcjslnzcr5AAppzV48WDOwMWjRaXotTUd_iN-dHgc,40654
|
200
200
|
sky/serve/service.py,sha256=DPU1PJGuHa1WaNqxYqgpmqd4LA9jBbQM-KlLrA6C1M0,12156
|
201
201
|
sky/serve/service_spec.py,sha256=Q0qnFRjNnfGIpksubH5VqPKIlvpWs5had_Ma_PSHyo8,16940
|
@@ -289,9 +289,9 @@ sky/utils/kubernetes/k8s_gpu_labeler_job.yaml,sha256=k0TBoQ4zgf79-sVkixKSGYFHQ7Z
|
|
289
289
|
sky/utils/kubernetes/k8s_gpu_labeler_setup.yaml,sha256=VLKT2KKimZu1GDg_4AIlIt488oMQvhRZWwsj9vBbPUg,3812
|
290
290
|
sky/utils/kubernetes/rsync_helper.sh,sha256=h4YwrPFf9727CACnMJvF3EyK_0OeOYKKt4su_daKekw,1256
|
291
291
|
sky/utils/kubernetes/ssh_jump_lifecycle_manager.py,sha256=Kq1MDygF2IxFmu9FXpCxqucXLmeUrvs6OtRij6XTQbo,6554
|
292
|
-
skypilot_nightly-1.0.0.
|
293
|
-
skypilot_nightly-1.0.0.
|
294
|
-
skypilot_nightly-1.0.0.
|
295
|
-
skypilot_nightly-1.0.0.
|
296
|
-
skypilot_nightly-1.0.0.
|
297
|
-
skypilot_nightly-1.0.0.
|
292
|
+
skypilot_nightly-1.0.0.dev20250204.dist-info/LICENSE,sha256=emRJAvE7ngL6x0RhQvlns5wJzGI3NEQ_WMjNmd9TZc4,12170
|
293
|
+
skypilot_nightly-1.0.0.dev20250204.dist-info/METADATA,sha256=8lPz1CRU_biL8tZh6AWIZKdor-F20sON44UpXTOyiSw,21251
|
294
|
+
skypilot_nightly-1.0.0.dev20250204.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
295
|
+
skypilot_nightly-1.0.0.dev20250204.dist-info/entry_points.txt,sha256=StA6HYpuHj-Y61L2Ze-hK2IcLWgLZcML5gJu8cs6nU4,36
|
296
|
+
skypilot_nightly-1.0.0.dev20250204.dist-info/top_level.txt,sha256=qA8QuiNNb6Y1OF-pCUtPEr6sLEwy2xJX06Bd_CrtrHY,4
|
297
|
+
skypilot_nightly-1.0.0.dev20250204.dist-info/RECORD,,
|
File without changes
|
{skypilot_nightly-1.0.0.dev20250202.dist-info → skypilot_nightly-1.0.0.dev20250204.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|
File without changes
|