Commit 3dc284a9 by Czémán Arnold

dashboard, storage: add DataStoreList view which shows all available data…

dashboard, storage: add DataStoreList view which shows all available data stores; add used_percent property for DataStore
parent 1ff259f9
...@@ -1617,6 +1617,16 @@ class DataStoreForm(ModelForm): ...@@ -1617,6 +1617,16 @@ class DataStoreForm(ModelForm):
fields = ("name", "path", "hostname", ) fields = ("name", "path", "hostname", )
class StorageListSearchForm(forms.Form):
s = forms.CharField(widget=forms.TextInput(attrs={
'class': "form-control input-tags",
'placeholder': _("Search...")
}))
def __init__(self, *args, **kwargs):
super(StorageListSearchForm, self).__init__(*args, **kwargs)
class DiskForm(ModelForm): class DiskForm(ModelForm):
created = forms.DateTimeField() created = forms.DateTimeField()
modified = forms.DateTimeField() modified = forms.DateTimeField()
......
...@@ -27,7 +27,7 @@ from django_tables2.columns import ( ...@@ -27,7 +27,7 @@ from django_tables2.columns import (
) )
from django_sshkey.models import UserKey from django_sshkey.models import UserKey
from storage.models import Disk from storage.models import Disk, DataStore
from vm.models import Node, InstanceTemplate, Lease from vm.models import Node, InstanceTemplate, Lease
from dashboard.models import ConnectCommand, Message from dashboard.models import ConnectCommand, Message
...@@ -371,3 +371,42 @@ class MessageListTable(Table): ...@@ -371,3 +371,42 @@ class MessageListTable(Table):
order_by = ("-pk", ) order_by = ("-pk", )
fields = ('pk', 'message', 'enabled', 'effect') fields = ('pk', 'message', 'enabled', 'effect')
empty_text = _("No messages.") empty_text = _("No messages.")
class StorageListTable(Table):
name = TemplateColumn(
verbose_name=_("Name"),
template_name="dashboard/storage-list/column-storage-name.html",
attrs={'th': {'data-sort': "string"}}
)
type = Column(
verbose_name=_("Type"),
attrs={'th': {'data-sort': "string"}}
)
path = Column(
verbose_name=_("Path or Pool name"),
attrs={'th': {'data-sort': "string"}}
)
hostname = Column(
verbose_name=_("Hostname"),
attrs={'th': {'data-sort': "string"}}
)
used_percent = TemplateColumn(
verbose_name=_("Usage"),
template_name="dashboard/storage-list/" +
"column-storage-used_percent.html",
orderable=False
)
class Meta:
model = DataStore
attrs = {'class': ('table table-bordered table-striped table-hover'
'storage-list-table')}
fields = ('name', 'type', 'path', 'hostname', 'used_percent')
prefix = "storage-"
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
</a> </a>
</li> </li>
<li> <li>
<a href="{% url "dashboard.views.storage" %}"> <a href="{% url "dashboard.views.storage-list" %}">
<i class="fa fa-database"></i> <i class="fa fa-database"></i>
<span class="hidden-sm">{% trans "Storage" %}</span> <span class="hidden-sm">{% trans "Storage" %}</span>
</a> </a>
......
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block title-page %}{% trans "Data stores" %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<a href="{% url "dashboard.views.storage-choose" %}" class="pull-right btn btn-success btn-xs storage-choose">
<i class="fa fa-plus"></i> {% trans "new data store" %}
</a>
<h3 class="no-margin"><i class="fa fa-database"></i> {% trans "Data stores" %}</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-offset-8 col-md-4" id="storage-list-search">
<form action="" method="GET">
<div class="input-group">
{{ search_form.s }}
<div class="input-group-btn">
{{ search_form.stype }}
<button type="submit" class="btn btn-primary input-tags">
<i class="fa fa-search"></i>
</button>
</div>
</div><!-- .input-group -->
</form>
</div><!-- .col-md-4 #storage-list-search -->
</div>
</div>
<div class="panel-body">
<div class="table-responsive">
{% render_table table %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
<a href="{% url "dashboard.views.storage-detail" pk=record.pk %}" title="{{ record.description }}">
{{ record.name }}
</a>
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-stripped"
role="progressbar" style="min-width: 30px; width: {{ record.used_percent }}%">
{{ record.used_percent }}%
</div>
</div>
...@@ -53,7 +53,7 @@ from .views import ( ...@@ -53,7 +53,7 @@ from .views import (
OpenSearchDescriptionView, OpenSearchDescriptionView,
NodeActivityView, NodeActivityView,
UserList, UserList,
StorageDetail, DiskDetail, StorageDetail, StorageList, DiskDetail,
MessageList, MessageDetail, MessageCreate, MessageDelete, MessageList, MessageDetail, MessageCreate, MessageDelete,
) )
from .views.vm import vm_ops, vm_mass_ops from .views.vm import vm_ops, vm_mass_ops
...@@ -233,8 +233,14 @@ urlpatterns = patterns( ...@@ -233,8 +233,14 @@ urlpatterns = patterns(
url(r'^vm/opensearch.xml$', OpenSearchDescriptionView.as_view(), url(r'^vm/opensearch.xml$', OpenSearchDescriptionView.as_view(),
name="dashboard.views.vm-opensearch"), name="dashboard.views.vm-opensearch"),
url(r'^storage/$', StorageDetail.as_view(), url(r'^storage/(?P<pk>\d+)/$', StorageDetail.as_view(),
name="dashboard.views.storage"), name='dashboard.views.storage-detail'),
url(r'^storage/list/$', StorageList.as_view(),
name="dashboard.views.storage-list"),
url(r'^storage/choose/$', StorageDetail.as_view(),
name="dashboard.views.storage-choose"),
url(r'^disk/(?P<pk>\d+)/$', DiskDetail.as_view(), url(r'^disk/(?P<pk>\d+)/$', DiskDetail.as_view(),
name="dashboard.views.disk-detail"), name="dashboard.views.disk-detail"),
......
...@@ -16,19 +16,67 @@ ...@@ -16,19 +16,67 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
import logging
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import UpdateView from django.views.generic import UpdateView
from django_tables2 import SingleTableView
from braces.views import SuperuserRequiredMixin from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from sizefield.utils import filesizeformat from sizefield.utils import filesizeformat
from common.models import WorkerNotFound from common.models import WorkerNotFound
from storage.models import DataStore, Disk from storage.models import DataStore, Disk
from ..tables import DiskListTable from ..tables import DiskListTable, StorageListTable
from ..forms import DataStoreForm, DiskForm from ..forms import DataStoreForm, DiskForm, StorageListSearchForm
from .util import FilterMixin
logger = logging.getLogger(__name__)
class StorageList(LoginRequiredMixin, FilterMixin, SingleTableView):
template_name = "dashboard/storage-list.html"
model = DataStore
table_class = StorageListTable
table_pagination = False
allowed_filters = {
'name': "name__icontains",
'type': "type__icontains",
'path': "path__icontains",
'poolname': "path__icontains",
'hostname': "hostname__iexact",
'address': "hosts__address__in"
}
def get_context_data(self, *args, **kwargs):
context = super(StorageList, self).get_context_data(*args, **kwargs)
context['search_form'] = self.search_form
return context
def get(self, *args, **kwargs):
self.search_form = StorageListSearchForm(self.request.GET)
self.search_form.full_clean()
return super(StorageList, self).get(*args, **kwargs)
def get_queryset(self):
logger.debug('StorageList.get_queryset() called. User: %s',
unicode(self.request.user))
qs = DataStore.get_all()
self.create_fake_get()
try:
filters, excludes = self.get_queryset_filters()
qs = qs.filter(**filters).exclude(**excludes).distinct()
except ValueError:
messages.error(self.request, _("Error during filtering."))
return qs
class StorageDetail(SuperuserRequiredMixin, UpdateView): class StorageDetail(SuperuserRequiredMixin, UpdateView):
......
...@@ -110,11 +110,22 @@ class DataStore(Model): ...@@ -110,11 +110,22 @@ class DataStore(Model):
return [(host.address, host.port) for host in self.hosts.all()] return [(host.address, host.port) for host in self.hosts.all()]
@property
def used_percent(self):
stats = self.get_statistics()
free_percent = float(stats['free_percent'])
return int(100 - free_percent)
@method_cache(30) @method_cache(30)
def get_statistics(self, timeout=15): def get_statistics(self, timeout=15):
q = self.get_remote_queue_name("storage", priority="fast") q = self.get_remote_queue_name("storage", priority="fast")
return storage_tasks.get_storage_stat.apply_async( try:
args=[self.path], queue=q).get(timeout=timeout) return storage_tasks.get_storage_stat.apply_async(
args=[self.type, self.path], queue=q).get(timeout=timeout)
except TimeoutError:
return {'free_space': -1,
'free_percent': -1}
@method_cache(30) @method_cache(30)
def get_orphan_disks(self, timeout=15): def get_orphan_disks(self, timeout=15):
......
...@@ -79,5 +79,5 @@ def recover_from_trash(datastore, disk_path): ...@@ -79,5 +79,5 @@ def recover_from_trash(datastore, disk_path):
@celery.task(name='storagedriver.get_storage_stat') @celery.task(name='storagedriver.get_storage_stat')
def get_storage_stat(path): def get_storage_stat(data_store_type, path):
pass pass
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment