diff --git a/mkdocs.yml b/mkdocs.yml index 2fc74935a..62d7feabf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -397,10 +397,8 @@ nav: - llms-full.txt: https://dstack.ai/llms-full.txt - skill.md: https://dstack.ai/skill.md - Case studies: blog/case-studies.md - - Benchmarks: blog/benchmarks.md - Blog: - blog/index.md - - Discord: https://discord.gg/u8SmfwPpMd" target="_blank # - Changelog: https://github.com/dstackai/dstack/releases" target="_blank # - GitHub: https://github.com/dstackai/dstack" target="_blank # - Sign in: https://sky.dstack.ai" target="_blank diff --git a/mkdocs/assets/stylesheets/cloudscape-docs.css b/mkdocs/assets/stylesheets/cloudscape-docs.css index cabeb93b9..8f1c5c6c0 100644 --- a/mkdocs/assets/stylesheets/cloudscape-docs.css +++ b/mkdocs/assets/stylesheets/cloudscape-docs.css @@ -204,15 +204,16 @@ white-space: nowrap; } -/* GitHub → outlined "GitHub" with a trailing external-link glyph (no star count). */ -[data-md-color-primary=white] .md-header__buttons .md-button--primary.github, -[data-md-color-primary=white] .md-header__buttons .md-button--primary.github:hover { +/* GitHub → outlined (normal) "GitHub" with a trailing external-link glyph. Weight 700 so it matches + the filled "Get started" trigger beside it (the two now read as one type, different fills). */ +[data-md-color-primary=white] .md-header__buttons .md-button--primary.github { background: transparent; color: var(--cs-text); border: 1px solid var(--cs-border); + font-weight: 700 !important; } -/* Gap between GitHub and Get started → 20px to match /old (was 5px). */ +/* Gap between GitHub and dstack Sky → 20px to match /old (was 5px). */ .md-header__buttons .md-button.github { margin-right: 20px; } @@ -235,138 +236,206 @@ mask: var(--cs-ext-icon) center / contain no-repeat; } -/* Get started → /old's split button (dark primary main + caret) with a dropdown menu. */ -.md-header__buttons .cs-get-started { - position: relative; - display: inline-flex; - align-items: stretch; - margin-right: 5px; -} - -[data-md-color-primary=white] .md-header__buttons .cs-get-started .md-button--primary, -[data-md-color-primary=white] .md-header__buttons .cs-get-started .md-button--primary:hover { - background: var(--cs-text); - color: var(--cs-bg); - border: 1px solid var(--cs-text); - margin: 0; - /* Primary (filled) button uses 500 weight per design — the GitHub outline button stays 700. */ - font-weight: 500 !important; +/* Try dstack Sky → outlined (normal) pill with a trailing external-link glyph, sitting after + the now-filled GitHub button (the two swapped emphasis vs the old GitHub + Get started). */ +[data-md-color-primary=white] .md-header__buttons .md-button--primary.cs-try-sky { + background: transparent; + color: var(--cs-text); + border: 1px solid var(--cs-border); } - -/* Hover states (Cloudscape): the outlined GitHub button fills subtly (NOT black — landing.css - forces `background:black !important` on primary hover, so these need !important too); the dark - Get-started button lightens to the button-hover token. */ -[data-md-color-primary=white] .md-header__buttons .md-button--primary.github:hover { +[data-md-color-primary=white] .md-header__buttons .md-button--primary.cs-try-sky:hover { background: var(--cs-hover) !important; border-color: var(--cs-border) !important; color: var(--cs-text) !important; } -/* Hovering EITHER segment lights up BOTH (the whole pill reads as one button that opens the menu). */ -[data-md-color-primary=white] .md-header__buttons .cs-get-started:hover .md-button--primary { - background: var(--cs-btn-hover) !important; - border-color: var(--cs-btn-hover) !important; - color: var(--cs-bg) !important; +.md-header__buttons .md-button.cs-try-sky::after { + content: ""; + display: inline-block; + width: 16px; + height: 16px; + margin-left: 6px; + flex: 0 0 auto; + background-color: currentColor; + -webkit-mask: var(--cs-ext-icon) center / contain no-repeat; + mask: var(--cs-ext-icon) center / contain no-repeat; } -.md-header__buttons .cs-get-started__main { - border-radius: 12px 0 0 12px !important; - border-right: 0 !important; - cursor: pointer; - /* No separator now, so pull the caret in close to the label (was 18px + the toggle's 6px). */ - padding-right: 6px !important; +/* "Get started" header dropdown — mirrors the landing's Products popup (featured open-source + + dstack Sky + Enterprise). Pure-CSS hover/focus-within (the docs are static); right-aligned so the + panel never overflows the header's right edge. Reuses the --cs-* tokens (defined in extra.css). */ +.cs-gs-menu { + position: relative; + display: inline-flex; + align-items: center; } - -/* No segment separator — the caret reads as part of the same button. */ -.md-header__buttons .cs-get-started__toggle { - border-radius: 0 12px 12px 0 !important; - padding: 0 6px !important; - border-left: 0 !important; - cursor: pointer; +.cs-gs-menu__trigger { + display: inline-flex !important; + align-items: center; + gap: 5px; + /* the caret is lighter than a full glyph, so trim the trailing padding (was 18px). */ + padding-right: 12px !important; } - -.md-header__buttons .cs-get-started__toggle svg { - width: 18px; - height: 18px; - fill: currentColor; +.cs-gs-menu__caret { + transition: transform 0.15s ease; } - -/* Matches the new landing / old Get-started popup (cloudscape-overrides.css): fixed 300px, flat - (no shadow), a single 0.5px cs-text border, 12px radius clipped to rounded corners. */ -.md-header__buttons .cs-get-started__menu { +.cs-gs-menu:hover .cs-gs-menu__caret, +.cs-gs-menu:focus-within .cs-gs-menu__caret { + transform: rotate(180deg); +} +.cs-gs-menu__popup { position: absolute; - top: calc(100% + 6px); + top: calc(100% + 8px); right: 0; - min-width: 300px; - padding: 8px 0; - display: flex; - flex-direction: column; + z-index: 1000; + width: 360px; + padding: 6px; background: var(--cs-bg); border: 0.5px solid var(--cs-text); border-radius: 12px; - box-shadow: none; - overflow: hidden; - z-index: 1000; - /* Match the landing dropdown's subpixel smoothing (Material defaults to antialiased = thinner). */ - -webkit-font-smoothing: auto; - -moz-osx-font-smoothing: auto; + box-shadow: 0 12px 34px rgba(0, 0, 0, 0.14); + opacity: 0; + visibility: hidden; + transform: translateY(-4px); + transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s; +} +/* Invisible bridge over the 8px gap so the pointer can travel from trigger to popup. */ +.cs-gs-menu__popup::before { + content: ""; + position: absolute; + inset: -8px 0 auto 0; + height: 8px; } - -.md-header__buttons .cs-get-started__menu[hidden] { - display: none; +.cs-gs-menu:hover .cs-gs-menu__popup, +.cs-gs-menu:focus-within .cs-gs-menu__popup { + opacity: 1; + visibility: visible; + transform: translateY(0); +} +/* Text inside the popup must wrap — Material forces white-space:nowrap on header links/buttons. */ +.cs-gs-menu__popup, +.cs-gs-menu__feat, +.cs-gs-menu__row, +.cs-gs-menu__feat *, +.cs-gs-menu__row * { + white-space: normal !important; +} +.cs-gs-menu__feat { + position: relative; + display: flex; + align-items: flex-start; + gap: 12px; + padding: 14px; + border-radius: 10px; + background: linear-gradient(135deg, #002aff, #002aff, #e165fe); + color: #fff; + text-decoration: none; } - -/* Group headers ("Products" / "Login") → 15px / 300, ~5.5px vertical padding, like the landing. */ -.md-header__buttons .cs-get-started__group { - padding: 5.5px 16px; - font-size: 15px; - font-weight: 300; - color: var(--cs-text); +.cs-gs-menu__feat-body { + flex: 1 1 auto; + min-width: 0; } - -/* Items → 4px/16px padding; a 15/600 heading-color title with an optional description below. */ -.md-header__buttons .cs-get-started__menu a { +/* Left column: icon tile with the live star count centered beneath it (mirrors the landing). */ +.cs-gs-menu__feat-iccol { display: flex; flex-direction: column; - padding: 4px 16px; - font-size: 15px; + align-items: center; + gap: 6px; + flex: 0 0 auto; +} +.cs-gs-menu__gh { + color: rgba(255, 255, 255, 0.88); + font-size: 12px; + font-weight: 600; + font-variant-numeric: tabular-nums; +} +.cs-gs-menu__feat-ic { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 34px; + height: 34px; + border-radius: 9px; + border: 0.5px solid rgba(255, 255, 255, 0.5); + color: #fff; +} +.cs-gs-menu__feat-ic svg { + width: 18px; + height: 18px; +} +.cs-gs-menu__feat-name { + display: block; + color: #fff; + font-size: 16px; font-weight: 600; +} +.cs-gs-menu__feat-desc { + display: block; + margin-top: 8px; + color: rgba(255, 255, 255, 0.88); + font-size: 13px; + line-height: 1.5; +} +.cs-gs-menu__list { + display: flex; + flex-direction: column; + margin-top: 6px; +} +.cs-gs-menu__row { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 11px 10px; + border-radius: 9px; color: var(--cs-text); text-decoration: none; } -.md-header__buttons .cs-get-started__item-title { - color: var(--cs-nav-heading); +.cs-gs-menu__row:hover { + background: var(--cs-hover); } -/* The generic external-link "↗" is an a::after rendered as a block, so the column flex dropped it - onto its own line below the description. Suppress it and put the icon inline after the TITLE - text instead (external items only), like the landing. */ -.md-header__buttons .cs-get-started__menu a::after { - content: none !important; - display: none !important; +.cs-gs-menu__ic { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 34px; + height: 34px; + border-radius: 9px; + border: 0.5px solid var(--cs-text); + color: var(--cs-text); } -.md-header__buttons .cs-get-started__menu a[target="_blank"] .cs-get-started__item-title::after { - content: ""; - display: inline-block; - width: 13px; - height: 13px; - margin-left: 5px; - vertical-align: middle; - background-color: currentColor; - -webkit-mask: var(--cs-ext-icon) center / contain no-repeat; - mask: var(--cs-ext-icon) center / contain no-repeat; +.cs-gs-menu__ic svg { + width: 20px; + height: 20px; } -/* Description: 13px / 300 / full text color (light weight reads muted) / 16px line-height, like - the landing's secondary text. */ -.md-header__buttons .cs-get-started__item-desc { - margin-top: 1.5px; - font-size: 13px; - font-weight: 300; - line-height: 16px; +.cs-gs-menu__rbody { + flex: 1 1 auto; + min-width: 0; +} +.cs-gs-menu__name { + display: block; color: var(--cs-text); - white-space: normal; + font-size: 15px; + font-weight: 600; +} +.cs-gs-menu__desc { + display: block; + margin-top: 3px; + color: var(--cs-muted); + font-size: 13px; + line-height: 1.5; +} +/* On tablet/mobile the header buttons collapse; keep the dropdown from overflowing tiny screens. */ +@media (max-width: 76.1875em) { + .cs-gs-menu__popup { width: min(360px, calc(100vw - 32px)); } } -.md-header__buttons .cs-get-started__menu a:hover { - background: var(--cs-hover); +/* GitHub hover: outlined button gets a faint tint (landing.css forces black !important on primary + hover, so these need !important to win). */ +[data-md-color-primary=white] .md-header__buttons .md-button--primary.github:hover { + background: var(--cs-hover) !important; + border-color: var(--cs-border) !important; + color: var(--cs-text) !important; } /* Burger (desktop sidebar toggle) — far left, like /old. */ diff --git a/mkdocs/overrides/header-2.html b/mkdocs/overrides/header-2.html index f6887a7a3..fc6eb3e39 100644 --- a/mkdocs/overrides/header-2.html +++ b/mkdocs/overrides/header-2.html @@ -101,29 +101,47 @@ GitHub -
- - -
@@ -134,21 +152,25 @@ document.body.classList.toggle('cs-nav-collapsed'); // The content column reflows (792↔960), so re-align the right rail's TOC offset. window.dispatchEvent(new Event('resize')); - return; - } - var menu = document.querySelector('.cs-get-started__menu'); - if (!menu) return; - var triggers = document.querySelectorAll('.cs-get-started__main, .cs-get-started__toggle'); - // Both segments open the popup (no default navigation). - if (e.target.closest('.cs-get-started__main, .cs-get-started__toggle')) { - var hidden = menu.hasAttribute('hidden'); - menu.toggleAttribute('hidden', !hidden); - triggers.forEach(function (t) { t.setAttribute('aria-expanded', String(hidden)); }); - } else if (!e.target.closest('.cs-get-started__menu')) { - menu.setAttribute('hidden', ''); - triggers.forEach(function (t) { t.setAttribute('aria-expanded', 'false'); }); } }); + + // Live GitHub star count under the "Get started" popup's open-source icon (mirrors the + // landing). Best-effort: if the API is rate-limited or errors, the badge stays hidden. + var el = document.querySelector('[data-cs-gh-stars]'); + if (el) { + fetch('https://api.github.com/repos/dstackai/dstack') + .then(function (r) { return r.ok ? r.json() : null; }) + .then(function (d) { + if (!d || typeof d.stargazers_count !== 'number') return; + var n = d.stargazers_count, label; + if (n < 1000) label = String(n); + else { var k = n / 1000; label = (k >= 10 ? Math.round(k) : Number(k.toFixed(1))) + 'k'; } + el.textContent = label; + el.hidden = false; + }) + .catch(function () {}); + } })(); diff --git a/website/.gitignore b/website/.gitignore index de4d1f007..311e030bb 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -1,2 +1,3 @@ dist node_modules +.vite diff --git a/website/mkdocs/docs/reference/plugins/rest/rest_plugin_openapi.json b/website/mkdocs/docs/reference/plugins/rest/rest_plugin_openapi.json new file mode 100644 index 000000000..d5955fcbe --- /dev/null +++ b/website/mkdocs/docs/reference/plugins/rest/rest_plugin_openapi.json @@ -0,0 +1 @@ +{"openapi": "3.1.0", "info": {"title": "REST Plugin OpenAPI Spec", "version": "0.0.0"}, "servers": [{"url": "http://localhost:8000", "description": "Local server"}], "paths": {"/apply_policies/on_run_apply": {"post": {"summary": "On Run Apply", "operationId": "on_run_apply_apply_policies_on_run_apply_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/apply_policies/on_fleet_apply": {"post": {"summary": "On Fleet Apply", "operationId": "on_fleet_apply_apply_policies_on_fleet_apply_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/apply_policies/on_volume_apply": {"post": {"summary": "On Volume Apply", "operationId": "on_volume_apply_apply_policies_on_volume_apply_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/apply_policies/on_gateway_apply": {"post": {"summary": "On Gateway Apply", "operationId": "on_gateway_apply_apply_policies_on_gateway_apply_post", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyRequest"}}}, "required": true}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpecApplyResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}}, "components": {"schemas": {"ACMGatewayCertificateRequest": {"properties": {"type": {"type": "string", "enum": ["acm"], "title": "Type", "description": "Certificates by AWS Certificate Manager (ACM)", "default": "acm"}, "arn": {"type": "string", "title": "Arn", "description": "The ARN of the wildcard ACM certificate for the domain"}}, "additionalProperties": false, "type": "object", "required": ["arn"], "title": "ACMGatewayCertificateRequest"}, "AWSVolumeConfigurationRequest": {"properties": {"type": {"type": "string", "enum": ["volume"], "title": "Type", "default": "volume"}, "backend": {"type": "string", "enum": ["aws"], "title": "Backend", "description": "The volume backend", "default": "aws"}, "name": {"type": "string", "title": "Name", "description": "The volume name"}, "size": {"type": "number", "title": "Size", "description": "The volume size. Must be specified when creating new volumes"}, "auto_cleanup_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Auto Cleanup Duration", "description": "Time to wait after volume is no longer used by any job before deleting it. Defaults to keep the volume indefinitely. Use the value `off` or `-1` to disable auto-cleanup"}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the volume. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "volume_id": {"type": "string", "title": "Volume Id", "description": "The volume ID. Must be specified when registering external volumes"}, "region": {"type": "string", "title": "Region", "description": "The volume region"}, "availability_zone": {"type": "string", "title": "Availability Zone", "description": "The volume availability zone"}}, "additionalProperties": false, "type": "object", "required": ["region"], "title": "AWSVolumeConfigurationRequest"}, "AcceleratorVendor": {"type": "string", "enum": ["nvidia", "amd", "google", "intel", "tenstorrent"], "title": "AcceleratorVendor", "description": "An enumeration."}, "BackendType": {"type": "string", "enum": ["amddevcloud", "aws", "azure", "cloudrift", "crusoe", "cudo", "datacrunch", "digitalocean", "dstack", "gcp", "hotaisle", "jarvislabs", "kubernetes", "lambda", "remote", "nebius", "oci", "runpod", "tensordock", "vastai", "verda", "vultr"], "title": "BackendType", "description": "Attributes:\n AMDDEVCLOUD (BackendType): AMD Developer Cloud\n AWS (BackendType): Amazon Web Services\n AZURE (BackendType): Microsoft Azure\n CLOUDRIFT (BackendType): CloudRift\n CRUSOE (BackendType): Crusoe\n CUDO (BackendType): Cudo\n DATACRUNCH (BackendType): DataCrunch (for backward compatibility)\n DIGITALOCEAN (BackendType): DigitalOcean\n DSTACK (BackendType): dstack Sky\n GCP (BackendType): Google Cloud Platform\n HOTAISLE (BackendType): Hot Aisle\n JARVISLABS (BackendType): JarvisLabs\n KUBERNETES (BackendType): Kubernetes\n LAMBDA (BackendType): Lambda Cloud\n NEBIUS (BackendType): Nebius AI Cloud\n OCI (BackendType): Oracle Cloud Infrastructure\n RUNPOD (BackendType): Runpod Cloud\n TENSORDOCK (BackendType): TensorDock Marketplace\n VASTAI (BackendType): Vast.ai Marketplace\n VERDA (BackendType): Verda Cloud\n VULTR (BackendType): Vultr"}, "CPUArchitecture": {"type": "string", "enum": ["x86", "arm"], "title": "CPUArchitecture", "description": "An enumeration."}, "CPUSpecRequest": {"properties": {"arch": {"allOf": [{"$ref": "#/components/schemas/CPUArchitecture"}], "description": "The CPU architecture, one of: `x86`, `arm`"}, "count": {"anyOf": [{"$ref": "#/components/schemas/Range_int_"}, {"type": "integer"}, {"type": "string"}], "title": "Count", "description": "The number of CPU cores", "default": {"min": 2}}}, "additionalProperties": false, "type": "object", "title": "CPUSpecRequest"}, "CreationPolicy": {"type": "string", "enum": ["reuse", "reuse-or-create"], "title": "CreationPolicy", "description": "An enumeration."}, "DevEnvironmentConfigurationRequest": {"properties": {"ide": {"anyOf": [{"type": "string", "enum": ["vscode"]}, {"type": "string", "enum": ["cursor"]}, {"type": "string", "enum": ["windsurf"]}, {"type": "string", "enum": ["zed"]}], "title": "Ide", "description": "The IDE to pre-install. Supported values include `vscode`, `cursor`, `windsurf`, and `zed`. Defaults to no IDE (SSH only)"}, "version": {"type": "string", "title": "Version", "description": "The version of the IDE. For `windsurf`, the version is in the format `version@commit`"}, "init": {"items": {"type": "string"}, "type": "array", "title": "Init", "description": "The shell commands to run on startup", "default": []}, "inactivity_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Inactivity Duration", "description": "The maximum amount of time the dev environment can be inactive (e.g., `2h`, `1d`, etc). After it elapses, the dev environment is automatically stopped. Inactivity is defined as the absence of SSH connections to the dev environment, including VS Code connections, `ssh ` shells, and attached `dstack apply` or `dstack attach` commands. Use `off` for unlimited duration. Can be updated in-place. Defaults to `off`"}, "ports": {"items": {"anyOf": [{"type": "integer", "maximum": 65536.0, "exclusiveMinimum": 0.0}, {"type": "string", "pattern": "^(?:[0-9]+|\\*):[0-9]+$"}, {"$ref": "#/components/schemas/PortMappingRequest"}]}, "type": "array", "title": "Ports", "description": "Port numbers/mapping to expose", "default": []}, "type": {"type": "string", "enum": ["dev-environment"], "title": "Type", "default": "dev-environment"}, "name": {"type": "string", "title": "Name", "description": "The run name. If not specified, a random name is generated"}, "image": {"type": "string", "title": "Image", "description": "The name of the Docker image to run"}, "user": {"type": "string", "title": "User", "description": "The user inside the container, `user_name_or_id[:group_name_or_id]` (e.g., `ubuntu`, `1000:1000`). Defaults to the default user from the `image`"}, "privileged": {"type": "boolean", "title": "Privileged", "description": "Run the container in privileged mode", "default": false}, "entrypoint": {"type": "string", "title": "Entrypoint", "description": "The Docker entrypoint"}, "working_dir": {"type": "string", "title": "Working Dir", "description": "The absolute path to the working directory inside the container. Defaults to the `image`'s default working directory"}, "home_dir": {"type": "string", "title": "Home Dir", "default": "/root"}, "registry_auth": {"allOf": [{"$ref": "#/components/schemas/RegistryAuthRequest"}], "title": "Registry Auth", "description": "Credentials for pulling a private Docker image"}, "python": {"allOf": [{"$ref": "#/components/schemas/PythonVersion"}], "description": "The major version of Python. Mutually exclusive with `image` and `docker`"}, "nvcc": {"type": "boolean", "title": "Nvcc", "description": "Use image with NVIDIA CUDA Compiler (NVCC) included. Mutually exclusive with `image` and `docker`"}, "single_branch": {"type": "boolean", "title": "Single Branch", "description": "Whether to clone and track only the current branch or all remote branches. Relevant only when using remote Git repos. Defaults to `false` for dev environments and to `true` for tasks and services"}, "env": {"allOf": [{"$ref": "#/components/schemas/Env"}], "title": "Env", "description": "The mapping or the list of environment variables", "default": {"__root__": {}}}, "shell": {"type": "string", "title": "Shell", "description": "The shell used to run commands. Allowed values are `sh`, `bash`, or an absolute path, e.g., `/usr/bin/zsh`. Defaults to `/bin/sh` if the `image` is specified, `/bin/bash` otherwise"}, "resources": {"allOf": [{"$ref": "#/components/schemas/ResourcesSpecRequest"}], "title": "Resources", "description": "The resources requirements to run the configuration", "default": {"cpu": {"min": 2}, "memory": {"min": 8.0}, "gpu": {"count": {"min": 0}}, "disk": {"size": {"min": 100.0}}}}, "priority": {"type": "integer", "maximum": 100.0, "minimum": 0.0, "title": "Priority", "description": "The priority of the run, an integer between `0` and `100`. `dstack` tries to provision runs with higher priority first. Defaults to `0`"}, "volumes": {"items": {"anyOf": [{"$ref": "#/components/schemas/VolumeMountPointRequest"}, {"$ref": "#/components/schemas/InstanceMountPointRequest"}, {"type": "string"}]}, "type": "array", "title": "Volumes", "description": "The volumes mount points", "default": []}, "docker": {"type": "boolean", "title": "Docker", "description": "Use Docker inside the container. Mutually exclusive with `image`, `python`, and `nvcc`. Overrides `privileged`"}, "repos": {"items": {"$ref": "#/components/schemas/RepoSpecRequest"}, "type": "array", "title": "Repos", "description": "The list of Git repos", "default": []}, "files": {"items": {"anyOf": [{"$ref": "#/components/schemas/FilePathMappingRequest"}, {"type": "string"}]}, "type": "array", "title": "Files", "description": "The local to container file path mappings", "default": []}, "setup": {"items": {"type": "string"}, "type": "array", "title": "Setup", "default": []}, "backends": {"items": {"$ref": "#/components/schemas/BackendType"}, "type": "array", "description": "The backends to consider for provisioning (e.g., `[aws, gcp]`)"}, "regions": {"items": {"type": "string"}, "type": "array", "title": "Regions", "description": "The regions to consider for provisioning (e.g., `[eu-west-1, us-west4, westeurope]`)"}, "availability_zones": {"items": {"type": "string"}, "type": "array", "title": "Availability Zones", "description": "The availability zones to consider for provisioning (e.g., `[eu-west-1a, us-west4-a]`)"}, "instance_types": {"items": {"type": "string"}, "type": "array", "title": "Instance Types", "description": "The cloud-specific instance types to consider for provisioning (e.g., `[g6e.24xlarge, n1-standard-4]`)"}, "reservation": {"type": "string", "title": "Reservation", "description": "The existing reservation to use for instance provisioning. Supports AWS Capacity Reservations, AWS Capacity Blocks, and GCP reservations"}, "spot_policy": {"allOf": [{"$ref": "#/components/schemas/SpotPolicy"}], "description": "The policy for provisioning spot or on-demand instances: `spot`, `on-demand`, `auto`. Defaults to `on-demand`"}, "retry": {"anyOf": [{"$ref": "#/components/schemas/ProfileRetryRequest"}, {"type": "boolean"}], "title": "Retry", "description": "The policy for resubmitting the run. Defaults to `false`"}, "max_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Max Duration", "description": "The maximum duration of a run (e.g., `2h`, `1d`, etc) in a running state, excluding provisioning and pulling. After it elapses, the run is automatically stopped. Use `off` for unlimited duration. Defaults to `off`"}, "stop_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Stop Duration", "description": "The maximum duration of a run graceful stopping. After it elapses, the run is automatically forced stopped. This includes force detaching volumes used by the run. Use `off` for unlimited duration. Defaults to `5m`"}, "max_price": {"type": "number", "exclusiveMinimum": 0.0, "title": "Max Price", "description": "The maximum instance price per hour, in dollars"}, "creation_policy": {"allOf": [{"$ref": "#/components/schemas/CreationPolicy"}], "description": "The policy for using instances from fleets: `reuse`, `reuse-or-create`. Defaults to `reuse-or-create`"}, "idle_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Idle Duration", "description": "Time to wait before terminating idle instances. When the run reuses an existing fleet instance, the fleet's `idle_duration` applies. When the run provisions a new instance, the shorter of the fleet's and run's values is used. Defaults to `5m` for runs and `3d` for fleets. Use `off` for unlimited duration. Only applied for VM-based backends"}, "utilization_policy": {"allOf": [{"$ref": "#/components/schemas/UtilizationPolicyRequest"}], "title": "Utilization Policy", "description": "Run termination policy based on utilization"}, "startup_order": {"allOf": [{"$ref": "#/components/schemas/StartupOrder"}], "description": "The order in which master and workers jobs are started: `any`, `master-first`, `workers-first`. Defaults to `any`"}, "stop_criteria": {"allOf": [{"$ref": "#/components/schemas/StopCriteria"}], "description": "The criteria determining when a multi-node run should be considered finished: `all-done`, `master-done`. Defaults to `all-done`"}, "schedule": {"allOf": [{"$ref": "#/components/schemas/ScheduleRequest"}], "title": "Schedule", "description": "The schedule for starting the run at specified time"}, "fleets": {"items": {"anyOf": [{"$ref": "#/components/schemas/EntityReferenceRequest"}, {"type": "string"}]}, "type": "array", "title": "Fleets", "description": "The fleets considered for reuse. For fleets owned by the current project, specify fleet names. For imported fleets, specify `/`"}, "instances": {"items": {"anyOf": [{"$ref": "#/components/schemas/InstanceNameSelectorRequest"}, {"$ref": "#/components/schemas/InstanceHostnameSelectorRequest"}, {"$ref": "#/components/schemas/FleetInstanceSelectorRequest"}, {"type": "string", "minLength": 1}]}, "type": "array", "minItems": 1, "title": "Instances", "description": "The specific fleet instances to consider for reuse. Each value can be an instance name string, or an object with `name`, `hostname`, or `fleet` and `instance`. When set, the run is only placed on matching existing instances."}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the resource. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "backend_options": {"items": {"$ref": "#/components/schemas/VastAIProfileOptions"}, "type": "array", "title": "Backend Options", "description": "Backend-specific options, applied only to offers from that backend"}}, "additionalProperties": false, "type": "object", "title": "DevEnvironmentConfigurationRequest"}, "DiskSpecRequest": {"properties": {"size": {"anyOf": [{"$ref": "#/components/schemas/Range_Memory_"}, {"type": "integer"}, {"type": "string"}], "title": "Size", "description": "Disk size"}}, "additionalProperties": false, "type": "object", "required": ["size"], "title": "DiskSpecRequest"}, "EntityReferenceRequest": {"properties": {"project": {"type": "string", "title": "Project", "description": "The project name. If unspecified, refers to the current project"}, "name": {"type": "string", "title": "Name", "description": "The entity name"}}, "additionalProperties": false, "type": "object", "required": ["name"], "title": "EntityReferenceRequest", "description": "Cross-project entity reference."}, "Env": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"additionalProperties": {"anyOf": [{"type": "string"}, {"$ref": "#/components/schemas/EnvSentinelRequest"}]}, "type": "object"}], "title": "Env", "description": "Env represents a mapping of process environment variables, as in environ(7).\nEnvironment values may be omitted, in that case the :class:`EnvSentinel`\nobject is used as a placeholder.\n\nTo create an instance from a `dict[str, str]` or a `list[str]` use pydantic's\n:meth:`BaseModel.parse_obj(dict | list)` method.\n\nNB: this is *NOT* a CoreModel, pydantic-duality, which is used as a base\nfor the CoreModel, doesn't play well with custom root models.", "default": {}}, "EnvSentinelRequest": {"properties": {"key": {"type": "string", "title": "Key"}}, "additionalProperties": false, "type": "object", "required": ["key"], "title": "EnvSentinelRequest"}, "FileArchiveMappingRequest": {"properties": {"id": {"type": "string", "format": "uuid", "title": "Id", "description": "The File archive ID"}, "path": {"type": "string", "title": "Path", "description": "The path in the container"}}, "additionalProperties": false, "type": "object", "required": ["id", "path"], "title": "FileArchiveMappingRequest"}, "FilePathMappingRequest": {"properties": {"local_path": {"type": "string", "title": "Local Path", "description": "The path on the user's machine. Relative paths are resolved relative to the parent directory of the the configuration file"}, "path": {"type": "string", "title": "Path", "description": "The path in the container. Relative paths are resolved relative to the working directory"}}, "additionalProperties": false, "type": "object", "required": ["local_path", "path"], "title": "FilePathMappingRequest"}, "FleetConfigurationRequest": {"properties": {"type": {"type": "string", "enum": ["fleet"], "title": "Type", "default": "fleet"}, "name": {"type": "string", "title": "Name", "description": "The fleet name"}, "placement": {"allOf": [{"$ref": "#/components/schemas/InstanceGroupPlacement"}], "description": "The placement of instances: `any` or `cluster`"}, "blocks": {"anyOf": [{"type": "string", "enum": ["auto"]}, {"type": "integer", "minimum": 1.0}], "title": "Blocks", "description": "The amount of blocks to split the instance into, a number or `auto`. `auto` means as many as possible. The number of GPUs and CPUs must be divisible by the number of blocks. Defaults to `1`, i.e. do not split", "default": 1}, "nodes": {"anyOf": [{"$ref": "#/components/schemas/FleetNodesSpecRequest"}, {"type": "integer"}, {"type": "string"}], "title": "Nodes", "description": "The number of instances"}, "reservation": {"type": "string", "title": "Reservation", "description": "The existing reservation to use for instance provisioning. Supports AWS Capacity Reservations, AWS Capacity Blocks, and GCP reservations"}, "resources": {"allOf": [{"$ref": "#/components/schemas/ResourcesSpecRequest"}], "title": "Resources", "description": "The resources requirements"}, "backends": {"items": {"$ref": "#/components/schemas/BackendType"}, "type": "array", "description": "The backends to consider for provisioning (e.g., `[aws, gcp]`)"}, "regions": {"items": {"type": "string"}, "type": "array", "title": "Regions", "description": "The regions to consider for provisioning (e.g., `[eu-west-1, us-west4, westeurope]`)"}, "availability_zones": {"items": {"type": "string"}, "type": "array", "title": "Availability Zones", "description": "The availability zones to consider for provisioning (e.g., `[eu-west-1a, us-west4-a]`)"}, "instance_types": {"items": {"type": "string"}, "type": "array", "title": "Instance Types", "description": "The cloud-specific instance types to consider for provisioning (e.g., `[g6e.24xlarge, n1-standard-4]`)"}, "spot_policy": {"allOf": [{"$ref": "#/components/schemas/SpotPolicy"}], "description": "The policy for provisioning spot or on-demand instances: `spot`, `on-demand`, `auto`. Defaults to `on-demand`"}, "retry": {"anyOf": [{"$ref": "#/components/schemas/ProfileRetryRequest"}, {"type": "boolean"}], "title": "Retry", "description": "The policy for provisioning retry. Defaults to `false`"}, "max_price": {"type": "number", "exclusiveMinimum": 0.0, "title": "Max Price", "description": "The maximum instance price per hour, in dollars"}, "idle_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Idle Duration", "description": "Time to wait before terminating idle instances. Instances are not terminated if the fleet is already at `nodes.min`. Defaults to `5m` for runs and `3d` for fleets. Use `off` for unlimited duration"}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the resource. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "backend_options": {"items": {"$ref": "#/components/schemas/VastAIProfileOptions"}, "type": "array", "title": "Backend Options", "description": "Backend-specific options, applied only to offers from that backend"}, "ssh_config": {"allOf": [{"$ref": "#/components/schemas/SSHParamsRequest"}], "title": "Ssh Config", "description": "The parameters for adding instances via SSH"}, "env": {"allOf": [{"$ref": "#/components/schemas/Env"}], "title": "Env", "description": "The mapping or the list of environment variables", "default": {"__root__": {}}}}, "additionalProperties": false, "type": "object", "title": "FleetConfigurationRequest"}, "FleetInstanceSelectorRequest": {"properties": {"fleet": {"anyOf": [{"$ref": "#/components/schemas/EntityReferenceRequest"}, {"type": "string", "minLength": 1}], "title": "Fleet", "description": "The fleet reference. For fleets owned by the current project, specify the fleet name. For a fleet from another project, specify `/` or an object with `project` and `name`."}, "instance": {"type": "integer", "minimum": 0.0, "title": "Instance", "description": "The fleet instance number"}}, "additionalProperties": false, "type": "object", "required": ["fleet", "instance"], "title": "FleetInstanceSelectorRequest"}, "FleetNodesSpecRequest": {"properties": {"min": {"type": "integer", "title": "Min", "description": "The minimum number of instances to maintain in the fleet"}, "target": {"type": "integer", "title": "Target", "description": "The number of instances to provision on fleet apply. `min` <= `target` <= `max` Defaults to `min`"}, "max": {"type": "integer", "title": "Max", "description": "The maximum number of instances allowed in the fleet. Unlimited if not specified"}}, "additionalProperties": false, "type": "object", "required": ["min", "target"], "title": "FleetNodesSpecRequest"}, "FleetSpecRequest": {"properties": {"configuration": {"$ref": "#/components/schemas/FleetConfigurationRequest"}, "configuration_path": {"type": "string", "title": "Configuration Path"}, "profile": {"$ref": "#/components/schemas/ProfileRequest"}, "autocreated": {"type": "boolean", "title": "Autocreated", "default": false}}, "additionalProperties": false, "type": "object", "required": ["configuration", "profile"], "title": "FleetSpecRequest"}, "GCPVolumeConfigurationRequest": {"properties": {"type": {"type": "string", "enum": ["volume"], "title": "Type", "default": "volume"}, "backend": {"type": "string", "enum": ["gcp"], "title": "Backend", "description": "The volume backend", "default": "gcp"}, "name": {"type": "string", "title": "Name", "description": "The volume name"}, "size": {"type": "number", "title": "Size", "description": "The volume size. Must be specified when creating new volumes"}, "auto_cleanup_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Auto Cleanup Duration", "description": "Time to wait after volume is no longer used by any job before deleting it. Defaults to keep the volume indefinitely. Use the value `off` or `-1` to disable auto-cleanup"}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the volume. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "volume_id": {"type": "string", "title": "Volume Id", "description": "The volume ID. Must be specified when registering external volumes"}, "region": {"type": "string", "title": "Region", "description": "The volume region"}, "availability_zone": {"type": "string", "title": "Availability Zone", "description": "The volume availability zone"}}, "additionalProperties": false, "type": "object", "required": ["region"], "title": "GCPVolumeConfigurationRequest"}, "GPUSpecRequest": {"properties": {"vendor": {"allOf": [{"$ref": "#/components/schemas/AcceleratorVendor"}], "description": "The vendor of the GPU/accelerator, one of: `nvidia`, `amd`, `google` (alias: `tpu`), `intel`"}, "name": {"anyOf": [{"type": "array"}, {"type": "string"}], "items": {"type": "string"}, "title": "Name", "description": "The name of the GPU (e.g., `A100` or `H100`)"}, "count": {"anyOf": [{"$ref": "#/components/schemas/Range_int_"}, {"type": "integer"}, {"type": "string"}], "title": "Count", "description": "The number of GPUs", "default": {"min": 1}}, "memory": {"anyOf": [{"$ref": "#/components/schemas/Range_Memory_"}, {"type": "integer"}, {"type": "string"}], "title": "Memory", "description": "The RAM size (e.g., `16GB`). Can be set to a range (e.g. `16GB..`, or `16GB..80GB`)"}, "total_memory": {"anyOf": [{"$ref": "#/components/schemas/Range_Memory_"}, {"type": "integer"}, {"type": "string"}], "title": "Total Memory", "description": "The total RAM size (e.g., `32GB`). Can be set to a range (e.g. `16GB..`, or `16GB..80GB`)"}, "compute_capability": {"items": {}, "type": "array", "title": "Compute Capability", "description": "The minimum compute capability of the GPU (e.g., `7.5`)"}}, "additionalProperties": false, "type": "object", "title": "GPUSpecRequest"}, "GatewayConfigurationRequest": {"properties": {"type": {"type": "string", "enum": ["gateway"], "title": "Type", "default": "gateway"}, "name": {"type": "string", "title": "Name", "description": "The gateway name"}, "default": {"type": "boolean", "title": "Default", "description": "Make the gateway default", "default": false}, "backend": {"allOf": [{"$ref": "#/components/schemas/BackendType"}], "description": "The gateway backend"}, "region": {"type": "string", "title": "Region", "description": "The gateway region"}, "instance_type": {"type": "string", "minLength": 1, "title": "Instance Type", "description": "Backend-specific instance type to use for the gateway instance. Omit to use the backend's default, which is typically a small non-GPU instance"}, "router": {"allOf": [{"$ref": "#/components/schemas/SGLangGatewayRouterConfigRequest"}], "title": "Router", "description": "The router configuration for this gateway. E.g. `{ type: sglang, policy: round_robin }`."}, "domain": {"type": "string", "title": "Domain", "description": "The gateway wildcard domain name, e.g. `example.com`. Service domain names are constructed as `./`"}, "instances": {"items": {"anyOf": [{"$ref": "#/components/schemas/InstanceNameSelectorRequest"}, {"$ref": "#/components/schemas/InstanceHostnameSelectorRequest"}, {"$ref": "#/components/schemas/FleetInstanceSelectorRequest"}, {"type": "string", "minLength": 1}]}, "type": "array", "minItems": 1, "title": "Instances", "description": "The specific fleet instances to consider for reuse. Each value can be an instance name string, or an object with `name`, `hostname`, or `fleet` and `instance`. When set, the run is only placed on matching existing instances."}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the resource. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "backend_options": {"items": {"$ref": "#/components/schemas/VastAIProfileOptions"}, "type": "array", "title": "Backend Options", "description": "Backend-specific options, applied only to offers from that backend"}, "name": {"type": "string", "title": "Name", "description": "The name of the profile that can be passed as `--profile` to `dstack apply`", "default": ""}, "default": {"type": "boolean", "title": "Default", "description": "If set to true, `dstack apply` will use this profile by default.", "default": false}}, "additionalProperties": false, "type": "object", "title": "ProfileRequest"}, "ProfileRetryRequest": {"properties": {"on_events": {"items": {"$ref": "#/components/schemas/RetryEvent"}, "type": "array", "description": "The list of events that should be handled with retry. Supported events are `no-capacity`, `interruption`, `error`. Omit to retry on all events"}, "duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Duration", "description": "The maximum period of retrying the run, e.g., `4h` or `1d`. The period is calculated as a run age for `no-capacity` event and as a time passed since the last `interruption` and `error` for `interruption` and `error` events."}}, "additionalProperties": false, "type": "object", "title": "ProfileRetryRequest"}, "PythonVersion": {"type": "string", "enum": ["3.9", "3.10", "3.11", "3.12", "3.13"], "title": "PythonVersion", "description": "An enumeration."}, "Range_Memory_": {"properties": {"min": {"type": "number", "title": "Min"}, "max": {"type": "number", "title": "Max"}}, "additionalProperties": false, "type": "object", "title": "Range[Memory]"}, "Range_int_": {"properties": {"min": {"type": "integer", "title": "Min"}, "max": {"type": "integer", "title": "Max"}}, "additionalProperties": false, "type": "object", "title": "Range[int]"}, "RateLimitRequest": {"properties": {"prefix": {"type": "string", "maxLength": 4094, "pattern": "^/[^\\s\\\\{}]*$", "title": "Prefix", "description": "URL path prefix to which this limit is applied. If an incoming request matches several prefixes, the longest prefix is applied", "default": "/"}, "key": {"oneOf": [{"$ref": "#/components/schemas/IPAddressPartitioningKeyRequest"}, {"$ref": "#/components/schemas/HeaderPartitioningKeyRequest"}], "title": "Key", "description": "The partitioning key. Each incoming request belongs to a partition and rate limits are applied per partition. Defaults to partitioning by client IP address", "default": {"type": "ip_address"}, "discriminator": {"propertyName": "type", "mapping": {"ip_address": "#/components/schemas/IPAddressPartitioningKeyRequest", "header": "#/components/schemas/HeaderPartitioningKeyRequest"}}}, "rps": {"type": "number", "maximum": 1.5372286728091293e+17, "minimum": 0.016666666666666666, "title": "Rps", "description": "Max allowed number of requests per second. Requests are tracked at millisecond granularity. For example, `rps: 10` means at most 1 request per 100ms"}, "burst": {"type": "integer", "maximum": 9.223372036854776e+18, "minimum": 0.0, "title": "Burst", "description": "Max number of requests that can be passed to the service ahead of the rate limit", "default": 0}}, "additionalProperties": false, "type": "object", "required": ["rps"], "title": "RateLimitRequest"}, "RegistryAuthRequest": {"properties": {"username": {"type": "string", "title": "Username", "description": "The username"}, "password": {"type": "string", "title": "Password", "description": "The password or access token"}}, "additionalProperties": false, "type": "object", "required": ["username", "password"], "title": "RegistryAuthRequest", "description": "Credentials for pulling a private Docker image.\n\nAttributes:\n username (str): The username\n password (str): The password or access token"}, "RemoteRunRepoDataRequest": {"properties": {"repo_type": {"type": "string", "enum": ["remote"], "title": "Repo Type", "default": "remote"}, "repo_name": {"type": "string", "title": "Repo Name"}, "repo_branch": {"type": "string", "title": "Repo Branch"}, "repo_hash": {"type": "string", "title": "Repo Hash"}, "repo_diff": {"type": "string", "format": "binary", "title": "Repo Diff"}, "repo_config_name": {"type": "string", "title": "Repo Config Name"}, "repo_config_email": {"type": "string", "title": "Repo Config Email"}}, "additionalProperties": false, "type": "object", "required": ["repo_name"], "title": "RemoteRunRepoDataRequest"}, "ReplicaGroupRequest": {"properties": {"name": {"type": "string", "title": "Name", "description": "The name of the replica group. If not provided, defaults to '0', '1', etc. based on position."}, "count": {"allOf": [{"$ref": "#/components/schemas/Range_int_"}], "title": "Count", "description": "The number of replicas. Can be a number (e.g. `2`) or a range (`0..4` or `1..8`). If it's a range, the `scaling` property is required"}, "scaling": {"allOf": [{"$ref": "#/components/schemas/ScalingSpecRequest"}], "title": "Scaling", "description": "The auto-scaling rules. Required if `count` is set to a range"}, "resources": {"allOf": [{"$ref": "#/components/schemas/ResourcesSpecRequest"}], "title": "Resources", "description": "The resources requirements for replicas in this group", "default": {"cpu": {"min": 2}, "memory": {"min": 8.0}, "gpu": {"count": {"min": 0}}, "disk": {"size": {"min": 100.0}}}}, "spot_policy": {"allOf": [{"$ref": "#/components/schemas/SpotPolicy"}], "description": "The policy for provisioning spot or on-demand instances for replicas in this group: `spot`, `on-demand`, `auto`"}, "reservation": {"type": "string", "title": "Reservation", "description": "The existing reservation to use for replicas in this group. Supports AWS Capacity Reservations, AWS Capacity Blocks, and GCP reservations"}, "commands": {"items": {"type": "string"}, "type": "array", "title": "Commands", "description": "The shell commands to run for replicas in this group", "default": []}, "image": {"type": "string", "title": "Image", "description": "The name of the Docker image to run for replicas in this group. Mutually exclusive with group-level `docker` and `python`."}, "python": {"allOf": [{"$ref": "#/components/schemas/PythonVersion"}], "description": "The major version of Python for replicas in this group. Mutually exclusive with group-level `image` and `docker`."}, "nvcc": {"type": "boolean", "title": "Nvcc", "description": "Use the image with NVIDIA CUDA Compiler (NVCC) included for replicas in this group. Mutually exclusive with group-level `docker`."}, "docker": {"type": "boolean", "title": "Docker", "description": "Use the docker-in-docker image for this group (injects `start-dockerd` and runs privileged). Mutually exclusive with group-level `image`, `python`, and `nvcc`."}, "privileged": {"type": "boolean", "title": "Privileged", "description": "Run replicas in this group in privileged mode."}, "router": {"allOf": [{"$ref": "#/components/schemas/ReplicaGroupRouterConfigRequest"}], "title": "Router", "description": "When set, replicas in this group run the in-service HTTP router (e.g. SGLang)."}}, "additionalProperties": false, "type": "object", "required": ["count"], "title": "ReplicaGroupRequest"}, "ReplicaGroupRouterConfigRequest": {"properties": {"type": {"type": "string", "enum": ["sglang", "dynamo"], "title": "Type", "description": "The router implementation for this replica group. `sglang` runs the SGLang router and dstack syncs worker URLs to it. `dynamo` runs the NVIDIA Dynamo frontend, which discovers workers itself via etcd/NATS.", "default": "sglang"}}, "additionalProperties": false, "type": "object", "title": "ReplicaGroupRouterConfigRequest"}, "RepoExistsAction": {"type": "string", "enum": ["error", "skip"], "title": "RepoExistsAction", "description": "An enumeration."}, "RepoSpecRequest": {"properties": {"local_path": {"type": "string", "title": "Local Path", "description": "The path to the Git repo on the user's machine. Relative paths are resolved relative to the parent directory of the the configuration file. Mutually exclusive with `url`"}, "url": {"type": "string", "title": "Url", "description": "The Git repo URL. Mutually exclusive with `local_path`"}, "branch": {"type": "string", "title": "Branch", "description": "The repo branch. Defaults to the active branch for local paths and the default branch for URLs"}, "hash": {"type": "string", "title": "Hash", "description": "The commit hash"}, "path": {"type": "string", "title": "Path", "description": "The repo path inside the run container. Relative paths are resolved relative to the working directory", "default": "."}, "if_exists": {"allOf": [{"$ref": "#/components/schemas/RepoExistsAction"}], "description": "The action to be taken if `path` exists and is not empty. One of: `error`, `skip`", "default": "error"}}, "additionalProperties": false, "type": "object", "title": "RepoSpecRequest"}, "ResourcesSpecRequest": {"properties": {"cpu": {"anyOf": [{"$ref": "#/components/schemas/CPUSpecRequest"}, {"$ref": "#/components/schemas/Range_int_"}, {"type": "integer"}, {"type": "string"}], "title": "Cpu", "description": "The CPU requirements", "default": {"count": {"min": 2}}}, "memory": {"anyOf": [{"$ref": "#/components/schemas/Range_Memory_"}, {"type": "integer"}, {"type": "string"}], "title": "Memory", "description": "The RAM size (e.g., `8GB`)", "default": {"min": 8.0}}, "shm_size": {"anyOf": [{"type": "number"}, {"type": "integer"}, {"type": "string"}], "title": "Shm Size", "description": "The size of shared memory (e.g., `8GB`). If you are using parallel communicating processes (e.g., dataloaders in PyTorch), you may need to configure this"}, "gpu": {"anyOf": [{"$ref": "#/components/schemas/GPUSpecRequest"}, {"type": "integer"}, {"type": "string"}], "title": "Gpu", "description": "The GPU requirements", "default": {"count": {"min": 0}}}, "disk": {"anyOf": [{"$ref": "#/components/schemas/DiskSpecRequest"}, {"type": "integer"}, {"type": "string"}], "title": "Disk", "description": "The disk resources", "default": {"size": {"min": 100.0}}}}, "additionalProperties": false, "type": "object", "title": "ResourcesSpecRequest"}, "RetryEvent": {"type": "string", "enum": ["no-capacity", "interruption", "error"], "title": "RetryEvent", "description": "An enumeration."}, "RunSpecRequest": {"properties": {"run_name": {"type": "string", "title": "Run Name", "description": "The run name. If not set, the run name is generated automatically."}, "repo_id": {"type": "string", "title": "Repo Id", "description": "Same `repo_id` that is specified when initializing the repo by calling the `/api/project/{project_name}/repos/init` endpoint. If not specified, a default virtual repo is used."}, "repo_data": {"oneOf": [{"$ref": "#/components/schemas/RemoteRunRepoDataRequest"}, {"$ref": "#/components/schemas/LocalRunRepoDataRequest"}, {"$ref": "#/components/schemas/VirtualRunRepoDataRequest"}], "title": "Repo Data", "description": "The repo data such as the current branch and commit.", "discriminator": {"propertyName": "repo_type", "mapping": {"remote": "#/components/schemas/RemoteRunRepoDataRequest", "local": "#/components/schemas/LocalRunRepoDataRequest", "virtual": "#/components/schemas/VirtualRunRepoDataRequest"}}}, "repo_code_hash": {"type": "string", "title": "Repo Code Hash", "description": "The hash of the repo diff. Can be omitted if there is no repo diff."}, "repo_dir": {"type": "string", "title": "Repo Dir", "description": "The repo path inside the container. Relative paths are resolved relative to the working directory."}, "file_archives": {"items": {"$ref": "#/components/schemas/FileArchiveMappingRequest"}, "type": "array", "title": "File Archives", "description": "The list of file archive ID to container path mappings.", "default": []}, "working_dir": {"type": "string", "title": "Working Dir"}, "configuration_path": {"type": "string", "title": "Configuration Path", "description": "The path to the run configuration YAML file. It can be omitted when using the programmatic API."}, "configuration": {"oneOf": [{"$ref": "#/components/schemas/DevEnvironmentConfigurationRequest"}, {"$ref": "#/components/schemas/TaskConfigurationRequest"}, {"$ref": "#/components/schemas/ServiceConfigurationRequest"}], "title": "Configuration", "discriminator": {"propertyName": "type", "mapping": {"dev-environment": "#/components/schemas/DevEnvironmentConfigurationRequest", "task": "#/components/schemas/TaskConfigurationRequest", "service": "#/components/schemas/ServiceConfigurationRequest"}}}, "profile": {"allOf": [{"$ref": "#/components/schemas/ProfileRequest"}], "title": "Profile", "description": "The profile parameters"}, "ssh_key_pub": {"type": "string", "title": "Ssh Key Pub", "description": "The contents of the SSH public key that will be used to connect to the run. Can be empty only before the run is submitted."}}, "additionalProperties": false, "type": "object", "required": ["configuration"], "title": "RunSpecRequest"}, "RunpodVolumeConfigurationRequest": {"properties": {"type": {"type": "string", "enum": ["volume"], "title": "Type", "default": "volume"}, "backend": {"type": "string", "enum": ["runpod"], "title": "Backend", "description": "The volume backend", "default": "runpod"}, "name": {"type": "string", "title": "Name", "description": "The volume name"}, "size": {"type": "number", "title": "Size", "description": "The volume size. Must be specified when creating new volumes"}, "auto_cleanup_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Auto Cleanup Duration", "description": "Time to wait after volume is no longer used by any job before deleting it. Defaults to keep the volume indefinitely. Use the value `off` or `-1` to disable auto-cleanup"}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the volume. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "volume_id": {"type": "string", "title": "Volume Id", "description": "The volume ID. Must be specified when registering external volumes"}, "region": {"type": "string", "title": "Region", "description": "The volume region"}, "availability_zone": {"type": "string", "title": "Availability Zone"}}, "additionalProperties": false, "type": "object", "required": ["region"], "title": "RunpodVolumeConfigurationRequest"}, "SGLangGatewayRouterConfigRequest": {"properties": {"type": {"type": "string", "enum": ["sglang"], "title": "Type", "description": "The router type enabled on this gateway.", "default": "sglang"}, "policy": {"type": "string", "enum": ["random", "round_robin", "cache_aware", "power_of_two"], "title": "Policy", "description": "The routing policy. Deprecated: prefer setting policy in the service's router config. Options: `random`, `round_robin`, `cache_aware`, `power_of_two`", "default": "cache_aware"}}, "additionalProperties": false, "type": "object", "title": "SGLangGatewayRouterConfigRequest", "description": "Gateway-level router configuration. type and policy only. pd_disaggregation is service-level."}, "SGLangServiceRouterConfigRequest": {"properties": {"type": {"type": "string", "enum": ["sglang"], "title": "Type", "description": "The router type", "default": "sglang"}, "policy": {"type": "string", "enum": ["random", "round_robin", "cache_aware", "power_of_two"], "title": "Policy", "description": "The routing policy. Options: `random`, `round_robin`, `cache_aware`, `power_of_two`", "default": "cache_aware"}, "pd_disaggregation": {"type": "boolean", "title": "Pd Disaggregation", "description": "Enable PD disaggregation mode for the SGLang router", "default": false}}, "additionalProperties": false, "type": "object", "title": "SGLangServiceRouterConfigRequest"}, "SSHHostParamsRequest": {"properties": {"hostname": {"type": "string", "title": "Hostname", "description": "The IP address or domain to connect to"}, "port": {"type": "integer", "title": "Port", "description": "The SSH port to connect to for this host"}, "user": {"type": "string", "title": "User", "description": "The user to log in with for this host"}, "identity_file": {"type": "string", "title": "Identity File", "description": "The private key to use for this host"}, "proxy_jump": {"allOf": [{"$ref": "#/components/schemas/SSHProxyParamsRequest"}], "title": "Proxy Jump", "description": "The SSH proxy configuration for this host"}, "internal_ip": {"type": "string", "title": "Internal Ip", "description": "The internal IP of the host used for communication inside the cluster. If not specified, `dstack` will use the IP address from `network` or from the first found internal network."}, "ssh_key": {"$ref": "#/components/schemas/SSHKeyRequest"}, "blocks": {"anyOf": [{"type": "string", "enum": ["auto"]}, {"type": "integer", "minimum": 1.0}], "title": "Blocks", "description": "The amount of blocks to split the instance into, a number or `auto`. `auto` means as many as possible. The number of GPUs and CPUs must be divisible by the number of blocks. Defaults to the top-level `blocks` value"}}, "additionalProperties": false, "type": "object", "required": ["hostname"], "title": "SSHHostParamsRequest"}, "SSHKeyRequest": {"properties": {"public": {"type": "string", "title": "Public"}, "private": {"type": "string", "title": "Private"}}, "additionalProperties": false, "type": "object", "required": ["public"], "title": "SSHKeyRequest"}, "SSHParamsRequest": {"properties": {"user": {"type": "string", "title": "User", "description": "The user to log in with on all hosts"}, "port": {"type": "integer", "title": "Port", "description": "The SSH port to connect to"}, "identity_file": {"type": "string", "title": "Identity File", "description": "The private key to use for all hosts"}, "ssh_key": {"$ref": "#/components/schemas/SSHKeyRequest"}, "proxy_jump": {"allOf": [{"$ref": "#/components/schemas/SSHProxyParamsRequest"}], "title": "Proxy Jump", "description": "The SSH proxy configuration for all hosts"}, "hosts": {"items": {"anyOf": [{"$ref": "#/components/schemas/SSHHostParamsRequest"}, {"type": "string"}]}, "type": "array", "title": "Hosts", "description": "The per host connection parameters: a hostname or an object that overrides default ssh parameters"}, "network": {"type": "string", "title": "Network", "description": "The network address for cluster setup in the format `/`. `dstack` will use IP addresses from this network for communication between hosts. If not specified, `dstack` will use IPs from the first found internal network."}}, "additionalProperties": false, "type": "object", "required": ["hosts"], "title": "SSHParamsRequest"}, "SSHProxyParamsRequest": {"properties": {"hostname": {"type": "string", "title": "Hostname", "description": "The IP address or domain of proxy host"}, "port": {"type": "integer", "title": "Port", "description": "The SSH port of proxy host"}, "user": {"type": "string", "title": "User", "description": "The user to log in with for proxy host"}, "identity_file": {"type": "string", "title": "Identity File", "description": "The private key to use for proxy host"}, "ssh_key": {"$ref": "#/components/schemas/SSHKeyRequest"}}, "additionalProperties": false, "type": "object", "required": ["hostname", "user", "identity_file"], "title": "SSHProxyParamsRequest"}, "ScalingSpecRequest": {"properties": {"metric": {"type": "string", "enum": ["rps"], "title": "Metric", "description": "The target metric to track. Currently, the only supported value is `rps` (meaning requests per second)"}, "target": {"type": "number", "exclusiveMinimum": 0.0, "title": "Target", "description": "The target value of the metric. The number of replicas is calculated based on this number and automatically adjusts (scales up or down) as this metric changes"}, "window": {"type": "integer", "title": "Window", "description": "The time window used to calculate requests per second. Allowed values: `30s`, `60s`, `300s`. Defaults to `60s`"}, "scale_up_delay": {"type": "integer", "title": "Scale Up Delay", "description": "The minimum time, in seconds, between a scaling event and the next scale-up decision. Used to prevent overly frequent scaling", "default": 300}, "scale_down_delay": {"type": "integer", "title": "Scale Down Delay", "description": "The minimum time, in seconds, between a scaling event and the next scale-down decision. Used to prevent overly frequent scaling", "default": 600}}, "additionalProperties": false, "type": "object", "required": ["metric", "target"], "title": "ScalingSpecRequest"}, "ScheduleRequest": {"properties": {"cron": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "string"}], "title": "Cron", "description": "A cron expression or a list of cron expressions specifying the UTC time when the run needs to be started"}}, "additionalProperties": false, "type": "object", "required": ["cron"], "title": "ScheduleRequest"}, "ServiceConfigurationRequest": {"properties": {"port": {"anyOf": [{"type": "integer", "maximum": 65536.0, "exclusiveMinimum": 0.0}, {"type": "string", "pattern": "^[0-9]+:[0-9]+$"}, {"$ref": "#/components/schemas/PortMappingRequest"}], "title": "Port", "description": "The port the application listens on"}, "gateway": {"anyOf": [{"type": "boolean"}, {"$ref": "#/components/schemas/EntityReferenceRequest"}, {"type": "string"}], "title": "Gateway", "description": "The name of the gateway. Specify boolean `false` to run without a gateway. Specify boolean `true` to run with the default gateway. Omit to run with the default gateway if there is one, or without a gateway otherwise"}, "strip_prefix": {"type": "boolean", "title": "Strip Prefix", "description": "Strip the `/proxy/services///` path prefix when forwarding requests to the service. Only takes effect when running the service without a gateway", "default": true}, "model": {"anyOf": [{"$ref": "#/components/schemas/TGIChatModelRequest"}, {"$ref": "#/components/schemas/OpenAIChatModelRequest"}, {"type": "string"}], "title": "Model", "description": "Mapping of the model for the OpenAI-compatible endpoint provided by `dstack`. Can be a full model format definition or just a model name. If it's a name, the service is expected to expose an OpenAI-compatible API at the `/v1` path"}, "https": {"anyOf": [{"type": "boolean"}, {"type": "string", "enum": ["auto"]}], "title": "Https", "description": "Enable HTTPS if running with a gateway. Set to `auto` to determine automatically based on gateway configuration. Defaults to `true`"}, "auth": {"type": "boolean", "title": "Auth", "description": "Enable the authorization", "default": true}, "scaling": {"allOf": [{"$ref": "#/components/schemas/ScalingSpecRequest"}], "title": "Scaling", "description": "The auto-scaling rules. Required if `replicas` is set to a range"}, "rate_limits": {"items": {"$ref": "#/components/schemas/RateLimitRequest"}, "type": "array", "title": "Rate Limits", "description": "Rate limiting rules", "default": []}, "probes": {"items": {"$ref": "#/components/schemas/ProbeConfigRequest"}, "type": "array", "title": "Probes", "description": "The list of probes to determine service health. If `model` is set, defaults to a `/v1/chat/completions` probe. Set explicitly to override"}, "replicas": {"anyOf": [{"items": {"$ref": "#/components/schemas/ReplicaGroupRequest"}, "type": "array"}, {"$ref": "#/components/schemas/Range_int_"}, {"type": "integer"}, {"type": "string"}], "title": "Replicas", "description": "The number of replicas or a list of replica groups. Can be an integer (e.g., `2`), a range (e.g., `0..4`), or a list of replica groups. Each replica group defines replicas with shared configuration (commands, resources, scaling). When `replicas` is a list of replica groups, top-level `scaling`, `commands`, and `resources` are not allowed and must be specified in each replica group instead. "}, "router": {"allOf": [{"$ref": "#/components/schemas/SGLangServiceRouterConfigRequest"}], "title": "Router", "description": "Router configuration for the service. Requires a gateway with matching router enabled. "}, "commands": {"items": {"type": "string"}, "type": "array", "title": "Commands", "description": "The shell commands to run", "default": []}, "type": {"type": "string", "enum": ["service"], "title": "Type", "default": "service"}, "name": {"type": "string", "title": "Name", "description": "The run name. If not specified, a random name is generated"}, "image": {"type": "string", "title": "Image", "description": "The name of the Docker image to run"}, "user": {"type": "string", "title": "User", "description": "The user inside the container, `user_name_or_id[:group_name_or_id]` (e.g., `ubuntu`, `1000:1000`). Defaults to the default user from the `image`"}, "privileged": {"type": "boolean", "title": "Privileged", "description": "Run the container in privileged mode", "default": false}, "entrypoint": {"type": "string", "title": "Entrypoint", "description": "The Docker entrypoint"}, "working_dir": {"type": "string", "title": "Working Dir", "description": "The absolute path to the working directory inside the container. Defaults to the `image`'s default working directory"}, "home_dir": {"type": "string", "title": "Home Dir", "default": "/root"}, "registry_auth": {"allOf": [{"$ref": "#/components/schemas/RegistryAuthRequest"}], "title": "Registry Auth", "description": "Credentials for pulling a private Docker image"}, "python": {"allOf": [{"$ref": "#/components/schemas/PythonVersion"}], "description": "The major version of Python. Mutually exclusive with `image` and `docker`"}, "nvcc": {"type": "boolean", "title": "Nvcc", "description": "Use image with NVIDIA CUDA Compiler (NVCC) included. Mutually exclusive with `image` and `docker`"}, "single_branch": {"type": "boolean", "title": "Single Branch", "description": "Whether to clone and track only the current branch or all remote branches. Relevant only when using remote Git repos. Defaults to `false` for dev environments and to `true` for tasks and services"}, "env": {"allOf": [{"$ref": "#/components/schemas/Env"}], "title": "Env", "description": "The mapping or the list of environment variables", "default": {"__root__": {}}}, "shell": {"type": "string", "title": "Shell", "description": "The shell used to run commands. Allowed values are `sh`, `bash`, or an absolute path, e.g., `/usr/bin/zsh`. Defaults to `/bin/sh` if the `image` is specified, `/bin/bash` otherwise"}, "resources": {"allOf": [{"$ref": "#/components/schemas/ResourcesSpecRequest"}], "title": "Resources", "description": "The resources requirements to run the configuration", "default": {"cpu": {"min": 2}, "memory": {"min": 8.0}, "gpu": {"count": {"min": 0}}, "disk": {"size": {"min": 100.0}}}}, "priority": {"type": "integer", "maximum": 100.0, "minimum": 0.0, "title": "Priority", "description": "The priority of the run, an integer between `0` and `100`. `dstack` tries to provision runs with higher priority first. Defaults to `0`"}, "volumes": {"items": {"anyOf": [{"$ref": "#/components/schemas/VolumeMountPointRequest"}, {"$ref": "#/components/schemas/InstanceMountPointRequest"}, {"type": "string"}]}, "type": "array", "title": "Volumes", "description": "The volumes mount points", "default": []}, "docker": {"type": "boolean", "title": "Docker", "description": "Use Docker inside the container. Mutually exclusive with `image`, `python`, and `nvcc`. Overrides `privileged`"}, "repos": {"items": {"$ref": "#/components/schemas/RepoSpecRequest"}, "type": "array", "title": "Repos", "description": "The list of Git repos", "default": []}, "files": {"items": {"anyOf": [{"$ref": "#/components/schemas/FilePathMappingRequest"}, {"type": "string"}]}, "type": "array", "title": "Files", "description": "The local to container file path mappings", "default": []}, "setup": {"items": {"type": "string"}, "type": "array", "title": "Setup", "default": []}, "backends": {"items": {"$ref": "#/components/schemas/BackendType"}, "type": "array", "description": "The backends to consider for provisioning (e.g., `[aws, gcp]`)"}, "regions": {"items": {"type": "string"}, "type": "array", "title": "Regions", "description": "The regions to consider for provisioning (e.g., `[eu-west-1, us-west4, westeurope]`)"}, "availability_zones": {"items": {"type": "string"}, "type": "array", "title": "Availability Zones", "description": "The availability zones to consider for provisioning (e.g., `[eu-west-1a, us-west4-a]`)"}, "instance_types": {"items": {"type": "string"}, "type": "array", "title": "Instance Types", "description": "The cloud-specific instance types to consider for provisioning (e.g., `[g6e.24xlarge, n1-standard-4]`)"}, "reservation": {"type": "string", "title": "Reservation", "description": "The existing reservation to use for instance provisioning. Supports AWS Capacity Reservations, AWS Capacity Blocks, and GCP reservations"}, "spot_policy": {"allOf": [{"$ref": "#/components/schemas/SpotPolicy"}], "description": "The policy for provisioning spot or on-demand instances: `spot`, `on-demand`, `auto`. Defaults to `on-demand`"}, "retry": {"anyOf": [{"$ref": "#/components/schemas/ProfileRetryRequest"}, {"type": "boolean"}], "title": "Retry", "description": "The policy for resubmitting the run. Defaults to `false`"}, "max_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Max Duration", "description": "The maximum duration of a run (e.g., `2h`, `1d`, etc) in a running state, excluding provisioning and pulling. After it elapses, the run is automatically stopped. Use `off` for unlimited duration. Defaults to `off`"}, "stop_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Stop Duration", "description": "The maximum duration of a run graceful stopping. After it elapses, the run is automatically forced stopped. This includes force detaching volumes used by the run. Use `off` for unlimited duration. Defaults to `5m`"}, "max_price": {"type": "number", "exclusiveMinimum": 0.0, "title": "Max Price", "description": "The maximum instance price per hour, in dollars"}, "creation_policy": {"allOf": [{"$ref": "#/components/schemas/CreationPolicy"}], "description": "The policy for using instances from fleets: `reuse`, `reuse-or-create`. Defaults to `reuse-or-create`"}, "idle_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Idle Duration", "description": "Time to wait before terminating idle instances. When the run reuses an existing fleet instance, the fleet's `idle_duration` applies. When the run provisions a new instance, the shorter of the fleet's and run's values is used. Defaults to `5m` for runs and `3d` for fleets. Use `off` for unlimited duration. Only applied for VM-based backends"}, "utilization_policy": {"allOf": [{"$ref": "#/components/schemas/UtilizationPolicyRequest"}], "title": "Utilization Policy", "description": "Run termination policy based on utilization"}, "startup_order": {"allOf": [{"$ref": "#/components/schemas/StartupOrder"}], "description": "The order in which master and workers jobs are started: `any`, `master-first`, `workers-first`. Defaults to `any`"}, "stop_criteria": {"allOf": [{"$ref": "#/components/schemas/StopCriteria"}], "description": "The criteria determining when a multi-node run should be considered finished: `all-done`, `master-done`. Defaults to `all-done`"}, "schedule": {"allOf": [{"$ref": "#/components/schemas/ScheduleRequest"}], "title": "Schedule", "description": "The schedule for starting the run at specified time"}, "fleets": {"items": {"anyOf": [{"$ref": "#/components/schemas/EntityReferenceRequest"}, {"type": "string"}]}, "type": "array", "title": "Fleets", "description": "The fleets considered for reuse. For fleets owned by the current project, specify fleet names. For imported fleets, specify `/`"}, "instances": {"items": {"anyOf": [{"$ref": "#/components/schemas/InstanceNameSelectorRequest"}, {"$ref": "#/components/schemas/InstanceHostnameSelectorRequest"}, {"$ref": "#/components/schemas/FleetInstanceSelectorRequest"}, {"type": "string", "minLength": 1}]}, "type": "array", "minItems": 1, "title": "Instances", "description": "The specific fleet instances to consider for reuse. Each value can be an instance name string, or an object with `name`, `hostname`, or `fleet` and `instance`. When set, the run is only placed on matching existing instances."}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the resource. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "backend_options": {"items": {"$ref": "#/components/schemas/VastAIProfileOptions"}, "type": "array", "title": "Backend Options", "description": "Backend-specific options, applied only to offers from that backend"}}, "additionalProperties": false, "type": "object", "required": ["port"], "title": "ServiceConfigurationRequest"}, "SpecApplyRequest": {"properties": {"user": {"type": "string", "title": "User", "description": "The name of the user making the apply request"}, "project": {"type": "string", "title": "Project", "description": "The name of the project the request is for"}, "spec": {"anyOf": [{"$ref": "#/components/schemas/RunSpecRequest"}, {"$ref": "#/components/schemas/FleetSpecRequest"}, {"$ref": "#/components/schemas/VolumeSpecRequest"}, {"$ref": "#/components/schemas/GatewaySpecRequest"}], "title": "Spec", "description": "The spec to be applied"}}, "type": "object", "required": ["user", "project", "spec"], "title": "SpecApplyRequest"}, "SpecApplyResponse": {"properties": {"spec": {"anyOf": [{"$ref": "#/components/schemas/RunSpecRequest"}, {"$ref": "#/components/schemas/FleetSpecRequest"}, {"$ref": "#/components/schemas/VolumeSpecRequest"}, {"$ref": "#/components/schemas/GatewaySpecRequest"}], "title": "Spec", "description": "The spec to apply, original spec if error otherwise original or mutated by plugin service if approved"}, "error": {"type": "string", "minLength": 1, "title": "Error", "description": "Error message if request is rejected"}}, "type": "object", "required": ["spec"], "title": "SpecApplyResponse"}, "SpotPolicy": {"type": "string", "enum": ["spot", "on-demand", "auto"], "title": "SpotPolicy", "description": "An enumeration."}, "StartupOrder": {"type": "string", "enum": ["any", "master-first", "workers-first"], "title": "StartupOrder", "description": "An enumeration."}, "StopCriteria": {"type": "string", "enum": ["all-done", "master-done"], "title": "StopCriteria", "description": "An enumeration."}, "TGIChatModelRequest": {"properties": {"type": {"type": "string", "enum": ["chat"], "title": "Type", "description": "The type of the model", "default": "chat"}, "name": {"type": "string", "title": "Name", "description": "The name of the model"}, "format": {"type": "string", "enum": ["tgi"], "title": "Format", "description": "The serving format. Must be set to `tgi`"}, "chat_template": {"type": "string", "title": "Chat Template", "description": "The custom prompt template for the model. If not specified, the default prompt template from the HuggingFace Hub configuration will be used"}, "eos_token": {"type": "string", "title": "Eos Token", "description": "The custom end of sentence token. If not specified, the default end of sentence token from the HuggingFace Hub configuration will be used"}}, "additionalProperties": false, "type": "object", "required": ["name", "format"], "title": "TGIChatModelRequest", "description": "Mapping of the model for the OpenAI-compatible endpoint.\n\nAttributes:\n type (str): The type of the model, e.g. \"chat\"\n name (str): The name of the model. This name will be used both to load model configuration from the HuggingFace Hub and in the OpenAI-compatible endpoint.\n format (str): The format of the model, e.g. \"tgi\" if the model is served with HuggingFace's Text Generation Inference.\n chat_template (Optional[str]): The custom prompt template for the model. If not specified, the default prompt template from the HuggingFace Hub configuration will be used.\n eos_token (Optional[str]): The custom end of sentence token. If not specified, the default end of sentence token from the HuggingFace Hub configuration will be used."}, "TaskConfigurationRequest": {"properties": {"nodes": {"type": "integer", "minimum": 1.0, "title": "Nodes", "description": "Number of nodes", "default": 1}, "ports": {"items": {"anyOf": [{"type": "integer", "maximum": 65536.0, "exclusiveMinimum": 0.0}, {"type": "string", "pattern": "^(?:[0-9]+|\\*):[0-9]+$"}, {"$ref": "#/components/schemas/PortMappingRequest"}]}, "type": "array", "title": "Ports", "description": "Port numbers/mapping to expose", "default": []}, "commands": {"items": {"type": "string"}, "type": "array", "title": "Commands", "description": "The shell commands to run", "default": []}, "type": {"type": "string", "enum": ["task"], "title": "Type", "default": "task"}, "name": {"type": "string", "title": "Name", "description": "The run name. If not specified, a random name is generated"}, "image": {"type": "string", "title": "Image", "description": "The name of the Docker image to run"}, "user": {"type": "string", "title": "User", "description": "The user inside the container, `user_name_or_id[:group_name_or_id]` (e.g., `ubuntu`, `1000:1000`). Defaults to the default user from the `image`"}, "privileged": {"type": "boolean", "title": "Privileged", "description": "Run the container in privileged mode", "default": false}, "entrypoint": {"type": "string", "title": "Entrypoint", "description": "The Docker entrypoint"}, "working_dir": {"type": "string", "title": "Working Dir", "description": "The absolute path to the working directory inside the container. Defaults to the `image`'s default working directory"}, "home_dir": {"type": "string", "title": "Home Dir", "default": "/root"}, "registry_auth": {"allOf": [{"$ref": "#/components/schemas/RegistryAuthRequest"}], "title": "Registry Auth", "description": "Credentials for pulling a private Docker image"}, "python": {"allOf": [{"$ref": "#/components/schemas/PythonVersion"}], "description": "The major version of Python. Mutually exclusive with `image` and `docker`"}, "nvcc": {"type": "boolean", "title": "Nvcc", "description": "Use image with NVIDIA CUDA Compiler (NVCC) included. Mutually exclusive with `image` and `docker`"}, "single_branch": {"type": "boolean", "title": "Single Branch", "description": "Whether to clone and track only the current branch or all remote branches. Relevant only when using remote Git repos. Defaults to `false` for dev environments and to `true` for tasks and services"}, "env": {"allOf": [{"$ref": "#/components/schemas/Env"}], "title": "Env", "description": "The mapping or the list of environment variables", "default": {"__root__": {}}}, "shell": {"type": "string", "title": "Shell", "description": "The shell used to run commands. Allowed values are `sh`, `bash`, or an absolute path, e.g., `/usr/bin/zsh`. Defaults to `/bin/sh` if the `image` is specified, `/bin/bash` otherwise"}, "resources": {"allOf": [{"$ref": "#/components/schemas/ResourcesSpecRequest"}], "title": "Resources", "description": "The resources requirements to run the configuration", "default": {"cpu": {"min": 2}, "memory": {"min": 8.0}, "gpu": {"count": {"min": 0}}, "disk": {"size": {"min": 100.0}}}}, "priority": {"type": "integer", "maximum": 100.0, "minimum": 0.0, "title": "Priority", "description": "The priority of the run, an integer between `0` and `100`. `dstack` tries to provision runs with higher priority first. Defaults to `0`"}, "volumes": {"items": {"anyOf": [{"$ref": "#/components/schemas/VolumeMountPointRequest"}, {"$ref": "#/components/schemas/InstanceMountPointRequest"}, {"type": "string"}]}, "type": "array", "title": "Volumes", "description": "The volumes mount points", "default": []}, "docker": {"type": "boolean", "title": "Docker", "description": "Use Docker inside the container. Mutually exclusive with `image`, `python`, and `nvcc`. Overrides `privileged`"}, "repos": {"items": {"$ref": "#/components/schemas/RepoSpecRequest"}, "type": "array", "title": "Repos", "description": "The list of Git repos", "default": []}, "files": {"items": {"anyOf": [{"$ref": "#/components/schemas/FilePathMappingRequest"}, {"type": "string"}]}, "type": "array", "title": "Files", "description": "The local to container file path mappings", "default": []}, "setup": {"items": {"type": "string"}, "type": "array", "title": "Setup", "default": []}, "backends": {"items": {"$ref": "#/components/schemas/BackendType"}, "type": "array", "description": "The backends to consider for provisioning (e.g., `[aws, gcp]`)"}, "regions": {"items": {"type": "string"}, "type": "array", "title": "Regions", "description": "The regions to consider for provisioning (e.g., `[eu-west-1, us-west4, westeurope]`)"}, "availability_zones": {"items": {"type": "string"}, "type": "array", "title": "Availability Zones", "description": "The availability zones to consider for provisioning (e.g., `[eu-west-1a, us-west4-a]`)"}, "instance_types": {"items": {"type": "string"}, "type": "array", "title": "Instance Types", "description": "The cloud-specific instance types to consider for provisioning (e.g., `[g6e.24xlarge, n1-standard-4]`)"}, "reservation": {"type": "string", "title": "Reservation", "description": "The existing reservation to use for instance provisioning. Supports AWS Capacity Reservations, AWS Capacity Blocks, and GCP reservations"}, "spot_policy": {"allOf": [{"$ref": "#/components/schemas/SpotPolicy"}], "description": "The policy for provisioning spot or on-demand instances: `spot`, `on-demand`, `auto`. Defaults to `on-demand`"}, "retry": {"anyOf": [{"$ref": "#/components/schemas/ProfileRetryRequest"}, {"type": "boolean"}], "title": "Retry", "description": "The policy for resubmitting the run. Defaults to `false`"}, "max_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Max Duration", "description": "The maximum duration of a run (e.g., `2h`, `1d`, etc) in a running state, excluding provisioning and pulling. After it elapses, the run is automatically stopped. Use `off` for unlimited duration. Defaults to `off`"}, "stop_duration": {"anyOf": [{"type": "string", "enum": ["off"]}, {"type": "integer"}, {"type": "boolean"}, {"type": "string"}], "title": "Stop Duration", "description": "The maximum duration of a run graceful stopping. After it elapses, the run is automatically forced stopped. This includes force detaching volumes used by the run. Use `off` for unlimited duration. Defaults to `5m`"}, "max_price": {"type": "number", "exclusiveMinimum": 0.0, "title": "Max Price", "description": "The maximum instance price per hour, in dollars"}, "creation_policy": {"allOf": [{"$ref": "#/components/schemas/CreationPolicy"}], "description": "The policy for using instances from fleets: `reuse`, `reuse-or-create`. Defaults to `reuse-or-create`"}, "idle_duration": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Idle Duration", "description": "Time to wait before terminating idle instances. When the run reuses an existing fleet instance, the fleet's `idle_duration` applies. When the run provisions a new instance, the shorter of the fleet's and run's values is used. Defaults to `5m` for runs and `3d` for fleets. Use `off` for unlimited duration. Only applied for VM-based backends"}, "utilization_policy": {"allOf": [{"$ref": "#/components/schemas/UtilizationPolicyRequest"}], "title": "Utilization Policy", "description": "Run termination policy based on utilization"}, "startup_order": {"allOf": [{"$ref": "#/components/schemas/StartupOrder"}], "description": "The order in which master and workers jobs are started: `any`, `master-first`, `workers-first`. Defaults to `any`"}, "stop_criteria": {"allOf": [{"$ref": "#/components/schemas/StopCriteria"}], "description": "The criteria determining when a multi-node run should be considered finished: `all-done`, `master-done`. Defaults to `all-done`"}, "schedule": {"allOf": [{"$ref": "#/components/schemas/ScheduleRequest"}], "title": "Schedule", "description": "The schedule for starting the run at specified time"}, "fleets": {"items": {"anyOf": [{"$ref": "#/components/schemas/EntityReferenceRequest"}, {"type": "string"}]}, "type": "array", "title": "Fleets", "description": "The fleets considered for reuse. For fleets owned by the current project, specify fleet names. For imported fleets, specify `/`"}, "instances": {"items": {"anyOf": [{"$ref": "#/components/schemas/InstanceNameSelectorRequest"}, {"$ref": "#/components/schemas/InstanceHostnameSelectorRequest"}, {"$ref": "#/components/schemas/FleetInstanceSelectorRequest"}, {"type": "string", "minLength": 1}]}, "type": "array", "minItems": 1, "title": "Instances", "description": "The specific fleet instances to consider for reuse. Each value can be an instance name string, or an object with `name`, `hostname`, or `fleet` and `instance`. When set, the run is only placed on matching existing instances."}, "tags": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Tags", "description": "The custom tags to associate with the resource. The tags are also propagated to the underlying backend resources. If there is a conflict with backend-level tags, does not override them"}, "backend_options": {"items": {"$ref": "#/components/schemas/VastAIProfileOptions"}, "type": "array", "title": "Backend Options", "description": "Backend-specific options, applied only to offers from that backend"}}, "additionalProperties": false, "type": "object", "title": "TaskConfigurationRequest"}, "UtilizationPolicyRequest": {"properties": {"min_gpu_utilization": {"type": "integer", "maximum": 100.0, "minimum": 0.0, "title": "Min Gpu Utilization", "description": "Minimum required GPU utilization, percent. If any GPU has utilization below specified value during the whole time window, the run is terminated"}, "time_window": {"anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Time Window", "description": "The time window of metric samples taking into account to measure utilization (e.g., `30m`, `1h`). Minimum is `5m`"}}, "additionalProperties": false, "type": "object", "required": ["min_gpu_utilization", "time_window"], "title": "UtilizationPolicyRequest"}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}, "VastAIOfferOrder": {"type": "string", "enum": ["score", "price"], "title": "VastAIOfferOrder", "description": "An enumeration."}, "VastAIProfileOptions": {"properties": {"type": {"type": "string", "enum": ["vastai"], "title": "Type", "default": "vastai"}, "offer_order": {"allOf": [{"$ref": "#/components/schemas/VastAIOfferOrder"}], "description": "Controls the order in which offers are considered for provisioning. Use `score` to prioritize the highest overall score first (the default order in the Vast.ai console), or `price` to prioritize the lowest-cost offers first. Lower-cost offers are often less reliable, so consider applying stricter filters when using `price`. Defaults to `score`"}, "min_reliability": {"type": "number", "maximum": 1.0, "minimum": 0.0, "title": "Min Reliability", "description": "The minimum reliability threshold for offers, on a scale from `0` to `1`. Defaults to `0.9`"}, "min_score": {"type": "integer", "minimum": 0.0, "title": "Min Score", "description": "The minimum overall score required for offers to be considered. The scoring scale varies and may require experimentation. Starting with a value in the low hundreds is generally recommended"}}, "additionalProperties": false, "type": "object", "title": "VastAIProfileOptions"}, "VirtualRunRepoDataRequest": {"properties": {"repo_type": {"type": "string", "enum": ["virtual"], "title": "Repo Type", "default": "virtual"}}, "additionalProperties": false, "type": "object", "title": "VirtualRunRepoDataRequest"}, "VolumeMountPointRequest": {"properties": {"name": {"anyOf": [{"type": "string"}, {"items": {"type": "string"}, "type": "array"}], "title": "Name", "description": "The network volume name or the list of network volume names to mount. If a list is specified, one of the volumes in the list will be mounted. Specify volumes from different backends/regions to increase availability"}, "path": {"type": "string", "title": "Path", "description": "The absolute container path to mount the volume at"}}, "additionalProperties": false, "type": "object", "required": ["name", "path"], "title": "VolumeMountPointRequest"}, "VolumeSpecRequest": {"properties": {"configuration": {"oneOf": [{"$ref": "#/components/schemas/AWSVolumeConfigurationRequest"}, {"$ref": "#/components/schemas/GCPVolumeConfigurationRequest"}, {"$ref": "#/components/schemas/RunpodVolumeConfigurationRequest"}, {"$ref": "#/components/schemas/KubernetesVolumeConfigurationRequest"}], "title": "Configuration", "discriminator": {"propertyName": "backend", "mapping": {"aws": "#/components/schemas/AWSVolumeConfigurationRequest", "gcp": "#/components/schemas/GCPVolumeConfigurationRequest", "runpod": "#/components/schemas/RunpodVolumeConfigurationRequest", "kubernetes": "#/components/schemas/KubernetesVolumeConfigurationRequest"}}}, "configuration_path": {"type": "string", "title": "Configuration Path"}}, "additionalProperties": false, "type": "object", "required": ["configuration"], "title": "VolumeSpecRequest"}}}} diff --git a/website/package-lock.json b/website/package-lock.json index 4bbcd544b..feba06fc1 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -1,16 +1,17 @@ { - "name": "cloudscape-reverse-engineering", + "name": "dstack-website", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "cloudscape-reverse-engineering", + "name": "dstack-website", "version": "0.1.0", "dependencies": { "@cloudscape-design/code-view": "^3.0.142", "@cloudscape-design/components": "^3.0.0", "@cloudscape-design/global-styles": "^1.0.0", + "@dstackai/sqircle": "^0.1.9", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.30.4" @@ -483,6 +484,16 @@ "react": ">=16.8.0" } }, + "node_modules/@dstackai/sqircle": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@dstackai/sqircle/-/sqircle-0.1.9.tgz", + "integrity": "sha512-/NrPXUILifHN8u8uGTZNVX6EK4BVut1fmhdHwXvOsvjJ9fU17ib6NkjAzp2PQSmCGUtnhin9crTxqV9iuZG/sg==", + "license": "MIT", + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", diff --git a/website/package.json b/website/package.json index c0d772303..09cff3cbb 100644 --- a/website/package.json +++ b/website/package.json @@ -12,6 +12,7 @@ "@cloudscape-design/code-view": "^3.0.142", "@cloudscape-design/components": "^3.0.0", "@cloudscape-design/global-styles": "^1.0.0", + "@dstackai/sqircle": "^0.1.9", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.30.4" diff --git a/website/public/static/dstack-gpu-artwork-dark.svg b/website/public/static/dstack-gpu-artwork-dark.svg deleted file mode 100644 index eb5b68eeb..000000000 --- a/website/public/static/dstack-gpu-artwork-dark.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/website/public/static/dstack-gpu-artwork.svg b/website/public/static/dstack-gpu-artwork.svg deleted file mode 100644 index 1c06e970a..000000000 --- a/website/public/static/dstack-gpu-artwork.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/website/src/cloudscape-overrides.css b/website/src/cloudscape-overrides.css index fcc988664..42230d504 100644 --- a/website/src/cloudscape-overrides.css +++ b/website/src/cloudscape-overrides.css @@ -14,7 +14,6 @@ not a breakage). Re-derive with: grep -roE '\-\-border-divider-section-width-[a-z0-9]+' node_modules/@cloudscape-design/components/container grep -roE '\-\-color-border-tabs-[a-z-]+-[a-z0-9]+' node_modules/@cloudscape-design/components/tabs - grep -roE 'awsui_split-trigger-wrapper_[a-z0-9_]+' node_modules/@cloudscape-design/components/button-dropdown */ /* NB: Cloudscape defines these tokens on `body` inside `@layer awsui-base-theme`. Our @@ -34,40 +33,18 @@ /* 2) Tabs: the bottom divider and the active-tab underline use the text color. Tabs use tab-specific border-color tokens that the theming API doesn't expose. */ body { - --color-border-tabs-divider-f5t9va: var(--cs-text); + --color-border-tabs-divider-f5t9va: color-mix(in srgb, var(--cs-text) 14%, transparent); --color-border-tabs-underline-gudemr: var(--cs-text); --color-border-dropdown-group-ylcnh8: transparent; /* no divider between dropdown groups */ --color-text-expandable-section-hover-ojzwhd: var(--cs-text); /* FAQ: don't turn the question blue on hover */ } -/* 3) Split ButtonDropdown ("Get started"): close the gap before the arrow (a 2px - margin on the main segment) and drop the main action's trailing padding so the - label and arrow sit flush. ButtonDropdown has no `style` prop, so this targets its - scoped classes by stable prefix (hash-independent). */ -/* !important is required: Cloudscape boosts its own rules' specificity with a :not(#\9) - trick, which an attribute selector can't otherwise outrank. */ -[class*='awsui_split-trigger-wrapper'] > [class*='awsui_trigger-item']:not(:last-child) > [class*='awsui_trigger-button'] { - margin-inline-end: 0 !important; /* close the 2px gap before the arrow */ - padding-inline-end: 16px !important; /* breathing room between the label and the divider */ -} -/* Match the other top-nav buttons' height (see menuButtonStyle, 7px block). */ -[class*='awsui_split-trigger-wrapper'] [class*='awsui_trigger-button'] { - padding-block: 7px !important; -} -/* Subtle 0.5px divider between the two segments — a hair lighter/darker than the fill - so the split reads, without breaking the single-button look. (Hover stays per-segment, - which is Cloudscape's default and the better affordance here.) */ -[class*='awsui_split-trigger-wrapper'] > [class*='awsui_trigger-item']:not(:first-child) > [class*='awsui_trigger-button'] { - border-inline-start: 0.5px solid var(--cs-seg-divider) !important; -} - /* 4) Content tabs. - a) Full-height vertical separators between tabs (default insets them 12px top/bottom). - `inset-block: 0` spans only the container's padding box, leaving a 1px gap at top/bottom - against the 1px transparent border; the negative inset extends them over that border so - they match the height of the edge separators (real borders on the border box). */ -[class*='awsui_tabs-tab_']:not(:last-child) > [class*='awsui_tabs-tab-header-container']::before { - inset-block: calc(-1 * var(--border-divider-section-width-uwo8my, 1px)) !important; + a) Drop the vertical separators Cloudscape draws between tabs. Selection is shown by the + active tab's pill background (section 9), so the dividers just add noise — the tabs read + as a row of pills separated by the cells' own padding (matching the landing's tab design). */ +[class*='awsui_tabs-tab_'] > [class*='awsui_tabs-tab-header-container']::before { + display: none !important; } /* b) Drop the gray border + shadow that Cloudscape shows on the scroll arrows when the tab strip overflows (the box-shadow renders both; border-inline is the divider). */ @@ -79,87 +56,12 @@ body { [class*='awsui_pagination-button-right'] { border-inline: 0 !important; } - -/* 5) Dropdown menu popups (the "Get started" and "Resources" menus). Targeted by stable - class prefix so the rules apply wherever the popup renders. - a) Fixed width, flat (no drop-shadow), and a single uniform 0.5px border on all four - sides. By default Cloudscape draws top/bottom on the wrapper (1px) and left/right on - a ::after — so we set the wrapper border ourselves and drop the ::after layer. */ -[class*='awsui_dropdown-content-wrapper'] { - inline-size: 300px !important; - box-shadow: none !important; - border: 0.5px solid var(--cs-text) !important; - border-radius: 12px !important; /* rounded popup */ - overflow: hidden; /* clip the menu items to the rounded corners */ -} -[class*='awsui_dropdown-content-wrapper']::after { - border: 0 !important; -} -/* b) Group headers ("Products" / "Login"). lighter (300) and a touch smaller - (15px) in the full text color (no longer muted). */ -[class*='awsui_header_16mm3'] { - font-weight: 300 !important; - font-size: 15px !important; - color: var(--cs-text) !important; - padding-inline: 16px !important; -} -/* c) Items: bold label (matching the group weight), tighter horizontal padding aligned - with the header. The description below keeps its normal/muted styling. */ -[class*='awsui_menu-item'] { - padding-inline: 16px !important; -} -[class*='awsui_menu-item'] [class*='awsui_main-row'] { - font-weight: 600; - font-size: 15px; /* (Cloudscape's default popup label is 14px) */ -} -/* The hovered item still picked up a border in dark mode (the token override didn't hold - there), so force it off on the highlighted item itself (the cue is the bg tint). */ -[class*='awsui_item-element'][class*='awsui_highlighted'] { - border-color: transparent !important; -} -/* Popup item descriptions: normal text color (not muted), 13px / weight 300. A hair of - separation (1.5px) from the label above so the two lines don't read as one block. */ -[class*='awsui_secondary-text'] { - color: var(--cs-text) !important; - margin-block-start: 1.5px; - font-size: 13px !important; - font-weight: 300 !important; -} -/* A little breathing room at the top and bottom of the popup (inside the border). Placed on - the first/last rows rather than on the list itself, so a hovered first/last item's - background fills that space instead of leaving a thin un-highlighted strip against the - border (the hover tint is painted on the item-element, which is what carries the padding). */ -[class*='awsui_options-list'] { - padding-block: 0 !important; - /* Cloudscape pulls the list 1px into the wrapper border (decrease-block-margin) to overlap - its default 1px divider. With our single hairline border that just lets a hovered - first/last item's fill paint over the border (most visible in dark mode) — so sit the - list flush inside the border instead. */ +/* c) The scroll-arrow buttons carry 12px block margins, so they are taller than the tab row and + stretch the whole strip (and its tabs) ~10px taller than a chevron-free strip. Zero the block + margin so the arrows no longer inflate the tab height — the strip matches the others. */ +[class*='awsui_pagination-button'] { margin-block: 0 !important; } -[class*='awsui_options-list'] > :first-child { - padding-block-start: 6px !important; -} -/* Last item — flat list (ButtonDropdown without groups, e.g. the Resources menu). */ -[class*='awsui_options-list'] > [class*='awsui_item-element']:last-child { - padding-block-end: 6px !important; -} -/* Last item — grouped list: the last item inside the last category (e.g. "Get started"). */ -[class*='awsui_options-list'] > [class*='awsui_category']:last-child [class*='awsui_item-element']:last-child { - padding-block-end: 6px !important; -} -/* d) Push the external-link icon to the right edge of the item. It's rendered inline at - the end of the label, so make the label row fill the width and flex the icon out. - Scoped under `awsui_main-row` (dropdown-item only) so it doesn't affect the external - icons in SideNavigation, which share the `awsui_external-icon` class. */ -[class*='awsui_main-row'] > :first-child { - display: flex !important; - flex: 1 1 auto !important; - align-items: center; -} -[class*='awsui_main-row'] [class*='awsui_external-icon'] { - margin-inline-start: auto !important; -} /* 6) FAQ accordion (ExpandableSection): faint background tint on hover that covers the whole block (question + answer) uniformly. Tint the section root, then neutralize @@ -199,62 +101,49 @@ body { /* Primary (filled) buttons use 500 weight per design — outline/normal buttons keep their weight. Applies app-wide (new landing + /old). */ -[class*='awsui_variant-primary'], -.site-get-started [class*='awsui_trigger-button'] { +[class*='awsui_variant-primary'] { font-weight: 500 !important; } -/* 9) Content tabs: highlight the selected (and hovered) tab with a background - tint instead of blue text + an underline indicator. The background uses the same tint as - the dropdown popup items (--cs-hover), so every hover surface matches. */ -/* a) The under-tab horizontal divider and the vertical tab separators keep the 1px outer - border width (they inherit --border-divider-section-width, no override needed here). */ -/* b) Drop the per-tab active underline indicator — selection is now shown by the background - (this also removes that indicator's rounded ends). */ +/* 9) Content tabs — "pill" selection, matching the landing's own tab design: the active tab is a + panel-grey rounded-top pill (a hovered tab gets the faint --cs-hover tint), inactive labels + are muted, and there are no separators (section 4a). The pill is painted on the inner link + so each cell's inline padding becomes the gap between pills; a little top padding on the + strip lets the pills float above the softened (section 2) under-tab divider. */ +/* a) Drop the per-tab active underline indicator — selection is shown by the pill background. */ [class*='awsui_tabs-tab-header-container']::after { display: none !important; } -/* c) Keep tab labels in the body color at rest, on hover, and when selected (no blue accent). */ -[class*='awsui_tabs-tab-link'] { - color: var(--cs-text) !important; +/* b) Float the pills: top padding above the strip, and tighter cell padding so neighbouring + pills sit close (the cell's inline padding is the inter-pill gap). */ +[class*='awsui_tabs-header-list'] { + padding: 6px 4px 0 !important; } -/* d) Background highlight for the hovered and selected tab — painted on the whole tab cell - (the header container fills edge to edge; the inner link has padding-inline:0, so painting - the link alone leaves gaps). aria-selected lives on the inner link, hence :has(). */ -[class*='awsui_tabs-tab-header-container']:hover, -[class*='awsui_tabs-tab-header-container']:has([aria-selected='true']) { - background: var(--cs-hover) !important; +[class*='awsui_tabs-tab-header-container'] { + padding-inline: 3px !important; } -/* e) 1px vertical separators bounding the strip (Cloudscape only draws them between tabs). - After the last tab: always. Before the first tab: ONLY when the strip overflows (a - scroll arrow is present). With no scrolling the first tab sits against the container's - left border, so a leading separator just reads as a doubled line — so we omit it. */ -[class*='awsui_tabs-tab_']:last-child > [class*='awsui_tabs-tab-header-container'] { - border-inline-end: 1px solid var(--color-border-tabs-divider-f5t9va) !important; -} -[class*='awsui_tab-header-scroll-container']:has([class*='pagination-button-left-scrollable'], [class*='pagination-button-right-scrollable']) - [class*='awsui_tabs-tab_']:first-child - > [class*='awsui_tabs-tab-header-container'] { - border-inline-start: 1px solid var(--color-border-tabs-divider-f5t9va) !important; -} - -/* 10) GPU table: 0.5px row separators that run edge to edge, while keeping the - container's breathing room. The Container padding lives on `content-inner` (the - `with-paddings` element). We drop only its INLINE padding so the table — and therefore - the row separators — spans the full width; the top/bottom padding is kept. A 20px inset - is then restored on just the outer cells so the text isn't flush against the border. */ -.gpu-scroll * { - --border-divider-list-width-tdfx1x: 0.5px; +/* c) The pill: muted label at rest, body color + faint tint on hover, panel-grey when selected. + Rounded top only — the flat bottom sits on the under-tab divider. aria-selected lives on + the inner link. */ +[class*='awsui_tabs-tab-link'] { + color: var(--cs-muted) !important; + padding-block: 13px !important; /* with the label padding zeroed (d), this matches .gs-tab's 15px/14px content-tab height */ + padding-inline: 14px !important; + border-radius: 8px 8px 0 0 !important; } -[class*='awsui_content-inner']:has(.gpu-scroll) { - padding-inline: 0 !important; - padding-block: 8px !important; /* a little top/bottom breathing room, trimmed from the default */ +[class*='awsui_tabs-tab-link']:hover { + color: var(--cs-text) !important; + background: var(--cs-hover) !important; } -.gpu-scroll tr > :first-child { - padding-inline-start: 20px !important; +[class*='awsui_tabs-tab-link'][aria-selected='true'] { + color: var(--cs-text) !important; + background: var(--cs-panel) !important; } -.gpu-scroll tr > :last-child { - padding-inline-end: 20px !important; +/* d) The label span carries its own inner padding (4px 8px) on top of the link's padding, which + makes the Cloudscape pill ~12px taller than the landing's own tabs. Zero it so the pill height + is just link-padding + text — matching .gs-tab / .gs-skytab exactly. */ +[class*='awsui_tabs-tab-label'] { + padding: 0 !important; } /* 11) Thin (0.5px) dividers between stacked FAQ items. The block's outer corners diff --git a/website/src/cloudscape-theme.ts b/website/src/cloudscape-theme.ts index 18d98325f..f5da772ff 100644 --- a/website/src/cloudscape-theme.ts +++ b/website/src/cloudscape-theme.ts @@ -112,3 +112,8 @@ export const mainButtonStyle: ButtonProps.Style = { export const menuButtonStyle: ButtonProps.Style = { root: { paddingBlock: '7px', paddingInline: '18px' }, }; +// Header "Get started" — same as menuButtonStyle but with 1px extra on the right so the label +// doesn't sit a hair close to the edge (inline-end 19px vs inline-start 18px). +export const getStartedButtonStyle: ButtonProps.Style = { + root: { paddingBlock: '7px', paddingInline: '18px 19px' }, +}; diff --git a/website/src/components/HeroSquircle.tsx b/website/src/components/HeroSquircle.tsx new file mode 100644 index 000000000..f6c5e9939 --- /dev/null +++ b/website/src/components/HeroSquircle.tsx @@ -0,0 +1,100 @@ +import { + SquircleScene, + type SquircleGeometryConfig, + type SquircleLayerConfig, + type SquircleLayerHoverContext, +} from '@dstackai/sqircle'; +import '@dstackai/sqircle/style.css'; +import type { ThemeMode } from '../theme'; + +// Live hero object, composed in the squircle constructor (@dstackai/sqircle) and pasted here. +// Layers, back → front: wireframe slab (dashed inlay) → wireframe slab → solid +// "GPU" face (dotted inlay). Layers cross-react on hover, and clicking any layer scrolls to +// Get-started. `theme` stays driven by the app toggle (the snippet hardcodes "light"). +const HERO_GEOMETRY: SquircleGeometryConfig = { + angleDegrees: 20, +}; + +const HERO_LAYERS: SquircleLayerConfig[] = [ + { + id: 'layer-1', + visible: true, + offset: { x: 0, y: 176 }, + base: { + material: 'wireframe', + paletteId: '15', + line: 'dashed', + lineColor: 'auto', + effect: 'off', + }, + stroke: { wire: 1.6, line: 2.2, face: 0, wireLine: 2.2 }, + // Hovering this bottom slab itself turns it into a solid metal "dstack" face (no inlay line). + hover: (ctx: SquircleLayerHoverContext) => { + if (ctx.hoveredLayerId === 'layer-1') + return { material: 'solid', paletteId: '20', effect: 'metal', text: 'dstack', line: false } + return false + } + }, + { + id: 'layer-2', + visible: true, + offset: { x: 0, y: 88 }, + base: { + material: 'transparent', + paletteId: '20', + // line: false, + }, + stroke: { face: 0 }, + // Cross-layer hover (0.1.4 resolver API): hovering the top (layer-3) or bottom (layer-1) + // slab keeps this middle a wireframe; hovering the middle itself turns it transparent. + hover: (ctx: SquircleLayerHoverContext) => { + if (ctx.hoveredLayerId === 'layer-2') return { material: 'wireframe' }; + if (ctx.hoveredLayerId === 'layer-1') + return { material: 'wireframe' }; + return false; + }, + }, + { + id: 'layer-3', + visible: true, + offset: { x: 0, y: 0 }, + base: { + material: 'solid', + paletteId: '20', + effect: 'metal', + text: 'GPU', + textColor: 'auto', + textStyle: 'solid', + // line: 'dotted', + lineColor: 'auto', + grain: true, + }, + // Top "GPU" face hover behavior: + // - hovering itself (layer-3): becomes solid palette 20 with the metal effect; + // - hovering the bottom (layer-1): wireframe, "GPU" text outlined, inlay line removed; + // - hovering the middle (layer-2): wireframe, "GPU" text outlined (line kept). + hover: (ctx: SquircleLayerHoverContext) => { + if (ctx.hoveredLayerId === 'layer-1') return { material: 'wireframe', line: 'dotted', palletteId: 15, textStyle: 'wireframe' }; + // if (ctx.hoveredLayerId === 'layer-2') return { material: 'wireframe', line: 'dotted', palletteId: 15 }; + if (ctx.hoveredLayerId === 'layer-3') return { material: 'wireframe', line: 'dotted', palletteId: 15 }; + return false; + }, + }, +]; + +export function HeroSquircle({ theme }: { theme: ThemeMode }) { + return ( +
+ + document.getElementById('resources')?.scrollIntoView({ behavior: 'smooth' }) + } + /> +
+ ); +} diff --git a/website/src/components/SiteFooter.tsx b/website/src/components/SiteFooter.tsx index c8625fe08..dbb8c83a3 100644 --- a/website/src/components/SiteFooter.tsx +++ b/website/src/components/SiteFooter.tsx @@ -66,7 +66,7 @@ const footerColumns: FooterColumn[] = [ heading: 'Company', links: [ { label: 'Blog', href: BLOG_URL }, - { label: 'Talk to us', href: 'https://calendly.com/dstackai/discovery-call', external: true }, + { label: 'Get a demo', href: 'https://calendly.com/dstackai/discovery-call', external: true }, { label: 'Terms of service', href: TERMS_URL }, { label: 'Privacy policy', href: PRIVACY_URL }, ], diff --git a/website/src/components/SiteNavigation.tsx b/website/src/components/SiteNavigation.tsx index 28b99d46d..4eac7b1bc 100644 --- a/website/src/components/SiteNavigation.tsx +++ b/website/src/components/SiteNavigation.tsx @@ -1,66 +1,190 @@ -import { useState } from 'react'; +import { ReactNode, useEffect, useRef, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import Button from '@cloudscape-design/components/button'; -import ButtonDropdown, { ButtonDropdownProps } from '@cloudscape-design/components/button-dropdown'; +import Icon from '@cloudscape-design/components/icon'; import SideNavigation, { SideNavigationProps } from '@cloudscape-design/components/side-navigation'; import SpaceBetween from '@cloudscape-design/components/space-between'; -import { menuButtonStyle } from '../cloudscape-theme'; +import { getStartedButtonStyle, menuButtonStyle } from '../cloudscape-theme'; import { ThemeToggle } from './ThemeToggle'; import { asset } from '../asset'; -import { BLOG_URL, DOCS_URL, ROUTES, docsUrl } from '../routes'; +import { BLOG_URL, DOCS_URL, ROUTES } from '../routes'; import { ThemeMode } from '../theme'; const dstackGithubUrl = 'https://github.com/dstackai/dstack'; +const dstackGithubApiUrl = 'https://api.github.com/repos/dstackai/dstack'; const externalIconAriaLabel = 'External link icon'; +// Compact star count: 1340 → "1.3k", 12000 → "12k", 980 → "980". +function formatStars(count: number): string { + if (count < 1000) return String(count); + const thousands = count / 1000; + return `${thousands >= 10 ? Math.round(thousands) : Number(thousands.toFixed(1))}k`; +} + +// Monochrome product glyphs for the "Products" menu (GitHub mark also doubles as the star badge). +const GithubGlyph = () => ( + +); +const CloudUploadGlyph = () => ( + +); +const FingerprintGlyph = () => ( + +); + // Primary links in the desktop top navigation (plain same-origin MkDocs links). The blog -// categories are listed individually (Case studies / Benchmarks / Blog) to mirror the docs +// categories are listed individually (Case studies / Blog) to mirror the docs // header tabs — no "Resources" dropdown. const audienceNavItems: Array<{ label: string; href: string }> = [ { label: 'Docs', href: DOCS_URL }, { label: 'Case studies', href: `${BLOG_URL}/case-studies/` }, - { label: 'Benchmarks', href: `${BLOG_URL}/benchmarks/` }, { label: 'Blog', href: BLOG_URL }, ]; -// "Get started" dropdown items. secondaryText is shown under each label. -const productDropdownItems: ButtonDropdownProps.Items = [ - { - text: 'Products', - items: [ - { id: 'open-source', text: 'dstack', secondaryText: 'The open-source control plane that works across clouds, Kubernetes, and on-prem.', href: docsUrl('installation') }, - { id: 'sky-product', text: 'dstack Sky', secondaryText: 'Access GPU marketplace, or bring your own clouds. Hosted by us.', href: 'https://sky.dstack.ai', external: true, externalIconAriaLabel }, - { id: 'enterprise', text: 'dstack Enterprise', secondaryText: 'Self-hosted with SSO, air-gapped setup, dedicated support, and more.', href: 'https://calendly.com/dstackai/discovery-call', external: true, externalIconAriaLabel }, - ], - }, - { - text: 'Login', - items: [ - { id: 'sky-login', text: 'dstack Sky', href: 'https://sky.dstack.ai', external: true, externalIconAriaLabel }, - ], - }, +type ProductLink = { + id: string; + text: string; + secondaryText: string; + href: string; + external?: boolean; + icon: ReactNode; +}; + +// The products. products[0] (open-source) is featured at the top of the "Products" menu; the rest +// follow as rows. Reused by the standalone top-nav hover menu and the mobile nav's "Products" +// section. +const products: ProductLink[] = [ + { id: 'open-source', text: 'dstack', secondaryText: 'The open-source control plane that works across clouds, Kubernetes, and on-prem.', href: DOCS_URL, icon: }, + { id: 'sky-product', text: 'dstack Sky', secondaryText: 'Access GPU marketplace, or bring your own clouds. Hosted and managed by us.', href: 'https://sky.dstack.ai', external: true, icon: }, + { id: 'enterprise', text: 'Enterprise', secondaryText: 'Self-hosted with SSO, air-gapped setup, dedicated support, and more.', href: 'https://calendly.com/dstackai/discovery-call', external: true, icon: }, ]; // Items for the mobile slide-out navigation. The blog categories are top-level links (mirroring // the flattened desktop nav), not a "Resources" section. const mobileNavigationItems: SideNavigationProps.Item[] = [ - { type: 'link', text: 'Docs', href: DOCS_URL }, - { type: 'link', text: 'Case studies', href: `${BLOG_URL}/case-studies/` }, - { type: 'link', text: 'Benchmarks', href: `${BLOG_URL}/benchmarks/` }, - { type: 'link', text: 'Blog', href: BLOG_URL }, - { type: 'link', text: 'GitHub', href: dstackGithubUrl, external: true, externalIconAriaLabel }, { type: 'section', - text: 'Get started', + text: 'Products', defaultExpanded: true, - items: [ - { type: 'link', text: 'dstack', href: docsUrl('installation'), external: true, externalIconAriaLabel }, - { type: 'link', text: 'dstack Sky', href: 'https://sky.dstack.ai', external: true, externalIconAriaLabel }, - { type: 'link', text: 'dstack Enterprise', href: 'https://calendly.com/dstackai/discovery-call', external: true, externalIconAriaLabel }, - ], + items: products.map((p): SideNavigationProps.Item => ({ + type: 'link', + text: p.text, + href: p.href, + ...(p.external ? { external: true, externalIconAriaLabel } : {}), + })), }, + { type: 'link', text: 'Docs', href: DOCS_URL }, + { type: 'link', text: 'Case studies', href: `${BLOG_URL}/case-studies/` }, + { type: 'link', text: 'Blog', href: BLOG_URL }, + { type: 'link', text: 'GitHub', href: dstackGithubUrl, external: true, externalIconAriaLabel }, ]; +// Standalone "Products" top-nav menu. The popup opens on hover (and on keyboard focus) and +// closes once the pointer/focus leaves both the trigger and the popup. The trigger reads like +// the plain text links beside it — no dropdown caret. A short close delay (hover-intent, below) +// lets the pointer cross the gap from trigger to popup without the menu dropping. +function ProductsHoverMenu() { + const [open, setOpen] = useState(false); + // Hover-intent: leaving the trigger schedules a close, but re-entering the wrapper (e.g. moving + // onto the popup, which is a child) cancels it — so the small gap between trigger and popup + // doesn't drop the menu. + const closeTimer = useRef(undefined); + const openMenu = () => { + window.clearTimeout(closeTimer.current); + setOpen(true); + }; + const scheduleClose = () => { + window.clearTimeout(closeTimer.current); + closeTimer.current = window.setTimeout(() => setOpen(false), 150); + }; + + // GitHub star count for the open-source repo, fetched once the menu first opens. Best-effort: + // if the API is rate-limited or errors, the badge simply doesn't render. + const [stars, setStars] = useState(null); + const starsFetched = useRef(false); + useEffect(() => { + if (!open || starsFetched.current) return; + starsFetched.current = true; + fetch(dstackGithubApiUrl) + .then(response => (response.ok ? response.json() : null)) + .then(data => { + if (data && typeof data.stargazers_count === 'number') setStars(data.stargazers_count); + }) + .catch(() => {}); + }, [open]); + + return ( +
{ + if (!event.currentTarget.contains(event.relatedTarget as Node | null)) { + setOpen(false); + } + }} + > + + {open && ( +
+ {/* Open-source featured on the brand gradient; the whole panel links to install. */} + + + + {stars !== null && ( + {formatStars(stars)} + )} + + + {products[0].text} + {products[0].secondaryText} + + + +
+ )} +
+ ); +} + // Global top navigation. On the Old page it also renders the trigger that toggles // that page's side navigation drawer (state owned by the App layout). export function SiteNavigation({ @@ -86,20 +210,17 @@ export function SiteNavigation({ setMobileNavigationOpen(false); }; - // Scroll to the "Get started" section, navigating home first if we're on another page. - const scrollToResources = () => { - const target = document.getElementById('resources'); - if (target) { - target.scrollIntoView({ behavior: 'smooth', block: 'start' }); - return; + // "Get started" scrolls to the Get-started section on the home page (id="resources"). From any + // other route, go home first, then scroll once it has rendered. + const goToGetStarted = (event: { preventDefault: () => void }) => { + event.preventDefault(); + setMobileNavigationOpen(false); + if (pathname === ROUTES.HOME) { + document.getElementById('resources')?.scrollIntoView({ behavior: 'smooth' }); + } else { + navigate(ROUTES.HOME); + window.setTimeout(() => document.getElementById('resources')?.scrollIntoView({ behavior: 'smooth' }), 120); } - - go(ROUTES.HOME); - window.requestAnimationFrame(() => { - window.requestAnimationFrame(() => { - document.getElementById('resources')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); - }); - }); }; return ( @@ -131,6 +252,8 @@ export function SiteNavigation({