diff --git a/heat_dashboard/api/heat.py b/heat_dashboard/api/heat.py index 0811fffc..04ac1757 100644 --- a/heat_dashboard/api/heat.py +++ b/heat_dashboard/api/heat.py @@ -112,7 +112,7 @@ def _ignore_if(key, value): @profiler.trace -def get_template_files(template_data=None, template_url=None): +def get_template_files(template_data=None, template_url=None, files=None): if template_data: tpl = template_data elif template_url: @@ -125,7 +125,8 @@ def get_template_files(template_data=None, template_url=None): if isinstance(tpl, six.binary_type): tpl = tpl.decode('utf-8') template = template_format.parse(tpl) - files = {} + if files is None: + files = {} _get_file_contents(template, files) return files, template @@ -138,6 +139,8 @@ def _get_file_contents(from_data, files): for key, value in from_data.items(): if _ignore_if(key, value): continue + if value in files: + continue if not value.startswith(('http://', 'https://')): raise exceptions.GetFileError(value, 'get_file') if value not in files: diff --git a/heat_dashboard/content/stacks/forms.py b/heat_dashboard/content/stacks/forms.py index 68513693..0f86b491 100644 --- a/heat_dashboard/content/stacks/forms.py +++ b/heat_dashboard/content/stacks/forms.py @@ -127,6 +127,10 @@ class TemplateForm(forms.SelfHandlingForm): widget=forms.widgets.Textarea(attrs=attributes), required=False) + referenced_files = forms.CharField(label=_('Referenced Files'), + widget=forms.widgets.HiddenInput, + required=False) + if django.VERSION >= (1, 9): # Note(Itxaka): On django>=1.9 Charfield has an strip option that # we need to set to False as to not hit @@ -146,6 +150,12 @@ class TemplateForm(forms.SelfHandlingForm): self.clean_uploaded_files('environment', _('environment'), cleaned, files) + referenced_files = cleaned.get('referenced_files') + if referenced_files: + referenced_files = json.loads(referenced_files) + elif referenced_files == '': + referenced_files = {} + # Validate the template and get back the params. kwargs = {} if cleaned['environment_data']: @@ -153,7 +163,8 @@ class TemplateForm(forms.SelfHandlingForm): try: files, tpl =\ api.heat.get_template_files(cleaned.get('template_data'), - cleaned.get('template_url')) + cleaned.get('template_url'), + referenced_files) kwargs['files'] = files kwargs['template'] = tpl validated = api.heat.template_validate(self.request, **kwargs) @@ -181,7 +192,6 @@ class TemplateForm(forms.SelfHandlingForm): :rtype: dict :return: cleaned dict including environment & template data """ - upload_str = prefix + "_upload" data_str = prefix + "_data" url = cleaned.get(prefix + '_url') diff --git a/heat_dashboard/content/stacks/templates/stacks/_select_template.html b/heat_dashboard/content/stacks/templates/stacks/_select_template.html index bd31f700..74cf38b5 100644 --- a/heat_dashboard/content/stacks/templates/stacks/_select_template.html +++ b/heat_dashboard/content/stacks/templates/stacks/_select_template.html @@ -4,4 +4,15 @@ {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Use one of the available template source options to specify the template to be used in creating this stack." %}

+ + + {% endblock %} diff --git a/heat_dashboard/content/stacks/templates/stacks/index.html b/heat_dashboard/content/stacks/templates/stacks/index.html new file mode 100644 index 00000000..b96e6200 --- /dev/null +++ b/heat_dashboard/content/stacks/templates/stacks/index.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{{ page_title }}{% endblock %} +{% block main %}{{ table.render }}{% endblock %} + +{% block js %} + {% include "horizon/_scripts.html" %} + + +{% endblock %} \ No newline at end of file diff --git a/heat_dashboard/content/stacks/views.py b/heat_dashboard/content/stacks/views.py index bbb90bb3..4540c8a8 100644 --- a/heat_dashboard/content/stacks/views.py +++ b/heat_dashboard/content/stacks/views.py @@ -39,6 +39,7 @@ from horizon import views class IndexView(tables.DataTableView): table_class = project_tables.StacksTable page_title = _("Stacks") + template_name = 'project/stacks/index.html' def __init__(self, *args, **kwargs): super(IndexView, self).__init__(*args, **kwargs) diff --git a/heat_dashboard/content/template_generator/api.py b/heat_dashboard/content/template_generator/api.py index 18466bee..6b2a4695 100644 --- a/heat_dashboard/content/template_generator/api.py +++ b/heat_dashboard/content/template_generator/api.py @@ -12,23 +12,29 @@ import json -from openstack_dashboard import api +from openstack_dashboard import api as dashboard_api from openstack_dashboard.api.neutron import neutronclient +from heat_dashboard import api + def get_resources(request): - volumes = [vol.to_dict() for vol in api.cinder.volume_list(request)] + volumes = [ + vol.to_dict() for vol in dashboard_api.cinder.volume_list(request)] volume_snapshots = [ volsnap.to_dict() - for volsnap in api.cinder.volume_snapshot_list(request)] - volume_types = [voltype.to_dict() - for voltype in api.cinder.volume_type_list(request)] - volume_backups = [volbackup.to_dict() - for volbackup in api.cinder.volume_backup_list(request)] + for volsnap in dashboard_api.cinder.volume_snapshot_list(request)] + volume_types = [ + voltype.to_dict() + for voltype in dashboard_api.cinder.volume_type_list(request)] + volume_backups = [ + volbackup.to_dict() + for volbackup in dashboard_api.cinder.volume_backup_list(request)] - images = [img.to_dict() - for img in api.glance.image_list_detailed(request)[0]] + images = [ + img.to_dict() + for img in dashboard_api.glance.image_list_detailed(request)[0]] neutron_client = neutronclient(request) floatingips = neutron_client.list_floatingips().get('floatingips') @@ -41,13 +47,17 @@ def get_resources(request): # qos_policies = neutron_client.list_security_groups().get('ports') availability_zones = \ - [az.to_dict() for az in api.nova.availability_zone_list(request)] + [az.to_dict() + for az in dashboard_api.nova.availability_zone_list(request)] flavors = \ - [flavor.to_dict() for flavor in api.nova.flavor_list(request)] + [flavor.to_dict() + for flavor in dashboard_api.nova.flavor_list(request)] instances = \ - [server.to_dict() for server in api.nova.server_list(request)[0]] + [server.to_dict() + for server in dashboard_api.nova.server_list(request)[0]] keypairs = \ - [keypair.to_dict() for keypair in api.nova.keypair_list(request)] + [keypair.to_dict() + for keypair in dashboard_api.nova.keypair_list(request)] opts = { 'user_roles': request.user.roles, @@ -76,28 +86,31 @@ def get_resource_options(request): volumes = [{'id': vol.id, 'name': vol.name if vol.name else '(%s)' % vol.id} - for vol in api.cinder.volume_list(request)] + for vol in dashboard_api.cinder.volume_list(request)] volume_snapshots = [ {'id': volsnap.id, 'name': volsnap.name if volsnap.name else '(%s)' % volsnap.id[:6]} - for volsnap in api.cinder.volume_snapshot_list(request)] + for volsnap in dashboard_api.cinder.volume_snapshot_list(request)] volume_types = [{ 'id': voltype.id, 'name': voltype.name if voltype.name else '(%s)' % voltype.id[:6]} - for voltype in api.cinder.volume_type_list(request)] - volume_backups = [{'id': volbackup.id, - 'name': volbackup.name - if volbackup.name else '(%s)' % volbackup.id[:6]} - for volbackup in api.cinder.volume_backup_list(request)] + for voltype in dashboard_api.cinder.volume_type_list(request)] + volume_backups = [ + {'id': volbackup.id, + 'name': volbackup.name + if volbackup.name else '(%s)' % volbackup.id[:6]} + for volbackup in dashboard_api.cinder.volume_backup_list(request)] - images = [{'id': img.id, - 'name': img.name if img.name else '(%s)' % img.id[:6]} - for img in api.glance.image_list_detailed(request)[0]] + images = [ + {'id': img.id, + 'name': img.name if img.name else '(%s)' % img.id[:6]} + for img in dashboard_api.glance.image_list_detailed(request)[0]] floatingips = [ {'id': fip.id, 'name': fip.floating_ip_address} - for fip in api.neutron.tenant_floating_ip_list(request, True)] - all_networks = api.neutron.network_list(request) + for fip in dashboard_api.neutron.tenant_floating_ip_list( + request, True)] + all_networks = dashboard_api.neutron.network_list(request) networks = [{'id': nw.id, 'name': nw.name if nw.name else '(%s)' % nw.id[:6]} for nw in all_networks if not nw['router:external']] @@ -108,44 +121,49 @@ def get_resource_options(request): ports = [{'id': port.id, 'name': port.name if port.name else '(%s)' % port.id[:6]} - for port in api.neutron.port_list(request)] + for port in dashboard_api.neutron.port_list(request)] security_groups = [ {'id': secgroup.id, 'name': secgroup.name if secgroup.name else '(%s)' % secgroup.id[:6]} - for secgroup in api.neutron.security_group_list(request)] - all_subnets = api.neutron.subnet_list(request) - subnets = [{'id': subnet.id, - 'name': subnet.name if subnet.name else '(%s)' % subnet.id[:6]} - for subnet in all_subnets] + for secgroup in dashboard_api.neutron.security_group_list(request)] + all_subnets = dashboard_api.neutron.subnet_list(request) + subnets = [ + {'id': subnet.id, + 'name': subnet.name if subnet.name else '(%s)' % subnet.id[:6]} + for subnet in all_subnets] floating_subnets = [{'id': subnet.id, 'name': subnet.name} for subnet in all_subnets if subnet.network_id in floating_network_ids] - routers = [{'id': router.id, - 'name': router.name if router.name else '(%s)' % router.id[:6]} - for router in api.neutron.router_list(request)] + routers = [ + {'id': router.id, + 'name': router.name if router.name else '(%s)' % router.id[:6]} + for router in dashboard_api.neutron.router_list(request)] qos_policies = [] - # qos_policies = [{'id': policy.id, - # 'name': policy.name - # if policy.name else '(%s)' % policy.id[:6]} - # for policy in api.neutron.policy_list(request)] + # qos_policies = [ + # {'id': policy.id, + # 'name': policy.name + # if policy.name else '(%s)' % policy.id[:6]} + # for policy in dashboard_api.neutron.policy_list(request)] - availability_zones = [{'id': az.zoneName, 'name': az.zoneName} - for az in api.nova.availability_zone_list(request)] + availability_zones = [ + {'id': az.zoneName, 'name': az.zoneName} + for az in dashboard_api.nova.availability_zone_list(request)] flavors = [{'id': flavor.name, 'name': flavor.name} - for flavor in api.nova.flavor_list(request)] + for flavor in dashboard_api.nova.flavor_list(request)] instances = [{'id': server.id, 'name': server.name if server.name else '(%s)' % server.id[:6]} - for server in api.nova.server_list(request)[0]] + for server in dashboard_api.nova.server_list(request)[0]] keypairs = [{'name': keypair.name} - for keypair in api.nova.keypair_list(request)] + for keypair in dashboard_api.nova.keypair_list(request)] - template_versions = [{'name': version.version, 'id': version.version} - for version in api.heat.template_version_list(request) - if version.type == 'hot'] + template_versions = [ + {'name': version.version, 'id': version.version} + for version in api.heat.template_version_list(request) + if version.type == 'hot'] opts = { 'auth': { diff --git a/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/globals.service.js b/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/globals.service.js index d0fd7529..2c9538d7 100644 --- a/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/globals.service.js +++ b/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/globals.service.js @@ -14,7 +14,7 @@ resource_options: {'auth': {'admin': false}}, template_version: null, resource_outputs: {}, - reference_file: {}, + reference_files: {}, }; return { @@ -79,10 +79,13 @@ return Object.keys(globals.resource_icons); }, set_reference_file: function(key, value){ - globals.reference_file[key] = value; + globals.reference_files[key] = value; }, get_reference_file: function(key){ - return globals.reference_file[key]; + return globals.reference_files[key]; + }, + get_reference_files: function(key){ + return globals.reference_files; }, }; diff --git a/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/modal-template.controller.js b/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/modal-template.controller.js index 7d9a2389..12e4a1d5 100644 --- a/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/modal-template.controller.js +++ b/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/modal-template.controller.js @@ -5,8 +5,10 @@ .controller('horizon.dashboard.project.heat_dashboard.template_generator.TempModalController', ['$scope', '$mdDialog', 'hotgenNotify', 'hotgenUtils', 'hotgenStates', 'hotgenGlobals', 'horizon.dashboard.project.heat_dashboard.template_generator.basePath', - function($scope, $mdDialog, hotgenNotify, hotgenUtils, hotgenStates, hotgenGlobals, basePath){ + 'horizon.dashboard.project.heat_dashboard.template_generator.projectPath', + function($scope, $mdDialog, hotgenNotify, hotgenUtils, hotgenStates, hotgenGlobals, basePath, projectPath){ $scope.basePath = basePath; + $scope.projectPath = projectPath; $scope.dialogController = function ($scope, $mdDialog, hotgenStates, hotgenGlobals, hotgenNotify) { $scope.all_saved = false; $scope.cancel = function() { @@ -22,11 +24,20 @@ hotgenNotify.show_success('Template is downloaded.'); }; - $scope.save = function() { - hotgenNotify.show_warning('Not implemented yet. To submit to create a stack.') - $mdDialog.cancel(); + + $scope.redirect = function(){ + var redirect = window.location.origin + projectPath + 'stacks/'; + window.location.href = redirect; }; + $scope.save = function() { + var files = hotgenGlobals.get_reference_files(); + sessionStorage.setItem('files', JSON.stringify(files)); + sessionStorage.setItem('generated', true); + sessionStorage.setItem('template', $scope.template_contents); + $scope.redirect(); +// $mdDialog.cancel(); + }; $scope.extract_properties = function(resource_data){ for (var property in resource_data){ var func = null; diff --git a/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/modal-template.controller.spec.js b/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/modal-template.controller.spec.js index ff76a6d2..0e9b75a2 100644 --- a/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/modal-template.controller.spec.js +++ b/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/modal-template.controller.spec.js @@ -7,12 +7,13 @@ beforeEach(module('appTemplates')); - var hotgenGlobals, hotgenStates, hotgenNotify, basePath; + var hotgenGlobals, hotgenStates, hotgenNotify, basePath, projectPath; beforeEach(inject(function($injector){ hotgenGlobals = $injector.get('hotgenGlobals'); hotgenStates = $injector.get('hotgenStates'); hotgenNotify = $injector.get('hotgenNotify'); basePath = $injector.get('horizon.dashboard.project.heat_dashboard.template_generator.basePath'); + projectPath = $injector.get('horizon.dashboard.project.heat_dashboard.template_generator.projectPath'); })); var $controller, controller, $scope, $rootScope, $mdDialog; @@ -83,9 +84,13 @@ it('$scope.dialogController save', function(){ $scope.dialogController($scope, $mdDialog, hotgenStates, hotgenGlobals, hotgenNotify); + + $scope.redirect = jasmine.createSpy().and.callFake(function() { + return function(){return true}; + }); $scope.save(); - expect($mdDialog.cancel).toHaveBeenCalled(); + expect($scope.redirect).toHaveBeenCalled(); }); it('$scope.dialogController extract_properties', function(){ diff --git a/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/template-generator.module.js b/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/template-generator.module.js index 5c5e6abc..a58213c4 100644 --- a/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/template-generator.module.js +++ b/heat_dashboard/static/dashboard/project/heat_dashboard/template_generator/js/components/template-generator.module.js @@ -20,8 +20,11 @@ }); }]) .config(['$provide', '$windowProvider', function($provide, $windowProvider){ - var path = $windowProvider.$get().STATIC_URL + 'dashboard/project/heat_dashboard/template_generator/'; - $provide.constant('horizon.dashboard.project.heat_dashboard.template_generator.basePath', path); + var project_window = $windowProvider.$get(); + var staticPath = project_window.STATIC_URL + 'dashboard/project/heat_dashboard/template_generator/'; + var projectPath = project_window.WEBROOT+'project/'; + $provide.constant('horizon.dashboard.project.heat_dashboard.template_generator.basePath', staticPath); + $provide.constant('horizon.dashboard.project.heat_dashboard.template_generator.projectPath', projectPath); }]) .constant('horizon.dashboard.project.heat_dashboard.template_generator.validationRules', { diff --git a/heat_dashboard/test/tests/content/test_stacks.py b/heat_dashboard/test/tests/content/test_stacks.py index fd512856..927bf660 100644 --- a/heat_dashboard/test/tests/content/test_stacks.py +++ b/heat_dashboard/test/tests/content/test_stacks.py @@ -35,7 +35,7 @@ from heat_dashboard.content.stacks import mappings from heat_dashboard.content.stacks import tables -INDEX_TEMPLATE = 'horizon/common/_data_table_view.html' +INDEX_TEMPLATE = 'project/stacks/index.html' INDEX_URL = reverse('horizon:project:stacks:index') DETAIL_URL = 'horizon:project:stacks:detail' @@ -270,6 +270,7 @@ class StackTests(test.TestCase): form_data = {'template_source': 'raw', 'template_data': template.data, + 'referenced_files': {}, 'method': forms.TemplateForm.__name__} res = self.client.post(url, form_data) self.assertTemplateUsed(res, 'project/stacks/create.html')