Skip to content

Commit

Permalink
Merge pull request #77 from jupyterhub/no-vendor
Browse files Browse the repository at this point in the history
Stop vendoring noVNC
  • Loading branch information
manics authored Feb 5, 2024
2 parents 6f527e8 + cfb1a14 commit e6e4afb
Show file tree
Hide file tree
Showing 210 changed files with 505 additions and 160,655 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ jobs:
python -m build --sdist --wheel .
ls -l dist
- name: test to see if built js file is in the package
run: |
tar -tvf dist/*.tar.gz | grep dist/viewer.js
unzip -l dist/*.whl | grep dist/viewer.js
- name: publish to pypi
uses: pypa/gh-action-pypi-publish@release/v1
if: startsWith(github.ref, 'refs/tags/')
4 changes: 3 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ jobs:
run: |
docker run -d -p 8888:8888 -e JUPYTER_TOKEN=secret jupyter-remote-desktop-proxy
sleep 10
curl 'http://localhost:8888/desktop/?token=secret' | grep 'Modified from novnc_lite.html example in noVNC'
curl 'http://localhost:8888/desktop/?token=secret' | grep 'Jupyter Remote Desktop Proxy'
# Test if the built JS file is present in the image
curl 'http://localhost:8888/desktop/dist/viewer.js?token=secret' > /dev/null
# TODO: Check VNC desktop works, e.g. by comparing Playwright screenshots
# https://playwright.dev/docs/test-snapshots
136 changes: 136 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Extra ignore patterns specific to this project
# Installed JS libraries
node_modules/
# Built JS files
jupyter_remote_desktop_proxy/static/dist

# Standard python gitignore patterns
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
12 changes: 7 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ RUN apt-get -y -qq update \

USER $NB_USER

COPY --chown=$NB_UID:$NB_GID jupyter_remote_desktop_proxy /opt/install/jupyter_remote_desktop_proxy
COPY --chown=$NB_UID:$NB_GID environment.yml setup.py MANIFEST.in README.md LICENSE /opt/install/
# Install the environment first, and then install the package separately for faster rebuilds
COPY --chown=$NB_UID:$NB_GID environment.yml /tmp
RUN . /opt/conda/bin/activate && \
mamba env update --quiet --file /tmp/environment.yml

RUN cd /opt/install && \
. /opt/conda/bin/activate && \
mamba env update --quiet --file environment.yml
COPY --chown=$NB_UID:$NB_GID . /opt/install
RUN . /opt/conda/bin/activate && \
pip install -e /opt/install
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
graft jupyter_remote_desktop_proxy/share
graft jupyter_remote_desktop_proxy/static
2 changes: 0 additions & 2 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,3 @@ dependencies:
- jupyterhub-singleuser
- pip
- websockify
- pip:
- .
142 changes: 142 additions & 0 deletions js/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* Derived from https://github.com/novnc/noVNC/blob/v1.4.0/vnc_lite.html, which was licensed
* under the 2-clause BSD license
*/

// RFB holds the API to connect and communicate with a VNC server
import RFB from "@novnc/novnc/core/rfb";

let rfb;
let desktopName;

// When this function is called we have
// successfully connected to a server
function connectedToServer(e) {
status("Connected to " + desktopName);
}

// This function is called when we are disconnected
function disconnectedFromServer(e) {
if (e.detail.clean) {
status("Disconnected");
} else {
status("Something went wrong, connection is closed");
}
}

// When this function is called, the server requires
// credentials to authenticate
function credentialsAreRequired(e) {
const password = prompt("Password Required:");
rfb.sendCredentials({ password: password });
}

// When this function is called we have received
// a desktop name from the server
function updateDesktopName(e) {
desktopName = e.detail.name;
}

// Since most operating systems will catch Ctrl+Alt+Del
// before they get a chance to be intercepted by the browser,
// we provide a way to emulate this key sequence.
function sendCtrlAltDel() {
rfb.sendCtrlAltDel();
return false;
}

// Show a status text in the top bar
function status(text) {
document.getElementById("status").textContent = text;
}

// This function extracts the value of one variable from the
// query string. If the variable isn't defined in the URL
// it returns the default value instead.
function readQueryVariable(name, defaultValue) {
// A URL with a query parameter can look like this:
// https://www.example.com?myqueryparam=myvalue
//
// Note that we use location.href instead of location.search
// because Firefox < 53 has a bug w.r.t location.search
const re = new RegExp(".*[?&]" + name + "=([^&#]*)"),
match = document.location.href.match(re);

if (match) {
// We have to decode the URL since want the cleartext value
return decodeURIComponent(match[1]);
}

return defaultValue;
}

document.getElementById("sendCtrlAltDelButton").onclick = sendCtrlAltDel;

// Read parameters specified in the URL query string
// By default, use the host and port of server that served this file
const host = readQueryVariable("host", window.location.hostname);
let port = readQueryVariable("port", window.location.port);
const password = readQueryVariable("password");

const path = readQueryVariable(
"path",
window.location.pathname.replace(/[^/]*$/, "").substring(1) + "websockify",
);

// | | | | | |
// | | | Connect | | |
// v v v v v v

status("Connecting");

// Build the websocket URL used to connect
let url;
if (window.location.protocol === "https:") {
url = "wss";
} else {
url = "ws";
}
url += "://" + host;
if (port) {
url += ":" + port;
}
url += "/" + path;

// Creating a new RFB object will start a new connection
rfb = new RFB(document.getElementById("screen"), url, {
credentials: { password: password },
});

// Add listeners to important events from the RFB module
rfb.addEventListener("connect", connectedToServer);
rfb.addEventListener("disconnect", disconnectedFromServer);
rfb.addEventListener("credentialsrequired", credentialsAreRequired);
rfb.addEventListener("desktopname", updateDesktopName);

// Set parameters that can be changed on an active connection
rfb.viewOnly = readQueryVariable("view_only", false);

rfb.scaleViewport = readQueryVariable("scale", true);

// Clipboard
function toggleClipboardPanel() {
document
.getElementById("noVNC_clipboard_area")
.classList.toggle("noVNC_clipboard_closed");
}
document
.getElementById("noVNC_clipboard_button")
.addEventListener("click", toggleClipboardPanel);

function clipboardReceive(e) {
document.getElementById("noVNC_clipboard_text").value = e.detail.text;
}
rfb.addEventListener("clipboard", clipboardReceive);

function clipboardSend() {
const text = document.getElementById("noVNC_clipboard_text").value;
rfb.clipboardPasteFrom(text);
}
document
.getElementById("noVNC_clipboard_text")
.addEventListener("change", clipboardSend);
4 changes: 2 additions & 2 deletions jupyter_remote_desktop_proxy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ def setup_desktop():
'websockify',
'-v',
'--web',
os.path.join(HERE, 'share/web/noVNC-1.2.0'),
os.path.join(HERE, 'static'),
'--heartbeat',
'30',
'{port}',
]
+ socket_args
+ ['--', '/bin/sh', '-c', f'cd {os.getcwd()} && {vnc_command}'],
'timeout': 30,
'mappath': {'/': '/vnc_lite.html'},
'mappath': {'/': '/index.html'},
'new_browser_window': True,
}

This file was deleted.

50 changes: 0 additions & 50 deletions jupyter_remote_desktop_proxy/share/web/noVNC-1.2.0/.eslintrc

This file was deleted.

Loading

0 comments on commit e6e4afb

Please sign in to comment.