retuve.app.routes.ui

  1import base64
  2import os
  3import re
  4import secrets
  5import shutil
  6import time
  7from datetime import datetime, timedelta
  8from hashlib import sha256
  9from tempfile import mkdtemp
 10
 11from cycler import V
 12from fastapi import (
 13    APIRouter,
 14    Depends,
 15    Header,
 16    HTTPException,
 17    Request,
 18    Response,
 19)
 20from fastapi.responses import HTMLResponse, JSONResponse
 21
 22from retuve.app.helpers import API_RESULTS_URL_ACCESS, web_templates
 23from retuve.app.utils import (
 24    API_TOKEN_STORE,
 25    TOKEN_STORE,
 26    generate_token,
 27    get_sorted_dicom_images,
 28    save_dicom_and_get_results,
 29    save_results,
 30    validate_api_token,
 31    validate_auth_token,
 32)
 33from retuve.keyphrases.config import Config
 34from retuve.trak.data import extract_files
 35
 36
 37def basic_auth_dependency(
 38    authorization: str = Header(None), response: Response = None
 39):
 40    if not authorization or not authorization.startswith("Basic "):
 41        raise HTTPException(
 42            status_code=401,
 43            detail="Unauthorized",
 44            headers={"WWW-Authenticate": "Basic"},
 45        )
 46    try:
 47        valid_username = Config.global_config.username
 48        valid_password = Config.global_config.password
 49
 50        auth_token = authorization.split(" ")[1]
 51        decoded_credentials = base64.b64decode(auth_token).decode("utf-8")
 52        username, password = decoded_credentials.split(":")
 53        if username != valid_username or password != valid_password:
 54            raise ValueError("Invalid credentials")
 55
 56        # Generate authentication token
 57        auth_token = generate_token()
 58        api_token = generate_token()
 59
 60        # Store tokens with expiration timestamps
 61        expiration = datetime.utcnow() + timedelta(hours=24)
 62        TOKEN_STORE[auth_token] = {
 63            "username": username,
 64            "expires": expiration,
 65        }
 66        API_TOKEN_STORE[api_token] = {
 67            "username": username,
 68            "expires": expiration,
 69        }
 70
 71        return {"auth_token": auth_token, "api_token": api_token}
 72
 73    except Exception:
 74        raise HTTPException(
 75            status_code=401,
 76            detail="Unauthorized",
 77            headers={"WWW-Authenticate": "Basic"},
 78        )
 79
 80
 81router = APIRouter()
 82
 83
 84@router.get("/", response_class=HTMLResponse)
 85async def web(
 86    request: Request,
 87    tokens: dict = Depends(basic_auth_dependency),
 88):
 89    """
 90    Open the main page of the Retuve Web Interface.
 91    """
 92    keyphrases = [keyphrase for keyphrase in Config.configs.keys()]
 93
 94    response = web_templates.TemplateResponse(
 95        f"index.html",
 96        {
 97            "request": request,
 98            "keyphrases": keyphrases,
 99        },
100    )
101
102    response.set_cookie(
103        "auth_token",
104        tokens["auth_token"],
105        expires=3600,
106        httponly=True,
107        secure=True,
108        samesite="Strict",
109    )
110    response.set_cookie(
111        "api_token",
112        tokens["api_token"],
113        expires=3600,
114        httponly=True,
115        secure=True,
116        samesite="Strict",
117    )
118
119    return response
120
121
122@router.get("/ui/live/", response_class=HTMLResponse)
123async def live_ui(request: Request):
124    """
125    Open the live page of the Retuve Web Interface.
126    """
127    auth_token = request.cookies.get("auth_token")
128    validate_auth_token(auth_token)
129
130    return web_templates.TemplateResponse(
131        f"live.html",
132        {
133            "request": request,
134            "keyphrase": Config.live_config.name,
135            "url": Config.live_config.api.url,
136        },
137    )
138
139
140@router.get("/ui/{keyphrase}", response_class=HTMLResponse)
141async def web_keyphrase(request: Request, keyphrase: str):
142    """
143    Open the page for a specific keyphrase.
144    """
145
146    try:
147        config = Config.get_config(keyphrase)
148    except ValueError:
149        raise HTTPException(
150            status_code=404, detail=f"Keyphrase {keyphrase} not found"
151        )
152    files_data = extract_files(config.api.db_path)
153
154    auth_token = request.cookies.get("auth_token")
155    validate_auth_token(auth_token)
156
157    return web_templates.TemplateResponse(
158        f"main.html",
159        {
160            "request": request,
161            "files": files_data,
162            "hip_mode": config.batch.hip_mode,
163            "keyphrase": keyphrase,
164            "url": config.api.url,
165        },
166    )
167
168
169@router.get("/ui/upload/", response_class=HTMLResponse)
170async def upload_form(request: Request):
171    """
172    Open the upload form for the Retuve Web Interface.
173    """
174
175    auth_token = request.cookies.get("auth_token")
176    validate_auth_token(auth_token)
177
178    keyphrases = [keyphrase for keyphrase in Config.configs.keys()]
179
180    return web_templates.TemplateResponse(
181        f"upload.html",
182        {
183            "request": request,
184            "keyphrases": keyphrases,
185        },
186    )
187
188
189@router.get("/ui/download/{keyphrase}")
190async def download_files(
191    request: Request, keyphrase: str, pattern: str = None
192):
193    """
194    Download files from the Retuve Web Interface.
195    """
196
197    auth_token = request.cookies.get("auth_token")
198    validate_auth_token(auth_token)
199
200    if not pattern:
201        raise HTTPException(status_code=400, detail="Pattern is required")
202
203    config = Config.get_config(keyphrase)
204    savedir = config.api.savedir
205
206    # Create a temporary directory to hold the folders
207    temp_dir = mkdtemp()
208
209    # Find all folders in savedir that match the pattern
210    # Copy each matching folder to the temporary directory
211    for folder in os.listdir(savedir):
212        if re.search(pattern, folder) and os.path.isdir(
213            os.path.join(savedir, folder)
214        ):
215            source_folder = os.path.join(savedir, folder)
216            destination_folder = os.path.join(temp_dir, folder)
217            shutil.copytree(source_folder, destination_folder)
218
219    # Create a zip file of the temporary directory
220    zip_file = os.path.join(savedir, f"{pattern}.zip")
221    shutil.make_archive(zip_file[:-4], "zip", temp_dir)
222
223    zip_file_url = (
224        f"{config.api.url}/{API_RESULTS_URL_ACCESS}/{keyphrase}/{pattern}.zip"
225    )
226
227    # Optionally, remove the temporary directory after creating the zip file
228    shutil.rmtree(temp_dir)
229
230    return web_templates.TemplateResponse(
231        "download.html",
232        {
233            "request": request,
234            "zip_file_url": zip_file_url,
235            "keyphrase": keyphrase,
236        },
237    )
def basic_auth_dependency( authorization: str = Header(None), response: starlette.responses.Response = None):
38def basic_auth_dependency(
39    authorization: str = Header(None), response: Response = None
40):
41    if not authorization or not authorization.startswith("Basic "):
42        raise HTTPException(
43            status_code=401,
44            detail="Unauthorized",
45            headers={"WWW-Authenticate": "Basic"},
46        )
47    try:
48        valid_username = Config.global_config.username
49        valid_password = Config.global_config.password
50
51        auth_token = authorization.split(" ")[1]
52        decoded_credentials = base64.b64decode(auth_token).decode("utf-8")
53        username, password = decoded_credentials.split(":")
54        if username != valid_username or password != valid_password:
55            raise ValueError("Invalid credentials")
56
57        # Generate authentication token
58        auth_token = generate_token()
59        api_token = generate_token()
60
61        # Store tokens with expiration timestamps
62        expiration = datetime.utcnow() + timedelta(hours=24)
63        TOKEN_STORE[auth_token] = {
64            "username": username,
65            "expires": expiration,
66        }
67        API_TOKEN_STORE[api_token] = {
68            "username": username,
69            "expires": expiration,
70        }
71
72        return {"auth_token": auth_token, "api_token": api_token}
73
74    except Exception:
75        raise HTTPException(
76            status_code=401,
77            detail="Unauthorized",
78            headers={"WWW-Authenticate": "Basic"},
79        )
router = <fastapi.routing.APIRouter object>
@router.get('/', response_class=HTMLResponse)
async def web( request: starlette.requests.Request, tokens: dict = Depends(basic_auth_dependency)):
 85@router.get("/", response_class=HTMLResponse)
 86async def web(
 87    request: Request,
 88    tokens: dict = Depends(basic_auth_dependency),
 89):
 90    """
 91    Open the main page of the Retuve Web Interface.
 92    """
 93    keyphrases = [keyphrase for keyphrase in Config.configs.keys()]
 94
 95    response = web_templates.TemplateResponse(
 96        f"index.html",
 97        {
 98            "request": request,
 99            "keyphrases": keyphrases,
100        },
101    )
102
103    response.set_cookie(
104        "auth_token",
105        tokens["auth_token"],
106        expires=3600,
107        httponly=True,
108        secure=True,
109        samesite="Strict",
110    )
111    response.set_cookie(
112        "api_token",
113        tokens["api_token"],
114        expires=3600,
115        httponly=True,
116        secure=True,
117        samesite="Strict",
118    )
119
120    return response

Open the main page of the Retuve Web Interface.

@router.get('/ui/live/', response_class=HTMLResponse)
async def live_ui(request: starlette.requests.Request):
123@router.get("/ui/live/", response_class=HTMLResponse)
124async def live_ui(request: Request):
125    """
126    Open the live page of the Retuve Web Interface.
127    """
128    auth_token = request.cookies.get("auth_token")
129    validate_auth_token(auth_token)
130
131    return web_templates.TemplateResponse(
132        f"live.html",
133        {
134            "request": request,
135            "keyphrase": Config.live_config.name,
136            "url": Config.live_config.api.url,
137        },
138    )

Open the live page of the Retuve Web Interface.

@router.get('/ui/{keyphrase}', response_class=HTMLResponse)
async def web_keyphrase(request: starlette.requests.Request, keyphrase: str):
141@router.get("/ui/{keyphrase}", response_class=HTMLResponse)
142async def web_keyphrase(request: Request, keyphrase: str):
143    """
144    Open the page for a specific keyphrase.
145    """
146
147    try:
148        config = Config.get_config(keyphrase)
149    except ValueError:
150        raise HTTPException(
151            status_code=404, detail=f"Keyphrase {keyphrase} not found"
152        )
153    files_data = extract_files(config.api.db_path)
154
155    auth_token = request.cookies.get("auth_token")
156    validate_auth_token(auth_token)
157
158    return web_templates.TemplateResponse(
159        f"main.html",
160        {
161            "request": request,
162            "files": files_data,
163            "hip_mode": config.batch.hip_mode,
164            "keyphrase": keyphrase,
165            "url": config.api.url,
166        },
167    )

Open the page for a specific keyphrase.

@router.get('/ui/upload/', response_class=HTMLResponse)
async def upload_form(request: starlette.requests.Request):
170@router.get("/ui/upload/", response_class=HTMLResponse)
171async def upload_form(request: Request):
172    """
173    Open the upload form for the Retuve Web Interface.
174    """
175
176    auth_token = request.cookies.get("auth_token")
177    validate_auth_token(auth_token)
178
179    keyphrases = [keyphrase for keyphrase in Config.configs.keys()]
180
181    return web_templates.TemplateResponse(
182        f"upload.html",
183        {
184            "request": request,
185            "keyphrases": keyphrases,
186        },
187    )

Open the upload form for the Retuve Web Interface.

@router.get('/ui/download/{keyphrase}')
async def download_files( request: starlette.requests.Request, keyphrase: str, pattern: str = None):
190@router.get("/ui/download/{keyphrase}")
191async def download_files(
192    request: Request, keyphrase: str, pattern: str = None
193):
194    """
195    Download files from the Retuve Web Interface.
196    """
197
198    auth_token = request.cookies.get("auth_token")
199    validate_auth_token(auth_token)
200
201    if not pattern:
202        raise HTTPException(status_code=400, detail="Pattern is required")
203
204    config = Config.get_config(keyphrase)
205    savedir = config.api.savedir
206
207    # Create a temporary directory to hold the folders
208    temp_dir = mkdtemp()
209
210    # Find all folders in savedir that match the pattern
211    # Copy each matching folder to the temporary directory
212    for folder in os.listdir(savedir):
213        if re.search(pattern, folder) and os.path.isdir(
214            os.path.join(savedir, folder)
215        ):
216            source_folder = os.path.join(savedir, folder)
217            destination_folder = os.path.join(temp_dir, folder)
218            shutil.copytree(source_folder, destination_folder)
219
220    # Create a zip file of the temporary directory
221    zip_file = os.path.join(savedir, f"{pattern}.zip")
222    shutil.make_archive(zip_file[:-4], "zip", temp_dir)
223
224    zip_file_url = (
225        f"{config.api.url}/{API_RESULTS_URL_ACCESS}/{keyphrase}/{pattern}.zip"
226    )
227
228    # Optionally, remove the temporary directory after creating the zip file
229    shutil.rmtree(temp_dir)
230
231    return web_templates.TemplateResponse(
232        "download.html",
233        {
234            "request": request,
235            "zip_file_url": zip_file_url,
236            "keyphrase": keyphrase,
237        },
238    )

Download files from the Retuve Web Interface.