skypilot-nightly 1.0.0.dev20250604__py3-none-any.whl → 1.0.0.dev20250605__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. sky/__init__.py +2 -2
  2. sky/admin_policy.py +5 -0
  3. sky/catalog/__init__.py +2 -2
  4. sky/catalog/common.py +7 -9
  5. sky/cli.py +11 -9
  6. sky/client/cli.py +11 -9
  7. sky/client/sdk.py +30 -12
  8. sky/dashboard/out/404.html +1 -1
  9. sky/dashboard/out/_next/static/chunks/614-635a84e87800f99e.js +66 -0
  10. sky/dashboard/out/_next/static/chunks/{856-f1b1f7f47edde2e8.js → 856-3a32da4b84176f6d.js} +1 -1
  11. sky/dashboard/out/_next/static/chunks/937.3759f538f11a0953.js +1 -0
  12. sky/dashboard/out/_next/static/chunks/pages/config-1a1eeb949dab8897.js +6 -0
  13. sky/dashboard/out/_next/static/chunks/pages/users-262aab38b9baaf3a.js +16 -0
  14. sky/dashboard/out/_next/static/chunks/pages/workspaces-384ea5fa0cea8f28.js +1 -0
  15. sky/dashboard/out/_next/static/chunks/{webpack-f27c9a32aa3d9c6d.js → webpack-65d465f948974c0d.js} +1 -1
  16. sky/dashboard/out/_next/static/css/667d941a2888ce6e.css +3 -0
  17. sky/dashboard/out/_next/static/qjhIe-yC6nHcLKBqpzO1M/_buildManifest.js +1 -0
  18. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  19. sky/dashboard/out/clusters/[cluster].html +1 -1
  20. sky/dashboard/out/clusters.html +1 -1
  21. sky/dashboard/out/config.html +1 -1
  22. sky/dashboard/out/index.html +1 -1
  23. sky/dashboard/out/infra/[context].html +1 -1
  24. sky/dashboard/out/infra.html +1 -1
  25. sky/dashboard/out/jobs/[job].html +1 -1
  26. sky/dashboard/out/jobs.html +1 -1
  27. sky/dashboard/out/users.html +1 -1
  28. sky/dashboard/out/workspace/new.html +1 -1
  29. sky/dashboard/out/workspaces/[name].html +1 -1
  30. sky/dashboard/out/workspaces.html +1 -1
  31. sky/execution.py +44 -46
  32. sky/global_user_state.py +118 -83
  33. sky/jobs/client/sdk.py +4 -1
  34. sky/jobs/server/core.py +5 -1
  35. sky/models.py +1 -0
  36. sky/resources.py +22 -1
  37. sky/server/constants.py +3 -1
  38. sky/server/requests/payloads.py +9 -0
  39. sky/server/server.py +30 -9
  40. sky/setup_files/MANIFEST.in +1 -0
  41. sky/setup_files/dependencies.py +2 -0
  42. sky/skylet/constants.py +10 -4
  43. sky/skypilot_config.py +4 -2
  44. sky/templates/websocket_proxy.py +11 -1
  45. sky/users/__init__.py +0 -0
  46. sky/users/model.conf +15 -0
  47. sky/users/permission.py +178 -0
  48. sky/users/rbac.py +86 -0
  49. sky/users/server.py +66 -0
  50. sky/utils/schemas.py +20 -7
  51. sky/workspaces/core.py +2 -2
  52. {skypilot_nightly-1.0.0.dev20250604.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/METADATA +3 -1
  53. {skypilot_nightly-1.0.0.dev20250604.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/RECORD +68 -64
  54. sky/catalog/constants.py +0 -8
  55. sky/dashboard/out/_next/static/chunks/614-3d29f98e0634b179.js +0 -66
  56. sky/dashboard/out/_next/static/chunks/937.f97f83652028e944.js +0 -1
  57. sky/dashboard/out/_next/static/chunks/pages/config-35383adcb0edb5e2.js +0 -6
  58. sky/dashboard/out/_next/static/chunks/pages/users-07b523ccb19317ad.js +0 -6
  59. sky/dashboard/out/_next/static/chunks/pages/workspaces-f54921ec9eb20965.js +0 -1
  60. sky/dashboard/out/_next/static/css/63d3995d8b528eb1.css +0 -3
  61. sky/dashboard/out/_next/static/vWwfD3jOky5J5jULHp8JT/_buildManifest.js +0 -1
  62. /sky/dashboard/out/_next/static/chunks/{121-8f55ee3fa6301784.js → 121-865d2bf8a3b84c6a.js} +0 -0
  63. /sky/dashboard/out/_next/static/chunks/{236-fef38aa6e5639300.js → 236-4c0dc6f63ccc6319.js} +0 -0
  64. /sky/dashboard/out/_next/static/chunks/{37-947904ccc5687bac.js → 37-beedd583fea84cc8.js} +0 -0
  65. /sky/dashboard/out/_next/static/chunks/{682-2be9b0f169727f2f.js → 682-6647f0417d5662f0.js} +0 -0
  66. /sky/dashboard/out/_next/static/chunks/{843-a097338acb89b7d7.js → 843-c296541442d4af88.js} +0 -0
  67. /sky/dashboard/out/_next/static/chunks/{969-d7b6fb7f602bfcb3.js → 969-c7abda31c10440ac.js} +0 -0
  68. /sky/dashboard/out/_next/static/chunks/pages/{_app-67925f5e6382e22f.js → _app-cb81dc4d27f4d009.js} +0 -0
  69. /sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/{[job]-158b70da336d8607.js → [job]-65d04d5d77cbb6b6.js} +0 -0
  70. /sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-62c9982dc3675725.js → [cluster]-beabbcd7606c1a23.js} +0 -0
  71. /sky/dashboard/out/_next/static/chunks/pages/jobs/{[job]-a62a3c65dc9bc57c.js → [job]-86c47edc500f15f9.js} +0 -0
  72. /sky/dashboard/out/_next/static/{vWwfD3jOky5J5jULHp8JT → qjhIe-yC6nHcLKBqpzO1M}/_ssgManifest.js +0 -0
  73. {skypilot_nightly-1.0.0.dev20250604.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/WHEEL +0 -0
  74. {skypilot_nightly-1.0.0.dev20250604.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/entry_points.txt +0 -0
  75. {skypilot_nightly-1.0.0.dev20250604.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/licenses/LICENSE +0 -0
  76. {skypilot_nightly-1.0.0.dev20250604.dist-info → skypilot_nightly-1.0.0.dev20250605.dist-info}/top_level.txt +0 -0
sky/__init__.py CHANGED
@@ -5,7 +5,7 @@ from typing import Optional
5
5
  import urllib.request
6
6
 
7
7
  # Replaced with the current commit when building the wheels.
8
- _SKYPILOT_COMMIT_SHA = '3c15a1fa980f6ac4d4ed742bb2e538dcf3c6650f'
8
+ _SKYPILOT_COMMIT_SHA = '5042bb8db4809907ebb3271f1abdb96ab3ffffb1'
9
9
 
10
10
 
11
11
  def _get_git_commit():
@@ -35,7 +35,7 @@ def _get_git_commit():
35
35
 
36
36
 
37
37
  __commit__ = _get_git_commit()
38
- __version__ = '1.0.0.dev20250604'
38
+ __version__ = '1.0.0.dev20250605'
39
39
  __root_dir__ = os.path.dirname(os.path.abspath(__file__))
40
40
 
41
41
 
sky/admin_policy.py CHANGED
@@ -21,6 +21,11 @@ class RequestOptions:
21
21
  dryrun: Is the request a dryrun?
22
22
  """
23
23
  cluster_name: Optional[str]
24
+ # Keep these two fields for backward compatibility. The values are copied
25
+ # from task.resources.autostop_config, so that legacy admin policy plugins
26
+ # can still read the correct autostop config from request options before
27
+ # we drop the compatibility.
28
+ # TODO(aylei): remove these fields after 0.12.0
24
29
  idle_minutes_to_autostop: Optional[int]
25
30
  down: bool
26
31
  dryrun: bool
sky/catalog/__init__.py CHANGED
@@ -4,8 +4,8 @@ import importlib
4
4
  import typing
5
5
  from typing import Dict, List, Optional, Set, Tuple, Union
6
6
 
7
- from sky.catalog import constants as service_catalog_constants
8
7
  from sky.catalog.config import fallback_to_default_catalog
8
+ from sky.skylet import constants
9
9
  from sky.utils import resources_utils
10
10
  from sky.utils import subprocess_utils
11
11
 
@@ -18,7 +18,7 @@ CloudFilter = Optional[Union[List[str], str]]
18
18
 
19
19
  def _map_clouds_catalog(clouds: CloudFilter, method_name: str, *args, **kwargs):
20
20
  if clouds is None:
21
- clouds = list(service_catalog_constants.ALL_CLOUDS)
21
+ clouds = list(constants.ALL_CLOUDS)
22
22
 
23
23
  # TODO(hemil): Remove this once the common service catalog
24
24
  # functions are refactored from clouds/kubernetes.py to
sky/catalog/common.py CHANGED
@@ -11,8 +11,8 @@ import filelock
11
11
 
12
12
  from sky import sky_logging
13
13
  from sky.adaptors import common as adaptors_common
14
- from sky.catalog import constants as service_catalog_constants
15
14
  from sky.clouds import cloud as cloud_lib
15
+ from sky.skylet import constants
16
16
  from sky.utils import common_utils
17
17
  from sky.utils import registry
18
18
  from sky.utils import rich_utils
@@ -28,8 +28,7 @@ else:
28
28
  logger = sky_logging.init_logger(__name__)
29
29
 
30
30
  _ABSOLUTE_VERSIONED_CATALOG_DIR = os.path.join(
31
- os.path.expanduser(service_catalog_constants.CATALOG_DIR),
32
- service_catalog_constants.CATALOG_SCHEMA_VERSION)
31
+ os.path.expanduser(constants.CATALOG_DIR), constants.CATALOG_SCHEMA_VERSION)
33
32
  os.makedirs(_ABSOLUTE_VERSIONED_CATALOG_DIR, exist_ok=True)
34
33
 
35
34
 
@@ -96,7 +95,7 @@ def get_modified_catalog_file_mounts() -> Dict[str, str]:
96
95
  def _get_modified_catalogs() -> List[str]:
97
96
  """Returns a list of modified catalogs relative to the catalog dir."""
98
97
  modified_catalogs = []
99
- for cloud_name in service_catalog_constants.ALL_CLOUDS:
98
+ for cloud_name in constants.ALL_CLOUDS:
100
99
  cloud_catalog_dir = os.path.join(_ABSOLUTE_VERSIONED_CATALOG_DIR,
101
100
  cloud_name)
102
101
  if not os.path.exists(cloud_catalog_dir):
@@ -114,9 +113,8 @@ def get_modified_catalog_file_mounts() -> Dict[str, str]:
114
113
  modified_catalog_path_map = {} # Map of remote: local catalog paths
115
114
  for catalog in modified_catalog_list:
116
115
  # Use relative paths for remote to handle varying usernames on the cloud
117
- remote_path = os.path.join(
118
- service_catalog_constants.CATALOG_DIR,
119
- service_catalog_constants.CATALOG_SCHEMA_VERSION, catalog)
116
+ remote_path = os.path.join(constants.CATALOG_DIR,
117
+ constants.CATALOG_SCHEMA_VERSION, catalog)
120
118
  local_path = os.path.expanduser(remote_path)
121
119
  modified_catalog_path_map[remote_path] = local_path
122
120
  return modified_catalog_path_map
@@ -198,8 +196,8 @@ def read_catalog(filename: str,
198
196
  # Atomic check, to avoid conflicts with other processes.
199
197
  with filelock.FileLock(meta_path + '.lock'):
200
198
  if _need_update():
201
- url = f'{service_catalog_constants.HOSTED_CATALOG_DIR_URL}/{service_catalog_constants.CATALOG_SCHEMA_VERSION}/{filename}' # pylint: disable=line-too-long
202
- url_fallback = f'{service_catalog_constants.HOSTED_CATALOG_DIR_URL_S3_MIRROR}/{service_catalog_constants.CATALOG_SCHEMA_VERSION}/{filename}' # pylint: disable=line-too-long
199
+ url = f'{constants.HOSTED_CATALOG_DIR_URL}/{constants.CATALOG_SCHEMA_VERSION}/{filename}' # pylint: disable=line-too-long
200
+ url_fallback = f'{constants.HOSTED_CATALOG_DIR_URL_S3_MIRROR}/{constants.CATALOG_SCHEMA_VERSION}/{filename}' # pylint: disable=line-too-long
203
201
  headers = {'User-Agent': 'SkyPilot/0.7'}
204
202
  update_frequency_str = ''
205
203
  if pull_frequency_hours is not None:
sky/cli.py CHANGED
@@ -26,6 +26,7 @@ each other.
26
26
  import collections
27
27
  import copy
28
28
  import datetime
29
+ import fnmatch
29
30
  import functools
30
31
  import getpass
31
32
  import os
@@ -61,7 +62,6 @@ from sky import skypilot_config
61
62
  from sky.adaptors import common as adaptors_common
62
63
  from sky.benchmark import benchmark_state
63
64
  from sky.benchmark import benchmark_utils
64
- from sky.catalog import constants as service_catalog_constants
65
65
  from sky.client import sdk
66
66
  from sky.data import storage_utils
67
67
  from sky.provision.kubernetes import constants as kubernetes_constants
@@ -209,14 +209,14 @@ def _get_cluster_records_and_set_ssh_config(
209
209
  return cluster_records
210
210
 
211
211
 
212
- def _get_glob_storages(storages: List[str]) -> List[str]:
213
- """Returns a list of storages that match the glob pattern."""
212
+ def _get_glob_matches(candidate_names: List[str],
213
+ glob_patterns: List[str]) -> List[str]:
214
+ """Returns a list of names that match the glob pattern."""
214
215
  glob_storages = []
215
- for storage_object in storages:
216
- # TODO(zhwu): client side should not rely on global_user_state.
217
- glob_storage = global_user_state.get_glob_storage_name(storage_object)
216
+ for glob_pattern in glob_patterns:
217
+ glob_storage = fnmatch.filter(candidate_names, glob_pattern)
218
218
  if not glob_storage:
219
- click.echo(f'Storage {storage_object} not found.')
219
+ click.echo(f'Storage {glob_pattern} not found.')
220
220
  glob_storages.extend(glob_storage)
221
221
  return list(set(glob_storages))
222
222
 
@@ -3836,7 +3836,7 @@ def show_gpus(
3836
3836
  clouds_to_list: Union[Optional[str], List[str]] = cloud_name
3837
3837
  if cloud_name is None:
3838
3838
  clouds_to_list = [
3839
- c for c in service_catalog_constants.ALL_CLOUDS
3839
+ c for c in constants.ALL_CLOUDS
3840
3840
  if c != 'kubernetes' and c != 'ssh'
3841
3841
  ]
3842
3842
 
@@ -4135,7 +4135,9 @@ def storage_delete(names: List[str], all: bool, yes: bool, async_call: bool): #
4135
4135
  return
4136
4136
  names = [storage['name'] for storage in storages]
4137
4137
  else:
4138
- names = _get_glob_storages(names)
4138
+ storages = sdk.get(sdk.storage_ls())
4139
+ existing_storage_names = [storage['name'] for storage in storages]
4140
+ names = _get_glob_matches(existing_storage_names, names)
4139
4141
  if names:
4140
4142
  if not yes:
4141
4143
  storage_names = ', '.join(names)
sky/client/cli.py CHANGED
@@ -26,6 +26,7 @@ each other.
26
26
  import collections
27
27
  import copy
28
28
  import datetime
29
+ import fnmatch
29
30
  import functools
30
31
  import getpass
31
32
  import os
@@ -61,7 +62,6 @@ from sky import skypilot_config
61
62
  from sky.adaptors import common as adaptors_common
62
63
  from sky.benchmark import benchmark_state
63
64
  from sky.benchmark import benchmark_utils
64
- from sky.catalog import constants as service_catalog_constants
65
65
  from sky.client import sdk
66
66
  from sky.data import storage_utils
67
67
  from sky.provision.kubernetes import constants as kubernetes_constants
@@ -209,14 +209,14 @@ def _get_cluster_records_and_set_ssh_config(
209
209
  return cluster_records
210
210
 
211
211
 
212
- def _get_glob_storages(storages: List[str]) -> List[str]:
213
- """Returns a list of storages that match the glob pattern."""
212
+ def _get_glob_matches(candidate_names: List[str],
213
+ glob_patterns: List[str]) -> List[str]:
214
+ """Returns a list of names that match the glob pattern."""
214
215
  glob_storages = []
215
- for storage_object in storages:
216
- # TODO(zhwu): client side should not rely on global_user_state.
217
- glob_storage = global_user_state.get_glob_storage_name(storage_object)
216
+ for glob_pattern in glob_patterns:
217
+ glob_storage = fnmatch.filter(candidate_names, glob_pattern)
218
218
  if not glob_storage:
219
- click.echo(f'Storage {storage_object} not found.')
219
+ click.echo(f'Storage {glob_pattern} not found.')
220
220
  glob_storages.extend(glob_storage)
221
221
  return list(set(glob_storages))
222
222
 
@@ -3836,7 +3836,7 @@ def show_gpus(
3836
3836
  clouds_to_list: Union[Optional[str], List[str]] = cloud_name
3837
3837
  if cloud_name is None:
3838
3838
  clouds_to_list = [
3839
- c for c in service_catalog_constants.ALL_CLOUDS
3839
+ c for c in constants.ALL_CLOUDS
3840
3840
  if c != 'kubernetes' and c != 'ssh'
3841
3841
  ]
3842
3842
 
@@ -4135,7 +4135,9 @@ def storage_delete(names: List[str], all: bool, yes: bool, async_call: bool): #
4135
4135
  return
4136
4136
  names = [storage['name'] for storage in storages]
4137
4137
  else:
4138
- names = _get_glob_storages(names)
4138
+ storages = sdk.get(sdk.storage_ls())
4139
+ existing_storage_names = [storage['name'] for storage in storages]
4140
+ names = _get_glob_matches(existing_storage_names, names)
4139
4141
  if names:
4140
4142
  if not yes:
4141
4143
  storage_names = ', '.join(names)
sky/client/sdk.py CHANGED
@@ -400,18 +400,22 @@ def launch(
400
400
  retry_until_up: whether to retry launching the cluster until it is
401
401
  up.
402
402
  idle_minutes_to_autostop: automatically stop the cluster after this
403
- many minute of idleness, i.e., no running or pending jobs in the
404
- cluster's job queue. Idleness gets reset whenever setting-up/
405
- running/pending jobs are found in the job queue. Setting this
406
- flag is equivalent to running ``sky.launch()`` and then
407
- ``sky.autostop(idle_minutes=<minutes>)``. If not set, the cluster
408
- will not be autostopped.
403
+ many minute of idleness, i.e., no running or pending jobs in the
404
+ cluster's job queue. Idleness gets reset whenever setting-up/
405
+ running/pending jobs are found in the job queue. Setting this
406
+ flag is equivalent to running
407
+ ``sky.launch(...)`` and then
408
+ ``sky.autostop(idle_minutes=<minutes>)``. If set, the autostop
409
+ config specified in the task' resources will be overridden by
410
+ this parameter.
409
411
  dryrun: if True, do not actually launch the cluster.
410
412
  down: Tear down the cluster after all jobs finish (successfully or
411
- abnormally). If --idle-minutes-to-autostop is also set, the
412
- cluster will be torn down after the specified idle time.
413
- Note that if errors occur during provisioning/data syncing/setting
414
- up, the cluster will not be torn down for debugging purposes.
413
+ abnormally). If --idle-minutes-to-autostop is also set, the
414
+ cluster will be torn down after the specified idle time.
415
+ Note that if errors occur during provisioning/data syncing/setting
416
+ up, the cluster will not be torn down for debugging purposes. If
417
+ set, the autostop config specified in the task' resources will be
418
+ overridden by this parameter.
415
419
  backend: backend to use. If None, use the default backend
416
420
  (CloudVMRayBackend).
417
421
  optimize_target: target to optimize for. Choices: OptimizeTarget.COST,
@@ -468,12 +472,28 @@ def launch(
468
472
  'Please contact the SkyPilot team if you '
469
473
  'need this feature at slack.skypilot.co.')
470
474
  dag = dag_utils.convert_entrypoint_to_dag(task)
475
+ # Override the autostop config from command line flags to task YAML.
476
+ for task in dag.tasks:
477
+ for resource in task.resources:
478
+ resource.override_autostop_config(
479
+ down=down, idle_minutes=idle_minutes_to_autostop)
480
+ if resource.autostop_config is not None:
481
+ # For backward-compatbility, get the final autostop config for
482
+ # admin policy.
483
+ # TODO(aylei): remove this after 0.12.0
484
+ down = resource.autostop_config.down
485
+ idle_minutes_to_autostop = resource.autostop_config.idle_minutes
486
+
471
487
  request_options = admin_policy.RequestOptions(
472
488
  cluster_name=cluster_name,
473
489
  idle_minutes_to_autostop=idle_minutes_to_autostop,
474
490
  down=down,
475
491
  dryrun=dryrun)
476
492
  validate(dag, admin_policy_request_options=request_options)
493
+ # The flags have been applied to the task YAML and the backward
494
+ # compatibility of admin policy has been handled. We should no longer use
495
+ # these flags.
496
+ del down, idle_minutes_to_autostop
477
497
 
478
498
  confirm_shown = False
479
499
  if _need_confirmation:
@@ -537,9 +557,7 @@ def launch(
537
557
  task=dag_str,
538
558
  cluster_name=cluster_name,
539
559
  retry_until_up=retry_until_up,
540
- idle_minutes_to_autostop=idle_minutes_to_autostop,
541
560
  dryrun=dryrun,
542
- down=down,
543
561
  backend=backend.NAME if backend else None,
544
562
  optimize_target=optimize_target,
545
563
  no_setup=no_setup,
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><title>404: This page could not be found</title><meta name="next-head-count" content="3"/><link rel="preload" href="/dashboard/_next/static/css/63d3995d8b528eb1.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/63d3995d8b528eb1.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-f27c9a32aa3d9c6d.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-87d061ee6ed71b28.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-e0e2335212e72357.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-67925f5e6382e22f.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-1be831200e60c5c0.js" defer=""></script><script src="/dashboard/_next/static/vWwfD3jOky5J5jULHp8JT/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/vWwfD3jOky5J5jULHp8JT/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"vWwfD3jOky5J5jULHp8JT","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><title>404: This page could not be found</title><meta name="next-head-count" content="3"/><link rel="preload" href="/dashboard/_next/static/css/667d941a2888ce6e.css" as="style"/><link rel="stylesheet" href="/dashboard/_next/static/css/667d941a2888ce6e.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/dashboard/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js"></script><script src="/dashboard/_next/static/chunks/webpack-65d465f948974c0d.js" defer=""></script><script src="/dashboard/_next/static/chunks/framework-87d061ee6ed71b28.js" defer=""></script><script src="/dashboard/_next/static/chunks/main-e0e2335212e72357.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_app-cb81dc4d27f4d009.js" defer=""></script><script src="/dashboard/_next/static/chunks/pages/_error-1be831200e60c5c0.js" defer=""></script><script src="/dashboard/_next/static/qjhIe-yC6nHcLKBqpzO1M/_buildManifest.js" defer=""></script><script src="/dashboard/_next/static/qjhIe-yC6nHcLKBqpzO1M/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"qjhIe-yC6nHcLKBqpzO1M","assetPrefix":"/dashboard","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>