Merge "Remove congress_dashboard from congress repo"

This commit is contained in:
Zuul 2017-11-21 07:38:42 +00:00 committed by Gerrit Code Review
commit aeeb3f0a44
38 changed files with 0 additions and 2739 deletions

View File

@ -1,25 +0,0 @@
Congress Dashboard
------------------
Congress Dashboard is an extension for OpenStack Dashboard that provides a UI
for Congress. With congress-dashboard, a user is able to easily write the
policies and rules for governance of cloud.
Setup Instructions
------------------
This instruction assumes that Horizon is already installed and its
installation folder is <horizon>. Detailed information on how to install
Horizon can be found at
https://docs.openstack.org/horizon/latest/install/index.html.
To integrate congress with horizon, copy the files in
<congress_dashboard>/enabled to <horizon>/openstack_dashboard/local/enabled/
$ cp -b <congress_dashboard>/enabled/_50_policy.py <horizon>/openstack_dashboard/local/enabled/
$ cp -b <congress_dashboard>/enabled/_60_policies.py <horizon>/openstack_dashboard/local/enabled/
$ cp -b <congress_dashboard>/enabled/_70_datasources.py <horizon>/openstack_dashboard/local/enabled/
Restart Apache server
sudo service apache2 restart

View File

@ -1,304 +0,0 @@
# Copyright 2014 VMware.
#
# 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 congressclient.v1 import client as congress_client
from django.conf import settings
import keystoneauth1.identity.v2 as v2
import keystoneauth1.identity.v3 as v3
import keystoneauth1.session as kssession
from openstack_dashboard.api import base
from oslo_log import log as logging
LITERALS_SEPARATOR = '),'
RULE_SEPARATOR = ':-'
TABLE_SEPARATOR = ':'
LOG = logging.getLogger(__name__)
def _set_id_as_name_if_empty(apidict, length=0):
try:
if not apidict._apidict.get('name'):
id = apidict._apidict['id']
if length:
id = id[:length]
apidict._apidict['name'] = '(%s)' % id
else:
apidict._apidict['name'] = id
except KeyError:
pass
class PolicyAPIDictWrapper(base.APIDictWrapper):
def set_id_as_name_if_empty(self):
_set_id_as_name_if_empty(self)
def set_id_if_empty(self, id):
apidict_id = self._apidict.get('id')
if not apidict_id or apidict_id == "None":
self._apidict['id'] = id
def set_value(self, key, value):
self._apidict[key] = value
def delete_by_key(self, key):
del self._apidict[key]
class PolicyRule(PolicyAPIDictWrapper):
"""Wrapper for a Congress policy's rule."""
def set_id_as_name_if_empty(self):
pass
class PolicyTable(PolicyAPIDictWrapper):
"""Wrapper for a Congress policy's data table."""
def set_policy_details(self, policy):
self._apidict['policy_name'] = policy['name']
self._apidict['policy_owner_id'] = policy['owner_id']
def congressclient(request):
"""Instantiate Congress client."""
auth_url = getattr(settings, 'OPENSTACK_KEYSTONE_URL')
user = request.user
session = get_keystone_session(auth_url, user)
region_name = user.services_region
kwargs = {
'session': session,
'auth': None,
'interface': 'publicURL',
'service_type': 'policy',
'region_name': region_name
}
return congress_client.Client(**kwargs)
def get_keystone_session(auth_url, user):
if auth_url[-3:] == '/v3':
auth = v3.Token(auth_url, user.token.id, project_id=user.tenant_id)
else:
auth = v2.Token(auth_url, user.token.id, tenant_id=user.tenant_id,
tenant_name=user.tenant_name)
session = kssession.Session(auth=auth)
return session
def policies_list(request):
"""List all policies."""
client = congressclient(request)
policies_list = client.list_policy()
results = policies_list['results']
policies = []
for p in results:
policy = PolicyAPIDictWrapper(p)
# Policies currently have a name but not necessarily a non-"None" id.
# Use the name to identify the policy, needed to differentiate them in
# DataTables.
policy.set_id_if_empty(policy.get('name'))
policies.append(policy)
return policies
def policy_create(request, args):
"""Create a policy with the given properties."""
client = congressclient(request)
policy = client.create_policy(args)
return policy
def policy_delete(request, policy_id):
"""Delete a policy by id."""
client = congressclient(request)
policy = client.delete_policy(policy_id)
return policy
def policy_get(request, policy_name):
"""Get a policy by name."""
# TODO(jwy): Use congress.show_policy() once system policies have unique
# IDs.
policies = policies_list(request)
for p in policies:
if p['name'] == policy_name:
return p
def policy_rule_create(request, policy_name, body=None):
"""Create a rule in the given policy, with the given properties."""
client = congressclient(request)
rule = client.create_policy_rule(policy_name, body=body)
return rule
def policy_rule_delete(request, policy_name, rule_id):
"""Delete a rule by id, from the given policy."""
client = congressclient(request)
rule = client.delete_policy_rule(policy_name, rule_id)
return rule
def policy_rules_list(request, policy_name):
"""List all rules in a policy, given by name."""
client = congressclient(request)
policy_rules_list = client.list_policy_rules(policy_name)
results = policy_rules_list['results']
return [PolicyRule(r) for r in results]
def policy_tables_list(request, policy_name):
"""List all data tables in a policy, given by name."""
client = congressclient(request)
policy_tables_list = client.list_policy_tables(policy_name)
results = policy_tables_list['results']
return [PolicyTable(t) for t in results]
def policy_table_get(request, policy_name, table_name):
"""Get a policy table in a policy, given by name."""
client = congressclient(request)
return client.show_policy_table(policy_name, table_name)
def policy_rows_list(request, policy_name, table_name):
"""List all rows in a policy's data table, given by name."""
client = congressclient(request)
policy_rows_list = client.list_policy_rows(policy_name, table_name)
results = policy_rows_list['results']
policy_rows = []
# Policy table rows currently don't have ids. However, the DataTable object
# requires an id for the table to get rendered properly. Otherwise, the
# same contents are displayed for every row in the table. Assign the rows
# ids here.
id = 0
for row in results:
new_row = PolicyAPIDictWrapper(row)
new_row.set_id_if_empty(id)
id += 1
policy_rows.append(new_row)
return policy_rows
def policy_table_schema_get(request, policy_name, table_name):
"""Get the schema for a policy table, based on the first matching rule."""
column_names = []
rules = policy_rules_list(request, policy_name)
# There might be multiple rules that use the same name in the head. Pick
# the first matching one, which is what the policy engine currently does.
for rule in rules:
rule_def = rule['rule']
head, _ = rule_def.split(RULE_SEPARATOR)
if head.strip().startswith('%s(' % table_name):
start = head.index('(') + 1
end = head.index(')')
column_names = head[start:end].split(',')
break
schema = {'table_id': table_name}
schema['columns'] = [{'name': name.strip(), 'description': None}
for name in column_names]
return schema
def datasources_list(request):
"""List all the data sources."""
client = congressclient(request)
datasources_list = client.list_datasources()
datasources = datasources_list['results']
return [PolicyAPIDictWrapper(d) for d in datasources]
def datasource_get(request, datasource_id):
"""Get a data source by id."""
# TODO(jwy): Need API in congress_client to retrieve data source by id.
datasources = datasources_list(request)
for d in datasources:
if d['id'] == datasource_id:
return d
def datasource_get_by_name(request, datasource_name):
"""Get a data source by name."""
datasources = datasources_list(request)
for d in datasources:
if d['name'] == datasource_name:
return d
def datasource_tables_list(request, datasource_id):
"""List all data tables in a data source, given by id."""
client = congressclient(request)
datasource_tables_list = client.list_datasource_tables(datasource_id)
results = datasource_tables_list['results']
return [PolicyAPIDictWrapper(t) for t in results]
def datasource_rows_list(request, datasource_id, table_name):
"""List all rows in a data source's data table, given by id."""
client = congressclient(request)
datasource_rows_list = client.list_datasource_rows(datasource_id,
table_name)
results = datasource_rows_list['results']
datasource_rows = []
id = 0
for row in results:
new_row = PolicyAPIDictWrapper(row)
new_row.set_id_if_empty(id)
id += 1
datasource_rows.append(new_row)
return datasource_rows
def datasource_schema_get(request, datasource_id):
"""Get the schema for all tables in the given data source."""
client = congressclient(request)
return client.show_datasource_schema(datasource_id)
def datasource_table_schema_get(request, datasource_id, table_name):
"""Get the schema for a data source table."""
client = congressclient(request)
return client.show_datasource_table_schema(datasource_id, table_name)
def datasource_table_schema_get_by_name(request, datasource_name, table_name):
"""Get the schema for a data source table."""
datasource = datasource_get_by_name(request, datasource_name)
client = congressclient(request)
return client.show_datasource_table_schema(datasource['id'], table_name)
def datasource_statuses_list(request):
client = congressclient(request)
datasources_list = client.list_datasources()
datasources = datasources_list['results']
ds_status = []
for ds in datasources:
try:
status = client.list_datasource_status(ds['id'])
except Exception:
LOG.exception("Exception while getting the status")
raise
wrapper = PolicyAPIDictWrapper(ds)
wrapper.set_value('service', ds['name'])
for key in status:
value = status[key]
wrapper.set_value(key, value)
ds_status.append(wrapper)
return ds_status

View File

@ -1,26 +0,0 @@
# Copyright 2014 VMware.
#
# 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 django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.dashboards.admin import dashboard
class DataSources(horizon.Panel):
name = _("Data Sources")
slug = "datasources"
permissions = ('openstack.roles.admin',)
dashboard.Admin.register(DataSources)

View File

@ -1,75 +0,0 @@
# Copyright 2014 VMware.
#
# 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 django.core.urlresolvers import reverse
from django.template.defaultfilters import unordered_list
from django.utils.translation import ugettext_lazy as _
from horizon import tables
def get_resource_url(obj):
return reverse('horizon:admin:datasources:datasource_table_detail',
args=(obj['datasource_id'], obj['table_id']))
class DataSourcesTablesTable(tables.DataTable):
name = tables.Column("name", verbose_name=_("Table Name"),
link=get_resource_url)
datasource_name = tables.Column("datasource_name",
verbose_name=_("Service"))
datasource_driver = tables.Column("datasource_driver",
verbose_name=_("Driver"))
class Meta(object):
name = "datasources_tables"
verbose_name = _("Service Data")
hidden_title = False
def get_policy_link(datum):
return reverse('horizon:admin:policies:detail',
args=(datum['policy_name'],))
def get_policy_table_link(datum):
return reverse('horizon:admin:datasources:policy_table_detail',
args=(datum['policy_name'], datum['name']))
class DataSourceRowsTable(tables.DataTable):
class Meta(object):
name = "datasource_rows"
verbose_name = _("Rows")
hidden_title = False
class DataSourceStatusesTable(tables.DataTable):
datasource_name = tables.Column("service",
verbose_name=_("Service"))
last_updated = tables.Column("last_updated",
verbose_name=_("Last Updated"))
subscriptions = tables.Column("subscriptions",
verbose_name=_("Subscriptions"),
wrap_list=True, filters=(unordered_list,))
last_error = tables.Column("last_error", verbose_name=_("Last Error"))
subscribers = tables.Column("subscribers", verbose_name=_("Subscribers"),
wrap_list=True, filters=(unordered_list,))
initialized = tables.Column("initialized", verbose_name=_("Initialized"))
number_of_updates = tables.Column("number_of_updates",
verbose_name=_("Number of Updates"))
class Meta(object):
name = "service_status"
verbose_name = _("Service Status")
hidden_title = False

View File

@ -1,14 +0,0 @@
{% load i18n %}
<h3>{% trans "Table Overview" %}</h3>
<div class="detail">
<dl class="dl-horizontal">
<dt>{{ datasource_type }} {% trans "Data Source" %}</dt>
<dd>{{ datasource_name }}</dd>
<dt>{% trans "Name" %}</dt>
<dd>{{ table_name }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ id|default:table_name }}</dd>
</dl>
</div>

View File

@ -1,14 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Data Source Table Details" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Data Source Table Details: ")|add:table_name %}
{% endblock page_header %}
{% block main %}
{% include "admin/datasources/_detail_overview.html" %}
<div id="datasource_table_rows">
{{ datasource_rows_table.render }}
</div>
{% endblock %}

View File

@ -1,19 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Data Sources" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Data Sources") %}
{% endblock page_header %}
{% block main %}
<div id="policies_tables">
{{ policies_tables_table.render }}
</div>
<div id="service_status_tables">
{{ service_status_table.render }}
</div>
<div id="service_tables">
{{ datasources_tables_table.render }}
</div>
{% endblock %}

View File

@ -1,30 +0,0 @@
# Copyright 2014 VMware.
#
# 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 django.conf.urls import patterns
from django.conf.urls import url
from congress_dashboard.datasources import views
SERVICES = (
r'^services/(?P<datasource_id>[^/]+)/(?P<service_table_name>[^/]+)/%s$')
urlpatterns = patterns(
'',
url(r'^$', views.IndexView.as_view(), name='index'),
url(SERVICES % 'detail', views.DetailView.as_view(),
name='datasource_table_detail'),
)

View File

@ -1,187 +0,0 @@
# Copyright 2015 VMware.
#
# 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.
import logging
from congress_dashboard.api import congress
LOG = logging.getLogger(__name__)
def _get_policy_tables(request):
# Return all policy tables.
all_tables = []
try:
# Get all the policies.
policies = congress.policies_list(request)
except Exception as e:
LOG.error('Unable to get list of policies: %s', str(e))
else:
try:
for policy in policies:
# Get all the tables in this policy.
policy_name = policy['name']
policy_tables = congress.policy_tables_list(request,
policy_name)
# Get the names of the tables.
datasource_tables = []
for table in policy_tables:
table.set_id_as_name_if_empty()
table_name = table['name']
# Exclude service-derived tables.
if congress.TABLE_SEPARATOR not in table_name:
datasource_tables.append(table['name'])
all_tables.append({'datasource': policy_name,
'tables': datasource_tables})
except Exception as e:
LOG.error('Unable to get tables for policy "%s": %s',
policy_name, str(e))
return all_tables
def _get_service_tables(request):
# Return all service tables.
all_tables = []
try:
# Get all the services.
services = congress.datasources_list(request)
except Exception as e:
LOG.error('Unable to get list of data sources: %s', str(e))
else:
try:
for service in services:
# Get all the tables in this service.
service_id = service['id']
service_tables = congress.datasource_tables_list(request,
service_id)
# Get the names of the tables.
datasource_tables = []
for table in service_tables:
table.set_id_as_name_if_empty()
datasource_tables.append(table['name'])
all_tables.append({'datasource': service['name'],
'tables': datasource_tables})
except Exception as e:
LOG.error('Unable to get tables for data source "%s": %s',
service_id, str(e))
return all_tables
def get_datasource_tables(request):
"""Get names of all data source tables.
Example:
[
{
'datasource': 'classification',
'tables': ['error']
},
{
'datasource': 'neutronv2'
'tables': ['networks', 'ports', ...]
},
...
]
"""
tables = _get_policy_tables(request)
tables.extend(_get_service_tables(request))
return tables
def get_datasource_columns(request):
"""Get of names of columns from all data sources.
Example:
[
{
'datasource': 'classification',
'tables': [
{
'table': 'error',
'columns': ['name']
}
]
},
{
'datasource': 'neutronv2',
'tables': [
{
'table': 'networks',
'columns': ['id', 'tenant_id', ...],
},
...
],
...
},
...
]
"""
all_columns = []
# Get all the policy tables.
policy_tables = _get_policy_tables(request)
try:
for policy in policy_tables:
# Get all the columns in this policy. Unlike for the services,
# there's currently no congress client API to get the schema for
# all tables in a policy in a single call.
policy_name = policy['datasource']
tables = policy['tables']
datasource_tables = []
for table_name in tables:
# Get all the columns in this policy table.
schema = congress.policy_table_schema_get(request, policy_name,
table_name)
columns = [c['name'] for c in schema['columns']]
datasource_tables.append({'table': table_name,
'columns': columns})
all_columns.append({'datasource': policy_name,
'tables': datasource_tables})
except Exception as e:
LOG.error('Unable to get schema for policy "%s" table "%s": %s',
policy_name, table_name, str(e))
try:
# Get all the services.
services = congress.datasources_list(request)
except Exception as e:
LOG.error('Unable to get list of data sources: %s', str(e))
else:
try:
for service in services:
# Get the schema for this service.
service_id = service['id']
service_name = service['name']
schema = congress.datasource_schema_get(request, service_id)
datasource_tables = []
for table in schema['tables']:
# Get the columns for this table.
columns = [c['name'] for c in table['columns']]
datasource_table = {'table': table['table_id'],
'columns': columns}
datasource_tables.append(datasource_table)
all_columns.append({'datasource': service_name,
'tables': datasource_tables})
except Exception as e:
LOG.error('Unable to get schema for data source "%s": %s',
service_id, str(e))
return all_columns

View File

@ -1,238 +0,0 @@
# Copyright 2014 VMware.
#
# 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.
import copy
import logging
from django.core.urlresolvers import reverse
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import messages
from horizon import tables
from congress_dashboard.api import congress
from congress_dashboard.datasources import tables as datasources_tables
logger = logging.getLogger(__name__)
class IndexView(tables.MultiTableView):
"""List service and policy defined data."""
table_classes = (datasources_tables.DataSourcesTablesTable,
datasources_tables.DataSourceStatusesTable,)
template_name = 'admin/datasources/index.html'
def get_datasources_tables_data(self):
try:
datasources = congress.datasources_list(self.request)
except Exception as e:
msg = _('Unable to get services list: %s') % str(e)
messages.error(self.request, msg)
return []
ds_temp = []
for ds in datasources:
ds_id = ds['id']
try:
ds_tables = congress.datasource_tables_list(self.request,
ds_id)
except Exception as e:
msg_args = {'ds_id': ds_id, 'error': str(e)}
msg = _('Unable to get tables list for service "%(ds_id)s": '
'%(error)s') % msg_args
messages.error(self.request, msg)
return []
for table in ds_tables:
table.set_value('datasource_id', ds_id)
table.set_value('datasource_name', ds['name'])
table.set_value('datasource_driver', ds['driver'])
table.set_id_as_name_if_empty()
# Object ids within a Horizon table must be unique. Otherwise,
# Horizon will cache the column values for the object by id and
# use the same column values for all rows with the same id.
table.set_value('table_id', table['id'])
table.set_value('id', '%s-%s' % (ds_id, table['table_id']))
ds_temp.append(table)
logger.debug("ds_temp %s" % ds_temp)
return ds_temp
def get_service_status_data(self):
ds = []
try:
ds = congress.datasource_statuses_list(self.request)
logger.debug("ds status : %s " % ds)
except Exception as e:
msg = _('Unable to get datasource status list: %s') % str(e)
messages.error(self.request, msg)
return ds
class DetailView(tables.DataTableView):
"""List details about and rows from a data source (service or policy)."""
table_class = datasources_tables.DataSourceRowsTable
template_name = 'admin/datasources/detail.html'
def get_data(self):
datasource_id = self.kwargs['datasource_id']
table_name = self.kwargs.get('policy_table_name')
is_service = False
try:
if table_name:
# Policy data table.
rows = congress.policy_rows_list(self.request, datasource_id,
table_name)
if congress.TABLE_SEPARATOR in table_name:
table_name_parts = table_name.split(
congress.TABLE_SEPARATOR)
maybe_datasource_name = table_name_parts[0]
datasources = congress.datasources_list(self.request)
for datasource in datasources:
if datasource['name'] == maybe_datasource_name:
# Serivce-derived policy data table.
is_service = True
datasource_id = datasource['id']
table_name = table_name_parts[1]
break
else:
# Service data table.
is_service = True
datasource = congress.datasource_get_by_name(
self.request, datasource_id)
table_name = self.kwargs['service_table_name']
rows = congress.datasource_rows_list(
self.request, datasource_id, table_name)
except Exception as e:
msg_args = {
'table_name': table_name,
'ds_id': datasource_id,
'error': str(e)
}
msg = _('Unable to get rows in table "%(table_name)s", data '
'source "%(ds_id)s": %(error)s') % msg_args
messages.error(self.request, msg)
redirect = reverse('horizon:admin:datasources:index')
raise exceptions.Http302(redirect)
# Normally, in Horizon, the columns for a table are defined as
# attributes of the Table class. When the class is instantiated,
# the columns are processed during the metaclass initialization. To
# add columns dynamically, re-create the class from the metaclass
# with the added columns, re-create the Table from the new class,
# then reassign the Table stored in this View.
column_names = []
table_class_attrs = copy.deepcopy(dict(self.table_class.__dict__))
# Get schema from the server.
try:
if is_service:
schema = congress.datasource_table_schema_get(
self.request, datasource_id, table_name)
else:
schema = congress.policy_table_schema_get(
self.request, datasource_id, table_name)
except Exception as e:
msg_args = {
'table_name': table_name,
'ds_id': datasource_id,
'error': str(e)
}
msg = _('Unable to get schema for table "%(table_name)s", '
'data source "%(ds_id)s": %(error)s') % msg_args
messages.error(self.request, msg)
redirect = reverse('horizon:admin:datasources:index')
raise exceptions.Http302(redirect)
columns = schema['columns']
row_len = 0
if len(rows):
row_len = len(rows[0].get('data', []))
if not row_len or row_len == len(columns):
for col in columns:
col_name = col['name']
# Attribute name for column in the class must be a valid
# identifier. Slugify it.
col_slug = slugify(col_name)
column_names.append(col_slug)
table_class_attrs[col_slug] = tables.Column(
col_slug, verbose_name=col_name)
else:
# There could be another table with the same name and different
# arity. Divide the rows into unnamed columns. Number them for
# internal reference.
for i in xrange(0, row_len):
col_name = str(i)
column_names.append(col_name)
table_class_attrs[col_name] = tables.Column(
col_name, verbose_name='')
# Class and object re-creation, using a new class name, the same base
# classes, and the new class attributes, which now includes columns.
columnized_table_class_name = '%s%sRows' % (
slugify(datasource_id).title(), slugify(table_name).title())
columnized_table_class = tables.base.DataTableMetaclass(
str(columnized_table_class_name), self.table_class.__bases__,
table_class_attrs)
self.table_class = columnized_table_class
columnized_table = columnized_table_class(self.request, **self.kwargs)
self._tables[columnized_table_class._meta.name] = columnized_table
# Map columns names to row values.
num_cols = len(column_names)
for row in rows:
try:
row_data = row['data']
row.delete_by_key('data')
for i in xrange(0, num_cols):
row.set_value(column_names[i], row_data[i])
except Exception as e:
msg_args = {
'table_name': table_name,
'ds_id': datasource_id,
'error': str(e)
}
msg = _('Unable to get data for table "%(table_name)s", data '
'source "%(ds_id)s": %(error)s') % msg_args
messages.error(self.request, msg)
redirect = reverse('horizon:admin:datasources:index')
raise exceptions.Http302(redirect)
return rows
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
if 'policy_table_name' in kwargs:
table_name = kwargs.get('policy_table_name')
context['datasource_type'] = _('Policy')
datasource_name = kwargs['datasource_id']
else:
table_name = kwargs['service_table_name']
context['datasource_type'] = _('Service')
try:
datasource_id = kwargs['datasource_id']
datasource = congress.datasource_get(self.request,
datasource_id)
datasource_name = datasource['name']
except Exception as e:
datasource_name = datasource_id
logger.info('Failed to get data source "%s": %s' %
(datasource_id, str(e)))
context['datasource_name'] = datasource_name
context['table_name'] = table_name
return context

View File

@ -1,3 +0,0 @@
PANEL_GROUP = 'policy'
PANEL_GROUP_NAME = 'Policy'
PANEL_GROUP_DASHBOARD = 'admin'

View File

@ -1,9 +0,0 @@
PANEL = 'policies'
PANEL_DASHBOARD = 'admin'
PANEL_GROUP = 'policy'
ADD_PANEL = 'congress_dashboard.policies.panel.Policies'
ADD_INSTALLED_APPS = [
'congress_dashboard',
]
AUTO_DISCOVER_STATIC_FILES = True
ADD_SCSS_FILES = ['congress_dashboard/static/admin/css/policies.css']

View File

@ -1,5 +0,0 @@
PANEL = 'datasources'
PANEL_DASHBOARD = 'admin'
PANEL_GROUP = 'policy'
ADD_PANEL = 'congress_dashboard.datasources.panel.DataSources'
AUTO_DISCOVER_STATIC_FILES = True

View File

@ -1,69 +0,0 @@
# Copyright 2015 VMware.
#
# 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.
import logging
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from congress_dashboard.api import congress
LOG = logging.getLogger(__name__)
POLICY_KIND_CHOICES = (
('nonrecursive', _('Nonrecursive')),
('action', _('Action')),
('database', _('Database')),
('materialized', _('Materialized')),
)
class CreatePolicy(forms.SelfHandlingForm):
name = forms.CharField(max_length=255, label=_("Policy Name"))
kind = forms.ChoiceField(choices=POLICY_KIND_CHOICES, label=_("Kind"),
initial='nonrecursive')
description = forms.CharField(label=_("Description"), required=False,
widget=forms.Textarea(attrs={'rows': 4}))
failure_url = 'horizon:admin:policies:index'
def handle(self, request, data):
policy_name = data['name']
policy_description = data.get('description')
policy_kind = data.pop('kind')
LOG.info('User %s creating policy "%s" of type %s in tenant %s',
request.user.username, policy_name, policy_kind,
request.user.tenant_name)
try:
params = {
'name': policy_name,
'description': policy_description,
'kind': policy_kind,
}
policy = congress.policy_create(request, params)
msg = _('Created policy "%s"') % policy_name
LOG.info(msg)
messages.success(request, msg)
except Exception as e:
msg_args = {'policy_name': policy_name, 'error': str(e)}
msg = _('Failed to create policy "%(policy_name)s": '
'%(error)s') % msg_args
LOG.error(msg)
messages.error(self.request, msg)
redirect = reverse(self.failure_url)
raise exceptions.Http302(redirect)
return policy

View File

@ -1,26 +0,0 @@
# Copyright 2014 VMware.
#
# 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 django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.dashboards.admin import dashboard
class Policies(horizon.Panel):
name = _("Policies")
slug = "policies"
permissions = ('openstack.roles.admin',)
dashboard.Admin.register(Policies)

View File

@ -1,127 +0,0 @@
# Copyright 2015 VMware.
#
# 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.
import logging
from django.core.urlresolvers import reverse
from django.template.defaultfilters import linebreaksbr
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import messages
from horizon import tables
from openstack_dashboard import policy
from congress_dashboard.api import congress
LOG = logging.getLogger(__name__)
class CreateRule(tables.LinkAction):
name = 'create_rule'
verbose_name = _('Create Rule')
url = 'horizon:admin:policies:create_rule'
classes = ('ajax-modal',)
icon = 'plus'
policy_rules = (('policy', 'create_rule'),)
def get_link_url(self, datum=None):
policy_name = self.table.kwargs['policy_name']
return reverse(self.url, args=(policy_name,))
class DeleteRule(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u'Delete Rule',
u'Delete Rules',
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u'Deleted rule',
u'Deleted rules',
count
)
redirect_url = 'horizon:admin:policies:detail'
def delete(self, request, obj_id):
policy_name = self.table.kwargs['policy_name']
LOG.info('User %s deleting policy "%s" rule "%s" in tenant %s',
request.user.username, policy_name, obj_id,
request.user.tenant_name)
try:
congress.policy_rule_delete(request, policy_name, obj_id)
LOG.info('Deleted policy rule "%s"', obj_id)
except Exception as e:
msg_args = {'rule_id': obj_id, 'error': str(e)}
msg = _('Failed to delete policy rule "%(rule_id)s": '
'%(error)s') % msg_args
LOG.error(msg)
messages.error(request, msg)
redirect = reverse(self.redirect_url, args=(policy_name,))
raise exceptions.Http302(redirect)
def _format_rule(rule):
"""Make rule's text more human readable."""
head_body = rule.split(congress.RULE_SEPARATOR)
if len(head_body) < 2:
return rule
head = head_body[0]
body = head_body[1]
# Add newline after each literal in the body.
body_literals = body.split(congress.LITERALS_SEPARATOR)
literals_break = congress.LITERALS_SEPARATOR + '\n'
new_body = literals_break.join(body_literals)
# Add newline after the head.
rules_break = congress.RULE_SEPARATOR + '\n'
return rules_break.join([head, new_body])
class PolicyRulesTable(tables.DataTable):
id = tables.Column("id", verbose_name=_("Rule ID"))
name = tables.Column("name", verbose_name=_("Name"))
comment = tables.Column("comment", verbose_name=_("Comment"))
rule = tables.Column("rule", verbose_name=_("Rule"),
filters=(_format_rule, linebreaksbr,))
class Meta(object):
name = "policy_rules"
verbose_name = _("Rules")
table_actions = (CreateRule, DeleteRule,)
row_actions = (DeleteRule,)
hidden_title = False
def get_policy_table_link(datum):
return reverse('horizon:admin:policies:policy_table_detail',
args=(datum['policy_name'], datum['name']))
class PoliciesTablesTable(tables.DataTable):
name = tables.Column("name", verbose_name=_("Table Name"),
link=get_policy_table_link)
class Meta(object):
name = "policies_tables"
verbose_name = _("Policy Table Data")
hidden_title = False

View File

@ -1,31 +0,0 @@
# Copyright 2015 VMware.
#
# 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 django.core.urlresolvers import reverse
from horizon import workflows
from congress_dashboard.policies.rules import workflows as rule_workflows
class CreateView(workflows.WorkflowView):
workflow_class = rule_workflows.CreateRule
ajax_template_name = 'admin/policies/rules/create.html'
success_url = 'horizon:admin:policies:detail'
def get_success_url(self):
return reverse(self.success_url,
args=(self.kwargs['policy_name'],))
def get_initial(self):
return {'policy_name': self.kwargs['policy_name']}

View File

@ -1,441 +0,0 @@
# Copyright 2015 VMware.
#
# 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.
import logging
import re
from django.core.urlresolvers import reverse
from django import template
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from horizon import forms
from horizon import workflows
import six
from congress_dashboard.api import congress
COLUMN_FORMAT = '<datasource>%s<table> <column>' % congress.TABLE_SEPARATOR
COLUMN_PATTERN = r'\s*[\w.]+%s[\w.]+\s+[\w.]+\s*$' % congress.TABLE_SEPARATOR
COLUMN_PATTERN_ERROR = 'Column name must be in "%s" format' % COLUMN_FORMAT
TABLE_FORMAT = '<datasource>%s<table>' % congress.TABLE_SEPARATOR
TABLE_PATTERN = r'\s*[\w.]+%s[\w.]+\s*$' % congress.TABLE_SEPARATOR
TABLE_PATTERN_ERROR = 'Table name must be in "%s" format' % TABLE_FORMAT
LOG = logging.getLogger(__name__)
class CreateOutputAction(workflows.Action):
policy_name = forms.CharField(widget=forms.HiddenInput(), required=False)
rule_name = forms.CharField(label=_('Rule Name'), max_length=255,
initial='', required=False)
comment = forms.CharField(label=_('Rule Comment'), initial='',
required=False)
policy_table = forms.CharField(label=_("Policy Table Name"), initial='',
max_length=255)
policy_columns = forms.CharField(
label=_('Policy Table Columns'), initial='',
help_text=_('Name the columns in the output table, one per textbox.'))
failure_url = 'horizon:admin:policies:detail'
def __init__(self, request, context, *args, **kwargs):
super(CreateOutputAction, self).__init__(request, context, *args,
**kwargs)
self.fields['policy_name'].initial = context['policy_name']
class Meta(object):
name = _('Output')
class CreateOutput(workflows.Step):
action_class = CreateOutputAction
contributes = ('policy_name', 'rule_name', 'comment', 'policy_table',
'policy_columns')
template_name = 'admin/policies/rules/_create_output.html'
help_text = _('Information about the rule and the policy table '
'being created.')
def render(self):
# Overriding parent method to add extra template context variables.
step_template = template.loader.get_template(self.template_name)
extra_context = {"form": self.action,
"step": self}
context = template.RequestContext(self.workflow.request, extra_context)
# Data needed to re-create policy column inputs after an error occurs.
policy_columns = self.workflow.request.POST.get('policy_columns', '')
columns_list = policy_columns.split(', ')
context['policy_columns_list'] = columns_list
context['policy_columns_count'] = len(columns_list)
return step_template.render(context)
class CreateConditionsAction(workflows.Action):
mappings = forms.CharField(label=_('Policy table columns:'), initial='')
class Meta(object):
name = _('Conditions')
class CreateConditions(workflows.Step):
action_class = CreateConditionsAction
contributes = ('mappings',)
template_name = 'admin/policies/rules/_create_conditions.html'
help_text = _('Sources from which the output policy table will get its '
'data, plus any constraints.')
def _compare_mapping_columns(self, x, y):
# x = "mapping_column_<int>", y = "mapping_column_<int>"
return cmp(int(x.split('_')[-1]), int(y.split('_')[-1]))
def render(self):
# Overriding parent method to add extra template context variables.
step_template = template.loader.get_template(self.template_name)
extra_context = {"form": self.action,
"step": self}
context = template.RequestContext(self.workflow.request, extra_context)
# Data needed to re-create mapping column inputs after an error occurs.
post = self.workflow.request.POST
mappings = []
policy_columns = post.get('policy_columns')
policy_columns_list = []
# Policy column to data source mappings.
if policy_columns:
policy_columns_list = policy_columns.split(', ')
mapping_columns = []
for param, value in post.items():
if (param.startswith('mapping_column_') and
param != 'mapping_column_0'):
mapping_columns.append(param)
# Mapping columns should be in the same order as the policy columns
# above to which they match.
sorted_mapping_columns = sorted(mapping_columns,
cmp=self._compare_mapping_columns)
mapping_columns_list = [post.get(c)
for c in sorted_mapping_columns]
mappings = zip(policy_columns_list, mapping_columns_list)
context['mappings'] = mappings
# Add one for the hidden template row.
context['mappings_count'] = len(mappings) + 1
# Data needed to re-create join, negation, and alias inputs.
joins = []
negations = []
aliases = []
for param, value in post.items():
if param.startswith('join_left_') and value:
join_num = param.split('_')[-1]
other_value = post.get('join_right_%s' % join_num)
join_op = post.get('join_op_%s' % join_num)
if other_value and join_op is not None:
joins.append((value, join_op, other_value))
elif param.startswith('negation_value_') and value:
negation_num = param.split('_')[-1]
negation_column = post.get('negation_column_%s' %
negation_num)
if negation_column:
negations.append((value, negation_column))
elif param.startswith('alias_column_') and value:
alias_num = param.split('_')[-1]
alias_name = post.get('alias_name_%s' % alias_num)
if alias_name:
aliases.append((value, alias_name))
# Make sure there's at least one empty row.
context['joins'] = joins or [('', '')]
context['joins_count'] = len(joins) or 1
context['negations'] = negations or [('', '')]
context['negations_count'] = len(negations) or 1
context['aliases'] = aliases or [('', '')]
context['aliases_count'] = len(aliases) or 1
# Input validation attributes.
context['column_pattern'] = COLUMN_PATTERN
context['column_pattern_error'] = COLUMN_PATTERN_ERROR
context['table_pattern'] = TABLE_PATTERN
context['table_pattern_error'] = TABLE_PATTERN_ERROR
return step_template.render(context)
def _underscore_slugify(name):
# Slugify given string, except using undesrscores instead of hyphens.
return slugify(name).replace('-', '_')
class CreateRule(workflows.Workflow):
slug = 'create_rule'
name = _('Create Rule')
finalize_button_name = _('Create')
success_message = _('Created rule%(rule_name)s.%(error)s')
failure_message = _('Unable to create rule%(rule_name)s: %(error)s')
default_steps = (CreateOutput, CreateConditions)
wizard = True
def get_success_url(self):
policy_name = self.context.get('policy_name')
return reverse('horizon:admin:policies:detail', args=(policy_name,))
def get_failure_url(self):
policy_name = self.context.get('policy_name')
return reverse('horizon:admin:policies:detail', args=(policy_name,))
def format_status_message(self, message):
rule_name = self.context.get('rule_name')
name_str = ''
if rule_name:
name_str = ' "%s"' % rule_name
else:
rule_id = self.context.get('rule_id')
if rule_id:
name_str = ' %s' % rule_id
return message % {'rule_name': name_str,
'error': self.context.get('error', '')}
def _get_schema_columns(self, request, table):
table_parts = table.split(congress.TABLE_SEPARATOR)
datasource = table_parts[0]
table_name = table_parts[1]
try:
schema = congress.datasource_table_schema_get_by_name(
request, datasource, table_name)
except Exception:
# Maybe it's a policy table, not a service.
try:
schema = congress.policy_table_schema_get(
request, datasource, table_name)
except Exception as e:
# Nope.
LOG.error('Unable to get schema for table "%s", '
'datasource "%s": %s',
table_name, datasource, str(e))
return str(e)
return schema['columns']
def handle(self, request, data):
policy_name = data['policy_name']
username = request.user.username
project_name = request.user.tenant_name
# Output data.
rule_name = data.get('rule_name')
comment = data.get('comment')
policy_table = _underscore_slugify(data['policy_table'])
if not data['policy_columns']:
self.context['error'] = 'Missing policy table columns'
return False
policy_columns = data['policy_columns'].split(', ')
# Conditions data.
if not data['mappings']:
self.context['error'] = ('Missing data source column mappings for '
'policy table columns')
return False
mapping_columns = [c.strip() for c in data['mappings'].split(', ')]
if len(policy_columns) != len(mapping_columns):
self.context['error'] = ('Missing data source column mappings for '
'some policy table columns')
return False
# Map columns used in rule's head. Every column in the head must also
# appear in the body.
head_columns = [_underscore_slugify(c).strip() for c in policy_columns]
column_variables = dict(zip(mapping_columns, head_columns))
# All tables needed in the body.
body_tables = set()
negation_tables = set()
# Keep track of the tables from the head that need to be in the body.
for column in mapping_columns:
if re.match(COLUMN_PATTERN, column) is None:
self.context['error'] = '%s: %s' % (COLUMN_PATTERN_ERROR,
column)
return False
table = column.split()[0]
body_tables.add(table)
# Make sure columns that are given a significant variable name are
# unique names by adding name_count as a suffix.
name_count = 0
for param, value in request.POST.items():
if param.startswith('join_left_') and value:
if re.match(COLUMN_PATTERN, value) is None:
self.context['error'] = '%s: %s' % (COLUMN_PATTERN_ERROR,
value)
return False
value = value.strip()
# Get operator and other column used in join.
join_num = param.split('_')[-1]
join_op = request.POST.get('join_op_%s' % join_num)
other_value = request.POST.get('join_right_%s' % join_num)
other_value = other_value.strip()
if join_op == '=':
try:
# Check if static value is a number, but keep it as a
# string, to be used later.
int(other_value)
column_variables[value] = other_value
except ValueError:
# Pass it along as a quoted string.
column_variables[value] = '"%s"' % other_value
else:
# Join between two columns.
if not other_value:
# Ignore incomplete pairing.
continue
if re.match(COLUMN_PATTERN, other_value) is None:
self.context['error'] = ('%s: %s' %
(COLUMN_PATTERN_ERROR,
other_value))
return False
# Tables used in the join need to be in the body.
value_parts = value.split()
body_tables.add(value_parts[0])
body_tables.add(other_value.split()[0])
# Arbitrarily name the right column the same as the left.
column_name = value_parts[1]
# Use existing variable name if there is already one for
# either column in this join.
if other_value in column_variables:
column_variables[value] = column_variables[other_value]
elif value in column_variables:
column_variables[other_value] = column_variables[value]
else:
variable = '%s_%s' % (column_name, name_count)
name_count += 1
column_variables[value] = variable
column_variables[other_value] = variable
elif param.startswith('negation_value_') and value:
if re.match(COLUMN_PATTERN, value) is None:
self.context['error'] = '%s: %s' % (COLUMN_PATTERN_ERROR,
value)
return False
value = value.strip()
# Get operator and other column used in negation.
negation_num = param.split('_')[-1]
negation_column = request.POST.get('negation_column_%s' %
negation_num)
if not negation_column:
# Ignore incomplete pairing.
continue
if re.match(COLUMN_PATTERN, negation_column) is None:
self.context['error'] = '%s: %s' % (COLUMN_PATTERN_ERROR,
negation_column)
return False
negation_column = negation_column.strip()
# Tables for columns referenced by the negation table must
# appear in the body.
value_parts = value.split()
body_tables.add(value_parts[0])
negation_tables.add(negation_column.split()[0])
# Use existing variable name if there is already one for either
# column in this negation.
if negation_column in column_variables:
column_variables[value] = column_variables[negation_column]
elif value in column_variables:
column_variables[negation_column] = column_variables[value]
else:
# Arbitrarily name the negated table's column the same as
# the value column.
column_name = value_parts[1]
variable = '%s_%s' % (column_name, name_count)
name_count += 1
column_variables[value] = variable
column_variables[negation_column] = variable
LOG.debug('column_variables for rule: %s', column_variables)
# Form the literals for all the tables needed in the body. Make sure
# column that have no relation to any other columns are given a unique
# variable name, using column_count.
column_count = 0
literals = []
for table in body_tables:
# Replace column names with variable names that join related
# columns together.
columns = self._get_schema_columns(request, table)
if isinstance(columns, six.string_types):
self.context['error'] = columns
return False
literal_columns = []
if columns:
for column in columns:
table_column = '%s %s' % (table, column['name'])
literal_columns.append(
column_variables.get(table_column, 'col_%s' %
column_count))
column_count += 1
literals.append('%s(%s)' % (table, ', '.join(literal_columns)))
else:
# Just the table name, such as for classification:true.
literals.append(table)
# Form the negated tables.
for table in negation_tables:
columns = self._get_schema_columns(request, table)
if isinstance(columns, six.string_types):
self.context['error'] = columns
return False
literal_columns = []
num_variables = 0
for column in columns:
table_column = '%s %s' % (table, column['name'])
if table_column in column_variables:
literal_columns.append(column_variables[table_column])
num_variables += 1
else:
literal_columns.append('col_%s' % column_count)
column_count += 1
literal = 'not %s(%s)' % (table, ', '.join(literal_columns))
literals.append(literal)
# Every column in the negated table must appear in a non-negated
# literal in the body. If there are some variables that have not
# been used elsewhere, repeat the literal in its non-negated form.
if num_variables != len(columns) and table not in body_tables:
literals.append(literal.replace('not ', ''))
# All together now.
rule = '%s(%s) %s %s' % (policy_table, ', '.join(head_columns),
congress.RULE_SEPARATOR, ', '.join(literals))
LOG.info('User %s creating policy "%s" rule "%s" in tenant %s: %s',
username, policy_name, rule_name, project_name, rule)
try:
params = {
'name': rule_name,
'comment': comment,
'rule': rule,
}
rule = congress.policy_rule_create(request, policy_name,
body=params)
LOG.info('Created rule %s', rule['id'])
self.context['rule_id'] = rule['id']
except Exception as e:
LOG.error('Error creating policy "%s" rule "%s": %s',
policy_name, rule_name, str(e))
self.context['error'] = str(e)
return False
return True

View File

@ -1,94 +0,0 @@
# Copyright 2014 VMware.
#
# 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.
import logging
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import messages
from horizon import tables
from openstack_dashboard import policy
from congress_dashboard.api import congress
LOG = logging.getLogger(__name__)
def get_policy_link(datum):
return reverse('horizon:admin:policies:detail', args=(datum['name'],))
class CreatePolicy(tables.LinkAction):
name = 'create_policy'
verbose_name = _('Create Policy')
url = 'horizon:admin:policies:create'
classes = ('ajax-modal',)
icon = 'plus'
class DeletePolicy(policy.PolicyTargetMixin, tables.DeleteAction):
@staticmethod
def action_present(count):
return ungettext_lazy(
u'Delete Policy',
u'Delete Policies',
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u'Deleted policy',
u'Deleted policies',
count
)
redirect_url = 'horizon:admin:policies:index'
def delete(self, request, obj_id):
LOG.info('User %s deleting policy "%s" in tenant %s',
request.user.username, obj_id, request.user.tenant_name)
try:
congress.policy_delete(request, obj_id)
LOG.info('Deleted policy "%s"', obj_id)
except Exception as e:
msg_args = {'policy_id': obj_id, 'error': str(e)}
msg = _('Failed to delete policy "%(policy_id)s": '
'%(error)s') % msg_args
LOG.error(msg)
messages.error(request, msg)
redirect = reverse(self.redirect_url)
raise exceptions.Http302(redirect)
def allowed(self, request, policy=None):
# Only user policies can be deleted.
if policy:
return policy['owner_id'] == 'user'
return True
class PoliciesTable(tables.DataTable):
name = tables.Column("name", verbose_name=_("Name"), link=get_policy_link)
description = tables.Column("description", verbose_name=_("Description"))
kind = tables.Column("kind", verbose_name=_("Kind"))
owner_id = tables.Column("owner_id", verbose_name=_("Owner ID"))
class Meta(object):
name = "policies"
verbose_name = _("Policies")
table_actions = (CreatePolicy, DeletePolicy)
row_actions = (DeletePolicy,)

View File

@ -1,22 +0,0 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% load url from future %}
{% block form_id %}create_policy_form{% endblock %}
{% block form_action %}{% url 'horizon:admin:policies:create' %}{% endblock %}
{% block modal_id %}create_policy_modal{% endblock %}
{% block modal-header %}{% trans "Create Policy" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Policy" %}" />
<a href="{% url 'horizon:admin:policies:index' %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -1,18 +0,0 @@
{% load i18n %}
<h3>{% trans "Policy Overview" %}</h3>
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "Policy Name" %}</dt>
<dd>{{ policy.name|default:policy.id }}</dd>
<dt>{% trans "Policy ID" %}</dt>
<dd>{{ policy.id }}</dd>
<dt>{% trans "Description" %}</dt>
<dd>{{ policy.description }}</dd>
<dt>{% trans "Kind" %}</dt>
<dd>{{ policy.kind }}</dd>
<dt>{% trans "Owner ID" %}</dt>
<dd>{{ policy.owner_id|default:"-" }}</dd>
</dl>
</div>

View File

@ -1,11 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Policy" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create Policy") %}
{% endblock page_header %}
{% block main %}
{% include "admin/policies/_create.html" %}
{% endblock %}

View File

@ -1,20 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Policy Details" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Policy Details: ")|add:policy.name %}
{% endblock page_header %}
{% block main %}
{% include "admin/policies/_detail_overview.html" %}
<hr>
<div id="policy_rules">
{{ policy_rules_table.render }}
</div>
<div id="policies_tables">
{{ policies_tables_table.render }}
</div>
<span id="ds_tables" class="hidden">{{ tables }}</span>
<span id="ds_columns" class="hidden">{{ columns }}</span>
{% endblock %}

View File

@ -1,13 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Policies" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Policies") %}
{% endblock page_header %}
{% block main %}
<div id="policies">
{{ policies_table.render }}
</div>
{% endblock %}

View File

@ -1,174 +0,0 @@
<noscript><h3>{{ step }}</h3></noscript>
{{ step.get_help_text }}
{% include 'horizon/common/_form_errors.html' with form=form %}
<div class="row">
<div class="col-sm-12">
<table id="mappings_table" class="table table-condensed" data-count="{{ mappings_count }}">
<tr>
<th colspan="5">&nbsp;<input class="hidden" id="mappings" name="mappings" type="text" value="{{ form.mappings.value }}" /></th>
</tr>
{% include 'admin/policies/rules/_mapping_row.html' with form=form count=0 column='' value='' %}
{% for column, value in mappings %}{% include 'admin/policies/rules/_mapping_row.html' with form=form count=forloop.counter column=column value=value %}{% endfor %}
<tr>
<td colspan="5" class="borderless input-errors">
{% for error in form.mappings.errors %}
<span class="help-block alert alert-danger {{ form.error_css_class }}">{{ error }}</span>
{% endfor %}
</td>
</tr>
</table>
<table id="joins_table" class="table table-condensed">
<tr>
<td colspan="4">
<div class="form-group">
<label class="control-label">Only including rows where:</label>
</div>
</td>
</tr>
{% for left, op, right in joins %}<tr id="join_{{ forloop.counter0 }}">
<td class="ui-front borderless input-cell">
<div class="has-feedback ac">
<input id="join_left_{{ forloop.counter0 }}" class="form-control ac-columns" name="join_left_{{ forloop.counter0 }}" type="text" value="{{ left }}" placeholder="e.g. neutronv2:ports device_id" pattern="{{ column_pattern }}" title="{{ column_pattern_error }}" />
<div class="form-control-feedback">
<span class="caret"></span>
</div>
</div>
</td>
<td class="borderless operator-cell">
<select id="join_op_{{ forloop.counter0 }}" class="form-control join-op" name="join_op_{{ forloop.counter0 }}">
<option value=""{% if op == '' %} selected="selected"{% endif %}>has same value as</option>
<option value="="{% if op == '=' %} selected="selected"{% endif %}>equals</option>
</select>
</td>
<td class="ui-front borderless input-cell">
<div class="has-feedback ac">
<input id="join_right_{{ forloop.counter0 }}" class="form-control ac-columns join-right" name="join_right_{{ forloop.counter0 }}" type="text" value="{{ right }}" placeholder="e.g. {% if op %}8, active{% else %}nova:servers id" pattern="{{ column_pattern }}" title="{{ column_pattern_error }}{% endif %}" data-column-example="e.g. nova:servers id" data-static-example="e.g. 8, active" data-pattern="{{ column_pattern }}" data-pattern-error="{{ column_pattern_error }}" />
<div class="form-control-feedback{% if op != '' %} hidden{% endif %}">
<span class="caret"></span>
</div>
</div>
</td>
<td class="borderless">
<a class="{% if forloop.first %}hidden {% endif %}remove-join-button btn btn-xs btn-primary">&#8211;</a>
</td>
</tr>{% endfor %}
</table>
<a id="add_join_button" class="btn btn-xs btn-primary" data-count="{{ joins_count }}">&amp;</a>
<table id="negations_table" class="table table-condensed">
<tr>
<td colspan="4">
<div class="form-group">
<label class="control-label">Except for rows where:</label>
<span class="help-icon" data-toggle="tooltip" data-placement="top" title="To exclude a row in the output policy table, select the columns on the left whose values should not exist together in the columns selected on the right."><span class="fa fa-question-circle"></span></span>
</div>
</div>
</td>
</tr>
{% for value, column in negations %}<tr id="negation_{{ forloop.counter0 }}">
<td class="ui-front borderless input-cell">
<div class="has-feedback ac">
<input id="negation_value_{{ forloop.counter0 }}" class="form-control ac-columns" name="negation_value_{{ forloop.counter0 }}" value="{{ value }}" type="text" placeholder="e.g. nova:servers tenant_id" pattern="{{ column_pattern }}" title="{{ column_pattern_error }}" />
<div class="form-control-feedback">
<span class="caret"></span>
</div>
</div>
</td>
<td class="borderless operator-cell">value is in</td>
<td class="ui-front borderless input-cell">
<div class="has-feedback ac">
<input id="negation_column_{{ forloop.counter0 }}" class="form-control ac-columns" name="negation_column_{{ forloop.counter0 }}" type="text" value="{{ column }}" placeholder="e.g. neutronv2:ports tenant_id" pattern="{{ column_pattern }}" title="{{ column_pattern_error }}" />
<div class="form-control-feedback">
<span class="caret"></span>
</div>
</div>
</td>
<td class="borderless">
<a class="{% if forloop.first %}hidden {% endif %}remove-negation-button btn btn-xs btn-primary">&#8211;</a>
</td>
</tr>{% endfor %}
</table>
<a id="add_negation_button" class="btn btn-xs btn-primary" data-count="{{ negations_count }}">&amp;</a>
{% comment %}
<table id="aliases_table" class="table table-condensed">
<tr>
<th colspan="5">
<div class="form-group">
<label class="control-label">&nbsp;</label>
</div>
</th>
</tr>
{% for column, name in aliases %}<tr id="alias_{{ forloop.counter0 }}">
<td class="borderless label-cell">
{% if forloop.first %}<div class="form-group">
<label class="control-label">Table Aliases:</label>
<span class="help-icon" data-toggle="tooltip" data-placement="top" title="Give an alternate name for a table if more than one instance of it is needed above."><span class="fa fa-question-circle"></span></span>
</div>{% endif %}
</td>
<td class="ui-front borderless input-cell">
<div class="has-feedback ac">
<input id="alias_column_{{ forloop.counter0 }}" class="form-control ac-tables" name="alias_column_{{ forloop.counter0 }}" type="text" value="{{ column }}" placeholder="e.g. nova:servers" pattern="{{ table_pattern }}" title="{{ table_pattern_error }}" />
<div class="form-control-feedback">
<span class="caret"></span>
</div>
</div>
</td>
<td class="borderless alias-text">as</td>
<td class="borderless input-cell">
<input id="alias_name_{{ forloop.counter0 }}" class="form-control alias-name-input" name="alias_name_{{ forloop.counter0 }}" type="text" value="{{ name }}" placeholder="e.g. nova:servers2" />
</td>
<td class="borderless">
<a class="{% if forloop.first %}hidden {% endif %}remove-alias-button btn btn-xs btn-primary">&#8211;</a>
</td>
</tr>{% endfor %}
</table>
<a id="add_alias_button" class="btn btn-xs btn-primary" data-count="{{ aliases_count }}">+</a>
{% endcomment %}
</div>
</div>
<script>
/* Add autocompletion. */
$('.ac input.ac-tables').autocomplete({
minLength: 0,
source: JSON.parse($('#ds_tables').text())
});
$('.ac input.ac-columns').each(function() {
var $input = $(this);
var $control = $input.closest('td').find('.form-control-feedback');
if (!$control.hasClass('hidden')) {
$input.autocomplete({
minLength: 0,
source: JSON.parse($('#ds_columns').text())
});
}
});
$('.ac div.form-control-feedback').click(function() {
var $div = $(this);
var $input = $div.siblings('.ac-tables, .ac-columns');
/* Focus on list now so that clicking outside of it closes it. */
$input.autocomplete('search', '').focus();
});
/* Combine mapping columns into single param. */
$('#mappings_table').closest('form').submit(function() {
var columns = [];
var incomplete = false;
$('#mappings_table').find('.mapping-column-input').not('#mapping_column_0')
.each(function() {
/* All values are required. */
if (incomplete) {
return;
}
var $input = $(this);
var name = $input.val();
if (name) {
columns.push(name);
} else {
incomplete = true;
columns = [];
return;
}
});
$('#mappings').val(columns.join(', '));
});
</script>

View File

@ -1,65 +0,0 @@
<noscript><h3>{{ step }}</h3></noscript>
{{ step.get_help_text }}
{% include 'horizon/common/_form_errors.html' with form=form %}
<div class="row">
<div class="col-sm-6">
<div class="form-group{% if form.rule_name.errors %} has-error{% endif %} {{ form.rule_name.css_classes }}">
<label class="control-label{% if form.rule_name.field.required %} {{ form.required_css_class }}{% endif %}" for="rule_name">{{ form.rule_name.label }}</label>
{% if form.rule_name.help_text %}
<span class="help-icon" data-toggle="tooltip" data-placement="top" title="{{ form.rule_name.help_text|safe }}"><span class="fa fa-question-circle"></span></span>
{% endif %}
<input class="form-control" id="rule_name" maxlength="{{ form.rule_name.field.max_length }}" name="rule_name" type="text" value="{{ form.rule_name.value }}" />
{% for error in form.rule_name.errors %}
<span class="help-block alert alert-danger {{ form.error_css_class }}">{{ error }}</span>
{% endfor %}
</div>
<div class="form-group{% if form.comment.errors %} has-error{% endif %} {{ form.comment.css_classes }}">
<label class="control-label{% if form.comment.field.required %} {{ form.required_css_class }}{% endif %}" for="comment">{{ form.comment.label }}</label>
{% if form.comment.help_text %}
<span class="help-icon" data-toggle="tooltip" data-placement="top" title="{{ form.comment.help_text|safe }}"><span class="fa fa-question-circle"></span></span>
{% endif %}
<textarea class="form-control" cols="40" id="comment" name="comment" rows="4">{{ form.comment.value }}</textarea>
{% for error in form.comment.errors %}
<span class="help-block alert alert-danger {{ form.error_css_class }}">{{ error }}</span>
{% endfor %}
</div>
<div class="form-group{% if form.policy_table.errors %} has-error{% endif %} {{ form.policy_table.css_classes }}">
<label class="control-label{% if form.policy_table.field.required %} {{ form.required_css_class }}{% endif %}" for="policy_table">{{ form.policy_table.label }}</label>
{% if form.policy_table.help_text %}
<span class="help-icon" data-toggle="tooltip" data-placement="top" title="{{ form.policy_table.help_text|safe }}"><span class="fa fa-question-circle"></span></span>
{% endif %}
<input class="form-control" id="policy_table" maxlength="{{ form.policy_table.field.max_length }}" name="policy_table" type="text" value="{{ form.policy_table.value }}" placeholder="e.g. error" pattern="[^0-9].*" title="Name cannot begin with a number" />
{% for error in form.policy_table.errors %}
<span class="help-block alert alert-danger {{ form.error_css_class }}">{{ error }}</span>
{% endfor %}
</div>
<div class="form-group{% if form.policy_columns.errors %} has-error{% endif %} {{ form.policy_columns.css_classes }}">
<label class="control-label{% if form.policy_columns.field.required %} {{ form.required_css_class }}{% endif %}" for="policy_columns">{{ form.policy_columns.label }}</label>
{% if form.policy_columns.help_text %}
<span class="help-icon" data-toggle="tooltip" data-placement="top" title="{{ form.policy_columns.help_text|safe }}"><span class="fa fa-question-circle"></span></span>
{% endif %}
<input class="hidden" id="policy_columns" name="policy_columns" type="text" value="{{ form.policy_columns.value }}" />
<table id="policy_columns_table" class="table table-condensed">
{% for column in policy_columns_list %}<tr id="policy_column_{{ forloop.counter0 }}">
<td class="borderless input-cell">
<input class="form-control policy-column-input" name="policy_column_{{ forloop.counter0 }}" type="text" value="{{ column }}" placeholder="e.g. name" pattern="[^0-9].*" title="Name cannot begin with a number" />
</td>
<td class="borderless button-cell">
<a class="{% if forloop.first %}hidden {% endif %}remove-policy-column-button btn btn-xs btn-primary">&#8211;</a>
</td>
</tr>{% endfor %}
<tr>
<td colspan="2" class="borderless input-errors">
{% for error in form.policy_columns.errors %}
<span class="help-block alert alert-danger {{ form.error_css_class }}">{{ error }}</span>
{% endfor %}
</td>
</tr>
</table>
<a id="add_policy_column_button" class="btn btn-xs btn-primary" data-count="{{ policy_columns_count }}">+</a>
</div>
</div>
</div>

View File

@ -1,21 +0,0 @@
<tr id="mapping_{{ count }}" class="{% if count == 0 %}hidden{% else %}mapping-row{% endif %}">
<td class="borderless label-cell">
{% if count <= 1 %}<div class="form-group{% if count == 1 and form.mappings.errors %} has-error{% endif %} {{ form.mappings.css_classes }}">
<label class="control-label{% if form.mappings.field.required %} {{ form.required_css_class }}{% endif %}" for="policy_columns">{{ form.mappings.label }}</label>
{% if form.mappings.help_text %}
<span class="help-icon" data-toggle="tooltip" data-placement="top" title="{{ form.mappings.help_text|safe }}"><span class="fa fa-question-circle"></span></span>
{% endif %}
</div>{% endif %}
</td>
<td class="borderless policy-column-name">{{ column }}</td>
<td class="borderless mapping-text">maps to</td>
<td class="ui-front borderless input-cell">
<div class="has-feedback ac">
<input id="mapping_column_{{ count }}" class="form-control ac-columns mapping-column-input" name="mapping_column_{{ count }}" type="text" placeholder="e.g. nova:servers name" value="{{ value }}" />
<div class="form-control-feedback">
<span class="caret"></span>
</div>
</div>
</td>
<td class="borderless"></td>
</tr>

View File

@ -1,24 +0,0 @@
{% extends 'horizon/common/_workflow.html' %}
{% load i18n %}
{% block modal-footer %}
{% if workflow.wizard %}
<div class="row">
<div class="col-sm-1">
<a href="{% url 'horizon:admin:policies:detail' policy_name %}" class="btn btn-default secondary cancel">{% trans "Cancel" %}</a>
</div>
<div class="col-sm-5 back">
<button type="button" class="btn btn-default button-previous">&laquo; {% trans "Back" %}</button>
</div>
<div class="col-sm-6 next">
<button type="button" class="btn btn-primary button-next">{% trans "Next" %} &raquo;</button>
<button type="submit" class="btn btn-primary button-final">{{ workflow.finalize_button_name }}</button>
</div>
</div>
{% else %}
<input class="btn btn-primary pull-right" type="submit" value="{{ workflow.finalize_button_name }}" />
{% if modal %}<a class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>{% endif %}
{% endif %}
{% endblock %}

View File

@ -1,36 +0,0 @@
# Copyright 2014 VMware.
#
# 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 django.conf.urls import patterns
from django.conf.urls import url
from congress_dashboard.datasources import views as data_views
from congress_dashboard.policies.rules import views as rule_views
from congress_dashboard.policies import views
POLICY = r'^(?P<policy_name>[^/]+)/%s$'
POLICYTABLE = r'^(?P<datasource_id>[^/]+)/(?P<policy_table_name>[^/]+)/%s$'
urlpatterns = patterns(
'',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^create/$', views.CreateView.as_view(), name='create'),
url(POLICY % 'detail', views.DetailView.as_view(), name='detail'),
url(POLICYTABLE % 'detail', data_views.DetailView.as_view(),
name='policy_table_detail'),
url(POLICY % 'rules/create',
rule_views.CreateView.as_view(), name='create_rule'),
)

View File

@ -1,157 +0,0 @@
# Copyright 2014 VMware.
#
# 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.
import json
import logging
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.template.defaultfilters import dictsort
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from horizon import tables
from congress_dashboard.api import congress
import congress_dashboard.datasources.utils as ds_utils
from congress_dashboard.policies import forms as policies_forms
from congress_dashboard.policies.rules import tables as rules_tables
from congress_dashboard.policies import tables as policies_tables
LOG = logging.getLogger(__name__)
class IndexView(tables.DataTableView):
"""List policies."""
table_class = policies_tables.PoliciesTable
template_name = 'admin/policies/index.html'
def get_data(self):
try:
policies = congress.policies_list(self.request)
except Exception as e:
msg = _('Unable to get policies list: %s') % str(e)
LOG.error(msg)
messages.error(self.request, msg)
return []
return policies
class CreateView(forms.ModalFormView):
form_class = policies_forms.CreatePolicy
template_name = 'admin/policies/create.html'
success_url = reverse_lazy('horizon:admin:policies:index')
class DetailView(tables.MultiTableView):
"""List details about and rules in a policy."""
table_classes = (rules_tables.PolicyRulesTable,
rules_tables.PoliciesTablesTable,)
template_name = 'admin/policies/detail.html'
def get_policies_tables_data(self):
policy_name = self.kwargs['policy_name']
try:
policy_tables = congress.policy_tables_list(self.request,
policy_name)
except Exception as e:
msg_args = {'policy_name': policy_name, 'error': str(e)}
msg = _('Unable to get tables list for policy '
'"%(policy_name)s": %(error)s') % msg_args
messages.error(self.request, msg)
return []
for pt in policy_tables:
pt.set_id_as_name_if_empty()
pt.set_value('policy_name', policy_name)
# Object ids within a Horizon table must be unique. Otherwise,
# Horizon will cache the column values for the object by id and
# use the same column values for all rows with the same id.
pt.set_value('table_id', pt['id'])
pt.set_value('id', '%s-%s' % (policy_name, pt['table_id']))
return policy_tables
def get_policy_rules_data(self):
policy_name = self.kwargs['policy_name']
try:
policy_rules = congress.policy_rules_list(self.request,
policy_name)
except Exception as e:
msg_args = {'policy_name': policy_name, 'error': str(e)}
msg = _('Unable to get rules in policy "%(policy_name)s": '
'%(error)s') % msg_args
LOG.error(msg)
messages.error(self.request, msg)
redirect = reverse('horizon:admin:policies:index')
raise exceptions.Http302(redirect)
for r in policy_rules:
r.set_id_as_name_if_empty()
return policy_rules
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
policy_name = kwargs['policy_name']
try:
policy = congress.policy_get(self.request, policy_name)
except Exception as e:
msg_args = {'policy_name': policy_name, 'error': str(e)}
msg = _('Unable to get policy "%(policy_name)s": '
'%(error)s') % msg_args
LOG.error(msg)
messages.error(self.request, msg)
redirect = reverse('horizon:admin:policies:index')
raise exceptions.Http302(redirect)
context['policy'] = policy
# Alphabetize and convert list of data source tables and columns into
# JSON formatted string consumable by JavaScript. Do this here instead
# of in the Create Rule form so that the tables and columns lists
# appear in the HTML document before the JavaScript that uses them.
all_tables = ds_utils.get_datasource_tables(self.request)
sorted_datasources = dictsort(all_tables, 'datasource')
tables = []
for ds in sorted_datasources:
datasource_tables = ds['tables']
datasource_tables.sort()
for table in ds['tables']:
tables.append('%s%s%s' % (ds['datasource'],
congress.TABLE_SEPARATOR, table))
context['tables'] = json.dumps(tables)
datasource_columns = ds_utils.get_datasource_columns(self.request)
sorted_datasources = dictsort(datasource_columns, 'datasource')
columns = []
for ds in sorted_datasources:
sorted_tables = dictsort(ds['tables'], 'table')
for tbl in sorted_tables:
# Ignore service-derived tables, which are already included.
if congress.TABLE_SEPARATOR in tbl['table']:
continue
table_columns = tbl['columns']
if table_columns:
table_columns.sort()
else:
# Placeholder name for column when the table has none.
table_columns = ['_']
for column in table_columns:
columns.append('%s%s%s %s' % (ds['datasource'],
congress.TABLE_SEPARATOR,
tbl['table'], column))
context['columns'] = json.dumps(columns)
return context

View File

@ -1,134 +0,0 @@
/* tables */
#policy_columns_table {
margin-bottom: 5px;
}
#policy_columns_table td.input-cell {
width: 94%;
padding-left: 0;
}
#policy_columns_table td.button-cell {
padding: 0;
text-align: right;
}
#policy_columns_table td.input-errors,
#mappings_table td.input-errors {
padding: 0;
}
#policy_columns_table td.borderless,
#mappings_table td.borderless,
#joins_table td.borderless,
#negations_table td.borderless,
#aliases_table td.borderless {
border: none;
}
#mappings_table td.input-cell,
#joins_table td.input-cell,
#negations_table td.input-cell,
#aliases_table td.input-cell {
width: 36%;
}
#mappings_table td.label-cell {
width: 22%;
}
#mappings_table td.policy-column-name {
width: 28%;
text-align: right;
font-style: italic;
}
#mappings_table td.mapping-text {
width: 10%;
text-align: center;
}
#joins_table,
#negations_table,
#aliases_table {
margin-top: 30px;
margin-bottom: 5px;
}
#joins_table td.operator-cell,
#negations_table td.operator-cell {
width: 24%;
text-align: center;
}
#aliases_table td.label-cell {
width: 19%;
}
#aliases_table td.alias-text {
width: 5%;
text-align: center;
}
/* forms */
#mappings_table div.form-group,
#joins_table div.form-group,
#negations_table div.form-group,
#aliases_table div.form-group {
margin-bottom: 0;
}
#mappings_table input.form-control,
#joins_table input.form-control,
#negations_table input.form-control,
#aliases_table input.form-control {
padding-right: 36px;
}
#mappings_table div.form-control-feedback,
#joins_table div.form-control-feedback,
#negations_table div.form-control-feedback,
#aliases_table div.form-control-feedback {
background-color: #DDDDDD;
width: 20px;
height: 24px;
top: 5px;
right: 5px;
}
#mappings_table span.caret,
#joins_table span.caret,
#negations_table span.caret,
#aliases_table span.caret {
border-width: 5px;
color: #333333;
margin-bottom: 10px;
}
#add_join_button,
#add_negation_button,
#add_alias_button {
margin-left: 5px;
}
/* autocompletion */
.ui-autocomplete {
max-height: 200px;
overflow-y: auto;
/* prevent horizontal scrollbar */
overflow-x: hidden;
}
/* IE 6 doesn't support max-height
* we use height instead, but this forces the menu to always be this tall
*/
* html .ui-autocomplete {
height: 200px;
}
.ui-widget {
font-family: inherit;
font-size: inherit;
}
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus,
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active {
border: 1px solid #285e8e;
background: none;
background-color: #3276b1;
font-weight: normal;
color: #ffffff;
}

View File

@ -1,288 +0,0 @@
horizon.policies = {
/* Update input attributes for column name autocompletion. */
updateColumnAcInput: function($input) {
$input.attr({
'placeholder': $input.attr('data-column-example'),
'pattern': $input.attr('data-pattern'),
'title': $input.attr('data-pattern-error')
});
/* form-control-feedback only hidden, so it still has autocompletion. */
$input.closest('td').find('.form-control-feedback')
.removeClass('hidden');
},
/* Get column names from conditions mappings. */
getMappedColumns: function() {
var mappings = [];
$('#mappings_table').find('.policy-column-name').each(function() {
var $td = $(this);
var column = $td.text();
if (column) {
mappings.push(column);
}
});
return mappings;
},
/* Check if any columns need to be removed from conditions mappings. */
scrubMappedColumns: function(columns) {
mappings = horizon.policies.getMappedColumns();
if (!columns) {
columns = [];
var $inputs = $('#policy_columns_table').find('.policy-column-input');
$inputs.each(function() {
var $input = $(this);
var name = $input.val();
if (name) {
columns.push(name);
}
});
}
for (var i = 0; i < mappings.length; i++) {
var name = mappings[i];
if ($.inArray(name, columns) == -1) {
$('#mappings_table').find('.policy-column-name:contains(' +
name + ')').closest('tr').remove();
}
}
/* Put label back if there's only one row left without it. */
var $rows = $('#mappings_table').find('.mapping-row');
if ($rows.length == 1 && !$rows.find('.label-cell').text()) {
var label = $('#mapping_0').find('.label-cell').html();
$rows.find('.label-cell').html(label);
}
},
}
horizon.addInitFunction(horizon.policies.init = function() {
/* Add another policy table column name. */
$(document).on('click', '#add_policy_column_button', function(evt) {
evt.preventDefault();
var $button = $(this);
var $tr = $('#policy_column_0').clone();
var count = $button.attr('data-count');
var cid = parseInt(count);
$button.attr('data-count', cid + 1);
/* Change ids and reset inputs. */
$tr.attr('id', 'policy_column_' + cid);
$tr.find('input[name]').val('').each(function() {
this.name = this.name.replace(/^(.+_)\d+$/, '$1' + cid);
});
$tr.find('.remove-policy-column-button').removeClass('hidden');
/* Add row before the one reserved for errors. */
$('#policy_columns_table').find('tr:last').before($tr);
});
/* Remove policy table column name input. */
$(document).on('click',
'#policy_columns_table a.remove-policy-column-button',
function(evt) {
evt.preventDefault();
var $a = $(this);
var $tr = $a.closest('tr');
$tr.remove();
horizon.policies.scrubMappedColumns();
});
/* Add policy table columns to conditions and combine into single param. */
$(document).on('change',
'#policy_columns_table input.policy-column-input',
function() {
var mappings = horizon.policies.getMappedColumns();
var columns = [];
var $inputs = $('#policy_columns_table').find('.policy-column-input');
$inputs.each(function() {
var $input = $(this);
var name = $input.val();
/* Does not make sense to have multiple of the same column. */
if (name && $.inArray(name, columns) == -1) {
columns.push(name);
if ($.inArray(name, mappings) == -1) {
/* Add mapping inputs for new policy column. */
var $tr = $('#mapping_0').clone();
var count = $('#mappings_table').attr('data-count');
var cid = parseInt(count);
$('#mappings_table').attr('data-count', cid + 1);
/* Change ids. */
$tr.attr('id', 'mapping_' + cid).toggleClass('hidden mapping-row');
$tr.find('.policy-column-name').text(name);
$tr.find('input[id]').each(function() {
this.id = this.id.replace(/^(.+_)\d+$/, '$1' + cid);
this.name = this.id;
});
/* Remove label if there's already a row with it. */
if ($('#mappings_table').find('.mapping-row').length) {
$tr.find('.label-cell').empty();
}
$('#mappings_table').find('tr:last').before($tr);
/* Add autocompletion. */
$('#mapping_column_' + cid).autocomplete({
minLength: 0,
source: JSON.parse($('#ds_columns').text())
});
$('#mapping_' + cid).find('.ac div.form-control-feedback')
.click(function() {
/* Focus on list now so that clicking outside of it closes it. */
$('#mapping_column_' + cid).autocomplete('search', '').focus();
});
}
}
});
/* Workflow expects one policy_columns value. */
$('#policy_columns').val(columns.join(', '));
horizon.policies.scrubMappedColumns(columns);
});
/* Add another join. */
$(document).on('click', '#add_join_button', function(evt) {
evt.preventDefault();
var $button = $(this);
var $tr = $('#join_0').clone();
var count = $button.attr('data-count');
var cid = parseInt(count);
$button.attr('data-count', cid + 1);
/* Change ids and reset inputs. */
$tr.attr('id', 'join_' + cid);
$tr.find('input[id], select[id]').val('').each(function() {
this.id = this.id.replace(/^(.+_)\d+$/, '$1' + cid);
this.name = this.id;
});
$tr.find('select').val($tr.find('option:first').val());
$tr.find('.remove-join-button').removeClass('hidden');
$('#joins_table').append($tr);
/* Add autocompletion. */
$('#join_left_' + cid + ', #join_right_' + cid).autocomplete({
minLength: 0,
source: JSON.parse($('#ds_columns').text())
});
horizon.policies.updateColumnAcInput($('#join_right_' + cid));
$('#join_' + cid).find('.ac div.form-control-feedback').click(function() {
var $div = $(this);
/* Focus on list now so that clicking outside of it closes it. */
$div.siblings('.ac-columns').autocomplete('search', '').focus();
});
});
/* Remove join input. */
$(document).on('click', '#joins_table a.remove-join-button',
function(evt) {
evt.preventDefault();
var $a = $(this);
var $tr = $a.closest('tr');
$tr.remove();
});
/* Update input attributes based on type selected. */
$(document).on('change', '#joins_table select.join-op', function() {
var $select = $(this);
var $input = $select.closest('tr').find('.join-right').val('');
if (!$select.val()) {
$input.autocomplete({
minLength: 0,
source: JSON.parse($('#ds_columns').text())
});
horizon.policies.updateColumnAcInput($input);
} else {
$input.closest('td').find('.form-control-feedback').addClass('hidden');
$input.autocomplete('destroy');
$input.attr('placeholder', $input.attr('data-static-example'));
$input.removeAttr('pattern').removeAttr('title');
}
});
/* Add another negation. */
$(document).on('click', '#add_negation_button', function(evt) {
evt.preventDefault();
var $button = $(this);
var $tr = $('#negation_0').clone();
var count = $button.attr('data-count');
var cid = parseInt(count);
$button.attr('data-count', cid + 1);
/* Change ids and reset inputs. */
$tr.attr('id', 'negation_' + cid);
$tr.find('input[id], select[id]').val('').each(function() {
this.id = this.id.replace(/^(.+_)\d+$/, '$1' + cid);
this.name = this.id;
});
$tr.find('select').val($tr.find('option:first').val());
$tr.find('.remove-negation-button').removeClass('hidden');
$('#negations_table').append($tr);
/* Add autocompletion. */
$('#negation_value_' + cid + ', #negation_column_' + cid).autocomplete({
minLength: 0,
source: JSON.parse($('#ds_columns').text())
});
$('#negation_' + cid).find('.ac div.form-control-feedback')
.click(function() {
var $div = $(this);
/* Focus on list now so that clicking outside of it closes it. */
$div.siblings('.ac-columns').autocomplete('search', '').focus();
});
});
/* Remove negation input. */
$(document).on('click', '#negations_table a.remove-negation-button',
function(evt) {
evt.preventDefault();
var $a = $(this);
var $tr = $a.closest('tr');
$tr.remove();
});
/* Add another alias. */
$(document).on('click', '#add_alias_button', function(evt) {
evt.preventDefault();
var $button = $(this);
var $tr = $('#alias_0').clone();
var count = $button.attr('data-count');
var cid = parseInt(count);
$button.attr('data-count', cid + 1);
/* Change ids and reset inputs. */
$tr.attr('id', 'alias_' + cid);
$tr.find('td:first').empty();
$tr.find('input[id]').val('').each(function() {
this.id = this.id.replace(/^(.+_)\d+$/, '$1' + cid);
this.name = this.id;
});
$tr.find('.remove-alias-button').removeClass('hidden');
$('#aliases_table').append($tr);
/* Add autocompletion. */
$('#alias_column_' + cid).autocomplete({
minLength: 0,
source: JSON.parse($('#ds_tables').text())
});
$('#alias_' + cid).find('.ac div.form-control-feedback')
.click(function() {
var $div = $(this);
/* Focus on list now so that clicking outside of it closes it. */
$div.siblings('.ac-tables').autocomplete('search', '').focus();
});
});
/* Remove alias input. */
$(document).on('click', '#aliases_table a.remove-alias-button',
function(evt) {
evt.preventDefault();
var $a = $(this);
var $tr = $a.closest('tr');
$tr.remove();
});
});

View File

@ -1,5 +0,0 @@
{% extends 'horizon/_scripts.html' %}
{% block custom_js_files %}
<script src='{{ STATIC_URL }}admin/js/policies.js' type='text/javascript' charset='utf-8'></script>
{% endblock %}

View File

@ -1,14 +0,0 @@
{% extends 'base.html' %}
{% block css %}
{% include "_stylesheets.html" %}
{% load compress %}
{% compress css %}
<link href='{{ STATIC_URL }}admin/css/policies.css' type='text/css' media='screen' rel='stylesheet' />
{% endcompress %}
{% endblock %}
{% block js %}
{% include "admin/_scripts.html" %}
{% endblock %}