From d5f761f8b64b3e9163d86bf3ccda55209576ba93 Mon Sep 17 00:00:00 2001 From: Mr-Neutr0n <64578610+Mr-Neutr0n@users.noreply.github.com> Date: Mon, 22 Jun 2026 10:32:34 +0000 Subject: [PATCH 1/2] Fix #1135: Runestone Issue: API keys shows two keys are added when only Signed-off-by: Mr-Neutr0n <64578610+Mr-Neutr0n@users.noreply.github.com> --- .../routers/instructor.py | 45 +++++++++ .../assignment/instructor/add_token.html | 95 ++++++++++++++++++- 2 files changed, 135 insertions(+), 5 deletions(-) diff --git a/bases/rsptx/assignment_server_api/routers/instructor.py b/bases/rsptx/assignment_server_api/routers/instructor.py index 111f57fe7..fd6cdc5c8 100644 --- a/bases/rsptx/assignment_server_api/routers/instructor.py +++ b/bases/rsptx/assignment_server_api/routers/instructor.py @@ -1465,9 +1465,15 @@ async def get_add_token_page( # Count tokens by provider token_counts = {} + token_list = [] for token in tokens: provider = token.provider token_counts[provider] = token_counts.get(provider, 0) + 1 + token_list.append({ + "id": token.id, + "provider": token.provider, + "masked_token": token.token[:4] + "****" + token.token[-4:] if len(token.token) > 8 else "****", + }) total_tokens = len(tokens) @@ -1480,6 +1486,7 @@ async def get_add_token_page( "student_page": False, "total_tokens": total_tokens, "token_counts": token_counts, + "token_list": token_list, } return templates.TemplateResponse("assignment/instructor/add_token.html", context) @@ -1517,6 +1524,44 @@ async def delete_course_tokens( ) +@router.delete("/delete_token/{token_id}") +@instructor_role_required() +@with_course() +async def delete_single_token( + token_id: int, + request: Request, + course=None, +): + """ + Delete a specific API token for the instructor's course. + + :param token_id: ID of the token to delete + :param course: Course object from decorator + :return: JSON response with success status + """ + try: + deleted = await delete_api_token(course_id=course.id, token_id=token_id) + if deleted: + return make_json_response( + status=status.HTTP_200_OK, + detail={ + "status": "success", + "message": "Token deleted successfully", + }, + ) + else: + return make_json_response( + status=status.HTTP_404_NOT_FOUND, + detail="Token not found", + ) + except Exception as e: + rslogger.error(f"Error deleting API token {token_id} for course {course.id}: {e}") + return make_json_response( + status=status.HTTP_400_BAD_REQUEST, + detail=f"Error deleting token: {str(e)}", + ) + + @router.delete("/assignments/{assignment_id}") @instructor_role_required() async def remove_assignment(assignment_id: int, request: Request): diff --git a/components/rsptx/templates/assignment/instructor/add_token.html b/components/rsptx/templates/assignment/instructor/add_token.html index 1e6e1e74a..21520086a 100644 --- a/components/rsptx/templates/assignment/instructor/add_token.html +++ b/components/rsptx/templates/assignment/instructor/add_token.html @@ -137,6 +137,42 @@ font-size: 16px; margin-bottom: 10px; } + + .token-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + margin-bottom: 6px; + background: #fff; + border: 1px solid #dee2e6; + border-radius: 4px; + } + + .token-item .token-provider { + font-weight: 600; + min-width: 100px; + } + + .token-item .token-masked { + font-family: monospace; + color: #6c757d; + flex: 1; + } + + .token-item .remove-token-btn { + background-color: #dc3545; + color: white; + border: none; + padding: 4px 10px; + border-radius: 4px; + cursor: pointer; + font-size: 13px; + } + + .token-item .remove-token-btn:hover { + background-color: #c82333; + } {% endblock %} @@ -151,12 +187,16 @@

Manage API Tokens

Current Tokens

Total tokens stored: {{ total_tokens }}
- {% if token_counts %} - +
{% endif %} {% endif %} @@ -198,6 +238,7 @@

Current Tokens