Remove djblets package from murano-dashboard.
Use horizon.tables package instead for drawing the tables (old term is datagrid). Now columns attribute is configurable in Service YAML-markup (it should be consistent with initial data for table). Last thing to be done is to somehow employ Actions from horizon.tables (if this can be done at all) - this eliminates need in custom js-code which adds and deletes table rows on client-side. Change-Id: I6e6cd41b717d48eafd7e4a5e1fc907d2f1ebf12d Closes-bug: #1234194
This commit is contained in:
parent
b3213e54e9
commit
7955c21359
@ -1,123 +0,0 @@
|
||||
# Copyright (c) 2013 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from djblets.datagrid.grids import Column, DataGrid
|
||||
from models import Node, FakeQuerySet
|
||||
import floppyforms as forms
|
||||
import json
|
||||
import re
|
||||
from django.contrib.auth.models import SiteProfileNotAvailable
|
||||
|
||||
|
||||
class PK(object):
|
||||
def __init__(self, value=0):
|
||||
self.value = value
|
||||
|
||||
def next(self):
|
||||
self.value += 1
|
||||
return self.value
|
||||
|
||||
def current(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class CheckColumn(Column):
|
||||
def render_data(self, item):
|
||||
checked = getattr(item, self.field_name)
|
||||
checked = 'checked="checked"' if checked else ''
|
||||
return '<input type="checkbox" %s/>' % (checked,)
|
||||
|
||||
|
||||
class RadioColumn(Column):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.name = kwargs.get('name', 'default')
|
||||
super(RadioColumn, self).__init__(*args, **kwargs)
|
||||
|
||||
def render_data(self, item):
|
||||
checked = getattr(item, self.field_name)
|
||||
checked = 'checked="checked"' if checked else ''
|
||||
name = 'name="%s"' % (self.name,)
|
||||
return '<input type="radio" %s %s/>' % (name, checked)
|
||||
|
||||
|
||||
class NodeDataGrid(DataGrid):
|
||||
name = Column('Node', sortable=False)
|
||||
isSync = CheckColumn('Sync')
|
||||
isMaster = RadioColumn('Primary')
|
||||
|
||||
def __init__(self, request, data):
|
||||
self.pk = PK()
|
||||
items = []
|
||||
if type(data) in (str, unicode):
|
||||
# BEWARE, UGLY HACK!!! Data should be list there already!
|
||||
data = json.loads(data)
|
||||
for kwargs in data:
|
||||
items.append(Node(**dict(kwargs.items() +
|
||||
[('id', self.pk.next())])))
|
||||
super(NodeDataGrid, self).__init__(request, FakeQuerySet(
|
||||
Node, items=items), optimize_sorts=False)
|
||||
self.default_sort = []
|
||||
self.default_columns = ['name', 'isSync', 'isMaster']
|
||||
|
||||
# hack
|
||||
def load_state(self, render_context=None):
|
||||
if self.request.user.is_authenticated():
|
||||
def get_profile():
|
||||
raise SiteProfileNotAvailable
|
||||
setattr(self.request.user, 'get_profile', get_profile)
|
||||
super(NodeDataGrid, self).load_state(render_context)
|
||||
|
||||
|
||||
class DataGridWidget(forms.widgets.Input):
|
||||
template_name = 'data_grid_field.html'
|
||||
|
||||
def get_context(self, name, value, attrs=None):
|
||||
ctx = super(DataGridWidget, self).get_context_data()
|
||||
ctx['data_grid'] = NodeDataGrid(self.request, data=value)
|
||||
return ctx
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
base, match = None, re.match('(.*)_[0-9]+', name)
|
||||
if match:
|
||||
base = match.group(1)
|
||||
if base:
|
||||
pattern = re.compile(base + '_[0-9]+')
|
||||
for key in data:
|
||||
if re.match(pattern, key):
|
||||
return data[key]
|
||||
return super(DataGridWidget, self).value_from_datadict(
|
||||
data, files, name)
|
||||
|
||||
class Media:
|
||||
css = {'all': ('css/datagrid.css',
|
||||
'muranodashboard/css/datagridfield.css')}
|
||||
js = ('js/jquery.gravy.js',
|
||||
'js/datagrid.js',
|
||||
'muranodashboard/js/datagridfield.js')
|
||||
|
||||
|
||||
class DataGridCompound(forms.MultiWidget):
|
||||
def __init__(self, attrs=None):
|
||||
_widgets = (DataGridWidget(),
|
||||
forms.HiddenInput(attrs={'class': 'gridfield-hidden'}))
|
||||
super(DataGridCompound, self).__init__(_widgets, attrs)
|
||||
|
||||
def update_request(self, request):
|
||||
self.widgets[0].request = request
|
||||
|
||||
def decompress(self, value):
|
||||
if value != '':
|
||||
return [json.loads(value), value]
|
||||
else:
|
||||
return [None, None]
|
@ -1,77 +1,3 @@
|
||||
"""
|
||||
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
|
||||
"""
|
||||
from django.db import models
|
||||
from django.db.models.query import EmptyQuerySet
|
||||
import copy
|
||||
|
||||
|
||||
class FakeQuerySet(EmptyQuerySet):
|
||||
"""Turn a list into a Django QuerySet... kind of."""
|
||||
def __init__(self, model=None, query=None, using=None, items=[]):
|
||||
super(FakeQuerySet, self).__init__(model, query, using)
|
||||
self._result_cache = items
|
||||
|
||||
def __getitem__(self, k):
|
||||
if isinstance(k, slice):
|
||||
obj = self._clone()
|
||||
obj._result_cache = super(FakeQuerySet, self).__getitem__(k)
|
||||
return obj
|
||||
else:
|
||||
return super(FakeQuerySet, self).__getitem__(k)
|
||||
|
||||
def count(self):
|
||||
return len(self)
|
||||
|
||||
def _clone(self, klass=None, setup=False, **kwargs):
|
||||
c = super(FakeQuerySet, self)._clone(klass, setup=setup, **kwargs)
|
||||
c._result_cache = copy.copy(self._result_cache)
|
||||
return c
|
||||
|
||||
def iterator(self):
|
||||
# This slightly odd construction is because we need an empty generator
|
||||
# (it raises StopIteration immediately).
|
||||
yield iter(self._result_cache).next()
|
||||
|
||||
def order_by(self, *fields):
|
||||
obj = self._clone()
|
||||
cache = obj._result_cache
|
||||
for field in fields:
|
||||
reverse = False
|
||||
if field[0] == '-':
|
||||
reverse = True
|
||||
field = field[1:]
|
||||
cache = sorted(cache, None, lambda item: getattr(item, field),
|
||||
reverse=reverse)
|
||||
obj._result_cache = cache
|
||||
return obj
|
||||
|
||||
def distinct(self, *fields):
|
||||
obj = self._clone()
|
||||
return obj
|
||||
|
||||
def values_list(self, *fields, **kwargs):
|
||||
obj = self._clone()
|
||||
cache = []
|
||||
for item in self._result_cache:
|
||||
value = []
|
||||
for field in fields:
|
||||
value.append(getattr(item, field))
|
||||
cache.append(tuple(value))
|
||||
if kwargs.get('flat', False) and len(fields) == 1:
|
||||
cache = [item[0] for item in cache]
|
||||
obj._result_cache = cache
|
||||
return obj
|
||||
|
||||
def add(self, item):
|
||||
self._result_cache.append(item)
|
||||
|
||||
def remove(self):
|
||||
self._result_cache.pop()
|
||||
|
||||
|
||||
class Node(models.Model):
|
||||
id = models.IntegerField(primary_key=True)
|
||||
name = models.CharField(max_length=20)
|
||||
isMaster = models.BooleanField(default=False)
|
||||
isSync = models.BooleanField(default=False)
|
||||
|
@ -23,11 +23,14 @@ from muranodashboard.panel import api
|
||||
from horizon import exceptions, messages
|
||||
from openstack_dashboard.api import glance
|
||||
from openstack_dashboard.api.nova import novaclient
|
||||
from muranodashboard.datagrids import DataGridCompound
|
||||
from django.template.defaultfilters import pluralize
|
||||
import copy
|
||||
import types
|
||||
import logging
|
||||
import itertools
|
||||
import horizon.tables as tables
|
||||
import floppyforms
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -168,24 +171,127 @@ class InstanceCountField(IntegerField):
|
||||
return interpolate(spec)
|
||||
|
||||
|
||||
class DataGridField(forms.MultiValueField, CustomPropertiesField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['widget'] = DataGridCompound
|
||||
super(DataGridField, self).__init__(
|
||||
(forms.CharField(required=False), forms.CharField()),
|
||||
*args, **kwargs)
|
||||
class Column(tables.Column):
|
||||
template_name = 'common/form-fields/data-grid/input.html'
|
||||
|
||||
def compress(self, data_list):
|
||||
return data_list[1]
|
||||
def __init__(self, transform, **kwargs):
|
||||
table_name = kwargs.pop('table_name', False)
|
||||
if hasattr(self, 'template_name'):
|
||||
def _transform(datum):
|
||||
context = {'data': getattr(datum, self.name, None),
|
||||
'row_index': str(datum.id),
|
||||
'table_name': table_name,
|
||||
'column_name': self.name}
|
||||
return render_to_string(self.template_name, context)
|
||||
_transform.__name__ = transform
|
||||
transform = _transform
|
||||
super(Column, self).__init__(transform, **kwargs)
|
||||
|
||||
|
||||
class CheckColumn(Column):
|
||||
template_name = 'common/form-fields/data-grid/checkbox.html'
|
||||
|
||||
|
||||
class RadioColumn(Column):
|
||||
template_name = 'common/form-fields/data-grid/radio.html'
|
||||
|
||||
|
||||
def DataTableFactory(name, columns):
|
||||
class Object(object):
|
||||
row_name_re = re.compile(r'.*\{0}.*')
|
||||
|
||||
def __init__(self, id, **kwargs):
|
||||
self.id = id
|
||||
for key, value in kwargs.iteritems():
|
||||
if isinstance(value, basestring) and \
|
||||
re.match(self.row_name_re, value):
|
||||
setattr(self, key, value.format(id))
|
||||
else:
|
||||
setattr(self, key, value)
|
||||
|
||||
class DataTableBase(tables.DataTable):
|
||||
def __init__(self, request, data, **kwargs):
|
||||
super(DataTableBase, self).__init__(
|
||||
request,
|
||||
[Object(i, **item) for (i, item) in enumerate(data, 1)],
|
||||
**kwargs)
|
||||
|
||||
class Meta:
|
||||
template = 'common/form-fields/data-grid/data_table.html'
|
||||
name = ''
|
||||
footer = False
|
||||
|
||||
attrs = dict((col_id, cls(col_id, verbose_name=col_name, table_name=name))
|
||||
for (col_id, cls, col_name) in columns)
|
||||
attrs['Meta'] = Meta
|
||||
return tables.base.DataTableMetaclass('DataTable', (DataTableBase,), attrs)
|
||||
|
||||
|
||||
class TableWidget(floppyforms.widgets.Input):
|
||||
template_name = 'common/form-fields/data-grid/table_field.html'
|
||||
delimiter_re = re.compile('([\w-]*)@@([0-9]*)@@([\w-]*)')
|
||||
types = {'label': Column,
|
||||
'radio': RadioColumn,
|
||||
'checkbox': CheckColumn}
|
||||
|
||||
def __init__(self, columns_spec, *args, **kwargs):
|
||||
columns = []
|
||||
for spec in columns_spec:
|
||||
name = spec['column_name']
|
||||
columns.append((name,
|
||||
self.types[spec['column_type']],
|
||||
spec.get('title', None) or name.title()))
|
||||
self.columns = columns
|
||||
super(TableWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_context(self, name, value, attrs=None):
|
||||
ctx = super(TableWidget, self).get_context_data()
|
||||
if value:
|
||||
ctx['data_table'] = DataTableFactory(name, self.columns)(
|
||||
self.request, value)
|
||||
return ctx
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
def extract_value(row_idx, col_id, col_cls):
|
||||
if col_cls == CheckColumn:
|
||||
val = data.get("{0}@@{1}@@{2}".format(name, row_idx, col_id),
|
||||
False)
|
||||
return val and val == 'on'
|
||||
elif col_cls == RadioColumn:
|
||||
row_id = data.get("{0}@@@@{1}".format(name, col_id), False)
|
||||
if row_id:
|
||||
return int(row_id) == row_idx
|
||||
return False
|
||||
else:
|
||||
return data.get("{0}@@{1}@@{2}".format(
|
||||
name, row_idx, col_id), None)
|
||||
|
||||
items = []
|
||||
main_column, rest_columns = self.columns[0], self.columns[1:]
|
||||
for row_index in itertools.count(1):
|
||||
if not extract_value(row_index, *main_column[:2]):
|
||||
break
|
||||
item = {}
|
||||
for column_id, column_instance, column_name in self.columns:
|
||||
value = extract_value(row_index, column_id, column_instance)
|
||||
item[column_id] = value
|
||||
items.append(item)
|
||||
print items
|
||||
return items
|
||||
|
||||
class Media:
|
||||
css = {'all': ('muranodashboard/css/tablefield.css',)}
|
||||
js = ('muranodashboard/js/tablefield.js',)
|
||||
|
||||
|
||||
class TableField(CustomPropertiesField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['widget'] = TableWidget(kwargs.pop('columns'))
|
||||
super(TableField, self).__init__(*args, **kwargs)
|
||||
|
||||
@with_request
|
||||
def update(self, request, **kwargs):
|
||||
self.widget.update_request(request)
|
||||
# hack to use json string instead of python dict get by YAQL
|
||||
data = kwargs['form'].service.cleaned_data
|
||||
if 'clusterConfiguration' in data:
|
||||
conf = data['clusterConfiguration']
|
||||
conf['dcInstances'] = json.dumps(conf['dcInstances'])
|
||||
self.widget.request = request
|
||||
|
||||
|
||||
class ChoiceField(forms.ChoiceField, CustomPropertiesField):
|
||||
|
@ -27,8 +27,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class UpdatableFieldsForm(forms.Form):
|
||||
def update_fields(self):
|
||||
|
||||
#Create 'Confirm Password' fields by duplicating" Password fields
|
||||
# Create 'Confirm Password' fields by duplicating password fields
|
||||
while True:
|
||||
index, inserted = 0, False
|
||||
for name, field in self.fields.iteritems():
|
||||
@ -60,7 +59,7 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
|
||||
'password': fields.PasswordField,
|
||||
'integer': fields.IntegerField,
|
||||
'databaselist': fields.DatabaseListField,
|
||||
'datagrid': fields.DataGridField,
|
||||
'table': fields.TableField,
|
||||
'flavor': fields.FlavorChoiceField,
|
||||
'image': fields.ImageChoiceField,
|
||||
'azone': fields.AZoneChoiceField,
|
||||
@ -74,6 +73,7 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
|
||||
super(ServiceConfigurationForm, self).__init__(*args, **kwargs)
|
||||
self.attribute_mappings = {}
|
||||
self.insert_fields(self.fields_template)
|
||||
self.initial = kwargs.get('initial', self.initial)
|
||||
self.update_fields()
|
||||
|
||||
@staticmethod
|
||||
@ -203,6 +203,7 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
|
||||
return 'validators', [prepare_regexp(spec)]
|
||||
else:
|
||||
return key, spec
|
||||
|
||||
for spec in field_specs:
|
||||
append_field(spec)
|
||||
|
||||
|
@ -60,11 +60,6 @@ class Wizard(ModalFormMixin, SessionWizardView):
|
||||
for form in form_list[1:]:
|
||||
form.extract_attributes(attributes)
|
||||
|
||||
# hack to destringify nodes into units
|
||||
if 'nodes' in attributes:
|
||||
attributes['units'] = json.loads(attributes['nodes'])
|
||||
del attributes['nodes']
|
||||
|
||||
try:
|
||||
api.service_create(self.request, environment_id, attributes)
|
||||
except HTTPForbidden:
|
||||
|
@ -8,13 +8,13 @@ description: >-
|
||||
unitTemplates:
|
||||
- isMaster: true
|
||||
isSync: true
|
||||
name: 'node#'
|
||||
name: 'node-#'
|
||||
- isMaster: false
|
||||
isSync: true
|
||||
name: 'node#'
|
||||
name: 'node-#'
|
||||
- isMaster: false
|
||||
isSync: false
|
||||
name: 'node#'
|
||||
name: 'node-#'
|
||||
|
||||
forms:
|
||||
- serviceConfiguration:
|
||||
@ -169,8 +169,19 @@ forms:
|
||||
- unitsConfiguration:
|
||||
fields:
|
||||
- name: nodes
|
||||
type: datagrid
|
||||
type: table
|
||||
label: Nodes
|
||||
attributeNames: units
|
||||
columns:
|
||||
- columnName: name
|
||||
columnType: label
|
||||
title: Node
|
||||
- columnName: isSync
|
||||
columnType: checkbox
|
||||
title: Sync
|
||||
- columnName: isMaster
|
||||
columnType: radio
|
||||
title: Master
|
||||
initial: {YAQL: $.clusterConfiguration.dcInstances}
|
||||
description: >-
|
||||
Configure cluster instances. Cluster node quantity can be set
|
||||
|
@ -1,3 +0,0 @@
|
||||
.left {
|
||||
overflow: hidden;
|
||||
}
|
10
muranodashboard/static/muranodashboard/css/tablefield.css
Normal file
10
muranodashboard/static/muranodashboard/css/tablefield.css
Normal file
@ -0,0 +1,10 @@
|
||||
input[type=radio].data-grid, input[type=checkbox].data-grid {
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.data-grid table {
|
||||
margin-bottom: 5px ! important;;
|
||||
}
|
@ -24,11 +24,11 @@ $(function() {
|
||||
var labels = [],
|
||||
max = 0,
|
||||
base = '';
|
||||
$('table.datagrid tbody tr td:first-child').each(function(i, td) {
|
||||
$('.data-grid table tbody tr td:first-child').each(function(i, td) {
|
||||
labels.push(trimLabel($(td).text()));
|
||||
});
|
||||
labels.forEach(function(label) {
|
||||
var match = /([a-zA-z]+)([0-9]+)/.exec(label),
|
||||
var match = /([a-zA-z]+)-([0-9]+)/.exec(label),
|
||||
n = +match[2];
|
||||
base = match[1];
|
||||
if ( n > max )
|
||||
@ -39,12 +39,12 @@ $(function() {
|
||||
|
||||
function getNextLabel() {
|
||||
var baseMax = getMaxLabel();
|
||||
return baseMax[0]+(+baseMax[1]+1);
|
||||
return baseMax[0]+'-'+(+baseMax[1]+1);
|
||||
}
|
||||
|
||||
function getNumOfSync() {
|
||||
var checked = 0;
|
||||
$('table.datagrid tbody td input:checkbox').each(function(index, cb) {
|
||||
$('.data-grid table tbody td input:checkbox').each(function(index, cb) {
|
||||
if ( $(cb).attr('checked') )
|
||||
checked++;
|
||||
});
|
||||
@ -65,7 +65,7 @@ $(function() {
|
||||
}
|
||||
}
|
||||
|
||||
var primary = $('table.datagrid tbody input:radio[checked="checked"]').parents().eq(1);
|
||||
var primary = $('.data-grid table tbody input:radio[checked="checked"]').parents().eq(1);
|
||||
|
||||
function validate_primary(event) {
|
||||
var radio = $(event.target),
|
||||
@ -78,19 +78,25 @@ $(function() {
|
||||
primary = radio.parents().eq(1);
|
||||
}
|
||||
|
||||
$('table.datagrid tbody td input:checkbox').click(validate_sync);
|
||||
$('table.datagrid tbody td input:radio').click(validate_primary);
|
||||
$('.data-grid table tbody td input:checkbox').click(validate_sync);
|
||||
$('.data-grid table tbody td input:radio').click(validate_primary);
|
||||
|
||||
$('button#node-add').click(function() {
|
||||
//debugger;
|
||||
if ( $('table.datagrid tbody tr').length >= MAX_NODES ) {
|
||||
if ( $('.data-grid table tbody tr').length >= MAX_NODES ) {
|
||||
alert('Maximum number of nodes ('+MAX_NODES+') already reached.');
|
||||
return;
|
||||
}
|
||||
var lastRow = $('table.datagrid tbody tr:last-child'),
|
||||
clone = lastRow.clone();
|
||||
clone.toggleClass('even').toggleClass('odd');
|
||||
clone.find('td:first-child').text(getNextLabel());
|
||||
var lastRow = $('.data-grid table tbody tr:last-child'),
|
||||
clone = lastRow.clone(),
|
||||
nameCell = clone.find('td:first-child'),
|
||||
hidden = nameCell.find('input:hidden'),
|
||||
match = /([^@]+)@@([0-9]+)@@(.+)/.exec(hidden.attr('name')),
|
||||
index = +match[2]+1,
|
||||
nextLabel = getNextLabel();
|
||||
nameCell.text(nextLabel);
|
||||
hidden.val(nextLabel);
|
||||
hidden.attr('name', match[1]+'@@'+index+'@@'+match[3]);
|
||||
nameCell.append(hidden);
|
||||
// toggle of sync and primary buttons of clone
|
||||
clone.find('input:checkbox').attr('checked', false);
|
||||
clone.find('input:checkbox').click(validate_sync);
|
||||
@ -100,37 +106,18 @@ $(function() {
|
||||
});
|
||||
|
||||
$('button#node-remove').click(function() {
|
||||
if ( $('table.datagrid tbody tr').length <= MIN_NODES ) {
|
||||
if ( $('.data-grid table tbody tr').length <= MIN_NODES ) {
|
||||
alert('There cannot be less than ' + MIN_NODES + ' nodes');
|
||||
return;
|
||||
}
|
||||
var labelNum = getMaxLabel(),
|
||||
label = labelNum.join(''),
|
||||
rowRef = 'table.datagrid tbody tr:contains('+label+')';
|
||||
if ( rowRef + ' :radio[checked="checked"]' ) {
|
||||
label = labelNum[0] + (labelNum[1] - 1);
|
||||
$('table.datagrid tbody tr:contains('+label+') :radio').attr(
|
||||
label = labelNum.join('-'),
|
||||
rowRef = '.data-grid table tbody tr:contains('+label+')';
|
||||
if ( $(rowRef + ' :radio[checked="checked"]') ) {
|
||||
label = labelNum[0] + '-' + (labelNum[1] - 1);
|
||||
$('.data-grid table tbody tr:contains('+label+') :radio').attr(
|
||||
'checked', 'checked');
|
||||
}
|
||||
$(rowRef).remove();
|
||||
});
|
||||
|
||||
$('.modal-footer input.btn-primary').click(function() {
|
||||
var data = [];
|
||||
$('table.datagrid tbody tr').each(function(i, tr) {
|
||||
function getInputVal(td) {
|
||||
return td.find('input').attr('checked') == 'checked';
|
||||
}
|
||||
data.push({
|
||||
name: trimLabel($(tr).children().eq(0).text()),
|
||||
isSync: getInputVal($(tr).children().eq(1)),
|
||||
isMaster: getInputVal($(tr).children().eq(2))
|
||||
})
|
||||
});
|
||||
$('input.gridfield-hidden').val(JSON.stringify(data));
|
||||
})
|
||||
|
||||
// temporarily disable all controls which are bugged
|
||||
$('table.datagrid th.edit-columns').remove();
|
||||
$('div.datagrid-titlebox').remove();
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
<input type="checkbox" class="data-grid"
|
||||
{% if data %}checked="checked"{% endif %}
|
||||
name="{{ table_name }}@@{{ row_index }}@@{{ column_name }}"/>
|
@ -0,0 +1,2 @@
|
||||
{% extends 'horizon/common/_data_table.html' %}
|
||||
{% block table_caption %}{% endblock table_caption %}
|
@ -0,0 +1,4 @@
|
||||
{{ data }}
|
||||
<input type="hidden" class="data-grid"
|
||||
name="{{ table_name }}@@{{ row_index }}@@{{ column_name }}"
|
||||
value="{{ data }}"/>
|
@ -0,0 +1,4 @@
|
||||
<input type="radio" class="data-grid"
|
||||
{% if data %}checked="checked"{% endif %}
|
||||
name="{{ table_name }}@@@@{{ column_name }}"
|
||||
value="{{ row_index }}"/>
|
@ -1,3 +1,3 @@
|
||||
{{ data_grid.render_listview }}
|
||||
<div class="data-grid">{{ data_table.render }}</div>
|
||||
<button type="button" id="node-add" class="btn btn-small">Add node</button>
|
||||
<button type="button" id="node-remove" class="btn btn-small">Remove node</button>
|
@ -23,7 +23,6 @@ PIPCMD=""
|
||||
SERVICE_SRV_NAME="murano-dashboard"
|
||||
GIT_CLONE_DIR=`echo $SERVICE_CONTENT_DIRECTORY | sed -e "s/$SERVICE_SRV_NAME//"`
|
||||
HORIZON_CONFIGS="/opt/stack/horizon/openstack_dashboard/settings.py,/usr/share/openstack-dashboard/openstack_dashboard/settings.py"
|
||||
NON_PIP_PACKAGES_BASE_URL=https://github.com
|
||||
# Functions
|
||||
# Loger function
|
||||
log()
|
||||
@ -117,7 +116,7 @@ HORIZON_CONFIG['exceptions']['recoverable'] = EXTENDED_RECOVERABLE_EXCEPTIONS
|
||||
HORIZON_CONFIG['exceptions']['not_found'] = EXTENDED_NOT_FOUND_EXCEPTIONS
|
||||
HORIZON_CONFIG['exceptions']['unauthorized'] = EXTENDED_UNAUTHORIZED_EXCEPTIONS
|
||||
HORIZON_CONFIG['customization_module'] = 'muranodashboard.panel.overrides'
|
||||
INSTALLED_APPS += ('muranodashboard','djblets','djblets.datagrid','djblets.util','floppyforms',)
|
||||
INSTALLED_APPS += ('muranodashboard','floppyforms',)
|
||||
MIDDLEWARE_CLASSES += ('muranodashboard.settings.ExceptionMiddleware',)
|
||||
LOGGING['formatters'] = {'verbose': {'format': '[%(asctime)s] [%(levelname)s] [pid=%(process)d] %(message)s'}}
|
||||
LOGGING['handlers']['file'] = {'level': 'DEBUG', 'formatter': 'verbose', 'class': 'logging.FileHandler', 'filename': '/var/log/murano-dashboard.log'}
|
||||
@ -256,29 +255,6 @@ CLONE_FROM_GIT=$1
|
||||
log "$PIPCMD install \"$TRBL_FILE\" FAILS, exiting!!!"
|
||||
exit 1
|
||||
fi
|
||||
# NON PIP PACKAGES INSTALL START
|
||||
for pkg in tsufiev.djblets; do
|
||||
PACKAGE=${pkg##*.}
|
||||
OWNER=${pkg%.*}
|
||||
SUFFIX=master.zip
|
||||
PACKAGE_OUTARCH_FILENAME=$PACKAGE-$SUFFIX
|
||||
cd $SERVICE_CONTENT_DIRECTORY/dist && wget $NON_PIP_PACKAGES_BASE_URL/$OWNER/$PACKAGE/archive/$SUFFIX -O $PACKAGE_OUTARCH_FILENAME
|
||||
if [ $? -ne 0 ];then
|
||||
log " Can't download \"$PACKAGE_OUTARCH_FILENAME\", exiting!!!"
|
||||
exit 1
|
||||
fi
|
||||
cd $SERVICE_CONTENT_DIRECTORY/dist && unzip $PACKAGE_OUTARCH_FILENAME
|
||||
if [ $? -ne 0 ];then
|
||||
log " Can't unzip \"$SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE_OUTARCH_FILENAME\", exiting!!!"
|
||||
exit 1
|
||||
fi
|
||||
cd $SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE-${SUFFIX%.*} && python setup.py install
|
||||
if [ $? -ne 0 ]; then
|
||||
log "\"$SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE-${SUFFIX%.*}/setup.py\" python setup FAILS, exiting!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
# NON PIP PACKAGES INSTALL END
|
||||
else
|
||||
log "$MRN_CND_SPY not found!"
|
||||
fi
|
||||
|
26
setup.sh
26
setup.sh
@ -21,7 +21,6 @@ PREREQ_PKGS="wget make git python-pip python-dev python-mysqldb libxml2-dev libx
|
||||
SERVICE_SRV_NAME="murano-dashboard"
|
||||
GIT_CLONE_DIR=`echo $SERVICE_CONTENT_DIRECTORY | sed -e "s/$SERVICE_SRV_NAME//"`
|
||||
HORIZON_CONFIGS="/opt/stack/horizon/openstack_dashboard/settings.py,/usr/share/openstack-dashboard/openstack_dashboard/settings.py"
|
||||
NON_PIP_PACKAGES_BASE_URL=https://github.com
|
||||
|
||||
# Functions
|
||||
# Logger function
|
||||
@ -98,7 +97,7 @@ HORIZON_CONFIG['exceptions']['recoverable'] = EXTENDED_RECOVERABLE_EXCEPTIONS
|
||||
HORIZON_CONFIG['exceptions']['not_found'] = EXTENDED_NOT_FOUND_EXCEPTIONS
|
||||
HORIZON_CONFIG['exceptions']['unauthorized'] = EXTENDED_UNAUTHORIZED_EXCEPTIONS
|
||||
HORIZON_CONFIG['customization_module'] = 'muranodashboard.panel.overrides'
|
||||
INSTALLED_APPS += ('muranodashboard','djblets','djblets.datagrid','djblets.util','floppyforms',)
|
||||
INSTALLED_APPS += ('muranodashboard','floppyforms',)
|
||||
MIDDLEWARE_CLASSES += ('muranodashboard.settings.ExceptionMiddleware',)
|
||||
LOGGING['formatters'] = {'verbose': {'format': '[%(asctime)s] [%(levelname)s] [pid=%(process)d] %(message)s'}}
|
||||
LOGGING['handlers']['file'] = {'level': 'DEBUG', 'formatter': 'verbose', 'class': 'logging.FileHandler', 'filename': '/var/log/murano-dashboard.log'}
|
||||
@ -192,29 +191,6 @@ CLONE_FROM_GIT=$1
|
||||
log "pip install \"$TRBL_FILE\" FAILS, exiting!!!"
|
||||
exit 1
|
||||
fi
|
||||
# NON PIP PACKAGES INSTALL START
|
||||
for pkg in tsufiev.djblets; do
|
||||
PACKAGE=${pkg##*.}
|
||||
OWNER=${pkg%.*}
|
||||
SUFFIX=master.zip
|
||||
PACKAGE_OUTARCH_FILENAME=$PACKAGE-$SUFFIX
|
||||
cd $SERVICE_CONTENT_DIRECTORY/dist && wget $NON_PIP_PACKAGES_BASE_URL/$OWNER/$PACKAGE/archive/$SUFFIX -O $PACKAGE_OUTARCH_FILENAME
|
||||
if [ $? -ne 0 ];then
|
||||
log " Can't download \"$PACKAGE_OUTARCH_FILENAME\", exiting!!!"
|
||||
exit 1
|
||||
fi
|
||||
cd $SERVICE_CONTENT_DIRECTORY/dist && unzip $PACKAGE_OUTARCH_FILENAME
|
||||
if [ $? -ne 0 ];then
|
||||
log " Can't unzip \"$SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE_OUTARCH_FILENAME\", exiting!!!"
|
||||
exit 1
|
||||
fi
|
||||
cd $SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE-${SUFFIX%.*} && python setup.py install
|
||||
if [ $? -ne 0 ]; then
|
||||
log "\"$SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE-${SUFFIX%.*}/setup.py\" python setup FAILS, exiting!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
# NON PIP PACKAGES INSTALL END
|
||||
else
|
||||
log "$MRN_CND_SPY not found!"
|
||||
fi
|
||||
|
Loading…
Reference in New Issue
Block a user