Statistics
| Branch: | Tag: | Revision:

root / Classes / AddServerViewController.m @ 9fee07a6

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