added Stufff
This commit is contained in:
		
							
								
								
									
										10
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								.env
									
									
									
									
									
								
							@@ -1,14 +1,14 @@
 | 
			
		||||
# JupyterHub Notebook Viewer Configuration
 | 
			
		||||
 | 
			
		||||
# Core Settings
 | 
			
		||||
JUPYTERHUB_SHARED_DIR=/shared
 | 
			
		||||
APP_TITLE=JupyterHub Notebook Viewer
 | 
			
		||||
JUPYTERHUB_SHARED_DIR=./shared
 | 
			
		||||
APP_TITLE="IFN Shared HUB"
 | 
			
		||||
 | 
			
		||||
# Flask Settings
 | 
			
		||||
FLASK_HOST=0.0.0.0
 | 
			
		||||
FLASK_PORT=5000
 | 
			
		||||
FLASK_PORT=5001
 | 
			
		||||
FLASK_DEBUG=True
 | 
			
		||||
FLASK_SECRET_KEY=your-secret-key-change-in-production
 | 
			
		||||
FLASK_SECRET_KEY="hoeuhfou0a9ufm08fwncznf0aauuf"
 | 
			
		||||
 | 
			
		||||
# File Handling
 | 
			
		||||
MAX_FILE_SIZE=16777216
 | 
			
		||||
@@ -19,5 +19,3 @@ ALLOWED_EXTENSIONS=.ipynb,.py,.md
 | 
			
		||||
ENABLE_DOWNLOAD=True
 | 
			
		||||
ENABLE_API=True
 | 
			
		||||
 | 
			
		||||
# UI Settings
 | 
			
		||||
THEME=dark
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,3 @@ ALLOWED_EXTENSIONS=.ipynb,.py,.md
 | 
			
		||||
# Feature Toggles
 | 
			
		||||
ENABLE_DOWNLOAD=True
 | 
			
		||||
ENABLE_API=True
 | 
			
		||||
 | 
			
		||||
# UI Settings
 | 
			
		||||
THEME=dark  # dark or light
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								__pycache__/app.cpython-311.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								__pycache__/app.cpython-311.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										296
									
								
								app.py
									
									
									
									
									
								
							
							
						
						
									
										296
									
								
								app.py
									
									
									
									
									
								
							@@ -1,5 +1,6 @@
 | 
			
		||||
import os
 | 
			
		||||
import json
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from flask import Flask, render_template, request, jsonify, send_file, abort
 | 
			
		||||
import nbformat
 | 
			
		||||
@@ -25,26 +26,27 @@ app.config.update({
 | 
			
		||||
    'ENABLE_DOWNLOAD': os.environ.get('ENABLE_DOWNLOAD', 'True').lower() == 'true',
 | 
			
		||||
    'ENABLE_API': os.environ.get('ENABLE_API', 'True').lower() == 'true',
 | 
			
		||||
    'APP_TITLE': os.environ.get('APP_TITLE', 'JupyterHub Notebook Viewer'),
 | 
			
		||||
    'THEME': os.environ.get('THEME', 'dark'),  # dark or light
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
def get_notebook_files(directory):
 | 
			
		||||
    """Recursively get all notebook files from directory"""
 | 
			
		||||
    notebooks = []
 | 
			
		||||
    allowed_extensions = app.config['ALLOWED_EXTENSIONS']
 | 
			
		||||
    
 | 
			
		||||
    dir = request.args.get('dir', '')
 | 
			
		||||
    print(dir)
 | 
			
		||||
    try:
 | 
			
		||||
        for root, dirs, files in os.walk(directory):
 | 
			
		||||
            for file in files:
 | 
			
		||||
                if any(file.endswith(ext) for ext in allowed_extensions):
 | 
			
		||||
                    full_path = os.path.join(root, file)
 | 
			
		||||
                    relative_path = os.path.relpath(full_path, directory)
 | 
			
		||||
                    mtime = datetime.fromtimestamp(os.path.getmtime(full_path)).strftime("%d.%m.%Y %H:%M") 
 | 
			
		||||
                    notebooks.append({
 | 
			
		||||
                        'name': file,
 | 
			
		||||
                        'path': relative_path,
 | 
			
		||||
                        'path': os.path.join(dir, relative_path),
 | 
			
		||||
                        'full_path': full_path,
 | 
			
		||||
                        'size': os.path.getsize(full_path),
 | 
			
		||||
                        'modified': os.path.getmtime(full_path)
 | 
			
		||||
                        'size': round(os.path.getsize(full_path) / 1024, 2),
 | 
			
		||||
                        'modified': mtime 
 | 
			
		||||
                    })
 | 
			
		||||
    except (OSError, PermissionError) as e:
 | 
			
		||||
        app.logger.error(f"Error accessing directory {directory}: {e}")
 | 
			
		||||
@@ -84,19 +86,7 @@ def convert_notebook_to_html(notebook_path):
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        return f"<div class='alert alert-danger'>Error converting notebook: {str(e)}</div>"
 | 
			
		||||
 | 
			
		||||
@app.route('/')
 | 
			
		||||
def index():
 | 
			
		||||
    """Main page showing all notebooks"""
 | 
			
		||||
    current_dir = request.args.get('dir', '')
 | 
			
		||||
    full_current_dir = os.path.join(app.config['SHARED_DIRECTORY'], current_dir)
 | 
			
		||||
    
 | 
			
		||||
    if not os.path.exists(full_current_dir):
 | 
			
		||||
        abort(404)
 | 
			
		||||
    
 | 
			
		||||
    notebooks = get_notebook_files(full_current_dir)
 | 
			
		||||
    directories = get_directory_structure(full_current_dir)
 | 
			
		||||
    
 | 
			
		||||
    # Breadcrumb navigation
 | 
			
		||||
def get_breadcrumbs(current_dir):
 | 
			
		||||
    breadcrumbs = []
 | 
			
		||||
    if current_dir:
 | 
			
		||||
        parts = current_dir.split(os.sep)
 | 
			
		||||
@@ -105,6 +95,21 @@ def index():
 | 
			
		||||
                'name': part,
 | 
			
		||||
                'path': os.sep.join(parts[:i+1])
 | 
			
		||||
            })
 | 
			
		||||
    return breadcrumbs
 | 
			
		||||
 | 
			
		||||
@app.route('/')
 | 
			
		||||
def index():
 | 
			
		||||
    """Main page showing all notebooks"""
 | 
			
		||||
    current_dir = request.args.get('dir', '')
 | 
			
		||||
    full_current_dir = os.path.join(app.config['SHARED_DIRECTORY'], current_dir)
 | 
			
		||||
    if not os.path.exists(full_current_dir):
 | 
			
		||||
        abort(404)
 | 
			
		||||
    
 | 
			
		||||
    notebooks = get_notebook_files(full_current_dir)
 | 
			
		||||
    directories = get_directory_structure(full_current_dir)
 | 
			
		||||
    print(directories)
 | 
			
		||||
    # Breadcrumb navigation
 | 
			
		||||
    breadcrumbs = get_breadcrumbs(current_dir) 
 | 
			
		||||
    
 | 
			
		||||
    return render_template('index.html', 
 | 
			
		||||
                         notebooks=notebooks, 
 | 
			
		||||
@@ -125,17 +130,15 @@ def view_notebook(notebook_path):
 | 
			
		||||
    if not any(full_path.endswith(ext) for ext in app.config['ALLOWED_EXTENSIONS']):
 | 
			
		||||
        abort(403)
 | 
			
		||||
    
 | 
			
		||||
    # Security check - ensure path is within shared directory
 | 
			
		||||
    if not os.path.commonpath([full_path, app.config['SHARED_DIRECTORY']]) == app.config['SHARED_DIRECTORY']:
 | 
			
		||||
        abort(403)
 | 
			
		||||
    
 | 
			
		||||
    breadcrumbs = get_breadcrumbs(notebook_path)
 | 
			
		||||
    html_content = convert_notebook_to_html(full_path)
 | 
			
		||||
    
 | 
			
		||||
    return render_template('notebook.html', 
 | 
			
		||||
                         html_content=html_content,
 | 
			
		||||
                         notebook_name=os.path.basename(notebook_path),
 | 
			
		||||
                         notebook_path=notebook_path,
 | 
			
		||||
                         config=app.config)
 | 
			
		||||
                        html_content=html_content,
 | 
			
		||||
                        notebook_name=os.path.basename(notebook_path),
 | 
			
		||||
                        notebook_path=notebook_path,
 | 
			
		||||
                        breadcrumbs=breadcrumbs,
 | 
			
		||||
                        current_dir=notebook_path,   
 | 
			
		||||
                        config=app.config)
 | 
			
		||||
 | 
			
		||||
@app.route('/download/<path:notebook_path>')
 | 
			
		||||
def download_notebook(notebook_path):
 | 
			
		||||
@@ -153,7 +156,7 @@ def download_notebook(notebook_path):
 | 
			
		||||
        abort(403)
 | 
			
		||||
    
 | 
			
		||||
    # Security check
 | 
			
		||||
    if not os.path.commonpath([full_path, app.config['SHARED_DIRECTORY']]) == app.config['SHARED_DIRECTORY']:
 | 
			
		||||
    if os.path.commonpath([full_path, app.config['SHARED_DIRECTORY']]) == app.config['SHARED_DIRECTORY']:
 | 
			
		||||
        abort(403)
 | 
			
		||||
    
 | 
			
		||||
    return send_file(full_path, as_attachment=True)
 | 
			
		||||
@@ -184,247 +187,30 @@ def api_notebooks():
 | 
			
		||||
@app.errorhandler(404)
 | 
			
		||||
def not_found(error):
 | 
			
		||||
    return render_template('error.html', 
 | 
			
		||||
                         error_code=404, 
 | 
			
		||||
                         error_message="Notebook or directory not found"), 404
 | 
			
		||||
                        error_code=404, 
 | 
			
		||||
                        error_message="Notebook or directory not found",
 | 
			
		||||
                        current_dir="404"), 404
 | 
			
		||||
 | 
			
		||||
@app.errorhandler(403)
 | 
			
		||||
def forbidden(error):
 | 
			
		||||
    return render_template('error.html', 
 | 
			
		||||
                         error_code=403, 
 | 
			
		||||
                         error_message="Access forbidden"), 403
 | 
			
		||||
                        error_code=403, 
 | 
			
		||||
                        error_message="Access forbidden",
 | 
			
		||||
                        current_dir="403"), 403
 | 
			
		||||
 | 
			
		||||
@app.errorhandler(500)
 | 
			
		||||
def server_error(error):
 | 
			
		||||
    return render_template('error.html',
 | 
			
		||||
                         error_code=500, 
 | 
			
		||||
                         error_message="Internal server error"), 500
 | 
			
		||||
                        error_code=500, 
 | 
			
		||||
                        error_message="Internal server error",
 | 
			
		||||
                        current_dir="500"), 500
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Create templates directory if it doesn't exist
 | 
			
		||||
    os.makedirs('templates', exist_ok=True)
 | 
			
		||||
    
 | 
			
		||||
    # Determine theme classes
 | 
			
		||||
    navbar_class = "navbar-dark bg-dark" if app.config['THEME'] == 'dark' else "navbar-light bg-light"
 | 
			
		||||
    
 | 
			
		||||
    # Basic templates
 | 
			
		||||
    index_template = f'''<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>{{{{ config.APP_TITLE }}}}</title>
 | 
			
		||||
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet">
 | 
			
		||||
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <nav class="navbar {navbar_class}">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <a class="navbar-brand" href="{{{{ url_for('index') }}}}">
 | 
			
		||||
                <i class="fas fa-book"></i> {{{{ config.APP_TITLE }}}}
 | 
			
		||||
            </a>
 | 
			
		||||
        </div>
 | 
			
		||||
    </nav>
 | 
			
		||||
 | 
			
		||||
    <div class="container mt-4">
 | 
			
		||||
        <!-- Breadcrumb Navigation -->
 | 
			
		||||
        {{% if breadcrumbs %}}
 | 
			
		||||
        <nav aria-label="breadcrumb">
 | 
			
		||||
            <ol class="breadcrumb">
 | 
			
		||||
                <li class="breadcrumb-item"><a href="{{{{ url_for('index') }}}}">Home</a></li>
 | 
			
		||||
                {{% for crumb in breadcrumbs %}}
 | 
			
		||||
                <li class="breadcrumb-item">
 | 
			
		||||
                    <a href="{{{{ url_for('index', dir=crumb.path) }}}}">{{{{ crumb.name }}}}</a>
 | 
			
		||||
                </li>
 | 
			
		||||
                {{% endfor %}}
 | 
			
		||||
            </ol>
 | 
			
		||||
        </nav>
 | 
			
		||||
        {{% endif %}}
 | 
			
		||||
 | 
			
		||||
        <h1 class="mb-4">
 | 
			
		||||
            {{% if current_dir %}}
 | 
			
		||||
                Notebooks in {{{{ current_dir }}}}
 | 
			
		||||
            {{% else %}}
 | 
			
		||||
                All Notebooks
 | 
			
		||||
            {{% endif %}}
 | 
			
		||||
        </h1>
 | 
			
		||||
 | 
			
		||||
        <!-- Directories -->
 | 
			
		||||
        {{% if directories %}}
 | 
			
		||||
        <div class="row mb-4">
 | 
			
		||||
            <div class="col-12">
 | 
			
		||||
                <h3><i class="fas fa-folder"></i> Directories</h3>
 | 
			
		||||
                {{% for dir in directories %}}
 | 
			
		||||
                <div class="card mb-2">
 | 
			
		||||
                    <div class="card-body">
 | 
			
		||||
                        <h5 class="card-title">
 | 
			
		||||
                            <a href="{{{{ url_for('index', dir=dir.path) }}}}" class="text-decoration-none">
 | 
			
		||||
                                <i class="fas fa-folder text-warning"></i> {{{{ dir.name }}}}
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </h5>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{% endfor %}}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        {{% endif %}}
 | 
			
		||||
 | 
			
		||||
        <!-- Notebooks -->
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-12">
 | 
			
		||||
                <h3><i class="fas fa-file-code"></i> Notebooks</h3>
 | 
			
		||||
                {{% if notebooks %}}
 | 
			
		||||
                    {{% for notebook in notebooks %}}
 | 
			
		||||
                    <div class="card mb-3">
 | 
			
		||||
                        <div class="card-body">
 | 
			
		||||
                            <div class="row align-items-center">
 | 
			
		||||
                                <div class="col-md-8">
 | 
			
		||||
                                    <h5 class="card-title">
 | 
			
		||||
                                        <i class="fas fa-file-code text-primary"></i> {{{{ notebook.name }}}}
 | 
			
		||||
                                    </h5>
 | 
			
		||||
                                    <p class="card-text text-muted">
 | 
			
		||||
                                        <small>
 | 
			
		||||
                                            Size: {{{{ "%.1f"|format(notebook.size/1024) }}}} KB | 
 | 
			
		||||
                                            Modified: {{{{ notebook.modified|int|datetime }}}}
 | 
			
		||||
                                        </small>
 | 
			
		||||
                                    </p>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="col-md-4 text-end">
 | 
			
		||||
                                    <a href="{{{{ url_for('view_notebook', notebook_path=notebook.path) }}}}" 
 | 
			
		||||
                                       class="btn btn-primary btn-sm me-2">
 | 
			
		||||
                                        <i class="fas fa-eye"></i> View
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                    {{% if config.ENABLE_DOWNLOAD %}}
 | 
			
		||||
                                    <a href="{{{{ url_for('download_notebook', notebook_path=notebook.path) }}}}" 
 | 
			
		||||
                                       class="btn btn-secondary btn-sm">
 | 
			
		||||
                                        <i class="fas fa-download"></i> Download
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                    {{% endif %}}
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {{% endfor %}}
 | 
			
		||||
                    {{% if notebooks|length == config.NOTEBOOKS_PER_PAGE %}}
 | 
			
		||||
                    <div class="alert alert-info">
 | 
			
		||||
                        <i class="fas fa-info-circle"></i> Showing first {{{{ config.NOTEBOOKS_PER_PAGE }}}} notebooks. Configure NOTEBOOKS_PER_PAGE to show more.
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {{% endif %}}
 | 
			
		||||
                {{% else %}}
 | 
			
		||||
                    <div class="alert alert-info">
 | 
			
		||||
                        <i class="fas fa-info-circle"></i> No notebooks found in this directory.
 | 
			
		||||
                    </div>
 | 
			
		||||
                {{% endif %}}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>'''
 | 
			
		||||
 | 
			
		||||
    notebook_template = f'''<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>{{{{ notebook_name }}}} - {{{{ config.APP_TITLE }}}}</title>
 | 
			
		||||
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet">
 | 
			
		||||
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
 | 
			
		||||
    <style>
 | 
			
		||||
        .notebook-content {{
 | 
			
		||||
            max-width: 100%;
 | 
			
		||||
            overflow-x: auto;
 | 
			
		||||
        }}
 | 
			
		||||
        .notebook-content img {{
 | 
			
		||||
            max-width: 100%;
 | 
			
		||||
            height: auto;
 | 
			
		||||
        }}
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <nav class="navbar {navbar_class}">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <a class="navbar-brand" href="{{{{ url_for('index') }}}}">
 | 
			
		||||
                <i class="fas fa-book"></i> {{{{ config.APP_TITLE }}}}
 | 
			
		||||
            </a>
 | 
			
		||||
            <div>
 | 
			
		||||
                {{% if config.ENABLE_DOWNLOAD %}}
 | 
			
		||||
                <a href="{{{{ url_for('download_notebook', notebook_path=notebook_path) }}}}" 
 | 
			
		||||
                   class="btn btn-outline-light btn-sm">
 | 
			
		||||
                    <i class="fas fa-download"></i> Download
 | 
			
		||||
                </a>
 | 
			
		||||
                {{% endif %}}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </nav>
 | 
			
		||||
 | 
			
		||||
    <div class="container-fluid mt-4">
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-12">
 | 
			
		||||
                <h2 class="mb-3">
 | 
			
		||||
                    <i class="fas fa-file-code text-primary"></i> {{{{ notebook_name }}}}
 | 
			
		||||
                </h2>
 | 
			
		||||
                <div class="notebook-content">
 | 
			
		||||
                    {{{{ html_content|safe }}}}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>'''
 | 
			
		||||
 | 
			
		||||
    error_template = f'''<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>Error {{{{ error_code }}}} - {{{{ config.APP_TITLE if config else 'JupyterHub Notebook Viewer' }}}}</title>
 | 
			
		||||
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet">
 | 
			
		||||
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <nav class="navbar {navbar_class}">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <a class="navbar-brand" href="{{{{ url_for('index') }}}}">
 | 
			
		||||
                <i class="fas fa-book"></i> {{{{ config.APP_TITLE if config else 'JupyterHub Notebook Viewer' }}}}
 | 
			
		||||
            </a>
 | 
			
		||||
        </div>
 | 
			
		||||
    </nav>
 | 
			
		||||
 | 
			
		||||
    <div class="container mt-5">
 | 
			
		||||
        <div class="row justify-content-center">
 | 
			
		||||
            <div class="col-md-6 text-center">
 | 
			
		||||
                <div class="alert alert-danger">
 | 
			
		||||
                    <h1><i class="fas fa-exclamation-triangle"></i></h1>
 | 
			
		||||
                    <h2>Error {{{{ error_code }}}}</h2>
 | 
			
		||||
                    <p>{{{{ error_message }}}}</p>
 | 
			
		||||
                    <a href="{{{{ url_for('index') }}}}" class="btn btn-primary">
 | 
			
		||||
                        <i class="fas fa-home"></i> Go Home
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>'''
 | 
			
		||||
 | 
			
		||||
    # Write templates
 | 
			
		||||
    with open('templates/index.html', 'w') as f:
 | 
			
		||||
        f.write(index_template)
 | 
			
		||||
    
 | 
			
		||||
    with open('templates/notebook.html', 'w') as f:
 | 
			
		||||
        f.write(notebook_template)
 | 
			
		||||
    
 | 
			
		||||
    with open('templates/error.html', 'w') as f:
 | 
			
		||||
        f.write(error_template)
 | 
			
		||||
 | 
			
		||||
    # Add datetime filter
 | 
			
		||||
    @app.template_filter('datetime')
 | 
			
		||||
    def datetime_filter(timestamp):
 | 
			
		||||
        from datetime import datetime
 | 
			
		||||
        return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
 | 
			
		||||
        return datetime.fromtimestamp(timestamp).strftime('%d.%m.%Y %H:%M:%S')
 | 
			
		||||
 | 
			
		||||
    print(f"Starting {app.config['APP_TITLE']}")
 | 
			
		||||
    print(f"Shared directory: {app.config['SHARED_DIRECTORY']}")
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,7 @@
 | 
			
		||||
          netcat
 | 
			
		||||
          
 | 
			
		||||
          # Process management
 | 
			
		||||
          supervisor
 | 
			
		||||
          #supervisor
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        # Shell script for easy setup
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								src/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/app.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
from flask import Flask, render_template, request
 | 
			
		||||
import os 
 | 
			
		||||
 | 
			
		||||
app = Flask(__name__)
 | 
			
		||||
 | 
			
		||||
app_title = os.getenv('APP_TITLE', 'JupyShare')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_breadcrumbs():
 | 
			
		||||
    path_parts = request.path.split('/')[1:]  # Remove empty first element
 | 
			
		||||
    breadcrumbs = []
 | 
			
		||||
    accumulated_path = ''
 | 
			
		||||
    
 | 
			
		||||
    for i, part in enumerate(path_parts):
 | 
			
		||||
        if not part:
 | 
			
		||||
            continue
 | 
			
		||||
        accumulated_path += f'/{part}'
 | 
			
		||||
        breadcrumbs.append({
 | 
			
		||||
            'name': part.capitalize(),
 | 
			
		||||
            'url': accumulated_path,
 | 
			
		||||
            'is_active': (i == len(path_parts) - 1)
 | 
			
		||||
        })
 | 
			
		||||
    
 | 
			
		||||
    # Always include home at the beginning
 | 
			
		||||
    if not breadcrumbs or breadcrumbs[0]['name'].lower() != 'home':
 | 
			
		||||
        breadcrumbs.insert(0, {
 | 
			
		||||
            'name': 'Home',
 | 
			
		||||
            'url': '/',
 | 
			
		||||
            'is_active': len(path_parts) == 0
 | 
			
		||||
        })
 | 
			
		||||
    
 | 
			
		||||
    return breadcrumbs
 | 
			
		||||
 | 
			
		||||
@app.route('/')
 | 
			
		||||
def home():
 | 
			
		||||
    print(get_breadcrumbs())
 | 
			
		||||
    return render_template('index.html', 
 | 
			
		||||
                            app_title=app_title,
 | 
			
		||||
                            breadcrumbs=get_breadcrumbs()
 | 
			
		||||
                           )
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    app.run(debug=True)
 | 
			
		||||
							
								
								
									
										79
									
								
								src/static/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/static/style.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
* {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    font-family: Arial, sans-serif;
 | 
			
		||||
    line-height: 1.6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main {
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    max-width: 800px;
 | 
			
		||||
    margin: 80px auto 0;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    border: 2px solid red;
 | 
			
		||||
    width: 80%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 {
 | 
			
		||||
    color: #333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navbar {
 | 
			
		||||
    background-color: #333;
 | 
			
		||||
    color: white;
 | 
			
		||||
    padding: 15px 0;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    width: 100vw;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    z-index: 1000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    max-width: 800px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    padding: 0 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.logo {
 | 
			
		||||
    color: white;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    font-size: 1.5em;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Breadcrumbs */
 | 
			
		||||
.breadcrumbs {
 | 
			
		||||
    padding: 15px 0;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
    color: #666;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
    border-bottom: 1px solid #eee;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb-link {
 | 
			
		||||
    color: #3498db;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb-link:hover {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb-separator {
 | 
			
		||||
    margin: 0 5px;
 | 
			
		||||
    color: #999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb-current {
 | 
			
		||||
    color: #333;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/templates/_breadcrumbs.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/templates/_breadcrumbs.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
<div class="breadcrumbs">
 | 
			
		||||
    {% for crumb in breadcrumbs %}
 | 
			
		||||
        {% if not crumb.is_active %}
 | 
			
		||||
            <a href="{{ crumb.url }}" class="breadcrumb-link">{{ crumb.name }}</a>
 | 
			
		||||
            <span class="breadcrumb-separator">/</span>
 | 
			
		||||
        {% else %}
 | 
			
		||||
            <span class="breadcrumb-current">{{ crumb.name }}</span>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										24
									
								
								src/templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/templates/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>{{ app_title }}</title>
 | 
			
		||||
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <nav class="navbar">
 | 
			
		||||
        <div class="nav-container">
 | 
			
		||||
            <a href="{{ url_for('home') }}" class="logo">{{ app_title }} </a>
 | 
			
		||||
        </div>
 | 
			
		||||
    </nav>
 | 
			
		||||
    
 | 
			
		||||
    <main>
 | 
			
		||||
        <h1>All Notebooks</h1>
 | 
			
		||||
 | 
			
		||||
        {% include '_breadcrumbs.html' %}
 | 
			
		||||
        <p>This is a basic Flask application.</p>
 | 
			
		||||
    </main>
 | 
			
		||||
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										233
									
								
								static/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								static/style.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,233 @@
 | 
			
		||||
/* Reset defaults */ 
 | 
			
		||||
.body {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    font-family: Arial, sans-serif;
 | 
			
		||||
    background-color: #f7f7f7;
 | 
			
		||||
    color: #333333;
 | 
			
		||||
    line-height: 1.5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main {
 | 
			
		||||
    max-width: 80vw;
 | 
			
		||||
    margin: 20px auto;
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
    padding-top: 0px;
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    border: 1px solid #e0e0e0;
 | 
			
		||||
    border-radius: 2px;
 | 
			
		||||
    box-shadow: 0 1px 2px rgba(0,0,0,0.7);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
i {
 | 
			
		||||
    color: #ffD43b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.a {
 | 
			
		||||
    color: #007bff;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    transition: color 0.15s ease-in-out;
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.a:hover {
 | 
			
		||||
    color: #0056b3;
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
hr {
 | 
			
		||||
    margin: 10px 0;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Navbar */
 | 
			
		||||
 | 
			
		||||
.navbar {
 | 
			
		||||
    position: sticky;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
 | 
			
		||||
    z-index: 1000;
 | 
			
		||||
    padding: 15px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navbar img {
 | 
			
		||||
    height: 68px;
 | 
			
		||||
    width: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navbar-container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    max-width: 1200px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    padding: 0 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navbar-middle {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    color: #333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navbar-right {
 | 
			
		||||
    background-color: #f0f0f0;
 | 
			
		||||
    padding: 8px 15px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Breadcrum */
 | 
			
		||||
 | 
			
		||||
.breadcrumb {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
    padding: 0.75rem 1rem;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    list-style: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb-item {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb-item:not(:last-child) + .breadcrumb-item::before {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    padding: 0 0.25rem;
 | 
			
		||||
    color: #6c757d;
 | 
			
		||||
    content: "→";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb-item:last-child {
 | 
			
		||||
    color: #495057;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb-item.active {
 | 
			
		||||
    color: #6c757d;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
    cursor: default;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nav[aria-label="breadcrumb"] {
 | 
			
		||||
    margin: 1rem 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Shared */
 | 
			
		||||
.row {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.row h3 {
 | 
			
		||||
    padding: 0 15px;
 | 
			
		||||
    font-size: 1.4rem;
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.row a {
 | 
			
		||||
    font-size: 1.1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.notebook-header {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    font-size: 1.5rem;
 | 
			
		||||
    margin: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Card */
 | 
			
		||||
.card-container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    border: 1px solid #6c757d;
 | 
			
		||||
    border-radius: 2px;
 | 
			
		||||
    margin: 10px;
 | 
			
		||||
    padding: 15px 25px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card-stats {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card-stats p {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
/* Buttons */
 | 
			
		||||
 | 
			
		||||
.buttons {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    justify-content: right;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button {
 | 
			
		||||
    padding: 5px 10px;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    color: white;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    transition: all 0.3 ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button:hover {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button i {
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.view-button {
 | 
			
		||||
    background-color: blue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.download-button {
 | 
			
		||||
    background-color: grey;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Error */ 
 | 
			
		||||
.error {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.error h1 {
 | 
			
		||||
    font-size: 3rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.error strong {
 | 
			
		||||
    font-size: 1.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.error i {
 | 
			
		||||
    color: red;
 | 
			
		||||
    font-size: 8rem;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								templates/_breadcrumb.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								templates/_breadcrumb.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
<nav aria-label="breadcrumb">
 | 
			
		||||
    <ol class="breadcrumb">
 | 
			
		||||
        <li class="breadcrumb-item"><a href="{{ url_for('index') }}" class="a">Home</a></li>
 | 
			
		||||
        {% for crumb in breadcrumbs %}
 | 
			
		||||
        <li class="breadcrumb-item">
 | 
			
		||||
            <a href="{{ url_for('index', dir=crumb.path) }}" class="a">{{ crumb.name }}</a>
 | 
			
		||||
        </li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </ol>
 | 
			
		||||
    <hr>
 | 
			
		||||
</nav>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								templates/_folder.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								templates/_folder.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
<div class="card-container">
 | 
			
		||||
    <div class="card">
 | 
			
		||||
        <a href="{{ url_for('index', dir=dir.path) }}" class="a">
 | 
			
		||||
            <i class="fas fa-folder"></i> {{ dir.name }}
 | 
			
		||||
        </a>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										19
									
								
								templates/_navbar.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								templates/_navbar.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
<nav class="navbar">
 | 
			
		||||
    <div class="navbar-container">
 | 
			
		||||
        <a href="{{ url_for('index') }}">
 | 
			
		||||
            <img src="https://www.tu-braunschweig.de/fileadmin/Logos_Einrichtungen/Institute_FK5/logo_IFN.svg"/>
 | 
			
		||||
        </a>
 | 
			
		||||
        
 | 
			
		||||
        <div class="navbar-middle">
 | 
			
		||||
            
 | 
			
		||||
            <h1>
 | 
			
		||||
                {% if current_dir %}
 | 
			
		||||
                    {{ current_dir }}
 | 
			
		||||
                {% else %}
 | 
			
		||||
                    All Notebooks
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </h1>
 | 
			
		||||
 | 
			
		||||
        </div>        
 | 
			
		||||
    </div>
 | 
			
		||||
</nav>
 | 
			
		||||
							
								
								
									
										21
									
								
								templates/_notebook.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								templates/_notebook.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
<div class="card-container">
 | 
			
		||||
    <div class="card">
 | 
			
		||||
        <a href="{{ url_for('view_notebook', notebook_path=notebook.path) }}" class="a">
 | 
			
		||||
            <i class="fas fa-file-code"></i> {{ notebook.name }}
 | 
			
		||||
        </a>
 | 
			
		||||
 | 
			
		||||
        <div class="buttons">
 | 
			
		||||
            <div class="card-stats">
 | 
			
		||||
                <p>Last Modified: <strong>{{ notebook.modified }}</strong></p>
 | 
			
		||||
                <p>Size: <strong>{{ notebook.size }} KB</strong></p>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% if config.ENABLE_DOWNLOAD %}
 | 
			
		||||
            <a href="{{ url_for('download_notebook', notebook_path=notebook.path) }}" class="button download-button">
 | 
			
		||||
                <i class="fas fa-download"></i> Download
 | 
			
		||||
            </a>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										19
									
								
								templates/base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								templates/base.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"/>
 | 
			
		||||
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
 | 
			
		||||
    <title>{{ config.APP_TITLE }}</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body class="body">
 | 
			
		||||
    {% include '_navbar.html' %}
 | 
			
		||||
 | 
			
		||||
    <main class="main">
 | 
			
		||||
        {% include '_breadcrumb.html' %}
 | 
			
		||||
        {% block content %} 
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
    </main>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										9
									
								
								templates/error.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								templates/error.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <div class="error">
 | 
			
		||||
        <i class="fas fa-triangle-exclamation"></i>
 | 
			
		||||
        <h1>Error {{error_code}}</h1>
 | 
			
		||||
        <strong>{{error_message}}</strong>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								templates/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <!-- Directories -->
 | 
			
		||||
    {% if directories %}
 | 
			
		||||
        <div class="row">
 | 
			
		||||
                <h3><i class="fas fa-folder"></i> Directories</h3>
 | 
			
		||||
 | 
			
		||||
                {% for dir in directories %}
 | 
			
		||||
                {% include '_folder.html' %}
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <hr />
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    <!-- Notebooks -->
 | 
			
		||||
    {% if notebooks %}
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <h3><i class="fas fa-file-code"></i> Notebooks</h3>
 | 
			
		||||
            {% for notebook in notebooks %}
 | 
			
		||||
            {% include '_notebook.html' %}
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								templates/notebook.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								templates/notebook.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
{% extends 'base.html' %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <div class="notebook-header">
 | 
			
		||||
        <h2>
 | 
			
		||||
            <i class="fas fa-file-code text-primary"></i> {{ notebook_name }}
 | 
			
		||||
        </h2>
 | 
			
		||||
        {% if config.ENABLE_DOWNLOAD %}
 | 
			
		||||
            <a href="{{ url_for('download_notebook', notebook_path=notebook_path) }}" class="a button download-button">
 | 
			
		||||
                <i class="fas fa-download"></i> Download
 | 
			
		||||
            </a>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
    {{ html_content|safe }}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user