Commit 9c7e1eaa authored by Stefano Alberto Russo's avatar Stefano Alberto Russo
Browse files

Added storage edit, add, delete and duplicate capabilities.

parent 683bd855
Loading
Loading
Loading
Loading
+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>
          &nbsp;
          <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
+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>
        &nbsp;|&nbsp;
        <a href="/storage/?action=delete&uuid={{ storage.uuid }}" onclick="return confirm('Are you sure you want to delete this storage resource?');">Delete</a>
        &nbsp;|&nbsp;
        <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
+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>
          &nbsp;
          <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
+40 −3
Original line number Diff line number Diff line
@@ -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 %}
            &nbsp; &nbsp; <font size=4.0em>|</font> &nbsp; <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>
+187 −3
Original line number Diff line number Diff line
@@ -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
@@ -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})


#=========================
@@ -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