diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 251ef8cc..8229c6e2 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -2,6 +2,8 @@ name: Docker Build
on:
push:
+ pull_request:
+
jobs:
test:
runs-on: ubuntu-latest
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 824053c8..1efb06a6 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -2,6 +2,8 @@ name: Lint
on:
push:
+ pull_request:
+
jobs:
build:
runs-on: ubuntu-latest
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 19e6af86..38ceb032 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,6 +2,8 @@ name: Test
on:
push:
+ pull_request:
+
jobs:
test:
runs-on: ubuntu-latest
diff --git a/jhub_apps/launcher/hub_client.py b/jhub_apps/launcher/hub_client.py
index 29bb8f56..2df1fb58 100644
--- a/jhub_apps/launcher/hub_client.py
+++ b/jhub_apps/launcher/hub_client.py
@@ -1,4 +1,6 @@
import os
+import re
+import uuid
import requests
@@ -35,8 +37,20 @@ def get_server(self, username, servername):
if name == servername:
return server
+ def normalize_server_name(self, servername):
+ # Convert text to lowercase
+ text = servername.lower()
+ # Remove all special characters except spaces and hyphen
+ text = re.sub(r"[^a-z0-9\s-]", "", text)
+ # Replace spaces with hyphens
+ text = text.replace(" ", "-")
+ return text
+
def create_server(self, username, servername, edit=True, params=None):
server = self.get_server(username, servername)
+ if not edit:
+ servername = self.normalize_server_name(servername)
+ servername = f"{servername}-{uuid.uuid4().hex[:7]}"
if server:
if edit:
self.delete_server(username, server["name"])
diff --git a/jhub_apps/launcher/panel_app.py b/jhub_apps/launcher/panel_app.py
index 82d70cbf..9a1b20cf 100644
--- a/jhub_apps/launcher/panel_app.py
+++ b/jhub_apps/launcher/panel_app.py
@@ -4,12 +4,53 @@
import panel as pn
from jhub_apps.launcher.hub_client import HubClient
-from jhub_apps.spawner.types import Framework, FRAMEWORKS_MAPPING, FrameworkConf
+from jhub_apps.spawner.types import FRAMEWORKS_MAPPING, FrameworkConf
EDIT_APP_BTN_TXT = "Edit App"
CREATE_APP_BTN_TXT = "Create App"
+css = """
+.custom-font {
+ font-family: Mukta, sans-serif;
+ font-size: 1.3em;
+}
+.bk-input {
+ font-family: Mukta, sans-serif;
+ font-size: 1.1em;
+}
+.bk-btn {
+ font-family: Mukta, sans-serif;
+ font-size: 1.4em;
+}
+.bk-btn:hover {
+ background: #034c76 !important;
+ color: white !important;
+}
+.bk-btn-danger:hover {
+ background: #dc3545 !important;
+ color: white !important;
+}
+.custom-heading {
+ text-align: center;
+ word-wrap: break-word;
+}
+
+h2 {
+ word-wrap: break-word;
+}
+.center-row-image {
+ display: flex;
+ justify-content: center;
+}
+.bk-Column {
+ padding-right: 12px;
+ padding-bottom: 12px;
+}
+"""
+pn.extension(raw_css=[css])
+
+
@dataclass
class InputFormWidget:
name_input: Any
@@ -69,20 +110,45 @@ def __init__(self, app: App, input_form_widget: InputFormWidget, **params):
self.username = params.get("username")
# Define Panel buttons
- self.view_button = pn.widgets.Button(name="View", button_type="primary")
- self.edit_button = pn.widgets.Button(name="Edit", button_type="warning")
- self.delete_button = pn.widgets.Button(name="Delete", button_type="danger")
+ self.view_button = pn.widgets.Button(name="Launch", button_type="primary")
+ self.edit_button = pn.widgets.Button(
+ name="Edit", button_type="primary", button_style="outline"
+ )
+ self.delete_button = pn.widgets.Button(
+ name="Delete", button_type="danger", button_style="outline"
+ )
# Set up event listeners for the buttons
- code = f"""window.location.href = '{self.app.url}'"""
+ code = f"""window.open('{self.app.url}', '_blank');"""
self.view_button.js_on_click(code=code)
self.edit_button.on_click(self.on_edit)
self.delete_button.on_click(self.on_delete)
# Using a Row to group the image, description, and buttons horizontally
self.content = pn.Row(
- pn.pane.PNG(self.app.logo, width=50),
- pn.pane.Markdown(f"**{self.app.name}**", margin=(0, 20, 0, 10)),
+ pn.pane.Image(
+ self.app.logo,
+ link_url=self.app.url,
+ width=80,
+ height=80,
+ align=("center", "center"),
+ ),
+ pn.pane.Markdown(
+ f"""
+
+
+
+ ## {self.app.name}
+
+ {self.app.description or "No description found for app"}
+
+ """,
+ margin=(0, 20, 0, 10),
+ ),
self.view_button,
self.edit_button,
self.delete_button,
@@ -127,6 +193,27 @@ def on_delete(self, event):
self.content.visible = False
+def heading_markdown(heading):
+ return pn.pane.Markdown(
+ f"""
+
+
+
+ # {heading}
+
+
+ """,
+ margin=0,
+ sizing_mode="stretch_width",
+ )
+
+
def create_list_apps(input_form_widget, username):
print("Create Dashboards Layout")
list_items = []
@@ -137,10 +224,9 @@ def create_list_apps(input_form_widget, username):
)
list_items.append(list_item)
- heading = pn.pane.Markdown("## Your Apps", sizing_mode="stretch_width")
# Wrap everything in a Column with the list-container class
layout = pn.Column(
- heading,
+ heading_markdown("Your Apps"),
*list_items,
css_classes=["list-container"],
width=800,
@@ -152,16 +238,24 @@ def create_list_apps(input_form_widget, username):
def get_input_form_widget():
frameworks_display = {f.display_name: f.name for f in FRAMEWORKS_MAPPING.values()}
- heading = pn.pane.Markdown("## Create Apps", sizing_mode="stretch_width")
+ heading = heading_markdown("Create Apps")
input_form_widget = InputFormWidget(
- name_input=pn.widgets.TextInput(name="Name", id="app_name_input"),
- filepath_input=pn.widgets.TextInput(name="Filepath"),
- description_input=pn.widgets.TextAreaInput(name="Description"),
+ name_input=pn.widgets.TextInput(
+ name="Name", id="app_name_input", css_classes=["custom-font"]
+ ),
+ filepath_input=pn.widgets.TextInput(
+ name="Filepath", css_classes=["custom-font"]
+ ),
+ description_input=pn.widgets.TextAreaInput(
+ name="Description", css_classes=["custom-font"]
+ ),
spinner=pn.indicators.LoadingSpinner(
size=30, value=True, color="secondary", bgcolor="dark", visible=True
),
button_widget=pn.widgets.Button(name=CREATE_APP_BTN_TXT, button_type="primary"),
- framework=pn.widgets.Select(name="Framework", options=frameworks_display),
+ framework=pn.widgets.Select(
+ name="Framework", options=frameworks_display, css_classes=["custom-font"]
+ ),
)
input_form = pn.Column(
heading,
@@ -220,7 +314,18 @@ def _create_server(event, input_form_widget, input_form, username):
dashboard_creation_action = "updated"
text_with_link = pn.pane.Markdown(
f"""
- ## 🚀 App {dashboard_creation_action}: [👉🔗]({dashboard_link})
+
+
+
+ ## 🚀 App {dashboard_creation_action}: [👉🔗]({dashboard_link})
+
+
"""
)
input_form.append(text_with_link)
diff --git a/jhub_apps/spawner/types.py b/jhub_apps/spawner/types.py
index 4a4a4ba5..741b5b43 100644
--- a/jhub_apps/spawner/types.py
+++ b/jhub_apps/spawner/types.py
@@ -27,7 +27,7 @@ def values(cls):
FrameworkConf(
name=Framework.panel.value,
display_name="Panel",
- logo="https://raw.githubusercontent.com/holoviz/panel/main/doc/_static/logo_stacked.png",
+ logo="https://raw.githubusercontent.com/holoviz/panel/5c69f11bc139076a0e55d444dcfbf3e44b3ed8a8/doc/_static/logo.png",
),
FrameworkConf(
name=Framework.bokeh.value,
@@ -47,17 +47,17 @@ def values(cls):
FrameworkConf(
name=Framework.plotlydash.value,
display_name="PlotlyDash",
- logo="https://repository-images.githubusercontent.com/33702544/b4400c80-718b-11e9-9f3a-306c07a5f3de",
+ logo="https://raw.githubusercontent.com/plotly/dash/6eaf2e17c25f7ca1847c41aafeb18e87c586cb9f/components/dash-table/tests/selenium/assets/logo.png",
),
FrameworkConf(
name=Framework.gradio.value,
display_name="Gradio",
- logo="https://avatars.githubusercontent.com/u/51063788?s=48&v=4",
+ logo="https://pbs.twimg.com/profile_images/1526964416834510848/Njy4Kh2q_400x400.jpg",
),
FrameworkConf(
name=Framework.jupyterlab.value,
display_name="JupyterLab",
- logo="https://jupyter.org/assets/logos/rectanglelogo-greytext-orangebody-greymoons.svg",
+ logo="https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/Jupyter_logo.svg/1200px-Jupyter_logo.svg.png",
),
]
diff --git a/jhub_apps/tests_e2e/test_playwright.py b/jhub_apps/tests_e2e/test_playwright.py
index 3b374e3a..b7c984d9 100644
--- a/jhub_apps/tests_e2e/test_playwright.py
+++ b/jhub_apps/tests_e2e/test_playwright.py
@@ -62,10 +62,16 @@ def test_panel_dashboard_creation(page: Page, framework, expected_title):
page.click(".bk-btn.bk-btn-primary")
# Wait for the dashboard to be created
time.sleep(5)
- # Click on View Dashboard
- page.click("text=View")
+ # Click on Launch Dashboard
+ launch_button = page.locator('button:text("Launch")')
+
+ assert launch_button.first.is_visible()
+
+ with page.expect_popup() as framework_page_info:
+ launch_button.click()
+ framework_page = framework_page_info.value
try:
- expect(page).to_have_title(re.compile(expected_title))
+ expect(framework_page).to_have_title(re.compile(expected_title))
except AssertionError as e:
# Go back to japps page
page.goto(url)