Merge lp:~bcsaller/juju-gui/renderBundle2 into lp:~bcsaller/juju-gui/charmFind
- renderBundle2
- Merge into charmFind
Proposed by
Benjamin Saller
Status: | Needs review |
---|---|
Proposed branch: | lp:~bcsaller/juju-gui/renderBundle2 |
Merge into: | lp:~bcsaller/juju-gui/charmFind |
Diff against target: |
5534 lines (+2257/-990) 76 files modified
app/app.js (+68/-4) app/assets/javascripts/d3-components.js (+12/-1) app/config-debug.js (+1/-1) app/config-prod.js (+1/-1) app/index.html (+3/-3) app/models/charm.js (+1/-1) app/models/model-controller.js (+30/-6) app/models/models.js (+19/-0) app/modules-debug.js (+3/-0) app/store/charm.js (+25/-0) app/store/env/env.js (+2/-2) app/store/env/fakebackend.js (+7/-1) app/store/env/go.js (+23/-5) app/store/env/python.js (+6/-1) app/store/env/sandbox.js (+4/-1) app/subapps/browser/browser.js (+51/-19) app/subapps/browser/templates/editorial.handlebars (+0/-1) app/subapps/browser/views/editorial.js (+0/-31) app/subapps/browser/views/minimized.js (+15/-8) app/subapps/browser/views/view.js (+7/-53) app/templates/category-icons.partial (+0/-57) app/templates/ghost-config-viewlet.handlebars (+12/-0) app/templates/inspector-header.handlebars (+3/-1) app/templates/service-constraints-viewlet.partial (+2/-2) app/templates/service-overview-constraints.handlebars (+24/-19) app/views/charm-panel.js (+1/-1) app/views/charm.js (+3/-0) app/views/environment.js (+2/-2) app/views/ghost-inspector.js (+17/-4) app/views/inspector.js (+31/-8) app/views/topology/bundle.js (+368/-0) app/views/topology/relation.js (+3/-0) app/views/topology/service.js (+424/-416) app/views/topology/topology.js (+15/-12) app/views/utils.js (+5/-4) app/views/viewlets/inspector-header.js (+5/-18) app/views/viewlets/service-constraints.js (+1/-0) app/views/viewlets/service-ghost.js (+17/-10) app/widgets/viewmode-controls.js (+87/-0) docs/d3-component-framework.rst (+5/-0) lib/views/browser/charm-full.less (+0/-1) lib/views/browser/charm-token.less (+0/-9) lib/views/juju-inspector.less (+15/-7) test/data/wp-deployer.yaml (+41/-0) test/index.html (+2/-1) test/test_app.js (+26/-13) test/test_browser_app.js (+101/-1) test/test_browser_editorial.js (+0/-20) test/test_bundle_module.js (+98/-0) test/test_charm_panel.js (+2/-1) test/test_charm_store.js (+30/-1) test/test_charm_view.js (+11/-12) test/test_endpoints.js (+14/-9) test/test_env.js (+2/-2) test/test_env_go.js (+28/-8) test/test_env_python.js (+13/-2) test/test_fakebackend.js (+27/-1) test/test_ghost_inspector.js (+74/-8) test/test_inspector_constraints.js (+12/-15) test/test_inspector_overview.js (+73/-25) test/test_inspector_settings.js (+2/-1) test/test_login.js (+19/-18) test/test_model.js (+43/-51) test/test_model_controller.js (+31/-3) test/test_notifications.js (+2/-2) test/test_sandbox_go.js (+67/-29) test/test_sandbox_python.js (+41/-3) test/test_service_config_view.js (+5/-4) test/test_service_view.js (+18/-24) test/test_startup.js.bottom (+6/-2) test/test_topology.js (+37/-0) test/test_unit_view.js (+21/-20) test/test_viewlet_manager.js (+1/-1) test/test_viewmode_controls_widget.js (+82/-0) test/test_websocket_logging.js (+10/-2) undocumented (+0/-1) |
To merge this branch: | bzr merge lp:~bcsaller/juju-gui/renderBundle2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benjamin Saller | Pending | ||
Review via email:
|
Commit message
Description of the change
Bundle Topology
Provide a reusable view of topologies with a much more
limited and non-interactive service module. The tests show
how this works but don't wire the view into any current
UI which comes later.
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Benjamin Saller (bcsaller) wrote : | # |
Please take a look.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Benjamin Saller (bcsaller) wrote : | # |
diff is totally broken, I'll try to repair this.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Benjamin Saller (bcsaller) wrote : | # |
Please take a look.
- 980. By Benjamin Saller
-
merge trunk
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Benjamin Saller (bcsaller) wrote : | # |
Please take a look.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Benjamin Saller (bcsaller) wrote : | # |
After repeated attempts this is still not getting a proper diff for me.
bzr diff -r ancestor:
will show it though.
Sorry and thanks.
- 981. By Benjamin Saller
-
more tests
- 982. By Benjamin Saller
-
missing file
- 983. By Benjamin Saller
-
lint, docs, various cleanups
Unmerged revisions
- 983. By Benjamin Saller
-
lint, docs, various cleanups
- 982. By Benjamin Saller
-
missing file
- 981. By Benjamin Saller
-
more tests
- 980. By Benjamin Saller
-
merge trunk
- 979. By Benjamin Saller
-
clean up diff, spotchecks
- 978. By Benjamin Saller
-
lint+
- 977. By Benjamin Saller
-
touchups
- 976. By Benjamin Saller
-
merge trunk
- 975. By Benjamin Saller
-
wip
- 974. By Benjamin Saller
-
wip
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'app/app.js' | |||
2 | --- app/app.js 2013-08-12 19:24:55 +0000 | |||
3 | +++ app/app.js 2013-08-30 19:48:17 +0000 | |||
4 | @@ -39,7 +39,8 @@ | |||
5 | 39 | var juju = Y.namespace('juju'), | 39 | var juju = Y.namespace('juju'), |
6 | 40 | models = Y.namespace('juju.models'), | 40 | models = Y.namespace('juju.models'), |
7 | 41 | views = Y.namespace('juju.views'), | 41 | views = Y.namespace('juju.views'), |
9 | 42 | utils = views.utils; | 42 | utils = views.utils, |
10 | 43 | widgets = Y.namespace('juju.widgets'); | ||
11 | 43 | 44 | ||
12 | 44 | /** | 45 | /** |
13 | 45 | * The main app class. | 46 | * The main app class. |
14 | @@ -398,7 +399,8 @@ | |||
15 | 398 | 399 | ||
16 | 399 | // Set up a new modelController instance. | 400 | // Set up a new modelController instance. |
17 | 400 | this.modelController = new juju.ModelController({ | 401 | this.modelController = new juju.ModelController({ |
19 | 401 | db: this.db | 402 | db: this.db, |
20 | 403 | store: this.get('store') | ||
21 | 402 | }); | 404 | }); |
22 | 403 | 405 | ||
23 | 404 | // Update the on-screen environment name provided in the configuration, | 406 | // Update the on-screen environment name provided in the configuration, |
24 | @@ -1082,7 +1084,6 @@ | |||
25 | 1082 | this.db.environment.set('defaultSeries', evt.newVal); | 1084 | this.db.environment.set('defaultSeries', evt.newVal); |
26 | 1083 | }, | 1085 | }, |
27 | 1084 | 1086 | ||
28 | 1085 | |||
29 | 1086 | /** | 1087 | /** |
30 | 1087 | Display the Environment Name. | 1088 | Display the Environment Name. |
31 | 1088 | 1089 | ||
32 | @@ -1126,10 +1127,71 @@ | |||
33 | 1126 | // route on root namespaced paths and this check will no longer | 1127 | // route on root namespaced paths and this check will no longer |
34 | 1127 | // be needed | 1128 | // be needed |
35 | 1128 | this.renderEnvironment = false; | 1129 | this.renderEnvironment = false; |
36 | 1130 | |||
37 | 1131 | // XXX bug:1217383 | ||
38 | 1132 | // We're hiding the subapp from view, but people want to be able to | ||
39 | 1133 | // click on the viewmode controls. We handle that here as a temp | ||
40 | 1134 | // hack until the old :gui: views are gone and we've moved to the | ||
41 | 1135 | // serviceInspector. Then the browser will always be around and can | ||
42 | 1136 | // handle this widget for us. This is horrible and we know it. When | ||
43 | 1137 | // the idea of 'hidden' is removed with the old views this hack will | ||
44 | 1138 | // go away with it. | ||
45 | 1139 | if (!this._controlEvents || this._controlEvents.length === 0) { | ||
46 | 1140 | this._controls = new widgets.ViewmodeControls({ | ||
47 | 1141 | currentViewmode: subapps.charmbrowser._viewState.viewmode | ||
48 | 1142 | }); | ||
49 | 1143 | this._controls.render(); | ||
50 | 1144 | this._controlEvents = []; | ||
51 | 1145 | this._controlEvents.push( | ||
52 | 1146 | this._controls.on( | ||
53 | 1147 | this._controls.EVT_FULLSCREEN, | ||
54 | 1148 | function(ev) { | ||
55 | 1149 | // Navigate away from anything in :gui: and to the | ||
56 | 1150 | // /fullscreen in :charmbrowser: | ||
57 | 1151 | this._controls._updateActiveNav('fullscreen'); | ||
58 | 1152 | this.navigate(this.nsRouter.url({ | ||
59 | 1153 | gui: '/', | ||
60 | 1154 | charmbrowser: '/fullscreen' | ||
61 | 1155 | }), { overrideAllNamespaces: true }); | ||
62 | 1156 | |||
63 | 1157 | }, this | ||
64 | 1158 | ) | ||
65 | 1159 | ); | ||
66 | 1160 | this._controlEvents.push( | ||
67 | 1161 | this._controls.on( | ||
68 | 1162 | this._controls.EVT_SIDEBAR, | ||
69 | 1163 | function(ev) { | ||
70 | 1164 | // Navigate away from anything in :gui: and to the | ||
71 | 1165 | // /sidebar in :charmbrowser: | ||
72 | 1166 | this._controls._updateActiveNav('sidebar'); | ||
73 | 1167 | this.navigate(this.nsRouter.url({ | ||
74 | 1168 | gui: '/', | ||
75 | 1169 | charmbrowser: '/sidebar' | ||
76 | 1170 | }), { overrideAllNamespaces: true }); | ||
77 | 1171 | }, this | ||
78 | 1172 | ) | ||
79 | 1173 | ); | ||
80 | 1174 | } | ||
81 | 1175 | |||
82 | 1129 | } else { | 1176 | } else { |
83 | 1130 | charmbrowser.hidden = false; | 1177 | charmbrowser.hidden = false; |
84 | 1131 | this.renderEnvironment = true; | 1178 | this.renderEnvironment = true; |
85 | 1179 | |||
86 | 1180 | // XXX bug:1217383 | ||
87 | 1181 | // Destroy the controls widget we might have had around for a bit. | ||
88 | 1182 | if (this._controlEvents) { | ||
89 | 1183 | this._controlEvents.forEach(function(ev) { | ||
90 | 1184 | ev.detach(); | ||
91 | 1185 | }); | ||
92 | 1186 | // reset the list to no events. | ||
93 | 1187 | this._controlEvents = []; | ||
94 | 1188 | } | ||
95 | 1189 | |||
96 | 1190 | if (this._controls) { | ||
97 | 1191 | this._controls.destroy(); | ||
98 | 1192 | } | ||
99 | 1132 | } | 1193 | } |
100 | 1194 | |||
101 | 1133 | charmbrowser.updateVisible(); | 1195 | charmbrowser.updateVisible(); |
102 | 1134 | } | 1196 | } |
103 | 1135 | 1197 | ||
104 | @@ -1444,6 +1506,8 @@ | |||
105 | 1444 | 'model-controller', | 1506 | 'model-controller', |
106 | 1445 | 'FileSaver', | 1507 | 'FileSaver', |
107 | 1446 | 'juju-inspector-widget', | 1508 | 'juju-inspector-widget', |
109 | 1447 | 'juju-ghost-inspector' | 1509 | 'juju-ghost-inspector', |
110 | 1510 | 'juju-view-bundle', | ||
111 | 1511 | 'viewmode-controls' | ||
112 | 1448 | ] | 1512 | ] |
113 | 1449 | }); | 1513 | }); |
114 | 1450 | 1514 | ||
115 | === modified file 'app/assets/javascripts/d3-components.js' | |||
116 | --- app/assets/javascripts/d3-components.js 2013-08-08 22:36:30 +0000 | |||
117 | +++ app/assets/javascripts/d3-components.js 2013-08-30 19:48:17 +0000 | |||
118 | @@ -132,6 +132,7 @@ | |||
119 | 132 | 132 | ||
120 | 133 | modEvents = module.events; | 133 | modEvents = module.events; |
121 | 134 | this.events[module.name] = modEvents; | 134 | this.events[module.name] = modEvents; |
122 | 135 | |||
123 | 135 | this.bind(module.name); | 136 | this.bind(module.name); |
124 | 136 | module.componentBound(); | 137 | module.componentBound(); |
125 | 137 | 138 | ||
126 | @@ -286,6 +287,7 @@ | |||
127 | 286 | * @method bind | 287 | * @method bind |
128 | 287 | **/ | 288 | **/ |
129 | 288 | bind: function(moduleName) { | 289 | bind: function(moduleName) { |
130 | 290 | if (this.get('interactive') === false) { return; } | ||
131 | 289 | var eventSet = this.events, | 291 | var eventSet = this.events, |
132 | 290 | filtered = {}; | 292 | filtered = {}; |
133 | 291 | 293 | ||
134 | @@ -319,6 +321,7 @@ | |||
135 | 319 | owns = Y.Object.owns, | 321 | owns = Y.Object.owns, |
136 | 320 | module; | 322 | module; |
137 | 321 | 323 | ||
138 | 324 | if (this.get('interactive') === false) { return; } | ||
139 | 322 | if (!modEvents || !modEvents.d3) { | 325 | if (!modEvents || !modEvents.d3) { |
140 | 323 | return; | 326 | return; |
141 | 324 | } | 327 | } |
142 | @@ -520,7 +523,15 @@ | |||
143 | 520 | } | 523 | } |
144 | 521 | }, { | 524 | }, { |
145 | 522 | ATTRS: { | 525 | ATTRS: { |
147 | 523 | container: {} | 526 | container: {}, |
148 | 527 | /** | ||
149 | 528 | Boolean indicating if bind should be allowed to actually | ||
150 | 529 | bind events for the component. | ||
151 | 530 | |||
152 | 531 | @property {Boolean} interactive | ||
153 | 532 | @default true | ||
154 | 533 | */ | ||
155 | 534 | interactive: {value: true} | ||
156 | 524 | } | 535 | } |
157 | 525 | 536 | ||
158 | 526 | }); | 537 | }); |
159 | 527 | 538 | ||
160 | === modified file 'app/config-debug.js' | |||
161 | --- app/config-debug.js 2013-08-14 14:34:19 +0000 | |||
162 | +++ app/config-debug.js 2013-08-30 19:48:17 +0000 | |||
163 | @@ -40,7 +40,7 @@ | |||
164 | 40 | socket_port: 8081, | 40 | socket_port: 8081, |
165 | 41 | user: 'admin', | 41 | user: 'admin', |
166 | 42 | password: 'admin', | 42 | password: 'admin', |
168 | 43 | apiBackend: 'python', // Value can be 'python' or 'go'. | 43 | apiBackend: 'go', // Value can be 'python' or 'go'. |
169 | 44 | sandbox: true, | 44 | sandbox: true, |
170 | 45 | // When in sandbox mode should we create events to simulate a live env. | 45 | // When in sandbox mode should we create events to simulate a live env. |
171 | 46 | // You can also use the :flags:/simulateEvents feature flag. | 46 | // You can also use the :flags:/simulateEvents feature flag. |
172 | 47 | 47 | ||
173 | === modified file 'app/config-prod.js' | |||
174 | --- app/config-prod.js 2013-07-31 13:20:37 +0000 | |||
175 | +++ app/config-prod.js 2013-08-30 19:48:17 +0000 | |||
176 | @@ -40,7 +40,7 @@ | |||
177 | 40 | socket_port: 8081, | 40 | socket_port: 8081, |
178 | 41 | user: undefined, | 41 | user: undefined, |
179 | 42 | password: undefined, | 42 | password: undefined, |
181 | 43 | apiBackend: 'python', // Value can be 'python' or 'go'. | 43 | apiBackend: 'go', // Value can be 'python' or 'go'. |
182 | 44 | sandbox: false, | 44 | sandbox: false, |
183 | 45 | // When in sandbox mode should we create events to simulate a live env. | 45 | // When in sandbox mode should we create events to simulate a live env. |
184 | 46 | // You can also use the :flags:/simulateEvents feature flag. | 46 | // You can also use the :flags:/simulateEvents feature flag. |
185 | 47 | 47 | ||
186 | === modified file 'app/index.html' | |||
187 | --- app/index.html 2013-08-01 23:47:57 +0000 | |||
188 | +++ app/index.html 2013-08-30 19:48:17 +0000 | |||
189 | @@ -82,7 +82,7 @@ | |||
190 | 82 | <div class="header"> | 82 | <div class="header"> |
191 | 83 | <div class="error"> | 83 | <div class="error"> |
192 | 84 | <span><i class="sprite alert_icon2"></i> | 84 | <span><i class="sprite alert_icon2"></i> |
194 | 85 | <h4>Your browser is not fully supported</h4></span> | 85 | <h4>Your browser is not supported</h4></span> |
195 | 86 | </div> | 86 | </div> |
196 | 87 | </div> | 87 | </div> |
197 | 88 | <p> | 88 | <p> |
198 | @@ -213,8 +213,8 @@ | |||
199 | 213 | }; | 213 | }; |
200 | 214 | 214 | ||
201 | 215 | isBrowserSupported = function(agent) { | 215 | isBrowserSupported = function(agent) { |
204 | 216 | // At the moment Chrome and Firefox are supported. | 216 | // Latest Chrome, Firefox, IE10 are supported |
205 | 217 | return (/Chrome|Firefox/.test(agent)); | 217 | return (/Chrome|Firefox|MSIE\ 10/.test(agent)); |
206 | 218 | }; | 218 | }; |
207 | 219 | 219 | ||
208 | 220 | displayBrowserWarning = function() { | 220 | displayBrowserWarning = function() { |
209 | 221 | 221 | ||
210 | === modified file 'app/models/charm.js' | |||
211 | --- app/models/charm.js 2013-08-21 16:02:10 +0000 | |||
212 | +++ app/models/charm.js 2013-08-30 19:48:17 +0000 | |||
213 | @@ -30,7 +30,7 @@ | |||
214 | 30 | var RECENT_DAYS = 30; | 30 | var RECENT_DAYS = 30; |
215 | 31 | 31 | ||
216 | 32 | var models = Y.namespace('juju.models'); | 32 | var models = Y.namespace('juju.models'); |
218 | 33 | var charmIdRe = /^(?:(\w+):)?(?:~(\S+)\/)?(\w+)\/(\S+?)-(\d+)$/; | 33 | var charmIdRe = /^(?:(\w+):)?(?:~(\S+)\/)?(\w+)\/(\S+?)-(\d+|HEAD)$/; |
219 | 34 | var idElements = ['scheme', 'owner', 'series', 'package_name', 'revision']; | 34 | var idElements = ['scheme', 'owner', 'series', 'package_name', 'revision']; |
220 | 35 | var simpleCharmIdRe = /^(?:(\w+):)?(?!:~)(\w+)$/; | 35 | var simpleCharmIdRe = /^(?:(\w+):)?(?!:~)(\w+)$/; |
221 | 36 | var simpleIdElements = ['scheme', 'package_name']; | 36 | var simpleIdElements = ['scheme', 'package_name']; |
222 | 37 | 37 | ||
223 | === modified file 'app/models/model-controller.js' | |||
224 | --- app/models/model-controller.js 2013-05-17 14:51:05 +0000 | |||
225 | +++ app/models/model-controller.js 2013-08-30 19:48:17 +0000 | |||
226 | @@ -82,7 +82,7 @@ | |||
227 | 82 | if (charm && charm.loaded) { | 82 | if (charm && charm.loaded) { |
228 | 83 | resolve(charm); | 83 | resolve(charm); |
229 | 84 | } else { | 84 | } else { |
231 | 85 | charm = db.charms.add({id: charmId}).load(env, | 85 | charm = db.charms.add({url: charmId}).load(env, |
232 | 86 | // If views are bound to the charm model, firing "update" is | 86 | // If views are bound to the charm model, firing "update" is |
233 | 87 | // unnecessary, and potentially even mildly harmful. | 87 | // unnecessary, and potentially even mildly harmful. |
234 | 88 | function(err, data) { | 88 | function(err, data) { |
235 | @@ -144,6 +144,7 @@ | |||
236 | 144 | getServiceWithCharm: function(serviceId) { | 144 | getServiceWithCharm: function(serviceId) { |
237 | 145 | var db = this.get('db'), | 145 | var db = this.get('db'), |
238 | 146 | env = this.get('env'), | 146 | env = this.get('env'), |
239 | 147 | store = this.get('store'), | ||
240 | 147 | mController = this; | 148 | mController = this; |
241 | 148 | 149 | ||
242 | 149 | return this._getPromise( | 150 | return this._getPromise( |
243 | @@ -151,7 +152,21 @@ | |||
244 | 151 | function(resolve, reject) { | 152 | function(resolve, reject) { |
245 | 152 | mController.getService(serviceId).then(function(service) { | 153 | mController.getService(serviceId).then(function(service) { |
246 | 153 | mController.getCharm(service.get('charm')).then(function(charm) { | 154 | mController.getCharm(service.get('charm')).then(function(charm) { |
248 | 154 | resolve({service: service, charm: charm}); | 155 | // Check if a newer charm is available for this service so that |
249 | 156 | // we can offer it as an upgrade. | ||
250 | 157 | // XXX Makyo Aug. 20 - Remove feature flag when upgradecharm | ||
251 | 158 | // feature lands. | ||
252 | 159 | if (charm.get('scheme') === 'cs' && | ||
253 | 160 | window.flags.upgradeCharm) { | ||
254 | 161 | store.promiseUpgradeAvailability(charm, db.charms) | ||
255 | 162 | .then(function(latestId) { | ||
256 | 163 | service.set('upgrade_available', !!latestId); | ||
257 | 164 | service.set('upgrade_to', latestId); | ||
258 | 165 | resolve({service: service, charm: charm}); | ||
259 | 166 | }, reject); | ||
260 | 167 | } else { | ||
261 | 168 | resolve({service: service, charm: charm}); | ||
262 | 169 | } | ||
263 | 155 | }, reject); | 170 | }, reject); |
264 | 156 | }, reject); | 171 | }, reject); |
265 | 157 | }); | 172 | }); |
266 | @@ -160,6 +175,15 @@ | |||
267 | 160 | }, { | 175 | }, { |
268 | 161 | ATTRS: { | 176 | ATTRS: { |
269 | 162 | /** | 177 | /** |
270 | 178 | Reference to the client db. | ||
271 | 179 | |||
272 | 180 | @attribute db | ||
273 | 181 | @type {Y.Base} | ||
274 | 182 | @default undefined | ||
275 | 183 | */ | ||
276 | 184 | db: {}, | ||
277 | 185 | |||
278 | 186 | /** | ||
279 | 163 | Reference to the client env. | 187 | Reference to the client env. |
280 | 164 | 188 | ||
281 | 165 | @attribute env | 189 | @attribute env |
282 | @@ -169,13 +193,13 @@ | |||
283 | 169 | env: {}, | 193 | env: {}, |
284 | 170 | 194 | ||
285 | 171 | /** | 195 | /** |
287 | 172 | Reference to the client db. | 196 | Reference to the client charm store. |
288 | 173 | 197 | ||
291 | 174 | @attribute db | 198 | @attribute store |
292 | 175 | @type {Y.Base} | 199 | @type {Charmworld2} |
293 | 176 | @default undefined | 200 | @default undefined |
294 | 177 | */ | 201 | */ |
296 | 178 | db: {} | 202 | store: {} |
297 | 179 | } | 203 | } |
298 | 180 | }); | 204 | }); |
299 | 181 | 205 | ||
300 | 182 | 206 | ||
301 | === modified file 'app/models/models.js' | |||
302 | --- app/models/models.js 2013-08-21 16:02:10 +0000 | |||
303 | +++ app/models/models.js 2013-08-30 19:48:17 +0000 | |||
304 | @@ -196,6 +196,25 @@ | |||
305 | 196 | value: ALIVE | 196 | value: ALIVE |
306 | 197 | }, | 197 | }, |
307 | 198 | unit_count: {}, | 198 | unit_count: {}, |
308 | 199 | |||
309 | 200 | /** | ||
310 | 201 | Whether or not an upgrade is available. | ||
311 | 202 | |||
312 | 203 | @attribute upgrade_available | ||
313 | 204 | @type {boolean} | ||
314 | 205 | @default false | ||
315 | 206 | */ | ||
316 | 207 | upgrade_available: { | ||
317 | 208 | value: false | ||
318 | 209 | }, | ||
319 | 210 | |||
320 | 211 | /** | ||
321 | 212 | The latest charm URL that the service can be upgraded to. | ||
322 | 213 | |||
323 | 214 | @attribute upgrade_to | ||
324 | 215 | @type {string} | ||
325 | 216 | */ | ||
326 | 217 | upgrade_to: {}, | ||
327 | 199 | aggregated_status: {} | 218 | aggregated_status: {} |
328 | 200 | } | 219 | } |
329 | 201 | }); | 220 | }); |
330 | 202 | 221 | ||
331 | === modified file 'app/modules-debug.js' | |||
332 | --- app/modules-debug.js 2013-08-19 15:53:41 +0000 | |||
333 | +++ app/modules-debug.js 2013-08-30 19:48:17 +0000 | |||
334 | @@ -226,6 +226,9 @@ | |||
335 | 226 | fullpath: '/juju-ui/views/topology/topology.js' | 226 | fullpath: '/juju-ui/views/topology/topology.js' |
336 | 227 | }, | 227 | }, |
337 | 228 | 228 | ||
338 | 229 | 'juju-view-bundle': { | ||
339 | 230 | fullpath: '/juju-ui/views/topology/bundle.js' | ||
340 | 231 | }, | ||
341 | 229 | 'juju-view-utils': { | 232 | 'juju-view-utils': { |
342 | 230 | fullpath: '/juju-ui/views/utils.js' | 233 | fullpath: '/juju-ui/views/utils.js' |
343 | 231 | }, | 234 | }, |
344 | 232 | 235 | ||
345 | === modified file 'app/store/charm.js' | |||
346 | --- app/store/charm.js 2013-08-21 16:02:10 +0000 | |||
347 | +++ app/store/charm.js 2013-08-30 19:48:17 +0000 | |||
348 | @@ -212,6 +212,31 @@ | |||
349 | 212 | }, | 212 | }, |
350 | 213 | 213 | ||
351 | 214 | /** | 214 | /** |
352 | 215 | Promises to return the latest charm ID for a given charm if a newer one | ||
353 | 216 | exists; this also caches the newer charm if one is available. | ||
354 | 217 | |||
355 | 218 | @method promiseUpgradeAvailability | ||
356 | 219 | @param {Charm} charm an existing charm potentially in need of an upgrade. | ||
357 | 220 | @param {ModelList} cache a local cache of browser charms. | ||
358 | 221 | @return {Promise} with an id or undefined. | ||
359 | 222 | */ | ||
360 | 223 | promiseUpgradeAvailability: function(charm, cache) { | ||
361 | 224 | // Get the charm's store ID, then replace the version number | ||
362 | 225 | // with '-HEAD' to retrieve the latest version of the charm. | ||
363 | 226 | var storeId = charm.get('storeId').replace(/-\d+$/, '-HEAD'); | ||
364 | 227 | return this.promiseCharm(storeId, cache) | ||
365 | 228 | .then(function(latest) { | ||
366 | 229 | var latestVersion = parseInt(latest.charm.id.split('-').pop(), 10), | ||
367 | 230 | currentVersion = parseInt(charm.get('revision'), 10); | ||
368 | 231 | if (latestVersion > currentVersion) { | ||
369 | 232 | return latest.charm.id; | ||
370 | 233 | } | ||
371 | 234 | }, function(e) { | ||
372 | 235 | throw e; | ||
373 | 236 | }); | ||
374 | 237 | }, | ||
375 | 238 | |||
376 | 239 | /** | ||
377 | 215 | * Api call to search charms | 240 | * Api call to search charms |
378 | 216 | * | 241 | * |
379 | 217 | * @method search | 242 | * @method search |
380 | 218 | 243 | ||
381 | === modified file 'app/store/env/env.js' | |||
382 | --- app/store/env/env.js 2013-05-17 14:51:05 +0000 | |||
383 | +++ app/store/env/env.js 2013-08-30 19:48:17 +0000 | |||
384 | @@ -26,8 +26,8 @@ | |||
385 | 26 | 26 | ||
386 | 27 | YUI.add('juju-env', function(Y) { | 27 | YUI.add('juju-env', function(Y) { |
387 | 28 | 28 | ||
390 | 29 | // Default to the Python environment. | 29 | // Default to the Go environment. |
391 | 30 | var DEFAULT_BACKEND = 'python'; | 30 | var DEFAULT_BACKEND = 'go'; |
392 | 31 | 31 | ||
393 | 32 | /** | 32 | /** |
394 | 33 | * Create and return a store environment suitable for connecting to the | 33 | * Create and return a store environment suitable for connecting to the |
395 | 34 | 34 | ||
396 | === modified file 'app/store/env/fakebackend.js' | |||
397 | --- app/store/env/fakebackend.js 2013-08-13 16:22:03 +0000 | |||
398 | +++ app/store/env/fakebackend.js 2013-08-30 19:48:17 +0000 | |||
399 | @@ -368,11 +368,17 @@ | |||
400 | 368 | throw e; | 368 | throw e; |
401 | 369 | } | 369 | } |
402 | 370 | } | 370 | } |
403 | 371 | |||
404 | 372 | var constraints = {}; | ||
405 | 373 | if (options.constraints) { | ||
406 | 374 | constraints = options.constraints; | ||
407 | 375 | } | ||
408 | 376 | |||
409 | 371 | var service = this.db.services.add({ | 377 | var service = this.db.services.add({ |
410 | 372 | id: options.name, | 378 | id: options.name, |
411 | 373 | name: options.name, | 379 | name: options.name, |
412 | 374 | charm: charm.get('id'), | 380 | charm: charm.get('id'), |
414 | 375 | constraints: {}, | 381 | constraints: constraints, |
415 | 376 | exposed: false, | 382 | exposed: false, |
416 | 377 | subordinate: charm.get('is_subordinate'), | 383 | subordinate: charm.get('is_subordinate'), |
417 | 378 | config: options.config | 384 | config: options.config |
418 | 379 | 385 | ||
419 | === modified file 'app/store/env/go.js' | |||
420 | --- app/store/env/go.js 2013-08-13 18:25:46 +0000 | |||
421 | +++ app/store/env/go.js 2013-08-30 19:48:17 +0000 | |||
422 | @@ -119,6 +119,8 @@ | |||
423 | 119 | 119 | ||
424 | 120 | Y.extend(GoEnvironment, environments.BaseEnvironment, { | 120 | Y.extend(GoEnvironment, environments.BaseEnvironment, { |
425 | 121 | 121 | ||
426 | 122 | genericConstraints: ['cpu-power', 'cpu-cores', 'mem', 'arch'], | ||
427 | 123 | |||
428 | 122 | /** | 124 | /** |
429 | 123 | * Go environment constructor. | 125 | * Go environment constructor. |
430 | 124 | * | 126 | * |
431 | @@ -273,7 +275,11 @@ | |||
432 | 273 | */ | 275 | */ |
433 | 274 | login: function() { | 276 | login: function() { |
434 | 275 | // If the user is already authenticated there is nothing to do. | 277 | // If the user is already authenticated there is nothing to do. |
436 | 276 | if (this.userIsAuthenticated || this.pendingLoginResponse) { | 278 | if (this.userIsAuthenticated) { |
437 | 279 | this.fire('login', {data: {result: true}}); | ||
438 | 280 | return; | ||
439 | 281 | } | ||
440 | 282 | if (this.pendingLoginResponse) { | ||
441 | 277 | return; | 283 | return; |
442 | 278 | } | 284 | } |
443 | 279 | var credentials = this.getCredentials(); | 285 | var credentials = this.getCredentials(); |
444 | @@ -337,17 +343,31 @@ | |||
445 | 337 | configuration options. Only one of `config` and `config_raw` should be | 343 | configuration options. Only one of `config` and `config_raw` should be |
446 | 338 | provided, though `config_raw` takes precedence if it is given. | 344 | provided, though `config_raw` takes precedence if it is given. |
447 | 339 | @param {Integer} num_units The number of units to be deployed. | 345 | @param {Integer} num_units The number of units to be deployed. |
448 | 346 | @param {Object} constraints The machine constraints to use in the | ||
449 | 347 | object format key: value. | ||
450 | 340 | @param {Function} callback A callable that must be called once the | 348 | @param {Function} callback A callable that must be called once the |
451 | 341 | operation is performed. | 349 | operation is performed. |
452 | 342 | @return {undefined} Sends a message to the server only. | 350 | @return {undefined} Sends a message to the server only. |
453 | 343 | */ | 351 | */ |
454 | 344 | deploy: function(charm_url, service_name, config, config_raw, num_units, | 352 | deploy: function(charm_url, service_name, config, config_raw, num_units, |
456 | 345 | callback) { | 353 | constraints, callback) { |
457 | 346 | var intermediateCallback = null; | 354 | var intermediateCallback = null; |
458 | 347 | if (callback) { | 355 | if (callback) { |
459 | 348 | intermediateCallback = Y.bind(this.handleDeploy, this, | 356 | intermediateCallback = Y.bind(this.handleDeploy, this, |
460 | 349 | callback, service_name, charm_url); | 357 | callback, service_name, charm_url); |
461 | 350 | } | 358 | } |
462 | 359 | |||
463 | 360 | if (constraints) { | ||
464 | 361 | // If the constraints is a function (this arg position used to be a | ||
465 | 362 | // callback) then log it out to the console to fix it. | ||
466 | 363 | if (typeof constraints === 'function') { | ||
467 | 364 | console.error('Constraints need to be an object not a function'); | ||
468 | 365 | console.warn(constraints); | ||
469 | 366 | } | ||
470 | 367 | } else { | ||
471 | 368 | constraints = {}; | ||
472 | 369 | } | ||
473 | 370 | |||
474 | 351 | this._send_rpc( | 371 | this._send_rpc( |
475 | 352 | { Type: 'Client', | 372 | { Type: 'Client', |
476 | 353 | Request: 'ServiceDeploy', | 373 | Request: 'ServiceDeploy', |
477 | @@ -355,6 +375,7 @@ | |||
478 | 355 | ServiceName: service_name, | 375 | ServiceName: service_name, |
479 | 356 | Config: stringifyObjectValues(config), | 376 | Config: stringifyObjectValues(config), |
480 | 357 | ConfigYAML: config_raw, | 377 | ConfigYAML: config_raw, |
481 | 378 | Constraints: constraints, | ||
482 | 358 | CharmUrl: charm_url, | 379 | CharmUrl: charm_url, |
483 | 359 | NumUnits: num_units | 380 | NumUnits: num_units |
484 | 360 | } | 381 | } |
485 | @@ -868,9 +889,6 @@ | |||
486 | 868 | }, intermediateCallback); | 889 | }, intermediateCallback); |
487 | 869 | }, | 890 | }, |
488 | 870 | 891 | ||
489 | 871 | // The constraints that the backend understands. Used to generate forms. | ||
490 | 872 | genericConstraints: ['cpu-power', 'cpu-cores', 'mem', 'arch'], | ||
491 | 873 | |||
492 | 874 | /** | 892 | /** |
493 | 875 | Change the constraints of the given service. | 893 | Change the constraints of the given service. |
494 | 876 | 894 | ||
495 | 877 | 895 | ||
496 | === modified file 'app/store/env/python.js' | |||
497 | --- app/store/env/python.js 2013-06-24 14:56:21 +0000 | |||
498 | +++ app/store/env/python.js 2013-08-30 19:48:17 +0000 | |||
499 | @@ -255,13 +255,18 @@ | |||
500 | 255 | * @return {undefined} Sends a message to the server only. | 255 | * @return {undefined} Sends a message to the server only. |
501 | 256 | */ | 256 | */ |
502 | 257 | deploy: function(charm_url, service_name, config, config_raw, num_units, | 257 | deploy: function(charm_url, service_name, config, config_raw, num_units, |
504 | 258 | callback) { | 258 | constraints, callback) { |
505 | 259 | if (!constraints) { | ||
506 | 260 | constraints = {}; | ||
507 | 261 | } | ||
508 | 262 | |||
509 | 259 | this._send_rpc( | 263 | this._send_rpc( |
510 | 260 | { op: 'deploy', | 264 | { op: 'deploy', |
511 | 261 | service_name: service_name, | 265 | service_name: service_name, |
512 | 262 | config: config, | 266 | config: config, |
513 | 263 | config_raw: config_raw, | 267 | config_raw: config_raw, |
514 | 264 | charm_url: charm_url, | 268 | charm_url: charm_url, |
515 | 269 | constraints: constraints, | ||
516 | 265 | num_units: num_units}, | 270 | num_units: num_units}, |
517 | 266 | callback, true); | 271 | callback, true); |
518 | 267 | }, | 272 | }, |
519 | 268 | 273 | ||
520 | === modified file 'app/store/env/sandbox.js' | |||
521 | --- app/store/env/sandbox.js 2013-08-14 19:15:01 +0000 | |||
522 | +++ app/store/env/sandbox.js 2013-08-30 19:48:17 +0000 | |||
523 | @@ -482,6 +482,7 @@ | |||
524 | 482 | name: data.service_name, | 482 | name: data.service_name, |
525 | 483 | config: data.config, | 483 | config: data.config, |
526 | 484 | configYAML: data.config_raw, | 484 | configYAML: data.config_raw, |
527 | 485 | constraints: data.constraints, | ||
528 | 485 | unitCount: data.num_units | 486 | unitCount: data.num_units |
529 | 486 | }); | 487 | }); |
530 | 487 | }, | 488 | }, |
531 | @@ -784,7 +785,6 @@ | |||
532 | 784 | @return {undefined} Nothing. | 785 | @return {undefined} Nothing. |
533 | 785 | */ | 786 | */ |
534 | 786 | receive: function(data) { | 787 | receive: function(data) { |
535 | 787 | console.log('client message', data); | ||
536 | 788 | if (this.connected) { | 788 | if (this.connected) { |
537 | 789 | var client = this.get('client'); | 789 | var client = this.get('client'); |
538 | 790 | this['handle' + data.Type + data.Request](data, | 790 | this['handle' + data.Type + data.Request](data, |
539 | @@ -995,10 +995,12 @@ | |||
540 | 995 | var callback = Y.bind(function(result) { | 995 | var callback = Y.bind(function(result) { |
541 | 996 | this._basicReceive(data, client, result); | 996 | this._basicReceive(data, client, result); |
542 | 997 | }, this); | 997 | }, this); |
543 | 998 | |||
544 | 998 | state.deploy(data.Params.CharmUrl, callback, { | 999 | state.deploy(data.Params.CharmUrl, callback, { |
545 | 999 | name: data.Params.ServiceName, | 1000 | name: data.Params.ServiceName, |
546 | 1000 | config: data.Params.Config, | 1001 | config: data.Params.Config, |
547 | 1001 | configYAML: data.Params.ConfigYAML, | 1002 | configYAML: data.Params.ConfigYAML, |
548 | 1003 | constraints: data.Params.Constraints, | ||
549 | 1002 | unitCount: data.Params.NumUnits | 1004 | unitCount: data.Params.NumUnits |
550 | 1003 | }); | 1005 | }); |
551 | 1004 | }, | 1006 | }, |
552 | @@ -1300,6 +1302,7 @@ | |||
553 | 1300 | 'base', | 1302 | 'base', |
554 | 1301 | 'js-yaml', | 1303 | 'js-yaml', |
555 | 1302 | 'json-parse', | 1304 | 'json-parse', |
556 | 1305 | 'juju-env-go', | ||
557 | 1303 | 'timers' | 1306 | 'timers' |
558 | 1304 | ] | 1307 | ] |
559 | 1305 | }); | 1308 | }); |
560 | 1306 | 1309 | ||
561 | === modified file 'app/subapps/browser/browser.js' | |||
562 | --- app/subapps/browser/browser.js 2013-08-15 15:40:05 +0000 | |||
563 | +++ app/subapps/browser/browser.js 2013-08-30 19:48:17 +0000 | |||
564 | @@ -51,8 +51,34 @@ | |||
565 | 51 | _cleanOldViews: function(newViewMode) { | 51 | _cleanOldViews: function(newViewMode) { |
566 | 52 | if (this._hasStateChanged('viewmode') && this._oldState.viewmode) { | 52 | if (this._hasStateChanged('viewmode') && this._oldState.viewmode) { |
567 | 53 | var viewAttr = '_' + this._oldState.viewmode; | 53 | var viewAttr = '_' + this._oldState.viewmode; |
570 | 54 | this[viewAttr].destroy(); | 54 | if (this[viewAttr]) { |
571 | 55 | delete this[viewAttr]; | 55 | this[viewAttr].destroy(); |
572 | 56 | delete this[viewAttr]; | ||
573 | 57 | } | ||
574 | 58 | } | ||
575 | 59 | }, | ||
576 | 60 | |||
577 | 61 | /** | ||
578 | 62 | * Destroy and remove any lingering views. | ||
579 | 63 | * | ||
580 | 64 | * Make sure they don't linger and hold UX bound events on us when they | ||
581 | 65 | * should be gone. | ||
582 | 66 | * | ||
583 | 67 | * @method _clearViews | ||
584 | 68 | * | ||
585 | 69 | */ | ||
586 | 70 | _clearViews: function() { | ||
587 | 71 | if (this._sidebar) { | ||
588 | 72 | this._sidebar.destroy(); | ||
589 | 73 | delete this._sidebar; | ||
590 | 74 | } | ||
591 | 75 | if (this._minimized) { | ||
592 | 76 | this._minimized.destroy(); | ||
593 | 77 | delete this._minimized; | ||
594 | 78 | } | ||
595 | 79 | if (this._fullscreen) { | ||
596 | 80 | this._fullscreen.destroy(); | ||
597 | 81 | delete this._fullscreen; | ||
598 | 56 | } | 82 | } |
599 | 57 | }, | 83 | }, |
600 | 58 | 84 | ||
601 | @@ -88,7 +114,6 @@ | |||
602 | 88 | */ | 114 | */ |
603 | 89 | _getStateUrl: function(change) { | 115 | _getStateUrl: function(change) { |
604 | 90 | var urlParts = []; | 116 | var urlParts = []; |
605 | 91 | this._oldState = this._viewState; | ||
606 | 92 | 117 | ||
607 | 93 | // If there are changes to the filters, we need to update our filter | 118 | // If there are changes to the filters, we need to update our filter |
608 | 94 | // object first, and then generate a new query string for the state to | 119 | // object first, and then generate a new query string for the state to |
609 | @@ -485,6 +510,7 @@ | |||
610 | 485 | viewmode: null | 510 | viewmode: null |
611 | 486 | }; | 511 | }; |
612 | 487 | this._viewState = Y.merge(this._oldState, {}); | 512 | this._viewState = Y.merge(this._oldState, {}); |
613 | 513 | this._clearViews(); | ||
614 | 488 | }, | 514 | }, |
615 | 489 | 515 | ||
616 | 490 | /** | 516 | /** |
617 | @@ -576,17 +602,6 @@ | |||
618 | 576 | // Add any sidebar charms to the running cache. | 602 | // Add any sidebar charms to the running cache. |
619 | 577 | this._cache = Y.merge(this._cache, ev.cache); | 603 | this._cache = Y.merge(this._cache, ev.cache); |
620 | 578 | }, this); | 604 | }, this); |
621 | 579 | this._editorial.on(this._editorial.EV_CATEGORY_LINK_CLICKED, | ||
622 | 580 | function(ev) { | ||
623 | 581 | var change = { | ||
624 | 582 | search: true, | ||
625 | 583 | filter: { | ||
626 | 584 | categories: [ev.category] | ||
627 | 585 | } | ||
628 | 586 | }; | ||
629 | 587 | this.fire('viewNavigate', {change: change}); | ||
630 | 588 | }); | ||
631 | 589 | |||
632 | 590 | this._editorial.render(this._cache.interesting); | 605 | this._editorial.render(this._cache.interesting); |
633 | 591 | this._editorial.addTarget(this); | 606 | this._editorial.addTarget(this); |
634 | 592 | }, | 607 | }, |
635 | @@ -644,7 +659,12 @@ | |||
636 | 644 | // If we've switched to viewmode fullscreen, we need to render it. | 659 | // If we've switched to viewmode fullscreen, we need to render it. |
637 | 645 | // We know the viewmode is already fullscreen because we're in this | 660 | // We know the viewmode is already fullscreen because we're in this |
638 | 646 | // function. | 661 | // function. |
640 | 647 | if (this._hasStateChanged('viewmode')) { | 662 | var forceFullscreen = false; |
641 | 663 | if (!this._fullscreen) { | ||
642 | 664 | forceFullscreen = true; | ||
643 | 665 | } | ||
644 | 666 | |||
645 | 667 | if (this._hasStateChanged('viewmode') || forceFullscreen) { | ||
646 | 648 | var extraCfg = {}; | 668 | var extraCfg = {}; |
647 | 649 | if (this._viewState.search || this._viewState.charmID) { | 669 | if (this._viewState.search || this._viewState.charmID) { |
648 | 650 | extraCfg.withHome = true; | 670 | extraCfg.withHome = true; |
649 | @@ -730,8 +750,14 @@ | |||
650 | 730 | @param {function} next callable for the next route in the chain. | 750 | @param {function} next callable for the next route in the chain. |
651 | 731 | */ | 751 | */ |
652 | 732 | sidebar: function(req, res, next) { | 752 | sidebar: function(req, res, next) { |
653 | 753 | // If we've gone from no _sidebar to having one, then force editorial to | ||
654 | 754 | // render. | ||
655 | 755 | var forceSidebar = false; | ||
656 | 756 | if (!this._sidebar) { | ||
657 | 757 | forceSidebar = true; | ||
658 | 758 | } | ||
659 | 733 | // If we've switched to viewmode sidebar, we need to render it. | 759 | // If we've switched to viewmode sidebar, we need to render it. |
661 | 734 | if (this._hasStateChanged('viewmode')) { | 760 | if (this._hasStateChanged('viewmode') || forceSidebar) { |
662 | 735 | this._sidebar = new views.Sidebar( | 761 | this._sidebar = new views.Sidebar( |
663 | 736 | this._getViewCfg({ | 762 | this._getViewCfg({ |
664 | 737 | container: this.get('container') | 763 | container: this.get('container') |
665 | @@ -759,9 +785,7 @@ | |||
666 | 759 | } | 785 | } |
667 | 760 | 786 | ||
668 | 761 | this.renderSearchResults(req, res, next); | 787 | this.renderSearchResults(req, res, next); |
672 | 762 | } | 788 | } else if (this._shouldShowEditorial() || forceSidebar) { |
670 | 763 | |||
671 | 764 | if (this._shouldShowEditorial()) { | ||
673 | 765 | // Showing editorial implies that other sidebar content is destroyed. | 789 | // Showing editorial implies that other sidebar content is destroyed. |
674 | 766 | if (this._search) { | 790 | if (this._search) { |
675 | 767 | this._search.destroy(); | 791 | this._search.destroy(); |
676 | @@ -770,6 +794,7 @@ | |||
677 | 770 | this.renderEditorial(req, res, next); | 794 | this.renderEditorial(req, res, next); |
678 | 771 | } | 795 | } |
679 | 772 | 796 | ||
680 | 797 | |||
681 | 773 | // If we've changed the charmID or the viewmode has changed and we have | 798 | // If we've changed the charmID or the viewmode has changed and we have |
682 | 774 | // a charmID, render charmDetails. | 799 | // a charmID, render charmDetails. |
683 | 775 | if (this._shouldShowCharm()) { | 800 | if (this._shouldShowCharm()) { |
684 | @@ -863,6 +888,8 @@ | |||
685 | 863 | this[viewmode](req, res, next); | 888 | this[viewmode](req, res, next); |
686 | 864 | } | 889 | } |
687 | 865 | } else { | 890 | } else { |
688 | 891 | // Update the app state even though we're not showing anything. | ||
689 | 892 | this._saveState(); | ||
690 | 866 | // Let the next route go on. | 893 | // Let the next route go on. |
691 | 867 | next(); | 894 | next(); |
692 | 868 | } | 895 | } |
693 | @@ -920,6 +947,8 @@ | |||
694 | 920 | if (!this.hidden) { | 947 | if (!this.hidden) { |
695 | 921 | this[viewmode](req, res, next); | 948 | this[viewmode](req, res, next); |
696 | 922 | } else { | 949 | } else { |
697 | 950 | // Update the app state even though we're not showing anything. | ||
698 | 951 | this._saveState(); | ||
699 | 923 | // Let the next route go on. | 952 | // Let the next route go on. |
700 | 924 | next(); | 953 | next(); |
701 | 925 | } | 954 | } |
702 | @@ -965,6 +994,8 @@ | |||
703 | 965 | if (!this.hidden) { | 994 | if (!this.hidden) { |
704 | 966 | this[req.params.viewmode](req, res, next); | 995 | this[req.params.viewmode](req, res, next); |
705 | 967 | } else { | 996 | } else { |
706 | 997 | // Update the app state even though we're not showing anything. | ||
707 | 998 | this._saveState(); | ||
708 | 968 | // Let the next route go on. | 999 | // Let the next route go on. |
709 | 969 | next(); | 1000 | next(); |
710 | 970 | } | 1001 | } |
711 | @@ -992,6 +1023,7 @@ | |||
712 | 992 | if (this.hidden) { | 1023 | if (this.hidden) { |
713 | 993 | browser.hide(); | 1024 | browser.hide(); |
714 | 994 | minview.hide(); | 1025 | minview.hide(); |
715 | 1026 | this._clearViews(); | ||
716 | 995 | } else { | 1027 | } else { |
717 | 996 | if (this._viewState.viewmode === 'minimized') { | 1028 | if (this._viewState.viewmode === 'minimized') { |
718 | 997 | minview.show(); | 1029 | minview.show(); |
719 | 998 | 1030 | ||
720 | === modified file 'app/subapps/browser/templates/editorial.handlebars' | |||
721 | --- app/subapps/browser/templates/editorial.handlebars 2013-07-17 18:27:38 +0000 | |||
722 | +++ app/subapps/browser/templates/editorial.handlebars 2013-08-30 19:48:17 +0000 | |||
723 | @@ -21,7 +21,6 @@ | |||
724 | 21 | <div class="featured"></div> | 21 | <div class="featured"></div> |
725 | 22 | <div class="popular"></div> | 22 | <div class="popular"></div> |
726 | 23 | <div class="new"></div> | 23 | <div class="new"></div> |
727 | 24 | {{> category-icons }} | ||
728 | 25 | 24 | ||
729 | 26 | {{#if isFullscreen}} | 25 | {{#if isFullscreen}} |
730 | 27 | </div> | 26 | </div> |
731 | 28 | 27 | ||
732 | === modified file 'app/subapps/browser/views/editorial.js' | |||
733 | --- app/subapps/browser/views/editorial.js 2013-07-12 16:46:15 +0000 | |||
734 | +++ app/subapps/browser/views/editorial.js 2013-08-30 19:48:17 +0000 | |||
735 | @@ -41,7 +41,6 @@ | |||
736 | 41 | */ | 41 | */ |
737 | 42 | ns.EditorialView = Y.Base.create('browser-view-sidebar', ns.CharmResults, [], | 42 | ns.EditorialView = Y.Base.create('browser-view-sidebar', ns.CharmResults, [], |
738 | 43 | { | 43 | { |
739 | 44 | EV_CATEGORY_LINK_CLICKED: 'category-link-clicked', | ||
740 | 45 | template: views.Templates.editorial, | 44 | template: views.Templates.editorial, |
741 | 46 | 45 | ||
742 | 47 | // How many of each charm container do we show by default. | 46 | // How many of each charm container do we show by default. |
743 | @@ -69,33 +68,6 @@ | |||
744 | 69 | }, | 68 | }, |
745 | 70 | 69 | ||
746 | 71 | /** | 70 | /** |
747 | 72 | Binds clicks on the category links in the editorial view and fires | ||
748 | 73 | that information to any listeners. | ||
749 | 74 | |||
750 | 75 | @private | ||
751 | 76 | @method _bindCategoryLinks | ||
752 | 77 | */ | ||
753 | 78 | _bindCategoryLinks: function() { | ||
754 | 79 | var categories = Y.one('#category-links'); | ||
755 | 80 | if (categories) { | ||
756 | 81 | categories.delegate('click', function(ev) { | ||
757 | 82 | // A link has been clicked, we need to kill the navigation | ||
758 | 83 | // event. | ||
759 | 84 | ev.halt(); | ||
760 | 85 | var category = ev.currentTarget.getData('link'); | ||
761 | 86 | var change = { | ||
762 | 87 | search: true, | ||
763 | 88 | filter: { | ||
764 | 89 | categories: [category], | ||
765 | 90 | replace: true | ||
766 | 91 | } | ||
767 | 92 | }; | ||
768 | 93 | this.fire('viewNavigate', {change: change}); | ||
769 | 94 | }, 'a', this); | ||
770 | 95 | } | ||
771 | 96 | }, | ||
772 | 97 | |||
773 | 98 | /** | ||
774 | 99 | Renders the editorial, "interesting" data to the view. | 71 | Renders the editorial, "interesting" data to the view. |
775 | 100 | 72 | ||
776 | 101 | @private | 73 | @private |
777 | @@ -195,9 +167,6 @@ | |||
778 | 195 | cache.charms.add(popularCharms); | 167 | cache.charms.add(popularCharms); |
779 | 196 | cache.charms.add(featuredCharms); | 168 | cache.charms.add(featuredCharms); |
780 | 197 | this.fire(this.EV_CACHE_UPDATED, {cache: cache}); | 169 | this.fire(this.EV_CACHE_UPDATED, {cache: cache}); |
781 | 198 | |||
782 | 199 | // Bind the category links, which now exist | ||
783 | 200 | this._bindCategoryLinks(); | ||
784 | 201 | }, | 170 | }, |
785 | 202 | 171 | ||
786 | 203 | /** | 172 | /** |
787 | 204 | 173 | ||
788 | === modified file 'app/subapps/browser/views/minimized.js' | |||
789 | --- app/subapps/browser/views/minimized.js 2013-08-02 15:34:18 +0000 | |||
790 | +++ app/subapps/browser/views/minimized.js 2013-08-30 19:48:17 +0000 | |||
791 | @@ -29,7 +29,6 @@ | |||
792 | 29 | YUI.add('subapp-browser-minimized', function(Y) { | 29 | YUI.add('subapp-browser-minimized', function(Y) { |
793 | 30 | var ns = Y.namespace('juju.browser.views'), | 30 | var ns = Y.namespace('juju.browser.views'), |
794 | 31 | views = Y.namespace('juju.views'); | 31 | views = Y.namespace('juju.views'); |
795 | 32 | |||
796 | 33 | /** | 32 | /** |
797 | 34 | * The minimized state view. | 33 | * The minimized state view. |
798 | 35 | * | 34 | * |
799 | @@ -37,7 +36,9 @@ | |||
800 | 37 | * @extends {Y.View} | 36 | * @extends {Y.View} |
801 | 38 | * | 37 | * |
802 | 39 | */ | 38 | */ |
804 | 40 | ns.MinimizedView = Y.Base.create('browser-view-minimized', Y.View, [], { | 39 | ns.MinimizedView = Y.Base.create('browser-view-minimized', Y.View, [ |
805 | 40 | Y.juju.widgets.ViewmodeControlsViewExtension | ||
806 | 41 | ], { | ||
807 | 41 | template: views.Templates.minimized, | 42 | template: views.Templates.minimized, |
808 | 42 | 43 | ||
809 | 43 | events: { | 44 | events: { |
810 | @@ -47,15 +48,13 @@ | |||
811 | 47 | }, | 48 | }, |
812 | 48 | 49 | ||
813 | 49 | /** | 50 | /** |
817 | 50 | * Toggle the visibility of the browser. Bound to nav controls in the | 51 | * Toggle the visibility of the browser. |
815 | 51 | * view, however this will be expanded to be controlled from the new | ||
816 | 52 | * constant nav menu outside of the view once it's completed. | ||
818 | 53 | * | 52 | * |
820 | 54 | * @method _toggle_sidebar | 53 | * @method _toggleMinimized |
821 | 55 | * @param {Event} ev event to trigger the toggle. | 54 | * @param {Event} ev event to trigger the toggle. |
822 | 56 | * | 55 | * |
823 | 57 | */ | 56 | */ |
825 | 58 | _toggleViewState: function(ev) { | 57 | _toggleMinimized: function(ev) { |
826 | 59 | ev.halt(); | 58 | ev.halt(); |
827 | 60 | 59 | ||
828 | 61 | this.get('container').hide(); | 60 | this.get('container').hide(); |
829 | @@ -88,6 +87,13 @@ | |||
830 | 88 | var tpl = this.template(), | 87 | var tpl = this.template(), |
831 | 89 | tplNode = Y.Node.create(tpl); | 88 | tplNode = Y.Node.create(tpl); |
832 | 90 | this.get('container').setHTML(tplNode); | 89 | this.get('container').setHTML(tplNode); |
833 | 90 | // Make sure the controls starts out setting the correct active state | ||
834 | 91 | // based on the current viewmode for our View. | ||
835 | 92 | this.controls = new Y.juju.widgets.ViewmodeControls({ | ||
836 | 93 | currentViewmode: this.get('oldViewMode') | ||
837 | 94 | }); | ||
838 | 95 | this.controls.render(); | ||
839 | 96 | this._bindViewmodeControls(this.controls); | ||
840 | 91 | } | 97 | } |
841 | 92 | 98 | ||
842 | 93 | }, { | 99 | }, { |
843 | @@ -116,6 +122,7 @@ | |||
844 | 116 | 'base', | 122 | 'base', |
845 | 117 | 'juju-templates', | 123 | 'juju-templates', |
846 | 118 | 'juju-views', | 124 | 'juju-views', |
848 | 119 | 'view' | 125 | 'view', |
849 | 126 | 'viewmode-controls' | ||
850 | 120 | ] | 127 | ] |
851 | 121 | }); | 128 | }); |
852 | 122 | 129 | ||
853 | === modified file 'app/subapps/browser/views/view.js' | |||
854 | --- app/subapps/browser/views/view.js 2013-08-08 15:17:01 +0000 | |||
855 | +++ app/subapps/browser/views/view.js 2013-08-30 19:48:17 +0000 | |||
856 | @@ -40,7 +40,8 @@ | |||
857 | 40 | * | 40 | * |
858 | 41 | */ | 41 | */ |
859 | 42 | ns.MainView = Y.Base.create('browser-view-mainview', Y.View, [ | 42 | ns.MainView = Y.Base.create('browser-view-mainview', Y.View, [ |
861 | 43 | Y.Event.EventTracker | 43 | Y.Event.EventTracker, |
862 | 44 | Y.juju.widgets.ViewmodeControlsViewExtension | ||
863 | 44 | ], { | 45 | ], { |
864 | 45 | 46 | ||
865 | 46 | /** | 47 | /** |
866 | @@ -85,20 +86,6 @@ | |||
867 | 85 | */ | 86 | */ |
868 | 86 | _bindSearchWidgetEvents: function() { | 87 | _bindSearchWidgetEvents: function() { |
869 | 87 | var container = this.get('container'); | 88 | var container = this.get('container'); |
870 | 88 | this.addEvent( | ||
871 | 89 | this.controls.on( | ||
872 | 90 | this.controls.EVT_TOGGLE_VIEWABLE, this._toggleBrowser, this) | ||
873 | 91 | ); | ||
874 | 92 | |||
875 | 93 | this.addEvent( | ||
876 | 94 | this.controls.on( | ||
877 | 95 | this.controls.EVT_FULLSCREEN, this._goFullscreen, this) | ||
878 | 96 | ); | ||
879 | 97 | this.addEvent( | ||
880 | 98 | this.controls.on( | ||
881 | 99 | this.controls.EVT_SIDEBAR, this._goSidebar, this) | ||
882 | 100 | ); | ||
883 | 101 | |||
884 | 102 | if (this.search) { | 89 | if (this.search) { |
885 | 103 | this.addEvent( | 90 | this.addEvent( |
886 | 104 | this.search.on( | 91 | this.search.on( |
887 | @@ -106,7 +93,6 @@ | |||
888 | 106 | ); | 93 | ); |
889 | 107 | } | 94 | } |
890 | 108 | 95 | ||
891 | 109 | |||
892 | 110 | if (this.search) { | 96 | if (this.search) { |
893 | 111 | this.addEvent( | 97 | this.addEvent( |
894 | 112 | this.search.on( | 98 | this.search.on( |
895 | @@ -137,6 +123,7 @@ | |||
896 | 137 | } | 123 | } |
897 | 138 | }, this); | 124 | }, this); |
898 | 139 | } | 125 | } |
899 | 126 | this._bindViewmodeControls(this.controls); | ||
900 | 140 | }, | 127 | }, |
901 | 141 | 128 | ||
902 | 142 | /** | 129 | /** |
903 | @@ -233,11 +220,13 @@ | |||
904 | 233 | view, however this will be expanded to be controlled from the new | 220 | view, however this will be expanded to be controlled from the new |
905 | 234 | constant nav menu outside of the view once it's completed. | 221 | constant nav menu outside of the view once it's completed. |
906 | 235 | 222 | ||
908 | 236 | @method _toggle_sidebar | 223 | This is called by the ViewmodeControlsViewExtension. |
909 | 224 | |||
910 | 225 | @method _toggleMinimized | ||
911 | 237 | @param {Event} ev event to trigger the toggle. | 226 | @param {Event} ev event to trigger the toggle. |
912 | 238 | 227 | ||
913 | 239 | */ | 228 | */ |
915 | 240 | _toggleBrowser: function(ev) { | 229 | _toggleMinimized: function(ev) { |
916 | 241 | ev.halt(); | 230 | ev.halt(); |
917 | 242 | 231 | ||
918 | 243 | this.fire('viewNavigate', { | 232 | this.fire('viewNavigate', { |
919 | @@ -248,44 +237,9 @@ | |||
920 | 248 | }, | 237 | }, |
921 | 249 | 238 | ||
922 | 250 | /** | 239 | /** |
923 | 251 | Upon clicking the browser icon make sure we re-route to the | ||
924 | 252 | new form of the UX. | ||
925 | 253 | |||
926 | 254 | @method _goFullscreen | ||
927 | 255 | @param {Event} ev the click event handler on the button. | ||
928 | 256 | |||
929 | 257 | */ | ||
930 | 258 | _goFullscreen: function(ev) { | ||
931 | 259 | ev.halt(); | ||
932 | 260 | this.fire('viewNavigate', { | ||
933 | 261 | change: { | ||
934 | 262 | viewmode: 'fullscreen' | ||
935 | 263 | } | ||
936 | 264 | }); | ||
937 | 265 | }, | ||
938 | 266 | |||
939 | 267 | /** | ||
940 | 268 | Upon clicking the build icon make sure we re-route to the | ||
941 | 269 | new form of the UX. | ||
942 | 270 | |||
943 | 271 | @method _goSidebar | ||
944 | 272 | @param {Event} ev the click event handler on the button. | ||
945 | 273 | |||
946 | 274 | */ | ||
947 | 275 | _goSidebar: function(ev) { | ||
948 | 276 | ev.halt(); | ||
949 | 277 | this.fire('viewNavigate', { | ||
950 | 278 | change: { | ||
951 | 279 | viewmode: 'sidebar' | ||
952 | 280 | } | ||
953 | 281 | }); | ||
954 | 282 | }, | ||
955 | 283 | |||
956 | 284 | /** | ||
957 | 285 | * Destroy this view and clear from the dom world. | 240 | * Destroy this view and clear from the dom world. |
958 | 286 | * | 241 | * |
959 | 287 | * @method destructor | 242 | * @method destructor |
960 | 288 | * | ||
961 | 289 | */ | 243 | */ |
962 | 290 | destructor: function() { | 244 | destructor: function() { |
963 | 291 | // Clean up any details view we might have hanging around. | 245 | // Clean up any details view we might have hanging around. |
964 | 292 | 246 | ||
965 | === removed file 'app/templates/category-icons.partial' | |||
966 | --- app/templates/category-icons.partial 2013-07-10 08:10:08 +0000 | |||
967 | +++ app/templates/category-icons.partial 1970-01-01 00:00:00 +0000 | |||
968 | @@ -1,57 +0,0 @@ | |||
969 | 1 | <div id="category-links" class="categories"> | ||
970 | 2 | {{#unless isFullscreen}} | ||
971 | 3 | <h3 class="section-title"> | ||
972 | 4 | Browse by category | ||
973 | 5 | </h3> | ||
974 | 6 | {{/unless}} | ||
975 | 7 | <ul> | ||
976 | 8 | <li> | ||
977 | 9 | <a data-link="databases" href=""> | ||
978 | 10 | <img | ||
979 | 11 | src="/juju-ui/assets/images/non-sprites/category_icons/category-database.svg" | ||
980 | 12 | alt="Databases" /> | ||
981 | 13 | <span>Databases</span> | ||
982 | 14 | </a> | ||
983 | 15 | </li> | ||
984 | 16 | <li> | ||
985 | 17 | <a data-link="file-servers" href=""> | ||
986 | 18 | <img | ||
987 | 19 | src="/juju-ui/assets/images/non-sprites/category_icons/category-file-server.svg" | ||
988 | 20 | alt="File Servers" /> | ||
989 | 21 | <span>File Servers</span> | ||
990 | 22 | </a> | ||
991 | 23 | </li> | ||
992 | 24 | <li> | ||
993 | 25 | <a data-link="applications" href=""> | ||
994 | 26 | <img | ||
995 | 27 | src="/juju-ui/assets/images/non-sprites/category_icons/category-application.svg" | ||
996 | 28 | alt="Applications" /> | ||
997 | 29 | <span>Applications</span> | ||
998 | 30 | </a> | ||
999 | 31 | </li> | ||
1000 | 32 | <li> | ||
1001 | 33 | <a data-link="cache-proxy" href=""> | ||
1002 | 34 | <img | ||
1003 | 35 | src="/juju-ui/assets/images/non-sprites/category_icons/category-cache-proxy.svg" | ||
1004 | 36 | alt="Cache/Proxy" /> | ||
1005 | 37 | <span>Cache/Proxy</span> | ||
1006 | 38 | </a> | ||
1007 | 39 | </li> | ||
1008 | 40 | <li> | ||
1009 | 41 | <a data-link="app-servers" href=""> | ||
1010 | 42 | <img | ||
1011 | 43 | src="/juju-ui/assets/images/non-sprites/category_icons/category-app-server.svg" | ||
1012 | 44 | alt="App Servers" /> | ||
1013 | 45 | <span>App Servers</span> | ||
1014 | 46 | </a> | ||
1015 | 47 | </li> | ||
1016 | 48 | <li> | ||
1017 | 49 | <a data-link="misc" href=""> | ||
1018 | 50 | <img | ||
1019 | 51 | src="/juju-ui/assets/images/non-sprites/category_icons/category-misc.svg" | ||
1020 | 52 | alt="Miscellaneous" /> | ||
1021 | 53 | <span>Miscellaneous</span> | ||
1022 | 54 | </a> | ||
1023 | 55 | </li> | ||
1024 | 56 | </ul> | ||
1025 | 57 | </div> | ||
1026 | 58 | 0 | ||
1027 | === modified file 'app/templates/ghost-config-viewlet.handlebars' | |||
1028 | --- app/templates/ghost-config-viewlet.handlebars 2013-08-08 00:06:48 +0000 | |||
1029 | +++ app/templates/ghost-config-viewlet.handlebars 2013-08-30 19:48:17 +0000 | |||
1030 | @@ -8,6 +8,17 @@ | |||
1031 | 8 | </div> | 8 | </div> |
1032 | 9 | </div> | 9 | </div> |
1033 | 10 | {{/unless}} | 10 | {{/unless}} |
1034 | 11 | |||
1035 | 12 | {{#if constraints}} | ||
1036 | 13 | <!-- Service Constraints added in --> | ||
1037 | 14 | <div class="ghost-config-wrapper service-constraints"> | ||
1038 | 15 | <div class="ghost-config-header">Constraints</div> | ||
1039 | 16 | <div class="ghost-config-content use-defaults"> | ||
1040 | 17 | {{> service-constraints-viewlet}} | ||
1041 | 18 | </div> | ||
1042 | 19 | </div> | ||
1043 | 20 | {{/if}} | ||
1044 | 21 | |||
1045 | 11 | {{#if settings}} | 22 | {{#if settings}} |
1046 | 12 | <!-- Service configuration form --> | 23 | <!-- Service configuration form --> |
1047 | 13 | <div class="ghost-config-wrapper service-configuration"> | 24 | <div class="ghost-config-wrapper service-configuration"> |
1048 | @@ -35,4 +46,5 @@ | |||
1049 | 35 | </div> | 46 | </div> |
1050 | 36 | </div> | 47 | </div> |
1051 | 37 | {{/if}} | 48 | {{/if}} |
1052 | 49 | |||
1053 | 38 | </div> | 50 | </div> |
1054 | 39 | 51 | ||
1055 | === modified file 'app/templates/inspector-header.handlebars' | |||
1056 | --- app/templates/inspector-header.handlebars 2013-07-25 19:15:42 +0000 | |||
1057 | +++ app/templates/inspector-header.handlebars 2013-08-30 19:48:17 +0000 | |||
1058 | @@ -1,6 +1,8 @@ | |||
1059 | 1 | <header> | 1 | <header> |
1060 | 2 | <div class="service-charm"> | 2 | <div class="service-charm"> |
1062 | 3 | <div class="charm-icon" data-bind="icon"></div> | 3 | <div class="icon"> |
1063 | 4 | <img src="{{icon}}" alt="{{name}} icon" class="icon"> | ||
1064 | 5 | </div> | ||
1065 | 4 | <div class="details-wrapper"> | 6 | <div class="details-wrapper"> |
1066 | 5 | {{#if ghost}} | 7 | {{#if ghost}} |
1067 | 6 | <input type="text" class="service-name config-field" name="service-name" value="{{package_name}}"><br /> | 8 | <input type="text" class="service-name config-field" name="service-name" value="{{package_name}}"><br /> |
1068 | 7 | 9 | ||
1069 | === renamed file 'app/templates/viewlet-manager.handlebars' => 'app/templates/service-config-wrapper.handlebars' | |||
1070 | === modified file 'app/templates/service-constraints-viewlet.partial' | |||
1071 | --- app/templates/service-constraints-viewlet.partial 2013-08-05 02:08:41 +0000 | |||
1072 | +++ app/templates/service-constraints-viewlet.partial 2013-08-30 19:48:17 +0000 | |||
1073 | @@ -1,6 +1,6 @@ | |||
1074 | 1 | {{#constraints}} | 1 | {{#constraints}} |
1077 | 2 | <div class="control-group settings-wrapper"> | 2 | <div class="settings-wrapper"> |
1078 | 3 | <div class="control-label" for="{{name}}">{{title}}</div> | 3 | <label for="{{name}}">{{title}}</label> |
1079 | 4 | <div> | 4 | <div> |
1080 | 5 | <input class="constraint-field" type="text" name="{{name}}" | 5 | <input class="constraint-field" type="text" name="{{name}}" |
1081 | 6 | value="{{value}}" data-bind="constraints.{{name}}" /> | 6 | value="{{value}}" data-bind="constraints.{{name}}" /> |
1082 | 7 | 7 | ||
1083 | === modified file 'app/templates/service-overview-constraints.handlebars' | |||
1084 | --- app/templates/service-overview-constraints.handlebars 2013-08-20 15:27:44 +0000 | |||
1085 | +++ app/templates/service-overview-constraints.handlebars 2013-08-30 19:48:17 +0000 | |||
1086 | @@ -1,25 +1,30 @@ | |||
1087 | 1 | <span>Scale up with the following constraints?</span> | 1 | <span>Scale up with the following constraints?</span> |
1106 | 2 | <span class="constraint-details"> | 2 | <span class="constraint-details hide-on-edit"> |
1107 | 3 | {{#if cpu}} | 3 | {{#srvConstraints}} |
1108 | 4 | {{cpu}}Ghz | 4 | {{#if cpu}} |
1109 | 5 | {{else}} | 5 | {{cpu}}Ghz |
1110 | 6 | Default CPU | 6 | {{else}} |
1111 | 7 | {{/if}} | 7 | Default CPU |
1112 | 8 | | 8 | {{/if}} |
1113 | 9 | {{#if mem}} | 9 | |
1114 | 10 | {{mem}}GB | 10 | {{#if mem}} |
1115 | 11 | {{else}} | 11 | {{mem}}GB |
1116 | 12 | Default Mem | 12 | {{else}} |
1117 | 13 | {{/if}} | 13 | Default Mem |
1118 | 14 | | 14 | {{/if}} |
1119 | 15 | {{#if arch}} | 15 | |
1120 | 16 | {{arch}} | 16 | {{#if arch}} |
1121 | 17 | {{else}} | 17 | {{arch}} |
1122 | 18 | Default Arch | 18 | {{else}} |
1123 | 19 | {{/if}} | 19 | Default Arch |
1124 | 20 | {{/if}} | ||
1125 | 21 | {{/srvConstraints}} | ||
1126 | 20 | </span> | 22 | </span> |
1127 | 21 | <div class="edit-constraints-wrapper"> | 23 | <div class="edit-constraints-wrapper"> |
1129 | 22 | <a class="edit-constraints">Edit</a> | 24 | <a class="edit-constraints hide-on-edit">Edit</a> |
1130 | 25 | <div class="editable-constraints" style="display: none;"> | ||
1131 | 26 | {{> service-constraints-viewlet}} | ||
1132 | 27 | </div> | ||
1133 | 23 | </div> | 28 | </div> |
1134 | 24 | <div class="overview-constraints"></div> | 29 | <div class="overview-constraints"></div> |
1135 | 25 | <div class="inspector-buttons"> | 30 | <div class="inspector-buttons"> |
1136 | 26 | 31 | ||
1137 | === modified file 'app/views/charm-panel.js' | |||
1138 | --- app/views/charm-panel.js 2013-08-14 15:52:29 +0000 | |||
1139 | +++ app/views/charm-panel.js 2013-08-30 19:48:17 +0000 | |||
1140 | @@ -632,7 +632,7 @@ | |||
1141 | 632 | } | 632 | } |
1142 | 633 | numUnits = charm.get('is_subordinate') ? 0 : parseInt(numUnits, 10); | 633 | numUnits = charm.get('is_subordinate') ? 0 : parseInt(numUnits, 10); |
1143 | 634 | env.deploy(url, serviceName, config, this.configFileContent, | 634 | env.deploy(url, serviceName, config, this.configFileContent, |
1145 | 635 | numUnits, function(ev) { | 635 | numUnits, null, function(ev) { |
1146 | 636 | if (ev.err) { | 636 | if (ev.err) { |
1147 | 637 | console.log(url + ' deployment failed', ev.err); | 637 | console.log(url + ' deployment failed', ev.err); |
1148 | 638 | db.notifications.add( | 638 | db.notifications.add( |
1149 | 639 | 639 | ||
1150 | === modified file 'app/views/charm.js' | |||
1151 | --- app/views/charm.js 2013-08-09 20:52:35 +0000 | |||
1152 | +++ app/views/charm.js 2013-08-30 19:48:17 +0000 | |||
1153 | @@ -153,6 +153,9 @@ | |||
1154 | 153 | charmId, | 153 | charmId, |
1155 | 154 | serviceName, | 154 | serviceName, |
1156 | 155 | config, | 155 | config, |
1157 | 156 | null, | ||
1158 | 157 | null, | ||
1159 | 158 | null, | ||
1160 | 156 | Y.bind(this._deployCallback, this) | 159 | Y.bind(this._deployCallback, this) |
1161 | 157 | ); | 160 | ); |
1162 | 158 | }, | 161 | }, |
1163 | 159 | 162 | ||
1164 | === modified file 'app/views/environment.js' | |||
1165 | --- app/views/environment.js 2013-08-21 15:47:36 +0000 | |||
1166 | +++ app/views/environment.js 2013-08-30 19:48:17 +0000 | |||
1167 | @@ -194,7 +194,7 @@ | |||
1168 | 194 | }, | 194 | }, |
1169 | 195 | '.cancel-num-units': { click: '_closeUnitConfirm'}, | 195 | '.cancel-num-units': { click: '_closeUnitConfirm'}, |
1170 | 196 | '.confirm-num-units': { click: '_confirmUnitChange'}, | 196 | '.confirm-num-units': { click: '_confirmUnitChange'}, |
1172 | 197 | 'a.edit-constraints': { click: '_editUnitConstraints'}, | 197 | 'a.edit-constraints': { click: '_showEditUnitConstraints'}, |
1173 | 198 | // Settings viewlet. | 198 | // Settings viewlet. |
1174 | 199 | 'input.expose-toggle': { click: 'toggleExpose' }, | 199 | 'input.expose-toggle': { click: 'toggleExpose' }, |
1175 | 200 | '.config-file .fakebutton': { click: 'handleFileClick'}, | 200 | '.config-file .fakebutton': { click: 'handleFileClick'}, |
1176 | @@ -216,7 +216,7 @@ | |||
1177 | 216 | 'unitDetails', | 216 | 'unitDetails', |
1178 | 217 | 'inspectorHeader' | 217 | 'inspectorHeader' |
1179 | 218 | ], | 218 | ], |
1181 | 219 | template: Y.juju.views.Templates['viewlet-manager'] | 219 | template: Y.juju.views.Templates['service-config-wrapper'] |
1182 | 220 | }, | 220 | }, |
1183 | 221 | configGhost: { | 221 | configGhost: { |
1184 | 222 | // controller will show the first one in this array by default | 222 | // controller will show the first one in this array by default |
1185 | 223 | 223 | ||
1186 | === modified file 'app/views/ghost-inspector.js' | |||
1187 | --- app/views/ghost-inspector.js 2013-08-14 15:31:50 +0000 | |||
1188 | +++ app/views/ghost-inspector.js 2013-08-30 19:48:17 +0000 | |||
1189 | @@ -121,13 +121,22 @@ | |||
1190 | 121 | container, '.service-config .config-field'); | 121 | container, '.service-config .config-field'); |
1191 | 122 | } | 122 | } |
1192 | 123 | 123 | ||
1193 | 124 | // Deploy needs constraints in simple key:value object. | ||
1194 | 125 | var constraints = utils.getElementsValuesMapping( | ||
1195 | 126 | container, '.constraint-field'); | ||
1196 | 127 | |||
1197 | 124 | options.env.deploy( | 128 | options.env.deploy( |
1198 | 125 | model.get('id'), | 129 | model.get('id'), |
1199 | 126 | serviceName, | 130 | serviceName, |
1200 | 127 | config, | 131 | config, |
1201 | 128 | this.viewletManager.configFileContent, | 132 | this.viewletManager.configFileContent, |
1202 | 129 | numUnits, | 133 | numUnits, |
1204 | 130 | Y.bind(this._deployCallbackHandler, this, serviceName, config)); | 134 | constraints, |
1205 | 135 | Y.bind(this._deployCallbackHandler, | ||
1206 | 136 | this, | ||
1207 | 137 | serviceName, | ||
1208 | 138 | config, | ||
1209 | 139 | constraints)); | ||
1210 | 131 | }, | 140 | }, |
1211 | 132 | 141 | ||
1212 | 133 | /** | 142 | /** |
1213 | @@ -225,10 +234,10 @@ | |||
1214 | 225 | 234 | ||
1215 | 226 | @method _deployCallbackHandler | 235 | @method _deployCallbackHandler |
1216 | 227 | @param {String} serviceName The service name. | 236 | @param {String} serviceName The service name. |
1218 | 228 | @param {Object} config The configuration oject of the service. | 237 | @param {Object} config The configuration object of the service. |
1219 | 229 | @param {Y.EventFacade} e The event facade from the deploy event. | 238 | @param {Y.EventFacade} e The event facade from the deploy event. |
1220 | 230 | */ | 239 | */ |
1222 | 231 | _deployCallbackHandler: function(serviceName, config, e) { | 240 | _deployCallbackHandler: function(serviceName, config, constraints, e) { |
1223 | 232 | var options = this.options, | 241 | var options = this.options, |
1224 | 233 | db = options.db, | 242 | db = options.db, |
1225 | 234 | ghostService = options.ghostService; | 243 | ghostService = options.ghostService; |
1226 | @@ -283,7 +292,8 @@ | |||
1227 | 283 | id: serviceName, | 292 | id: serviceName, |
1228 | 284 | pending: false, | 293 | pending: false, |
1229 | 285 | loading: false, | 294 | loading: false, |
1231 | 286 | config: config | 295 | config: config, |
1232 | 296 | constraints: constraints | ||
1233 | 287 | }); | 297 | }); |
1234 | 288 | 298 | ||
1235 | 289 | this.closeInspector(); | 299 | this.closeInspector(); |
1236 | @@ -291,4 +301,7 @@ | |||
1237 | 291 | 301 | ||
1238 | 292 | }; | 302 | }; |
1239 | 293 | 303 | ||
1240 | 304 | }, '0.1.0', { | ||
1241 | 305 | requires: [ | ||
1242 | 306 | ] | ||
1243 | 294 | }); | 307 | }); |
1244 | 295 | 308 | ||
1245 | === modified file 'app/views/inspector.js' | |||
1246 | --- app/views/inspector.js 2013-08-16 20:29:39 +0000 | |||
1247 | +++ app/views/inspector.js 2013-08-30 19:48:17 +0000 | |||
1248 | @@ -123,10 +123,14 @@ | |||
1249 | 123 | */ | 123 | */ |
1250 | 124 | _confirmUnitConstraints: function(requestedUnitCount) { | 124 | _confirmUnitConstraints: function(requestedUnitCount) { |
1251 | 125 | var container = this.viewletManager.viewlets.overview.container, | 125 | var container = this.viewletManager.viewlets.overview.container, |
1252 | 126 | genericConstraints = this.options.env.genericConstraints, | ||
1253 | 126 | confirm = container.one('.unit-constraints-confirm'), | 127 | confirm = container.one('.unit-constraints-confirm'), |
1255 | 127 | constraints = this.model.get('constraints') || {}; | 128 | srvConstraints = this.model.get('constraints') || {}; |
1256 | 128 | 129 | ||
1258 | 129 | confirm.setHTML(Templates['service-overview-constraints'](constraints)); | 130 | confirm.setHTML(Templates['service-overview-constraints']({ |
1259 | 131 | srvConstraints: srvConstraints, | ||
1260 | 132 | constraints: utils.getConstraints(srvConstraints, genericConstraints) | ||
1261 | 133 | })); | ||
1262 | 130 | confirm.removeClass('closed'); | 134 | confirm.removeClass('closed'); |
1263 | 131 | }, | 135 | }, |
1264 | 132 | 136 | ||
1265 | @@ -145,7 +149,10 @@ | |||
1266 | 145 | this.resetUnits(); | 149 | this.resetUnits(); |
1267 | 146 | } | 150 | } |
1268 | 147 | 151 | ||
1269 | 152 | // editing class added if the user clicked 'edit' | ||
1270 | 153 | confirm.removeClass('editing'); | ||
1271 | 148 | confirm.addClass('closed'); | 154 | confirm.addClass('closed'); |
1272 | 155 | this.overviewConstraintsEdit = false; | ||
1273 | 149 | }, | 156 | }, |
1274 | 150 | 157 | ||
1275 | 151 | /** | 158 | /** |
1276 | @@ -156,8 +163,19 @@ | |||
1277 | 156 | */ | 163 | */ |
1278 | 157 | _confirmUnitChange: function(e) { | 164 | _confirmUnitChange: function(e) { |
1279 | 158 | e.halt(); | 165 | e.halt(); |
1282 | 159 | var container = this.viewletManager.viewlets.overview.container; | 166 | var container = this.viewletManager.viewlets.overview.container, |
1283 | 160 | this._modifyUnits(container.one('input.num-units-control').get('value')); | 167 | unitCount = container.one('input.num-units-control').get('value'), |
1284 | 168 | service = this.model; | ||
1285 | 169 | |||
1286 | 170 | // If the user chose to edit the constraints | ||
1287 | 171 | if (this.overviewConstraintsEdit) { | ||
1288 | 172 | var constraints = utils.getElementsValuesMapping( | ||
1289 | 173 | container, '.constraint-field'); | ||
1290 | 174 | var cb = Y.bind(this._modifyUnits, this, unitCount); | ||
1291 | 175 | this.options.env.set_constraints(service.get('id'), constraints, cb); | ||
1292 | 176 | } else { | ||
1293 | 177 | this._modifyUnits(unitCount); | ||
1294 | 178 | } | ||
1295 | 161 | this._closeUnitConfirm(); | 179 | this._closeUnitConfirm(); |
1296 | 162 | }, | 180 | }, |
1297 | 163 | 181 | ||
1298 | @@ -165,11 +183,15 @@ | |||
1299 | 165 | Shows the unit constraints when the user wants to edit them | 183 | Shows the unit constraints when the user wants to edit them |
1300 | 166 | while increasing the total number of units | 184 | while increasing the total number of units |
1301 | 167 | 185 | ||
1303 | 168 | @method _editUnitConstraints | 186 | @method _showEditUnitConstraints |
1304 | 169 | */ | 187 | */ |
1308 | 170 | _editUnitConstraints: function() { | 188 | _showEditUnitConstraints: function(e) { |
1309 | 171 | // show constraints viewlet on overview page to allow the user to | 189 | e.halt(); |
1310 | 172 | // edit them without changing viewlets. | 190 | var container = this.viewletManager.viewlets.overview.container; |
1311 | 191 | container.all('.hide-on-edit').hide(); | ||
1312 | 192 | container.one('.editable-constraints').show(); | ||
1313 | 193 | container.one('.unit-constraints-confirm').addClass('editing'); | ||
1314 | 194 | this.overviewConstraintsEdit = true; | ||
1315 | 173 | }, | 195 | }, |
1316 | 174 | 196 | ||
1317 | 175 | _modifyUnits: function(requested_unit_count) { | 197 | _modifyUnits: function(requested_unit_count) { |
1318 | @@ -181,6 +203,7 @@ | |||
1319 | 181 | container = this.get('container'); | 203 | container = this.get('container'); |
1320 | 182 | env = this.get('env'); | 204 | env = this.get('env'); |
1321 | 183 | } | 205 | } |
1322 | 206 | |||
1323 | 184 | var service = this.model || this.get('model'); | 207 | var service = this.model || this.get('model'); |
1324 | 185 | var unit_count = service.get('unit_count'); | 208 | var unit_count = service.get('unit_count'); |
1325 | 186 | var field = container.one('.num-units-control'); | 209 | var field = container.one('.num-units-control'); |
1326 | 187 | 210 | ||
1327 | === added file 'app/views/topology/bundle.js' | |||
1328 | --- app/views/topology/bundle.js 1970-01-01 00:00:00 +0000 | |||
1329 | +++ app/views/topology/bundle.js 2013-08-30 19:48:17 +0000 | |||
1330 | @@ -0,0 +1,368 @@ | |||
1331 | 1 | /* | ||
1332 | 2 | This file is part of the Juju GUI, which lets users view and manage Juju | ||
1333 | 3 | environments within a graphical interface (https://launchpad.net/juju-gui). | ||
1334 | 4 | Copyright (C) 2012-2013 Canonical Ltd. | ||
1335 | 5 | |||
1336 | 6 | This program is free software: you can redistribute it and/or modify it under | ||
1337 | 7 | the terms of the GNU Affero General Public License version 3, as published by | ||
1338 | 8 | the Free Software Foundation. | ||
1339 | 9 | |||
1340 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT | ||
1341 | 11 | ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | ||
1342 | 12 | SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero | ||
1343 | 13 | General Public License for more details. | ||
1344 | 14 | |||
1345 | 15 | You should have received a copy of the GNU Affero General Public License along | ||
1346 | 16 | with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1347 | 17 | */ | ||
1348 | 18 | |||
1349 | 19 | 'use strict'; | ||
1350 | 20 | |||
1351 | 21 | /** | ||
1352 | 22 | * Provide the BundleTopology class. | ||
1353 | 23 | * | ||
1354 | 24 | * @module views | ||
1355 | 25 | * @submodule views.BundleTopology | ||
1356 | 26 | */ | ||
1357 | 27 | |||
1358 | 28 | YUI.add('juju-view-bundle', function(Y) { | ||
1359 | 29 | |||
1360 | 30 | var juju = Y.namespace('juju'), | ||
1361 | 31 | views = Y.namespace('juju.views'), | ||
1362 | 32 | utils = Y.namespace('juju.views.utils'), | ||
1363 | 33 | models = Y.namespace('juju.models'), | ||
1364 | 34 | d3ns = Y.namespace('d3'), | ||
1365 | 35 | topoUtils = Y.namespace('juju.topology.utils'); | ||
1366 | 36 | |||
1367 | 37 | /** | ||
1368 | 38 | Manage service rendering and events. | ||
1369 | 39 | |||
1370 | 40 | @class BundleModule | ||
1371 | 41 | */ | ||
1372 | 42 | |||
1373 | 43 | var BundleModule = Y.Base.create('BundleModule', d3ns.Module, | ||
1374 | 44 | [views.ServiceModuleCommon], { | ||
1375 | 45 | |||
1376 | 46 | /** | ||
1377 | 47 | Attempt to reuse as much of the existing graph and view models | ||
1378 | 48 | as possible to re-render the graph. | ||
1379 | 49 | |||
1380 | 50 | @method update | ||
1381 | 51 | */ | ||
1382 | 52 | update: function() { | ||
1383 | 53 | var self = this, | ||
1384 | 54 | topo = this.get('component'), | ||
1385 | 55 | width = topo.get('width'), | ||
1386 | 56 | height = topo.get('height'); | ||
1387 | 57 | |||
1388 | 58 | // Process any changed data. | ||
1389 | 59 | this.updateData(); | ||
1390 | 60 | |||
1391 | 61 | // Generate a node for each service, draw it as a rect with | ||
1392 | 62 | // labels for service and charm. | ||
1393 | 63 | var node = this.node; | ||
1394 | 64 | |||
1395 | 65 | // enter | ||
1396 | 66 | node | ||
1397 | 67 | .enter().append('g') | ||
1398 | 68 | .attr({ | ||
1399 | 69 | 'class': function(d) { | ||
1400 | 70 | return (d.subordinate ? 'subordinate ' : '') + | ||
1401 | 71 | (d.pending ? 'pending ' : '') + 'service'; | ||
1402 | 72 | }, | ||
1403 | 73 | 'transform': function(d) { return d.translateStr;}}) | ||
1404 | 74 | .call(self.createServiceNode, self); | ||
1405 | 75 | |||
1406 | 76 | // Update all nodes. | ||
1407 | 77 | self.updateServiceNodes(node); | ||
1408 | 78 | }, | ||
1409 | 79 | |||
1410 | 80 | /** | ||
1411 | 81 | Fill a service node with empty structures that will be filled out | ||
1412 | 82 | in the update stage. | ||
1413 | 83 | |||
1414 | 84 | @param {object} node the node to construct. | ||
1415 | 85 | @param {object} self reference to the view instance. | ||
1416 | 86 | @return {null} side effects only. | ||
1417 | 87 | @method createServiceNode | ||
1418 | 88 | */ | ||
1419 | 89 | createServiceNode: function(node, self) { | ||
1420 | 90 | node.append('image') | ||
1421 | 91 | .classed('service-icon', true) | ||
1422 | 92 | .attr({ | ||
1423 | 93 | 'xlink:href': function(d) { | ||
1424 | 94 | return d.icon; | ||
1425 | 95 | }, | ||
1426 | 96 | width: 96, | ||
1427 | 97 | height: 96 | ||
1428 | 98 | }); | ||
1429 | 99 | node.append('text').append('tspan') | ||
1430 | 100 | .attr('class', 'name') | ||
1431 | 101 | .text(function(d) {return d.displayName; }); | ||
1432 | 102 | }, | ||
1433 | 103 | |||
1434 | 104 | /** | ||
1435 | 105 | Fill the empty structures within a service node such that they | ||
1436 | 106 | match the db. | ||
1437 | 107 | |||
1438 | 108 | @param {object} node the collection of nodes to update. | ||
1439 | 109 | @return {null} side effects only. | ||
1440 | 110 | @method updateServiceNodes | ||
1441 | 111 | */ | ||
1442 | 112 | updateServiceNodes: function(node) { | ||
1443 | 113 | if (node.empty()) { | ||
1444 | 114 | return; | ||
1445 | 115 | } | ||
1446 | 116 | var self = this, | ||
1447 | 117 | topo = this.get('component'), | ||
1448 | 118 | landscape = topo.get('landscape'); | ||
1449 | 119 | |||
1450 | 120 | // Apply Position Annotations | ||
1451 | 121 | // This is done after the services_boxes | ||
1452 | 122 | // binding as the event handler will | ||
1453 | 123 | // use that index. | ||
1454 | 124 | node.each(function(d) { | ||
1455 | 125 | var service = d.model, | ||
1456 | 126 | annotations = service.get('annotations'), | ||
1457 | 127 | x, y; | ||
1458 | 128 | |||
1459 | 129 | if (!annotations) { | ||
1460 | 130 | return; | ||
1461 | 131 | } | ||
1462 | 132 | |||
1463 | 133 | // If there are x/y annotations on the service model and they are | ||
1464 | 134 | // different from the node's current x/y coordinates, update the | ||
1465 | 135 | // node, as the annotations may have been set in another session. | ||
1466 | 136 | x = annotations['gui-x']; | ||
1467 | 137 | y = annotations['gui-y']; | ||
1468 | 138 | if (!d || | ||
1469 | 139 | (x !== undefined && x !== d.x) || | ||
1470 | 140 | (y !== undefined && y !== d.y)) { | ||
1471 | 141 | d.x = x; | ||
1472 | 142 | d.y = y; | ||
1473 | 143 | d3.select(this).attr({ | ||
1474 | 144 | x: x, | ||
1475 | 145 | y: y, | ||
1476 | 146 | transform: d.translateStr}); | ||
1477 | 147 | |||
1478 | 148 | }}); | ||
1479 | 149 | |||
1480 | 150 | // Mark subordinates as such. This is needed for when a new service | ||
1481 | 151 | // is created. | ||
1482 | 152 | node.filter(function(d) { | ||
1483 | 153 | return d.subordinate; | ||
1484 | 154 | }).classed('subordinate', true); | ||
1485 | 155 | |||
1486 | 156 | // Size the node for drawing. | ||
1487 | 157 | node.attr({ | ||
1488 | 158 | 'width': function(box) { box.w = 96; return box.w;}, | ||
1489 | 159 | 'height': function(box) { box.h = 96; return box.h;} | ||
1490 | 160 | }); | ||
1491 | 161 | |||
1492 | 162 | // Draw a subordinate relation indicator. | ||
1493 | 163 | var subRelationIndicator = node.filter(function(d) { | ||
1494 | 164 | return d.subordinate && | ||
1495 | 165 | d3.select(this) | ||
1496 | 166 | .select('.sub-rel-block').empty(); | ||
1497 | 167 | }) | ||
1498 | 168 | .append('g') | ||
1499 | 169 | .attr('class', 'sub-rel-block') | ||
1500 | 170 | .attr('transform', function(d) { | ||
1501 | 171 | // Position the block so that the relation indicator will | ||
1502 | 172 | // appear at the right connector. | ||
1503 | 173 | return 'translate(' + [d.w, d.h / 2 - 26] + ')'; | ||
1504 | 174 | }); | ||
1505 | 175 | |||
1506 | 176 | subRelationIndicator.append('image') | ||
1507 | 177 | .attr({'xlink:href': '/juju-ui/assets/svgs/sub_relation.svg', | ||
1508 | 178 | 'width': 87, | ||
1509 | 179 | 'height': 47}); | ||
1510 | 180 | subRelationIndicator.append('text').append('tspan') | ||
1511 | 181 | .attr({'class': 'sub-rel-count', | ||
1512 | 182 | 'x': 64, | ||
1513 | 183 | 'y': 47 * 0.8}); | ||
1514 | 184 | |||
1515 | 185 | // The following are sizes in pixels of the SVG assets used to | ||
1516 | 186 | // render a service, and are used to in calculating the vertical | ||
1517 | 187 | // positioning of text down along the service block. | ||
1518 | 188 | var service_height = 224, | ||
1519 | 189 | name_size = 22, | ||
1520 | 190 | charm_label_size = 16, | ||
1521 | 191 | name_padding = 26, | ||
1522 | 192 | charm_label_padding = 150; | ||
1523 | 193 | |||
1524 | 194 | node.select('.name') | ||
1525 | 195 | .attr({'style': function(d) { | ||
1526 | 196 | // Programmatically size the font. | ||
1527 | 197 | // Number derived from service assets: | ||
1528 | 198 | // font-size 22px when asset is 224px. | ||
1529 | 199 | return 'font-size:' + d.h * | ||
1530 | 200 | (name_size / service_height) + 'px'; | ||
1531 | 201 | }, | ||
1532 | 202 | 'x': function(d) { return d.w / 2; }, | ||
1533 | 203 | 'y': function(d) { | ||
1534 | 204 | // Number derived from service assets: | ||
1535 | 205 | // padding-top 26px when asset is 224px. | ||
1536 | 206 | return d.h * (name_padding / service_height) + d.h * | ||
1537 | 207 | (name_size / service_height) / 2; | ||
1538 | 208 | } | ||
1539 | 209 | }); | ||
1540 | 210 | |||
1541 | 211 | // Show whether or not the service is exposed using an indicator. | ||
1542 | 212 | var exposed = node.filter(function(d) { | ||
1543 | 213 | return d.exposed; | ||
1544 | 214 | }); | ||
1545 | 215 | exposed.each(function(d) { | ||
1546 | 216 | var existing = Y.one(this).one('.exposed-indicator'); | ||
1547 | 217 | if (!existing) { | ||
1548 | 218 | existing = d3.select(this).append('image') | ||
1549 | 219 | .attr({'class': 'exposed-indicator on', | ||
1550 | 220 | 'xlink:href': '/juju-ui/assets/svgs/exposed.svg', | ||
1551 | 221 | 'width': 32, | ||
1552 | 222 | 'height': 32 | ||
1553 | 223 | }) | ||
1554 | 224 | .append('title') | ||
1555 | 225 | .text(function(d) { | ||
1556 | 226 | return d.exposed ? 'Exposed' : ''; | ||
1557 | 227 | }); | ||
1558 | 228 | } | ||
1559 | 229 | existing = d3.select(this).select('.exposed-indicator') | ||
1560 | 230 | .attr({ | ||
1561 | 231 | 'x': 145, | ||
1562 | 232 | 'y': 79 | ||
1563 | 233 | }); | ||
1564 | 234 | }); | ||
1565 | 235 | }, | ||
1566 | 236 | |||
1567 | 237 | |||
1568 | 238 | /** | ||
1569 | 239 | Pans the environment view to the center all the services on the canvas. | ||
1570 | 240 | |||
1571 | 241 | @method panToCenter | ||
1572 | 242 | @param {object} evt The event fired. | ||
1573 | 243 | @return {undefined} Side effects only. | ||
1574 | 244 | */ | ||
1575 | 245 | panToCenter: function(evt) { | ||
1576 | 246 | var topo = this.get('component'); | ||
1577 | 247 | var vertices = topoUtils.serviceBoxesToVertices(topo.service_boxes); | ||
1578 | 248 | this.findAndSetCentroid(vertices); | ||
1579 | 249 | }, | ||
1580 | 250 | |||
1581 | 251 | /** | ||
1582 | 252 | Given a set of vertices, find the centroid and pan to that location. | ||
1583 | 253 | |||
1584 | 254 | @method findAndSetCentroid | ||
1585 | 255 | @param {array} vertices A list of vertices in the form [x, y]. | ||
1586 | 256 | @return {undefined} Side effects only. | ||
1587 | 257 | */ | ||
1588 | 258 | findAndSetCentroid: function(vertices) { | ||
1589 | 259 | var topo = this.get('component'); | ||
1590 | 260 | var centroid = topoUtils.centroid(vertices); | ||
1591 | 261 | /* The centroid is set on the topology object due to the fact that it | ||
1592 | 262 | is used as a sigil to tell whether or not to pan to the point | ||
1593 | 263 | after the first delta. */ | ||
1594 | 264 | topo.centroid = centroid; | ||
1595 | 265 | topo.fire('panToPoint', {point: topo.centroid}); | ||
1596 | 266 | } | ||
1597 | 267 | }, { | ||
1598 | 268 | ATTRS: { | ||
1599 | 269 | /** | ||
1600 | 270 | @property {d3ns.Component} component | ||
1601 | 271 | */ | ||
1602 | 272 | component: {} | ||
1603 | 273 | } | ||
1604 | 274 | }); | ||
1605 | 275 | views.BundleModule = BundleModule; | ||
1606 | 276 | |||
1607 | 277 | /** | ||
1608 | 278 | Display a Bundle using an internal topology. | ||
1609 | 279 | |||
1610 | 280 | @class BundleTopology | ||
1611 | 281 | */ | ||
1612 | 282 | function BundleTopology(options) { | ||
1613 | 283 | // Options and Init | ||
1614 | 284 | var self = this; | ||
1615 | 285 | options = options || {}; | ||
1616 | 286 | this.options = options; | ||
1617 | 287 | this._cleanups = []; | ||
1618 | 288 | this.db = options.db; | ||
1619 | 289 | if (!this.db) { | ||
1620 | 290 | this.db = new models.Database(); | ||
1621 | 291 | this._cleanups.push(this.db.destroy); | ||
1622 | 292 | } | ||
1623 | 293 | this.store = options.store; | ||
1624 | 294 | if (!this.store) { | ||
1625 | 295 | this.store = new juju.Charmworld2({}); | ||
1626 | 296 | this._cleanups.push(this.store.destroy); | ||
1627 | 297 | } | ||
1628 | 298 | this.container = options.container; | ||
1629 | 299 | if (!this.container) { | ||
1630 | 300 | this.container = Y.Node.create('<div>'); | ||
1631 | 301 | this.container.addClass('topology-canvas'); | ||
1632 | 302 | this._cleanups.push(function() { | ||
1633 | 303 | self.container.remove(true); | ||
1634 | 304 | }); | ||
1635 | 305 | } | ||
1636 | 306 | |||
1637 | 307 | var topo = this.topology = new views.Topology(); | ||
1638 | 308 | topo.setAttrs(Y.mix(options, { | ||
1639 | 309 | interactive: false, | ||
1640 | 310 | container: this.container, | ||
1641 | 311 | db: this.db, | ||
1642 | 312 | store: this.store | ||
1643 | 313 | }, true)); | ||
1644 | 314 | |||
1645 | 315 | // Service view doesn't support Level Of Detail views. | ||
1646 | 316 | // BundleModule provides an icon centric view | ||
1647 | 317 | // of services till service module can support this directly. | ||
1648 | 318 | topo.addModule(views.BundleModule); | ||
1649 | 319 | topo.addModule(views.RelationModule); | ||
1650 | 320 | topo.addModule(views.PanZoomModule); | ||
1651 | 321 | } | ||
1652 | 322 | |||
1653 | 323 | BundleTopology.prototype.centerViewport = function(scale) { | ||
1654 | 324 | this.topology.modules.PanZoomModule._fire_zoom(scale); | ||
1655 | 325 | // Pan to the centroid of it all after the zoom | ||
1656 | 326 | this.panToCenter(); | ||
1657 | 327 | return this; | ||
1658 | 328 | }; | ||
1659 | 329 | |||
1660 | 330 | /** | ||
1661 | 331 | Pans the canvas to the center all the services. | ||
1662 | 332 | |||
1663 | 333 | @method panToCenter | ||
1664 | 334 | @param {object} evt The event fired. | ||
1665 | 335 | @return {undefined} Side effects only. | ||
1666 | 336 | */ | ||
1667 | 337 | BundleTopology.prototype.panToCenter = function() { | ||
1668 | 338 | var topo = this.topology; | ||
1669 | 339 | var vertices = topoUtils.serviceBoxesToVertices(topo.service_boxes); | ||
1670 | 340 | var centroid = topoUtils.centroid(vertices); | ||
1671 | 341 | this.topology.modules.PanZoomModule.panToPoint({point: centroid}); | ||
1672 | 342 | }; | ||
1673 | 343 | |||
1674 | 344 | |||
1675 | 345 | BundleTopology.prototype.render = function() { | ||
1676 | 346 | this.topology.render(); | ||
1677 | 347 | this.centerViewport(0.66); | ||
1678 | 348 | return this; | ||
1679 | 349 | }; | ||
1680 | 350 | |||
1681 | 351 | BundleTopology.prototype.destroy = function() { | ||
1682 | 352 | this._cleanups.forEach(function(cleanupFunc) { | ||
1683 | 353 | cleanupFunc(); | ||
1684 | 354 | }); | ||
1685 | 355 | }; | ||
1686 | 356 | |||
1687 | 357 | views.BundleTopology = BundleTopology; | ||
1688 | 358 | |||
1689 | 359 | }, '0.1.0', { | ||
1690 | 360 | requires: [ | ||
1691 | 361 | 'd3', | ||
1692 | 362 | 'd3-components', | ||
1693 | 363 | 'juju-charm-store', | ||
1694 | 364 | 'juju-models', | ||
1695 | 365 | 'juju-topology', | ||
1696 | 366 | 'juju-view-utils' | ||
1697 | 367 | ] | ||
1698 | 368 | }); | ||
1699 | 0 | 369 | ||
1700 | === modified file 'app/views/topology/relation.js' | |||
1701 | --- app/views/topology/relation.js 2013-07-03 17:35:21 +0000 | |||
1702 | +++ app/views/topology/relation.js 2013-08-30 19:48:17 +0000 | |||
1703 | @@ -178,6 +178,9 @@ | |||
1704 | 178 | var db = topo.get('db'); | 178 | var db = topo.get('db'); |
1705 | 179 | var self = this; | 179 | var self = this; |
1706 | 180 | var relations = db.relations.toArray(); | 180 | var relations = db.relations.toArray(); |
1707 | 181 | if (!relations || relations.length === 0) { | ||
1708 | 182 | return; | ||
1709 | 183 | } | ||
1710 | 181 | this.relations = this.decorateRelations(relations); | 184 | this.relations = this.decorateRelations(relations); |
1711 | 182 | this.updateLinks(); | 185 | this.updateLinks(); |
1712 | 183 | this.updateSubordinateRelationsCount(); | 186 | this.updateSubordinateRelationsCount(); |
1713 | 184 | 187 | ||
1714 | === modified file 'app/views/topology/service.js' | |||
1715 | --- app/views/topology/service.js 2013-08-21 16:02:10 +0000 | |||
1716 | +++ app/views/topology/service.js 2013-08-30 19:48:17 +0000 | |||
1717 | @@ -33,35 +33,303 @@ | |||
1718 | 33 | d3ns = Y.namespace('d3'), | 33 | d3ns = Y.namespace('d3'), |
1719 | 34 | Templates = views.Templates; | 34 | Templates = views.Templates; |
1720 | 35 | 35 | ||
1748 | 36 | /** | 36 | var ServiceModuleCommon = function() {}; |
1749 | 37 | * Manage service rendering and events. | 37 | /** |
1750 | 38 | * | 38 | Sync view models with current db.models. |
1751 | 39 | * ## Emitted events: | 39 | |
1752 | 40 | * | 40 | @method updateData |
1753 | 41 | * - *clearState:* clear all possible states that the environment view can be | 41 | */ |
1754 | 42 | * in as it pertains to actions (building a relation, viewing | 42 | ServiceModuleCommon.prototype.updateData = function() { |
1755 | 43 | * a service menu, etc.) | 43 | //model data |
1756 | 44 | * - *snapToService:* fired when mousing over a service, causing the pending | 44 | var topo = this.get('component'); |
1757 | 45 | * relation dragline to snap to the service rather than | 45 | var vis = topo.vis; |
1758 | 46 | * following the mouse. | 46 | var db = topo.get('db'); |
1759 | 47 | * - *snapOutOfService:* fired when mousing out of a service, causing the | 47 | var store = topo.get('store'); |
1760 | 48 | * pending relation line to follow the mouse again. | 48 | |
1761 | 49 | * - *addRelationDrag:* | 49 | var visibleServices = db.services.visible(); |
1762 | 50 | * - *addRelationDragStart:* | 50 | views.toBoundingBoxes(this, visibleServices, topo.service_boxes, store); |
1763 | 51 | * - *addRelationDragEnd:* fired when creating a relation through the long- | 51 | // Break a reference cycle that results in uncollectable objects leaking. |
1764 | 52 | * click process, when moving the cursor over the environment, and when | 52 | visibleServices.reset(); |
1765 | 53 | * dropping the endpoint on a valid service. | 53 | |
1766 | 54 | * - *cancelRelationBuild:* fired when dropping a pending relation line | 54 | // Nodes are mapped by modelId tuples. |
1767 | 55 | * started through the long-click method somewhere other than a valid | 55 | this.node = vis.selectAll('.service') |
1768 | 56 | * service. | 56 | .data(Y.Object.values(topo.service_boxes), |
1769 | 57 | * - *serviceMoved:* fired when a service block is dragged so that relation | 57 | function(d) {return d.modelId;}); |
1770 | 58 | * endpoints can follow it. | 58 | }; |
1771 | 59 | * - *navigateTo:* fired when clicking the "View Service" menu item or when | 59 | |
1772 | 60 | * double-clicking a service. | 60 | /** |
1773 | 61 | * | 61 | Fill the empty structures within a service node such that they |
1774 | 62 | * @class ServiceModule | 62 | match the db. |
1775 | 63 | |||
1776 | 64 | @param {object} node the collection of nodes to update. | ||
1777 | 65 | @return {null} side effects only. | ||
1778 | 66 | @method updateServiceNodes | ||
1779 | 67 | */ | ||
1780 | 68 | ServiceModuleCommon.prototype.updateServiceNodes = function(node) { | ||
1781 | 69 | if (node.empty()) { | ||
1782 | 70 | return; | ||
1783 | 71 | } | ||
1784 | 72 | var self = this, | ||
1785 | 73 | topo = this.get('component'), | ||
1786 | 74 | landscape = topo.get('landscape'), | ||
1787 | 75 | service_scale = this.service_scale, | ||
1788 | 76 | service_scale_width = this.service_scale_width, | ||
1789 | 77 | service_scale_height = this.service_scale_height; | ||
1790 | 78 | |||
1791 | 79 | // Apply Position Annotations | ||
1792 | 80 | // This is done after the services_boxes | ||
1793 | 81 | // binding as the event handler will | ||
1794 | 82 | // use that index. | ||
1795 | 83 | node.each(function(d) { | ||
1796 | 84 | var service = d.model, | ||
1797 | 85 | annotations = service.get('annotations'), | ||
1798 | 86 | x, y; | ||
1799 | 87 | |||
1800 | 88 | // If there are no annotations or the service is being dragged | ||
1801 | 89 | if (!annotations || service.inDrag === views.DRAG_ACTIVE) { | ||
1802 | 90 | return; | ||
1803 | 91 | } | ||
1804 | 92 | |||
1805 | 93 | // If there are x/y annotations on the service model and they are | ||
1806 | 94 | // different from the node's current x/y coordinates, update the | ||
1807 | 95 | // node, as the annotations may have been set in another session. | ||
1808 | 96 | x = annotations['gui-x']; | ||
1809 | 97 | y = annotations['gui-y']; | ||
1810 | 98 | if (!d || | ||
1811 | 99 | (x !== undefined && x !== d.x) || | ||
1812 | 100 | (y !== undefined && y !== d.y)) { | ||
1813 | 101 | // Delete gui-x and gui-y from annotations as we use the values. | ||
1814 | 102 | // This is to prevent deltas coming in on a service while it is | ||
1815 | 103 | // being dragged from resetting its position during the drag. | ||
1816 | 104 | |||
1817 | 105 | delete annotations['gui-x']; | ||
1818 | 106 | delete annotations['gui-y']; | ||
1819 | 107 | // Only update position if we're not already in a drag state (the | ||
1820 | 108 | // current drag supercedes any previous annotations). | ||
1821 | 109 | if (!d.inDrag) { | ||
1822 | 110 | self.drag.call(this, d, self, {x: x, y: y}, | ||
1823 | 111 | self.get('useTransitions')); | ||
1824 | 112 | } | ||
1825 | 113 | }}); | ||
1826 | 114 | |||
1827 | 115 | // Mark subordinates as such. This is needed for when a new service | ||
1828 | 116 | // is created. | ||
1829 | 117 | node.filter(function(d) { | ||
1830 | 118 | return d.subordinate; | ||
1831 | 119 | }) | ||
1832 | 120 | .classed('subordinate', true); | ||
1833 | 121 | |||
1834 | 122 | // Size the node for drawing. | ||
1835 | 123 | node.attr({ | ||
1836 | 124 | 'width': function(box) { box.w = 190; return box.w;}, | ||
1837 | 125 | 'height': function(box) { box.h = 190; return box.h;} | ||
1838 | 126 | }); | ||
1839 | 127 | |||
1840 | 128 | node.select('.service-block-image').each(function(d) { | ||
1841 | 129 | var curr_node = d3.select(this); | ||
1842 | 130 | var curr_href = curr_node.attr('xlink:href'); | ||
1843 | 131 | var new_href = d.subordinate ? | ||
1844 | 132 | '/juju-ui/assets/svgs/sub_module.svg' : | ||
1845 | 133 | '/juju-ui/assets/svgs/service_module.svg'; | ||
1846 | 134 | |||
1847 | 135 | // Only set 'xlink:href' if not already set to the new value, | ||
1848 | 136 | // thus avoiding redundant requests to the server. #1182135 | ||
1849 | 137 | if (curr_href !== new_href) { | ||
1850 | 138 | curr_node.attr({'xlink:href': new_href}); | ||
1851 | 139 | } | ||
1852 | 140 | curr_node.attr({ | ||
1853 | 141 | 'width': d.w, | ||
1854 | 142 | 'height': d.h | ||
1855 | 143 | }); | ||
1856 | 144 | }); | ||
1857 | 145 | |||
1858 | 146 | // Draw a subordinate relation indicator. | ||
1859 | 147 | var subRelationIndicator = node.filter(function(d) { | ||
1860 | 148 | return d.subordinate && | ||
1861 | 149 | d3.select(this) | ||
1862 | 150 | .select('.sub-rel-block').empty(); | ||
1863 | 151 | }) | ||
1864 | 152 | .append('g') | ||
1865 | 153 | .attr('class', 'sub-rel-block') | ||
1866 | 154 | .attr('transform', function(d) { | ||
1867 | 155 | // Position the block so that the relation indicator will | ||
1868 | 156 | // appear at the right connector. | ||
1869 | 157 | return 'translate(' + [d.w, d.h / 2 - 26] + ')'; | ||
1870 | 158 | }); | ||
1871 | 159 | |||
1872 | 160 | subRelationIndicator.append('image') | ||
1873 | 161 | .attr({'xlink:href': '/juju-ui/assets/svgs/sub_relation.svg', | ||
1874 | 162 | 'width': 87, | ||
1875 | 163 | 'height': 47}); | ||
1876 | 164 | subRelationIndicator.append('text').append('tspan') | ||
1877 | 165 | .attr({'class': 'sub-rel-count', | ||
1878 | 166 | 'x': 64, | ||
1879 | 167 | 'y': 47 * 0.8}); | ||
1880 | 168 | |||
1881 | 169 | // Landscape badge | ||
1882 | 170 | if (landscape) { | ||
1883 | 171 | node.each(function(d) { | ||
1884 | 172 | var landscapeAsset; | ||
1885 | 173 | var securityBadge = landscape.getLandscapeBadge( | ||
1886 | 174 | d.model, 'security', 'round'); | ||
1887 | 175 | var rebootBadge = landscape.getLandscapeBadge( | ||
1888 | 176 | d.model, 'reboot', 'round'); | ||
1889 | 177 | |||
1890 | 178 | if (securityBadge && rebootBadge) { | ||
1891 | 179 | landscapeAsset = | ||
1892 | 180 | '/juju-ui/assets/images/landscape_restart_round.png'; | ||
1893 | 181 | } else if (securityBadge) { | ||
1894 | 182 | landscapeAsset = | ||
1895 | 183 | '/juju-ui/assets/images/landscape_security_round.png'; | ||
1896 | 184 | } else if (rebootBadge) { | ||
1897 | 185 | landscapeAsset = | ||
1898 | 186 | '/juju-ui/assets/images/landscape_restart_round.png'; | ||
1899 | 187 | } | ||
1900 | 188 | if (landscapeAsset === undefined) { | ||
1901 | 189 | // Remove any existing badge. | ||
1902 | 190 | d3.select(this).select('.landscape-badge').remove(); | ||
1903 | 191 | } else { | ||
1904 | 192 | var existing = Y.one(this).one('.landscape-badge'), | ||
1905 | 193 | curr_href, target; | ||
1906 | 194 | |||
1907 | 195 | if (!existing) { | ||
1908 | 196 | existing = d3.select(this).append('image'); | ||
1909 | 197 | existing.attr({ | ||
1910 | 198 | 'class': 'landscape-badge', | ||
1911 | 199 | 'width': 32, | ||
1912 | 200 | 'height': 32 | ||
1913 | 201 | }); | ||
1914 | 202 | } | ||
1915 | 203 | existing = d3.select(this).select('.landscape-badge'); | ||
1916 | 204 | existing.attr({ | ||
1917 | 205 | 'x': 13, | ||
1918 | 206 | 'y': 79 | ||
1919 | 207 | }); | ||
1920 | 208 | |||
1921 | 209 | // Only set 'xlink:href' if not already set to the new value, | ||
1922 | 210 | // thus avoiding redundant requests to the server. #1182135 | ||
1923 | 211 | curr_href = existing.attr('xlink:href'); | ||
1924 | 212 | if (curr_href !== landscapeAsset) { | ||
1925 | 213 | existing.attr({'xlink:href': landscapeAsset}); | ||
1926 | 214 | } | ||
1927 | 215 | } | ||
1928 | 216 | }); | ||
1929 | 217 | } | ||
1930 | 218 | // The following are sizes in pixels of the SVG assets used to | ||
1931 | 219 | // render a service, and are used to in calculating the vertical | ||
1932 | 220 | // positioning of text down along the service block. | ||
1933 | 221 | var service_height = 224, | ||
1934 | 222 | name_size = 22, | ||
1935 | 223 | charm_label_size = 16, | ||
1936 | 224 | name_padding = 26, | ||
1937 | 225 | charm_label_padding = 150; | ||
1938 | 226 | |||
1939 | 227 | node.select('.name') | ||
1940 | 228 | .attr({'style': function(d) { | ||
1941 | 229 | // Programmatically size the font. | ||
1942 | 230 | // Number derived from service assets: | ||
1943 | 231 | // font-size 22px when asset is 224px. | ||
1944 | 232 | return 'font-size:' + d.h * | ||
1945 | 233 | (name_size / service_height) + 'px'; | ||
1946 | 234 | }, | ||
1947 | 235 | 'x': function(d) { return d.w / 2; }, | ||
1948 | 236 | 'y': function(d) { | ||
1949 | 237 | // Number derived from service assets: | ||
1950 | 238 | // padding-top 26px when asset is 224px. | ||
1951 | 239 | return d.h * (name_padding / service_height) + d.h * | ||
1952 | 240 | (name_size / service_height) / 2; | ||
1953 | 241 | } | ||
1954 | 242 | }); | ||
1955 | 243 | node.select('.charm-label') | ||
1956 | 244 | .attr({'style': function(d) { | ||
1957 | 245 | // Programmatically size the font. | ||
1958 | 246 | // Number derived from service assets: | ||
1959 | 247 | // font-size 16px when asset is 224px. | ||
1960 | 248 | return 'font-size:' + d.h * | ||
1961 | 249 | (charm_label_size / service_height) + 'px'; | ||
1962 | 250 | }, | ||
1963 | 251 | 'x': function(d) { return d.w / 2;}, | ||
1964 | 252 | 'y': function(d) { | ||
1965 | 253 | // Number derived from service assets: | ||
1966 | 254 | // padding-top: 118px when asset is 224px. | ||
1967 | 255 | return d.h * (charm_label_padding / service_height) - d.h * | ||
1968 | 256 | (charm_label_size / service_height) / 2; | ||
1969 | 257 | } | ||
1970 | 258 | }); | ||
1971 | 259 | |||
1972 | 260 | // Show whether or not the service is exposed using an indicator. | ||
1973 | 261 | var exposed = node.filter(function(d) { | ||
1974 | 262 | return d.exposed; | ||
1975 | 263 | }); | ||
1976 | 264 | exposed.each(function(d) { | ||
1977 | 265 | var existing = Y.one(this).one('.exposed-indicator'); | ||
1978 | 266 | if (!existing) { | ||
1979 | 267 | existing = d3.select(this).append('image') | ||
1980 | 268 | .attr({'class': 'exposed-indicator on', | ||
1981 | 269 | 'xlink:href': '/juju-ui/assets/svgs/exposed.svg', | ||
1982 | 270 | 'width': 32, | ||
1983 | 271 | 'height': 32 | ||
1984 | 272 | }) | ||
1985 | 273 | .append('title') | ||
1986 | 274 | .text(function(d) { | ||
1987 | 275 | return d.exposed ? 'Exposed' : ''; | ||
1988 | 276 | }); | ||
1989 | 277 | } | ||
1990 | 278 | existing = d3.select(this).select('.exposed-indicator') | ||
1991 | 279 | .attr({ | ||
1992 | 280 | 'x': 145, | ||
1993 | 281 | 'y': 79 | ||
1994 | 282 | }); | ||
1995 | 283 | }); | ||
1996 | 284 | |||
1997 | 285 | // Remove exposed indicator from nodes that are no longer exposed. | ||
1998 | 286 | node.filter(function(d) { | ||
1999 | 287 | return !d.exposed && | ||
2000 | 288 | !d3.select(this) | ||
2001 | 289 | .select('.exposed-indicator').empty(); | ||
2002 | 290 | }).select('.exposed-indicator').remove(); | ||
2003 | 291 | |||
2004 | 292 | // Adds the relative health in the form of a percentage bar. | ||
2005 | 293 | node.each(function(d) { | ||
2006 | 294 | var status_graph = d3.select(this).select('.statusbar'); | ||
2007 | 295 | var status_bar = status_graph.property('status_bar'); | ||
2008 | 296 | if (status_bar && !d.subordinate) { | ||
2009 | 297 | status_bar.update(d.aggregated_status); | ||
2010 | 298 | } | ||
2011 | 299 | }); | ||
2012 | 300 | }; | ||
2013 | 301 | views.ServiceModuleCommon = ServiceModuleCommon; | ||
2014 | 302 | |||
2015 | 303 | /** | ||
2016 | 304 | Manage service rendering and events. | ||
2017 | 305 | |||
2018 | 306 | ## Emitted events: | ||
2019 | 307 | |||
2020 | 308 | - *clearState:* clear all possible states that the environment view can be | ||
2021 | 309 | in as it pertains to actions (building a relation, viewing | ||
2022 | 310 | a service menu, etc.) | ||
2023 | 311 | - *snapToService:* fired when mousing over a service, causing the pending | ||
2024 | 312 | relation dragline to snap to the service rather than | ||
2025 | 313 | following the mouse. | ||
2026 | 314 | - *snapOutOfService:* fired when mousing out of a service, causing the | ||
2027 | 315 | pending relation line to follow the mouse again. | ||
2028 | 316 | - *addRelationDrag:* | ||
2029 | 317 | - *addRelationDragStart:* | ||
2030 | 318 | - *addRelationDragEnd:* fired when creating a relation through the long- | ||
2031 | 319 | click process, when moving the cursor over the environment, and when | ||
2032 | 320 | dropping the endpoint on a valid service. | ||
2033 | 321 | - *cancelRelationBuild:* fired when dropping a pending relation line | ||
2034 | 322 | started through the long-click method somewhere other than a valid | ||
2035 | 323 | service. | ||
2036 | 324 | - *serviceMoved:* fired when a service block is dragged so that relation | ||
2037 | 325 | endpoints can follow it. | ||
2038 | 326 | - *navigateTo:* fired when clicking the "View Service" menu item or when | ||
2039 | 327 | double-clicking a service. | ||
2040 | 328 | |||
2041 | 329 | @class ServiceModule | ||
2042 | 63 | */ | 330 | */ |
2044 | 64 | var ServiceModule = Y.Base.create('ServiceModule', d3ns.Module, [], { | 331 | var ServiceModule = Y.Base.create('ServiceModule', d3ns.Module, [ |
2045 | 332 | ServiceModuleCommon], { | ||
2046 | 65 | events: { | 333 | events: { |
2047 | 66 | scene: { | 334 | scene: { |
2048 | 67 | '.service': { | 335 | '.service': { |
2049 | @@ -192,14 +460,15 @@ | |||
2050 | 192 | */ | 460 | */ |
2051 | 193 | _attachDragEvents: function() { | 461 | _attachDragEvents: function() { |
2052 | 194 | var container = this.get('container'), | 462 | var container = this.get('container'), |
2055 | 195 | ZP = '.zoom-plane', | 463 | ZP = '.zoom-plane', |
2056 | 196 | EC = 'i.sprite.empty_canvas'; | 464 | EC = 'i.sprite.empty_canvas'; |
2057 | 197 | 465 | ||
2058 | 198 | container.delegate('drop', this.canvasDropHandler, ZP, this); | 466 | container.delegate('drop', this.canvasDropHandler, ZP, this); |
2059 | 199 | container.delegate('dragenter', this._ignore, ZP, this); | 467 | container.delegate('dragenter', this._ignore, ZP, this); |
2060 | 200 | container.delegate('dragover', this._ignore, ZP, this); | 468 | container.delegate('dragover', this._ignore, ZP, this); |
2061 | 201 | 469 | ||
2063 | 202 | // allows the user to drop the charm on the 'drop here' help text in IE10. | 470 | // allows the user to drop the charm on the 'drop here' help text in |
2064 | 471 | // IE10. | ||
2065 | 203 | container.delegate('drop', this.canvasDropHandler, EC, this); | 472 | container.delegate('drop', this.canvasDropHandler, EC, this); |
2066 | 204 | container.delegate('dragenter', this._ignore, EC, this); | 473 | container.delegate('dragenter', this._ignore, EC, this); |
2067 | 205 | container.delegate('dragover', this._ignore, EC, this); | 474 | container.delegate('dragover', this._ignore, EC, this); |
2068 | @@ -225,7 +494,7 @@ | |||
2069 | 225 | */ | 494 | */ |
2070 | 226 | attachTouchstartEvents: function(data, node) { | 495 | attachTouchstartEvents: function(data, node) { |
2071 | 227 | var topo = this.get('component'), | 496 | var topo = this.get('component'), |
2073 | 228 | yuiNode = Y.Node(node); | 497 | yuiNode = Y.Node(node); |
2074 | 229 | 498 | ||
2075 | 230 | // Do not attach the event to the ghost nodes | 499 | // Do not attach the event to the ghost nodes |
2076 | 231 | if (!d3.select(node).classed('pending')) { | 500 | if (!d3.select(node).classed('pending')) { |
2077 | @@ -244,9 +513,9 @@ | |||
2078 | 244 | // To execute the serviceClick method under the same context as | 513 | // To execute the serviceClick method under the same context as |
2079 | 245 | // click we call it under the touch target context | 514 | // click we call it under the touch target context |
2080 | 246 | var node = e.currentTarget.getDOMNode(), | 515 | var node = e.currentTarget.getDOMNode(), |
2084 | 247 | box = d3.select(node).datum(); | 516 | box = d3.select(node).datum(); |
2085 | 248 | // If we're dragging with two fingers, ignore this as a tap and let drag | 517 | // If we're dragging with two fingers, ignore this as a tap and let |
2086 | 249 | // take over. | 518 | // drag take over. |
2087 | 250 | if (e.touches.length > 1) { | 519 | if (e.touches.length > 1) { |
2088 | 251 | box.tapped = false; | 520 | box.tapped = false; |
2089 | 252 | return; | 521 | return; |
2090 | @@ -284,9 +553,9 @@ | |||
2091 | 284 | return; | 553 | return; |
2092 | 285 | } | 554 | } |
2093 | 286 | } else { | 555 | } else { |
2097 | 287 | // Touch events will also fire a click event about 300ms later. If this | 556 | // Touch events will also fire a click event about 300ms later. If |
2098 | 288 | // event isn't ignored, the service menu will disappear 300ms after it | 557 | // this event isn't ignored, the service menu will disappear 300ms |
2099 | 289 | // appears, so set a flag to ignore that event. | 558 | // after it appears, so set a flag to ignore that event. |
2100 | 290 | box.ignoreNextClick = true; | 559 | box.ignoreNextClick = true; |
2101 | 291 | } | 560 | } |
2102 | 292 | 561 | ||
2103 | @@ -298,8 +567,8 @@ | |||
2104 | 298 | // If the service box is pending, ensure that the charm panel is | 567 | // If the service box is pending, ensure that the charm panel is |
2105 | 299 | // visible, but don't do anything else. | 568 | // visible, but don't do anything else. |
2106 | 300 | if (box.pending && !window.flags.serviceInspector) { | 569 | if (box.pending && !window.flags.serviceInspector) { |
2109 | 301 | // Prevent the clickoutside event from firing and immediately closing | 570 | // Prevent the clickoutside event from firing and immediately |
2110 | 302 | // the panel. | 571 | // closing the panel. |
2111 | 303 | d3.event.halt(); | 572 | d3.event.halt(); |
2112 | 304 | // Ensure service menus are closed. | 573 | // Ensure service menus are closed. |
2113 | 305 | topo.fire('clearState'); | 574 | topo.fire('clearState'); |
2114 | @@ -327,10 +596,11 @@ | |||
2115 | 327 | return; | 596 | return; |
2116 | 328 | } | 597 | } |
2117 | 329 | // Just show the service on double-click. | 598 | // Just show the service on double-click. |
2122 | 330 | var topo = self.get('component'), | 599 | var topo = self.get('component'); |
2123 | 331 | service = box.model; | 600 | var service = box.model; |
2124 | 332 | // The browser sends a click event right before the dblclick one, and it | 601 | // The browser sends a click event right before the dblclick one, and |
2125 | 333 | // opens the service menu: close it before moving to the service details. | 602 | // it opens the service menu: close it before moving to the service |
2126 | 603 | // details. | ||
2127 | 334 | self.hideServiceMenu(); | 604 | self.hideServiceMenu(); |
2128 | 335 | self.show_service(service); | 605 | self.show_service(service); |
2129 | 336 | }, | 606 | }, |
2130 | @@ -440,7 +710,7 @@ | |||
2131 | 440 | reader.onload = function(e) { | 710 | reader.onload = function(e) { |
2132 | 441 | // Import each into the environment | 711 | // Import each into the environment |
2133 | 442 | db.importDeployer(jsyaml.safeLoad(e.target.result), | 712 | db.importDeployer(jsyaml.safeLoad(e.target.result), |
2135 | 443 | store, {useGhost: false}) | 713 | store, {useGhost: false}) |
2136 | 444 | .then(function() { | 714 | .then(function() { |
2137 | 445 | notifications.add({ | 715 | notifications.add({ |
2138 | 446 | title: 'Imported Environment', | 716 | title: 'Imported Environment', |
2139 | @@ -468,14 +738,15 @@ | |||
2140 | 468 | // required to position the service in the proper y position. | 738 | // required to position the service in the proper y position. |
2141 | 469 | var dropXY = [evt.clientX, (evt.clientY - 71)]; | 739 | var dropXY = [evt.clientX, (evt.clientY - 71)]; |
2142 | 470 | 740 | ||
2144 | 471 | // Take the x,y offset (translation) of the topology view into account. | 741 | // Take the x,y offset (translation) of the topology view into |
2145 | 742 | // account. | ||
2146 | 472 | Y.Array.each(dropXY, function(_, index) { | 743 | Y.Array.each(dropXY, function(_, index) { |
2147 | 473 | ghostAttributes.coordinates[index] = | 744 | ghostAttributes.coordinates[index] = |
2148 | 474 | (dropXY[index] - translation[index]) / scale; | 745 | (dropXY[index] - translation[index]) / scale; |
2149 | 475 | }); | 746 | }); |
2150 | 476 | if (dragData.dataType === 'charm-token-drag-and-drop') { | 747 | if (dragData.dataType === 'charm-token-drag-and-drop') { |
2153 | 477 | // The charm data was JSON encoded because the dataTransfer mechanism | 748 | // The charm data was JSON encoded because the dataTransfer |
2154 | 478 | // only allows for string values. | 749 | // mechanism only allows for string values. |
2155 | 479 | var charmData = Y.JSON.parse(dragData.charmData); | 750 | var charmData = Y.JSON.parse(dragData.charmData); |
2156 | 480 | // Add the icon url to the ghost attributes for the ghost icon | 751 | // Add the icon url to the ghost attributes for the ghost icon |
2157 | 481 | ghostAttributes.icon = dragData.iconSrc; | 752 | ghostAttributes.icon = dragData.iconSrc; |
2158 | @@ -494,7 +765,7 @@ | |||
2159 | 494 | */ | 765 | */ |
2160 | 495 | clearStateHandler: function() { | 766 | clearStateHandler: function() { |
2161 | 496 | var container = this.get('container'), | 767 | var container = this.get('container'), |
2163 | 497 | topo = this.get('component'); | 768 | topo = this.get('component'); |
2164 | 498 | container.all('.environment-menu.active').removeClass('active'); | 769 | container.all('.environment-menu.active').removeClass('active'); |
2165 | 499 | this.hideServiceMenu(); | 770 | this.hideServiceMenu(); |
2166 | 500 | }, | 771 | }, |
2167 | @@ -568,7 +839,7 @@ | |||
2168 | 568 | context.longClickTimer = Y.later(750, this, function(d, e) { | 839 | context.longClickTimer = Y.later(750, this, function(d, e) { |
2169 | 569 | // Provide some leeway for accidental dragging. | 840 | // Provide some leeway for accidental dragging. |
2170 | 570 | if ((Math.abs(box.x - box.oldX) + Math.abs(box.y - box.oldY)) / | 841 | if ((Math.abs(box.x - box.oldX) + Math.abs(box.y - box.oldY)) / |
2172 | 571 | 2 > 5) { | 842 | 2 > 5) { |
2173 | 572 | return; | 843 | return; |
2174 | 573 | } | 844 | } |
2175 | 574 | 845 | ||
2176 | @@ -598,29 +869,6 @@ | |||
2177 | 598 | context.longClickTimer.cancel(); | 869 | context.longClickTimer.cancel(); |
2178 | 599 | } | 870 | } |
2179 | 600 | }, | 871 | }, |
2180 | 601 | /* | ||
2181 | 602 | * Sync view models with current db.models. | ||
2182 | 603 | * | ||
2183 | 604 | * @method updateData | ||
2184 | 605 | */ | ||
2185 | 606 | updateData: function() { | ||
2186 | 607 | //model data | ||
2187 | 608 | var topo = this.get('component'); | ||
2188 | 609 | var vis = topo.vis; | ||
2189 | 610 | var db = topo.get('db'); | ||
2190 | 611 | var store = topo.get('store'); | ||
2191 | 612 | |||
2192 | 613 | var visibleServices = db.services.visible(); | ||
2193 | 614 | views.toBoundingBoxes(this, visibleServices, topo.service_boxes, store); | ||
2194 | 615 | // Break a reference cycle that results in uncollectable objects leaking. | ||
2195 | 616 | visibleServices.reset(); | ||
2196 | 617 | |||
2197 | 618 | // Nodes are mapped by modelId tuples. | ||
2198 | 619 | this.node = vis.selectAll('.service') | ||
2199 | 620 | .data(Y.Object.values(topo.service_boxes), | ||
2200 | 621 | function(d) {return d.modelId;}); | ||
2201 | 622 | }, | ||
2202 | 623 | |||
2203 | 624 | /** | 872 | /** |
2204 | 625 | * Handle drag events for a service. | 873 | * Handle drag events for a service. |
2205 | 626 | * | 874 | * |
2206 | @@ -650,17 +898,17 @@ | |||
2207 | 650 | } | 898 | } |
2208 | 651 | else { | 899 | else { |
2209 | 652 | 900 | ||
2213 | 653 | // If the service hasn't been dragged (in the case of long-click to add | 901 | // If the service hasn't been dragged (in the case of long-click to |
2214 | 654 | // relation, or a double-fired event) or the old and new coordinates | 902 | // add relation, or a double-fired event) or the old and new |
2215 | 655 | // are the same, exit. | 903 | // coordinates are the same, exit. |
2216 | 656 | if (!box.inDrag || | 904 | if (!box.inDrag || |
2219 | 657 | (box.oldX === box.x && | 905 | (box.oldX === box.x && |
2220 | 658 | box.oldY === box.y)) { | 906 | box.oldY === box.y)) { |
2221 | 659 | return; | 907 | return; |
2222 | 660 | } | 908 | } |
2223 | 661 | 909 | ||
2226 | 662 | // If the service is still pending, persist x/y coordinates in order | 910 | // If the service is still pending, persist x/y coordinates in |
2227 | 663 | // to set them as annotations when the service is created. | 911 | // order to set them as annotations when the service is created. |
2228 | 664 | if (box.pending) { | 912 | if (box.pending) { |
2229 | 665 | box.model.set('hasBeenPositioned', true); | 913 | box.model.set('hasBeenPositioned', true); |
2230 | 666 | box.model.set('x', box.x); | 914 | box.model.set('x', box.x); |
2231 | @@ -681,21 +929,21 @@ | |||
2232 | 681 | }, | 929 | }, |
2233 | 682 | 930 | ||
2234 | 683 | /** | 931 | /** |
2250 | 684 | * Specialized drag event handler | 932 | Specialized drag event handler |
2251 | 685 | * when called as an event handler it | 933 | when called as an event handler it |
2252 | 686 | * Allows optional extra param, pos | 934 | Allows optional extra param, pos |
2253 | 687 | * which when used overrides the mouse | 935 | which when used overrides the mouse |
2254 | 688 | * handling. This method can then be | 936 | handling. This method can then be |
2255 | 689 | * though of as 'drag to position'. | 937 | though of as 'drag to position'. |
2256 | 690 | * | 938 | |
2257 | 691 | * @method drag | 939 | @method drag |
2258 | 692 | * @param {Box} d viewModel BoundingBox. | 940 | @param {Box} d viewModel BoundingBox. |
2259 | 693 | * @param {ServiceModule} self ServiceModule. | 941 | @param {ServiceModule} self ServiceModule. |
2260 | 694 | * @param {Object} pos (optional) containing x/y numbers. | 942 | @param {Object} pos (optional) containing x/y numbers. |
2261 | 695 | * @param {Boolean} includeTransition (optional) Use transition to drag. | 943 | @param {Boolean} includeTransition (optional) Use transition to drag. |
2262 | 696 | * | 944 | |
2263 | 697 | * [At the time of this writing useTransition works in practice but | 945 | [At the time of this writing useTransition works in practice but |
2264 | 698 | * introduces a timing issue in the tests.] | 946 | introduces a timing issue in the tests.] |
2265 | 699 | */ | 947 | */ |
2266 | 700 | drag: function(box, self, pos, includeTransition) { | 948 | drag: function(box, self, pos, includeTransition) { |
2267 | 701 | if (box.tapped) { | 949 | if (box.tapped) { |
2268 | @@ -711,9 +959,9 @@ | |||
2269 | 711 | if (self.longClickTimer) { | 959 | if (self.longClickTimer) { |
2270 | 712 | self.longClickTimer.cancel(); | 960 | self.longClickTimer.cancel(); |
2271 | 713 | } | 961 | } |
2275 | 714 | // Translate the service (and, potentially, menu). | 962 | // Translate the service (and, potentially, menu). If a position was |
2276 | 715 | // If a position was provided, update the box's coordinates and the | 963 | // provided, update the box's coordinates and the selection's bound |
2277 | 716 | // selection's bound data. | 964 | // data. |
2278 | 717 | if (pos) { | 965 | if (pos) { |
2279 | 718 | box.x = pos.x; | 966 | box.x = pos.x; |
2280 | 719 | box.y = pos.y; | 967 | box.y = pos.y; |
2281 | @@ -726,8 +974,8 @@ | |||
2282 | 726 | 974 | ||
2283 | 727 | if (includeTransition) { | 975 | if (includeTransition) { |
2284 | 728 | selection = selection.transition() | 976 | selection = selection.transition() |
2287 | 729 | .duration(500) | 977 | .duration(500) |
2288 | 730 | .ease('elastic'); | 978 | .ease('elastic'); |
2289 | 731 | } | 979 | } |
2290 | 732 | 980 | ||
2291 | 733 | selection.attr('transform', function(d, i) { | 981 | selection.attr('transform', function(d, i) { |
2292 | @@ -739,7 +987,7 @@ | |||
2293 | 739 | 987 | ||
2294 | 740 | // Remove any active menus. | 988 | // Remove any active menus. |
2295 | 741 | self.get('container').all('.environment-menu.active') | 989 | self.get('container').all('.environment-menu.active') |
2297 | 742 | .removeClass('active'); | 990 | .removeClass('active'); |
2298 | 743 | if (box.inDrag === views.DRAG_START) { | 991 | if (box.inDrag === views.DRAG_START) { |
2299 | 744 | self.hideServiceMenu(); | 992 | self.hideServiceMenu(); |
2300 | 745 | box.inDrag = views.DRAG_ACTIVE; | 993 | box.inDrag = views.DRAG_ACTIVE; |
2301 | @@ -749,12 +997,12 @@ | |||
2302 | 749 | topo.fire('serviceMoved', { service: box }); | 997 | topo.fire('serviceMoved', { service: box }); |
2303 | 750 | }, | 998 | }, |
2304 | 751 | 999 | ||
2311 | 752 | /* | 1000 | /** |
2312 | 753 | * Attempt to reuse as much of the existing graph and view models | 1001 | Attempt to reuse as much of the existing graph and view models as |
2313 | 754 | * as possible to re-render the graph. | 1002 | possible to re-render the graph. |
2314 | 755 | * | 1003 | |
2315 | 756 | * @method update | 1004 | @method update |
2316 | 757 | */ | 1005 | */ |
2317 | 758 | update: function() { | 1006 | update: function() { |
2318 | 759 | var self = this, | 1007 | var self = this, |
2319 | 760 | topo = this.get('component'), | 1008 | topo = this.get('component'), |
2320 | @@ -776,18 +1024,18 @@ | |||
2321 | 776 | 1024 | ||
2322 | 777 | if (!this.tree) { | 1025 | if (!this.tree) { |
2323 | 778 | this.tree = d3.layout.unscaledPack() | 1026 | this.tree = d3.layout.unscaledPack() |
2329 | 779 | .size([width, height]) | 1027 | .size([width, height]) |
2330 | 780 | .value(function(d) { | 1028 | .value(function(d) { |
2331 | 781 | return Math.max(d.unit_count, 1); | 1029 | return Math.max(d.unit_count, 1); |
2332 | 782 | }) | 1030 | }) |
2333 | 783 | .padding(300); | 1031 | .padding(300); |
2334 | 784 | } | 1032 | } |
2335 | 785 | 1033 | ||
2336 | 786 | if (!this.dragBehavior) { | 1034 | if (!this.dragBehavior) { |
2337 | 787 | this.dragBehavior = d3.behavior.drag() | 1035 | this.dragBehavior = d3.behavior.drag() |
2341 | 788 | .on('dragstart', function(d) { self.dragstart.call(this, d, self);}) | 1036 | .on('dragstart', function(d) { self.dragstart.call(this, d, self);}) |
2342 | 789 | .on('drag', function(d) { self.drag.call(this, d, self);}) | 1037 | .on('drag', function(d) { self.drag.call(this, d, self);}) |
2343 | 790 | .on('dragend', function(d) { self.dragend.call(this, d, self);}); | 1038 | .on('dragend', function(d) { self.dragend.call(this, d, self);}); |
2344 | 791 | } | 1039 | } |
2345 | 792 | 1040 | ||
2346 | 793 | //Process any changed data. | 1041 | //Process any changed data. |
2347 | @@ -798,16 +1046,15 @@ | |||
2348 | 798 | var node = this.node; | 1046 | var node = this.node; |
2349 | 799 | 1047 | ||
2350 | 800 | // Rerun the pack layout. | 1048 | // Rerun the pack layout. |
2356 | 801 | // Pack doesn't honor existing positions and will | 1049 | // Pack doesn't honor existing positions and will re-layout the |
2357 | 802 | // re-layout the entire graph. As a short term work | 1050 | // entire graph. As a short term work around we layout only new |
2358 | 803 | // around we layout only new nodes. This has the side | 1051 | // nodes. This has the side effect that service blocks can overlap |
2359 | 804 | // effect that service blocks can overlap and will | 1052 | // and will be fixed later. |
2355 | 805 | // be fixed later. | ||
2360 | 806 | var vertices; | 1053 | var vertices; |
2361 | 807 | var new_services = Y.Object.values(topo.service_boxes) | 1054 | var new_services = Y.Object.values(topo.service_boxes) |
2365 | 808 | .filter(function(boundingBox) { | 1055 | .filter(function(boundingBox) { |
2366 | 809 | return !Y.Lang.isNumber(boundingBox.x); | 1056 | return !Y.Lang.isNumber(boundingBox.x); |
2367 | 810 | }); | 1057 | }); |
2368 | 811 | if (new_services.length > 0) { | 1058 | if (new_services.length > 0) { |
2369 | 812 | // If the there is only one new service and it's pending (as in, it was | 1059 | // If the there is only one new service and it's pending (as in, it was |
2370 | 813 | // added via the charm panel as a ghost), position it intelligently and | 1060 | // added via the charm panel as a ghost), position it intelligently and |
2371 | @@ -816,16 +1063,19 @@ | |||
2372 | 816 | // in the case of opening an unannotated environment for the first | 1063 | // in the case of opening an unannotated environment for the first |
2373 | 817 | // time). | 1064 | // time). |
2374 | 818 | var pendingServicePlaced = false; | 1065 | var pendingServicePlaced = false; |
2376 | 819 | if (new_services.length === 1 && new_services[0].model.get('pending')) { | 1066 | if (new_services.length === 1 && |
2377 | 1067 | new_services[0].model.get('pending')) { | ||
2378 | 820 | pendingServicePlaced = true; | 1068 | pendingServicePlaced = true; |
2379 | 821 | // Get a coordinate outside the cluster of existing services. | 1069 | // Get a coordinate outside the cluster of existing services. |
2380 | 822 | var coords = topo.servicePointOutside(); | 1070 | var coords = topo.servicePointOutside(); |
2382 | 823 | // Set the coordinates on both the box model and the service model. | 1071 | // Set the coordinates on both the box model and the service |
2383 | 1072 | // model. | ||
2384 | 824 | new_services[0].x = coords[0]; | 1073 | new_services[0].x = coords[0]; |
2385 | 825 | new_services[0].y = coords[1]; | 1074 | new_services[0].y = coords[1]; |
2386 | 826 | new_services[0].model.set('x', coords[0]); | 1075 | new_services[0].model.set('x', coords[0]); |
2387 | 827 | new_services[0].model.set('y', coords[1]); | 1076 | new_services[0].model.set('y', coords[1]); |
2389 | 828 | // This ensures that the x/y coordinates will be saved as annotations. | 1077 | // This ensures that the x/y coordinates will be saved as |
2390 | 1078 | // annotations. | ||
2391 | 829 | new_services[0].model.set('hasBeenPositioned', true); | 1079 | new_services[0].model.set('hasBeenPositioned', true); |
2392 | 830 | // Set the centroid to the new service's position | 1080 | // Set the centroid to the new service's position |
2393 | 831 | topo.centroid = coords; | 1081 | topo.centroid = coords; |
2394 | @@ -833,8 +1083,8 @@ | |||
2395 | 833 | } else { | 1083 | } else { |
2396 | 834 | this.tree.nodes({children: new_services}); | 1084 | this.tree.nodes({children: new_services}); |
2397 | 835 | } | 1085 | } |
2400 | 836 | // Update annotations settings position on backend | 1086 | // Update annotations settings position on backend (but only do |
2401 | 837 | // (but only do this if there is no existing annotations). | 1087 | // this if there is no existing annotations). |
2402 | 838 | if (!pendingServicePlaced) { | 1088 | if (!pendingServicePlaced) { |
2403 | 839 | vertices = []; | 1089 | vertices = []; |
2404 | 840 | } | 1090 | } |
2405 | @@ -858,7 +1108,8 @@ | |||
2406 | 858 | }); | 1108 | }); |
2407 | 859 | } | 1109 | } |
2408 | 860 | if (!topo.centroid || vertices) { | 1110 | if (!topo.centroid || vertices) { |
2410 | 861 | // Find the centroid of our hull of services and inform the topology. | 1111 | // Find the centroid of our hull of services and inform the |
2411 | 1112 | // topology. | ||
2412 | 862 | if (!vertices) { | 1113 | if (!vertices) { |
2413 | 863 | vertices = topoUtils.serviceBoxesToVertices(topo.service_boxes); | 1114 | vertices = topoUtils.serviceBoxesToVertices(topo.service_boxes); |
2414 | 864 | } | 1115 | } |
2415 | @@ -866,9 +1117,9 @@ | |||
2416 | 866 | } | 1117 | } |
2417 | 867 | // enter | 1118 | // enter |
2418 | 868 | node | 1119 | node |
2422 | 869 | .enter().append('g') | 1120 | .enter().append('g') |
2423 | 870 | .attr({ | 1121 | .attr({ |
2424 | 871 | 'pointer-events': 'all', // IE doesn't drag properly without this. | 1122 | 'pointer-events': 'all', // IE needs this. |
2425 | 872 | 'class': function(d) { | 1123 | 'class': function(d) { |
2426 | 873 | return (d.subordinate ? 'subordinate ' : '') + | 1124 | return (d.subordinate ? 'subordinate ' : '') + |
2427 | 874 | (d.pending ? 'pending ' : '') + 'service'; | 1125 | (d.pending ? 'pending ' : '') + 'service'; |
2428 | @@ -882,18 +1133,18 @@ | |||
2429 | 882 | 1133 | ||
2430 | 883 | // Remove old nodes. | 1134 | // Remove old nodes. |
2431 | 884 | node.exit() | 1135 | node.exit() |
2433 | 885 | .each(function(d) { | 1136 | .each(function(d) { |
2434 | 886 | delete topo.service_boxes[d.id]; | 1137 | delete topo.service_boxes[d.id]; |
2435 | 887 | }) | 1138 | }) |
2437 | 888 | .remove(); | 1139 | .remove(); |
2438 | 889 | }, | 1140 | }, |
2439 | 890 | 1141 | ||
2440 | 891 | /** | 1142 | /** |
2442 | 892 | Pans the environment view to the center all the services on the canvas. | 1143 | Pans the environment view to the center all the services on the canvas. |
2443 | 893 | 1144 | ||
2447 | 894 | @method panToCenter | 1145 | @method panToCenter |
2448 | 895 | @param {object} evt The event fired. | 1146 | @param {object} evt The event fired. |
2449 | 896 | @return {undefined} Side effects only. | 1147 | @return {undefined} Side effects only. |
2450 | 897 | */ | 1148 | */ |
2451 | 898 | panToCenter: function(evt) { | 1149 | panToCenter: function(evt) { |
2452 | 899 | var topo = this.get('component'); | 1150 | var topo = this.get('component'); |
2453 | @@ -902,15 +1153,15 @@ | |||
2454 | 902 | }, | 1153 | }, |
2455 | 903 | 1154 | ||
2456 | 904 | /** | 1155 | /** |
2458 | 905 | Given a set of vertices, find the centroid and pan to that location. | 1156 | Given a set of vertices, find the centroid and pan to that location. |
2459 | 906 | 1157 | ||
2463 | 907 | @method findAndSetCentroid | 1158 | @method findAndSetCentroid |
2464 | 908 | @param {array} vertices A list of vertices in the form [x, y]. | 1159 | @param {array} vertices A list of vertices in the form [x, y]. |
2465 | 909 | @return {undefined} Side effects only. | 1160 | @return {undefined} Side effects only. |
2466 | 910 | */ | 1161 | */ |
2467 | 911 | findAndSetCentroid: function(vertices) { | 1162 | findAndSetCentroid: function(vertices) { |
2468 | 912 | var topo = this.get('component'), | 1163 | var topo = this.get('component'), |
2470 | 913 | centroid = topoUtils.centroid(vertices); | 1164 | centroid = topoUtils.centroid(vertices); |
2471 | 914 | // The centroid is set on the topology object due to the fact that it is | 1165 | // The centroid is set on the topology object due to the fact that it is |
2472 | 915 | // used as a sigil to tell whether or not to pan to the point after the | 1166 | // used as a sigil to tell whether or not to pan to the point after the |
2473 | 916 | // first delta. | 1167 | // first delta. |
2474 | @@ -950,13 +1201,13 @@ | |||
2475 | 950 | node.append('image') | 1201 | node.append('image') |
2476 | 951 | .classed('service-icon', true) | 1202 | .classed('service-icon', true) |
2477 | 952 | .attr({ | 1203 | .attr({ |
2485 | 953 | 'xlink:href': function(d) { | 1204 | 'xlink:href': function(d) { |
2486 | 954 | return d.icon; | 1205 | return d.icon; |
2487 | 955 | }, | 1206 | }, |
2488 | 956 | width: 96, | 1207 | width: 96, |
2489 | 957 | height: 96, | 1208 | height: 96, |
2490 | 958 | transform: 'translate(47, 50)' | 1209 | transform: 'translate(47, 50)' |
2491 | 959 | }); | 1210 | }); |
2492 | 960 | node.append('text').append('tspan') | 1211 | node.append('text').append('tspan') |
2493 | 961 | .attr('class', 'name') | 1212 | .attr('class', 'name') |
2494 | 962 | .text(function(d) {return d.displayName; }); | 1213 | .text(function(d) {return d.displayName; }); |
2495 | @@ -985,249 +1236,6 @@ | |||
2496 | 985 | }); | 1236 | }); |
2497 | 986 | }, | 1237 | }, |
2498 | 987 | 1238 | ||
2499 | 988 | /** | ||
2500 | 989 | * Fill the empty structures within a service node such that they | ||
2501 | 990 | * match the db. | ||
2502 | 991 | * | ||
2503 | 992 | * @param {object} node the collection of nodes to update. | ||
2504 | 993 | * @return {null} side effects only. | ||
2505 | 994 | * @method updateServiceNodes | ||
2506 | 995 | */ | ||
2507 | 996 | updateServiceNodes: function(node) { | ||
2508 | 997 | if (node.empty()) { | ||
2509 | 998 | return; | ||
2510 | 999 | } | ||
2511 | 1000 | var self = this, | ||
2512 | 1001 | topo = this.get('component'), | ||
2513 | 1002 | landscape = topo.get('landscape'), | ||
2514 | 1003 | service_scale = this.service_scale, | ||
2515 | 1004 | service_scale_width = this.service_scale_width, | ||
2516 | 1005 | service_scale_height = this.service_scale_height; | ||
2517 | 1006 | |||
2518 | 1007 | // Apply Position Annotations | ||
2519 | 1008 | // This is done after the services_boxes | ||
2520 | 1009 | // binding as the event handler will | ||
2521 | 1010 | // use that index. | ||
2522 | 1011 | node.each(function(d) { | ||
2523 | 1012 | var service = d.model, | ||
2524 | 1013 | annotations = service.get('annotations'), | ||
2525 | 1014 | x, y; | ||
2526 | 1015 | |||
2527 | 1016 | // If there are no annotations or the service is being dragged | ||
2528 | 1017 | if (!annotations || service.inDrag === views.DRAG_ACTIVE) { | ||
2529 | 1018 | return; | ||
2530 | 1019 | } | ||
2531 | 1020 | |||
2532 | 1021 | // If there are x/y annotations on the service model and they are | ||
2533 | 1022 | // different from the node's current x/y coordinates, update the | ||
2534 | 1023 | // node, as the annotations may have been set in another session. | ||
2535 | 1024 | x = annotations['gui-x']; | ||
2536 | 1025 | y = annotations['gui-y']; | ||
2537 | 1026 | if (!d || | ||
2538 | 1027 | (x !== undefined && x !== d.x) || | ||
2539 | 1028 | (y !== undefined && y !== d.y)) { | ||
2540 | 1029 | // Delete gui-x and gui-y from annotations as we use the values. | ||
2541 | 1030 | // This is to prevent deltas coming in on a service while it is | ||
2542 | 1031 | // being dragged from resetting its position during the drag. | ||
2543 | 1032 | |||
2544 | 1033 | delete annotations['gui-x']; | ||
2545 | 1034 | delete annotations['gui-y']; | ||
2546 | 1035 | // Only update position if we're not already in a drag state (the | ||
2547 | 1036 | // current drag supercedes any previous annotations). | ||
2548 | 1037 | if (!d.inDrag) { | ||
2549 | 1038 | self.drag.call(this, d, self, {x: x, y: y}, | ||
2550 | 1039 | self.get('useTransitions')); | ||
2551 | 1040 | } | ||
2552 | 1041 | }}); | ||
2553 | 1042 | |||
2554 | 1043 | // Mark subordinates as such. This is needed for when a new service | ||
2555 | 1044 | // is created. | ||
2556 | 1045 | node.filter(function(d) { | ||
2557 | 1046 | return d.subordinate; | ||
2558 | 1047 | }) | ||
2559 | 1048 | .classed('subordinate', true); | ||
2560 | 1049 | |||
2561 | 1050 | // Size the node for drawing. | ||
2562 | 1051 | node.attr({ | ||
2563 | 1052 | 'width': function(box) { box.w = 190; return box.w;}, | ||
2564 | 1053 | 'height': function(box) { box.h = 190; return box.h;} | ||
2565 | 1054 | }); | ||
2566 | 1055 | |||
2567 | 1056 | node.select('.service-block-image').each(function(d) { | ||
2568 | 1057 | var curr_node = d3.select(this); | ||
2569 | 1058 | var curr_href = curr_node.attr('xlink:href'); | ||
2570 | 1059 | var new_href = d.subordinate ? | ||
2571 | 1060 | '/juju-ui/assets/svgs/sub_module.svg' : | ||
2572 | 1061 | '/juju-ui/assets/svgs/service_module.svg'; | ||
2573 | 1062 | |||
2574 | 1063 | // Only set 'xlink:href' if not already set to the new value, | ||
2575 | 1064 | // thus avoiding redundant requests to the server. #1182135 | ||
2576 | 1065 | if (curr_href !== new_href) { | ||
2577 | 1066 | curr_node.attr({'xlink:href': new_href}); | ||
2578 | 1067 | } | ||
2579 | 1068 | curr_node.attr({ | ||
2580 | 1069 | 'width': d.w, | ||
2581 | 1070 | 'height': d.h | ||
2582 | 1071 | }); | ||
2583 | 1072 | }); | ||
2584 | 1073 | |||
2585 | 1074 | // Draw a subordinate relation indicator. | ||
2586 | 1075 | var subRelationIndicator = node.filter(function(d) { | ||
2587 | 1076 | return d.subordinate && | ||
2588 | 1077 | d3.select(this) | ||
2589 | 1078 | .select('.sub-rel-block').empty(); | ||
2590 | 1079 | }) | ||
2591 | 1080 | .append('g') | ||
2592 | 1081 | .attr('class', 'sub-rel-block') | ||
2593 | 1082 | .attr('transform', function(d) { | ||
2594 | 1083 | // Position the block so that the relation indicator will | ||
2595 | 1084 | // appear at the right connector. | ||
2596 | 1085 | return 'translate(' + [d.w, d.h / 2 - 26] + ')'; | ||
2597 | 1086 | }); | ||
2598 | 1087 | |||
2599 | 1088 | subRelationIndicator.append('image') | ||
2600 | 1089 | .attr({'xlink:href': '/juju-ui/assets/svgs/sub_relation.svg', | ||
2601 | 1090 | 'width': 87, | ||
2602 | 1091 | 'height': 47}); | ||
2603 | 1092 | subRelationIndicator.append('text').append('tspan') | ||
2604 | 1093 | .attr({'class': 'sub-rel-count', | ||
2605 | 1094 | 'x': 64, | ||
2606 | 1095 | 'y': 47 * 0.8}); | ||
2607 | 1096 | |||
2608 | 1097 | // Landscape badge | ||
2609 | 1098 | if (landscape) { | ||
2610 | 1099 | node.each(function(d) { | ||
2611 | 1100 | var landscapeAsset; | ||
2612 | 1101 | var securityBadge = landscape.getLandscapeBadge( | ||
2613 | 1102 | d.model, 'security', 'round'); | ||
2614 | 1103 | var rebootBadge = landscape.getLandscapeBadge( | ||
2615 | 1104 | d.model, 'reboot', 'round'); | ||
2616 | 1105 | |||
2617 | 1106 | if (securityBadge && rebootBadge) { | ||
2618 | 1107 | landscapeAsset = | ||
2619 | 1108 | '/juju-ui/assets/images/landscape_restart_round.png'; | ||
2620 | 1109 | } else if (securityBadge) { | ||
2621 | 1110 | landscapeAsset = | ||
2622 | 1111 | '/juju-ui/assets/images/landscape_security_round.png'; | ||
2623 | 1112 | } else if (rebootBadge) { | ||
2624 | 1113 | landscapeAsset = | ||
2625 | 1114 | '/juju-ui/assets/images/landscape_restart_round.png'; | ||
2626 | 1115 | } | ||
2627 | 1116 | if (landscapeAsset === undefined) { | ||
2628 | 1117 | // Remove any existing badge. | ||
2629 | 1118 | d3.select(this).select('.landscape-badge').remove(); | ||
2630 | 1119 | } else { | ||
2631 | 1120 | var existing = Y.one(this).one('.landscape-badge'), | ||
2632 | 1121 | curr_href, target; | ||
2633 | 1122 | |||
2634 | 1123 | if (!existing) { | ||
2635 | 1124 | existing = d3.select(this).append('image'); | ||
2636 | 1125 | existing.attr({ | ||
2637 | 1126 | 'class': 'landscape-badge', | ||
2638 | 1127 | 'width': 32, | ||
2639 | 1128 | 'height': 32 | ||
2640 | 1129 | }); | ||
2641 | 1130 | } | ||
2642 | 1131 | existing = d3.select(this).select('.landscape-badge'); | ||
2643 | 1132 | existing.attr({ | ||
2644 | 1133 | 'x': 13, | ||
2645 | 1134 | 'y': 79 | ||
2646 | 1135 | }); | ||
2647 | 1136 | |||
2648 | 1137 | // Only set 'xlink:href' if not already set to the new value, | ||
2649 | 1138 | // thus avoiding redundant requests to the server. #1182135 | ||
2650 | 1139 | curr_href = existing.attr('xlink:href'); | ||
2651 | 1140 | if (curr_href !== landscapeAsset) { | ||
2652 | 1141 | existing.attr({'xlink:href': landscapeAsset}); | ||
2653 | 1142 | } | ||
2654 | 1143 | } | ||
2655 | 1144 | }); | ||
2656 | 1145 | } | ||
2657 | 1146 | // The following are sizes in pixels of the SVG assets used to | ||
2658 | 1147 | // render a service, and are used to in calculating the vertical | ||
2659 | 1148 | // positioning of text down along the service block. | ||
2660 | 1149 | var service_height = 224, | ||
2661 | 1150 | name_size = 22, | ||
2662 | 1151 | charm_label_size = 16, | ||
2663 | 1152 | name_padding = 26, | ||
2664 | 1153 | charm_label_padding = 150; | ||
2665 | 1154 | |||
2666 | 1155 | node.select('.name') | ||
2667 | 1156 | .attr({'style': function(d) { | ||
2668 | 1157 | // Programmatically size the font. | ||
2669 | 1158 | // Number derived from service assets: | ||
2670 | 1159 | // font-size 22px when asset is 224px. | ||
2671 | 1160 | return 'font-size:' + d.h * | ||
2672 | 1161 | (name_size / service_height) + 'px'; | ||
2673 | 1162 | }, | ||
2674 | 1163 | 'x': function(d) { return d.w / 2; }, | ||
2675 | 1164 | 'y': function(d) { | ||
2676 | 1165 | // Number derived from service assets: | ||
2677 | 1166 | // padding-top 26px when asset is 224px. | ||
2678 | 1167 | return d.h * (name_padding / service_height) + d.h * | ||
2679 | 1168 | (name_size / service_height) / 2; | ||
2680 | 1169 | } | ||
2681 | 1170 | }); | ||
2682 | 1171 | node.select('.charm-label') | ||
2683 | 1172 | .attr({'style': function(d) { | ||
2684 | 1173 | // Programmatically size the font. | ||
2685 | 1174 | // Number derived from service assets: | ||
2686 | 1175 | // font-size 16px when asset is 224px. | ||
2687 | 1176 | return 'font-size:' + d.h * | ||
2688 | 1177 | (charm_label_size / service_height) + 'px'; | ||
2689 | 1178 | }, | ||
2690 | 1179 | 'x': function(d) { return d.w / 2;}, | ||
2691 | 1180 | 'y': function(d) { | ||
2692 | 1181 | // Number derived from service assets: | ||
2693 | 1182 | // padding-top: 118px when asset is 224px. | ||
2694 | 1183 | return d.h * (charm_label_padding / service_height) - d.h * | ||
2695 | 1184 | (charm_label_size / service_height) / 2; | ||
2696 | 1185 | } | ||
2697 | 1186 | }); | ||
2698 | 1187 | |||
2699 | 1188 | // Show whether or not the service is exposed using an indicator. | ||
2700 | 1189 | var exposed = node.filter(function(d) { | ||
2701 | 1190 | return d.exposed; | ||
2702 | 1191 | }); | ||
2703 | 1192 | exposed.each(function(d) { | ||
2704 | 1193 | var existing = Y.one(this).one('.exposed-indicator'); | ||
2705 | 1194 | if (!existing) { | ||
2706 | 1195 | existing = d3.select(this).append('image') | ||
2707 | 1196 | .attr({'class': 'exposed-indicator on', | ||
2708 | 1197 | 'xlink:href': '/juju-ui/assets/svgs/exposed.svg', | ||
2709 | 1198 | 'width': 32, | ||
2710 | 1199 | 'height': 32 | ||
2711 | 1200 | }) | ||
2712 | 1201 | .append('title') | ||
2713 | 1202 | .text(function(d) { | ||
2714 | 1203 | return d.exposed ? 'Exposed' : ''; | ||
2715 | 1204 | }); | ||
2716 | 1205 | } | ||
2717 | 1206 | existing = d3.select(this).select('.exposed-indicator') | ||
2718 | 1207 | .attr({ | ||
2719 | 1208 | 'x': 145, | ||
2720 | 1209 | 'y': 79 | ||
2721 | 1210 | }); | ||
2722 | 1211 | }); | ||
2723 | 1212 | |||
2724 | 1213 | // Remove exposed indicator from nodes that are no longer exposed. | ||
2725 | 1214 | node.filter(function(d) { | ||
2726 | 1215 | return !d.exposed && | ||
2727 | 1216 | !d3.select(this) | ||
2728 | 1217 | .select('.exposed-indicator').empty(); | ||
2729 | 1218 | }).select('.exposed-indicator').remove(); | ||
2730 | 1219 | |||
2731 | 1220 | // Adds the relative health in the form of a percentage bar. | ||
2732 | 1221 | node.each(function(d) { | ||
2733 | 1222 | var status_graph = d3.select(this).select('.statusbar'); | ||
2734 | 1223 | var status_bar = status_graph.property('status_bar'); | ||
2735 | 1224 | if (status_bar && !d.subordinate) { | ||
2736 | 1225 | status_bar.update(d.aggregated_status); | ||
2737 | 1226 | } | ||
2738 | 1227 | }); | ||
2739 | 1228 | }, | ||
2740 | 1229 | |||
2741 | 1230 | |||
2742 | 1231 | /* | 1239 | /* |
2743 | 1232 | * Show/hide/fade selection. | 1240 | * Show/hide/fade selection. |
2744 | 1233 | */ | 1241 | */ |
2745 | @@ -1245,7 +1253,7 @@ | |||
2746 | 1245 | 1253 | ||
2747 | 1246 | fade: function(evt) { | 1254 | fade: function(evt) { |
2748 | 1247 | var selection = evt.selection, | 1255 | var selection = evt.selection, |
2750 | 1248 | alpha = evt.alpha; | 1256 | alpha = evt.alpha; |
2751 | 1249 | selection.transition() | 1257 | selection.transition() |
2752 | 1250 | .duration(400) | 1258 | .duration(400) |
2753 | 1251 | .attr('opacity', alpha !== undefined && alpha || '0.2'); | 1259 | .attr('opacity', alpha !== undefined && alpha || '0.2'); |
2754 | @@ -1267,20 +1275,20 @@ | |||
2755 | 1267 | 1275 | ||
2756 | 1268 | updateServiceMenuLocation: function() { | 1276 | updateServiceMenuLocation: function() { |
2757 | 1269 | var topo = this.get('component'), | 1277 | var topo = this.get('component'), |
2763 | 1270 | container = this.get('container'), | 1278 | container = this.get('container'), |
2764 | 1271 | cp = container.one('.environment-menu.active'), | 1279 | cp = container.one('.environment-menu.active'), |
2765 | 1272 | service = topo.get('active_service'), | 1280 | service = topo.get('active_service'), |
2766 | 1273 | tr = topo.get('translate'), | 1281 | tr = topo.get('translate'), |
2767 | 1274 | z = topo.get('scale'); | 1282 | z = topo.get('scale'); |
2768 | 1275 | 1283 | ||
2769 | 1276 | if (service && cp) { | 1284 | if (service && cp) { |
2770 | 1277 | var cpRect = cp.getDOMNode().getClientRects()[0], | 1285 | var cpRect = cp.getDOMNode().getClientRects()[0], |
2774 | 1278 | cpWidth = cpRect.width, | 1286 | cpWidth = cpRect.width, |
2775 | 1279 | serviceCenter = service.relativeCenter, | 1287 | serviceCenter = service.relativeCenter, |
2776 | 1280 | menuLeft = (service.x * z + tr[0] + serviceCenter[0] * z < | 1288 | menuLeft = (service.x * z + tr[0] + serviceCenter[0] * z < |
2777 | 1281 | topo.get('width') / 2), | 1289 | topo.get('width') / 2), |
2780 | 1282 | cpHeight = cpRect.height, | 1290 | cpHeight = cpRect.height, |
2781 | 1283 | arrowWidth = 16; // Hard coded for now for simplicity. | 1291 | arrowWidth = 16; // Hard coded for now for simplicity. |
2782 | 1284 | 1292 | ||
2783 | 1285 | if (menuLeft) { | 1293 | if (menuLeft) { |
2784 | 1286 | cp.removeClass('left') | 1294 | cp.removeClass('left') |
2785 | @@ -1298,14 +1306,14 @@ | |||
2786 | 1298 | // right, and vice versa. | 1306 | // right, and vice versa. |
2787 | 1299 | cp.setStyles({ | 1307 | cp.setStyles({ |
2788 | 1300 | 'top': ( | 1308 | 'top': ( |
2791 | 1301 | service.y * z + tr[1] + | 1309 | service.y * z + tr[1] + |
2792 | 1302 | (serviceCenter[1] * z) - (cpHeight / 2)), | 1310 | (serviceCenter[1] * z) - (cpHeight / 2)), |
2793 | 1303 | 'left': ( | 1311 | 'left': ( |
2794 | 1304 | service.x * z + | 1312 | service.x * z + |
2799 | 1305 | (menuLeft ? | 1313 | (menuLeft ? |
2800 | 1306 | service.w * z + arrowWidth : | 1314 | service.w * z + arrowWidth : |
2801 | 1307 | -(cpWidth) - arrowWidth) + | 1315 | -(cpWidth) - arrowWidth) + |
2802 | 1308 | tr[0]) | 1316 | tr[0]) |
2803 | 1309 | }); | 1317 | }); |
2804 | 1310 | } | 1318 | } |
2805 | 1311 | }, | 1319 | }, |
2806 | @@ -1453,7 +1461,7 @@ | |||
2807 | 1453 | // Show dialog. | 1461 | // Show dialog. |
2808 | 1454 | this.set('destroy_dialog', views.createModalPanel( | 1462 | this.set('destroy_dialog', views.createModalPanel( |
2809 | 1455 | 'Are you sure you want to destroy the service? ' + | 1463 | 'Are you sure you want to destroy the service? ' + |
2811 | 1456 | 'This cannot be undone.', | 1464 | 'This cannot be undone.', |
2812 | 1457 | '#destroy-modal-panel', | 1465 | '#destroy-modal-panel', |
2813 | 1458 | 'Destroy Service', | 1466 | 'Destroy Service', |
2814 | 1459 | Y.bind(function(ev) { | 1467 | Y.bind(function(ev) { |
2815 | @@ -1471,16 +1479,16 @@ | |||
2816 | 1471 | */ | 1479 | */ |
2817 | 1472 | destroyService: function(btn) { | 1480 | destroyService: function(btn) { |
2818 | 1473 | var env = this.get('component').get('env'), | 1481 | var env = this.get('component').get('env'), |
2820 | 1474 | service = this.get('destroy_service'); | 1482 | service = this.get('destroy_service'); |
2821 | 1475 | env.destroy_service(service.get('id'), | 1483 | env.destroy_service(service.get('id'), |
2824 | 1476 | Y.bind(this._destroyCallback, this, | 1484 | Y.bind(this._destroyCallback, this, |
2825 | 1477 | service, btn)); | 1485 | service, btn)); |
2826 | 1478 | }, | 1486 | }, |
2827 | 1479 | 1487 | ||
2828 | 1480 | _destroyCallback: function(service, btn, ev) { | 1488 | _destroyCallback: function(service, btn, ev) { |
2829 | 1481 | var getModelURL = this.get('component').get('getModelURL'), | 1489 | var getModelURL = this.get('component').get('getModelURL'), |
2832 | 1482 | topo = this.get('component'), | 1490 | topo = this.get('component'), |
2833 | 1483 | db = topo.get('db'); | 1491 | db = topo.get('db'); |
2834 | 1484 | if (ev.err) { | 1492 | if (ev.err) { |
2835 | 1485 | db.notifications.add( | 1493 | db.notifications.add( |
2836 | 1486 | new models.Notification({ | 1494 | new models.Notification({ |
2837 | 1487 | 1495 | ||
2838 | === modified file 'app/views/topology/topology.js' | |||
2839 | --- app/views/topology/topology.js 2013-05-17 14:51:05 +0000 | |||
2840 | +++ app/views/topology/topology.js 2013-08-30 19:48:17 +0000 | |||
2841 | @@ -49,17 +49,15 @@ | |||
2842 | 49 | */ | 49 | */ |
2843 | 50 | var Topology = Y.Base.create('Topology', d3ns.Component, [], { | 50 | var Topology = Y.Base.create('Topology', d3ns.Component, [], { |
2844 | 51 | initializer: function(options) { | 51 | initializer: function(options) { |
2847 | 52 | Topology.superclass.constructor.apply(this, arguments); | 52 | this.options = Y.mix(options || {}, { |
2846 | 53 | this.options = Y.mix(options || { | ||
2848 | 54 | minZoom: 0.25, | 53 | minZoom: 0.25, |
2849 | 55 | maxZoom: 2, | 54 | maxZoom: 2, |
2850 | 56 | minSlider: 25, | 55 | minSlider: 25, |
2851 | 57 | maxSlider: 200 | 56 | maxSlider: 200 |
2852 | 58 | }); | 57 | }); |
2854 | 59 | 58 | Topology.superclass.constructor.apply(this, arguments); | |
2855 | 60 | // Build a service.id -> BoundingBox map for services. | 59 | // Build a service.id -> BoundingBox map for services. |
2856 | 61 | this.service_boxes = {}; | 60 | this.service_boxes = {}; |
2857 | 62 | |||
2858 | 63 | this._subscriptions = []; | 61 | this._subscriptions = []; |
2859 | 64 | }, | 62 | }, |
2860 | 65 | 63 | ||
2861 | @@ -111,11 +109,16 @@ | |||
2862 | 111 | this.computeScales(); | 109 | this.computeScales(); |
2863 | 112 | 110 | ||
2864 | 113 | // Set up the visualization with a pack layout. | 111 | // Set up the visualization with a pack layout. |
2870 | 114 | svg = d3.select(container.getDOMNode()) | 112 | var canvas = d3.select(container.getDOMNode()); |
2871 | 115 | .selectAll('.topology-canvas') | 113 | var base = canvas.select('.topology-canvas'); |
2872 | 116 | .append('svg:svg') | 114 | if (base.empty()) { |
2873 | 117 | .attr('width', width) | 115 | base = canvas.append('div') |
2874 | 118 | .attr('height', height); | 116 | .classed('topology-canvas', true); |
2875 | 117 | } | ||
2876 | 118 | |||
2877 | 119 | svg = base.append('svg:svg') | ||
2878 | 120 | .attr('width', width) | ||
2879 | 121 | .attr('height', height); | ||
2880 | 119 | this.svg = svg; | 122 | this.svg = svg; |
2881 | 120 | 123 | ||
2882 | 121 | this.zoomPlane = svg.append('rect') | 124 | this.zoomPlane = svg.append('rect') |
2883 | @@ -209,9 +212,9 @@ | |||
2884 | 209 | getter: function() {return this.get('size')[1];} | 212 | getter: function() {return this.get('size')[1];} |
2885 | 210 | }, | 213 | }, |
2886 | 211 | /* | 214 | /* |
2890 | 212 | * Scale and translate are managed by an external module | 215 | Scale and translate are managed by an external module |
2891 | 213 | * (PanZoom in this case). If that module isn't | 216 | (PanZoom in this case). If that module isn't |
2892 | 214 | * loaded nothing will modify these values. | 217 | loaded nothing will modify these values. |
2893 | 215 | */ | 218 | */ |
2894 | 216 | scale: { | 219 | scale: { |
2895 | 217 | getter: function() {return this.zoom.scale();}, | 220 | getter: function() {return this.zoom.scale();}, |
2896 | 218 | 221 | ||
2897 | === modified file 'app/views/utils.js' | |||
2898 | --- app/views/utils.js 2013-08-19 20:51:17 +0000 | |||
2899 | +++ app/views/utils.js 2013-08-30 19:48:17 +0000 | |||
2900 | @@ -564,11 +564,11 @@ | |||
2901 | 564 | @property constraintDescriptions | 564 | @property constraintDescriptions |
2902 | 565 | */ | 565 | */ |
2903 | 566 | utils.constraintDescriptions = { | 566 | utils.constraintDescriptions = { |
2906 | 567 | arch: {title: 'Architecture'}, | 567 | 'arch': {title: 'Architecture'}, |
2907 | 568 | cpu: {title: 'CPU', unit: 'GHz'}, | 568 | 'cpu': {title: 'CPU', unit: 'GHz'}, |
2908 | 569 | 'cpu-cores': {title: 'CPU Cores'}, | 569 | 'cpu-cores': {title: 'CPU Cores'}, |
2909 | 570 | 'cpu-power': {title: 'CPU Power', unit: 'GHz'}, | 570 | 'cpu-power': {title: 'CPU Power', unit: 'GHz'}, |
2911 | 571 | mem: {title: 'Memory', unit: 'GB'} | 571 | 'mem': {title: 'Memory', unit: 'GB'} |
2912 | 572 | }; | 572 | }; |
2913 | 573 | 573 | ||
2914 | 574 | /** | 574 | /** |
2915 | @@ -1457,10 +1457,11 @@ | |||
2916 | 1457 | debugger; | 1457 | debugger; |
2917 | 1458 | /*jshint debug:false */ | 1458 | /*jshint debug:false */ |
2918 | 1459 | }); | 1459 | }); |
2919 | 1460 | |||
2920 | 1460 | /* | 1461 | /* |
2921 | 1461 | * Extension for views to provide an apiFailure method. | 1462 | * Extension for views to provide an apiFailure method. |
2922 | 1462 | * | 1463 | * |
2924 | 1463 | * @class apiFailure | 1464 | * @class apiFailingView |
2925 | 1464 | */ | 1465 | */ |
2926 | 1465 | utils.apiFailingView = function() { | 1466 | utils.apiFailingView = function() { |
2927 | 1466 | this._initAPIFailingView(); | 1467 | this._initAPIFailingView(); |
2928 | 1467 | 1468 | ||
2929 | === modified file 'app/views/viewlets/inspector-header.js' | |||
2930 | --- app/views/viewlets/inspector-header.js 2013-08-12 15:13:17 +0000 | |||
2931 | +++ app/views/viewlets/inspector-header.js 2013-08-30 19:48:17 +0000 | |||
2932 | @@ -29,35 +29,22 @@ | |||
2933 | 29 | name: 'inspectorHeader', | 29 | name: 'inspectorHeader', |
2934 | 30 | template: templates['inspector-header'], | 30 | template: templates['inspector-header'], |
2935 | 31 | slot: 'header', | 31 | slot: 'header', |
2936 | 32 | bindings: { | ||
2937 | 33 | icon: { | ||
2938 | 34 | 'update': function(node, value) { | ||
2939 | 35 | // XXX: Icon is only present on services that pass through | ||
2940 | 36 | // the Ghost phase of the GUI. Once we have better integration | ||
2941 | 37 | // with the charm browser API services handling of icon | ||
2942 | 38 | // can be improved. | ||
2943 | 39 | var icon = node.one('img'); | ||
2944 | 40 | if (icon === null && value) { | ||
2945 | 41 | node.append('<img>'); | ||
2946 | 42 | icon = node.one('img'); | ||
2947 | 43 | } | ||
2948 | 44 | if (value) { | ||
2949 | 45 | icon.set('src', value); | ||
2950 | 46 | } | ||
2951 | 47 | } | ||
2952 | 48 | } | ||
2953 | 49 | }, | ||
2954 | 50 | 'render': function(model, viewContainerAttrs) { | 32 | 'render': function(model, viewContainerAttrs) { |
2955 | 51 | this.container = Y.Node.create(this.templateWrapper); | 33 | this.container = Y.Node.create(this.templateWrapper); |
2956 | 52 | var pojoModel = model.getAttrs(); | 34 | var pojoModel = model.getAttrs(); |
2957 | 53 | if (pojoModel.scheme) { | 35 | if (pojoModel.scheme) { |
2958 | 54 | pojoModel.ghost = true; | 36 | pojoModel.ghost = true; |
2959 | 55 | } | 37 | } |
2960 | 38 | // If this is a service, the id is the service.charm. | ||
2961 | 56 | if (pojoModel.charm) { | 39 | if (pojoModel.charm) { |
2962 | 57 | pojoModel.charmUrl = pojoModel.charm; | 40 | pojoModel.charmUrl = pojoModel.charm; |
2963 | 58 | } else { | 41 | } else { |
2964 | 42 | // If this is a charm model, just use the id. | ||
2965 | 59 | pojoModel.charmUrl = pojoModel.id; | 43 | pojoModel.charmUrl = pojoModel.id; |
2966 | 60 | } | 44 | } |
2967 | 45 | // Manually add the icon url for the charm since we don't have access to | ||
2968 | 46 | // the browser handlebars helper at this location. | ||
2969 | 47 | pojoModel.icon = viewContainerAttrs.store.iconpath(pojoModel.charmUrl); | ||
2970 | 61 | this.container.setHTML(this.template(pojoModel)); | 48 | this.container.setHTML(this.template(pojoModel)); |
2971 | 62 | } | 49 | } |
2972 | 63 | }; | 50 | }; |
2973 | 64 | 51 | ||
2974 | === modified file 'app/views/viewlets/service-constraints.js' | |||
2975 | --- app/views/viewlets/service-constraints.js 2013-08-19 16:58:14 +0000 | |||
2976 | +++ app/views/viewlets/service-constraints.js 2013-08-30 19:48:17 +0000 | |||
2977 | @@ -58,6 +58,7 @@ | |||
2978 | 58 | } | 58 | } |
2979 | 59 | 59 | ||
2980 | 60 | }; | 60 | }; |
2981 | 61 | |||
2982 | 61 | }, '0.0.1', { | 62 | }, '0.0.1', { |
2983 | 62 | requires: [ | 63 | requires: [ |
2984 | 63 | 'node', | 64 | 'node', |
2985 | 64 | 65 | ||
2986 | === modified file 'app/views/viewlets/service-ghost.js' | |||
2987 | --- app/views/viewlets/service-ghost.js 2013-08-08 17:30:58 +0000 | |||
2988 | +++ app/views/viewlets/service-ghost.js 2013-08-30 19:48:17 +0000 | |||
2989 | @@ -43,27 +43,34 @@ | |||
2990 | 43 | } | 43 | } |
2991 | 44 | } | 44 | } |
2992 | 45 | }, | 45 | }, |
2994 | 46 | 'render': function(model) { | 46 | 'render': function(model, viewletMgrAttrs) { |
2995 | 47 | this.container = Y.Node.create(this.templateWrapper); | 47 | this.container = Y.Node.create(this.templateWrapper); |
2996 | 48 | 48 | ||
2997 | 49 | // This is to allow for data binding on the ghost settings | 49 | // This is to allow for data binding on the ghost settings |
2998 | 50 | // while using a shared template across both inspectors | 50 | // while using a shared template across both inspectors |
3000 | 51 | var options = model.getAttrs(); | 51 | var templateOptions = model.getAttrs(); |
3001 | 52 | 52 | ||
3002 | 53 | // XXX - Jeff | 53 | // XXX - Jeff |
3003 | 54 | // not sure this should be done like this | 54 | // not sure this should be done like this |
3004 | 55 | // but this will allow us to use the old template. | 55 | // but this will allow us to use the old template. |
3006 | 56 | options.settings = utils.extractServiceSettings(options.options); | 56 | templateOptions.settings = utils.extractServiceSettings( |
3007 | 57 | templateOptions.options); | ||
3008 | 58 | |||
3009 | 59 | templateOptions.constraints = utils.getConstraints( | ||
3010 | 60 | // no current constraints in play. | ||
3011 | 61 | {}, | ||
3012 | 62 | viewletMgrAttrs.env.genericConstraints); | ||
3013 | 57 | 63 | ||
3014 | 58 | // Signalling to the shared templates that this is the ghost view. | 64 | // Signalling to the shared templates that this is the ghost view. |
3017 | 59 | options.ghost = true; | 65 | templateOptions.ghost = true; |
3018 | 60 | this.container.setHTML(this.template(options)); | 66 | this.container.setHTML(this.template(templateOptions)); |
3019 | 61 | 67 | ||
3025 | 62 | this.container.all('textarea.config-field') | 68 | var ResizingTextarea = plugins.ResizingTextArea; |
3026 | 63 | .plug(plugins.ResizingTextarea, | 69 | this.container.all('textarea.config-field').plug(ResizingTextarea, { |
3027 | 64 | { max_height: 200, | 70 | max_height: 200, |
3028 | 65 | min_height: 18, | 71 | min_height: 18, |
3029 | 66 | single_line: 18}); | 72 | single_line: 18 |
3030 | 73 | }); | ||
3031 | 67 | } | 74 | } |
3032 | 68 | }; | 75 | }; |
3033 | 69 | 76 | ||
3034 | 70 | 77 | ||
3035 | === modified file 'app/widgets/viewmode-controls.js' | |||
3036 | --- app/widgets/viewmode-controls.js 2013-07-15 13:17:42 +0000 | |||
3037 | +++ app/widgets/viewmode-controls.js 2013-08-30 19:48:17 +0000 | |||
3038 | @@ -142,6 +142,16 @@ | |||
3039 | 142 | ); | 142 | ); |
3040 | 143 | 143 | ||
3041 | 144 | this._updateActiveNav(this.get('currentViewmode')); | 144 | this._updateActiveNav(this.get('currentViewmode')); |
3042 | 145 | this.on('destroy', function(ev) { | ||
3043 | 146 | // We don't actually want the widget to remove any DOM. Just run our | ||
3044 | 147 | // unbinding of events for us. | ||
3045 | 148 | ev.halt(); | ||
3046 | 149 | this._events.forEach(function(e) { | ||
3047 | 150 | e.detach(); | ||
3048 | 151 | }); | ||
3049 | 152 | this._events = []; | ||
3050 | 153 | }, this); | ||
3051 | 154 | |||
3052 | 145 | }, | 155 | }, |
3053 | 146 | 156 | ||
3054 | 147 | /** | 157 | /** |
3055 | @@ -189,6 +199,83 @@ | |||
3056 | 189 | } | 199 | } |
3057 | 190 | }); | 200 | }); |
3058 | 191 | 201 | ||
3059 | 202 | /** | ||
3060 | 203 | * Extension for views to provide viewmode controls. | ||
3061 | 204 | * | ||
3062 | 205 | * @class viewmodeControllingView | ||
3063 | 206 | */ | ||
3064 | 207 | ns.ViewmodeControlsViewExtension = function() {}; | ||
3065 | 208 | ns.ViewmodeControlsViewExtension.prototype = { | ||
3066 | 209 | /** | ||
3067 | 210 | * Binds the viewmode controls on the page to the viewmode change events. | ||
3068 | 211 | * | ||
3069 | 212 | * @method _bindViewmodeControls | ||
3070 | 213 | * @param {Y.Widget} controls The viewmode control widget. | ||
3071 | 214 | */ | ||
3072 | 215 | _bindViewmodeControls: function(controls) { | ||
3073 | 216 | this._fullscreen = controls.on( | ||
3074 | 217 | controls.EVT_FULLSCREEN, this._goFullscreen, this); | ||
3075 | 218 | this._sidebar = controls.on( | ||
3076 | 219 | controls.EVT_SIDEBAR, this._goSidebar, this); | ||
3077 | 220 | this._minimized = controls.on( | ||
3078 | 221 | controls.EVT_TOGGLE_VIEWABLE, this._toggleMinimized, this); | ||
3079 | 222 | this._destroyMe = this.on('destroy', function() { | ||
3080 | 223 | // Unbind the View events listening for events from the widget. | ||
3081 | 224 | this._fullscreen.detach(); | ||
3082 | 225 | this._sidebar.detach(); | ||
3083 | 226 | this._minimized.detach(); | ||
3084 | 227 | // Including this event. | ||
3085 | 228 | this._destroyMe.detach(); | ||
3086 | 229 | // Finally, make sure we run destroy on the widget itself to unbind | ||
3087 | 230 | // it's own events. | ||
3088 | 231 | controls.destroy(); | ||
3089 | 232 | }); | ||
3090 | 233 | }, | ||
3091 | 234 | |||
3092 | 235 | /** | ||
3093 | 236 | Upon clicking the browser icon make sure we re-route to the | ||
3094 | 237 | new form of the UX. | ||
3095 | 238 | |||
3096 | 239 | @method _goFullscreen | ||
3097 | 240 | @param {Event} ev the click event handler on the button. | ||
3098 | 241 | |||
3099 | 242 | */ | ||
3100 | 243 | _goFullscreen: function(ev) { | ||
3101 | 244 | ev.halt(); | ||
3102 | 245 | this.fire('viewNavigate', { | ||
3103 | 246 | change: { | ||
3104 | 247 | viewmode: 'fullscreen' | ||
3105 | 248 | } | ||
3106 | 249 | }); | ||
3107 | 250 | }, | ||
3108 | 251 | |||
3109 | 252 | /** | ||
3110 | 253 | Upon clicking the build icon make sure we re-route to the | ||
3111 | 254 | new form of the UX. | ||
3112 | 255 | |||
3113 | 256 | @method _goSidebar | ||
3114 | 257 | @param {Event} ev the click event handler on the button. | ||
3115 | 258 | |||
3116 | 259 | */ | ||
3117 | 260 | _goSidebar: function(ev) { | ||
3118 | 261 | ev.halt(); | ||
3119 | 262 | this.fire('viewNavigate', { | ||
3120 | 263 | change: { | ||
3121 | 264 | viewmode: 'sidebar' | ||
3122 | 265 | } | ||
3123 | 266 | }); | ||
3124 | 267 | }, | ||
3125 | 268 | |||
3126 | 269 | /** | ||
3127 | 270 | * Place holder to toggle the minimized view; in minimized this should show | ||
3128 | 271 | * sidebar, in sidebar this should show minimized. | ||
3129 | 272 | * @method _toggleMinimized | ||
3130 | 273 | * @param {Event} ev event to trigger the toggle. | ||
3131 | 274 | * | ||
3132 | 275 | */ | ||
3133 | 276 | _toggleMinimized: function(ev) {} | ||
3134 | 277 | }; | ||
3135 | 278 | |||
3136 | 192 | }, '0.1.0', { | 279 | }, '0.1.0', { |
3137 | 193 | requires: [ | 280 | requires: [ |
3138 | 194 | 'base', | 281 | 'base', |
3139 | 195 | 282 | ||
3140 | === modified file 'docs/d3-component-framework.rst' | |||
3141 | --- docs/d3-component-framework.rst 2013-01-15 14:39:44 +0000 | |||
3142 | +++ docs/d3-component-framework.rst 2013-08-30 19:48:17 +0000 | |||
3143 | @@ -117,6 +117,11 @@ | |||
3144 | 117 | module declaration, but a module writer must understand them all to properly use | 117 | module declaration, but a module writer must understand them all to properly use |
3145 | 118 | the framework. | 118 | the framework. |
3146 | 119 | 119 | ||
3147 | 120 | When a Component is created it can be in either an interactive or | ||
3148 | 121 | non-interactive state. This is controlled through a Boolean 'interactive' | ||
3149 | 122 | attribute which defaults to true. When false events will not be bound and this | ||
3150 | 123 | section can be skipped. | ||
3151 | 124 | |||
3152 | 120 | When modules are added, three sets of declarative events are bound. This is | 125 | When modules are added, three sets of declarative events are bound. This is |
3153 | 121 | done by including in the module an events object with the following (each | 126 | done by including in the module an events object with the following (each |
3154 | 122 | optional) sections:: | 127 | optional) sections:: |
3155 | 123 | 128 | ||
3156 | === modified file 'lib/views/browser/charm-full.less' | |||
3157 | --- lib/views/browser/charm-full.less 2013-08-13 00:35:01 +0000 | |||
3158 | +++ lib/views/browser/charm-full.less 2013-08-30 19:48:17 +0000 | |||
3159 | @@ -86,7 +86,6 @@ | |||
3160 | 86 | float: left; | 86 | float: left; |
3161 | 87 | } | 87 | } |
3162 | 88 | .charm-icon, | 88 | .charm-icon, |
3163 | 89 | .category-icon, | ||
3164 | 90 | img.icon { | 89 | img.icon { |
3165 | 91 | width: 120px; | 90 | width: 120px; |
3166 | 92 | height: 120px; | 91 | height: 120px; |
3167 | 93 | 92 | ||
3168 | === modified file 'lib/views/browser/charm-token.less' | |||
3169 | --- lib/views/browser/charm-token.less 2013-08-09 14:28:06 +0000 | |||
3170 | +++ lib/views/browser/charm-token.less 2013-08-30 19:48:17 +0000 | |||
3171 | @@ -13,15 +13,12 @@ | |||
3172 | 13 | min-width: 200px; | 13 | min-width: 200px; |
3173 | 14 | 14 | ||
3174 | 15 | .charm-icon, | 15 | .charm-icon, |
3175 | 16 | .category-icon, | ||
3176 | 17 | .icon { | 16 | .icon { |
3177 | 18 | margin-right: 12px; | 17 | margin-right: 12px; |
3178 | 19 | position: relative; | 18 | position: relative; |
3179 | 20 | } | 19 | } |
3180 | 21 | .charm-icon, | 20 | .charm-icon, |
3181 | 22 | .charm-icon img, | 21 | .charm-icon img, |
3182 | 23 | .category-icon, | ||
3183 | 24 | .category-icon img, | ||
3184 | 25 | .icon, | 22 | .icon, |
3185 | 26 | .icon img { | 23 | .icon img { |
3186 | 27 | width: 48px; | 24 | width: 48px; |
3187 | @@ -39,15 +36,12 @@ | |||
3188 | 39 | } | 36 | } |
3189 | 40 | &.small { | 37 | &.small { |
3190 | 41 | .charm-icon, | 38 | .charm-icon, |
3191 | 42 | .category-icon, | ||
3192 | 43 | .icon { | 39 | .icon { |
3193 | 44 | margin-right: 10px; | 40 | margin-right: 10px; |
3194 | 45 | position: relative; | 41 | position: relative; |
3195 | 46 | } | 42 | } |
3196 | 47 | .charm-icon, | 43 | .charm-icon, |
3197 | 48 | .charm-icon img, | 44 | .charm-icon img, |
3198 | 49 | .category-icon, | ||
3199 | 50 | .category-icon img, | ||
3200 | 51 | .icon, | 45 | .icon, |
3201 | 52 | .icon img { | 46 | .icon img { |
3202 | 53 | width: 50px; | 47 | width: 50px; |
3203 | @@ -67,15 +61,12 @@ | |||
3204 | 67 | } | 61 | } |
3205 | 68 | &.large { | 62 | &.large { |
3206 | 69 | .charm-icon, | 63 | .charm-icon, |
3207 | 70 | .category-icon, | ||
3208 | 71 | .icon { | 64 | .icon { |
3209 | 72 | margin-right: 16px; | 65 | margin-right: 16px; |
3210 | 73 | position: relative; | 66 | position: relative; |
3211 | 74 | } | 67 | } |
3212 | 75 | .charm-icon, | 68 | .charm-icon, |
3213 | 76 | .charm-icon img, | 69 | .charm-icon img, |
3214 | 77 | .category-icon, | ||
3215 | 78 | .category-icon img, | ||
3216 | 79 | .icon, | 70 | .icon, |
3217 | 80 | .icon img { | 71 | .icon img { |
3218 | 81 | width: 96px; | 72 | width: 96px; |
3219 | 82 | 73 | ||
3220 | === modified file 'lib/views/juju-inspector.less' | |||
3221 | --- lib/views/juju-inspector.less 2013-08-21 03:20:46 +0000 | |||
3222 | +++ lib/views/juju-inspector.less 2013-08-30 19:48:17 +0000 | |||
3223 | @@ -41,7 +41,7 @@ | |||
3224 | 41 | position: absolute; | 41 | position: absolute; |
3225 | 42 | top: 20px; | 42 | top: 20px; |
3226 | 43 | bottom: @navbar-bottom-height + 20px; | 43 | bottom: @navbar-bottom-height + 20px; |
3228 | 44 | right: @inspector-width + 35px; | 44 | right: @inspector-width + 71px; |
3229 | 45 | overflow-y: auto; | 45 | overflow-y: auto; |
3230 | 46 | overflow-x: hidden; | 46 | overflow-x: hidden; |
3231 | 47 | width: @bws-panel-width; | 47 | width: @bws-panel-width; |
3232 | @@ -115,7 +115,7 @@ | |||
3233 | 115 | .create-border-radius(@border-radius); | 115 | .create-border-radius(@border-radius); |
3234 | 116 | position: absolute; | 116 | position: absolute; |
3235 | 117 | top: 20px; | 117 | top: 20px; |
3237 | 118 | right: 35px; | 118 | right: 70px; |
3238 | 119 | width: @inspector-width; | 119 | width: @inspector-width; |
3239 | 120 | min-height: 250px; | 120 | min-height: 250px; |
3240 | 121 | border: 0; | 121 | border: 0; |
3241 | @@ -248,6 +248,9 @@ | |||
3242 | 248 | padding-left: 10px; | 248 | padding-left: 10px; |
3243 | 249 | cursor: pointer; | 249 | cursor: pointer; |
3244 | 250 | } | 250 | } |
3245 | 251 | .editable-constraints { | ||
3246 | 252 | padding-left: 10px; | ||
3247 | 253 | } | ||
3248 | 251 | .unit-constraints-confirm { | 254 | .unit-constraints-confirm { |
3249 | 252 | overflow: hidden; | 255 | overflow: hidden; |
3250 | 253 | height: 130px; | 256 | height: 130px; |
3251 | @@ -272,6 +275,9 @@ | |||
3252 | 272 | .unit-constraints-confirm.closed { | 275 | .unit-constraints-confirm.closed { |
3253 | 273 | height: 0; | 276 | height: 0; |
3254 | 274 | } | 277 | } |
3255 | 278 | .unit-constraints-confirm.editing { | ||
3256 | 279 | height: 300px; | ||
3257 | 280 | } | ||
3258 | 275 | } | 281 | } |
3259 | 276 | 282 | ||
3260 | 277 | .settings-wrapper { | 283 | .settings-wrapper { |
3261 | @@ -290,6 +296,9 @@ | |||
3262 | 290 | textarea { | 296 | textarea { |
3263 | 291 | width: 100%; | 297 | width: 100%; |
3264 | 292 | } | 298 | } |
3265 | 299 | input[type=text] { | ||
3266 | 300 | width: 150px; | ||
3267 | 301 | } | ||
3268 | 293 | .settings-description { | 302 | .settings-description { |
3269 | 294 | font-size: 12px; | 303 | font-size: 12px; |
3270 | 295 | 304 | ||
3271 | @@ -389,7 +398,7 @@ | |||
3272 | 389 | } | 398 | } |
3273 | 390 | 399 | ||
3274 | 391 | /* Resetting panel styles. Can be removed after old inspector styles are | 400 | /* Resetting panel styles. Can be removed after old inspector styles are |
3276 | 392 | removed. */ | 401 | removed. (ServiceInspector) */ |
3277 | 393 | .charm-panel-configure { | 402 | .charm-panel-configure { |
3278 | 394 | background: transparent; | 403 | background: transparent; |
3279 | 395 | float: none; | 404 | float: none; |
3280 | @@ -468,11 +477,11 @@ | |||
3281 | 468 | input.service-name { | 477 | input.service-name { |
3282 | 469 | width: 125px; | 478 | width: 125px; |
3283 | 470 | } | 479 | } |
3285 | 471 | .charm-icon { | 480 | .icon { |
3286 | 472 | width: 64px; | 481 | width: 64px; |
3287 | 473 | height: 64px; | 482 | height: 64px; |
3288 | 474 | margin-right: 1em; | 483 | margin-right: 1em; |
3290 | 475 | background: transparent url(/juju-ui/assets/images/charm_64.png) left top no-repeat; | 484 | |
3291 | 476 | img { | 485 | img { |
3292 | 477 | /* bootstrap sets this to middle */ | 486 | /* bootstrap sets this to middle */ |
3293 | 478 | vertical-align: baseline; | 487 | vertical-align: baseline; |
3294 | @@ -582,8 +591,7 @@ | |||
3295 | 582 | font-size: 18px; | 591 | font-size: 18px; |
3296 | 583 | padding-left: 0; | 592 | padding-left: 0; |
3297 | 584 | } | 593 | } |
3300 | 585 | .settings-wrapper, | 594 | .settings-wrapper { |
3299 | 586 | .control-group { | ||
3301 | 587 | position: relative; | 595 | position: relative; |
3302 | 588 | padding: 0 10px; | 596 | padding: 0 10px; |
3303 | 589 | 597 | ||
3304 | 590 | 598 | ||
3305 | === added file 'test/data/wp-deployer.yaml' | |||
3306 | --- test/data/wp-deployer.yaml 1970-01-01 00:00:00 +0000 | |||
3307 | +++ test/data/wp-deployer.yaml 2013-08-30 19:48:17 +0000 | |||
3308 | @@ -0,0 +1,41 @@ | |||
3309 | 1 | envExport: | ||
3310 | 2 | series: precise | ||
3311 | 3 | services: | ||
3312 | 4 | mysql: | ||
3313 | 5 | charm: "cs:precise/mysql-27" | ||
3314 | 6 | num_units: 1 | ||
3315 | 7 | options: | ||
3316 | 8 | "binlog-format": MIXED | ||
3317 | 9 | "block-size": "5" | ||
3318 | 10 | "dataset-size": "80%" | ||
3319 | 11 | flavor: distro | ||
3320 | 12 | 'gui-x': 50 | ||
3321 | 13 | 'gui-y': 50 | ||
3322 | 14 | "ha-bindiface": eth0 | ||
3323 | 15 | "ha-mcastport": "5411" | ||
3324 | 16 | "max-connections": "-1" | ||
3325 | 17 | "preferred-storage-engine": InnoDB | ||
3326 | 18 | "query-cache-size": "-1" | ||
3327 | 19 | "query-cache-type": "OFF" | ||
3328 | 20 | "rbd-name": mysql1 | ||
3329 | 21 | "tuning-level": safest | ||
3330 | 22 | vip: "" | ||
3331 | 23 | vip_cidr: "24" | ||
3332 | 24 | vip_iface: eth0 | ||
3333 | 25 | annotations: | ||
3334 | 26 | "gui-x": 115 | ||
3335 | 27 | "gui-y": 89 | ||
3336 | 28 | wordpress: | ||
3337 | 29 | charm: "cs:precise/wordpress-16" | ||
3338 | 30 | num_units: 1 | ||
3339 | 31 | options: | ||
3340 | 32 | debug: "no" | ||
3341 | 33 | engine: nginx | ||
3342 | 34 | tuning: single | ||
3343 | 35 | "wp-content": "" | ||
3344 | 36 | annotations: | ||
3345 | 37 | "gui-x": 510 | ||
3346 | 38 | "gui-y": 184 | ||
3347 | 39 | relations: | ||
3348 | 40 | - - "wordpress:db" | ||
3349 | 41 | - "mysql:db" | ||
3350 | 0 | 42 | ||
3351 | === modified file 'test/index.html' | |||
3352 | --- test/index.html 2013-08-06 04:35:06 +0000 | |||
3353 | +++ test/index.html 2013-08-30 19:48:17 +0000 | |||
3354 | @@ -32,7 +32,7 @@ | |||
3355 | 32 | should = chai.should(); | 32 | should = chai.should(); |
3356 | 33 | mocha.reporter('html'); | 33 | mocha.reporter('html'); |
3357 | 34 | mocha.ui('bdd'); | 34 | mocha.ui('bdd'); |
3359 | 35 | mocha.setup({ignoreLeaks: false, timeout: 10000}) | 35 | mocha.setup({ignoreLeaks: false, timeout: 10000}); |
3360 | 36 | </script> | 36 | </script> |
3361 | 37 | 37 | ||
3362 | 38 | <!-- Load up YUI base, app modules, and test utils --> | 38 | <!-- Load up YUI base, app modules, and test utils --> |
3363 | @@ -56,6 +56,7 @@ | |||
3364 | 56 | <script src="test_browser_models.js"></script> | 56 | <script src="test_browser_models.js"></script> |
3365 | 57 | <script src="test_browser_search_view.js"></script> | 57 | <script src="test_browser_search_view.js"></script> |
3366 | 58 | <script src="test_browser_search_widget.js"></script> | 58 | <script src="test_browser_search_widget.js"></script> |
3367 | 59 | <script src="test_bundle_module.js"></script> | ||
3368 | 59 | <script src="test_charm_configuration.js"></script> | 60 | <script src="test_charm_configuration.js"></script> |
3369 | 60 | <script src="test_charm_container.js"></script> | 61 | <script src="test_charm_container.js"></script> |
3370 | 61 | <script src="test_charm_panel.js"></script> | 62 | <script src="test_charm_panel.js"></script> |
3371 | 62 | 63 | ||
3372 | === modified file 'test/test_app.js' | |||
3373 | --- test/test_app.js 2013-07-31 14:30:47 +0000 | |||
3374 | +++ test/test_app.js 2013-08-30 19:48:17 +0000 | |||
3375 | @@ -160,7 +160,7 @@ | |||
3376 | 160 | 160 | ||
3377 | 161 | it('should be able to route objects to internal URLs', function() { | 161 | it('should be able to route objects to internal URLs', function() { |
3378 | 162 | constructAppInstance({ | 162 | constructAppInstance({ |
3380 | 163 | env: juju.newEnvironment({ conn: new utils.SocketStub() }) | 163 | env: juju.newEnvironment({conn: new utils.SocketStub()}, 'python') |
3381 | 164 | }); | 164 | }); |
3382 | 165 | // Take handles to database objects and ensure we can route to the view | 165 | // Take handles to database objects and ensure we can route to the view |
3383 | 166 | // needed to show them. | 166 | // needed to show them. |
3384 | @@ -241,6 +241,14 @@ | |||
3385 | 241 | }) | 241 | }) |
3386 | 242 | }); | 242 | }); |
3387 | 243 | 243 | ||
3388 | 244 | // XXX bug:1217383 | ||
3389 | 245 | // Force an app._controlEvents so that we don't try to bind viewmode | ||
3390 | 246 | // controls. | ||
3391 | 247 | var fakeEv = { | ||
3392 | 248 | detach: function() {} | ||
3393 | 249 | }; | ||
3394 | 250 | app._controlEvents = [fakeEv, fakeEv]; | ||
3395 | 251 | |||
3396 | 244 | var checkUrls = [{ | 252 | var checkUrls = [{ |
3397 | 245 | url: ':gui:/service/memcached/', | 253 | url: ':gui:/service/memcached/', |
3398 | 246 | hidden: true | 254 | hidden: true |
3399 | @@ -329,6 +337,12 @@ | |||
3400 | 329 | return app; | 337 | return app; |
3401 | 330 | }; | 338 | }; |
3402 | 331 | 339 | ||
3403 | 340 | // Ensure the given message is a login request. | ||
3404 | 341 | var assertIsLogin = function(message) { | ||
3405 | 342 | assert.equal('Admin', message.Type); | ||
3406 | 343 | assert.equal('Login', message.Request); | ||
3407 | 344 | }; | ||
3408 | 345 | |||
3409 | 332 | it('avoids trying to login if the env is not connected', function(done) { | 346 | it('avoids trying to login if the env is not connected', function(done) { |
3410 | 333 | var app = makeApp(false); // Create a disconnected app. | 347 | var app = makeApp(false); // Create a disconnected app. |
3411 | 334 | app.after('ready', function() { | 348 | app.after('ready', function() { |
3412 | @@ -341,7 +355,7 @@ | |||
3413 | 341 | var app = makeApp(true); // Create a connected app. | 355 | var app = makeApp(true); // Create a connected app. |
3414 | 342 | app.after('ready', function() { | 356 | app.after('ready', function() { |
3415 | 343 | assert.equal(1, conn.messages.length); | 357 | assert.equal(1, conn.messages.length); |
3417 | 344 | assert.equal('login', conn.last_message().op); | 358 | assertIsLogin(conn.last_message()); |
3418 | 345 | done(); | 359 | done(); |
3419 | 346 | }); | 360 | }); |
3420 | 347 | }); | 361 | }); |
3421 | @@ -360,21 +374,22 @@ | |||
3422 | 360 | it('displays the login view if credentials are not valid', function(done) { | 374 | it('displays the login view if credentials are not valid', function(done) { |
3423 | 361 | var app = makeApp(true); // Create a connected app. | 375 | var app = makeApp(true); // Create a connected app. |
3424 | 362 | app.after('ready', function() { | 376 | app.after('ready', function() { |
3427 | 363 | // Mimic a login failed response. | 377 | app.env.login(); |
3428 | 364 | conn.msg({op: 'login', result: false}); | 378 | // Mimic a login failed response assuming login is the first request. |
3429 | 379 | conn.msg({RequestId: 1, Error: 'Invalid user or password'}); | ||
3430 | 365 | assert.equal(1, conn.messages.length); | 380 | assert.equal(1, conn.messages.length); |
3432 | 366 | assert.equal('login', conn.last_message().op); | 381 | assertIsLogin(conn.last_message()); |
3433 | 367 | assert.equal(LOGIN_VIEW_NAME, app.get('activeView').name); | 382 | assert.equal(LOGIN_VIEW_NAME, app.get('activeView').name); |
3434 | 368 | done(); | 383 | done(); |
3435 | 369 | }); | 384 | }); |
3436 | 370 | }); | 385 | }); |
3437 | 371 | 386 | ||
3439 | 372 | it('login method hanlder is called after successful login', function(done) { | 387 | it('login method handler is called after successful login', function(done) { |
3440 | 373 | var oldOnLogin = Y.juju.App.onLogin; | 388 | var oldOnLogin = Y.juju.App.onLogin; |
3441 | 374 | Y.juju.App.prototype.onLogin = function(e) { | 389 | Y.juju.App.prototype.onLogin = function(e) { |
3442 | 375 | assert.equal(conn.messages.length, 1); | 390 | assert.equal(conn.messages.length, 1); |
3445 | 376 | assert.equal(conn.last_message().op, 'login'); | 391 | assertIsLogin(conn.last_message()); |
3446 | 377 | assert.equal(e.data.result, true); | 392 | assert.isTrue(e.data.result, true); |
3447 | 378 | Y.juju.App.onLogin = oldOnLogin; | 393 | Y.juju.App.onLogin = oldOnLogin; |
3448 | 379 | done(); | 394 | done(); |
3449 | 380 | }; | 395 | }; |
3450 | @@ -391,7 +406,7 @@ | |||
3451 | 391 | app.after('ready', function() { | 406 | app.after('ready', function() { |
3452 | 392 | env.connect(); | 407 | env.connect(); |
3453 | 393 | assert.equal(1, conn.messages.length); | 408 | assert.equal(1, conn.messages.length); |
3455 | 394 | assert.equal('login', conn.last_message().op); | 409 | assertIsLogin(conn.last_message()); |
3456 | 395 | done(); | 410 | done(); |
3457 | 396 | }); | 411 | }); |
3458 | 397 | }); | 412 | }); |
3459 | @@ -403,10 +418,8 @@ | |||
3460 | 403 | // Disconnect and reconnect the WebSocket. | 418 | // Disconnect and reconnect the WebSocket. |
3461 | 404 | conn.transient_close(); | 419 | conn.transient_close(); |
3462 | 405 | conn.open(); | 420 | conn.open(); |
3467 | 406 | assert.equal(2, conn.messages.length); | 421 | assert.equal(1, conn.messages.length); |
3468 | 407 | Y.each(conn.messages, function(message) { | 422 | assertIsLogin(conn.last_message()); |
3465 | 408 | assert.equal('login', message.op); | ||
3466 | 409 | }); | ||
3469 | 410 | done(); | 423 | done(); |
3470 | 411 | }); | 424 | }); |
3471 | 412 | }); | 425 | }); |
3472 | 413 | 426 | ||
3473 | === modified file 'test/test_browser_app.js' | |||
3474 | --- test/test_browser_app.js 2013-08-19 17:15:16 +0000 | |||
3475 | +++ test/test_browser_app.js 2013-08-30 19:48:17 +0000 | |||
3476 | @@ -181,6 +181,54 @@ | |||
3477 | 181 | })(); | 181 | })(); |
3478 | 182 | 182 | ||
3479 | 183 | (function() { | 183 | (function() { |
3480 | 184 | describe('browser minimzed view', function() { | ||
3481 | 185 | var Y, browser, container, view, views, Minimized; | ||
3482 | 186 | |||
3483 | 187 | before(function(done) { | ||
3484 | 188 | Y = YUI(GlobalConfig).use( | ||
3485 | 189 | 'juju-browser', | ||
3486 | 190 | 'juju-models', | ||
3487 | 191 | 'juju-views', | ||
3488 | 192 | 'juju-tests-utils', | ||
3489 | 193 | 'subapp-browser-minimized', | ||
3490 | 194 | function(Y) { | ||
3491 | 195 | browser = Y.namespace('juju.browser'); | ||
3492 | 196 | views = Y.namespace('juju.browser.views'); | ||
3493 | 197 | Minimized = views.MinimizedView; | ||
3494 | 198 | done(); | ||
3495 | 199 | }); | ||
3496 | 200 | }); | ||
3497 | 201 | |||
3498 | 202 | beforeEach(function() { | ||
3499 | 203 | container = Y.namespace('juju-tests.utils').makeContainer('container'); | ||
3500 | 204 | addBrowserContainer(Y, container); | ||
3501 | 205 | // Mock out a dummy location for the Store used in view instances. | ||
3502 | 206 | window.juju_config = { | ||
3503 | 207 | charmworldURL: 'http://localhost' | ||
3504 | 208 | }; | ||
3505 | 209 | }); | ||
3506 | 210 | |||
3507 | 211 | afterEach(function() { | ||
3508 | 212 | view.destroy(); | ||
3509 | 213 | Y.one('#subapp-browser').remove(true); | ||
3510 | 214 | delete window.juju_config; | ||
3511 | 215 | container.remove(true); | ||
3512 | 216 | }); | ||
3513 | 217 | |||
3514 | 218 | it('toggles to sidebar', function(done) { | ||
3515 | 219 | var container = Y.one('#subapp-browser'); | ||
3516 | 220 | view = new Minimized(); | ||
3517 | 221 | view.on('viewNavigate', function(ev) { | ||
3518 | 222 | assert(ev.change.viewmode === 'sidebar'); | ||
3519 | 223 | done(); | ||
3520 | 224 | }); | ||
3521 | 225 | view.render(container); | ||
3522 | 226 | view.controls._toggleViewable({halt: function() {}}); | ||
3523 | 227 | }); | ||
3524 | 228 | }); | ||
3525 | 229 | })(); | ||
3526 | 230 | |||
3527 | 231 | (function() { | ||
3528 | 184 | describe('browser sidebar view', function() { | 232 | describe('browser sidebar view', function() { |
3529 | 185 | var Y, browser, container, view, views, Sidebar; | 233 | var Y, browser, container, view, views, Sidebar; |
3530 | 186 | 234 | ||
3531 | @@ -332,13 +380,14 @@ | |||
3532 | 332 | 380 | ||
3533 | 333 | (function() { | 381 | (function() { |
3534 | 334 | describe('browser app', function() { | 382 | describe('browser app', function() { |
3536 | 335 | var Y, app, browser, Charmworld2, next; | 383 | var Y, app, browser, Charmworld2, container, next; |
3537 | 336 | 384 | ||
3538 | 337 | before(function(done) { | 385 | before(function(done) { |
3539 | 338 | Y = YUI(GlobalConfig).use( | 386 | Y = YUI(GlobalConfig).use( |
3540 | 339 | 'app-subapp-extension', | 387 | 'app-subapp-extension', |
3541 | 340 | 'juju-browser', | 388 | 'juju-browser', |
3542 | 341 | 'juju-charm-store', | 389 | 'juju-charm-store', |
3543 | 390 | 'juju-tests-utils', | ||
3544 | 342 | 'juju-views', | 391 | 'juju-views', |
3545 | 343 | 'subapp-browser', function(Y) { | 392 | 'subapp-browser', function(Y) { |
3546 | 344 | browser = Y.namespace('juju.subapps'); | 393 | browser = Y.namespace('juju.subapps'); |
3547 | @@ -353,12 +402,16 @@ | |||
3548 | 353 | window.juju_config = { | 402 | window.juju_config = { |
3549 | 354 | charmworldURL: 'http://localhost' | 403 | charmworldURL: 'http://localhost' |
3550 | 355 | }; | 404 | }; |
3551 | 405 | container = Y.namespace('juju-tests.utils').makeContainer('container'); | ||
3552 | 406 | addBrowserContainer(Y, container); | ||
3553 | 407 | |||
3554 | 356 | }); | 408 | }); |
3555 | 357 | 409 | ||
3556 | 358 | afterEach(function() { | 410 | afterEach(function() { |
3557 | 359 | if (app) { | 411 | if (app) { |
3558 | 360 | app.destroy(); | 412 | app.destroy(); |
3559 | 361 | } | 413 | } |
3560 | 414 | container.remove(true); | ||
3561 | 362 | window.juju_config = undefined; | 415 | window.juju_config = undefined; |
3562 | 363 | }); | 416 | }); |
3563 | 364 | 417 | ||
3564 | @@ -383,6 +436,28 @@ | |||
3565 | 383 | assert.isTrue(called); | 436 | assert.isTrue(called); |
3566 | 384 | }); | 437 | }); |
3567 | 385 | 438 | ||
3568 | 439 | it('resets using initState', function() { | ||
3569 | 440 | app = new browser.Browser(); | ||
3570 | 441 | var mockView = { | ||
3571 | 442 | destroy: function() {} | ||
3572 | 443 | }; | ||
3573 | 444 | app._sidebar = mockView; | ||
3574 | 445 | app._minimized = mockView; | ||
3575 | 446 | app._fullscreen = mockView; | ||
3576 | 447 | |||
3577 | 448 | // Setup some previous state to check for clearing. | ||
3578 | 449 | app._oldState.viewmode = 'fullscreen'; | ||
3579 | 450 | app._viewState.viewmode = 'sidebar'; | ||
3580 | 451 | |||
3581 | 452 | app.initState(); | ||
3582 | 453 | |||
3583 | 454 | assert.equal(app._sidebar, undefined, 'sidebar is removed'); | ||
3584 | 455 | assert.equal(app._fullscreen, undefined, 'fullscreen is removed'); | ||
3585 | 456 | assert.equal(app._minimized, undefined, 'minimized is removed'); | ||
3586 | 457 | assert.equal(app._oldState.viewmode, null, 'old state is reset'); | ||
3587 | 458 | assert.equal(app._viewState.viewmode, null, 'view state is reset'); | ||
3588 | 459 | }); | ||
3589 | 460 | |||
3590 | 386 | it('correctly strips viewmode from the charmID', function() { | 461 | it('correctly strips viewmode from the charmID', function() { |
3591 | 387 | app = new browser.Browser(); | 462 | app = new browser.Browser(); |
3592 | 388 | var paths = [ | 463 | var paths = [ |
3593 | @@ -609,6 +684,7 @@ | |||
3594 | 609 | 'app-subapp-extension', | 684 | 'app-subapp-extension', |
3595 | 610 | 'juju-views', | 685 | 'juju-views', |
3596 | 611 | 'juju-browser', | 686 | 'juju-browser', |
3597 | 687 | 'juju-tests-utils', | ||
3598 | 612 | 'subapp-browser', function(Y) { | 688 | 'subapp-browser', function(Y) { |
3599 | 613 | browser = Y.namespace('juju.subapps'); | 689 | browser = Y.namespace('juju.subapps'); |
3600 | 614 | 690 | ||
3601 | @@ -723,6 +799,8 @@ | |||
3602 | 723 | it('resets filters when navigating away from search', function() { | 799 | it('resets filters when navigating away from search', function() { |
3603 | 724 | browser._viewState.search = true; | 800 | browser._viewState.search = true; |
3604 | 725 | browser._filter.set('text', 'foo'); | 801 | browser._filter.set('text', 'foo'); |
3605 | 802 | // Set the state before changing up. | ||
3606 | 803 | browser._saveState(); | ||
3607 | 726 | browser._getStateUrl({search: false}); | 804 | browser._getStateUrl({search: false}); |
3608 | 727 | assert.equal('', browser._filter.get('text')); | 805 | assert.equal('', browser._filter.get('text')); |
3609 | 728 | }); | 806 | }); |
3610 | @@ -1143,6 +1221,18 @@ | |||
3611 | 1143 | 1221 | ||
3612 | 1144 | it('when hidden the browser avoids routing', function() { | 1222 | it('when hidden the browser avoids routing', function() { |
3613 | 1145 | browser.hidden = true; | 1223 | browser.hidden = true; |
3614 | 1224 | // XXX bug:1217383 | ||
3615 | 1225 | // We also want to verify that the old views are cleared to avoid | ||
3616 | 1226 | // having hidden views doing UX work for us. | ||
3617 | 1227 | var hitCount = 0; | ||
3618 | 1228 | var mockView = { | ||
3619 | 1229 | destroy: function() { | ||
3620 | 1230 | hitCount = hitCount + 1; | ||
3621 | 1231 | } | ||
3622 | 1232 | }; | ||
3623 | 1233 | browser._sidebar = mockView; | ||
3624 | 1234 | browser._minimized = mockView; | ||
3625 | 1235 | browser._fullscreen = mockView; | ||
3626 | 1146 | 1236 | ||
3627 | 1147 | var req = { | 1237 | var req = { |
3628 | 1148 | path: '/minimized', | 1238 | path: '/minimized', |
3629 | @@ -1161,6 +1251,13 @@ | |||
3630 | 1161 | 1251 | ||
3631 | 1162 | minNode.getComputedStyle('display').should.eql('none'); | 1252 | minNode.getComputedStyle('display').should.eql('none'); |
3632 | 1163 | browserNode.getComputedStyle('display').should.eql('none'); | 1253 | browserNode.getComputedStyle('display').should.eql('none'); |
3633 | 1254 | |||
3634 | 1255 | assert.equal(hitCount, 3); | ||
3635 | 1256 | |||
3636 | 1257 | // The view state needs to also be sync'd and updated even though | ||
3637 | 1258 | // we're hidden so that we can detect changes in the app state across | ||
3638 | 1259 | // requests while hidden. | ||
3639 | 1260 | assert.equal(browser._oldState.viewmode, 'minimized'); | ||
3640 | 1164 | }); | 1261 | }); |
3641 | 1165 | 1262 | ||
3642 | 1166 | it('knows when the search cache should be updated', function() { | 1263 | it('knows when the search cache should be updated', function() { |
3643 | @@ -1169,16 +1266,19 @@ | |||
3644 | 1169 | 'querystring': 'text=apache' | 1266 | 'querystring': 'text=apache' |
3645 | 1170 | }); | 1267 | }); |
3646 | 1171 | assert.isTrue(browser._searchChanged()); | 1268 | assert.isTrue(browser._searchChanged()); |
3647 | 1269 | browser._saveState(); | ||
3648 | 1172 | browser._getStateUrl({ | 1270 | browser._getStateUrl({ |
3649 | 1173 | 'search': true, | 1271 | 'search': true, |
3650 | 1174 | 'querystring': 'text=apache' | 1272 | 'querystring': 'text=apache' |
3651 | 1175 | }); | 1273 | }); |
3652 | 1176 | assert.isFalse(browser._searchChanged()); | 1274 | assert.isFalse(browser._searchChanged()); |
3653 | 1275 | browser._saveState(); | ||
3654 | 1177 | browser._getStateUrl({ | 1276 | browser._getStateUrl({ |
3655 | 1178 | 'search': true, | 1277 | 'search': true, |
3656 | 1179 | 'querystring': 'text=ceph' | 1278 | 'querystring': 'text=ceph' |
3657 | 1180 | }); | 1279 | }); |
3658 | 1181 | assert.isTrue(browser._searchChanged()); | 1280 | assert.isTrue(browser._searchChanged()); |
3659 | 1281 | browser._saveState(); | ||
3660 | 1182 | }); | 1282 | }); |
3661 | 1183 | 1283 | ||
3662 | 1184 | it('permits a filter clear command', function() { | 1284 | it('permits a filter clear command', function() { |
3663 | 1185 | 1285 | ||
3664 | === modified file 'test/test_browser_editorial.js' | |||
3665 | --- test/test_browser_editorial.js 2013-07-16 19:05:46 +0000 | |||
3666 | +++ test/test_browser_editorial.js 2013-08-30 19:48:17 +0000 | |||
3667 | @@ -140,26 +140,6 @@ | |||
3668 | 140 | assert(node.all('.yui3-charmtoken-hidden').size() === 14); | 140 | assert(node.all('.yui3-charmtoken-hidden').size() === 14); |
3669 | 141 | }); | 141 | }); |
3670 | 142 | 142 | ||
3671 | 143 | it('does a search when a category link is clicked', function(done) { | ||
3672 | 144 | view = new EditorialView({ | ||
3673 | 145 | renderTo: Y.one('.bws-content') | ||
3674 | 146 | }); | ||
3675 | 147 | var results = { | ||
3676 | 148 | featuredCharms: [], | ||
3677 | 149 | newCharms: [], | ||
3678 | 150 | popularCharms: [] | ||
3679 | 151 | }; | ||
3680 | 152 | view.on('viewNavigate', function(ev) { | ||
3681 | 153 | assert.isTrue(ev.change.search); | ||
3682 | 154 | assert.equal(1, ev.change.filter.categories.length); | ||
3683 | 155 | assert.equal('databases', ev.change.filter.categories[0]); | ||
3684 | 156 | assert.equal(ev.change.filter.replace, true); | ||
3685 | 157 | done(); | ||
3686 | 158 | }); | ||
3687 | 159 | view.render(results); | ||
3688 | 160 | Y.one('#category-links').one('a').simulate('click'); | ||
3689 | 161 | }); | ||
3690 | 162 | |||
3691 | 163 | it('clicking a charm navigates for fullscreen', function(done) { | 143 | it('clicking a charm navigates for fullscreen', function(done) { |
3692 | 164 | fakeStore = new Y.juju.Charmworld2({}); | 144 | fakeStore = new Y.juju.Charmworld2({}); |
3693 | 165 | fakeStore.set('datasource', { | 145 | fakeStore.set('datasource', { |
3694 | 166 | 146 | ||
3695 | === added file 'test/test_bundle_module.js' | |||
3696 | --- test/test_bundle_module.js 1970-01-01 00:00:00 +0000 | |||
3697 | +++ test/test_bundle_module.js 2013-08-30 19:48:17 +0000 | |||
3698 | @@ -0,0 +1,98 @@ | |||
3699 | 1 | /* | ||
3700 | 2 | This file is part of the Juju GUI, which lets users view and manage Juju | ||
3701 | 3 | environments within a graphical interface (https://launchpad.net/juju-gui). | ||
3702 | 4 | Copyright (C) 2012-2013 Canonical Ltd. | ||
3703 | 5 | |||
3704 | 6 | This program is free software: you can redistribute it and/or modify it under | ||
3705 | 7 | the terms of the GNU Affero General Public License version 3, as published by | ||
3706 | 8 | the Free Software Foundation. | ||
3707 | 9 | |||
3708 | 10 | This program is distributed in the hope that it will be useful, but WITHOUT | ||
3709 | 11 | ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | ||
3710 | 12 | SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero | ||
3711 | 13 | General Public License for more details. | ||
3712 | 14 | |||
3713 | 15 | You should have received a copy of the GNU Affero General Public License along | ||
3714 | 16 | with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3715 | 17 | */ | ||
3716 | 18 | |||
3717 | 19 | 'use strict'; | ||
3718 | 20 | |||
3719 | 21 | describe('bundle module', function() { | ||
3720 | 22 | var db, juju, models, utils, views, Y, bundleModule; | ||
3721 | 23 | var bundle, container, fakeStore; | ||
3722 | 24 | |||
3723 | 25 | before(function(done) { | ||
3724 | 26 | Y = YUI(GlobalConfig).use([ | ||
3725 | 27 | 'juju-topology', | ||
3726 | 28 | 'juju-view-bundle', | ||
3727 | 29 | 'juju-charm-store', | ||
3728 | 30 | 'juju-models', | ||
3729 | 31 | 'juju-tests-utils' | ||
3730 | 32 | ], | ||
3731 | 33 | function(Y) { | ||
3732 | 34 | juju = Y.namespace('juju'); | ||
3733 | 35 | models = Y.namespace('juju.models'); | ||
3734 | 36 | utils = Y.namespace('juju-tests.utils'); | ||
3735 | 37 | views = Y.namespace('juju.views'); | ||
3736 | 38 | done(); | ||
3737 | 39 | }); | ||
3738 | 40 | }); | ||
3739 | 41 | |||
3740 | 42 | beforeEach(function() { | ||
3741 | 43 | container = utils.makeContainer(); | ||
3742 | 44 | db = new models.Database(); | ||
3743 | 45 | }); | ||
3744 | 46 | |||
3745 | 47 | afterEach(function() { | ||
3746 | 48 | if (container) { container.remove(true); } | ||
3747 | 49 | if (bundle) { bundle.destroy(); } | ||
3748 | 50 | |||
3749 | 51 | }); | ||
3750 | 52 | |||
3751 | 53 | function promiseBundle() { | ||
3752 | 54 | db.environment.set('defaultSeries', 'precise'); | ||
3753 | 55 | var fakeStore = utils.makeFakeStore(db.charms); | ||
3754 | 56 | fakeStore.iconpath = function() { return 'fake.svg'; }; | ||
3755 | 57 | |||
3756 | 58 | return db.importDeployer( | ||
3757 | 59 | jsyaml.safeLoad(utils.loadFixture('data/wp-deployer.yaml')), | ||
3758 | 60 | fakeStore, {useGhost: true, targetBundle: 'wordpress-prod'}) | ||
3759 | 61 | .then(function() { | ||
3760 | 62 | bundle = new views.BundleTopology({ | ||
3761 | 63 | db: db, | ||
3762 | 64 | container: container, | ||
3763 | 65 | store: fakeStore | ||
3764 | 66 | }).render(); | ||
3765 | 67 | bundleModule = bundle.topology.modules.BundleModule; | ||
3766 | 68 | bundleModule.set('useTransitions', false); | ||
3767 | 69 | return bundle; | ||
3768 | 70 | }); | ||
3769 | 71 | } | ||
3770 | 72 | |||
3771 | 73 | it('should create a proper service for each model', function(done) { | ||
3772 | 74 | promiseBundle() | ||
3773 | 75 | .then(function(bundle) { | ||
3774 | 76 | // The size of the element should reflect the passed in params | ||
3775 | 77 | var svg = d3.select(container.getDOMNode()).select('svg'); | ||
3776 | 78 | assert.equal(svg.attr('width'), 640); | ||
3777 | 79 | assert.equal(svg.attr('height'), 480); | ||
3778 | 80 | |||
3779 | 81 | // We should have the two rendered services | ||
3780 | 82 | assert.equal(container.all('.service').size(), 2); | ||
3781 | 83 | assert.deepEqual(container.all('tspan.name').get('text'), | ||
3782 | 84 | ['mysql', 'wordpress']); | ||
3783 | 85 | var service = svg.select('.service'); | ||
3784 | 86 | assert.equal(service.attr('width'), '96'); | ||
3785 | 87 | assert.equal(service.attr('transform'), 'translate(115,89)'); | ||
3786 | 88 | |||
3787 | 89 | |||
3788 | 90 | container.remove(true); | ||
3789 | 91 | bundle.destroy(); | ||
3790 | 92 | done(); | ||
3791 | 93 | }).then(undefined, done); | ||
3792 | 94 | |||
3793 | 95 | }); | ||
3794 | 96 | |||
3795 | 97 | }); | ||
3796 | 98 | |||
3797 | 0 | 99 | ||
3798 | === modified file 'test/test_charm_panel.js' | |||
3799 | --- test/test_charm_panel.js 2013-08-12 14:58:23 +0000 | |||
3800 | +++ test/test_charm_panel.js 2013-08-30 19:48:17 +0000 | |||
3801 | @@ -118,7 +118,8 @@ | |||
3802 | 118 | serviceName = 'membase'; | 118 | serviceName = 'membase'; |
3803 | 119 | // Mock the relevant environment calls. | 119 | // Mock the relevant environment calls. |
3804 | 120 | env = { | 120 | env = { |
3806 | 121 | deploy: function(url, service, config, config_raw, units, callback) { | 121 | deploy: function(url, service, config, config_raw, units, |
3807 | 122 | constraints, callback) { | ||
3808 | 122 | callback({err: false}); | 123 | callback({err: false}); |
3809 | 123 | }, | 124 | }, |
3810 | 124 | update_annotations: function(service, type, annotations, callback) { | 125 | update_annotations: function(service, type, annotations, callback) { |
3811 | 125 | 126 | ||
3812 | === modified file 'test/test_charm_store.js' | |||
3813 | --- test/test_charm_store.js 2013-08-21 16:02:10 +0000 | |||
3814 | +++ test/test_charm_store.js 2013-08-30 19:48:17 +0000 | |||
3815 | @@ -21,15 +21,17 @@ | |||
3816 | 21 | (function() { | 21 | (function() { |
3817 | 22 | 22 | ||
3818 | 23 | describe('juju Charmworld2 api', function() { | 23 | describe('juju Charmworld2 api', function() { |
3820 | 24 | var Y, models, conn, env, app, container, data, juju; | 24 | var Y, models, conn, env, app, container, data, juju, utils; |
3821 | 25 | 25 | ||
3822 | 26 | before(function(done) { | 26 | before(function(done) { |
3823 | 27 | Y = YUI(GlobalConfig).use( | 27 | Y = YUI(GlobalConfig).use( |
3824 | 28 | 'datasource-local', 'json-stringify', 'juju-charm-store', | 28 | 'datasource-local', 'json-stringify', 'juju-charm-store', |
3825 | 29 | 'datasource-io', 'io', 'array-extras', 'juju-charm-models', | 29 | 'datasource-io', 'io', 'array-extras', 'juju-charm-models', |
3826 | 30 | 'juju-tests-utils', | ||
3827 | 30 | function(Y) { | 31 | function(Y) { |
3828 | 31 | juju = Y.namespace('juju'); | 32 | juju = Y.namespace('juju'); |
3829 | 32 | models = Y.namespace('juju.models'); | 33 | models = Y.namespace('juju.models'); |
3830 | 34 | utils = Y.namespace('juju-tests').utils; | ||
3831 | 33 | done(); | 35 | done(); |
3832 | 34 | }); | 36 | }); |
3833 | 35 | }); | 37 | }); |
3834 | @@ -210,6 +212,33 @@ | |||
3835 | 210 | assert.equal(api.normalizeCharmId('cs:precise/wordpress-10'), | 212 | assert.equal(api.normalizeCharmId('cs:precise/wordpress-10'), |
3836 | 211 | 'precise/wordpress-10'); | 213 | 'precise/wordpress-10'); |
3837 | 212 | }); | 214 | }); |
3838 | 215 | |||
3839 | 216 | it('finds upgrades for charms - upgrade available', function(done) { | ||
3840 | 217 | var store = utils.makeFakeStore(); | ||
3841 | 218 | var charm = new models.BrowserCharm({url: 'cs:precise/wordpress-10'}); | ||
3842 | 219 | store.promiseUpgradeAvailability(charm) | ||
3843 | 220 | .then(function(upgrade) { | ||
3844 | 221 | assert.equal(upgrade, 'precise/wordpress-15'); | ||
3845 | 222 | done(); | ||
3846 | 223 | }, function(error) { | ||
3847 | 224 | assert.isTrue(false, 'We should not get here'); | ||
3848 | 225 | done(); | ||
3849 | 226 | }); | ||
3850 | 227 | }); | ||
3851 | 228 | |||
3852 | 229 | it('finds upgrades for charms - no upgrade available', function(done) { | ||
3853 | 230 | var store = utils.makeFakeStore(); | ||
3854 | 231 | var charm = new models.BrowserCharm({url: 'cs:precise/wordpress-15'}); | ||
3855 | 232 | store.promiseUpgradeAvailability(charm) | ||
3856 | 233 | .then(function(upgrade) { | ||
3857 | 234 | assert.isUndefined(upgrade); | ||
3858 | 235 | done(); | ||
3859 | 236 | }, function(error) { | ||
3860 | 237 | assert.isTrue(false, 'We should not get here'); | ||
3861 | 238 | done(); | ||
3862 | 239 | }); | ||
3863 | 240 | }); | ||
3864 | 241 | |||
3865 | 213 | }); | 242 | }); |
3866 | 214 | 243 | ||
3867 | 215 | })(); | 244 | })(); |
3868 | 216 | 245 | ||
3869 | === modified file 'test/test_charm_view.js' | |||
3870 | --- test/test_charm_view.js 2013-07-26 22:05:56 +0000 | |||
3871 | +++ test/test_charm_view.js 2013-08-30 19:48:17 +0000 | |||
3872 | @@ -138,14 +138,12 @@ | |||
3873 | 138 | }); | 138 | }); |
3874 | 139 | var deployInput = charmView.get('container').one('#charm-deploy'); | 139 | var deployInput = charmView.get('container').one('#charm-deploy'); |
3875 | 140 | deployInput.after('click', function() { | 140 | deployInput.after('click', function() { |
3879 | 141 | var msg = conn.last_message(), | 141 | var msg = conn.last_message(); |
3877 | 142 | charm = charmResults.charm, | ||
3878 | 143 | expected = charm.series + '/' + charm.name; | ||
3880 | 144 | // Ensure the websocket received the `deploy` message. | 142 | // Ensure the websocket received the `deploy` message. |
3883 | 145 | msg.op.should.equal('deploy'); | 143 | assert.equal('ServiceDeploy', msg.Request); |
3884 | 146 | msg.charm_url.should.contain(expected); | 144 | assert.equal(charmResults.charm.id, msg.Params.CharmUrl); |
3885 | 147 | // A click to the deploy button redirects to the root page. | 145 | // A click to the deploy button redirects to the root page. |
3887 | 148 | redirected.should.equal(true); | 146 | assert.isTrue(redirected); |
3888 | 149 | done(); | 147 | done(); |
3889 | 150 | }); | 148 | }); |
3890 | 151 | deployInput.simulate('click'); | 149 | deployInput.simulate('click'); |
3891 | @@ -164,8 +162,8 @@ | |||
3892 | 164 | // Assertions are in a callback, so set them up first. | 162 | // Assertions are in a callback, so set them up first. |
3893 | 165 | deployButton.after('click', function() { | 163 | deployButton.after('click', function() { |
3894 | 166 | var msg = conn.last_message(); | 164 | var msg = conn.last_message(); |
3897 | 167 | assert.equal(msg.op, 'deploy'); | 165 | assert.equal('ServiceDeploy', msg.Request); |
3898 | 168 | assert.equal(msg.service_name, serviceName); | 166 | assert.equal(serviceName, msg.Params.ServiceName); |
3899 | 169 | done(); | 167 | done(); |
3900 | 170 | }); | 168 | }); |
3901 | 171 | var serviceNameField = container.one('#service-name'); | 169 | var serviceNameField = container.one('#service-name'); |
3902 | @@ -188,9 +186,10 @@ | |||
3903 | 188 | // Assertions are in a callback, so set them up first. | 186 | // Assertions are in a callback, so set them up first. |
3904 | 189 | deployButton.after('click', function() { | 187 | deployButton.after('click', function() { |
3905 | 190 | var msg = conn.last_message(); | 188 | var msg = conn.last_message(); |
3909 | 191 | assert.equal(msg.op, 'deploy'); | 189 | assert.equal('ServiceDeploy', msg.Request); |
3910 | 192 | assert.property(msg.config, 'option0'); | 190 | var config = msg.Params.Config; |
3911 | 193 | assert.equal(msg.config.option0, option0Value); | 191 | assert.property(config, 'option0'); |
3912 | 192 | assert.equal(option0Value, config.option0); | ||
3913 | 194 | done(); | 193 | done(); |
3914 | 195 | }); | 194 | }); |
3915 | 196 | container.one('#input-option0').set('value', option0Value); | 195 | container.one('#input-option0').set('value', option0Value); |
3916 | @@ -225,7 +224,7 @@ | |||
3917 | 225 | // Assertions are in a callback, so set them up first. | 224 | // Assertions are in a callback, so set them up first. |
3918 | 226 | deployButton.after('click', function() { | 225 | deployButton.after('click', function() { |
3919 | 227 | var msg = conn.last_message(); | 226 | var msg = conn.last_message(); |
3921 | 228 | assert.equal(msg.op, 'deploy'); | 227 | assert.equal('ServiceDeploy', msg.Request); |
3922 | 229 | done(); | 228 | done(); |
3923 | 230 | }); | 229 | }); |
3924 | 231 | deployButton.simulate('click'); | 230 | deployButton.simulate('click'); |
3925 | 232 | 231 | ||
3926 | === modified file 'test/test_endpoints.js' | |||
3927 | --- test/test_endpoints.js 2013-08-09 14:44:16 +0000 | |||
3928 | +++ test/test_endpoints.js 2013-08-30 19:48:17 +0000 | |||
3929 | @@ -378,6 +378,7 @@ | |||
3930 | 378 | 378 | ||
3931 | 379 | it('should update endpoints map when non-pending services are added', | 379 | it('should update endpoints map when non-pending services are added', |
3932 | 380 | function(done) { | 380 | function(done) { |
3933 | 381 | var store = utils.makeFakeStore(); | ||
3934 | 381 | var service_name = 'wordpress'; | 382 | var service_name = 'wordpress'; |
3935 | 382 | var charm_id = 'cs:precise/wordpress-2'; | 383 | var charm_id = 'cs:precise/wordpress-2'; |
3936 | 383 | app.db.charms.add({id: charm_id}); | 384 | app.db.charms.add({id: charm_id}); |
3937 | @@ -388,6 +389,7 @@ | |||
3938 | 388 | id: service_name, | 389 | id: service_name, |
3939 | 389 | pending: true, | 390 | pending: true, |
3940 | 390 | loaded: true, | 391 | loaded: true, |
3941 | 392 | store: store, | ||
3942 | 391 | charm: charm_id}); | 393 | charm: charm_id}); |
3943 | 392 | 394 | ||
3944 | 393 | controller.on('endpointMapAdded', function() { | 395 | controller.on('endpointMapAdded', function() { |
3945 | @@ -535,7 +537,13 @@ | |||
3946 | 535 | }); | 537 | }); |
3947 | 536 | }); | 538 | }); |
3948 | 537 | 539 | ||
3950 | 538 | it('should not call get_service when a pending service is added', | 540 | // Ensure the last message in the connection is a ServiceGet request. |
3951 | 541 | var assertServiceGetCalled = function() { | ||
3952 | 542 | assert.equal(1, conn.messages.length); | ||
3953 | 543 | assert.equal('ServiceGet', conn.last_message().Request); | ||
3954 | 544 | }; | ||
3955 | 545 | |||
3956 | 546 | it('should not call ServiceGet when a pending service is added', | ||
3957 | 539 | function() { | 547 | function() { |
3958 | 540 | var charm_id = 'cs:precise/wordpress-2'; | 548 | var charm_id = 'cs:precise/wordpress-2'; |
3959 | 541 | app.db.services.add({ | 549 | app.db.services.add({ |
3960 | @@ -545,7 +553,7 @@ | |||
3961 | 545 | assert.equal(0, conn.messages.length); | 553 | assert.equal(0, conn.messages.length); |
3962 | 546 | }); | 554 | }); |
3963 | 547 | 555 | ||
3965 | 548 | it('should call get_service when non-pending services are added', | 556 | it('should call ServiceGet when non-pending services are added', |
3966 | 549 | function() { | 557 | function() { |
3967 | 550 | var service_name = 'wordpress'; | 558 | var service_name = 'wordpress'; |
3968 | 551 | var charm_id = 'cs:precise/wordpress-2'; | 559 | var charm_id = 'cs:precise/wordpress-2'; |
3969 | @@ -558,11 +566,10 @@ | |||
3970 | 558 | charm: charm_id}); | 566 | charm: charm_id}); |
3971 | 559 | var svc = app.db.services.getById(service_name); | 567 | var svc = app.db.services.getById(service_name); |
3972 | 560 | svc.set('pending', false); | 568 | svc.set('pending', false); |
3975 | 561 | assert.equal(1, conn.messages.length); | 569 | assertServiceGetCalled(); |
3974 | 562 | assert.equal('get_service', conn.last_message().op); | ||
3976 | 563 | }); | 570 | }); |
3977 | 564 | 571 | ||
3979 | 565 | it('should call get_service when a service\'s charm changes', function() { | 572 | it('should call ServiceGet when a service\'s charm changes', function() { |
3980 | 566 | var service_name = 'wordpress'; | 573 | var service_name = 'wordpress'; |
3981 | 567 | var charm_id = 'cs:precise/wordpress-2'; | 574 | var charm_id = 'cs:precise/wordpress-2'; |
3982 | 568 | var charm = app.db.charms.add({id: charm_id}); | 575 | var charm = app.db.charms.add({id: charm_id}); |
3983 | @@ -572,15 +579,13 @@ | |||
3984 | 572 | id: service_name, | 579 | id: service_name, |
3985 | 573 | pending: false, | 580 | pending: false, |
3986 | 574 | charm: charm_id}); | 581 | charm: charm_id}); |
3989 | 575 | assert.equal(1, conn.messages.length); | 582 | assertServiceGetCalled(); |
3988 | 576 | assert.equal('get_service', conn.last_message().op); | ||
3990 | 577 | var svc = app.db.services.getById(service_name); | 583 | var svc = app.db.services.getById(service_name); |
3991 | 578 | charm_id = 'cs:precise/wordpress-3'; | 584 | charm_id = 'cs:precise/wordpress-3'; |
3992 | 579 | var charm2 = app.db.charms.add({id: charm_id, loaded: true}); | 585 | var charm2 = app.db.charms.add({id: charm_id, loaded: true}); |
3993 | 580 | destroyMe.push(charm2); | 586 | destroyMe.push(charm2); |
3994 | 581 | svc.set('charm', charm_id); | 587 | svc.set('charm', charm_id); |
3997 | 582 | assert.equal(1, conn.messages.length); | 588 | assertServiceGetCalled(); |
3996 | 583 | assert.equal('get_service', conn.last_message().op); | ||
3998 | 584 | }); | 589 | }); |
3999 | 585 | 590 | ||
4000 | 586 | }); | 591 | }); |
4001 | 587 | 592 | ||
4002 | === modified file 'test/test_env.js' | |||
4003 | --- test/test_env.js 2013-07-25 16:59:30 +0000 | |||
4004 | +++ test/test_env.js 2013-08-30 19:48:17 +0000 | |||
4005 | @@ -43,12 +43,12 @@ | |||
4006 | 43 | 43 | ||
4007 | 44 | it('returns the default env if none is specified', function() { | 44 | it('returns the default env if none is specified', function() { |
4008 | 45 | var env = juju.newEnvironment({}); | 45 | var env = juju.newEnvironment({}); |
4010 | 46 | assert.equal('python-env', env.name); | 46 | assert.equal('go-env', env.name); |
4011 | 47 | }); | 47 | }); |
4012 | 48 | 48 | ||
4013 | 49 | it('returns the default env if an invalid one is specified', function() { | 49 | it('returns the default env if an invalid one is specified', function() { |
4014 | 50 | var env = juju.newEnvironment({}, 'invalid-api-backend'); | 50 | var env = juju.newEnvironment({}, 'invalid-api-backend'); |
4016 | 51 | assert.equal('python-env', env.name); | 51 | assert.equal('go-env', env.name); |
4017 | 52 | }); | 52 | }); |
4018 | 53 | 53 | ||
4019 | 54 | it('sets up the env using the provided options', function() { | 54 | it('sets up the env using the provided options', function() { |
4020 | 55 | 55 | ||
4021 | === modified file 'test/test_env_go.js' | |||
4022 | --- test/test_env_go.js 2013-06-12 18:11:05 +0000 | |||
4023 | +++ test/test_env_go.js 2013-08-30 19:48:17 +0000 | |||
4024 | @@ -55,6 +55,7 @@ | |||
4025 | 55 | 55 | ||
4026 | 56 | }); | 56 | }); |
4027 | 57 | 57 | ||
4028 | 58 | |||
4029 | 58 | describe('Go Juju JSON replacer', function() { | 59 | describe('Go Juju JSON replacer', function() { |
4030 | 59 | var cleanUpJSON, Y; | 60 | var cleanUpJSON, Y; |
4031 | 60 | 61 | ||
4032 | @@ -417,6 +418,7 @@ | |||
4033 | 417 | Request: 'ServiceDeploy', | 418 | Request: 'ServiceDeploy', |
4034 | 418 | Params: { | 419 | Params: { |
4035 | 419 | Config: {}, | 420 | Config: {}, |
4036 | 421 | Constraints: {}, | ||
4037 | 420 | CharmUrl: 'precise/mysql' | 422 | CharmUrl: 'precise/mysql' |
4038 | 421 | }, | 423 | }, |
4039 | 422 | RequestId: 1 | 424 | RequestId: 1 |
4040 | @@ -432,6 +434,7 @@ | |||
4041 | 432 | Params: { | 434 | Params: { |
4042 | 433 | // Configuration values are sent as strings. | 435 | // Configuration values are sent as strings. |
4043 | 434 | Config: {debug: 'true', logo: 'example.com/mylogo.png'}, | 436 | Config: {debug: 'true', logo: 'example.com/mylogo.png'}, |
4044 | 437 | Constraints: {}, | ||
4045 | 435 | CharmUrl: 'precise/mediawiki' | 438 | CharmUrl: 'precise/mediawiki' |
4046 | 436 | }, | 439 | }, |
4047 | 437 | RequestId: 1 | 440 | RequestId: 1 |
4048 | @@ -450,6 +453,7 @@ | |||
4049 | 450 | Request: 'ServiceDeploy', | 453 | Request: 'ServiceDeploy', |
4050 | 451 | Params: { | 454 | Params: { |
4051 | 452 | Config: {}, | 455 | Config: {}, |
4052 | 456 | Constraints: {}, | ||
4053 | 453 | ConfigYAML: config_raw, | 457 | ConfigYAML: config_raw, |
4054 | 454 | CharmUrl: 'precise/mysql' | 458 | CharmUrl: 'precise/mysql' |
4055 | 455 | }, | 459 | }, |
4056 | @@ -460,15 +464,29 @@ | |||
4057 | 460 | assert.deepEqual(expected, msg); | 464 | assert.deepEqual(expected, msg); |
4058 | 461 | }); | 465 | }); |
4059 | 462 | 466 | ||
4060 | 467 | it('successfully deploys a service with constraints', function() { | ||
4061 | 468 | var constraints = { | ||
4062 | 469 | 'cpu-cores': 1, | ||
4063 | 470 | 'cpu-power': 0, | ||
4064 | 471 | 'mem': '512M', | ||
4065 | 472 | 'arch': 'i386' | ||
4066 | 473 | }; | ||
4067 | 474 | env.deploy('precise/mediawiki', null, null, null, 1, constraints); | ||
4068 | 475 | msg = conn.last_message(); | ||
4069 | 476 | assert.deepEqual(msg.Params.Constraints, constraints); | ||
4070 | 477 | }); | ||
4071 | 478 | |||
4072 | 463 | it('successfully deploys a service storing charm data', function() { | 479 | it('successfully deploys a service storing charm data', function() { |
4073 | 464 | var charm_url; | 480 | var charm_url; |
4074 | 465 | var err; | 481 | var err; |
4075 | 466 | var service_name; | 482 | var service_name; |
4081 | 467 | env.deploy('precise/mysql', 'mysql', null, null, null, function(data) { | 483 | env.deploy( |
4082 | 468 | charm_url = data.charm_url; | 484 | 'precise/mysql', 'mysql', null, null, null, null, function(data) { |
4083 | 469 | err = data.err; | 485 | charm_url = data.charm_url; |
4084 | 470 | service_name = data.service_name; | 486 | err = data.err; |
4085 | 471 | }); | 487 | service_name = data.service_name; |
4086 | 488 | } | ||
4087 | 489 | ); | ||
4088 | 472 | // Mimic response. | 490 | // Mimic response. |
4089 | 473 | conn.msg({ | 491 | conn.msg({ |
4090 | 474 | RequestId: 1, | 492 | RequestId: 1, |
4091 | @@ -481,9 +499,11 @@ | |||
4092 | 481 | 499 | ||
4093 | 482 | it('handles failed service deploy', function() { | 500 | it('handles failed service deploy', function() { |
4094 | 483 | var err; | 501 | var err; |
4098 | 484 | env.deploy('precise/mysql', 'mysql', null, null, null, function(data) { | 502 | env.deploy( |
4099 | 485 | err = data.err; | 503 | 'precise/mysql', 'mysql', null, null, null, null, function(data) { |
4100 | 486 | }); | 504 | err = data.err; |
4101 | 505 | } | ||
4102 | 506 | ); | ||
4103 | 487 | // Mimic response. | 507 | // Mimic response. |
4104 | 488 | conn.msg({ | 508 | conn.msg({ |
4105 | 489 | RequestId: 1, | 509 | RequestId: 1, |
4106 | 490 | 510 | ||
4107 | === modified file 'test/test_env_python.js' | |||
4108 | --- test/test_env_python.js 2013-05-17 14:51:05 +0000 | |||
4109 | +++ test/test_env_python.js 2013-08-30 19:48:17 +0000 | |||
4110 | @@ -37,7 +37,7 @@ | |||
4111 | 37 | testUtils = Y.namespace('juju-tests.utils'); | 37 | testUtils = Y.namespace('juju-tests.utils'); |
4112 | 38 | conn = new testUtils.SocketStub(); | 38 | conn = new testUtils.SocketStub(); |
4113 | 39 | juju = Y.namespace('juju'); | 39 | juju = Y.namespace('juju'); |
4115 | 40 | env = juju.newEnvironment({conn: conn}); | 40 | env = juju.newEnvironment({conn: conn}, 'python'); |
4116 | 41 | env.connect(); | 41 | env.connect(); |
4117 | 42 | conn.open(); | 42 | conn.open(); |
4118 | 43 | done(); | 43 | done(); |
4119 | @@ -70,6 +70,17 @@ | |||
4120 | 70 | msg.config_raw.should.equal(config_raw); | 70 | msg.config_raw.should.equal(config_raw); |
4121 | 71 | }); | 71 | }); |
4122 | 72 | 72 | ||
4123 | 73 | it('successfully deploys a service with constraints', function() { | ||
4124 | 74 | var constraints = { | ||
4125 | 75 | 'cpu': 1, | ||
4126 | 76 | 'mem': '512M', | ||
4127 | 77 | 'arch': 'i386' | ||
4128 | 78 | }; | ||
4129 | 79 | env.deploy('precise/mysql', null, null, null, 1, constraints); | ||
4130 | 80 | msg = conn.last_message(); | ||
4131 | 81 | assert.deepEqual(msg.constraints, constraints); | ||
4132 | 82 | }); | ||
4133 | 83 | |||
4134 | 73 | it('can add a unit', function() { | 84 | it('can add a unit', function() { |
4135 | 74 | env.add_unit('mysql', 3); | 85 | env.add_unit('mysql', 3); |
4136 | 75 | msg = conn.last_message(); | 86 | msg = conn.last_message(); |
4137 | @@ -442,7 +453,7 @@ | |||
4138 | 442 | 453 | ||
4139 | 443 | it('denies deploying a charm if the GUI is read-only', function() { | 454 | it('denies deploying a charm if the GUI is read-only', function() { |
4140 | 444 | assertOperationDenied( | 455 | assertOperationDenied( |
4142 | 445 | 'deploy', ['cs:precise/haproxy', 'haproxy', {}, null, 3]); | 456 | 'deploy', ['cs:precise/haproxy', 'haproxy', {}, null, 3, null]); |
4143 | 446 | }); | 457 | }); |
4144 | 447 | 458 | ||
4145 | 448 | it('denies exposing a service if the GUI is read-only', function() { | 459 | it('denies exposing a service if the GUI is read-only', function() { |
4146 | 449 | 460 | ||
4147 | === modified file 'test/test_fakebackend.js' | |||
4148 | --- test/test_fakebackend.js 2013-07-30 21:00:47 +0000 | |||
4149 | +++ test/test_fakebackend.js 2013-08-30 19:48:17 +0000 | |||
4150 | @@ -134,7 +134,9 @@ | |||
4151 | 134 | pending: false, | 134 | pending: false, |
4152 | 135 | life: 'alive', | 135 | life: 'alive', |
4153 | 136 | subordinate: false, | 136 | subordinate: false, |
4155 | 137 | unit_count: undefined | 137 | unit_count: undefined, |
4156 | 138 | upgrade_available: false, | ||
4157 | 139 | upgrade_to: undefined | ||
4158 | 138 | }); | 140 | }); |
4159 | 139 | var units = fakebackend.db.units.get_units_for_service(service); | 141 | var units = fakebackend.db.units.get_units_for_service(service); |
4160 | 140 | assert.lengthOf(units, 1); | 142 | assert.lengthOf(units, 1); |
4161 | @@ -143,6 +145,30 @@ | |||
4162 | 143 | assert.equal(units[0].service, 'wordpress'); | 145 | assert.equal(units[0].service, 'wordpress'); |
4163 | 144 | }); | 146 | }); |
4164 | 145 | 147 | ||
4165 | 148 | it('deploys a charm with constraints', function() { | ||
4166 | 149 | var options = { | ||
4167 | 150 | constraints: { | ||
4168 | 151 | cpu: 1, | ||
4169 | 152 | mem: '4G', | ||
4170 | 153 | arch: 'i386' | ||
4171 | 154 | } | ||
4172 | 155 | }; | ||
4173 | 156 | assert.isNull( | ||
4174 | 157 | fakebackend.db.charms.getById('cs:precise/wordpress-15')); | ||
4175 | 158 | fakebackend.deploy('cs:precise/wordpress-15', callback, options); | ||
4176 | 159 | var service = fakebackend.db.services.getById('wordpress'); | ||
4177 | 160 | assert.isObject( | ||
4178 | 161 | service, | ||
4179 | 162 | 'Null returend when a service was expected.'); | ||
4180 | 163 | assert.strictEqual(service, result.service); | ||
4181 | 164 | var attrs = service.getAttrs(); | ||
4182 | 165 | var deployedConstraints = attrs.constraints; | ||
4183 | 166 | assert.deepEqual( | ||
4184 | 167 | options.constraints, | ||
4185 | 168 | deployedConstraints | ||
4186 | 169 | ); | ||
4187 | 170 | }); | ||
4188 | 171 | |||
4189 | 146 | it('rejects names that duplicate an existing service', function() { | 172 | it('rejects names that duplicate an existing service', function() { |
4190 | 147 | fakebackend.deploy('cs:precise/wordpress-15', callback); | 173 | fakebackend.deploy('cs:precise/wordpress-15', callback); |
4191 | 148 | assert.isUndefined(result.error); | 174 | assert.isUndefined(result.error); |
4192 | 149 | 175 | ||
4193 | === modified file 'test/test_ghost_inspector.js' | |||
4194 | --- test/test_ghost_inspector.js 2013-08-15 00:40:51 +0000 | |||
4195 | +++ test/test_ghost_inspector.js 2013-08-30 19:48:17 +0000 | |||
4196 | @@ -70,8 +70,8 @@ | |||
4197 | 70 | service = db.services.ghostService(charm); | 70 | service = db.services.ghostService(charm); |
4198 | 71 | 71 | ||
4199 | 72 | var fakeStore = new Y.juju.Charmworld2({}); | 72 | var fakeStore = new Y.juju.Charmworld2({}); |
4202 | 73 | fakeStore.iconpath = function() { | 73 | fakeStore.iconpath = function(id) { |
4203 | 74 | return 'charm icon url'; | 74 | return '/icon/' + id; |
4204 | 75 | }; | 75 | }; |
4205 | 76 | 76 | ||
4206 | 77 | view = new jujuViews.environment({ | 77 | view = new jujuViews.environment({ |
4207 | @@ -118,6 +118,14 @@ | |||
4208 | 118 | serviceNameInput.set('value', 'foo'); | 118 | serviceNameInput.set('value', 'foo'); |
4209 | 119 | }); | 119 | }); |
4210 | 120 | 120 | ||
4211 | 121 | it('displays the charms icon when rendered', function() { | ||
4212 | 122 | inspector = setUpInspector(); | ||
4213 | 123 | var icon = container.one('.icon img'); | ||
4214 | 124 | |||
4215 | 125 | // The icon url is from the fakestore we manually defined. | ||
4216 | 126 | assert.equal(icon.getAttribute('src'), '/icon/cs:precise/mediawiki-8'); | ||
4217 | 127 | }); | ||
4218 | 128 | |||
4219 | 121 | it('deploys a service with the specified unit count & config', function() { | 129 | it('deploys a service with the specified unit count & config', function() { |
4220 | 122 | inspector = setUpInspector(); | 130 | inspector = setUpInspector(); |
4221 | 123 | env.connect(); | 131 | env.connect(); |
4222 | @@ -129,16 +137,74 @@ | |||
4223 | 129 | vmContainer.one('.viewlet-manager-footer button.confirm').simulate('click'); | 137 | vmContainer.one('.viewlet-manager-footer button.confirm').simulate('click'); |
4224 | 130 | 138 | ||
4225 | 131 | var message = env.ws.last_message(); | 139 | var message = env.ws.last_message(); |
4230 | 132 | assert.equal(message.num_units, numUnits); | 140 | var params = message.Params; |
4231 | 133 | assert.equal(message.op, 'deploy'); | 141 | var config = { |
4228 | 134 | assert.equal(message.service_name, 'mediawiki'); | ||
4229 | 135 | assert.deepEqual(message.config, { | ||
4232 | 136 | admins: '', | 142 | admins: '', |
4234 | 137 | debug: false, | 143 | debug: 'false', |
4235 | 138 | logo: '', | 144 | logo: '', |
4236 | 139 | name: 'foo', | 145 | name: 'foo', |
4237 | 140 | skin: 'vector' | 146 | skin: 'vector' |
4239 | 141 | }); | 147 | }; |
4240 | 148 | assert.equal('ServiceDeploy', message.Request); | ||
4241 | 149 | assert.equal('mediawiki', params.ServiceName); | ||
4242 | 150 | assert.equal(numUnits, params.NumUnits); | ||
4243 | 151 | assert.deepEqual(config, params.Config); | ||
4244 | 152 | }); | ||
4245 | 153 | |||
4246 | 154 | it('presents the contraints to the user in python env', function() { | ||
4247 | 155 | // Create our own env to make sure we know which backend we're creating it | ||
4248 | 156 | // against. | ||
4249 | 157 | env.destroy(); | ||
4250 | 158 | env = juju.newEnvironment({conn: conn}, 'python'); | ||
4251 | 159 | inspector = setUpInspector(); | ||
4252 | 160 | var constraintsNode = container.all('.service-constraints'); | ||
4253 | 161 | assert.equal(constraintsNode.size(), 1); | ||
4254 | 162 | |||
4255 | 163 | var inputNodes = container.all('.service-constraints input'); | ||
4256 | 164 | assert.equal(inputNodes.size(), 3); | ||
4257 | 165 | }); | ||
4258 | 166 | |||
4259 | 167 | it('presents the contraints to the user in go env', function() { | ||
4260 | 168 | // Create our own env to make sure we know which backend we're creating it | ||
4261 | 169 | // against. | ||
4262 | 170 | env.destroy(); | ||
4263 | 171 | env = juju.newEnvironment({conn: conn}, 'go'); | ||
4264 | 172 | inspector = setUpInspector(); | ||
4265 | 173 | var constraintsNode = container.all('.service-constraints'); | ||
4266 | 174 | assert.equal(constraintsNode.size(), 1); | ||
4267 | 175 | |||
4268 | 176 | var inputNodes = container.all('.service-constraints input'); | ||
4269 | 177 | assert.equal(inputNodes.size(), 4); | ||
4270 | 178 | }); | ||
4271 | 179 | |||
4272 | 180 | it('deploys with constraints in python env', function() { | ||
4273 | 181 | env.destroy(); | ||
4274 | 182 | env = juju.newEnvironment({conn: conn}, 'python'); | ||
4275 | 183 | inspector = setUpInspector(); | ||
4276 | 184 | env.connect(); | ||
4277 | 185 | var vmContainer = inspector.viewletManager.get('container'); | ||
4278 | 186 | |||
4279 | 187 | vmContainer.one('input[name=cpu]').set('value', 2); | ||
4280 | 188 | // Called the deploy button, but the css if confirm. | ||
4281 | 189 | vmContainer.one('.viewlet-manager-footer button.confirm').simulate('click'); | ||
4282 | 190 | |||
4283 | 191 | var message = env.ws.last_message(); | ||
4284 | 192 | assert.equal(message.constraints.cpu, '2'); | ||
4285 | 193 | }); | ||
4286 | 194 | |||
4287 | 195 | it('deploys with constraints in go env', function() { | ||
4288 | 196 | env.destroy(); | ||
4289 | 197 | env = juju.newEnvironment({conn: conn}, 'go'); | ||
4290 | 198 | inspector = setUpInspector(); | ||
4291 | 199 | env.connect(); | ||
4292 | 200 | var vmContainer = inspector.viewletManager.get('container'); | ||
4293 | 201 | |||
4294 | 202 | vmContainer.one('input[name=cpu-power]').set('value', 2); | ||
4295 | 203 | // Called the deploy button, but the css if confirm. | ||
4296 | 204 | vmContainer.one('.viewlet-manager-footer button.confirm').simulate('click'); | ||
4297 | 205 | |||
4298 | 206 | var message = env.ws.last_message(); | ||
4299 | 207 | assert.equal(message.Params.Constraints['cpu-power'], '2'); | ||
4300 | 142 | }); | 208 | }); |
4301 | 143 | 209 | ||
4302 | 144 | it('disables and resets input fields when \'use default config\' is active', | 210 | it('disables and resets input fields when \'use default config\' is active', |
4303 | 145 | 211 | ||
4304 | === modified file 'test/test_inspector_constraints.js' | |||
4305 | --- test/test_inspector_constraints.js 2013-08-19 21:07:02 +0000 | |||
4306 | +++ test/test_inspector_constraints.js 2013-08-30 19:48:17 +0000 | |||
4307 | @@ -85,14 +85,15 @@ | |||
4308 | 85 | return inspector; | 85 | return inspector; |
4309 | 86 | }; | 86 | }; |
4310 | 87 | 87 | ||
4312 | 88 | // Create a fake response from the API server. | 88 | // Create a fake response from the juju-core API server. |
4313 | 89 | var makeResponse = function(service, error) { | 89 | var makeResponse = function(service, error) { |
4320 | 90 | return { | 90 | var response = {RequestId: 1}; |
4321 | 91 | err: error, | 91 | if (error) { |
4322 | 92 | op: 'set_constraints', | 92 | response.Error = 'bad wolf'; |
4323 | 93 | request_id: 1, | 93 | } else { |
4324 | 94 | service_name: service.get('id') | 94 | response.Response = {}; |
4325 | 95 | }; | 95 | } |
4326 | 96 | return response; | ||
4327 | 96 | }; | 97 | }; |
4328 | 97 | 98 | ||
4329 | 98 | // Retrieve and return the constraints viewlet. | 99 | // Retrieve and return the constraints viewlet. |
4330 | @@ -131,7 +132,7 @@ | |||
4331 | 131 | var constraintDescriptions = viewUtils.constraintDescriptions; | 132 | var constraintDescriptions = viewUtils.constraintDescriptions; |
4332 | 132 | 133 | ||
4333 | 133 | Y.Array.each(env.genericConstraints, function(key) { | 134 | Y.Array.each(env.genericConstraints, function(key) { |
4335 | 134 | var node = container.one('div[for=' + key + '].control-label'); | 135 | var node = container.one('label[for=' + key + ']'); |
4336 | 135 | var expectedTitle = constraintDescriptions[key].title; | 136 | var expectedTitle = constraintDescriptions[key].title; |
4337 | 136 | assert.strictEqual(expectedTitle, node.getHTML()); | 137 | assert.strictEqual(expectedTitle, node.getHTML()); |
4338 | 137 | }); | 138 | }); |
4339 | @@ -156,7 +157,7 @@ | |||
4340 | 156 | }); | 157 | }); |
4341 | 157 | 158 | ||
4342 | 158 | it('can save constraints', function() { | 159 | it('can save constraints', function() { |
4344 | 159 | var expected = {arch: 'amd64', cpu: 'photon', mem: '1 teraflop'}; | 160 | var expected = {arch: 'amd64', 'cpu-power': 100, mem: 4}; |
4345 | 160 | // Change values in the form. | 161 | // Change values in the form. |
4346 | 161 | Y.Object.each(expected, function(value, key) { | 162 | Y.Object.each(expected, function(value, key) { |
4347 | 162 | var node = container.one('input[name=' + key + '].constraint-field'); | 163 | var node = container.one('input[name=' + key + '].constraint-field'); |
4348 | @@ -167,14 +168,10 @@ | |||
4349 | 167 | saveButton.simulate('click'); | 168 | saveButton.simulate('click'); |
4350 | 168 | var lastMessage = env.ws.last_message(); | 169 | var lastMessage = env.ws.last_message(); |
4351 | 169 | // The set_constraint API method is correctly called. | 170 | // The set_constraint API method is correctly called. |
4353 | 170 | assert.equal('set_constraints', lastMessage.op); | 171 | assert.equal('SetServiceConstraints', lastMessage.Request); |
4354 | 171 | // The expected constraints are passed in the API call. | 172 | // The expected constraints are passed in the API call. |
4355 | 172 | var obtained = Object.create(null); | 173 | var obtained = Object.create(null); |
4361 | 173 | Y.Array.each(lastMessage.constraints, function(value) { | 174 | assert.deepEqual(expected, lastMessage.Params.Constraints); |
4357 | 174 | var pair = value.split('='); | ||
4358 | 175 | obtained[pair[0]] = pair[1]; | ||
4359 | 176 | }); | ||
4360 | 177 | assert.deepEqual(expected, obtained); | ||
4362 | 178 | }); | 175 | }); |
4363 | 179 | 176 | ||
4364 | 180 | it('handles error responses from the environment', function() { | 177 | it('handles error responses from the environment', function() { |
4365 | 181 | 178 | ||
4366 | === modified file 'test/test_inspector_overview.js' | |||
4367 | --- test/test_inspector_overview.js 2013-08-19 15:14:33 +0000 | |||
4368 | +++ test/test_inspector_overview.js 2013-08-30 19:48:17 +0000 | |||
4369 | @@ -20,7 +20,9 @@ | |||
4370 | 20 | describe('Inspector Overview', function() { | 20 | describe('Inspector Overview', function() { |
4371 | 21 | 21 | ||
4372 | 22 | var view, service, db, models, utils, juju, env, conn, container, | 22 | var view, service, db, models, utils, juju, env, conn, container, |
4374 | 23 | inspector, Y, jujuViews, ENTER, charmConfig; | 23 | inspector, Y, jujuViews, ENTER, charmConfig, |
4375 | 24 | |||
4376 | 25 | client, backendJuju, state; | ||
4377 | 24 | 26 | ||
4378 | 25 | before(function(done) { | 27 | before(function(done) { |
4379 | 26 | var requires = ['juju-gui', 'juju-views', 'juju-tests-utils', | 28 | var requires = ['juju-gui', 'juju-views', 'juju-tests-utils', |
4380 | @@ -60,6 +62,16 @@ | |||
4381 | 60 | env.destroy(); | 62 | env.destroy(); |
4382 | 61 | container.remove(true); | 63 | container.remove(true); |
4383 | 62 | window.flags = {}; | 64 | window.flags = {}; |
4384 | 65 | |||
4385 | 66 | if (client) { | ||
4386 | 67 | client.destroy(); | ||
4387 | 68 | } | ||
4388 | 69 | if (backendJuju) { | ||
4389 | 70 | backendJuju.destroy(); | ||
4390 | 71 | } | ||
4391 | 72 | if (state) { | ||
4392 | 73 | state.destroy(); | ||
4393 | 74 | } | ||
4394 | 63 | }); | 75 | }); |
4395 | 64 | 76 | ||
4396 | 65 | var setUpInspector = function() { | 77 | var setUpInspector = function() { |
4397 | @@ -78,8 +90,8 @@ | |||
4398 | 78 | ['unit', 'add', {id: 'mediawiki/2', agent_state: 'pending'}] | 90 | ['unit', 'add', {id: 'mediawiki/2', agent_state: 'pending'}] |
4399 | 79 | ]}}); | 91 | ]}}); |
4400 | 80 | var fakeStore = new Y.juju.Charmworld2({}); | 92 | var fakeStore = new Y.juju.Charmworld2({}); |
4403 | 81 | fakeStore.iconpath = function() { | 93 | fakeStore.iconpath = function(id) { |
4404 | 82 | return 'charm icon url'; | 94 | return '/icon/' + id; |
4405 | 83 | }; | 95 | }; |
4406 | 84 | view = new jujuViews.environment({ | 96 | view = new jujuViews.environment({ |
4407 | 85 | container: container, | 97 | container: container, |
4408 | @@ -96,6 +108,14 @@ | |||
4409 | 96 | return inspector; | 108 | return inspector; |
4410 | 97 | }; | 109 | }; |
4411 | 98 | 110 | ||
4412 | 111 | it('should show the proper icon based off the charm model', function() { | ||
4413 | 112 | inspector = setUpInspector(); | ||
4414 | 113 | var icon = container.one('.icon img'); | ||
4415 | 114 | |||
4416 | 115 | // The icon url comes from the fake store and the service charm attribute. | ||
4417 | 116 | assert.equal(icon.getAttribute('src'), '/icon/precise/mediawiki-4'); | ||
4418 | 117 | }); | ||
4419 | 118 | |||
4420 | 99 | it('should start with the proper number of units shown in the text field', | 119 | it('should start with the proper number of units shown in the text field', |
4421 | 100 | function() { | 120 | function() { |
4422 | 101 | inspector = setUpInspector(); | 121 | inspector = setUpInspector(); |
4423 | @@ -110,8 +130,9 @@ | |||
4424 | 110 | control.set('value', 1); | 130 | control.set('value', 1); |
4425 | 111 | control.simulate('keydown', { keyCode: ENTER }); // Simulate Enter. | 131 | control.simulate('keydown', { keyCode: ENTER }); // Simulate Enter. |
4426 | 112 | var message = conn.last_message(); | 132 | var message = conn.last_message(); |
4429 | 113 | message.op.should.equal('remove_units'); | 133 | assert.equal('DestroyServiceUnits', message.Request); |
4430 | 114 | message.unit_names.should.eql(['mediawiki/2', 'mediawiki/1']); | 134 | assert.deepEqual( |
4431 | 135 | ['mediawiki/2', 'mediawiki/1'], message.Params.UnitNames); | ||
4432 | 115 | }); | 136 | }); |
4433 | 116 | 137 | ||
4434 | 117 | it('should not do anything if requested is < 1', | 138 | it('should not do anything if requested is < 1', |
4435 | @@ -132,10 +153,39 @@ | |||
4436 | 132 | control.simulate('keydown', { keyCode: ENTER }); | 153 | control.simulate('keydown', { keyCode: ENTER }); |
4437 | 133 | // confirm the 'please confirm constraints' dialogue | 154 | // confirm the 'please confirm constraints' dialogue |
4438 | 134 | container.one('.confirm-num-units').simulate('click'); | 155 | container.one('.confirm-num-units').simulate('click'); |
4443 | 135 | var message = conn.last_message(); | 156 | assert.equal(container.one('.unit-constraints-confirm') |
4444 | 136 | message.op.should.equal('add_unit'); | 157 | .one('span:first-child') |
4445 | 137 | message.service_name.should.equal('mediawiki'); | 158 | .getHTML(), 'Scale up with the following constraints?'); |
4446 | 138 | message.num_units.should.equal(4); | 159 | var message = conn.last_message(); |
4447 | 160 | assert.equal('AddServiceUnits', message.Request); | ||
4448 | 161 | assert.equal('mediawiki', message.Params.ServiceName); | ||
4449 | 162 | assert.equal(4, message.Params.NumUnits); | ||
4450 | 163 | }); | ||
4451 | 164 | |||
4452 | 165 | it('should set the constraints before deploying any more units', | ||
4453 | 166 | function() { | ||
4454 | 167 | setUpInspector(true); | ||
4455 | 168 | var control = container.one('.num-units-control'); | ||
4456 | 169 | control.set('value', 7); | ||
4457 | 170 | control.simulate('keydown', { keyCode: ENTER }); | ||
4458 | 171 | var editConstraintsButton = container.one('.edit-constraints'); | ||
4459 | 172 | editConstraintsButton.simulate('click'); | ||
4460 | 173 | // It should be hidden after being clicked to display the constraints | ||
4461 | 174 | assert.equal(editConstraintsButton.getStyle('display'), 'none'); | ||
4462 | 175 | var constraintsWrapper = container.one('.editable-constraints'); | ||
4463 | 176 | assert.equal(constraintsWrapper.getStyle('display'), 'block'); | ||
4464 | 177 | var constraints = {arch: 'amd64', 'cpu-cores': 4, mem: 8}; | ||
4465 | 178 | Y.Object.each(constraints, function(value, key) { | ||
4466 | 179 | var node = constraintsWrapper.one('input[name=' + key + ']'); | ||
4467 | 180 | node.set('value', value); | ||
4468 | 181 | }); | ||
4469 | 182 | |||
4470 | 183 | // confirm the 'please confirm constraints' dialogue | ||
4471 | 184 | container.one('.confirm-num-units').simulate('click'); | ||
4472 | 185 | var message = conn.last_message(); | ||
4473 | 186 | assert.equal('SetServiceConstraints', message.Request); | ||
4474 | 187 | assert.equal('mediawiki', message.Params.ServiceName); | ||
4475 | 188 | assert.deepEqual(constraints, message.Params.Constraints); | ||
4476 | 139 | }); | 189 | }); |
4477 | 140 | 190 | ||
4478 | 141 | it('generates a proper statuses object', function() { | 191 | it('generates a proper statuses object', function() { |
4479 | @@ -283,15 +333,14 @@ | |||
4480 | 283 | 333 | ||
4481 | 284 | unit.simulate('click'); | 334 | unit.simulate('click'); |
4482 | 285 | retryButton.simulate('click'); | 335 | retryButton.simulate('click'); |
4483 | 286 | var msg = env.ws.last_message(); | ||
4484 | 287 | 336 | ||
4492 | 288 | assert.deepEqual(msg, { | 337 | var expected = { |
4493 | 289 | op: 'resolved', | 338 | Params: {Retry: false, UnitName: 'mediawiki/7'}, |
4494 | 290 | unit_name: 'mediawiki/7', | 339 | Request: 'Resolved', |
4495 | 291 | relation_name: null, | 340 | RequestId: 1, |
4496 | 292 | retry: false, | 341 | Type: 'Client' |
4497 | 293 | request_id: 1 | 342 | }; |
4498 | 294 | }); | 343 | assert.deepEqual(expected, env.ws.last_message()); |
4499 | 295 | }); | 344 | }); |
4500 | 296 | 345 | ||
4501 | 297 | it('sends the retry command to the env for the selected unit', function() { | 346 | it('sends the retry command to the env for the selected unit', function() { |
4502 | @@ -311,15 +360,14 @@ | |||
4503 | 311 | 360 | ||
4504 | 312 | unit.simulate('click'); | 361 | unit.simulate('click'); |
4505 | 313 | retryButton.simulate('click'); | 362 | retryButton.simulate('click'); |
4506 | 314 | var msg = env.ws.last_message(); | ||
4507 | 315 | 363 | ||
4515 | 316 | assert.deepEqual(msg, { | 364 | var expected = { |
4516 | 317 | op: 'resolved', | 365 | Params: {Retry: true, UnitName: 'mediawiki/7'}, |
4517 | 318 | unit_name: 'mediawiki/7', | 366 | Request: 'Resolved', |
4518 | 319 | relation_name: null, | 367 | RequestId: 1, |
4519 | 320 | retry: true, | 368 | Type: 'Client' |
4520 | 321 | request_id: 1 | 369 | }; |
4521 | 322 | }); | 370 | assert.deepEqual(expected, env.ws.last_message()); |
4522 | 323 | }); | 371 | }); |
4523 | 324 | 372 | ||
4524 | 325 | it('generates the button display map for each unit category', function() { | 373 | it('generates the button display map for each unit category', function() { |
4525 | 326 | 374 | ||
4526 | === modified file 'test/test_inspector_settings.js' | |||
4527 | --- test/test_inspector_settings.js 2013-08-15 15:37:52 +0000 | |||
4528 | +++ test/test_inspector_settings.js 2013-08-30 19:48:17 +0000 | |||
4529 | @@ -307,7 +307,8 @@ | |||
4530 | 307 | input.set('value', 'foo'); | 307 | input.set('value', 'foo'); |
4531 | 308 | 308 | ||
4532 | 309 | button.simulate('click'); | 309 | button.simulate('click'); |
4534 | 310 | assert.equal(env.ws.last_message().config.admins, 'foo'); | 310 | var message = env.ws.last_message(); |
4535 | 311 | assert.equal('foo', message.Params.Config.admins); | ||
4536 | 311 | assert.equal(button.getHTML(), 'Save Changes'); | 312 | assert.equal(button.getHTML(), 'Save Changes'); |
4537 | 312 | }); | 313 | }); |
4538 | 313 | 314 | ||
4539 | 314 | 315 | ||
4540 | === modified file 'test/test_login.js' | |||
4541 | --- test/test_login.js 2013-05-17 14:51:05 +0000 | |||
4542 | +++ test/test_login.js 2013-08-30 19:48:17 +0000 | |||
4543 | @@ -45,26 +45,25 @@ | |||
4544 | 45 | }); | 45 | }); |
4545 | 46 | 46 | ||
4546 | 47 | test('the user is initially assumed to be unauthenticated', function() { | 47 | test('the user is initially assumed to be unauthenticated', function() { |
4548 | 48 | assert.equal(env.userIsAuthenticated, false); | 48 | assert.isFalse(env.userIsAuthenticated); |
4549 | 49 | }); | 49 | }); |
4550 | 50 | 50 | ||
4551 | 51 | test('successful login event marks user as authenticated', function() { | 51 | test('successful login event marks user as authenticated', function() { |
4555 | 52 | var evt = {data: {op: 'login', result: true}}; | 52 | var data = {Response: {}}; |
4556 | 53 | env.handleLoginEvent(evt); | 53 | env.handleLogin(data); |
4557 | 54 | assert.equal(env.userIsAuthenticated, true); | 54 | assert.isTrue(env.userIsAuthenticated); |
4558 | 55 | }); | 55 | }); |
4559 | 56 | 56 | ||
4560 | 57 | test('unsuccessful login event keeps user unauthenticated', function() { | 57 | test('unsuccessful login event keeps user unauthenticated', function() { |
4564 | 58 | var evt = {data: {op: 'login'}}; | 58 | var data = {Error: 'who are you?'}; |
4565 | 59 | env.handleLoginEvent(evt); | 59 | env.handleLogin(data); |
4566 | 60 | assert.equal(env.userIsAuthenticated, false); | 60 | assert.isFalse(env.userIsAuthenticated); |
4567 | 61 | }); | 61 | }); |
4568 | 62 | 62 | ||
4569 | 63 | test('bad credentials are removed', function() { | 63 | test('bad credentials are removed', function() { |
4574 | 64 | var evt = {data: {op: 'login'}}; | 64 | var data = {Error: 'who are you?'}; |
4575 | 65 | env.handleLoginEvent(evt); | 65 | env.handleLogin(data); |
4576 | 66 | var credentials = env.getCredentials(); | 66 | assert.isNull(env.getCredentials()); |
4573 | 67 | assert.equal(credentials, null); | ||
4577 | 68 | }); | 67 | }); |
4578 | 69 | 68 | ||
4579 | 70 | test('credentials passed to the constructor are stored', function() { | 69 | test('credentials passed to the constructor are stored', function() { |
4580 | @@ -93,11 +92,12 @@ | |||
4581 | 93 | }); | 92 | }); |
4582 | 94 | 93 | ||
4583 | 95 | test('with credentials set, login() sends an RPC message', function() { | 94 | test('with credentials set, login() sends an RPC message', function() { |
4585 | 96 | env.setCredentials({ user: 'user', password: 'password' }); | 95 | env.setCredentials({user: 'user', password: 'password'}); |
4586 | 97 | env.login(); | 96 | env.login(); |
4590 | 98 | assert.equal(conn.last_message().op, 'login'); | 97 | var message = conn.last_message(); |
4591 | 99 | assert.equal(conn.last_message().user, 'user'); | 98 | assert.equal('Login', message.Request); |
4592 | 100 | assert.equal(conn.last_message().password, 'password'); | 99 | assert.equal('user', message.Params.AuthTag); |
4593 | 100 | assert.equal('password', message.Params.Password); | ||
4594 | 101 | }); | 101 | }); |
4595 | 102 | 102 | ||
4596 | 103 | }); | 103 | }); |
4597 | @@ -143,9 +143,10 @@ | |||
4598 | 143 | container.appendChild('<input/>').set('type', 'password').set( | 143 | container.appendChild('<input/>').set('type', 'password').set( |
4599 | 144 | 'value', 'password'); | 144 | 'value', 'password'); |
4600 | 145 | loginView.login(ev); | 145 | loginView.login(ev); |
4604 | 146 | assert.equal(conn.last_message().op, 'login'); | 146 | var message = conn.last_message(); |
4605 | 147 | assert.equal(conn.last_message().user, 'user'); | 147 | assert.equal('Login', message.Request); |
4606 | 148 | assert.equal(conn.last_message().password, 'password'); | 148 | assert.equal('user', message.Params.AuthTag); |
4607 | 149 | assert.equal('password', message.Params.Password); | ||
4608 | 149 | }); | 150 | }); |
4609 | 150 | 151 | ||
4610 | 151 | test('the view render method adds the login form', function() { | 152 | test('the view render method adds the login form', function() { |
4611 | 152 | 153 | ||
4612 | === modified file 'test/test_model.js' | |||
4613 | --- test/test_model.js 2013-08-21 16:02:10 +0000 | |||
4614 | +++ test/test_model.js 2013-08-30 19:48:17 +0000 | |||
4615 | @@ -522,7 +522,7 @@ | |||
4616 | 522 | it('must send request to juju environment for local charms', function() { | 522 | it('must send request to juju environment for local charms', function() { |
4617 | 523 | var charm = new models.BrowserCharm({id: 'local:precise/foo-4'}).load(env); | 523 | var charm = new models.BrowserCharm({id: 'local:precise/foo-4'}).load(env); |
4618 | 524 | assert(!charm.loaded); | 524 | assert(!charm.loaded); |
4620 | 525 | conn.last_message().op.should.equal('get_charm'); | 525 | assert.equal('CharmInfo', conn.last_message().Request); |
4621 | 526 | }); | 526 | }); |
4622 | 527 | 527 | ||
4623 | 528 | it('must handle success from local charm request', function(done) { | 528 | it('must handle success from local charm request', function(done) { |
4624 | @@ -530,57 +530,47 @@ | |||
4625 | 530 | env, | 530 | env, |
4626 | 531 | function(err, response) { | 531 | function(err, response) { |
4627 | 532 | assert(!err); | 532 | assert(!err); |
4629 | 533 | charm.get('summary').should.equal('wowza'); | 533 | assert.equal('wowza', charm.get('summary')); |
4630 | 534 | assert(charm.loaded); | 534 | assert(charm.loaded); |
4631 | 535 | done(); | 535 | done(); |
4632 | 536 | }); | 536 | }); |
4680 | 537 | var response = conn.last_message(); | 537 | var response = { |
4681 | 538 | response.result = { summary: 'wowza' }; | 538 | RequestId: conn.last_message().RequestId, |
4682 | 539 | env.dispatch_result(response); | 539 | Response: {Meta: {Summary: 'wowza'}, Config: {}} |
4683 | 540 | // The test in the callback above should run. | 540 | }; |
4684 | 541 | }); | 541 | env.dispatch_result(response); |
4685 | 542 | 542 | // The test in the callback above should run. | |
4686 | 543 | it('parses the old charm model options location correctly', function(done) { | 543 | }); |
4687 | 544 | var charm = new models.BrowserCharm({id: 'local:precise/foo-4'}).load( | 544 | |
4688 | 545 | env, | 545 | it('parses charm model options correctly', function(done) { |
4689 | 546 | function(err, response) { | 546 | var charm = new models.BrowserCharm({id: 'local:precise/foo-4'}).load( |
4690 | 547 | assert(!err); | 547 | env, |
4691 | 548 | // This checks to make sure the parse mechanism is working properly | 548 | function(err, response) { |
4692 | 549 | // for both the old ane new charm browser. | 549 | assert(!err); |
4693 | 550 | assert.equal(charm.get('options').default_log['default'], 'global'); | 550 | // This checks to make sure the parse mechanism is working properly |
4694 | 551 | done(); | 551 | // for both the old ane new charm browser. |
4695 | 552 | }); | 552 | var option = charm.get('options').default_log; |
4696 | 553 | var response = conn.last_message(); | 553 | assert.equal('global', option['default']); |
4697 | 554 | response.result = { | 554 | assert.equal('Default log', option.description); |
4698 | 555 | config: { | 555 | done(); |
4699 | 556 | options: { | 556 | }); |
4700 | 557 | default_log: { | 557 | var response = { |
4701 | 558 | 'default': 'global', | 558 | RequestId: conn.last_message().RequestId, |
4702 | 559 | description: 'Default log', | 559 | Response: { |
4703 | 560 | type: 'string' | 560 | Meta: {}, |
4704 | 561 | }}}}; | 561 | Config: { |
4705 | 562 | env.dispatch_result(response); | 562 | Options: { |
4706 | 563 | }); | 563 | default_log: { |
4707 | 564 | 564 | Default: 'global', | |
4708 | 565 | it('parses the new charm model options location correctly', function(done) { | 565 | Description: 'Default log', |
4709 | 566 | var charm = new models.BrowserCharm({id: 'local:precise/foo-4'}).load( | 566 | Type: 'string' |
4710 | 567 | env, | 567 | } |
4711 | 568 | function(err, response) { | 568 | } |
4712 | 569 | assert(!err); | 569 | } |
4713 | 570 | // This checks to make sure the parse mechanism is working properly | 570 | } |
4714 | 571 | // for both the old ane new charm browser. | 571 | }; |
4715 | 572 | assert.equal(charm.get('options').default_log['default'], 'global'); | 572 | env.dispatch_result(response); |
4716 | 573 | done(); | 573 | // The test in the callback above should run. |
4670 | 574 | }); | ||
4671 | 575 | var response = conn.last_message(); | ||
4672 | 576 | response.result = { | ||
4673 | 577 | options: { | ||
4674 | 578 | default_log: { | ||
4675 | 579 | 'default': 'global', | ||
4676 | 580 | description: 'Default log', | ||
4677 | 581 | type: 'string' | ||
4678 | 582 | }}}; | ||
4679 | 583 | env.dispatch_result(response); | ||
4717 | 584 | }); | 574 | }); |
4718 | 585 | 575 | ||
4719 | 586 | it('must handle failure from local charm request', function(done) { | 576 | it('must handle failure from local charm request', function(done) { |
4720 | @@ -592,8 +582,10 @@ | |||
4721 | 592 | assert(!charm.loaded); | 582 | assert(!charm.loaded); |
4722 | 593 | done(); | 583 | done(); |
4723 | 594 | }); | 584 | }); |
4726 | 595 | var response = conn.last_message(); | 585 | var response = { |
4727 | 596 | response.err = true; | 586 | RequestId: conn.last_message().RequestId, |
4728 | 587 | Error: 'error' | ||
4729 | 588 | }; | ||
4730 | 597 | env.dispatch_result(response); | 589 | env.dispatch_result(response); |
4731 | 598 | // The test in the callback above should run. | 590 | // The test in the callback above should run. |
4732 | 599 | }); | 591 | }); |
4733 | 600 | 592 | ||
4734 | === modified file 'test/test_model_controller.js' | |||
4735 | --- test/test_model_controller.js 2013-08-12 14:56:38 +0000 | |||
4736 | +++ test/test_model_controller.js 2013-08-30 19:48:17 +0000 | |||
4737 | @@ -20,7 +20,7 @@ | |||
4738 | 20 | 20 | ||
4739 | 21 | describe('Model Controller Promises', function() { | 21 | describe('Model Controller Promises', function() { |
4740 | 22 | var modelController, yui, env, db, conn, environment, load, serviceError, | 22 | var modelController, yui, env, db, conn, environment, load, serviceError, |
4742 | 23 | getService, cleanups, aEach; | 23 | getService, cleanups, aEach, utils; |
4743 | 24 | 24 | ||
4744 | 25 | before(function(done) { | 25 | before(function(done) { |
4745 | 26 | YUI(GlobalConfig).use( | 26 | YUI(GlobalConfig).use( |
4746 | @@ -31,12 +31,13 @@ | |||
4747 | 31 | load = Y.juju.models.BrowserCharm.prototype.load; | 31 | load = Y.juju.models.BrowserCharm.prototype.load; |
4748 | 32 | getService = environments.PythonEnvironment.prototype.get_service; | 32 | getService = environments.PythonEnvironment.prototype.get_service; |
4749 | 33 | aEach = Y.Array.each; | 33 | aEach = Y.Array.each; |
4750 | 34 | utils = Y.namespace('juju-tests.utils'); | ||
4751 | 34 | done(); | 35 | done(); |
4752 | 35 | }); | 36 | }); |
4753 | 36 | }); | 37 | }); |
4754 | 37 | 38 | ||
4755 | 38 | beforeEach(function() { | 39 | beforeEach(function() { |
4757 | 39 | conn = new yui['juju-tests'].utils.SocketStub(); | 40 | conn = new utils.SocketStub(); |
4758 | 40 | environment = env = yui.juju.newEnvironment( | 41 | environment = env = yui.juju.newEnvironment( |
4759 | 41 | {conn: conn}); | 42 | {conn: conn}); |
4760 | 42 | db = new yui.juju.models.Database(); | 43 | db = new yui.juju.models.Database(); |
4761 | @@ -56,6 +57,7 @@ | |||
4762 | 56 | yui.Array.each(cleanups, function(cleanup) { | 57 | yui.Array.each(cleanups, function(cleanup) { |
4763 | 57 | cleanup(); | 58 | cleanup(); |
4764 | 58 | }); | 59 | }); |
4765 | 60 | window.flags = {}; | ||
4766 | 59 | }); | 61 | }); |
4767 | 60 | 62 | ||
4768 | 61 | /** | 63 | /** |
4769 | @@ -91,7 +93,7 @@ | |||
4770 | 91 | @static | 93 | @static |
4771 | 92 | */ | 94 | */ |
4772 | 93 | function clobberGetService() { | 95 | function clobberGetService() { |
4774 | 94 | yui.juju.environments.PythonEnvironment.prototype.get_service = function( | 96 | yui.juju.environments.GoEnvironment.prototype.get_service = function( |
4775 | 95 | serviceName, callback) { | 97 | serviceName, callback) { |
4776 | 96 | assert(typeof serviceName, 'string'); | 98 | assert(typeof serviceName, 'string'); |
4777 | 97 | // This is to test the error reject path of the getService tests | 99 | // This is to test the error reject path of the getService tests |
4778 | @@ -244,4 +246,30 @@ | |||
4779 | 244 | done(); | 246 | done(); |
4780 | 245 | }); | 247 | }); |
4781 | 246 | }); | 248 | }); |
4782 | 249 | |||
4783 | 250 | it('can check for available upgrades', function(done) { | ||
4784 | 251 | clobberLoad(); | ||
4785 | 252 | clobberGetService(); | ||
4786 | 253 | var serviceId = 'wordpress', | ||
4787 | 254 | charmId = 'cs:precise/wordpress-7'; | ||
4788 | 255 | db.services.add({ | ||
4789 | 256 | id: serviceId, | ||
4790 | 257 | loaded: true, | ||
4791 | 258 | charm: charmId | ||
4792 | 259 | }); | ||
4793 | 260 | window.flags.upgradeCharm = true; | ||
4794 | 261 | modelController.set('store', utils.makeFakeStore()); | ||
4795 | 262 | var promise = modelController.getServiceWithCharm(serviceId); | ||
4796 | 263 | promise.then( | ||
4797 | 264 | function(result) { | ||
4798 | 265 | var service = db.services.getById(serviceId); | ||
4799 | 266 | assert(service.get('upgrade_available'), true); | ||
4800 | 267 | assert(service.get('upgrade_to'), 'precise/wordpress-15'); | ||
4801 | 268 | done(); | ||
4802 | 269 | }, | ||
4803 | 270 | function() { | ||
4804 | 271 | assert.fail('This should not have failed.'); | ||
4805 | 272 | done(); | ||
4806 | 273 | }); | ||
4807 | 274 | }); | ||
4808 | 247 | }); | 275 | }); |
4809 | 248 | 276 | ||
4810 | === modified file 'test/test_notifications.js' | |||
4811 | --- test/test_notifications.js 2013-07-31 14:30:47 +0000 | |||
4812 | +++ test/test_notifications.js 2013-08-30 19:48:17 +0000 | |||
4813 | @@ -263,7 +263,7 @@ | |||
4814 | 263 | var container = Y.Node.create( | 263 | var container = Y.Node.create( |
4815 | 264 | '<div id="test" class="container"></div>'), | 264 | '<div id="test" class="container"></div>'), |
4816 | 265 | conn = new(Y.namespace('juju-tests.utils')).SocketStub(), | 265 | conn = new(Y.namespace('juju-tests.utils')).SocketStub(), |
4818 | 266 | env = juju.newEnvironment({conn: conn}); | 266 | env = juju.newEnvironment({conn: conn}, 'python'); |
4819 | 267 | app = new Y.juju.App({ | 267 | app = new Y.juju.App({ |
4820 | 268 | env: env, | 268 | env: env, |
4821 | 269 | container: container, | 269 | container: container, |
4822 | @@ -307,7 +307,7 @@ | |||
4823 | 307 | var container = Y.Node.create( | 307 | var container = Y.Node.create( |
4824 | 308 | '<div id="test" class="container"></div>'); | 308 | '<div id="test" class="container"></div>'); |
4825 | 309 | var conn = new(Y.namespace('juju-tests.utils')).SocketStub(); | 309 | var conn = new(Y.namespace('juju-tests.utils')).SocketStub(); |
4827 | 310 | var env = juju.newEnvironment({conn: conn}); | 310 | var env = juju.newEnvironment({conn: conn}, 'python'); |
4828 | 311 | env.connect(); | 311 | env.connect(); |
4829 | 312 | app = new Y.juju.App({ | 312 | app = new Y.juju.App({ |
4830 | 313 | env: env, | 313 | env: env, |
4831 | 314 | 314 | ||
4832 | === modified file 'test/test_sandbox_go.js' | |||
4833 | --- test/test_sandbox_go.js 2013-08-14 19:15:01 +0000 | |||
4834 | +++ test/test_sandbox_go.js 2013-08-30 19:48:17 +0000 | |||
4835 | @@ -277,6 +277,32 @@ | |||
4836 | 277 | {llama: 'pajama'}, | 277 | {llama: 'pajama'}, |
4837 | 278 | null, | 278 | null, |
4838 | 279 | 1, | 279 | 1, |
4839 | 280 | null, | ||
4840 | 281 | callback); | ||
4841 | 282 | }); | ||
4842 | 283 | |||
4843 | 284 | it('can deploy with constraints', function(done) { | ||
4844 | 285 | var constraints = { | ||
4845 | 286 | 'cpu-cores': 1, | ||
4846 | 287 | 'cpu-power': 0, | ||
4847 | 288 | 'mem': '512M', | ||
4848 | 289 | 'arch': 'i386' | ||
4849 | 290 | }; | ||
4850 | 291 | |||
4851 | 292 | env.connect(); | ||
4852 | 293 | // We begin logged in. See utils.makeFakeBackend. | ||
4853 | 294 | var callback = function(result) { | ||
4854 | 295 | var service = state.db.services.getById('kumquat'); | ||
4855 | 296 | assert.deepEqual(service.get('constraints'), constraints); | ||
4856 | 297 | done(); | ||
4857 | 298 | }; | ||
4858 | 299 | env.deploy( | ||
4859 | 300 | 'cs:precise/wordpress-15', | ||
4860 | 301 | 'kumquat', | ||
4861 | 302 | {llama: 'pajama'}, | ||
4862 | 303 | null, | ||
4863 | 304 | 1, | ||
4864 | 305 | constraints, | ||
4865 | 280 | callback); | 306 | callback); |
4866 | 281 | }); | 307 | }); |
4867 | 282 | 308 | ||
4868 | @@ -288,8 +314,8 @@ | |||
4869 | 288 | result.err, 'A service with this name already exists.'); | 314 | result.err, 'A service with this name already exists.'); |
4870 | 289 | done(); | 315 | done(); |
4871 | 290 | }; | 316 | }; |
4874 | 291 | env.deploy('cs:precise/wordpress-15', undefined, undefined, undefined, 1, | 317 | env.deploy('cs:precise/wordpress-15', undefined, undefined, undefined, |
4875 | 292 | callback); | 318 | 1, null, callback); |
4876 | 293 | }); | 319 | }); |
4877 | 294 | 320 | ||
4878 | 295 | it('can destroy a service', function(done) { | 321 | it('can destroy a service', function(done) { |
4879 | @@ -609,6 +635,7 @@ | |||
4880 | 609 | {llama: 'pajama'}, | 635 | {llama: 'pajama'}, |
4881 | 610 | null, | 636 | null, |
4882 | 611 | 1, | 637 | 1, |
4883 | 638 | null, | ||
4884 | 612 | localCb); | 639 | localCb); |
4885 | 613 | } | 640 | } |
4886 | 614 | 641 | ||
4887 | @@ -661,6 +688,7 @@ | |||
4888 | 661 | {llama: 'pajama'}, | 688 | {llama: 'pajama'}, |
4889 | 662 | null, | 689 | null, |
4890 | 663 | 1, | 690 | 1, |
4891 | 691 | null, | ||
4892 | 664 | localCb); | 692 | localCb); |
4893 | 665 | } | 693 | } |
4894 | 666 | 694 | ||
4895 | @@ -904,19 +932,23 @@ | |||
4896 | 904 | 932 | ||
4897 | 905 | it('can add a relation (integration)', function(done) { | 933 | it('can add a relation (integration)', function(done) { |
4898 | 906 | env.connect(); | 934 | env.connect(); |
4912 | 907 | env.deploy('cs:precise/wordpress-15', null, null, null, 1, function() { | 935 | env.deploy( |
4913 | 908 | env.deploy('cs:precise/mysql-26', null, null, null, 1, function() { | 936 | 'cs:precise/wordpress-15', null, null, null, 1, null, function() { |
4914 | 909 | var endpointA = ['wordpress', {name: 'db', role: 'client'}], | 937 | env.deploy( |
4915 | 910 | endpointB = ['mysql', {name: 'db', role: 'server'}]; | 938 | 'cs:precise/mysql-26', null, null, null, 1, null, function() { |
4916 | 911 | env.add_relation(endpointA, endpointB, function(recData) { | 939 | var endpointA = ['wordpress', {name: 'db', role: 'client'}], |
4917 | 912 | assert.equal(recData.err, undefined); | 940 | endpointB = ['mysql', {name: 'db', role: 'server'}]; |
4918 | 913 | assert.equal(recData.endpoint_a, 'wordpress:db'); | 941 | env.add_relation(endpointA, endpointB, function(recData) { |
4919 | 914 | assert.equal(recData.endpoint_b, 'mysql:db'); | 942 | assert.equal(recData.err, undefined); |
4920 | 915 | assert.isObject(recData.result); | 943 | assert.equal(recData.endpoint_a, 'wordpress:db'); |
4921 | 916 | done(); | 944 | assert.equal(recData.endpoint_b, 'mysql:db'); |
4922 | 917 | }); | 945 | assert.isObject(recData.result); |
4923 | 918 | }); | 946 | done(); |
4924 | 919 | }); | 947 | }); |
4925 | 948 | } | ||
4926 | 949 | ); | ||
4927 | 950 | } | ||
4928 | 951 | ); | ||
4929 | 920 | }); | 952 | }); |
4930 | 921 | 953 | ||
4931 | 922 | it('is able to add a relation with a subordinate service', function(done) { | 954 | it('is able to add a relation with a subordinate service', function(done) { |
4932 | @@ -1020,20 +1052,26 @@ | |||
4933 | 1020 | 1052 | ||
4934 | 1021 | it('can remove a relation(integration)', function(done) { | 1053 | it('can remove a relation(integration)', function(done) { |
4935 | 1022 | env.connect(); | 1054 | env.connect(); |
4950 | 1023 | env.deploy('cs:precise/wordpress-15', null, null, null, 1, function() { | 1055 | env.deploy( |
4951 | 1024 | env.deploy('cs:precise/mysql-26', null, null, null, 1, function() { | 1056 | 'cs:precise/wordpress-15', null, null, null, 1, null, function() { |
4952 | 1025 | var endpointA = ['wordpress', {name: 'db', role: 'client'}], | 1057 | env.deploy( |
4953 | 1026 | endpointB = ['mysql', {name: 'db', role: 'server'}]; | 1058 | 'cs:precise/mysql-26', null, null, null, 1, null, function() { |
4954 | 1027 | env.add_relation(endpointA, endpointB, function() { | 1059 | var endpointA = ['wordpress', {name: 'db', role: 'client'}], |
4955 | 1028 | env.remove_relation(endpointA, endpointB, function(recData) { | 1060 | endpointB = ['mysql', {name: 'db', role: 'server'}]; |
4956 | 1029 | assert.equal(recData.err, undefined); | 1061 | env.add_relation(endpointA, endpointB, function() { |
4957 | 1030 | assert.equal(recData.endpoint_a, 'wordpress:db'); | 1062 | env.remove_relation( |
4958 | 1031 | assert.equal(recData.endpoint_b, 'mysql:db'); | 1063 | endpointA, endpointB, function(recData) { |
4959 | 1032 | done(); | 1064 | assert.equal(recData.err, undefined); |
4960 | 1033 | }); | 1065 | assert.equal(recData.endpoint_a, 'wordpress:db'); |
4961 | 1034 | }); | 1066 | assert.equal(recData.endpoint_b, 'mysql:db'); |
4962 | 1035 | }); | 1067 | done(); |
4963 | 1036 | }); | 1068 | } |
4964 | 1069 | ); | ||
4965 | 1070 | }); | ||
4966 | 1071 | } | ||
4967 | 1072 | ); | ||
4968 | 1073 | } | ||
4969 | 1074 | ); | ||
4970 | 1037 | }); | 1075 | }); |
4971 | 1038 | 1076 | ||
4972 | 1039 | }); | 1077 | }); |
4973 | 1040 | 1078 | ||
4974 | === modified file 'test/test_sandbox_python.js' | |||
4975 | --- test/test_sandbox_python.js 2013-07-30 21:00:47 +0000 | |||
4976 | +++ test/test_sandbox_python.js 2013-08-30 19:48:17 +0000 | |||
4977 | @@ -154,6 +154,7 @@ | |||
4978 | 154 | {llama: 'pajama'}, | 154 | {llama: 'pajama'}, |
4979 | 155 | null, | 155 | null, |
4980 | 156 | 1, | 156 | 1, |
4981 | 157 | null, | ||
4982 | 157 | localCb); | 158 | localCb); |
4983 | 158 | }); | 159 | }); |
4984 | 159 | env.connect(); | 160 | env.connect(); |
4985 | @@ -209,6 +210,7 @@ | |||
4986 | 209 | {llama: 'pajama'}, | 210 | {llama: 'pajama'}, |
4987 | 210 | null, | 211 | null, |
4988 | 211 | 1, | 212 | 1, |
4989 | 213 | null, | ||
4990 | 212 | localCb); | 214 | localCb); |
4991 | 213 | }); | 215 | }); |
4992 | 214 | env.connect(); | 216 | env.connect(); |
4993 | @@ -383,6 +385,7 @@ | |||
4994 | 383 | {llama: 'pajama'}, | 385 | {llama: 'pajama'}, |
4995 | 384 | null, | 386 | null, |
4996 | 385 | 1, | 387 | 1, |
4997 | 388 | null, | ||
4998 | 386 | callback); | 389 | callback); |
4999 | 387 | }); | 390 | }); |
5000 | 388 | env.connect(); | 391 | env.connect(); |
The diff has been truncated for viewing.
Reviewers: mp+182516_ code.launchpad. net,
Message:
Please take a look.
Description:
Bundle Topology
Provide a reusable view of topologies with a much more
limited and non-interactive service module. The tests show
how this works but don't wire the view into any current
UI which comes later.
https:/ /code.launchpad .net/~bcsaller/ juju-gui/ renderBundle2/ +merge/ 182516
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/13245045/
Affected files: javascripts/ d3-components. js model-controlle r.js models. js debug.js env/env. js env/fakebackend .js env/python. js env/sandbox. js browser/ browser. js browser/ templates/ editorial. handlebars browser/ views/editorial .js browser/ views/minimized .js browser/ views/view. js category- icons.partial ghost-config- viewlet. handlebars inspector- header. handlebars service- config- wrapper. handlebars service- constraints- viewlet. partial service- overview- constraints. handlebars charm-panel. js environment. js ghost-inspector .js inspector. js topology/ bundle. js topology/ relation. js topology/ topology. js viewlets/ inspector- header. js viewlets/ service- constraints. js viewlets/ service- ghost.js viewmode- controls. js browser/ charm-full. less browser/ charm-token. less juju-inspector. less wp-deployer. yaml browser_ app.js browser_ editorial. js charm_panel. js charm_store. js charm_view. js endpoints. js env_python. js fakebackend. js ghost_inspector .js inspector_ constraints. js inspector_ overview. js inspector_ settings. js model_controlle r.js notifications. js sandbox_ go.js sandbox_ python. js service_ config_ view.js service_ view.js startup. js.bottom topology. js unit_view. js viewlet_ manager. js viewmode_ controls_ widget. js websocket_ logging. js
A [revision details]
M app/app.js
M app/assets/
M app/config-debug.js
M app/config-prod.js
M app/index.html
M app/models/charm.js
M app/models/
M app/models/
M app/modules-
M app/store/charm.js
M app/store/
M app/store/
M app/store/env/go.js
M app/store/
M app/store/
M app/subapps/
M app/subapps/
M app/subapps/
M app/subapps/
M app/subapps/
D app/templates/
M app/templates/
M app/templates/
M app/templates/
M app/templates/
M app/templates/
M app/views/
M app/views/charm.js
M app/views/
M app/views/
M app/views/
A app/views/
M app/views/
M app/views/
M app/views/utils.js
M app/views/
M app/views/
M app/views/
M app/widgets/
M lib/views/
M lib/views/
M lib/views/
A test/data/
M test/index.html
M test/test_app.js
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_env.js
M test/test_env_go.js
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_login.js
M test/test_model.js
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_