Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop vendoring noVNC #77

Merged
merged 9 commits into from
Feb 5, 2024
Merged
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
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
manics marked this conversation as resolved.
Show resolved Hide resolved
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
Loading