Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions sky/dashboard/src/components/clusters.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -619,10 +619,15 @@ export function ClusterTable({
const activeClusters = await dashboardCache.get(getClusters);

if (showHistory) {
const historyClusters = await dashboardCache.get(getClusterHistory, [
null,
historyDays,
]);
let historyClusters = [];
try {
historyClusters = await dashboardCache.get(getClusterHistory, [
null,
historyDays,
]);
} catch (error) {
console.error('Error fetching cluster history:', error);
}
// Mark clusters as active or historical for UI distinction
const markedActiveClusters = activeClusters.map((cluster) => ({
...cluster,
Expand Down
6 changes: 6 additions & 0 deletions sky/dashboard/src/components/elements/version-display.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export function VersionDisplay() {

const getVersion = async () => {
const data = await apiClient.get('/api/health');
if (!data.ok) {
console.error(
`API request /api/health failed with status ${data.status}`
);
return;
}
const healthData = await data.json();
if (healthData.version) {
setVersion(healthData.version);
Expand Down
16 changes: 6 additions & 10 deletions sky/dashboard/src/components/jobs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { getPoolStatus } from '@/data/connectors/jobs';
import jobsCacheManager from '@/lib/jobs-cache-manager';
import { getClusters, downloadJobLogs } from '@/data/connectors/clusters';
import { getWorkspaces } from '@/data/connectors/workspaces';
import { getUsers } from '@/data/connectors/users';
import {
CustomTooltip as Tooltip,
NonCapitalizedTooltip,
Expand Down Expand Up @@ -219,7 +218,6 @@ export function ManagedJobs() {
jobsCacheManager.invalidateCache();
dashboardCache.invalidate(getPoolStatus, [{}]);
dashboardCache.invalidate(getWorkspaces);
dashboardCache.invalidate(getUsers);

// Trigger a re-fetch in both tables via their refreshDataRef
if (jobsRefreshRef.current) {
Expand Down Expand Up @@ -441,15 +439,13 @@ export function ManagedJobsTable({
const isDataLoading = jobsCacheManager.isDataLoading(params);

if (includeStatus) {
const [jr, cd] = await Promise.all([
jobsCacheManager.getPaginatedJobs(params),
dashboardCache.get(getClusters),
]);
jobsResponse = jr;
clustersData = cd;
} else {
jobsResponse = await jobsCacheManager.getPaginatedJobs(params);
try {
clustersData = await dashboardCache.get(getClusters);
} catch (error) {
console.error('Error fetching clusters:', error);
}
}
jobsResponse = await jobsCacheManager.getPaginatedJobs(params);

// Always process the response, even if it's null
const {
Expand Down
72 changes: 45 additions & 27 deletions sky/dashboard/src/components/users.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1337,23 +1337,29 @@ function UsersTable({
if (showLoading) setIsLoading(false);

// Step 2: Load clusters and jobs in background and update counts
const [clustersData, managedJobsResponse] = await Promise.all([
dashboardCache.get(getClusters),
dashboardCache.get(getManagedJobs, [
{
allUsers: true,
skipFinished: true,
fields: [
'user_hash',
'status',
'accelerators',
'job_name',
'job_id',
'infra',
],
},
]),
]);
let clustersData = [];
let managedJobsResponse = { jobs: [] };
try {
[clustersData, managedJobsResponse] = await Promise.all([
dashboardCache.get(getClusters),
dashboardCache.get(getManagedJobs, [
{
allUsers: true,
skipFinished: true,
fields: [
'user_hash',
'status',
'accelerators',
'job_name',
'job_id',
'infra',
],
},
]),
]);
} catch (error) {
console.error('Error fetching clusters and managed jobs:', error);
}

const jobsData = managedJobsResponse.jobs || [];

Expand Down Expand Up @@ -2332,16 +2338,28 @@ function ServiceAccountTokensView({
setTokens(tokensData || []);

// Step 2: Fetch clusters and jobs data in parallel
const [clustersResponse, jobsResponse] = await Promise.all([
dashboardCache.get(getClusters),
dashboardCache.get(getManagedJobs, [
{
allUsers: true,
skipFinished: true,
fields: ['user_hash', 'status', 'accelerators', 'job_id', 'infra'],
},
]),
]);
let clustersResponse = [];
let jobsResponse = { jobs: [] };
try {
[clustersResponse, jobsResponse] = await Promise.all([
dashboardCache.get(getClusters),
dashboardCache.get(getManagedJobs, [
{
allUsers: true,
skipFinished: true,
fields: [
'user_hash',
'status',
'accelerators',
'job_id',
'infra',
],
},
]),
]);
} catch (error) {
console.error('Error fetching clusters and managed jobs:', error);
}

const clustersData = clustersResponse || [];
const jobsData = jobsResponse?.jobs || [];
Expand Down
88 changes: 65 additions & 23 deletions sky/dashboard/src/components/workspaces.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ import { REFRESH_INTERVALS } from '@/lib/config';
import cachePreloader from '@/lib/cache-preloader';
import { apiClient } from '@/data/connectors/client';
import { sortData } from '@/data/utils';
import { CLOUD_CANONICALIZATIONS } from '@/data/connectors/constants';
import {
CLOUD_CANONICALIZATIONS,
CLUSTER_NOT_UP_ERROR,
} from '@/data/connectors/constants';
import Link from 'next/link';

// Workspace-aware API functions (cacheable)
Expand Down Expand Up @@ -93,11 +96,9 @@ export async function getWorkspaceClusters(workspaceName) {
);
return filteredClusters;
} catch (error) {
console.error(
`Error fetching clusters for workspace ${workspaceName}:`,
error
);
return [];
const msg = `Error fetching clusters for workspace ${workspaceName}: ${error}`;
console.error(msg);
throw new Error(msg);
}
}

Expand All @@ -112,8 +113,41 @@ export async function getWorkspaceManagedJobs(workspaceName) {
override_skypilot_config: { active_workspace: workspaceName },
});

// Check if initial request succeeded
if (!response.ok) {
const msg = `Initial API request to get managed jobs failed with status ${response.status} for workspace ${workspaceName}`;
throw new Error(msg);
}

const id = response.headers.get('X-Skypilot-Request-ID');
// Handle empty request ID
if (!id) {
const msg = `No request ID received from server for getting managed jobs for workspace ${workspaceName}`;
throw new Error(msg);
}
const fetchedData = await apiClient.get(`/api/get?request_id=${id}`);
if (fetchedData.status === 500) {
try {
const data = await fetchedData.json();
if (data.detail && data.detail.error) {
try {
const error = JSON.parse(data.detail.error);
// Handle specific error types
if (error.type && error.type === CLUSTER_NOT_UP_ERROR) {
return { jobs: [] };
}
} catch (jsonError) {
console.error('Error parsing JSON:', jsonError);
}
}
} catch (parseError) {
console.error('Error parsing JSON:', parseError);
}
}
if (!fetchedData.ok) {
const msg = `API request to get managed jobs result failed with status ${fetchedData.status} for workspace ${workspaceName}`;
throw new Error(msg);
}
const data = await fetchedData.json();
const jobsData = data.return_value
? JSON.parse(data.return_value)
Expand All @@ -134,11 +168,9 @@ export async function getWorkspaceManagedJobs(workspaceName) {

return jobsData;
} catch (error) {
console.error(
`Error fetching managed jobs for workspace ${workspaceName}:`,
error
);
return { jobs: [] };
const msg = `Error fetching managed jobs for workspace ${workspaceName}: ${error}`;
console.error(msg);
throw new Error(msg);
}
}

Expand Down Expand Up @@ -407,18 +439,28 @@ export function Workspaces() {
// Fetch data for each workspace in parallel using workspace-aware API calls
const workspaceDataPromises = configuredWorkspaceNames.map(
async (wsName) => {
const [enabledClouds, clusters, managedJobs] = await Promise.all([
dashboardCache.get(getEnabledClouds, [wsName]),
dashboardCache.get(getWorkspaceClusters, [wsName]),
dashboardCache.get(getWorkspaceManagedJobs, [wsName]),
]);

return {
workspaceName: wsName,
enabledClouds,
clusters: clusters || [],
managedJobs: managedJobs || { jobs: [] },
};
try {
const [enabledClouds, clusters, managedJobs] = await Promise.all([
dashboardCache.get(getEnabledClouds, [wsName]),
dashboardCache.get(getWorkspaceClusters, [wsName]),
dashboardCache.get(getWorkspaceManagedJobs, [wsName]),
]);

return {
workspaceName: wsName,
enabledClouds,
clusters: clusters || [],
managedJobs: managedJobs || { jobs: [] },
};
} catch (error) {
console.error('Error fetching workspace data:', error);
return {
workspaceName: wsName,
enabledClouds: [],
clusters: [],
managedJobs: { jobs: [] },
};
}
}
);

Expand Down
74 changes: 52 additions & 22 deletions sky/dashboard/src/data/connectors/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,57 @@ import { ENDPOINT } from './constants';

export const apiClient = {
fetch: async (path, body, method = 'POST') => {
const headers =
method === 'POST'
? {
'Content-Type': 'application/json',
}
: {};
try {
const headers =
method === 'POST'
? {
'Content-Type': 'application/json',
}
: {};

const baseUrl = window.location.origin;
const fullUrl = `${baseUrl}${ENDPOINT}${path}`;
const baseUrl = window.location.origin;
const fullUrl = `${baseUrl}${ENDPOINT}${path}`;

const response = await fetch(fullUrl, {
method,
headers,
body: method === 'POST' ? JSON.stringify(body) : undefined,
});
const response = await fetch(fullUrl, {
method,
headers,
body: method === 'POST' ? JSON.stringify(body) : undefined,
});

// Check if initial request succeeded
if (!response.ok) {
const msg = `Initial API request ${path} failed with status ${response.status}`;
throw new Error(msg);
}

// Handle X-Request-ID for API requests
const id =
response.headers.get('X-Skypilot-Request-ID') ||
response.headers.get('X-Request-ID');
// Handle X-Request-ID for API requests
const id =
response.headers.get('X-Skypilot-Request-ID') ||
response.headers.get('X-Request-ID');

const fetchedData = await fetch(
`${baseUrl}${ENDPOINT}/api/get?request_id=${id}`
);
const data = await fetchedData.json();
return data.return_value ? JSON.parse(data.return_value) : [];
// Handle empty request ID
if (!id) {
const msg = `No request ID received from server for ${path}`;
throw new Error(msg);
}

const fetchedData = await fetch(
`${baseUrl}${ENDPOINT}/api/get?request_id=${id}`
);

// Handle all error status codes (4xx, 5xx, etc.)
if (!fetchedData.ok) {
const msg = `API request to get ${path} result failed with status ${fetchedData.status}`;
throw new Error(msg);
}

const data = await fetchedData.json();
return data.return_value ? JSON.parse(data.return_value) : [];
} catch (error) {
const msg = `Error in apiClient.fetch for ${path}: ${error}`;
console.error(msg);
throw new Error(msg);
}
},

// Helper method for POST requests
Expand All @@ -51,6 +76,11 @@ export const apiClient = {
// Helper method for streaming responses
stream: async (path, body, onData) => {
const response = await apiClient.post(path, body);
if (!response.ok) {
const msg = `API request ${path} failed with status ${response.status}`;
console.error(msg);
throw new Error(msg);
}
const reader = response.body.getReader();

try {
Expand Down
Loading
Loading