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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/_nebari/stages/kubernetes_services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,33 @@ class ArgoWorkflows(schema.Base):
nebari_workflow_controller: NebariWorkflowController = NebariWorkflowController()


class JhubAppsAdditionalService(schema.Base):
"""
List of additional external services to display in JupyterHub's services menu.
Services with `pinned=True` will also appear in the quick access section
for easy access.

- `name` (required): Display name of the service
- `url` (required): URL path for the service
- `description` (optional): Description of the service shown in the UI
- `pinned` (optional): Whether the service should appear in the quick access
section (default: `False`)
- `thumbnail` (optional): URL or base64-encoded data URL for the service icon
(e.g., `"https://..."` or `"data:image/png;base64,..."`)

"""

name: str
url: str
description: Optional[str] = None
pinned: Optional[bool] = None
thumbnail: Optional[str] = None


class JHubApps(schema.Base):
enabled: bool = False
overrides: Dict = {}
overrides: Dict[str, Any] = Field(default_factory=dict)
additional_services: List[JhubAppsAdditionalService] = Field(default_factory=list)


class MonitoringOverrides(schema.Base):
Expand Down Expand Up @@ -558,6 +582,10 @@ class JupyterhubInputVars(schema.Base):
argo_workflows_enabled: bool = Field(alias="argo-workflows-enabled")
jhub_apps_enabled: bool = Field(alias="jhub-apps-enabled")
jhub_apps_overrides: str = Field(alias="jhub-apps-overrides")
jhub_apps_additional_services: List[JhubAppsAdditionalService] = Field(
alias="jhub-apps-additional-services",
default_factory=list,
)
cloud_provider: str = Field(alias="cloud-provider")
jupyterlab_preferred_dir: Optional[str] = Field(alias="jupyterlab-preferred-dir")
shared_fs_type: SharedFsEnum
Expand Down Expand Up @@ -753,6 +781,7 @@ def _node_taint_tolerations(node_group_name: str) -> List[Toleration]:
argo_workflows_enabled=self.config.argo_workflows.enabled,
jhub_apps_enabled=self.config.jhub_apps.enabled,
jhub_apps_overrides=json.dumps(self.config.jhub_apps.overrides),
jhub_apps_additional_services=self.config.jhub_apps.additional_services,
initial_repositories=str(self.config.jupyterlab.initial_repositories),
jupyterlab_default_settings=self.config.jupyterlab.default_settings,
jupyterlab_gallery_settings=self.config.jupyterlab.gallery_settings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ module "jupyterhub" {
jhub-apps-enabled = var.jhub-apps-enabled
node-taint-tolerations = var.node-taint-tolerations
jhub-apps-overrides = var.jhub-apps-overrides
jhub-apps-additional-services = var.jhub-apps-additional-services

extra-mounts = {
"/etc/dask" = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,30 +241,54 @@ def get_conda_store_environments(user_info: dict):
c.JAppsConfig.hub_host = "hub"
c.JAppsConfig.service_workers = 4

# ------------------------------------------------------------------
# Base additional services shown in the JupyterHub Apps UI
# ------------------------------------------------------------------
base_additional_services = [
{
"name": "Monitoring",
"url": "/monitoring",
"description": "System monitoring dashboard",
"pinned": True,
},
{
"name": "Argo",
"url": "/argo",
"description": "Argo Workflows UI",
},
{
"name": "Users",
"url": "/auth/admin/nebari/console/",
"description": "Keycloak user and group management",
},
{
"name": "Environments",
"url": "/conda-store",
"description": "Conda environment manager",
"pinned": True,
},
]

# Extra services injected via Terraform as JSON (custom.jhub-apps-additional-services)
extra_services_raw = z2jh.get_config("custom.jhub-apps-additional-services")
extra_additional_services = []
if extra_services_raw:
try:
extra_additional_services = json.loads(extra_services_raw)
except Exception:
# If misconfigured, fall back to just the base list
extra_additional_services = []

# Set the final list for JHub Apps
c.JAppsConfig.additional_services = (
base_additional_services + extra_additional_services
)

# Apply generic overrides (can override additional_services entirely if desired)
jhub_apps_overrides = json.loads(z2jh.get_config("custom.jhub-apps-overrides"))
for config_key, config_value in jhub_apps_overrides.items():
setattr(c.JAppsConfig, config_key, config_value)

def service_for_jhub_apps(name, url):
return {
"name": name,
"display": True,
"info": {
"name": name,
"url": url,
"external": True,
},
}

c.JupyterHub.services.extend(
[
service_for_jhub_apps(name="Argo", url="/argo"),
service_for_jhub_apps(name="Users", url="/auth/admin/nebari/console/"),
service_for_jhub_apps(name="Environments", url="/conda-store"),
service_for_jhub_apps(name="Monitoring", url="/monitoring"),
]
)

c.JupyterHub.template_paths = theme_template_paths

kwargs = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ resource "helm_release" "jupyterhub" {
conda-store-jhub-apps-token = var.conda-store-jhub-apps-token
jhub-apps-enabled = var.jhub-apps-enabled
jhub-apps-overrides = var.jhub-apps-overrides
jhub-apps-additional-services = jsonencode(var.jhub-apps-additional-services)
initial-repositories = var.initial-repositories
node-taint-tolerations = var.node-taint-tolerations
skel-mount = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,27 @@ variable "node-taint-tolerations" {
effect = string
}))
}

variable "jhub-apps-additional-services" {
description = <<-EOT
Extra services to display in the JupyterHub App Launcher (jhub-apps).
These are *appended* to the built-in base services defined in 02-spawner.py.

Each element has the same shape as JAppsConfig.additional_services:
- name (string, required)
- url (string, required)
- description (string, optional)
- pinned (bool, optional)
- thumbnail (string, optional; URL or data:image/... base64)
EOT

type = list(object({
name = string
url = string
description = optional(string)
pinned = optional(bool)
thumbnail = optional(string)
}))

default = []
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ variable "jhub-apps-overrides" {
default = "{}"
}

variable "jhub-apps-additional-services" {
description = ""
type = list(map(any))
default = [{}]
}

variable "cloud-provider" {
description = "Name of cloud provider."
type = string
Expand Down
Loading