From 7fc50585becb17c680bcbf299ba393c8bdc6581f Mon Sep 17 00:00:00 2001 From: Timur Sufiev Date: Mon, 6 Jul 2015 19:08:31 +0300 Subject: [PATCH] Start developing HOT Builder on Merlin Create basic file structure and use common Django template for both views. Also rework forms/YAML/Graph divs layout to make it more flexible. Create draft version of 'Add Resource' panel for HotBuilder - with resources filtering and a list of all available resources obtained from server-side Heat API. Change-Id: Ia2e4f8a63d85d8d5dd7cdd731cc4878836176070 --- .../enabled/_51_add_hotbuilder_panel.py | 17 ++++ extensions/hotbuilder/__init__.py | 0 extensions/hotbuilder/api.py | 30 +++++++ extensions/hotbuilder/panel.py | 20 +++++ .../hotbuilder/js/hotbuilder.controllers.js | 46 ++++++++++ .../static/hotbuilder/js/hotbuilder.init.js | 14 +++ .../static/hotbuilder/js/hotbuilder.models.js | 10 +++ .../templates/hotbuilder/index.html | 88 +++++++++++++++++++ extensions/hotbuilder/urls.py | 26 ++++++ extensions/hotbuilder/views.py | 45 ++++++++++ .../mistral/templates/mistral/create.html | 35 ++------ merlin/static/merlin/js/merlin.directives.js | 18 ++++ merlin/static/merlin/scss/merlin.scss | 13 ++- .../merlin/templates/draggable-entry.html | 13 +++ merlin/templates/merlin/base.html | 26 ++++++ package.json | 2 +- 16 files changed, 372 insertions(+), 31 deletions(-) create mode 100644 extensions/enabled/_51_add_hotbuilder_panel.py create mode 100644 extensions/hotbuilder/__init__.py create mode 100644 extensions/hotbuilder/api.py create mode 100644 extensions/hotbuilder/panel.py create mode 100644 extensions/hotbuilder/static/hotbuilder/js/hotbuilder.controllers.js create mode 100644 extensions/hotbuilder/static/hotbuilder/js/hotbuilder.init.js create mode 100644 extensions/hotbuilder/static/hotbuilder/js/hotbuilder.models.js create mode 100644 extensions/hotbuilder/templates/hotbuilder/index.html create mode 100644 extensions/hotbuilder/urls.py create mode 100644 extensions/hotbuilder/views.py create mode 100644 merlin/static/merlin/templates/draggable-entry.html create mode 100644 merlin/templates/merlin/base.html diff --git a/extensions/enabled/_51_add_hotbuilder_panel.py b/extensions/enabled/_51_add_hotbuilder_panel.py new file mode 100644 index 0000000..20054e3 --- /dev/null +++ b/extensions/enabled/_51_add_hotbuilder_panel.py @@ -0,0 +1,17 @@ +# The name of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'hotbuilder' +# The name of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' +# The name of the panel group the PANEL is associated with. +PANEL_GROUP = 'orchestration' + +ADD_INSTALLED_APPS = ['merlin', 'hotbuilder'] + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'hotbuilder.panel.HotBuilderPanel' + +ADD_ANGULAR_MODULES = ['merlin', 'hotbuilder'] +ADD_JS_FILES = ['merlin/js/custom-libs/ui-bootstrap-tpls-0.12.1.js', + 'merlin/js/merlin.init.js', + 'merlin/js/merlin.templates.js', + 'hotbuilder/js/hotbuilder.init.js'] diff --git a/extensions/hotbuilder/__init__.py b/extensions/hotbuilder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/extensions/hotbuilder/api.py b/extensions/hotbuilder/api.py new file mode 100644 index 0000000..c24ec6d --- /dev/null +++ b/extensions/hotbuilder/api.py @@ -0,0 +1,30 @@ +# Copyright (c) 2015 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +from openstack_dashboard.api import heat + +logger = logging.getLogger(__name__) + + +def resource_type_list(request): + client = heat.heatclient(request) + kwargs = {} + return client.resource_types.list(**kwargs) + + +def resource_type_show(request, resource_type): + client = heat.heatclient(request) + return client.resource_types.get(resource_type) + diff --git a/extensions/hotbuilder/panel.py b/extensions/hotbuilder/panel.py new file mode 100644 index 0000000..32f5b5d --- /dev/null +++ b/extensions/hotbuilder/panel.py @@ -0,0 +1,20 @@ +# Copyright (c) 2014 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import horizon + + +class HotBuilderPanel(horizon.Panel): + name = 'HOT Templates' + slug = 'hotbuilder' diff --git a/extensions/hotbuilder/static/hotbuilder/js/hotbuilder.controllers.js b/extensions/hotbuilder/static/hotbuilder/js/hotbuilder.controllers.js new file mode 100644 index 0000000..d8216ab --- /dev/null +++ b/extensions/hotbuilder/static/hotbuilder/js/hotbuilder.controllers.js @@ -0,0 +1,46 @@ +(function() { + 'use strict'; + + angular + .module('hotbuilder') + .controller('HotbuilderController', HotbuilderController); + + HotbuilderController.$inject = ['$q', '$http']; + + function HotbuilderController($q, $http) { + var deferred = $q.defer(); + var vm = this; + + deferred.promise.then(function(url) { + $http.get(url).success(function(data) { + data.forEach(function(resourceType) { + $http.get(url + '/' + resourceType).success(function(data) { + vm.allEntries.push(data); + }); + }); + }); + }); + + vm.setUrl = setUrl; + + vm.allEntries = []; + + vm.entries = [ + + ]; + + vm.panel = { + title: 'Add Resource' + }; + + vm.tabs = [ + {title: 'Parameters', content: 'There'}, + {title: 'Outputs', content: 'Where'} + ]; + + function setUrl(url) { + deferred.resolve(url); + } + } + +})(); diff --git a/extensions/hotbuilder/static/hotbuilder/js/hotbuilder.init.js b/extensions/hotbuilder/static/hotbuilder/js/hotbuilder.init.js new file mode 100644 index 0000000..6edc4e6 --- /dev/null +++ b/extensions/hotbuilder/static/hotbuilder/js/hotbuilder.init.js @@ -0,0 +1,14 @@ +(function() { + 'use strict'; + + angular + .module('hotbuilder', ['merlin']) + .run(initModule); + + initModule.$inject = ['merlin.templates']; + + function initModule(templates) { + templates.prefetch('/static/hotbuilder/templates/fields/', []); + } + +})(); diff --git a/extensions/hotbuilder/static/hotbuilder/js/hotbuilder.models.js b/extensions/hotbuilder/static/hotbuilder/js/hotbuilder.models.js new file mode 100644 index 0000000..249ea83 --- /dev/null +++ b/extensions/hotbuilder/static/hotbuilder/js/hotbuilder.models.js @@ -0,0 +1,10 @@ +/** + * Created by tsufiev on 2/24/15. + */ +(function() { + 'use strict'; + + angular + .module('hotbuilder'); + +})(); diff --git a/extensions/hotbuilder/templates/hotbuilder/index.html b/extensions/hotbuilder/templates/hotbuilder/index.html new file mode 100644 index 0000000..e1c7c1b --- /dev/null +++ b/extensions/hotbuilder/templates/hotbuilder/index.html @@ -0,0 +1,88 @@ +{% extends 'merlin/base.html' %} +{% load i18n %} +{% block title %}{% trans "Template Builder" %}{% endblock %} + +{% block merlin-css %} +{% endblock %} + +{% block merlin-js %} + + + +{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Template Builder") %} +{% endblock page_header %} + +{% block main %} +

Compose Template

+
+
+
+
+ + + + + +
+ + +
+
+ +
+
+ + +
+ + +
+
+
+
+ + + {$ tab.content $} + + +
+
+
+
+ + +
+
+
+
{$ workbook.toYAML() $}
+
+
+ Here will be a fancy Graph View as soon as we implement it! +
+
+
+
+ +
+
+ + +
+
+
+
+{% endblock %} diff --git a/extensions/hotbuilder/urls.py b/extensions/hotbuilder/urls.py new file mode 100644 index 0000000..8e0fae2 --- /dev/null +++ b/extensions/hotbuilder/urls.py @@ -0,0 +1,26 @@ +# Copyright (c) 2014 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.conf.urls import patterns +from django.conf.urls import url + +from hotbuilder import views + +urlpatterns = patterns('', + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^heat_resources$', views.ResourceTypesView.as_view(), + name='resource_types'), + url(r'^heat_resources/(?P[^/]+)$', + views.ShowResourceView.as_view(), name='show_resource') +) diff --git a/extensions/hotbuilder/views.py b/extensions/hotbuilder/views.py new file mode 100644 index 0000000..f35067f --- /dev/null +++ b/extensions/hotbuilder/views.py @@ -0,0 +1,45 @@ +# Copyright 2014 Rackspace +# +# 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 + +from django.views.generic import TemplateView, View +from django.http import HttpResponse + +from hotbuilder import api + + +class JSONView(View): + def get_data(self, request, *args, **kwargs): + pass + + def get(self, request, *args, **kwargs): + return HttpResponse( + json.dumps(self.get_data(request, *args, **kwargs)), + content_type='application/json') + + +class ResourceTypesView(JSONView): + def get_data(self, request, *args, **kwargs): + return [resource.resource_type for resource in + api.resource_type_list(request)] + + +class ShowResourceView(JSONView): + def get_data(self, request, *args, **kwargs): + return api.resource_type_show(request, kwargs['resource_type']) + + +class IndexView(TemplateView): + template_name = 'hotbuilder/index.html' diff --git a/extensions/mistral/templates/mistral/create.html b/extensions/mistral/templates/mistral/create.html index 931f969..69a671e 100644 --- a/extensions/mistral/templates/mistral/create.html +++ b/extensions/mistral/templates/mistral/create.html @@ -1,41 +1,18 @@ -{% extends "base.html" %} +{% extends "merlin/base.html" %} {% load i18n %} -{% load url from future %} -{% load static %} -{% load compress %} {% block title %}{% trans "Create Workbook" %}{% endblock %} {% block page_header %} {% include "horizon/common/_page_header.html" with title=_("Workbooks") %} {% endblock page_header %} -{% block js %} -{% include "horizon/_scripts.html" %} - - - - - - - - - - - - - - - +{% block merlin-js %} + + + {% endblock %} -{% block css %} - {% include "_stylesheets.html" %} - {% compress css %} - - {% endcompress %} - - {% block merlin-css %}{% endblock %} -{% endblock %} +{% block merlin-css %}{% endblock %} {% block main %}

Create Workbook

diff --git a/merlin/static/merlin/js/merlin.directives.js b/merlin/static/merlin/js/merlin.directives.js index fa0e69d..57030b8 100644 --- a/merlin/static/merlin/js/merlin.directives.js +++ b/merlin/static/merlin/js/merlin.directives.js @@ -42,6 +42,7 @@ .directive('typedField', typedField) .directive('editableTitle', editableTitle) +.directive('draggableEntry', draggableEntry) .directive('labeled', labeled); function labeled() { @@ -230,4 +231,21 @@ } }; } + + function draggableEntry() { + return { + restrict: 'E', + scope: { + entry: '=' + }, + link: link, + templateUrl: '/static/merlin/templates/draggable-entry.html' + }; + + function link(scope, element, attrs) { + if (angular.isDefined(attrs.entryIcon)) { + scope.iconCls = 'fa-' + attrs.entryIcon; + } + } + } })(); diff --git a/merlin/static/merlin/scss/merlin.scss b/merlin/static/merlin/scss/merlin.scss index fa84b67..8db93e1 100644 --- a/merlin/static/merlin/scss/merlin.scss +++ b/merlin/static/merlin/scss/merlin.scss @@ -61,4 +61,15 @@ i.fa-times-circle { .section .section .section-heading & { margin-top: 7px; } -}; +} + +.has-feedback { + input { + width: 100%; + } + .form-control-feedback { + top: -5px; + right: 7px; + } +} + diff --git a/merlin/static/merlin/templates/draggable-entry.html b/merlin/static/merlin/templates/draggable-entry.html new file mode 100644 index 0000000..5ad7238 --- /dev/null +++ b/merlin/static/merlin/templates/draggable-entry.html @@ -0,0 +1,13 @@ +
+
+
+ + {$ entry.resource_type $} +
+
+ +
+
+ +
diff --git a/merlin/templates/merlin/base.html b/merlin/templates/merlin/base.html new file mode 100644 index 0000000..785b0ad --- /dev/null +++ b/merlin/templates/merlin/base.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} +{% load compress %} + +{% block js %} + {% include "horizon/_scripts.html" %} + + + + + + + + + + + {% block merlin-js %}{% endblock %} +{% endblock %} + +{% block css %} + {% include "_stylesheets.html" %} + {% compress css %} + + + {% endcompress %} + {% block merlin-css %}{% endblock %} +{% endblock %} diff --git a/package.json b/package.json index ad05b7e..9f95eeb 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,6 @@ "postinstall": "bower install", "test-unit": "grunt test:unit", "test": "karma start ./karma-unit.conf.js", - "lint": "eslint --no-color ./merlin/static ./extensions/mistral/static" + "lint": "eslint --no-color ./merlin/static ./extensions/mistral/static ./extensions/hotbuilder/static" } }