Fix bug where WEBROOT is not respected

The WEB_ROOT setting needs to be respected rather than assuming / as
the root of the application. Simiarly, we can not assume that STATIC_URL
is a sub url of the WEB_ROOT. They can be configured as two indepedent url.

Angular templates are loaded dynamically with Ajax, which requires an
absolute base path. We define those base paths as angular constants for
template use and future routing.

TO TEST, follow directions at:
  http://docs.openstack.org/developer/horizon/topics/settings.html#webroot

Co-Authored-By: Matt Borland <matt.borland@hp.com>
Co-Authored-By: Shaoquan Chen <sean.chen2@hp.com>
Co-Authored-By: Thai Tran <tqtran@us.ibm.com>

Change-Id: Ifcd459633682edc94b270019ce77e17d64bea22d
Closes-Bug: #1451681
This commit is contained in:
Thai Tran 2015-07-21 10:45:40 -07:00 committed by Shaoquan Chen
parent ede7402604
commit 35e47358f0
19 changed files with 117 additions and 77 deletions

View File

@ -7,16 +7,20 @@
'horizon.framework.util',
'horizon.framework.widgets'
])
.constant('horizon.framework.basePath', '/static/framework/')
.config(frameworkConfiguration);
.config(config);
frameworkConfiguration.$inject = [
config.$inject = [
'$provide',
'$interpolateProvider',
'$httpProvider',
'$windowProvider'
];
function frameworkConfiguration($interpolateProvider, $httpProvider, $windowProvider) {
function config($provide, $interpolateProvider, $httpProvider, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'framework/';
$provide.constant('horizon.framework.basePath', path);
// Replacing the default angular symbol
// allow us to mix angular with django templates
$interpolateProvider.startSymbol('{$');

View File

@ -21,11 +21,17 @@ limitations under the License.
.module('horizon.framework.util.http', [])
.service('horizon.framework.util.http.service', ApiService);
ApiService.$inject = ['$http'];
ApiService.$inject = ['$http', '$window'];
function ApiService($http) {
function ApiService($http, $window) {
var httpCall = function (method, url, data, config) {
/* eslint-disable angular/ng_window_service */
url = $window.WEBROOT + url;
/* eslint-enable angular/ng_window_service */
url = url.replace(/\/+/g, '/');
if (angular.isUndefined(config)) {
config = {};
}

View File

@ -1,14 +1,21 @@
(function () {
'use strict';
angular.module('horizon.framework.util', [
'horizon.framework.util.bind-scope',
'horizon.framework.util.filters',
'horizon.framework.util.http',
'horizon.framework.util.i18n',
'horizon.framework.util.tech-debt',
'horizon.framework.util.workflow',
'horizon.framework.util.validators'
])
.constant('horizon.framework.util.basePath', '/static/framework/util/');
angular
.module('horizon.framework.util', [
'horizon.framework.util.bind-scope',
'horizon.framework.util.filters',
'horizon.framework.util.http',
'horizon.framework.util.i18n',
'horizon.framework.util.tech-debt',
'horizon.framework.util.workflow',
'horizon.framework.util.validators'
])
.config(config);
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'framework/util/';
$provide.constant('horizon.framework.util.basePath', path);
}
})();

View File

@ -79,11 +79,14 @@
}
];
var markup = '<magic-search ' +
'template="/static/framework/widgets/magic-search/magic-search.html" ' +
'strings="filterStrings" ' +
'facets="{{ filterFacets }}">' +
'</magic-search>';
/* eslint-disable angular/ng_window_service */
var markup =
'<magic-search ' +
'template="' + window.STATIC_URL + 'framework/widgets/magic-search/magic-search.html" ' +
'strings="filterStrings" ' +
'facets="{{ filterFacets }}">' +
'</magic-search>';
/* eslint-enable angular/ng_window_service */
$element = $compile(angular.element(markup))($scope);

View File

@ -1,19 +1,25 @@
(function () {
'use strict';
angular.module('horizon.framework.widgets', [
'horizon.framework.widgets.help-panel',
'horizon.framework.widgets.wizard',
'horizon.framework.widgets.table',
'horizon.framework.widgets.modal',
'horizon.framework.widgets.modal-wait-spinner',
'horizon.framework.widgets.transfer-table',
'horizon.framework.widgets.charts',
'horizon.framework.widgets.action-list',
'horizon.framework.widgets.metadata-tree',
'horizon.framework.widgets.metadata-display',
'horizon.framework.widgets.toast'
])
.constant('horizon.framework.widgets.basePath', '/static/framework/widgets/');
angular
.module('horizon.framework.widgets', [
'horizon.framework.widgets.help-panel',
'horizon.framework.widgets.wizard',
'horizon.framework.widgets.table',
'horizon.framework.widgets.modal',
'horizon.framework.widgets.modal-wait-spinner',
'horizon.framework.widgets.transfer-table',
'horizon.framework.widgets.charts',
'horizon.framework.widgets.action-list',
'horizon.framework.widgets.metadata-tree',
'horizon.framework.widgets.metadata-display',
'horizon.framework.widgets.toast'
])
.config(config);
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'framework/widgets/';
$provide.constant('horizon.framework.widgets.basePath', path);
}
})();

View File

@ -1,4 +1,3 @@
/* global STATIC_URL, console */
/* Namespace for core functionality related to DataTables. */
horizon.datatables = {
update: function () {
@ -70,7 +69,8 @@ horizon.datatables = {
var imagePath = $new_row.find('.btn-action-required').length > 0 ?
"dashboard/img/action_required.png":
"dashboard/img/loading.gif";
imagePath = STATIC_URL + imagePath;
imagePath = window.STATIC_URL + imagePath;
spinner_elm.prepend(
$("<div>")
.addClass("loading_gif")

View File

@ -5,6 +5,10 @@
<title>Jasmine Spec Runner</title>
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}horizon/lib/jasmine/jasmine.css">
<script>
window.STATIC_URL = '/static/';
window.WEBROOT = '/';
</script>
<script src="{% url 'horizon:jsi18n' 'horizon' %}"></script>
<script src="{{ STATIC_URL }}horizon/lib/jasmine/jasmine.js"></script>
<script src="{{ STATIC_URL }}horizon/lib/jasmine/jasmine-html.js"></script>

View File

@ -53,4 +53,7 @@ def openstack(request):
region in available_regions]}
context['regions'] = regions
# Adding webroot access
context['WEBROOT'] = getattr(settings, "WEBROOT", "/")
return context

View File

@ -29,15 +29,11 @@
'hz.dashboard.identity.users',
'hz.dashboard.identity.projects'
])
.config(config);
/**
* @name hz.dashboard.identity.basePath
* @description Base path for the identity dashboard
*/
.constant('hz.dashboard.identity.basePath', getBasePath());
function getBasePath() {
return (window.WEBROOT || '') + '/static/dashboard/identity/';
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/identity/';
$provide.constant('hz.dashboard.identity.basePath', path);
}
})();

View File

@ -24,10 +24,11 @@
*/
angular
.module('hz.dashboard.identity.projects', [])
.constant('hz.dashboard.identity.projects.basePath', basePath());
.config(config);
function basePath() {
return (window.WEBROOT || '') + '/static/dashboard/identity/projects/';
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/identity/projects/';
$provide.constant('hz.dashboard.identity.projects.basePath', path);
}
})();

View File

@ -44,6 +44,7 @@
var toastService;
var policyAPI;
var keystoneAPI;
var staticUrl;
///////////////////////
@ -61,6 +62,7 @@
policyAPI = $injector.get('horizon.openstack-service-api.policy');
keystoneAPI = $injector.get('horizon.openstack-service-api.keystone');
controller = $injector.get('$controller');
staticUrl = $injector.get('$window').STATIC_URL;
spyOn(toastService, 'add').and.callFake(fakeToast);
spyOn(policyAPI, 'check').and.callFake(fakePolicy);
@ -77,7 +79,7 @@
}
it('should set path properly', function() {
var path = '/static/dashboard/identity/users/table/';
var path = staticUrl + 'dashboard/identity/users/table/';
expect(createController().path).toEqual(path);
});
@ -99,4 +101,4 @@
});
});
})();
})();

View File

@ -60,20 +60,20 @@ class LaunchImage(tables.LinkAction):
class LaunchImageNG(LaunchImage):
name = "launch_image_ng"
verbose_name = _("Launch")
url = "horizon:project:images:index"
classes = ("btn-launch")
ajax = False
def __init__(self, attrs=None, **kwargs):
if attrs is None:
attrs = {"ng-controller": "LaunchInstanceModalController"}
kwargs['preempt'] = True
super(LaunchImage, self).__init__(attrs, **kwargs)
def get_link_url(self, datum):
imageId = self.table.get_object_id(datum)
clickValue = "openLaunchInstanceWizard({successUrl: " +\
"'/project/images/', imageId: '%s'})" % (imageId)
self.attrs['ng-click'] = clickValue
url = reverse(self.url)
ngclick = "openLaunchInstanceWizard({successUrl: '%s', imageId: '%s'})"
self.attrs.update({"ng-controller": "LaunchInstanceModalController",
"ng-click": ngclick % (url, imageId)})
return "javascript:void(0);"

View File

@ -349,18 +349,16 @@ class LaunchLink(tables.LinkAction):
class LaunchLinkNG(LaunchLink):
name = "launch-ng"
url = "horizon:project:instances:index"
ajax = False
classes = ("btn-launch")
def __init__(self,
attrs={
"ng-controller": "LaunchInstanceModalController",
"ng-click": "openLaunchInstanceWizard(" +
"{successUrl: '/project/instances/'})"
},
**kwargs):
kwargs['preempt'] = True
super(LaunchLink, self).__init__(attrs, **kwargs)
def get_default_attrs(self):
url = urlresolvers.reverse(self.url)
ngclick = "openLaunchInstanceWizard({ successUrl: '%s' })" % url
self.attrs.update({'ng-controller': 'LaunchInstanceModalController',
'ng-click': ngclick})
return super(LaunchLinkNG, self).get_default_attrs()
def get_link_url(self, datum=None):
return "javascript:void(0);"

View File

@ -29,7 +29,8 @@
<div class="launchButtons">
{% if launch_instance_allowed %}
{% if show_ng_launch %}
<a ng-controller="LaunchInstanceModalController" ng-click="openLaunchInstanceWizard({successUrl: '/project/network_topology/', dismissUrl: '/project/network_topology/'})" id="instances__action_launch" class="btn btn-default btn-sm btn-launch ajax-modal {% if instance_quota_exceeded %}disabled{% endif %}"><span class="fa fa-cloud-upload"></span> {% if instance_quota_exceeded %}{% trans "Launch Instance (Quota exceeded)"%}{% else %}{% trans "Launch Instance"%}{% endif %}</a>
{% url 'horizon:project:network_topology:index' as networkUrl %}
<a ng-controller="LaunchInstanceModalController" ng-click="openLaunchInstanceWizard({successUrl: '{{networkUrl}}', dismissUrl: '{{networkUrl}}'})" id="instances__action_launch" class="btn btn-default btn-sm btn-launch ajax-modal {% if instance_quota_exceeded %}disabled{% endif %}"><span class="fa fa-cloud-upload"></span> {% if instance_quota_exceeded %}{% trans "Launch Instance (Quota exceeded)"%}{% else %}{% trans "Launch Instance"%}{% endif %}</a>
{% endif %}
<a href="{% url 'horizon:project:network_topology:launchinstance' %}" id="instances__action_launch" class="btn btn-default btn-sm btn-launch ajax-modal {% if instance_quota_exceeded %}disabled{% endif %}"><span class="fa fa-cloud-upload"></span> {% if instance_quota_exceeded %}{% trans "Launch Instance (Quota exceeded)"%}{% else %}{% trans "Launch Instance"%}{% endif %}</a>
{% endif %}

View File

@ -364,6 +364,7 @@ POLICY_CHECK_FUNCTION = policy_backend.check
# Add HORIZON_CONFIG to the context information for offline compression
COMPRESS_OFFLINE_CONTEXT = {
'WEBROOT': WEBROOT,
'STATIC_URL': STATIC_URL,
'HORIZON_CONFIG': HORIZON_CONFIG,
}

View File

@ -1,11 +1,16 @@
(function () {
'use strict';
angular.module('hz.dashboard', [
'hz.dashboard.launch-instance',
'hz.dashboard.tech-debt'
])
angular
.module('hz.dashboard', [
'hz.dashboard.launch-instance',
'hz.dashboard.tech-debt'
])
.config(config);
.constant('dashboardBasePath', '/static/dashboard/');
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/';
$provide.constant('dashboardBasePath', path);
}
})();

View File

@ -24,18 +24,20 @@
describe('hz.dashboard:constant:dashboardBasePath', function () {
var dashboardBasePath;
var staticUrl;
beforeEach(module('hz.dashboard'));
beforeEach(inject(function ($injector) {
dashboardBasePath = $injector.get('dashboardBasePath');
staticUrl = $injector.get('$window').STATIC_URL;
}));
it('should be defined', function () {
expect(dashboardBasePath).toBeDefined();
});
it('should equal to "/static/dashboard/"', function () {
expect(dashboardBasePath).toEqual('/static/dashboard/');
it('should get set correctly', function () {
expect(dashboardBasePath).toEqual(staticUrl + 'dashboard/');
});
});

View File

@ -8,7 +8,10 @@
{% comment %} Compress jQuery, Angular, Plugins, Bootstrap, Hogan.js and Horizon-specific JS. {% endcomment %}
{% compress js %}
<script type="text/javascript">var STATIC_URL = "{{ STATIC_URL }}";</script>
<script type="text/javascript">
var STATIC_URL = "{{ STATIC_URL }}";
var WEBROOT = "{{ WEBROOT }}";
</script>
<script src='{{ STATIC_URL }}horizon/lib/angular/angular-cookies.js'></script>
<script src='{{ STATIC_URL }}horizon/lib/angular/angular-sanitize.js'></script>
<script src="{{ STATIC_URL }}horizon/lib/angular/lrdragndrop.js"></script>

View File

@ -90,9 +90,7 @@ var horizonPlugInModules = [];
globals.npgettext = django.npgettext;
globals.interpolate = django.interpolate;
globals.get_format = django.get_format;
globals.STATIC_URL = '/static/';
globals.WEBROOT = '/';
}(this));