Loading services/webapp/code/rosetta/core_app/templates/add_storage.html 0 → 100644 +71 −0 Original line number Diff line number Diff line {% load static %} {% include "header.html" %} {% include "navigation.html" %} {% include "logo.html" %} <div class="container"> <div class="dashboard"> <div class="span8 offset2"> <h1><a href="/storage/?manage=True">Storage resources</a> <span style="font-size:18px">/ add</span></h1> <hr> {% if data.error %} <div class="alert alert-danger">{{ data.error }}</div> {% endif %} {% if data.added %} <div class="alert alert-success">Storage added successfully.</div> {% endif %} <form action="#" method="POST"> {% csrf_token %} <table class="dashboard" style="width:500px; margin-bottom:25px"> <tr> <td><b>Name</b></td> <td><input type="text" name="name" value="" size="30" required /></td> </tr> <tr> <td><b>Type</b></td> <td><input type="text" name="type" value="" size="30" required /></td> </tr> <tr> <td><b>Access mode</b></td> <td><input type="text" name="access_mode" value="" size="30" required /></td> </tr> <tr> <td><b>Auth mode</b></td> <td><input type="text" name="auth_mode" value="" size="30" required /></td> </tr> <tr> <td><b>Base path</b></td> <td><input type="text" name="base_path" value="" size="30" required /></td> </tr> <tr> <td><b>Bind path</b></td> <td><input type="text" name="bind_path" value="" size="30" /></td> </tr> <tr> <td><b>Read only</b></td> <td><input type="checkbox" name="read_only" value="True" /></td> </tr> <tr> <td><b>Browsable</b></td> <td><input type="checkbox" name="browsable" value="True" checked /></td> </tr> <tr> <td><b>Conf</b><br/><span style="font-size:0.8em">(JSON dict)</span></td> <td><textarea name="conf" rows="2" cols="30"></textarea></td> </tr> </table> <table style="width:500px; border:0; background:#ffffff; margin-top:20px"> <tr><td align="center"> <a href="/storage/?manage=True" class="btn btn-primary" style="margin-left:10px">Back</a> <input type="submit" value="Save" class="btn btn-primary"> </td></tr> </table> </form> </div> </div> </div> {% include "footer.html" %} No newline at end of file services/webapp/code/rosetta/core_app/templates/components/storage.html 0 → 100644 +53 −0 Original line number Diff line number Diff line {% comment %} Storage resource component, main list and detailed view {% endcomment %} {% if data.storage and storage.uuid == data.storage.uuid %} <div style="float:left; width:500px; margin:10px; margin-bottom:20px"> <table class="dashboard" style="width:100%"> <tr><td><b>Name</b></td><td>{{ storage.name }}</td></tr> <tr><td><b>Description</b></td><td>{{ storage.description }}</td></tr> <tr><td><b>Type</b></td><td>{{ storage.type }}</td></tr> <tr><td><b>Access mode</b></td><td>{{ storage.access_mode }}</td></tr> <tr><td><b>Auth mode</b></td><td>{{ storage.auth_mode }}</td></tr> <tr><td><b>Base path</b></td><td><code>{{ storage.base_path }}</code></td></tr> <tr><td><b>Bind path</b></td><td><code>{{ storage.bind_path }}</code></td></tr> <tr><td><b>Read only</b></td><td>{% if storage.read_only %}Yes{% else %}No{% endif %}</td></tr> <tr><td><b>Browsable</b></td><td>{% if storage.browsable %}Yes{% else %}No{% endif %}</td></tr> <tr><td><b>Group</b></td><td>{% if storage.group %}{{ storage.group }}{% else %}Platform{% endif %}</td></tr> <tr><td><b>Computing</b></td><td>{% if storage.computing %}{{ storage.computing.name }}{% else %}-{% endif %}</td></tr> <tr><td><b>Conf</b></td><td><pre style="font-size:0.9em">{{ storage.conf|default_if_none:'' }}</pre></td></tr> {% if request.user.is_staff %} <tr> <td><b>Actions</b></td> <td> <a href="/edit_storage/?uuid={{ storage.uuid }}">Edit</a> | <a href="/storage/?action=delete&uuid={{ storage.uuid }}" onclick="return confirm('Are you sure you want to delete this storage resource?');">Delete</a> | <a href="/storage/?action=duplicate&uuid={{ storage.uuid }}" onclick="return confirm('Duplicate this storage resource?');">Duplicate</a> </td> </tr> {% endif %} </table> </div> {% else %} <div style="width:300px; float:left; border: #e0e0e0 solid 1px; margin:10px; background:#f8f8f8; margin-bottom:15px;"> <div style="padding:10px; margin-top:5px; text-align:center; border-bottom: #e0e0e0 solid 1px; "> <a href="/storage/?manage=True&uuid={{ storage.uuid }}">{{ storage.name }}</a> </div> <div style="padding:10px;"> {% if storage.description %} <div class="description-box" title="{{ storage.description }}"> {{ storage.description }} </div> {% else %} <br/> {% endif %} <div class="image-version-box"> <b>Type:</b> {{ storage.type }}<br/> <b>Base path:</b> <code>{{ storage.base_path }}</code><br/> <b>Bind path:</b> <code>{{ storage.bind_path }}</code><br/> <b>Read only:</b> {% if storage.read_only %}Yes{% else %}No{% endif %}<br/> <b>Browsable:</b> {% if storage.browsable %}Yes{% else %}No{% endif %}<br/> </div> </div> </div> {% endif %} No newline at end of file services/webapp/code/rosetta/core_app/templates/edit_storage.html 0 → 100644 +71 −0 Original line number Diff line number Diff line {% load static %} {% include "header.html" %} {% include "navigation.html" %} {% include "logo.html" %} <div class="container"> <div class="dashboard"> <div class="span8 offset2"> <h1><a href="/storage">Storage resources</a> <span style="font-size:18px"> / <a href="/storage/?uuid={{ data.storage.uuid }}">{{ data.storage.name }}</a> /edit</span></h1> <hr> {% if data.error %} <div class="alert alert-danger">{{ data.error }}</div> {% endif %} {% if data.edited %} <div class="alert alert-success">Storage updated successfully.</div> {% endif %} <form action="#" method="POST"> {% csrf_token %} <table class="dashboard" style="width:500px; margin-bottom:25px"> <tr> <td><b>Name</b></td> <td><input type="text" name="name" value="{{ data.storage.name }}" size="30" required /></td> </tr> <tr> <td><b>Type</b></td> <td><input type="text" name="type" value="{{ data.storage.type }}" size="30" required /></td> </tr> <tr> <td><b>Access mode</b></td> <td><input type="text" name="access_mode" value="{{ data.storage.access_mode }}" size="30" required /></td> </tr> <tr> <td><b>Auth mode</b></td> <td><input type="text" name="auth_mode" value="{{ data.storage.auth_mode }}" size="30" required /></td> </tr> <tr> <td><b>Base path</b></td> <td><input type="text" name="base_path" value="{{ data.storage.base_path }}" size="30" required /></td> </tr> <tr> <td><b>Bind path</b></td> <td><input type="text" name="bind_path" value="{{ data.storage.bind_path }}" size="30" /></td> </tr> <tr> <td><b>Read only</b></td> <td><input type="checkbox" name="read_only" value="True" {% if data.storage.read_only %}checked{% endif %} /></td> </tr> <tr> <td><b>Browsable</b></td> <td><input type="checkbox" name="browsable" value="True" {% if data.storage.browsable %}checked{% endif %} /></td> </tr> <tr> <td><b>Conf</b><br/><span style="font-size:0.8em">(JSON dict)</span></td> <td><textarea name="conf" rows="2" cols="30">{{ data.conf_json }}</textarea></td> </tr> </table> <table style="width:500px; border:0; background:#ffffff; margin-top:20px"> <tr><td align="center"> <a href="/storage/?manage=True&uuid={{ data.storage.uuid }}" class="btn btn-primary" style="margin-left:10px">Back</a> <input type="submit" value="Save changes" class="btn btn-primary"> </td></tr> </table> </form> </div> </div> </div> {% include "footer.html" %} No newline at end of file services/webapp/code/rosetta/core_app/templates/storage.html +40 −3 Original line number Diff line number Diff line Loading @@ -7,15 +7,52 @@ <div class="dashboard" style="height:100%;"> <div class="span8 offset2" style="height:100%;"> {% if not data.storage %} <h1>Storage resources</h1> {% else %} <h1><a href="/storage/?manage=True">Storage resources</a> <span style="font-size:18px"> / <a href="/storage/?manage=True&uuid={{ data.storage.uuid }}">{{ data.storage.name }}</a></span></h1> {% endif %} <hr/> {% if not data.manage %} <div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">Browse</div> <div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="/storage/?manage=True">Manage</a></div> <hr style="margin-top:0;"> <iframe src="/static/RichFilemanager/index.html" style="overflow:hidden;height:100%;width:100%; border: 1px solid #c0c0c0" height="100%" width="100%"></iframe> {% else %} <div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="/storage/">Browse</a></div> <div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">Manage</div> <hr style="margin-top:0;"> {% if not data.storage %} <div class="form-filter" style="margin-bottom:20px"> <form action="" method="POST"> <input type="text" class="form-control" id="search_text" name="search_text" placeholder="Search..." style="width:200px; margin:0; display:inline" value="{{data.search_text}}" autofocus> <select class="form-control" id="search_owner" name="search_owner" style="width:120px; margin:0; display:inline"> {% if data.search_owner == 'All' %}<option selected>All</option>{% else %}<option>All</option>{% endif %} {% if data.search_owner == 'Platform' %}<option selected>Platform</option>{% else %}<option>Platform</option>{% endif %} {% if data.search_owner == 'User' or data.search_owner == 'Group' %}<option selected>User</option>{% else %}<option>User</option>{% endif %} </select> {% csrf_token %} <button type="submit" class="btn btn-secondary">Go</button> {% if request.user.is_staff %} <font size=4.0em>|</font> <a href="/add_storage/">Add new...</a> {% endif %} </form> </div> {% endif %} <div class="row" style="padding:5px"> {% for storage in data.storages %} {% include "components/storage.html" with storage=storage %} {% empty %} <p>No storage resources found.</p> {% endfor %} </div> {% endif %} </div> </div> </div> Loading services/webapp/code/rosetta/core_app/views.py +187 −3 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound from django.contrib.auth.models import User from django.shortcuts import redirect from django.db.models import Q from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, Page from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, Page, Storage from .utils import send_email, format_exception, timezonize, os_shell, booleanize, get_rosetta_tasks_tunnel_host from .utils import get_rosetta_tasks_proxy_host, random_username, setup_tunnel_and_proxy, finalize_user_creation from .utils import sanitize_container_env_vars, get_or_create_container_from_repository Loading Loading @@ -1237,14 +1237,151 @@ def edit_computing(request): #========================= @private_view def storage(request): # Set data & render data = {} data['user'] = request.user data['manage'] = request.GET.get('manage', False) storage_uuid = request.GET.get('uuid', None) action = request.GET.get('action', None) search_text = request.POST.get('search_text', '') search_owner = request.POST.get('search_owner', 'All') data['search_text'] = search_text data['search_owner'] = search_owner # Handle delete action if action == 'delete' and storage_uuid: if not request.user.is_staff: data['error'] = 'You do not have permission to delete this storage.' return render(request, 'error.html', {'data': data}) try: storage = Storage.objects.get(uuid=storage_uuid) storage.delete() return redirect('/storage/?manage=True') except Storage.DoesNotExist: data['error'] = 'Storage not found.' return render(request, 'error.html', {'data': data}) except Exception as e: data['error'] = f'Error deleting storage: {e}' return render(request, 'error.html', {'data': data}) # Handle duplicate action if action == 'duplicate' and storage_uuid: if not request.user.is_staff: data['error'] = 'You do not have permission to duplicate this storage.' return render(request, 'error.html', {'data': data}) try: storage = Storage.objects.get(uuid=storage_uuid) # Create a copy from copy import deepcopy new_storage = Storage( name=f"{storage.name} (copy)", type=storage.type, access_mode=storage.access_mode, auth_mode=storage.auth_mode, base_path=storage.base_path, bind_path=storage.bind_path, read_only=storage.read_only, browsable=storage.browsable, group=storage.group, computing=storage.computing, access_through_computing=storage.access_through_computing, conf=deepcopy(storage.conf) if storage.conf else None ) new_storage.save() return redirect(f'/edit_storage/?uuid={new_storage.uuid}') except Storage.DoesNotExist: data['error'] = 'Storage not found.' return render(request, 'error.html', {'data': data}) except Exception as e: data['error'] = f'Error duplicating storage: {e}' return render(request, 'error.html', {'data': data}) if storage_uuid: try: storage = Storage.objects.get(uuid=storage_uuid) data['storages'] = [storage] data['storage'] = storage except Storage.DoesNotExist: data['storages'] = [] data['storage'] = None data['error'] = 'Storage not found.' else: storages = Storage.objects.all() # Filtering by search_text if search_text: from django.db.models import Q storages = storages.filter( Q(name__icontains=search_text) | Q(base_path__icontains=search_text) | Q(bind_path__icontains=search_text) | Q(description__icontains=search_text) ) # Filtering by owner if search_owner != 'All': if search_owner == 'Platform': storages = storages.filter(group=None) elif search_owner == 'User' or search_owner == 'Group': storages = storages.exclude(group=None) data['storages'] = list(storages) data['storage'] = None return render(request, 'storage.html', {'data': data}) @private_view def edit_storage(request): data = {} data['user'] = request.user storage_uuid = request.GET.get('uuid', None) if not storage_uuid: data['error'] = 'No storage specified.' return render(request, 'error.html', {'data': data}) try: storage = Storage.objects.get(uuid=storage_uuid) except Storage.DoesNotExist: data['error'] = 'Storage does not exist.' return render(request, 'error.html', {'data': data}) if not request.user.is_staff: data['error'] = 'You do not have permission to edit this storage.' return render(request, 'error.html', {'data': data}) data['storage'] = storage data['edited'] = False # For JSON field display data['conf_json'] = json.dumps(storage.conf, indent=2) if storage.conf else '' if request.method == 'POST': storage.name = request.POST.get('name', storage.name) storage.type = request.POST.get('type', storage.type) storage.access_mode = request.POST.get('access_mode', storage.access_mode) storage.auth_mode = request.POST.get('auth_mode', storage.auth_mode) storage.base_path = request.POST.get('base_path', storage.base_path) storage.bind_path = request.POST.get('bind_path', storage.bind_path) storage.read_only = bool(request.POST.get('read_only', False)) storage.browsable = bool(request.POST.get('browsable', False)) # Foreign keys (group, computing) not handled for now conf = request.POST.get('conf', None) if conf: try: storage.conf = json.loads(conf) except Exception: data['error'] = 'Invalid conf format (must be JSON dict).' return render(request, 'edit_storage.html', {'data': data}) try: storage.save() data['edited'] = True data['conf_json'] = json.dumps(storage.conf, indent=2) if storage.conf else '' except Exception as e: data['error'] = f'Error saving storage: {e}' return render(request, 'edit_storage.html', {'data': data}) return render(request, 'edit_storage.html', {'data': data}) #========================= Loading Loading @@ -1500,3 +1637,50 @@ def import_repository(request): return render(request, 'import_repository.html', {'data': data}) @private_view def add_storage(request): data = {} data['user'] = request.user data['added'] = False if not request.user.is_staff: data['error'] = 'You do not have permission to add storage.' return render(request, 'error.html', {'data': data}) if request.method == 'POST': name = request.POST.get('name', None) type_ = request.POST.get('type', None) access_mode = request.POST.get('access_mode', None) auth_mode = request.POST.get('auth_mode', None) base_path = request.POST.get('base_path', None) bind_path = request.POST.get('bind_path', None) read_only = bool(request.POST.get('read_only', False)) browsable = bool(request.POST.get('browsable', False)) conf = request.POST.get('conf', None) conf_obj = None if conf: try: conf_obj = json.loads(conf) except Exception: data['error'] = 'Invalid conf format (must be JSON dict).' return render(request, 'add_storage.html', {'data': data}) try: Storage.objects.create( name=name, type=type_, access_mode=access_mode, auth_mode=auth_mode, base_path=base_path, bind_path=bind_path, read_only=read_only, browsable=browsable, conf=conf_obj ) data['added'] = True return redirect('/storage/?manage=True') except Exception as e: data['error'] = f'Error creating storage: {e}' return render(request, 'add_storage.html', {'data': data}) return render(request, 'add_storage.html', {'data': data}) Loading
services/webapp/code/rosetta/core_app/templates/add_storage.html 0 → 100644 +71 −0 Original line number Diff line number Diff line {% load static %} {% include "header.html" %} {% include "navigation.html" %} {% include "logo.html" %} <div class="container"> <div class="dashboard"> <div class="span8 offset2"> <h1><a href="/storage/?manage=True">Storage resources</a> <span style="font-size:18px">/ add</span></h1> <hr> {% if data.error %} <div class="alert alert-danger">{{ data.error }}</div> {% endif %} {% if data.added %} <div class="alert alert-success">Storage added successfully.</div> {% endif %} <form action="#" method="POST"> {% csrf_token %} <table class="dashboard" style="width:500px; margin-bottom:25px"> <tr> <td><b>Name</b></td> <td><input type="text" name="name" value="" size="30" required /></td> </tr> <tr> <td><b>Type</b></td> <td><input type="text" name="type" value="" size="30" required /></td> </tr> <tr> <td><b>Access mode</b></td> <td><input type="text" name="access_mode" value="" size="30" required /></td> </tr> <tr> <td><b>Auth mode</b></td> <td><input type="text" name="auth_mode" value="" size="30" required /></td> </tr> <tr> <td><b>Base path</b></td> <td><input type="text" name="base_path" value="" size="30" required /></td> </tr> <tr> <td><b>Bind path</b></td> <td><input type="text" name="bind_path" value="" size="30" /></td> </tr> <tr> <td><b>Read only</b></td> <td><input type="checkbox" name="read_only" value="True" /></td> </tr> <tr> <td><b>Browsable</b></td> <td><input type="checkbox" name="browsable" value="True" checked /></td> </tr> <tr> <td><b>Conf</b><br/><span style="font-size:0.8em">(JSON dict)</span></td> <td><textarea name="conf" rows="2" cols="30"></textarea></td> </tr> </table> <table style="width:500px; border:0; background:#ffffff; margin-top:20px"> <tr><td align="center"> <a href="/storage/?manage=True" class="btn btn-primary" style="margin-left:10px">Back</a> <input type="submit" value="Save" class="btn btn-primary"> </td></tr> </table> </form> </div> </div> </div> {% include "footer.html" %} No newline at end of file
services/webapp/code/rosetta/core_app/templates/components/storage.html 0 → 100644 +53 −0 Original line number Diff line number Diff line {% comment %} Storage resource component, main list and detailed view {% endcomment %} {% if data.storage and storage.uuid == data.storage.uuid %} <div style="float:left; width:500px; margin:10px; margin-bottom:20px"> <table class="dashboard" style="width:100%"> <tr><td><b>Name</b></td><td>{{ storage.name }}</td></tr> <tr><td><b>Description</b></td><td>{{ storage.description }}</td></tr> <tr><td><b>Type</b></td><td>{{ storage.type }}</td></tr> <tr><td><b>Access mode</b></td><td>{{ storage.access_mode }}</td></tr> <tr><td><b>Auth mode</b></td><td>{{ storage.auth_mode }}</td></tr> <tr><td><b>Base path</b></td><td><code>{{ storage.base_path }}</code></td></tr> <tr><td><b>Bind path</b></td><td><code>{{ storage.bind_path }}</code></td></tr> <tr><td><b>Read only</b></td><td>{% if storage.read_only %}Yes{% else %}No{% endif %}</td></tr> <tr><td><b>Browsable</b></td><td>{% if storage.browsable %}Yes{% else %}No{% endif %}</td></tr> <tr><td><b>Group</b></td><td>{% if storage.group %}{{ storage.group }}{% else %}Platform{% endif %}</td></tr> <tr><td><b>Computing</b></td><td>{% if storage.computing %}{{ storage.computing.name }}{% else %}-{% endif %}</td></tr> <tr><td><b>Conf</b></td><td><pre style="font-size:0.9em">{{ storage.conf|default_if_none:'' }}</pre></td></tr> {% if request.user.is_staff %} <tr> <td><b>Actions</b></td> <td> <a href="/edit_storage/?uuid={{ storage.uuid }}">Edit</a> | <a href="/storage/?action=delete&uuid={{ storage.uuid }}" onclick="return confirm('Are you sure you want to delete this storage resource?');">Delete</a> | <a href="/storage/?action=duplicate&uuid={{ storage.uuid }}" onclick="return confirm('Duplicate this storage resource?');">Duplicate</a> </td> </tr> {% endif %} </table> </div> {% else %} <div style="width:300px; float:left; border: #e0e0e0 solid 1px; margin:10px; background:#f8f8f8; margin-bottom:15px;"> <div style="padding:10px; margin-top:5px; text-align:center; border-bottom: #e0e0e0 solid 1px; "> <a href="/storage/?manage=True&uuid={{ storage.uuid }}">{{ storage.name }}</a> </div> <div style="padding:10px;"> {% if storage.description %} <div class="description-box" title="{{ storage.description }}"> {{ storage.description }} </div> {% else %} <br/> {% endif %} <div class="image-version-box"> <b>Type:</b> {{ storage.type }}<br/> <b>Base path:</b> <code>{{ storage.base_path }}</code><br/> <b>Bind path:</b> <code>{{ storage.bind_path }}</code><br/> <b>Read only:</b> {% if storage.read_only %}Yes{% else %}No{% endif %}<br/> <b>Browsable:</b> {% if storage.browsable %}Yes{% else %}No{% endif %}<br/> </div> </div> </div> {% endif %} No newline at end of file
services/webapp/code/rosetta/core_app/templates/edit_storage.html 0 → 100644 +71 −0 Original line number Diff line number Diff line {% load static %} {% include "header.html" %} {% include "navigation.html" %} {% include "logo.html" %} <div class="container"> <div class="dashboard"> <div class="span8 offset2"> <h1><a href="/storage">Storage resources</a> <span style="font-size:18px"> / <a href="/storage/?uuid={{ data.storage.uuid }}">{{ data.storage.name }}</a> /edit</span></h1> <hr> {% if data.error %} <div class="alert alert-danger">{{ data.error }}</div> {% endif %} {% if data.edited %} <div class="alert alert-success">Storage updated successfully.</div> {% endif %} <form action="#" method="POST"> {% csrf_token %} <table class="dashboard" style="width:500px; margin-bottom:25px"> <tr> <td><b>Name</b></td> <td><input type="text" name="name" value="{{ data.storage.name }}" size="30" required /></td> </tr> <tr> <td><b>Type</b></td> <td><input type="text" name="type" value="{{ data.storage.type }}" size="30" required /></td> </tr> <tr> <td><b>Access mode</b></td> <td><input type="text" name="access_mode" value="{{ data.storage.access_mode }}" size="30" required /></td> </tr> <tr> <td><b>Auth mode</b></td> <td><input type="text" name="auth_mode" value="{{ data.storage.auth_mode }}" size="30" required /></td> </tr> <tr> <td><b>Base path</b></td> <td><input type="text" name="base_path" value="{{ data.storage.base_path }}" size="30" required /></td> </tr> <tr> <td><b>Bind path</b></td> <td><input type="text" name="bind_path" value="{{ data.storage.bind_path }}" size="30" /></td> </tr> <tr> <td><b>Read only</b></td> <td><input type="checkbox" name="read_only" value="True" {% if data.storage.read_only %}checked{% endif %} /></td> </tr> <tr> <td><b>Browsable</b></td> <td><input type="checkbox" name="browsable" value="True" {% if data.storage.browsable %}checked{% endif %} /></td> </tr> <tr> <td><b>Conf</b><br/><span style="font-size:0.8em">(JSON dict)</span></td> <td><textarea name="conf" rows="2" cols="30">{{ data.conf_json }}</textarea></td> </tr> </table> <table style="width:500px; border:0; background:#ffffff; margin-top:20px"> <tr><td align="center"> <a href="/storage/?manage=True&uuid={{ data.storage.uuid }}" class="btn btn-primary" style="margin-left:10px">Back</a> <input type="submit" value="Save changes" class="btn btn-primary"> </td></tr> </table> </form> </div> </div> </div> {% include "footer.html" %} No newline at end of file
services/webapp/code/rosetta/core_app/templates/storage.html +40 −3 Original line number Diff line number Diff line Loading @@ -7,15 +7,52 @@ <div class="dashboard" style="height:100%;"> <div class="span8 offset2" style="height:100%;"> {% if not data.storage %} <h1>Storage resources</h1> {% else %} <h1><a href="/storage/?manage=True">Storage resources</a> <span style="font-size:18px"> / <a href="/storage/?manage=True&uuid={{ data.storage.uuid }}">{{ data.storage.name }}</a></span></h1> {% endif %} <hr/> {% if not data.manage %} <div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">Browse</div> <div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="/storage/?manage=True">Manage</a></div> <hr style="margin-top:0;"> <iframe src="/static/RichFilemanager/index.html" style="overflow:hidden;height:100%;width:100%; border: 1px solid #c0c0c0" height="100%" width="100%"></iframe> {% else %} <div style="font-size:1.1em; background:white; display:inline-block; padding:2px 15px 2px 15px"><a href="/storage/">Browse</a></div> <div style="font-size:1.1em; background:whitesmoke; display:inline-block; padding:2px 15px 2px 15px">Manage</div> <hr style="margin-top:0;"> {% if not data.storage %} <div class="form-filter" style="margin-bottom:20px"> <form action="" method="POST"> <input type="text" class="form-control" id="search_text" name="search_text" placeholder="Search..." style="width:200px; margin:0; display:inline" value="{{data.search_text}}" autofocus> <select class="form-control" id="search_owner" name="search_owner" style="width:120px; margin:0; display:inline"> {% if data.search_owner == 'All' %}<option selected>All</option>{% else %}<option>All</option>{% endif %} {% if data.search_owner == 'Platform' %}<option selected>Platform</option>{% else %}<option>Platform</option>{% endif %} {% if data.search_owner == 'User' or data.search_owner == 'Group' %}<option selected>User</option>{% else %}<option>User</option>{% endif %} </select> {% csrf_token %} <button type="submit" class="btn btn-secondary">Go</button> {% if request.user.is_staff %} <font size=4.0em>|</font> <a href="/add_storage/">Add new...</a> {% endif %} </form> </div> {% endif %} <div class="row" style="padding:5px"> {% for storage in data.storages %} {% include "components/storage.html" with storage=storage %} {% empty %} <p>No storage resources found.</p> {% endfor %} </div> {% endif %} </div> </div> </div> Loading
services/webapp/code/rosetta/core_app/views.py +187 −3 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound from django.contrib.auth.models import User from django.shortcuts import redirect from django.db.models import Q from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, Page from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, Page, Storage from .utils import send_email, format_exception, timezonize, os_shell, booleanize, get_rosetta_tasks_tunnel_host from .utils import get_rosetta_tasks_proxy_host, random_username, setup_tunnel_and_proxy, finalize_user_creation from .utils import sanitize_container_env_vars, get_or_create_container_from_repository Loading Loading @@ -1237,14 +1237,151 @@ def edit_computing(request): #========================= @private_view def storage(request): # Set data & render data = {} data['user'] = request.user data['manage'] = request.GET.get('manage', False) storage_uuid = request.GET.get('uuid', None) action = request.GET.get('action', None) search_text = request.POST.get('search_text', '') search_owner = request.POST.get('search_owner', 'All') data['search_text'] = search_text data['search_owner'] = search_owner # Handle delete action if action == 'delete' and storage_uuid: if not request.user.is_staff: data['error'] = 'You do not have permission to delete this storage.' return render(request, 'error.html', {'data': data}) try: storage = Storage.objects.get(uuid=storage_uuid) storage.delete() return redirect('/storage/?manage=True') except Storage.DoesNotExist: data['error'] = 'Storage not found.' return render(request, 'error.html', {'data': data}) except Exception as e: data['error'] = f'Error deleting storage: {e}' return render(request, 'error.html', {'data': data}) # Handle duplicate action if action == 'duplicate' and storage_uuid: if not request.user.is_staff: data['error'] = 'You do not have permission to duplicate this storage.' return render(request, 'error.html', {'data': data}) try: storage = Storage.objects.get(uuid=storage_uuid) # Create a copy from copy import deepcopy new_storage = Storage( name=f"{storage.name} (copy)", type=storage.type, access_mode=storage.access_mode, auth_mode=storage.auth_mode, base_path=storage.base_path, bind_path=storage.bind_path, read_only=storage.read_only, browsable=storage.browsable, group=storage.group, computing=storage.computing, access_through_computing=storage.access_through_computing, conf=deepcopy(storage.conf) if storage.conf else None ) new_storage.save() return redirect(f'/edit_storage/?uuid={new_storage.uuid}') except Storage.DoesNotExist: data['error'] = 'Storage not found.' return render(request, 'error.html', {'data': data}) except Exception as e: data['error'] = f'Error duplicating storage: {e}' return render(request, 'error.html', {'data': data}) if storage_uuid: try: storage = Storage.objects.get(uuid=storage_uuid) data['storages'] = [storage] data['storage'] = storage except Storage.DoesNotExist: data['storages'] = [] data['storage'] = None data['error'] = 'Storage not found.' else: storages = Storage.objects.all() # Filtering by search_text if search_text: from django.db.models import Q storages = storages.filter( Q(name__icontains=search_text) | Q(base_path__icontains=search_text) | Q(bind_path__icontains=search_text) | Q(description__icontains=search_text) ) # Filtering by owner if search_owner != 'All': if search_owner == 'Platform': storages = storages.filter(group=None) elif search_owner == 'User' or search_owner == 'Group': storages = storages.exclude(group=None) data['storages'] = list(storages) data['storage'] = None return render(request, 'storage.html', {'data': data}) @private_view def edit_storage(request): data = {} data['user'] = request.user storage_uuid = request.GET.get('uuid', None) if not storage_uuid: data['error'] = 'No storage specified.' return render(request, 'error.html', {'data': data}) try: storage = Storage.objects.get(uuid=storage_uuid) except Storage.DoesNotExist: data['error'] = 'Storage does not exist.' return render(request, 'error.html', {'data': data}) if not request.user.is_staff: data['error'] = 'You do not have permission to edit this storage.' return render(request, 'error.html', {'data': data}) data['storage'] = storage data['edited'] = False # For JSON field display data['conf_json'] = json.dumps(storage.conf, indent=2) if storage.conf else '' if request.method == 'POST': storage.name = request.POST.get('name', storage.name) storage.type = request.POST.get('type', storage.type) storage.access_mode = request.POST.get('access_mode', storage.access_mode) storage.auth_mode = request.POST.get('auth_mode', storage.auth_mode) storage.base_path = request.POST.get('base_path', storage.base_path) storage.bind_path = request.POST.get('bind_path', storage.bind_path) storage.read_only = bool(request.POST.get('read_only', False)) storage.browsable = bool(request.POST.get('browsable', False)) # Foreign keys (group, computing) not handled for now conf = request.POST.get('conf', None) if conf: try: storage.conf = json.loads(conf) except Exception: data['error'] = 'Invalid conf format (must be JSON dict).' return render(request, 'edit_storage.html', {'data': data}) try: storage.save() data['edited'] = True data['conf_json'] = json.dumps(storage.conf, indent=2) if storage.conf else '' except Exception as e: data['error'] = f'Error saving storage: {e}' return render(request, 'edit_storage.html', {'data': data}) return render(request, 'edit_storage.html', {'data': data}) #========================= Loading Loading @@ -1500,3 +1637,50 @@ def import_repository(request): return render(request, 'import_repository.html', {'data': data}) @private_view def add_storage(request): data = {} data['user'] = request.user data['added'] = False if not request.user.is_staff: data['error'] = 'You do not have permission to add storage.' return render(request, 'error.html', {'data': data}) if request.method == 'POST': name = request.POST.get('name', None) type_ = request.POST.get('type', None) access_mode = request.POST.get('access_mode', None) auth_mode = request.POST.get('auth_mode', None) base_path = request.POST.get('base_path', None) bind_path = request.POST.get('bind_path', None) read_only = bool(request.POST.get('read_only', False)) browsable = bool(request.POST.get('browsable', False)) conf = request.POST.get('conf', None) conf_obj = None if conf: try: conf_obj = json.loads(conf) except Exception: data['error'] = 'Invalid conf format (must be JSON dict).' return render(request, 'add_storage.html', {'data': data}) try: Storage.objects.create( name=name, type=type_, access_mode=access_mode, auth_mode=auth_mode, base_path=base_path, bind_path=bind_path, read_only=read_only, browsable=browsable, conf=conf_obj ) data['added'] = True return redirect('/storage/?manage=True') except Exception as e: data['error'] = f'Error creating storage: {e}' return render(request, 'add_storage.html', {'data': data}) return render(request, 'add_storage.html', {'data': data})