root / Classes / AddServerViewController.m @ e06c24cf
History | View | Annotate | Download (28.4 kB)
1 |
// |
---|---|
2 |
// AddServerViewController.m |
3 |
// OpenStack |
4 |
// |
5 |
// Created by Mike Mayo on 10/8/10. |
6 |
// The OpenStack project is provided under the Apache 2.0 license. |
7 |
// |
8 |
|
9 |
#import "AddServerViewController.h" |
10 |
#import "OpenStackAccount.h" |
11 |
#import "Image.h" |
12 |
#import "Flavor.h" |
13 |
#import "Server.h" |
14 |
#import "UIViewController+Conveniences.h" |
15 |
#import "ImagePickerViewController.h" |
16 |
#import "UIColor+MoreColors.h" |
17 |
#import "SimpleImagePickerViewController.h" |
18 |
#import "Provider.h" |
19 |
#import "AddServerPlugin.h" |
20 |
#import "AddServerPluginHandler.h" |
21 |
#import "ServersViewController.h" |
22 |
#import "AccountManager.h" |
23 |
#import "OpenStackRequest.h" |
24 |
#import "LogEntryModalViewController.h" |
25 |
#import "APILogEntry.h" |
26 |
#import "RateLimit.h" |
27 |
#import "RSTextFieldCell.h" |
28 |
#import "OpenStackAppDelegate.h" |
29 |
#import "AccountHomeViewController.h" |
30 |
#import "ServerViewController.h" |
31 |
#import "AccountHomeViewController.h" |
32 |
|
33 |
#define kNodeCount 0 |
34 |
#define kNodeDetails 1 |
35 |
|
36 |
#define kName 0 |
37 |
#define kSize 1 |
38 |
#define kImageRow 2 |
39 |
|
40 |
// TODO: bring back passwords |
41 |
|
42 |
/* |
43 |
Files |
44 |
<file injection ui, however that should work; pick from files synced from iTunes i guess. maybe object storage> |
45 |
|
46 |
--- third party section --- |
47 |
-- third party server create tools should be a plugin style, separate classes -- |
48 |
[Chef Bootstrapped - on/off] |
49 |
run list |
50 |
<sub text from iPad> Visit opscode.com/chef for more information. |
51 |
(perhaps load roles and recipes via API) |
52 |
|
53 |
[Puppet Bootstrapped - on/off] |
54 |
whatever puppet "plugin" needs |
55 |
*/ |
56 |
|
57 |
@implementation AddServerViewController |
58 |
|
59 |
@synthesize account, selectedImage, serversViewController, accountHomeViewController; |
60 |
|
61 |
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation { |
62 |
return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait); |
63 |
} |
64 |
|
65 |
- (void)setNewSelectedImage:(Image *)image { |
66 |
self.selectedImage = image; |
67 |
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:2 inSection:1]; |
68 |
//[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone]; |
69 |
[self.tableView reloadData]; |
70 |
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; |
71 |
} |
72 |
|
73 |
#pragma mark - |
74 |
#pragma mark Button Handlers |
75 |
|
76 |
- (void)saveButtonPressed:(id)sender { |
77 |
|
78 |
// validate required fields? name isn't actually required; flavor and image are defaulted |
79 |
|
80 |
// populate the server(s) info, then pass the server along to all the plugins |
81 |
for (int i = 0; i < nodeCount; i++) { |
82 |
Server *server = [[Server alloc] init]; |
83 |
if (nodeCount == 1) { |
84 |
server.name = nameTextField.text; |
85 |
} else { |
86 |
if (nameTextField.text == nil || [nameTextField.text isEqualToString:@""]) { |
87 |
server.name = [NSString stringWithFormat:@"slice%i", i + 1]; |
88 |
} else { |
89 |
server.name = [NSString stringWithFormat:@"%@%i", nameTextField.text, i + 1]; |
90 |
} |
91 |
} |
92 |
|
93 |
server.flavor = [self.account.sortedFlavors objectAtIndex:flavorIndex]; |
94 |
|
95 |
server.image = selectedImage; |
96 |
|
97 |
for (int i = 0; i < [plugins count]; i++) { |
98 |
id <AddServerPlugin> plugin = [plugins objectAtIndex:i]; |
99 |
[plugin configureServer:&server]; |
100 |
} |
101 |
|
102 |
// TODO: handle plugin validation and check personality file count |
103 |
// perhaps via configureServer throwing an exception? |
104 |
|
105 |
[account.manager createServer:server]; |
106 |
|
107 |
// now we need to register for the creation success and failure events. |
108 |
id success = [[NSNotificationCenter defaultCenter] addObserverForName:@"createServerSucceeded" object:server |
109 |
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) |
110 |
{ |
111 |
successCount++; |
112 |
|
113 |
// insert the server into the account's sorted array |
114 |
NSMutableDictionary *servers = [[NSMutableDictionary alloc] initWithDictionary:self.account.servers]; |
115 |
|
116 |
OpenStackRequest *request = [notification.userInfo objectForKey:@"request"]; |
117 |
Server *createdServer = [request server]; |
118 |
createdServer.flavor = [self.account.flavors objectForKey:createdServer.flavorId]; |
119 |
createdServer.image = [self.account.images objectForKey:createdServer.imageId]; |
120 |
|
121 |
[servers setObject:createdServer forKey:createdServer.identifier]; |
122 |
|
123 |
self.account.servers = [NSMutableDictionary dictionaryWithDictionary:servers]; |
124 |
|
125 |
[servers release]; |
126 |
|
127 |
[self.account persist]; |
128 |
|
129 |
// insert the server into the servers list table |
130 |
// using reloadData instead of insertRows to avoid race conditions |
131 |
serversViewController.tableView.allowsSelection = YES; |
132 |
serversViewController.tableView.scrollEnabled = YES; |
133 |
[serversViewController.tableView reloadData]; |
134 |
|
135 |
if (nodeCount - successCount - failureCount == 0) { |
136 |
[serversViewController hideToolbarActivityMessage]; |
137 |
} else { |
138 |
if (nodeCount - successCount - failureCount == 1) { |
139 |
[serversViewController showToolbarActivityMessage:@"Creating 1 server..."]; |
140 |
} else { |
141 |
[serversViewController showToolbarActivityMessage:[NSString stringWithFormat:@"Creating %i servers...", nodeCount - successCount - failureCount]]; |
142 |
} |
143 |
} |
144 |
|
145 |
if (failureCount + successCount == nodeCount) { |
146 |
[serversViewController hideToolbarActivityMessage]; |
147 |
|
148 |
if (failureCount > 0) { |
149 |
if (nodeCount == 1) { |
150 |
[self alert:@"There was a problem creating your Cloud Server." request:[notification.userInfo objectForKey:@"request"]]; |
151 |
} else if (failureCount == nodeCount) { |
152 |
[self alert:@"There was a problem creating your Cloud Servers. To see details for all failures, go to API Logs." request:[notification.userInfo objectForKey:@"request"]]; |
153 |
} else { |
154 |
[self alert:[NSString stringWithFormat:@"There was a problem creating %i of your Cloud Servers. To see details for all failures, go to API Logs.", failureCount] request:[notification.userInfo objectForKey:@"request"]]; |
155 |
} |
156 |
} |
157 |
} |
158 |
|
159 |
}]; |
160 |
|
161 |
id failure = [[NSNotificationCenter defaultCenter] addObserverForName:@"createServerFailed" object:server |
162 |
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) |
163 |
{ |
164 |
failureCount++; |
165 |
|
166 |
// if fail count + success count == node count, show an alert that |
167 |
// says how many failed. if fail count > 1, tell the user to view |
168 |
// API Logs to see all of the failures. Details button should show |
169 |
// the first failure |
170 |
|
171 |
if (nodeCount - successCount - failureCount == 0) { |
172 |
[serversViewController hideToolbarActivityMessage]; |
173 |
} else { |
174 |
if (nodeCount - successCount - failureCount == 1) { |
175 |
[serversViewController showToolbarActivityMessage:@"Creating 1 server..."]; |
176 |
} else { |
177 |
[serversViewController showToolbarActivityMessage:[NSString stringWithFormat:@"Creating %i servers...", nodeCount - successCount - failureCount]]; |
178 |
} |
179 |
} |
180 |
|
181 |
if (failureCount + successCount == nodeCount) { |
182 |
[serversViewController hideToolbarActivityMessage]; |
183 |
|
184 |
if (failureCount > 0) { |
185 |
if (nodeCount == 1) { |
186 |
[self alert:@"There was a problem creating your Cloud Server." request:[notification.userInfo objectForKey:@"request"]]; |
187 |
} else if (failureCount == nodeCount) { |
188 |
[self alert:@"There was a problem creating your Cloud Servers. To see details for all failures, go to API Logs." request:[notification.userInfo objectForKey:@"request"]]; |
189 |
} else { |
190 |
[self alert:[NSString stringWithFormat:@"There was a problem creating %i of your Cloud Server. To see details for all failures, go to API Logs.", failureCount] request:[notification.userInfo objectForKey:@"request"]]; |
191 |
} |
192 |
} |
193 |
} |
194 |
|
195 |
}]; |
196 |
|
197 |
[createServerObservers addObject:success]; |
198 |
[createServerObservers addObject:failure]; |
199 |
|
200 |
[server release]; |
201 |
} |
202 |
|
203 |
// since UIProgressBar looks like crap on a non-default tint UIToolbar, |
204 |
// we'll just use text and count down as the servers are created |
205 |
if (nodeCount == 1) { |
206 |
[serversViewController showToolbarActivityMessage:@"Creating server..."]; |
207 |
} else { |
208 |
[serversViewController showToolbarActivityMessage:[NSString stringWithFormat:@"Creating %i servers...", nodeCount]]; |
209 |
} |
210 |
|
211 |
[self dismissModalViewControllerAnimated:YES]; |
212 |
|
213 |
} |
214 |
|
215 |
#pragma mark - |
216 |
#pragma mark View lifecycle |
217 |
|
218 |
- (void)viewDidLoad { |
219 |
[super viewDidLoad]; |
220 |
|
221 |
successCount = 0; |
222 |
failureCount = 0; |
223 |
|
224 |
nodeCount = 1; |
225 |
self.navigationItem.title = @"Add Server"; |
226 |
|
227 |
serverCountSlider = [[UISlider alloc] init]; |
228 |
flavorSlider = [[UISlider alloc] init]; |
229 |
|
230 |
[self addCancelButton]; |
231 |
[self addSaveButton]; |
232 |
|
233 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
234 |
nameTextField = [[UITextField alloc] initWithFrame:CGRectMake(98.0, 13.0, 400.0, 24.0)]; |
235 |
} else { |
236 |
nameTextField = [[UITextField alloc] initWithFrame:CGRectMake(79.0, 13.0, 222.0, 24.0)]; |
237 |
} |
238 |
nameTextField.delegate = self; |
239 |
nameTextField.font = [UIFont systemFontOfSize:17.0]; |
240 |
nameTextField.textColor = [UIColor value1DetailTextLabelColor]; |
241 |
nameTextField.backgroundColor = [UIColor clearColor]; |
242 |
nameTextField.textAlignment = UITextAlignmentRight; |
243 |
nameTextField.returnKeyType = UIReturnKeyDone; |
244 |
nameTextField.autocorrectionType = UITextAutocorrectionTypeNo; |
245 |
nameTextField.autocapitalizationType = UITextAutocapitalizationTypeNone; |
246 |
nameTextField.placeholder = @"Ex: web-server"; |
247 |
|
248 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
249 |
serverNumbersLabel = [[UILabel alloc] initWithFrame:CGRectMake(40.0, 15.5, 458.0, 18.0)]; |
250 |
} else { |
251 |
serverNumbersLabel = [[UILabel alloc] initWithFrame:CGRectMake(21.0, 15.5, 280.0, 18.0)]; |
252 |
} |
253 |
serverNumbersLabel.font = [UIFont systemFontOfSize:17.0]; |
254 |
serverNumbersLabel.textColor = [UIColor value1DetailTextLabelColor]; |
255 |
serverNumbersLabel.backgroundColor = [UIColor clearColor]; |
256 |
serverNumbersLabel.textAlignment = UITextAlignmentRight; |
257 |
serverNumbersLabel.text = @""; |
258 |
|
259 |
NSMutableArray *allPlugins = [[NSMutableArray alloc] initWithArray:[AddServerPluginHandler plugins]]; |
260 |
for (id <AddServerPlugin> plugin in [AddServerPluginHandler plugins]) { |
261 |
if ([plugin pluginShouldAppear]) { |
262 |
[plugin pluginWillAppear]; |
263 |
} else { |
264 |
[allPlugins removeObject:plugin]; |
265 |
} |
266 |
} |
267 |
|
268 |
plugins = [[NSArray alloc] initWithArray:allPlugins]; |
269 |
[allPlugins release]; |
270 |
|
271 |
createServerObservers = [[NSMutableArray alloc] init]; |
272 |
} |
273 |
|
274 |
- (void)viewDidUnload { |
275 |
for (id observer in createServerObservers) { |
276 |
[[NSNotificationCenter defaultCenter] removeObserver:observer]; |
277 |
} |
278 |
} |
279 |
|
280 |
- (void)viewWillAppear:(BOOL)animated { |
281 |
[super viewWillAppear:animated]; |
282 |
|
283 |
RateLimit *limit = [OpenStackRequest createServerLimit:self.account]; |
284 |
maxServers = limit.remaining; |
285 |
|
286 |
// load defaults for flavor and image. if no default set yet, use the smallest flavor |
287 |
// and the newest Ubuntu |
288 |
|
289 |
if (account.lastUsedFlavorId == 0) { |
290 |
flavorIndex = 0; |
291 |
account.lastUsedFlavorId = [[self.account.sortedFlavors objectAtIndex:0] identifier]; |
292 |
} else { |
293 |
for (int i = 0; i < [self.account.sortedFlavors count]; i++) { |
294 |
Flavor *flavor = [self.account.sortedFlavors objectAtIndex:i]; |
295 |
if (flavor.identifier == account.lastUsedFlavorId) { |
296 |
flavorIndex = i; |
297 |
flavorSlider.value = ((flavorIndex * 1.0) / ([self.account.sortedFlavors count] - 1)); |
298 |
flavorLabel.text = [NSString stringWithFormat:@"%i MB RAM, %i GB Disk", flavor.ram, flavor.disk]; |
299 |
} |
300 |
} |
301 |
} |
302 |
|
303 |
if (account.lastUsedImageId == 0) { |
304 |
// select the newest ubuntu as a default. if there's not a newest ubuntu, select the |
305 |
// first image id |
306 |
NSMutableArray *ubuntus = [[NSMutableArray alloc] init]; |
307 |
|
308 |
NSArray *sortedImages = [self.account sortedImages]; |
309 |
|
310 |
for (int i = 0; i < [sortedImages count]; i++) { |
311 |
Image *image = [sortedImages objectAtIndex:i]; |
312 |
if ([image respondsToSelector:@selector(logoPrefix)] && [[image logoPrefix] isEqualToString:@"ubuntu"]) { |
313 |
[ubuntus addObject:image]; |
314 |
} |
315 |
} |
316 |
|
317 |
if ([ubuntus count] > 0) { |
318 |
selectedImage = [ubuntus lastObject]; |
319 |
} else { |
320 |
selectedImage = [sortedImages objectAtIndex:0]; |
321 |
} |
322 |
account.lastUsedImageId = selectedImage.identifier; |
323 |
[ubuntus release]; |
324 |
|
325 |
} else { |
326 |
selectedImage = [self.account.images objectForKey:account.lastUsedImageId]; |
327 |
} |
328 |
|
329 |
[self.tableView reloadData]; // force the image name to show up |
330 |
} |
331 |
|
332 |
#pragma mark - |
333 |
#pragma mark Table view data source |
334 |
|
335 |
- (CGFloat)findLabelHeight:(NSString*)text font:(UIFont *)font { |
336 |
CGSize textLabelSize = CGSizeMake(260.0, 9000.0f); |
337 |
CGSize stringSize = [text sizeWithFont:font constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeWordWrap]; |
338 |
return stringSize.height; |
339 |
} |
340 |
|
341 |
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { |
342 |
if (indexPath.section == kNodeCount) { |
343 |
return tableView.rowHeight + flavorSlider.frame.size.height + 3.0; |
344 |
} else if (indexPath.section == kNodeDetails) { |
345 |
if (indexPath.row == kSize) { |
346 |
return tableView.rowHeight + serverCountSlider.frame.size.height + 3.0; |
347 |
} else if (indexPath.row == kImageRow) { |
348 |
CGSize size = [selectedImage.name sizeWithFont:[UIFont systemFontOfSize:18.0] constrainedToSize:CGSizeMake(220.0, 9000.0f) lineBreakMode:UILineBreakModeWordWrap]; |
349 |
CGFloat result = size.height; |
350 |
if (result > 22.0) { |
351 |
result += 22.0; |
352 |
} |
353 |
return MAX(tableView.rowHeight, result); |
354 |
} else { |
355 |
return tableView.rowHeight; |
356 |
} |
357 |
} else { |
358 |
return tableView.rowHeight; |
359 |
} |
360 |
} |
361 |
|
362 |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { |
363 |
// Return the number of sections. |
364 |
return 2 + [plugins count]; |
365 |
} |
366 |
|
367 |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { |
368 |
// Return the number of rows in the section. |
369 |
if (section == kNodeCount) { |
370 |
return 1; |
371 |
} else if (section == kNodeDetails) { |
372 |
return 3; |
373 |
} else { |
374 |
id <AddServerPlugin> plugin = [plugins objectAtIndex:section - 2]; |
375 |
return [plugin tableView:tableView numberOfRowsInSection:section]; |
376 |
} |
377 |
} |
378 |
|
379 |
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { |
380 |
return @""; |
381 |
} |
382 |
|
383 |
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { |
384 |
if (section == kNodeCount) { |
385 |
if (maxServers == 1) { |
386 |
return @"With your current API rate limit, you can create one node at a time."; |
387 |
} else { |
388 |
//return [NSString stringWithFormat:@"With your current API rate limit, you can create up to %i Cloud Servers at a time.", maxServers]; |
389 |
return [NSString stringWithFormat:@"With your current API rate limit, you can create up to %i servers at a time.", maxServers]; |
390 |
} |
391 |
} else if (section == kNodeDetails) { |
392 |
return [self.account.provider isRackspace] ? @"Please refer to rackspace.com/cloud for Cloud Servers pricing." : @""; |
393 |
} else { |
394 |
id <AddServerPlugin> plugin = [plugins objectAtIndex:section - 2]; |
395 |
return [plugin tableView:tableView titleForFooterInSection:section]; |
396 |
} |
397 |
} |
398 |
|
399 |
- (void)serverCountSliderMoved:(id)sender { |
400 |
nodeCount = (NSInteger)(serverCountSlider.value * maxServers); |
401 |
if (nodeCount == 0 || nodeCount == 1) { |
402 |
nodeCount = 1; |
403 |
serverCountLabel.text = @"1 server"; |
404 |
serverNumbersLabel.text = @""; |
405 |
nameTextField.placeholder = @"Ex: web-server"; |
406 |
|
407 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
408 |
nameTextField.frame = CGRectMake(98.0, 13.0, 400.0, 24.0); |
409 |
} else { |
410 |
nameTextField.frame = CGRectMake(79.0, 13.0, 222.0, 24.0); |
411 |
} |
412 |
|
413 |
} else { |
414 |
serverCountLabel.text = [NSString stringWithFormat:@"%i servers", nodeCount]; |
415 |
if (nodeCount == 2) { |
416 |
serverNumbersLabel.text = @"[1,2]"; |
417 |
} else { |
418 |
serverNumbersLabel.text = [NSString stringWithFormat:@"[1..%i]", nodeCount]; |
419 |
} |
420 |
|
421 |
nameTextField.placeholder = @""; |
422 |
|
423 |
// move the text field to make room for the numbers label |
424 |
CGSize size = [serverNumbersLabel.text sizeWithFont:serverNumbersLabel.font constrainedToSize:CGSizeMake(280.0, 900.0f)]; |
425 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
426 |
nameTextField.frame = CGRectMake(98.0, 13.0, 400.0 - size.width, 24.0); |
427 |
} else { |
428 |
nameTextField.frame = CGRectMake(79.0, 13.0, 222.0 - size.width, 24.0); |
429 |
} |
430 |
|
431 |
} |
432 |
} |
433 |
|
434 |
- (UITableViewCell *)tableView:(UITableView *)tableView serverCountCellForRowAtIndexPath:(NSIndexPath *)indexPath { |
435 |
static NSString *CellIdentifier = @"ServerCountCell"; |
436 |
|
437 |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; |
438 |
if (cell == nil) { |
439 |
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease]; |
440 |
cell.selectionStyle = UITableViewCellSelectionStyleNone; |
441 |
|
442 |
UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(20.0, 13.0, 280.0, 20.0)]; |
443 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
444 |
textLabel.frame = CGRectMake(41.0, 13.0, 458.0, 20.0); |
445 |
} |
446 |
textLabel.font = [UIFont boldSystemFontOfSize:17.0]; |
447 |
textLabel.text = @"Cloud Servers"; |
448 |
textLabel.textColor = [UIColor blackColor]; |
449 |
textLabel.backgroundColor = [UIColor clearColor]; |
450 |
[cell addSubview:textLabel]; |
451 |
[textLabel release]; |
452 |
|
453 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
454 |
serverCountSlider.frame = CGRectMake(41.0, 38.0, 458.0, serverCountSlider.frame.size.height); |
455 |
} else { |
456 |
serverCountSlider.frame = CGRectMake(20.0, 38.0, 280.0, serverCountSlider.frame.size.height); |
457 |
} |
458 |
[serverCountSlider addTarget:self action:@selector(serverCountSliderMoved:) forControlEvents:UIControlEventValueChanged]; |
459 |
|
460 |
[cell addSubview:serverCountSlider]; |
461 |
serverCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(20.0, 14.0, 280.0, 18.0)]; |
462 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
463 |
serverCountLabel.frame = CGRectMake(41.0, 14.0, 458.0, 18.0); |
464 |
} |
465 |
serverCountLabel.font = [UIFont systemFontOfSize:17.0]; |
466 |
serverCountLabel.textColor = [UIColor value1DetailTextLabelColor]; |
467 |
serverCountLabel.backgroundColor = [UIColor clearColor]; |
468 |
serverCountLabel.textAlignment = UITextAlignmentRight; |
469 |
[cell addSubview:serverCountLabel]; |
470 |
} |
471 |
|
472 |
if (nodeCount == 1) { |
473 |
serverCountLabel.text = @"1 server"; |
474 |
} else { |
475 |
serverCountLabel.text = [NSString stringWithFormat:@"%i servers", nodeCount]; |
476 |
} |
477 |
|
478 |
return cell; |
479 |
} |
480 |
|
481 |
- (void)flavorSliderFinished:(id)sender { |
482 |
flavorIndex = MIN([self.account.sortedFlavors count] - 1, (NSInteger)[self.account.sortedFlavors count] * flavorSlider.value); |
483 |
Flavor *flavor = [self.account.sortedFlavors objectAtIndex:flavorIndex]; |
484 |
flavorLabel.text = [NSString stringWithFormat:@"%i MB RAM, %i GB Disk", flavor.ram, flavor.disk]; |
485 |
self.account.lastUsedFlavorId = flavor.identifier; |
486 |
|
487 |
NSMutableArray *accountArr = [NSMutableArray arrayWithArray:[OpenStackAccount accounts]]; |
488 |
for (int i = 0; i < [accountArr count]; i++) { |
489 |
OpenStackAccount *anAccount = [accountArr objectAtIndex:i]; |
490 |
if ([self.account.uuid isEqualToString:anAccount.uuid]) { |
491 |
[accountArr replaceObjectAtIndex:i withObject:self.account]; |
492 |
} |
493 |
} |
494 |
[self.account persist]; |
495 |
|
496 |
} |
497 |
|
498 |
- (void)flavorSliderMoved:(id)sender { |
499 |
flavorIndex = MIN([self.account.sortedFlavors count] - 1, (NSInteger)[self.account.sortedFlavors count] * flavorSlider.value); |
500 |
Flavor *flavor = [self.account.sortedFlavors objectAtIndex:flavorIndex]; |
501 |
flavorLabel.text = [NSString stringWithFormat:@"%i MB RAM, %i GB Disk", flavor.ram, flavor.disk]; |
502 |
self.account.lastUsedFlavorId = flavor.identifier; |
503 |
} |
504 |
|
505 |
- (UITableViewCell *)tableView:(UITableView *)tableView sizeCellForRowAtIndexPath:(NSIndexPath *)indexPath { |
506 |
static NSString *CellIdentifier = @"SizeCell"; |
507 |
|
508 |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; |
509 |
if (cell == nil) { |
510 |
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease]; |
511 |
cell.selectionStyle = UITableViewCellSelectionStyleNone; |
512 |
|
513 |
UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(20.0, 13.0, 280.0, 20.0)]; |
514 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
515 |
textLabel.frame = CGRectMake(41.0, 13.0, 458.0, 20.0); |
516 |
} |
517 |
textLabel.font = [UIFont boldSystemFontOfSize:17.0]; |
518 |
textLabel.text = @"Size"; |
519 |
textLabel.textColor = [UIColor blackColor]; |
520 |
textLabel.backgroundColor = [UIColor clearColor]; |
521 |
[cell addSubview:textLabel]; |
522 |
[textLabel release]; |
523 |
|
524 |
flavorSlider.frame = CGRectMake(20.0, 38.0, 280.0, flavorSlider.frame.size.height); |
525 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
526 |
flavorSlider.frame = CGRectMake(41.0, 38.0, 458.0, flavorSlider.frame.size.height); |
527 |
} |
528 |
[flavorSlider addTarget:self action:@selector(flavorSliderMoved:) forControlEvents:UIControlEventValueChanged]; |
529 |
[flavorSlider addTarget:self action:@selector(flavorSliderFinished:) forControlEvents:UIControlEventTouchUpInside]; |
530 |
|
531 |
[cell addSubview:flavorSlider]; |
532 |
flavorLabel = [[UILabel alloc] initWithFrame:CGRectMake(20.0, 14.0, 280.0, 18.0)]; |
533 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
534 |
flavorLabel.frame = CGRectMake(41.0, 14.0, 458.0, 18.0); |
535 |
} |
536 |
flavorLabel.font = [UIFont systemFontOfSize:17.0]; |
537 |
flavorLabel.textColor = [UIColor value1DetailTextLabelColor]; |
538 |
flavorLabel.backgroundColor = [UIColor clearColor]; |
539 |
flavorLabel.textAlignment = UITextAlignmentRight; |
540 |
[cell addSubview:flavorLabel]; |
541 |
} |
542 |
|
543 |
Flavor *flavor = [self.account.sortedFlavors objectAtIndex:flavorIndex]; |
544 |
flavorLabel.text = [NSString stringWithFormat:@"%i MB RAM, %i GB Disk", flavor.ram, flavor.disk]; |
545 |
|
546 |
return cell; |
547 |
} |
548 |
|
549 |
- (UITableViewCell *)tableView:(UITableView *)tableView nameCellForRowAtIndexPath:(NSIndexPath *)indexPath { |
550 |
static NSString *CellIdentifier = @"NameCell"; |
551 |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; |
552 |
if (cell == nil) { |
553 |
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease]; |
554 |
cell.selectionStyle = UITableViewCellSelectionStyleNone; |
555 |
cell.textLabel.text = @"Name"; |
556 |
[cell addSubview:nameTextField]; |
557 |
[cell addSubview:serverNumbersLabel]; |
558 |
} |
559 |
return cell; |
560 |
} |
561 |
|
562 |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { |
563 |
|
564 |
if (indexPath.section == kNodeCount) { |
565 |
return [self tableView:tableView serverCountCellForRowAtIndexPath:indexPath]; |
566 |
} else if (indexPath.section == kNodeDetails) { |
567 |
if (indexPath.row == kName) { |
568 |
return [self tableView:tableView nameCellForRowAtIndexPath:indexPath]; |
569 |
} else if (indexPath.row == kSize) { |
570 |
return [self tableView:tableView sizeCellForRowAtIndexPath:indexPath]; |
571 |
} else { |
572 |
static NSString *CellIdentifier = @"Cell"; |
573 |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; |
574 |
if (cell == nil) { |
575 |
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease]; |
576 |
cell.textLabel.numberOfLines = 0; |
577 |
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; |
578 |
cell.selectionStyle = UITableViewCellSelectionStyleBlue; |
579 |
cell.detailTextLabel.numberOfLines = 0; |
580 |
cell.detailTextLabel.lineBreakMode = UILineBreakModeWordWrap; |
581 |
cell.detailTextLabel.textAlignment = UITextAlignmentLeft; |
582 |
} |
583 |
cell.textLabel.text = @"Image"; |
584 |
cell.detailTextLabel.text = self.selectedImage.name; |
585 |
|
586 |
return cell; |
587 |
} |
588 |
} else { |
589 |
id <AddServerPlugin> plugin = [plugins objectAtIndex:indexPath.section - 2]; |
590 |
return [plugin tableView:tableView cellForRowAtIndexPath:indexPath]; |
591 |
} |
592 |
} |
593 |
|
594 |
#pragma mark - |
595 |
#pragma mark Table view delegate |
596 |
|
597 |
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { |
598 |
if (indexPath.section == kNodeDetails && indexPath.row == kImageRow) { |
599 |
SimpleImagePickerViewController *vc = [[SimpleImagePickerViewController alloc] initWithNibName:@"SimpleImagePickerViewController" bundle:nil]; |
600 |
vc.mode = kModeChooseImage; |
601 |
vc.account = self.account; |
602 |
vc.selectedImageId = selectedImage.identifier; |
603 |
vc.delegate = self; |
604 |
[self.navigationController pushViewController:vc animated:YES]; |
605 |
[vc release]; |
606 |
} |
607 |
} |
608 |
|
609 |
#pragma mark - |
610 |
#pragma mark Text Field Delegate |
611 |
|
612 |
- (BOOL)textFieldShouldReturn:(UITextField *)textField { |
613 |
[textField resignFirstResponder]; |
614 |
return NO; |
615 |
} |
616 |
|
617 |
#pragma mark - |
618 |
#pragma mark Error Alert |
619 |
|
620 |
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { |
621 |
if (buttonIndex == 1) { // details button |
622 |
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { |
623 |
logEntryModalViewController.modalPresentationStyle = UIModalPresentationFormSheet; |
624 |
} |
625 |
[self.serversViewController presentModalViewController:logEntryModalViewController animated:YES]; |
626 |
[logEntryModalViewController release]; |
627 |
} |
628 |
} |
629 |
|
630 |
- (void)alert:(NSString *)message request:(OpenStackRequest *)request { |
631 |
if (request.responseStatusCode == 0) { |
632 |
[self failOnBadConnection]; |
633 |
} else { |
634 |
logEntryModalViewController = [[LogEntryModalViewController alloc] initWithNibName:@"LogEntryModalViewController" bundle:nil]; |
635 |
logEntryModalViewController.logEntry = [[[APILogEntry alloc] initWithRequest:request] autorelease]; |
636 |
logEntryModalViewController.requestDescription = [logEntryModalViewController.logEntry requestDescription]; |
637 |
logEntryModalViewController.responseDescription = [logEntryModalViewController.logEntry responseDescription]; |
638 |
logEntryModalViewController.requestMethod = [logEntryModalViewController.logEntry requestMethod]; |
639 |
logEntryModalViewController.url = [[logEntryModalViewController.logEntry url] description]; |
640 |
|
641 |
// present an alert with a Details button to show the API log entry |
642 |
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:message delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Details", nil]; |
643 |
[alert show]; |
644 |
[alert release]; |
645 |
} |
646 |
} |
647 |
|
648 |
#pragma mark - |
649 |
#pragma mark Memory management |
650 |
|
651 |
- (void)dealloc { |
652 |
[account release]; |
653 |
[serverCountSlider release]; |
654 |
[flavorSlider release]; |
655 |
[nameTextField release]; |
656 |
[serverNumbersLabel release]; |
657 |
[selectedImage release]; |
658 |
[plugins release]; |
659 |
[serversViewController release]; |
660 |
[createServerObservers release]; |
661 |
[accountHomeViewController release]; |
662 |
[super dealloc]; |
663 |
} |
664 |
|
665 |
@end |