Browse Source

Unifies the compute and GBP member launch modals

Modifies the create member button in group details to use the compute launch
instance AngularJS based launch dialog. The dialog itself is changed through
the Angular API to replace standard networking pages with a group setting page.

Change-Id: Ib9f5b601939ed9e8258e4eab2f2180efc98378ae
Closes-Bug: 1582457
Marek Lycka 1 year ago
parent
commit
d86c6ea3cc

+ 1
- 0
gbpui/_1550_gbp_project_add_panel_group.py View File

@@ -17,3 +17,4 @@ PANEL_GROUP_DASHBOARD = 'project'
17 17
 
18 18
 AUTO_DISCOVER_STATIC_FILES = True
19 19
 ADD_ANGULAR_MODULES = ['gbpui', ]
20
+ADD_SCSS_FILES = ['dashboard/gbpui/group-member/group-member.scss']

+ 219
- 0
gbpui/panels/policytargets/restApi.py View File

@@ -0,0 +1,219 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+import re
13
+
14
+from django.conf import settings
15
+from django import shortcuts
16
+from django.utils.translation import ugettext_lazy as _
17
+from django.views import generic
18
+
19
+from openstack_dashboard import api
20
+from openstack_dashboard.api.rest import utils as rest_utils
21
+
22
+from horizon import exceptions
23
+
24
+from gbpui import client
25
+
26
+from netaddr import IPAddress
27
+from netaddr import IPNetwork
28
+
29
+import logging
30
+
31
+LOG = logging.getLogger(__name__)
32
+
33
+
34
+class PolicyTargets(generic.View):
35
+    # todo: This is a direct port from the old form version; it needs to be
36
+    #       tested and revised; It may be dodgy and/or redundant because:
37
+    #       1)  The created regex might be nonsenseical (needs testing)
38
+    #       2)  The proxy_group_ids might be logical duplication of the third
39
+    #           third conditional
40
+    @staticmethod
41
+    def is_proxy_group(policy_target, proxy_group_ids):
42
+        if hasattr(settings, 'GBPUI_HIDE_PTG_NAMES_FROM_MEMBER_CREATE'):
43
+            regex = "(" + ")|(".join(
44
+                settings.GBPUI_HIDE_PTG_NAMES_FROM_MEMBER_CREATE) \
45
+                + ")"
46
+
47
+            if re.match(regex, policy_target.get('name')):
48
+                return True
49
+
50
+        if policy_target.id in proxy_group_ids:
51
+            return True
52
+
53
+        if policy_target.get('proxied_group_id'):
54
+            return True
55
+
56
+        return False
57
+
58
+    @rest_utils.ajax()
59
+    def get(self, request):
60
+        policy_targets = client.policy_target_list(
61
+            request, tenant_id=request.user.tenant_id
62
+        )
63
+
64
+        proxy_group_ids = [pt.get('proxy_group_id') for pt in policy_targets
65
+                           if pt.get('proxy_group_id')]
66
+
67
+        try:
68
+            policy_target_objects = []
69
+
70
+            for policy_target in policy_targets:
71
+                if not self.is_proxy_group(policy_target, proxy_group_ids):
72
+                    subnet_objects = []
73
+
74
+                    for subnet_id in policy_target.subnets:
75
+                        try:
76
+                            subnet = api.neutron.subnet_get(request, subnet_id)
77
+                            allocation_pool_objects = []
78
+
79
+                            allocation_pools = subnet['allocation_pools']
80
+                            if allocation_pools:
81
+                                for allocation_pool in allocation_pools:
82
+                                    allocation_pool_object = {
83
+                                        "start": allocation_pool['start'],
84
+                                        "end": allocation_pool['end']
85
+                                    }
86
+                                    allocation_pool_objects.append(
87
+                                        allocation_pool_object)
88
+
89
+                            subnet_object = {
90
+                                "cidr": subnet['cidr'],
91
+                                "allocation_pools": allocation_pool_objects
92
+                            }
93
+                            subnet_objects.append(subnet_object)
94
+                        except Exception:
95
+                            LOG.exception("Unable to retrieve subnet.")
96
+
97
+                    policy_target_object = {
98
+                        "id": policy_target.id,
99
+                        "name_or_id": policy_target.name_or_id,
100
+                        "subnets": subnet_objects
101
+                    }
102
+                    policy_target_objects.append(policy_target_object)
103
+
104
+            return rest_utils.JSONResponse(policy_target_objects)
105
+        except Exception:
106
+            msg = _("Failed to retrieve groups")
107
+            LOG.error(msg)
108
+            exceptions.handle(request, msg, redirect=shortcuts.redirect)
109
+
110
+
111
+class Members(generic.View):
112
+    optional_arguments = [
113
+        'block_device_mapping',
114
+        'block_device_mapping_v2',
115
+        'availability_zone',
116
+        'admin_pass', 'disk_config',
117
+        'config_drive'
118
+    ]
119
+
120
+    @rest_utils.ajax()
121
+    def post(self, request):
122
+        instance_count = request.DATA['instance_count']
123
+
124
+        try:
125
+            if instance_count == 1:
126
+                self.create_instance(request)
127
+            elif instance_count > 1:
128
+                for i in range(0, instance_count):
129
+                    self.create_instance(request, "_" + str(i))
130
+
131
+        except Exception:
132
+            instance_name = request.DATA['name']
133
+
134
+            error = _("Unable to launch member %(count)s with name %(name)s")
135
+            message = error % {
136
+                'count': instance_count,
137
+                'name': instance_name
138
+            }
139
+            LOG.exception(message)
140
+
141
+            raise rest_utils.AjaxError(400, message)
142
+
143
+        return rest_utils.CreatedResponse('/api/nova/servers/%s')
144
+
145
+    def create_instance(self, request, suffix=""):
146
+        # Instances need to be created one by one, because each instance
147
+        # needs to have it's own GBP port
148
+        kw = {
149
+            'instance_count': 1
150
+        }
151
+
152
+        # Mandatory creation arguments and port creation
153
+        try:
154
+            instance_name = request.DATA['name'] + suffix
155
+
156
+            meta_data, nics = self.create_ports(request, instance_name)
157
+
158
+            kw['meta'] = meta_data
159
+            kw['nics'] = nics
160
+
161
+            args = (
162
+                request,
163
+                instance_name,
164
+                request.DATA['source_id'],
165
+                request.DATA['flavor_id'],
166
+                request.DATA['key_name'],
167
+                request.DATA['user_data'],
168
+                request.DATA['security_groups'],
169
+            )
170
+
171
+        except KeyError as e:
172
+            raise rest_utils.AjaxError(400, 'Missing required parameter '
173
+                                            "'%s'" % e.args[0])
174
+
175
+        # Optional creation arguments
176
+        for name in self.optional_arguments:
177
+            if name in request.DATA:
178
+                kw[name] = request.DATA[name]
179
+
180
+        return api.nova.server_create(*args, **kw)
181
+
182
+    # 1) Missing request.DATA entries get propagated to 'create_instance' as
183
+    #    KeyError
184
+    # 2) All other errors are propagated to 'post' as generic failure Exception
185
+    @staticmethod
186
+    def create_ports(request, instance_name):
187
+        nics = []
188
+        pts = []
189
+
190
+        for policy_target_id in request.DATA["group_policy_targets"]:
191
+            policy_target = client.policy_target_get(request,
192
+                                                     policy_target_id['id'])
193
+
194
+            args = {
195
+                'policy_target_group_id': policy_target.id,
196
+                'name': instance_name[:41] + "_gbpui"
197
+            }
198
+
199
+            for subnet_id in policy_target.subnets:
200
+                subnet = api.neutron.subnet_get(request, subnet_id)
201
+
202
+                if 'fixed_ip' in policy_target_id and IPAddress(
203
+                        policy_target_id['fixed_ip']) in \
204
+                        IPNetwork(subnet['cidr']):
205
+                    args['fixed_ips'] = [{
206
+                        'subnet_id': subnet['id'],
207
+                        'ip_address': policy_target_id['fixed_ip']
208
+                    }]
209
+
210
+            port = client.pt_create(request, **args)
211
+
212
+            nics.append({
213
+                'port-id': port.port_id
214
+            })
215
+            pts.append(port.id)
216
+
217
+        meta_data = {'pts': ','.join(pts)}
218
+
219
+        return meta_data, nics

+ 23
- 7
gbpui/panels/policytargets/tables.py View File

@@ -136,14 +136,30 @@ class ExternalPTGsTable(tables.DataTable):
136 136
 
137 137
 
138 138
 class LaunchVMLink(tables.LinkAction):
139
-    name = "launch_vm"
139
+    name = "launch_vm-ng"
140 140
     verbose_name = _("Create Member")
141
-    classes = ("ajax-modal", "btn-addvm",)
142
-
143
-    def get_link_url(self):
144
-        return reverse("horizon:project:policytargets:addvm",
145
-               kwargs={'policy_target_id':
146
-                   self.table.kwargs['policy_target_id']})
141
+    url = "horizon:project:policytargets:policy_targetdetails"
142
+    ajax = False
143
+    classes = ("btn-launch", )
144
+
145
+    def get_default_attrs(self):
146
+        url_kwargs = {
147
+            'policy_target_id': self.table.kwargs['policy_target_id']
148
+        }
149
+        url = reverse(self.url, kwargs=url_kwargs)
150
+        ngclick = "modal.openLaunchInstanceWizard(" \
151
+                  "{ successUrl: '%s'," \
152
+                  " defaults: ['%s'] }" \
153
+                  ")" % (url, self.table.kwargs['policy_target_id'])
154
+
155
+        self.attrs.update({
156
+            'ng-controller': 'LaunchInstanceModalController as modal',
157
+            'ng-click': ngclick
158
+        })
159
+        return super(LaunchVMLink, self).get_default_attrs()
160
+
161
+    def get_link_url(self, datum=None):
162
+        return ""
147 163
 
148 164
 
149 165
 class RemoveVMLink(tables.DeleteAction):

+ 9
- 3
gbpui/panels/policytargets/urls.py View File

@@ -10,11 +10,12 @@
10 10
 #    License for the specific language governing permissions and limitations
11 11
 #    under the License.
12 12
 
13
-
14 13
 from django.conf.urls import url  # noqa
15 14
 
16 15
 import views
17 16
 
17
+import restApi
18
+
18 19
 urlpatterns = [
19 20
     url(r'^$',
20 21
         views.IndexView.as_view(),
@@ -37,8 +38,6 @@ urlpatterns = [
37 38
     url(r'^ext_policy_target/(?P<ext_policy_target_id>[^/]+)/$',
38 39
         views.ExternalPTGDetailsView.as_view(),
39 40
         name='ext_policy_targetdetails'),
40
-    url(r'^addvm/(?P<policy_target_id>[^/]+)/$',
41
-        views.LaunchVMView.as_view(), name='addvm'),
42 41
     url(r'^ext_add_policy_rule_set/(?P<ext_policy_target_id>[^/]+)/$',
43 42
         views.ExtAddProvidedPRSView.as_view(),
44 43
         name='ext_add_provided_prs'),
@@ -66,4 +65,11 @@ urlpatterns = [
66 65
     url(r'/check_ip_availability',
67 66
         views.check_ip_availability,
68 67
         name='check_ip_availability'),
68
+    # Rest APIs for use with AJAX/ANGULARJS calls
69
+    url(r'policy_target_groups/$',
70
+        restApi.PolicyTargets.as_view(),
71
+        name='policy_target_groups'),
72
+    url(r'launch_instance/$',
73
+        restApi.Members.as_view(),
74
+        name='launch_instance'),
69 75
 ]

+ 3
- 1
gbpui/panels/policytargets/views.py View File

@@ -88,14 +88,16 @@ class ExternalPTGDetailsView(tabs.TabbedTableView):
88 88
         return context
89 89
 
90 90
 
91
+'''
91 92
 class LaunchVMView(workflows.WorkflowView):
92 93
     workflow_class = policy_target_workflows.LaunchInstance
93 94
 
94 95
     def get_initial(self):
95
-        initial = super(LaunchVMView, self).get_initial()
96
+        initial = super( LaunchVMView, self).get_initial()
96 97
         initial['project_id'] = self.request.user.tenant_id
97 98
         initial['user_id'] = self.request.user.id
98 99
         return initial
100
+'''
99 101
 
100 102
 
101 103
 class UpdatePTGView(gbforms.HelpTextModalMixin,

+ 97
- 81
gbpui/static/dashboard/css/scspec.css View File

@@ -1,16 +1,16 @@
1
-#nodeListSortContainer,#nodeListIdContainer{
2
-  width:100%;
1
+#nodeListSortContainer, #nodeListIdContainer {
2
+    width: 100%;
3 3
 }
4 4
 
5 5
 #selected_node {
6
-  margin-bottom: 1.5em;
7
-  counter-reset: v1 0;
8
-  background: #edf9ff;
9
-  border: 1px solid #c0d9e4;
6
+    margin-bottom: 1.5em;
7
+    counter-reset: v1 0;
8
+    background: #edf9ff;
9
+    border: 1px solid #c0d9e4;
10 10
 }
11 11
 
12 12
 #selected_node li {
13
-  position: relative;
13
+    position: relative;
14 14
 }
15 15
 
16 16
 #selected_node li a.btn:before {
@@ -18,87 +18,103 @@
18 18
 }
19 19
 
20 20
 #selected_node li:before {
21
-  content: "Node:" counter(v1);
22
-  counter-increment: v1;
23
-  display: inline-block;
24
-  margin-right: 5px;
25
-  background: #555555;
26
-  color: #ffffff;
27
-  font-size: 90%;
28
-  padding: 0px 4px;
29
-  vertical-align: middle;
30
-  border-radius: 2px;
31
-  position: absolute;
32
-  left: -4em;
21
+    content: "Node:" counter(v1);
22
+    counter-increment: v1;
23
+    display: inline-block;
24
+    margin-right: 5px;
25
+    background: #555555;
26
+    color: #ffffff;
27
+    font-size: 90%;
28
+    padding: 0px 4px;
29
+    vertical-align: middle;
30
+    border-radius: 2px;
31
+    position: absolute;
32
+    left: -4em;
33 33
 }
34 34
 
35 35
 #selected_node.dragging li:before {
36
-  content: "Node:";
37
-  background-color: rgba(102, 102, 102, 0.5);
38
-  padding-right: 10px;
36
+    content: "Node:";
37
+    background-color: rgba(102, 102, 102, 0.5);
38
+    padding-right: 10px;
39 39
 }
40 40
 
41 41
 #selected_node.dragging li.ui-state-highlight:before {
42
-  content: "";
43
-  background: transparent;
42
+    content: "";
43
+    background: transparent;
44 44
 }
45
+
45 46
 .nodelist {
46
-                  padding: 6px;
47
-                  background: #eee;
48
-                  border: 1px solid #dddddd;
49
-                  min-height: 2em;
50
-                  width: auto !important;
51
-                  -webkit-box-sizing: border-box;
52
-                  -moz-box-sizing: border-box;
53
-                  box-sizing: border-box; }
54
-                  .nodelist li {
55
-                    width: 226px;
56
-                    list-style-type: none;
57
-                    margin: 6px auto;
58
-                    padding: 3px;
59
-                    background: #ffffff;
60
-                    border: 1px solid #aaa;
61
-                    line-height: 18px;
62
-                    border-radius: 3px;
63
-                    cursor: move;
64
-                    padding-left: 23px;
65
-                    background: #ffffff url('../img/drag.png?93ec7e23f795') no-repeat 11px 50%; }
66
-                    .nodelist li em {
67
-                      font-size: 0.5em;
68
-                      line-height: 1em;
69
-                      color: #999;
70
-                      font-style: normal;
71
-                      margin-left: 0.8em; }
72
-                      .nodelist li i {
73
-                        margin-right: 5px;
74
-                        vertical-align: middle; }
75
-                        .nodelist li a.btn {
76
-                          -webkit-box-sizing: border-box;
77
-                          -moz-box-sizing: border-box;
78
-                          box-sizing: border-box;
79
-                          font-size: 11px;
80
-                          line-height: 12px;
81
-                          padding: 2px 5px 3px;
82
-                          margin-right: 1px;
83
-                          width: 18px;
84
-                          text-align: center;
85
-                          right: 5px;
86
-                          vertical-align: middle;
87
-                          float: right; }
88
-                          .nodelist li a.btn:before {
89
-                            content: "+"; }
90
-                  .nodelist li.ui-sortable-helper {
91
-                    background-color: #def; }
92
-                    .nodelist li.ui-state-highlight {
93
-                      border: 1px dotted #cccccc;
94
-                      background: #efefef;
95
-                      height: 0.5em; }
96
-                      .nodelist li:after {
97
-                        visibility: hidden;
98
-                        display: block;
99
-                        font-size: 0;
100
-                        content: " ";
101
-                        clear: both;
102
-                        height: 0; }
47
+    padding: 6px;
48
+    background: #eee;
49
+    border: 1px solid #dddddd;
50
+    min-height: 2em;
51
+    width: auto !important;
52
+    -webkit-box-sizing: border-box;
53
+    -moz-box-sizing: border-box;
54
+    box-sizing: border-box;
55
+}
56
+
57
+.nodelist li {
58
+    width: 226px;
59
+    list-style-type: none;
60
+    margin: 6px auto;
61
+    padding: 3px;
62
+    background: #ffffff;
63
+    border: 1px solid #aaa;
64
+    line-height: 18px;
65
+    border-radius: 3px;
66
+    cursor: move;
67
+    padding-left: 23px;
68
+    background: #ffffff url('../img/drag.png?93ec7e23f795') no-repeat 11px 50%;
69
+}
70
+
71
+.nodelist li em {
72
+    font-size: 0.5em;
73
+    line-height: 1em;
74
+    color: #999;
75
+    font-style: normal;
76
+    margin-left: 0.8em;
77
+}
78
+
79
+.nodelist li i {
80
+    margin-right: 5px;
81
+    vertical-align: middle;
82
+}
103 83
 
84
+.nodelist li a.btn {
85
+    -webkit-box-sizing: border-box;
86
+    -moz-box-sizing: border-box;
87
+    box-sizing: border-box;
88
+    font-size: 11px;
89
+    line-height: 12px;
90
+    padding: 2px 5px 3px;
91
+    margin-right: 1px;
92
+    width: 18px;
93
+    text-align: center;
94
+    right: 5px;
95
+    vertical-align: middle;
96
+    float: right;
97
+}
98
+
99
+.nodelist li a.btn:before {
100
+    content: "+";
101
+}
102
+
103
+.nodelist li.ui-sortable-helper {
104
+    background-color: #def;
105
+}
106
+
107
+.nodelist li.ui-state-highlight {
108
+    border: 1px dotted #cccccc;
109
+    background: #efefef;
110
+    height: 0.5em;
111
+}
104 112
 
113
+.nodelist li:after {
114
+    visibility: hidden;
115
+    display: block;
116
+    font-size: 0;
117
+    content: " ";
118
+    clear: both;
119
+    height: 0;
120
+}

+ 1
- 1
gbpui/static/dashboard/gbpui/gbpui.module.js View File

@@ -13,7 +13,7 @@
13 13
  */
14 14
 (function () {
15 15
     angular
16
-        .module('gbpui', ['gbpui.transfer-table-bridge'])
16
+        .module('gbpui', ['gbpui.transfer-table-bridge', 'gbpui.group-member'])
17 17
         .config(module_config);
18 18
 
19 19
     module_config.$inject = ["$provide","$windowProvider"];

+ 47
- 0
gbpui/static/dashboard/gbpui/group-member/group-member-gbp-api.js View File

@@ -0,0 +1,47 @@
1
+/**
2
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+ * not use this file except in compliance with the License. You may obtain
4
+ * a copy of the License at
5
+ *
6
+ *    http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+ * License for the specific language governing permissions and limitations
12
+ * under the License.
13
+ */
14
+
15
+(function () {
16
+    angular
17
+        .module('gbpui.group-member')
18
+        .factory('gbpui.group-member.gbp-api', gbpApi);
19
+
20
+    gbpApi.$inject = [
21
+        'horizon.framework.util.http.service',
22
+        'horizon.framework.widgets.toast.service'
23
+    ];
24
+
25
+    function gbpApi(apiService, toastService) {
26
+
27
+        function getPolicyTargetGroups() {
28
+            return apiService.get('project/policytargets/policy_target_groups')
29
+                .error(function () {
30
+                    toastService.add('error', gettext('Unable to retrieve Policy Target Groups'))
31
+                });
32
+        }
33
+
34
+        function createServer(newServer) {
35
+            return apiService
36
+                .post('project/policytargets/launch_instance/', newServer)
37
+                .error(function () {
38
+                    toastService.add('error', gettext('Unable to create Instance'))
39
+                })
40
+        }
41
+
42
+        return {
43
+            getPolicyTargetGroups: getPolicyTargetGroups,
44
+            createServer: createServer
45
+        };
46
+    }
47
+})();

+ 34
- 0
gbpui/static/dashboard/gbpui/group-member/group-member-launch-context.service.js View File

@@ -0,0 +1,34 @@
1
+/**
2
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+ * not use this file except in compliance with the License. You may obtain
4
+ * a copy of the License at
5
+ *
6
+ *    http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+ * License for the specific language governing permissions and limitations
12
+ * under the License.
13
+ */
14
+
15
+
16
+// Workaround service that allows for the modal launchContext object to be used
17
+// in any controller/service.
18
+(function () {
19
+    'use strict';
20
+    angular
21
+        .module('gbpui.group-member')
22
+        .factory('gbpui.group-member.launch-context.service', launchContextService);
23
+
24
+
25
+    launchContextService.$inject = [];
26
+
27
+    function launchContextService() {
28
+        return {
29
+            launchContext: {
30
+                defaults: []
31
+            }
32
+        };
33
+    }
34
+})();

+ 71
- 0
gbpui/static/dashboard/gbpui/group-member/group-member-launch-instance-model.patcher.js View File

@@ -0,0 +1,71 @@
1
+/**
2
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+ * not use this file except in compliance with the License. You may obtain
4
+ * a copy of the License at
5
+ *
6
+ *    http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+ * License for the specific language governing permissions and limitations
12
+ * under the License.
13
+ */
14
+
15
+(function () {
16
+    'use strict';
17
+
18
+    angular
19
+        .module('gbpui.group-member')
20
+        .config(launchInstanceModelPatcher);
21
+
22
+    launchInstanceModelPatcher.$inject = ['$provide'];
23
+
24
+    function launchInstanceModelPatcher($provide) {
25
+        $provide.decorator(
26
+            'launchInstanceModel',
27
+            ['$delegate', 'gbpui.group-member.gbp-api', '$q',
28
+                'gbpui.group-member.launch-context.service',
29
+                function($delegate, gbpApi, $q, launchContextService) {
30
+
31
+                var createInstance = $delegate.createInstance;
32
+                $delegate.createInstance = function() {
33
+                    // This is a workaround for new instance initalization
34
+                    // inside 'launchInstanceModel' done by functions not
35
+                    // exposed by the $delegate
36
+                    $delegate.newInstanceSpec.group_policy_targets =
37
+                        $delegate.allocated_group_policy_targets;
38
+                    return createInstance();
39
+                };
40
+
41
+                $delegate.group_policy_targets = [];
42
+                $delegate.allocated_group_policy_targets = [];
43
+
44
+                var initialize = $delegate.initialize;
45
+
46
+                $delegate.initialize = function(deep) {
47
+                    $delegate.group_policy_targets.length = 0;
48
+                    $delegate.allocated_group_policy_targets.length = 0;
49
+
50
+                    $q.all([gbpApi.getPolicyTargetGroups().then(function(data){
51
+                            var defaults =
52
+                                launchContextService.launchContext.defaults;
53
+
54
+                            angular.forEach(data.data, function(value, index) {
55
+                                $delegate.group_policy_targets.push(value);
56
+
57
+                                if(defaults.indexOf(value.id) > -1) {
58
+                                    $delegate.allocated_group_policy_targets
59
+                                        .push(value);
60
+                                }
61
+                            });
62
+                        }), initialize(deep)
63
+                    ]);
64
+                };
65
+
66
+                return $delegate
67
+            }]
68
+        )
69
+    }
70
+
71
+})();

+ 41
- 0
gbpui/static/dashboard/gbpui/group-member/group-member-launch-instance-service.patcher.js View File

@@ -0,0 +1,41 @@
1
+/**
2
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+ * not use this file except in compliance with the License. You may obtain
4
+ * a copy of the License at
5
+ *
6
+ *    http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+ * License for the specific language governing permissions and limitations
12
+ * under the License.
13
+ */
14
+
15
+(function () {
16
+    'use strict';
17
+
18
+    angular.module('gbpui.group-member')
19
+        .config(launchInstanceServicePatcher);
20
+
21
+    launchInstanceServicePatcher.$inject = ['$provide'];
22
+
23
+    function launchInstanceServicePatcher($provide) {
24
+        $provide.decorator(
25
+            'horizon.dashboard.project.workflow.launch-instance.modal.service',
26
+            ['$delegate', 'gbpui.group-member.launch-context.service', function(
27
+                $delegate, launchContextService
28
+            ) {
29
+
30
+                var open = $delegate.open;
31
+                $delegate.open = function(launchContext) {
32
+                    angular.extend(launchContextService.launchContext,
33
+                        launchContext);
34
+                    return open(launchContext);
35
+                };
36
+
37
+                return $delegate
38
+            }]
39
+        );
40
+    }
41
+})();

+ 65
- 0
gbpui/static/dashboard/gbpui/group-member/group-member-launch-instance.patcher.js View File

@@ -0,0 +1,65 @@
1
+/**
2
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+ * not use this file except in compliance with the License. You may obtain
4
+ * a copy of the License at
5
+ *
6
+ *    http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+ * License for the specific language governing permissions and limitations
12
+ * under the License.
13
+ */
14
+
15
+(function () {
16
+    'use strict';
17
+
18
+    angular
19
+        .module('gbpui.group-member')
20
+        .config(launchInstancePatcher);
21
+
22
+    launchInstancePatcher.$inject = ["$provide", 'gbpui.basePath'];
23
+
24
+    function launchInstancePatcher($provide, basePath) {
25
+       $provide.decorator(
26
+           'horizon.dashboard.project.workflow.launch-instance.workflow',
27
+           ["$delegate", 'horizon.app.core.workflow.factory',
28
+               function($delegate, dashboardWorkflow)
29
+               {
30
+                var steps = $delegate.steps;
31
+                var gbstep = {
32
+                    id: 'gbp',
33
+                    title: gettext('GBP'),
34
+                    templateUrl: basePath + 'group-member/group-member.html',
35
+                    helpUrl: basePath + 'group-member/group-member.help.html',
36
+                    formName: 'gbpForm'
37
+                };
38
+
39
+                // Finds and replaces the Network and Port wizard pages with
40
+                // the GBP wizard page
41
+                var networkIndex = -1;
42
+                var portIndex = -1;
43
+                angular.forEach(steps, function (step) {
44
+                    if(step.id == 'networks') {
45
+                        networkIndex = steps.indexOf(step)
46
+                    } else if(step.id == 'ports') {
47
+                        portIndex = steps.indexOf(step);
48
+                    }
49
+                });
50
+
51
+                if(networkIndex > -1) {
52
+                    steps.splice(networkIndex, 1, gbstep);
53
+                }
54
+
55
+                if(portIndex > -1) {
56
+                    steps.splice(portIndex, 1);
57
+                }
58
+
59
+                var result = dashboardWorkflow($delegate);
60
+                return result;
61
+           }]
62
+       );
63
+    }
64
+
65
+})();

+ 39
- 0
gbpui/static/dashboard/gbpui/group-member/group-member-nova-api.patcher.js View File

@@ -0,0 +1,39 @@
1
+/**
2
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+ * not use this file except in compliance with the License. You may obtain
4
+ * a copy of the License at
5
+ *
6
+ *    http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+ * License for the specific language governing permissions and limitations
12
+ * under the License.
13
+ */
14
+
15
+(function () {
16
+    'use strict';
17
+
18
+    angular
19
+        .module('gbpui.group-member')
20
+        .config(novaApiPatcher);
21
+
22
+    novaApiPatcher.$inject = ['$provide'];
23
+
24
+    function novaApiPatcher($provide) {
25
+        $provide.decorator(
26
+            'horizon.app.core.openstack-service-api.nova',
27
+            ['$delegate', 'gbpui.group-member.gbp-api',
28
+            function($delegate, gbpApi) {
29
+                var createServer = $delegate.createServer;
30
+
31
+                $delegate.createServer = function(newServer) {
32
+                    return gbpApi.createServer(newServer);
33
+                };
34
+
35
+                return $delegate
36
+            }]
37
+        );
38
+    }
39
+})();

+ 64
- 0
gbpui/static/dashboard/gbpui/group-member/group-member.controller.js View File

@@ -0,0 +1,64 @@
1
+/**
2
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+ * not use this file except in compliance with the License. You may obtain
4
+ * a copy of the License at
5
+ *
6
+ *    http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+ * License for the specific language governing permissions and limitations
12
+ * under the License.
13
+ */
14
+
15
+(function() {
16
+    angular
17
+        .module('gbpui.group-member')
18
+        .controller('GBPController', gbpController);
19
+
20
+
21
+    gbpController.$inject = [
22
+        '$scope',
23
+        'horizon.dashboard.project.workflow.launch-instance.modal.service',
24
+        'gbpui.group-member.launch-context.service'
25
+    ];
26
+
27
+    function gbpController(
28
+        $scope, launchInstanceModalService, launchContextService)
29
+    {
30
+        var ctrl = this;
31
+
32
+        ctrl.launchContext = launchContextService.launchContext;
33
+        ctrl.inputIPs = {
34
+
35
+        };
36
+
37
+        ctrl.tableHelp = {
38
+            noneAllocText: gettext('Select a group-member from the table below'),
39
+            availHelpText: gettext('Select one or more groups'),
40
+            noneAvailText: gettext('No groups available'),
41
+        };
42
+
43
+        ctrl.tableLimits = {
44
+            maxAllocation: -1
45
+        };
46
+
47
+        ctrl.addFixedIp = function(row) {
48
+            var ipAddress = ctrl.inputIPs[row.id];
49
+            row.fixed_ip = ipAddress;
50
+        };
51
+
52
+        ctrl.removeFixedIp = function(row) {
53
+            delete row['fixed_ip'];
54
+        };
55
+
56
+        ctrl.tableData = {
57
+            available: $scope.model.group_policy_targets,
58
+            allocated: $scope.model.allocated_group_policy_targets,
59
+            displayedAvailable: [],
60
+            displayedAllocated: [],
61
+            minItems: 1
62
+        };
63
+    }
64
+})();

+ 7
- 0
gbpui/static/dashboard/gbpui/group-member/group-member.help.html View File

@@ -0,0 +1,7 @@
1
+<div>
2
+  <p translate>
3
+    Instances belonging to network groups will automatically have network
4
+    policy rule sets applied to them as defined in the individual network
5
+    groups.
6
+  </p>
7
+</div>

+ 187
- 0
gbpui/static/dashboard/gbpui/group-member/group-member.html View File

@@ -0,0 +1,187 @@
1
+<div ng-controller="GBPController as ctrl">
2
+    <p class="step-description" translate>Select one or more policy groups for the new instance.</p>
3
+
4
+    <transfer-table tr-model="ctrl.tableData" limits="ctrl.tableLimits"
5
+                    help-text="ctrl.tableHelp">
6
+        <allocated ng-model="ctrl.tableData.allocated.length"
7
+                   validate-number-min="{$ ctrl.tableData.minItems $}">
8
+            <table st-table="ctrl.tableData.allocated"
9
+                   hz-table
10
+                   class="table table-striped table-rsp table-detail">
11
+                <thead>
12
+                <tr>
13
+                    <th>Groups</th>
14
+                </tr>
15
+                </thead>
16
+                <tbody>
17
+
18
+                <tr ng-if="ctrl.tableData.allocated.length === 0">
19
+                    <td colspan="8">
20
+                        <div class="no-rows-help">
21
+                            {$ ::trCtrl.helpText.noneAllocText $}
22
+                        </div>
23
+                    </td>
24
+                </tr>
25
+
26
+
27
+                <tr ng-repeat-start="row in ctrl.tableData.allocated track by row.id">
28
+                    <td class="expander">
29
+                        <span class="fa fa-chevron-right" hz-expand-detail
30
+                              title="{$ 'Click to see more details'|translate $}"></span>
31
+                    </td>
32
+                    <td>{$ row.name_or_id $} <span ng-if="row.fixed_ip">({$ row.fixed_ip $})</span>
33
+                    </td>
34
+                    <td class="actions_column">
35
+                        <action-list>
36
+                            <action action-classes="'btn btn-default'"
37
+                                    callback="trCtrl.deallocate"
38
+                                    item="row">
39
+                                <span class="fa fa-arrow-down"></span>
40
+                            </action>
41
+                        </action-list>
42
+                    </td>
43
+                </tr>
44
+
45
+                <tr ng-repeat-end class="detail-row">
46
+                    <td colspan="9" class="detail">
47
+                        <div class="container-fluid">
48
+                            <div class="row">
49
+                                <div class="col-md-12">
50
+                                    <span class="ng-scope">Subnets</span>
51
+                                </div>
52
+                            </div>
53
+                            <div class="row">
54
+                                <div class="col-md-12">
55
+                                    <table class="table table-bordered text-center subnet-table">
56
+                                        <thead>
57
+                                        <tr>
58
+                                            <th rowspan="2">
59
+                                                CIDR
60
+                                            </th>
61
+                                            <th colspan="2">
62
+                                                Allocation pools
63
+                                            </th>
64
+                                        </tr>
65
+                                        <tr>
66
+                                            <th>Start</th>
67
+                                            <th>End</th>
68
+                                        </tr>
69
+                                        </thead>
70
+                                        <tbody>
71
+                                        <tr ng-repeat-start="subnet in row.subnets">
72
+                                            <td>
73
+                                                {$ subnet.cidr $}
74
+                                            </td>
75
+                                            <td>
76
+                                                {$
77
+                                                subnet.allocation_pools.length
78
+                                                > 0 ?
79
+                                                subnet.allocation_pools[0].start
80
+                                                : '' $}
81
+                                            </td>
82
+                                            <td>
83
+                                                {$
84
+                                                subnet.allocation_pools.length
85
+                                                > 0 ?
86
+                                                subnet.allocation_pools[0].end
87
+                                                : '' $}
88
+                                            </td>
89
+                                        </tr>
90
+                                        <tr ng-repeat="allocation_pool in subnet.allocation_pools | limitTo: (1 - subnet.allocation_pools.length)">
91
+                                            <td></td>
92
+                                            <td>
93
+                                                {$ allocation_pool.start $}
94
+                                            </td>
95
+                                            <td>
96
+                                                {$ allocation_pool.end $}
97
+                                            </td>
98
+                                        </tr>
99
+
100
+                                        <tr ng-if="0" ng-repeat-end>
101
+
102
+                                        </tr>
103
+
104
+                                        </tbody>
105
+                                    </table>
106
+
107
+                                </div>
108
+                            </div>
109
+                            <div class="row">
110
+                                <div class="col-md-12">
111
+                                    <span class="ng-scope">Fixed IP</span>
112
+                                </div>
113
+                            </div>
114
+                            <div class="row">
115
+                                <div class="col-md-12">
116
+                                    <form class="form-inline">
117
+                                        <div class="form-group">
118
+                                            <input class="form-control"
119
+                                                   type="text"
120
+                                                   ng-model="ctrl.inputIPs[row.id]">
121
+                                            <input class="form-control"
122
+                                                   type="button" value="Set"
123
+                                                   ng-click="ctrl.addFixedIp(row)">
124
+                                            <input class="form-control"
125
+                                                   type="button" value="Unset"
126
+                                                   ng-click="ctrl.removeFixedIp(row)">
127
+                                        </div>
128
+                                    </form>
129
+                                </div>
130
+                            </div>
131
+                        </div>
132
+                    </td>
133
+                </tr>
134
+
135
+                </tbody>
136
+            </table>
137
+
138
+        </allocated>
139
+        <available>
140
+            <table st-table="ctrl.tableData.available"
141
+                   hz-table
142
+                   class="table table-striped table-rsp table-detail">
143
+                <thead>
144
+                <tr>
145
+                    <th>Groups</th>
146
+                </tr>
147
+                </thead>
148
+                <tbody>
149
+                <tr ng-if="trCtrl.numAvailable() === 0">
150
+                  <td colspan="8">
151
+                    <div class="no-rows-help">
152
+                      {$ ::trCtrl.helpText.noneAvailText $}
153
+                    </div>
154
+                  </td>
155
+                </tr>
156
+
157
+                <tr ng-repeat-start="row in ctrl.tableData.available track by row.id"
158
+                    ng-if="!trCtrl.allocatedIds[row.id]">
159
+                    <td>{$ row.name_or_id $}</td>
160
+                    <td class="actions_column">
161
+                        <action-list button-tooltip="row.warningMessage"
162
+                                     bt-model="ctrl.tooltipModel"
163
+                                     bt-disabled="!row.disabled"
164
+                                     warning-classes="'invalid'">
165
+                            <notifications>
166
+                                        <span class="fa fa-exclamation-circle invalid"
167
+                                              ng-show="row.disabled"></span>
168
+                            </notifications>
169
+                            <action action-classes="'btn btn-default'"
170
+                                    callback="trCtrl.allocate"
171
+                                    item="row"
172
+                                    disabled="row.disabled">
173
+                                <span class="fa fa-arrow-up"></span>
174
+                            </action>
175
+                        </action-list>
176
+                    </td>
177
+                </tr>
178
+                <tr ng-repeat-end class="detail-row">
179
+
180
+                </tr>
181
+                </tbody>
182
+            </table>
183
+
184
+
185
+        </available>
186
+    </transfer-table>
187
+</div>

+ 25
- 0
gbpui/static/dashboard/gbpui/group-member/group-member.module.js View File

@@ -0,0 +1,25 @@
1
+/**
2
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+ * not use this file except in compliance with the License. You may obtain
4
+ * a copy of the License at
5
+ *
6
+ *    http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+ * License for the specific language governing permissions and limitations
12
+ * under the License.
13
+ */
14
+(function(){
15
+    angular
16
+        .module('gbpui.group-member', [])
17
+        .config(module_config);
18
+
19
+    module_config.$inject = ["$provide","$windowProvider"];
20
+
21
+    function module_config($provide, $windowProvider) {
22
+        var path = $windowProvider.$get().STATIC_URL + 'dashboard/gbpui/';
23
+        $provide.constant('gbpui.basePath', path);
24
+    }
25
+})();

+ 12
- 0
gbpui/static/dashboard/gbpui/group-member/group-member.scss View File

@@ -0,0 +1,12 @@
1
+.subnet-table {
2
+  width: 100%;
3
+}
4
+
5
+.subnet-table td, .subnet-table th {
6
+  background-color: white;
7
+  text-align: center;
8
+}
9
+
10
+.subnet-table > thead > tr > th {
11
+  padding:2px;
12
+}

gbpui/static/dashboard/gbpui/transfer-table-bridge/transfer-table.module.js → gbpui/static/dashboard/gbpui/transfer-table-bridge/transfer-table-bridge.module.js View File


Loading…
Cancel
Save