Skip to content

[UX] update docs and minors for dashboard #5287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
19 changes: 19 additions & 0 deletions docs/source/getting-started/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,25 @@ This may show multiple clusters, if you have created several:

See here for a list of all possible :ref:`cluster states <sky-status>`.

Access the dashboard
====================

If you install SkyPilot from the official package, after starting the API server (when running locally, the first CLI or SDK call will automatically start a SkyPilot API server), run :code:`sky dashboard` to access the dashboard. This automatically opens a browser tab to show the dashboard for the clusters and managed jobs status.

If you install SkyPilot from source, before starting the API server, refer to the `Dashboard README <https://github.com/skypilot-org/skypilot/blob/master/sky/dashboard/README.md#access-the-dashboard>`_ to build the dashboard. Then you can start your API server and access the dashboard through :code:`sky dashboard`.

The clusters page example:

.. image:: ../images/dashboard-clusters.png
:width: 800
:alt: Clusters dashboard

The managed jobs page example:

.. image:: ../images/dashboard-managed-jobs.png
:width: 800
:alt: Managed jobs dashboard

.. _ssh:

SSH into clusters
Expand Down
Binary file added docs/source/images/dashboard-clusters.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/images/dashboard-managed-jobs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions docs/source/reference/api-server/api-server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Local API server (individual users)
For an individual user, SkyPilot can be used as a normal command line
tool. Whenever a SkyPilot command is run and an API server is not detected, SkyPilot will automatically start
a SkyPilot API server running locally in the background. No user action is needed.
And you can access the dashboard at `http://127.0.0.1:46580/dashboard` by default.

.. image:: ../../images/client-server/local.png
:alt: SkyPilot API server local mode
Expand Down Expand Up @@ -90,7 +91,7 @@ To verify that the API server is working, run ``sky api info``:
.. code-block:: console
$ sky api info
Using SkyPilot API server: http://127.0.0.1:46580
Using SkyPilot API server: http://127.0.0.1:46580 Dashboard: http://127.0.0.1:46580/dashboard
├── Status: healthy, commit: xxxxx, version: 1.0.0-dev0
└── User: skypilot-user (xxxxxx)
Expand All @@ -103,7 +104,7 @@ To verify that the API server is working, run ``sky api info``:
$ export SKYPILOT_API_SERVER_ENDPOINT=http://skypilot:[email protected]:30050
$ sky api info
Using SkyPilot API server: http://myendpoint.com:30050
Using SkyPilot API server: http://myendpoint.com:30050 Dashboard: http://myendpoint.com:30050/dashboard
├── Status: healthy, commit: xxxxx, version: 1.0.0-dev0
└── User: skypilot-user (xxxxxx)
Expand Down
11 changes: 10 additions & 1 deletion sky/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4271,6 +4271,14 @@ def jobs_dashboard():
managed_jobs.dashboard()


@cli.command(cls=_DocumentedCodeCommand)
@config_option(expose_value=False)
@usage_lib.entrypoint
def dashboard() -> None:
"""Starts the dashboard for skypilot."""
sdk.dashboard()


@cli.group(cls=_NaturalOrderGroup)
def serve():
"""SkyServe CLI (multi-region, multi-cloud serving)."""
Expand Down Expand Up @@ -5902,7 +5910,8 @@ def api_info():
api_server_info = sdk.api_info()
user_name = os.getenv(constants.USER_ENV_VAR, getpass.getuser())
user_hash = common_utils.get_user_hash()
click.echo(f'Using SkyPilot API server: {url}\n'
dashboard_url = f'{url}/dashboard'
click.echo(f'Using SkyPilot API server: {url} Dashboard: {dashboard_url}\n'
f'{ux_utils.INDENT_SYMBOL}Status: {api_server_info["status"]}, '
f'commit: {api_server_info["commit"]}, '
f'version: {api_server_info["version"]}\n'
Expand Down
15 changes: 14 additions & 1 deletion sky/client/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import subprocess
import typing
from typing import Any, Dict, List, Optional, Tuple, Union
import webbrowser

import click
import colorama
Expand Down Expand Up @@ -294,6 +295,17 @@ def validate(
response.json().get('detail'))


@usage_lib.entrypoint
@server_common.check_server_healthy_or_start
@annotations.client_api
def dashboard() -> None:
"""Starts the dashboard for SkyPilot."""
api_server_url = server_common.get_server_url()
url = f'{api_server_url}/dashboard'
logger.info(f'Opening dashboard in browser: {url}')
webbrowser.open(url)


@usage_lib.entrypoint
@server_common.check_server_healthy_or_start
@annotations.client_api
Expand Down Expand Up @@ -1713,8 +1725,9 @@ def api_start(
if foreground:
# Explain why current process exited
logger.info('API server is already running:')
dashboard_msg = f'Dashboard: {server_common.get_server_url(host)}/dashboard'
logger.info(f'{ux_utils.INDENT_SYMBOL}SkyPilot API server: '
f'{server_common.get_server_url(host)}\n'
f'{server_common.get_server_url(host)} {dashboard_msg}\n'
f'{ux_utils.INDENT_LAST_SYMBOL}'
f'View API server logs at: {constants.API_SERVER_LOGS}')

Expand Down
Binary file modified sky/dashboard/public/favicon.ico
Binary file not shown.
14 changes: 10 additions & 4 deletions sky/dashboard/src/components/clusters.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ import {
VSCodeInstructionsModal,
} from '@/components/elements/modals';
import { StatusBadge } from '@/components/elements/StatusBadge';
import { useMobile } from '@/hooks/useMobile';

export function Clusters() {
const [loading, setLoading] = useState(false);
const refreshDataRef = React.useRef(null);
const [isSSHModalOpen, setIsSSHModalOpen] = useState(false);
const [isVSCodeModalOpen, setIsVSCodeModalOpen] = useState(false);
const [selectedCluster, setSelectedCluster] = useState(null);
const isMobile = useMobile();

const handleRefresh = () => {
if (refreshDataRef.current) {
Expand All @@ -47,7 +49,10 @@ export function Clusters() {
<Layout highlighted="clusters">
<div className="flex items-center justify-between mb-4 h-5">
<div className="text-base">
<Link href="/clusters" className="text-sky-blue leading-none">
<Link
href="/clusters"
className="text-sky-blue hover:underline leading-none"
>
Sky Clusters
</Link>
</div>
Expand All @@ -65,7 +70,7 @@ export function Clusters() {
className="text-sky-blue hover:text-sky-blue-bright flex items-center"
>
<RotateCwIcon className="h-4 w-4 mr-1.5" />
<span>Refresh</span>
{!isMobile && <span>Refresh</span>}
</Button>
</div>
</div>
Expand Down Expand Up @@ -430,6 +435,7 @@ export function Status2Actions({
onOpenVSCodeModal,
}) {
const actions = enabledActions(status);
const isMobile = useMobile();

const handleActionClick = (actionName) => {
switch (actionName) {
Expand Down Expand Up @@ -476,7 +482,7 @@ export function Status2Actions({
className="text-sky-blue hover:text-sky-blue-bright font-medium inline-flex items-center"
>
{actionIcon}
{label && <span className="ml-1.5">{label}</span>}
{!isMobile && <span className="ml-1.5">{label}</span>}
</button>
</Tooltip>
);
Expand All @@ -492,7 +498,7 @@ export function Status2Actions({
title={actionName}
>
{actionIcon}
{label && <span className="ml-1.5">{label}</span>}
{!isMobile && <span className="ml-1.5">{label}</span>}
</span>
</Tooltip>
);
Expand Down
20 changes: 3 additions & 17 deletions sky/dashboard/src/components/elements/layout.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
import React, { useState, useEffect } from 'react';
import React from 'react';
import { TopBar, SidebarProvider } from './sidebar';
import { useMobile } from '@/hooks/useMobile';

function LayoutContent({ children, highlighted }) {
const [isMobile, setIsMobile] = useState(false);

useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768);
};

// Check on initial load
checkMobile();

// Add resize listener
window.addEventListener('resize', checkMobile);

// Cleanup
return () => window.removeEventListener('resize', checkMobile);
}, []);
const isMobile = useMobile();

return (
<div className="min-h-screen bg-gray-50">
Expand Down
5 changes: 4 additions & 1 deletion sky/dashboard/src/components/elements/modals.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Card } from '@/components/ui/card';
import { CopyIcon } from 'lucide-react';
import { CustomTooltip as Tooltip } from '@/components/utils';
import { BASE_PATH } from '@/data/connectors/constants';
import { useMobile } from '@/hooks/useMobile';

export function SSHInstructionsModal({ isOpen, onClose, cluster }) {
const [copied, setCopied] = React.useState(false);
Expand Down Expand Up @@ -77,6 +78,8 @@ export function SSHInstructionsModal({ isOpen, onClose, cluster }) {
}

export function VSCodeInstructionsModal({ isOpen, onClose, cluster }) {
const isMobile = useMobile();

return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="sm:max-w-3xl">
Expand All @@ -101,7 +104,7 @@ export function VSCodeInstructionsModal({ isOpen, onClose, cluster }) {
Connect with VSCode/Cursor
</h3>
<div
className="relative -mt-10"
className={`relative ${isMobile ? '-mt-5' : '-mt-10'}`}
style={{ paddingBottom: '75%' }}
>
<video
Expand Down
Loading