From c3311e980f8e1c13fef21fa3fa49ebde39fe5bb7 Mon Sep 17 00:00:00 2001 From: Jens Flemming Date: Thu, 29 Aug 2024 19:14:21 +0200 Subject: [PATCH 01/29] Fix broken link in doc --- doc/src/container-admins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/container-admins.md b/doc/src/container-admins.md index b7c8bab..bca5aea 100644 --- a/doc/src/container-admins.md +++ b/doc/src/container-admins.md @@ -266,7 +266,7 @@ Note that the value for `c.LTI13Authenticator.client_id` has to be a list of str Don't abuse `30_lms.py` for other configuration purposes than the described LTI configuration. This may lead to unexpected behavior. -(lit-lms)= +(lti-lms)= #### LMS For your LMS you need the following configuration information (field names are taken from Moodle here and may be slightly different in other LMS): From c2d8d71be5d22d36c9d99bac5b3a77221bbd339f Mon Sep 17 00:00:00 2001 From: Jens Flemming Date: Fri, 30 Aug 2024 09:25:55 +0200 Subject: [PATCH 02/29] Correct typo on changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 514c50a..ebfe31e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Ananke 0.5 * New features: * **BREAKING CHANGE** - LTI/Nbgrader course and grades management is now accessible through the 'Kore' menu item in the instructor's JupyterLab (see [kore-extension](https://github.com/marcwit/kore-extension)). This replaces the former Kore service GUI. See [Update form Ananke 0.4 to Ananke 0.5](https://gauss.whz.de/ananke/doc/container-admins.html#update-to-0_5) for hints on updating your installation. - * **BREAKING CHANGE** - Images and Containers now are managed by one Python script instead of several bash scripts. Directory structure changed, too. See [Update form Ananke 0.4 to Ananke 0.5](https://gauss.whz.de/ananke/doc/container-admins.html#update-to-0_5) for hints on updating your installation. + * **BREAKING CHANGE** - Images and Containers now are managed by one Python script instead of several bash scripts. Directory structure changed, too. See [Update from Ananke 0.4 to Ananke 0.5](https://gauss.whz.de/ananke/doc/container-admins.html#update-to-0_5) for hints on updating your installation. * [jupyterlab-execute-time extension](https://github.com/deshaw/jupyterlab-execute-time) is activated in all JupyterLabs by default. * Minor additions and corrections to doc. * Bugfixes: From e8960d61ba55598a43052d31d033b43cafc971b5 Mon Sep 17 00:00:00 2001 From: Marcus Wittig Date: Tue, 3 Sep 2024 14:40:33 +0200 Subject: [PATCH 03/29] Trivial: Fixed break of while loop. --- ananke | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ananke b/ananke index 885f2d5..6a55ac9 100755 --- a/ananke +++ b/ananke @@ -227,6 +227,7 @@ def ask_int(low: int, high: int, default: int) -> int: int Chosen integer. """ + range_text = f'{low if low is not None else "-∞"}...{high if high is not None else "+∞"}' while True: @@ -424,12 +425,18 @@ def subcmd_create(args): if not choice: break else: + choice_valid = True + for gpu in choice.split(','): gpu = gpu.strip() if gpu in gpus: chosen_gpus.append(gpu) else: print(f'GPU name "{gpu}" invalid!') + choice_valid = False + + if choice_valid: + break for gpu in chosen_gpus: gpu_devices.append(f'nvidia.com/gpu={gpu}') From 5879bcfaab2b65b0ec792fe7d62e28f84dd8468d Mon Sep 17 00:00:00 2001 From: Marcus Wittig Date: Wed, 4 Sep 2024 09:27:23 +0200 Subject: [PATCH 04/29] Added href for kore extension to base.html.jinja. --- images/ananke-nbgrader/assets/kore/templates/base.html.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/ananke-nbgrader/assets/kore/templates/base.html.jinja b/images/ananke-nbgrader/assets/kore/templates/base.html.jinja index dd0b7ff..3537259 100644 --- a/images/ananke-nbgrader/assets/kore/templates/base.html.jinja +++ b/images/ananke-nbgrader/assets/kore/templates/base.html.jinja @@ -64,7 +64,7 @@
  • - Kore extension on GitHub + Kore extension on GitHub
  • From ede9ebfea795bdfab0e5b66034bcbf140b2e2336 Mon Sep 17 00:00:00 2001 From: Marcus Wittig Date: Thu, 5 Sep 2024 14:36:18 +0200 Subject: [PATCH 05/29] Refactored kore service related files. --- .../assets/kore/kore_jhub_config.py | 2 +- .../assets/kore/models/lti_file_reader.py | 81 ---- .../assets/kore/routes/home_route.py | 30 +- .../assets/kore/templates/home.html.jinja | 425 ++++++++---------- .../assets/kore/templates/style.html.jinja | 8 - 5 files changed, 206 insertions(+), 340 deletions(-) delete mode 100755 images/ananke-nbgrader/assets/kore/models/lti_file_reader.py diff --git a/images/ananke-nbgrader/assets/kore/kore_jhub_config.py b/images/ananke-nbgrader/assets/kore/kore_jhub_config.py index 89f97f0..a471eba 100644 --- a/images/ananke-nbgrader/assets/kore/kore_jhub_config.py +++ b/images/ananke-nbgrader/assets/kore/kore_jhub_config.py @@ -478,7 +478,7 @@ async def run_as_user(username: str, cmd: str, args: list[str]) -> None: c.JupyterHub.services.append({ 'name': 'kore', 'url': 'http://127.0.0.1:10001', - 'display': False, # Will be changed once refactored. + 'display': True, 'api_token': kore_token, 'oauth_no_confirm': True, 'cwd': '/opt/kore', diff --git a/images/ananke-nbgrader/assets/kore/models/lti_file_reader.py b/images/ananke-nbgrader/assets/kore/models/lti_file_reader.py deleted file mode 100755 index f4bc337..0000000 --- a/images/ananke-nbgrader/assets/kore/models/lti_file_reader.py +++ /dev/null @@ -1,81 +0,0 @@ -import hashlib -import json -import logging -from typing import Optional - -from flask import Response - - -class LTIFileReader: - def __init__(self, user_name: str, file_path: str) -> None: - self.user_name: str = user_name - self.file_path: str = file_path - self.lti_state: Optional[dict] = None - self.read_success: bool = False - self.course_id: Optional[str] = None - self.course_title: Optional[str] = None - self.grader_user: Optional[str] = None - self.parse_success: bool = False - self.error_response: Optional[Response] = None - self.preflight_error: Optional[str] = None - - def read_file(self) -> None: - try: - with open(self.file_path, 'r') as file: - # Check if the file has a JSON extension otherwise raise an error. - if not self.file_path.lower().endswith('.json'): - raise ValueError('File is not a JSON file.') - - # Read content of file and set boolean value to True if reading is successful. - self.lti_state = json.load(file) - self.read_success = True - except FileNotFoundError: - logging.error(f'LTI state file for user {self.user_name} not found!') - self.error_response = Response(response=json.dumps({'message': 'FileNotFoundError'}), status=404) - self.preflight_error = 'LTI file for current user could not be found. Contact administrator or see logs for more details.' - except PermissionError: - logging.error(f'LTI state file for user {self.user_name} not readable!') - self.error_response = Response(response=json.dumps({'message': 'PermissionError'}), status=400) - self.preflight_error = 'LTI file for current user could not be read. Contact administrator or see logs for more details.' - except ValueError: - logging.error(f'LTI state file is not a JSON!') - self.error_response = Response(response=json.dumps({'message': 'ValueError'}), status=400) - self.preflight_error = 'LTI file for current user is not a JSON file. Contact administrator or see logs for more details.' - except OSError: - logging.error(f'LTI state file for user {self.user_name} can not be opened!') - self.error_response = Response(response=json.dumps({'message': 'OSError'}), status=500) - self.preflight_error = 'LTI file for current user could not be opened. Contact administrator or see logs for more details.' - - def extract_values(self) -> None: - if not self.read_success: - return - - if not isinstance(self.lti_state, dict): - logging.error('LTI state content is not a dict!') - self.error_response = Response(response=json.dumps({'message': 'ContentError'}), status=500) - return - - # Extract course id, course title and grader username from lti state. - deployment_id = self.lti_state.get('https://purl.imsglobal.org/spec/lti/claim/deployment_id', '0') - resource_link_id = self.lti_state.get('https://purl.imsglobal.org/spec/lti/claim/resource_link').get('id') - resource_link_title = self.lti_state.get('https://purl.imsglobal.org/spec/lti/claim/resource_link').get('title') - context_title = self.lti_state.get('https://purl.imsglobal.org/spec/lti/claim/context', {}).get('title') - - h = hashlib.shake_256(f'{deployment_id}-{resource_link_id}'.encode()) - self.course_id = 'c-' + h.hexdigest(8) - self.grader_user = self.course_id[0:32] - - if resource_link_title and context_title: - course_title = f'{context_title} - {resource_link_title}' - elif resource_link_title: - course_title = resource_link_title - elif context_title: - course_title = context_title - else: - course_title = 'No title available' - self.course_title = f'{course_title} ({self.course_id})'.replace('\'', '') - - logging.debug(f'Course ID: {self.course_id}') - logging.debug(f'Course title: {self.course_title}') - logging.debug(f'Grader user: {self.grader_user}') - self.parse_success = True diff --git a/images/ananke-nbgrader/assets/kore/routes/home_route.py b/images/ananke-nbgrader/assets/kore/routes/home_route.py index 3f88a44..2302ab0 100644 --- a/images/ananke-nbgrader/assets/kore/routes/home_route.py +++ b/images/ananke-nbgrader/assets/kore/routes/home_route.py @@ -1,3 +1,4 @@ +import json import logging from flask import make_response @@ -7,8 +8,6 @@ from flask import session as flask_session from jupyterhub.services.auth import HubOAuth -from models.lti_file_reader import LTIFileReader - home_bp = Blueprint('home', __name__) @@ -41,24 +40,27 @@ def wrapper(*args, **kwargs): @home_bp.route('', methods=['GET']) def home(user): # Defining object containing relevant information for the HTML template. - tmpl_data = {} + data = {} config_loader = current_app.config['CONFIG_LOADER'] if config_loader.preflight_error: - tmpl_data['preflight_error'] = config_loader.preflight_error + data['preflight_error'] = config_loader.preflight_error user_name = user.get('name') - tmpl_data['user'] = user_name - - logging.info(f'User {user_name} is accessing home page of kore service.') + data['user'] = user_name - lti_file_reader: LTIFileReader = LTIFileReader(user_name=user_name, file_path=f'runtime/lti_{user_name}.json') - lti_file_reader.read_file() + try: + with open(f'runtime/lti_{user_name}.json', 'r') as file: + lti_state = json.load(file) + data['url'] = f"{lti_state['https://purl.imsglobal.org/spec/lti/claim/target_link_uri']}/services/kore" + except (FileNotFoundError, PermissionError, ValueError, OSError): + logging.error(f'LTI state file for user {user_name} not found!') - if not lti_file_reader.read_success: - if 'preflight_error' in tmpl_data: - tmpl_data['preflight_error'] += f'\n{lti_file_reader.preflight_error}' + if 'preflight_error' in data: + data['preflight_error'] += f'\nError while reading lti state file.' else: - tmpl_data['preflight_error'] = lti_file_reader.preflight_error + data['preflight_error'] = 'Error while reading lti state file.' + + logging.info(f'User {user_name} is accessing home page of kore service.') - return render_template('home.html.jinja', data=tmpl_data) + return render_template('home.html.jinja', data=data) diff --git a/images/ananke-nbgrader/assets/kore/templates/home.html.jinja b/images/ananke-nbgrader/assets/kore/templates/home.html.jinja index 9db1b1d..75e62bc 100644 --- a/images/ananke-nbgrader/assets/kore/templates/home.html.jinja +++ b/images/ananke-nbgrader/assets/kore/templates/home.html.jinja @@ -2,97 +2,179 @@ {% block head %} {% endblock %} {% block body %} -
    +