Merge "Add Developer Dashboard + Bootstrap Theme Preview"

This commit is contained in:
Jenkins 2015-12-08 05:09:12 +00:00 committed by Gerrit Code Review
commit 4a826b58b6
22 changed files with 1724 additions and 12 deletions

View File

@ -0,0 +1,26 @@
# Copyright 2015 Cisco Systems, 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 django.utils.translation import ugettext_lazy as _
import horizon
class Developer(horizon.Dashboard):
name = _("Developer")
slug = "developer"
default_panel = "theme_preview"
horizon.register(Developer)

View File

@ -0,0 +1,45 @@
/*
* (c) Copyright 2015 Cisco Systems, 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.
*/
(function () {
'use strict';
/**
* @ngdoc module
* @ngname horizon.dashboard.developer
* @description
* Dashboard module to host developer panels.
*/
angular
.module('horizon.dashboard.developer', [
'horizon.dashboard.developer.theme-preview'
])
.config(config);
config.$inject = [
'$provide',
'$windowProvider'
];
/**
* @name horizon.dashboard.developer.basePath
* @description Base path for the developer dashboard
*/
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/developer/';
$provide.constant('horizon.dashboard.developer.basePath', path);
}
})();

View File

@ -0,0 +1,42 @@
/*
* (c) Copyright 2015 Cisco Systems, 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.
*/
(function () {
'use strict';
describe('horizon.dashboard.developer', function () {
it('should be defined', function () {
expect(angular.module('horizon.dashboard.developer')).toBeDefined();
});
});
describe('horizon.dashboard.developer.basePath constant', function () {
var developerBasePath, staticUrl;
beforeEach(module('horizon.dashboard.developer'));
beforeEach(inject(function ($injector) {
developerBasePath = $injector.get('horizon.dashboard.developer.basePath');
staticUrl = $injector.get('$window').STATIC_URL;
}));
it('should be defined', function () {
expect(developerBasePath).toBeDefined();
});
it('should equal to "/static/dashboard/developer/"', function () {
expect(developerBasePath).toEqual(staticUrl + 'dashboard/developer/');
});
});
})();

View File

@ -0,0 +1,2 @@
// Top level file for Developer dashboard SCSS
@import "theme-preview/theme-preview";

View File

@ -0,0 +1,101 @@
/*
* (c) Copyright 2015 Cisco Systems, 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.
*/
(function() {
'use strict';
angular
.module('horizon.dashboard.developer.theme-preview')
.directive('themepreview', themePreview);
themePreview.$inject = ['horizon.dashboard.developer.basePath'];
/**
* @ngdoc directive
* @name themepreview
* @description
* Wraps the JS code for displaying the theme preview page. Shouldnt be used elsewhere.
*/
function themePreview(path) {
var directive = {
restrict: 'E',
templateUrl: path + 'theme-preview/theme-preview.html',
link: link
};
return directive;
}
function link(scope, element) {
//TODO(tqtran) Use angular, not jquery
$('a[href="#"]').click(function(e) {
e.preventDefault();
});
var $modal = $('#source-modal');
var $pre = $modal.find('pre');
var $button = $('<div id="source-button" class="btn btn-primary btn-xs"><span class="fa fa-code"></span></div>')
.click(function(){
var $fragment = stripAngular($(this).parent().clone());
var html = cleanSource($fragment.html());
$pre.text(html);
$modal.modal();
});
var $component = $('.bs-component');
$component.find('[data-toggle="popover"]').popover();
$component.find('[data-toggle="tooltip"]').tooltip();
$component.hover(function() {
$(this).append($button);
$button.show();
});
}
// Utility function to clean up the source code before displaying
function stripAngular($frag) {
var $translated = $frag.find('[translate]')
.removeAttr('translate');
$translated.html($translated.find('> span').html());
$frag.find('.ng-scope').removeClass('ng-scope');
$frag.find('.ng-pristine').removeClass('ng-pristine');
$frag.find('.ng-valid').removeClass('ng-valid');
$frag.find('input').removeAttr('style');
return $frag;
}
// Utility function to clean up the source code before displaying
function cleanSource(html) {
var lines = html.split(/\n/);
lines.shift();
lines.splice(-1, 1);
var indentSize = lines[0].length - lines[0].trim().length;
var re = new RegExp(' {' + indentSize + '}');
lines = lines.map(function(line) {
if (line.match(re)) {
line = line.substring(indentSize);
}
return line;
});
lines = lines.join('\n');
return lines;
}
})();

View File

@ -0,0 +1,28 @@
/*
* (c) Copyright 2015 Cisco Systems, 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.
*/
(function () {
'use strict';
/**
* @ngdoc module
* @ngname horizon.dashboard.developer.theme-preview
* @description
* Dashboard module for the bootstrap theme preview panel.
*/
angular
.module('horizon.dashboard.developer.theme-preview', [])
})();

View File

@ -0,0 +1,25 @@
/*
* (c) Copyright 2015 Cisco Systems, 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.
*/
(function () {
'use strict';
describe('horizon.dashboard.developer.theme-preview', function () {
it('should be defined', function () {
expect(angular.module('horizon.dashboard.developer.theme-preview')).toBeDefined();
});
});
})();

View File

@ -0,0 +1,53 @@
@import "/custom/variables";
themepreview {
#source-button {
font-weight: 700;
position: absolute;
right: 0;
top: 0;
z-index: 100;
}
.bs-component {
position: relative;
}
.bs-component .modal-dialog {
width: 90%;
}
.bs-component .modal {
bottom: auto;
display: block;
left: auto;
position: relative;
right: auto;
top: auto;
z-index: 1;
}
.bs-component .popover {
display: inline-block;
margin: 20px;
position: relative;
width: 220px;
}
.left .tooltip-inner {
float: right;
}
#nav-tabs .nav-tabs {
margin-bottom: 15px;
}
.nav-pills.nav-pills-stacked {
max-width: 300px;
}
.navbar {
margin-bottom: $padding-large-horizontal*2;
}
}

View File

@ -0,0 +1,27 @@
# Copyright 2015 Cisco Systems, 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.
import horizon
from horizon import base
from openstack_dashboard.test import helpers as test
class DeveloperTests(test.TestCase):
# Tests are run with DEBUG=False, so check that dashboard isn't registered
def test_registration_failure(self):
with self.assertRaises(base.NotRegistered):
horizon.get_dashboard("developer")

View File

@ -0,0 +1,22 @@
# Copyright 2015 Cisco Systems, 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 django.utils.translation import ugettext_lazy as _
import horizon
class Preview(horizon.Panel):
name = _("Bootstrap Theme Preview")
slug = 'theme_preview'

View File

@ -0,0 +1,14 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}
{% trans "Theme Preview" %}
{% endblock %}
{% block page_header %}
<h1>{{ skin }} <small>{{ skin_desc }}</small></h1>
{% endblock %}
{% block main %}
<themepreview></themepreview>
{% endblock %}

View File

@ -0,0 +1,45 @@
# Copyright 2015 Cisco Systems, 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 django.conf import settings
from django.core import urlresolvers
from django.core.urlresolvers import reverse
from django.utils.importlib import import_module # noqa
from horizon import base
from openstack_dashboard.contrib.developer.dashboard import Developer
from openstack_dashboard.test import helpers as test
class ThemePreviewTests(test.TestCase):
# Manually register Developer Dashboard, as DEBUG=False in tests
def setUp(self):
super(ThemePreviewTests, self).setUp()
urlresolvers.clear_url_caches()
reload(import_module(settings.ROOT_URLCONF))
base.Horizon.register(Developer)
base.Horizon._urls()
def tearDown(self):
super(ThemePreviewTests, self).tearDown()
base.Horizon.unregister(Developer)
base.Horizon._urls()
def test_index(self):
index = reverse('horizon:developer:theme_preview:index')
res = self.client.get(index)
self.assertTemplateUsed(res, 'developer/theme_preview/index.html')

View File

@ -0,0 +1,24 @@
# Copyright 2015 Cisco Systems, 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 django.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.contrib.developer.theme_preview import views
urlpatterns = patterns(
'openstack_dashboard.contrib.developer.theme_preview.views',
url(r'^$', views.IndexView.as_view(), name='index'),
)

View File

@ -0,0 +1,34 @@
# Copyright 2015 Cisco Systems, 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.
import logging
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from horizon import views
LOG = logging.getLogger(__name__)
class IndexView(views.HorizonTemplateView):
template_name = 'developer/theme_preview/index.html'
page_title = _("Bootstrap Theme Preview")
def get_context_data(self, **kwargs):
theme_path = settings.CUSTOM_THEME_PATH
context = super(IndexView, self).get_context_data(**kwargs)
context['skin'] = theme_path.split('/')[-1]
context['skin_desc'] = theme_path
return context

View File

@ -0,0 +1,36 @@
# Copyright 2015 Cisco Systems, 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 django.conf import settings
DASHBOARD = 'developer'
ADD_ANGULAR_MODULES = [
'horizon.dashboard.developer'
]
ADD_INSTALLED_APPS = [
'openstack_dashboard.contrib.developer'
]
ADD_SCSS_FILES = [
'dashboard/developer/developer.scss',
]
AUTO_DISCOVER_STATIC_FILES = True
DISABLED = True
if getattr(settings, 'DEBUG', False):
DISABLED = False

View File

@ -0,0 +1,21 @@
# Copyright 2015 Cisco Systems, 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.
PANEL = 'theme_preview'
PANEL_GROUP = 'default'
PANEL_DASHBOARD = 'developer'
ADD_PANEL = \
'openstack_dashboard.contrib.developer.theme_preview.panel.Preview'

View File

@ -327,6 +327,18 @@ if os.path.exists(os.path.join(CUSTOM_THEME, 'img')):
# specs files and external templates.
find_static_files(HORIZON_CONFIG)
# Ensure that we always have a SECRET_KEY set, even when no local_settings.py
# file is present. See local_settings.py.example for full documentation on the
# horizon.utils.secret_key module and its use.
if not SECRET_KEY:
if not LOCAL_PATH:
LOCAL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'local')
from horizon.utils import secret_key
SECRET_KEY = secret_key.generate_or_read_from_file(os.path.join(LOCAL_PATH,
'.secret_key_store'))
# Load the pluggable dashboard settings
import openstack_dashboard.enabled
import openstack_dashboard.local.enabled
@ -343,18 +355,6 @@ settings.update_dashboards(
)
INSTALLED_APPS[0:0] = ADD_INSTALLED_APPS
# Ensure that we always have a SECRET_KEY set, even when no local_settings.py
# file is present. See local_settings.py.example for full documentation on the
# horizon.utils.secret_key module and its use.
if not SECRET_KEY:
if not LOCAL_PATH:
LOCAL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'local')
from horizon.utils import secret_key
SECRET_KEY = secret_key.generate_or_read_from_file(os.path.join(LOCAL_PATH,
'.secret_key_store'))
from openstack_auth import policy
POLICY_CHECK_FUNCTION = policy.check

View File

@ -35,6 +35,8 @@ TEMPLATE_DIRS = (
os.path.join(TEST_DIR, 'templates'),
)
CUSTOM_THEME_PATH = 'themes/default'
TEMPLATE_CONTEXT_PROCESSORS += (
'openstack_dashboard.context_processors.openstack',
)

View File

@ -0,0 +1,8 @@
---
features:
- Added the Developer dashboard plugin to contrib. This runs when
``DEBUG=True``, and adds tooling to the UI to aid in development.
- Added the Bootstrap Theme Preview panel to the Developer dashboard. This
panel contains a list of Bootstrap components with source code, so that
developers can see examples of how to structure this code and the
effects their theme will have upon it.