Statistics
| Branch: | Tag: | Revision:

root / Classes / ServerViewController.m @ 4b4b833e

History | View | Annotate | Download (58.1 kB)

1
//
2
//  ServerViewController.m
3
//  OpenStack
4
//
5
//  Created by Mike Mayo on 10/7/10.
6
//  The OpenStack project is provided under the Apache 2.0 license.
7
//
8

    
9
#import "ServerViewController.h"
10
#import "Server.h"
11
#import "OpenStackAccount.h"
12
#import "Image.h"
13
#import "Flavor.h"
14
#import "UIViewController+Conveniences.h"
15
#import "PingIPAddressViewController.h"
16
#import "RenameServerViewController.h"
17
#import "ResetServerAdminPasswordViewController.h"
18
#import "AccountManager.h"
19
#import "NSObject+Conveniences.h"
20
#import "ServersViewController.h"
21
#import "ResizeServerViewController.h"
22
#import "RebuildServerViewController.h"
23
#import "AnimatedProgressView.h"
24
#import "OpenStackRequest.h"
25
#import "RateLimit.h"
26
#import "ManageBackupScheduleViewController.h"
27
#import "SimpleImagePickerViewController.h"
28
#import "UIColor+MoreColors.h"
29
#import <QuartzCore/QuartzCore.h>
30
#import "AccountHomeViewController.h"
31
#import "ServersOnHostViewController.h"
32
#import "NameAndStatusTitleView.h"
33
#import "APICallback.h"
34
#import "Provider.h"
35
#import "Analytics.h"
36

    
37
// TODO: bring back host id section as "n servers on this (physical) host"
38

    
39
@implementation ServerViewController
40

    
41
@synthesize server, account, tableView, selectedIPAddressIndexPath, serversViewController, selectedServerIndexPath, accountHomeViewController;
42

    
43
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
44
    //CGPoint point = scrollView.contentOffset;
45
    //CGRect tr = titleView.frame;
46
    //CGRect ar = actionView.frame;
47
    /*
48
    if (previousScrollPoint.y - point.y < 0) {
49
        titleView.frame = CGRectMake(tr.origin.x, (previousScrollPoint.y - point.y) / 3.0, tr.size.width, tr.size.height);
50
        actionView.frame = CGRectMake(ar.origin.x, 64 + ((previousScrollPoint.y - point.y) / 2.0), ar.size.width, ar.size.height);
51
    }
52
    */
53
    [UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction animations:^{
54
        CGRect tr = titleView.frame;
55
        CGRect ar = actionView.frame;
56
        
57
        if (self.tableView.contentOffset.y >= 150) {
58
            tr.origin.y = -67;
59
            ar.origin.y = -136.0;
60
        } else {
61
            tr.origin.y = 0;
62
            ar.origin.y = 64;
63
        }
64
        titleView.frame = tr;
65
        actionView.frame = ar;
66
    } completion:^(BOOL finished) {
67
    }];
68
    
69
}
70

    
71
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
72
    return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
73
}
74

    
75
- (void)setParallaxViews {
76
    
77
    if (!titleView) {    
78
        // make an offset for the table
79
        self.tableView.tableHeaderView = [[[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 134.0)] autorelease];
80

    
81
        NSString *logoFilename = @"";
82
        if ([self.server.image respondsToSelector:@selector(logoPrefix)]) {
83
            logoFilename = [[self.server.image logoPrefix] stringByAppendingString:@"-icon.png"];
84
        }
85
        
86
        titleView = [[NameAndStatusTitleView alloc] initWithEntity:self.server logoFilename:logoFilename];
87
        [self.view addSubview:titleView];
88
        [titleView setNeedsDisplay];
89
    }
90
    
91
    actionView.backgroundColor = [UIColor colorWithRed:0.929 green:0.929 blue:0.929 alpha:1];
92
    actionView.clipsToBounds = NO;
93
    [actionView.layer setShadowColor:[[UIColor blackColor] CGColor]];
94
    [actionView.layer setShadowRadius:2.0f];
95
    [actionView.layer setShadowOffset:CGSizeMake(1, 1)];
96
    [actionView.layer setShadowOpacity:0.8f];
97
    
98
    /*
99
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
100
        // line up the action buttons with the table view on iPad
101
        CGRect rebootFrame = rebootButton.frame;
102
        rebootFrame.origin.x += 14;
103
        rebootButton.frame = rebootFrame;
104
    }
105
     */
106
    
107
    
108
    [self scrollViewDidScroll:self.tableView];
109
}
110

    
111
- (void)setBackgroundView {
112
    if (self.server) {
113
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
114
            UIView *backgroundContainer = [[UIView alloc] init];
115
            backgroundContainer.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
116
            backgroundContainer.backgroundColor = [UIColor iPadTableBackgroundColor];
117
            
118
            NSString *logoFilename = @"";
119
            if ([self.server.image respondsToSelector:@selector(logoPrefix)]) {
120
                logoFilename = [[self.server.image logoPrefix] stringByAppendingString:@"-large.png"];
121
            }
122
            
123
            UIImageView *osLogo = [[UIImageView alloc] initWithImage:[UIImage imageNamed:logoFilename]];
124
            osLogo.contentMode = UIViewContentModeScaleAspectFit;
125
            osLogo.frame = CGRectMake(100.0, 100.0, 1000.0, 1000.0);
126
            if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
127
                osLogo.alpha = 0.3;        
128
            }
129
            
130
            [backgroundContainer addSubview:osLogo];
131
            [osLogo release];
132
            
133
            tableView.backgroundView = backgroundContainer;
134
            [backgroundContainer release];
135
            
136
        } else { 
137
            self.tableView.backgroundView = nil;
138
            /*
139
            NSString *logoFilename = [[server.image logoPrefix] stringByAppendingString:@"-large.png"];    
140
            UIImageView *osLogo = [[UIImageView alloc] initWithImage:[UIImage imageNamed:logoFilename]];
141
            osLogo.contentMode = UIViewContentModeScaleAspectFit;
142
            osLogo.frame = CGRectMake(0.0, 0.0, 320.0, 480.0);
143
            tableView.backgroundView = osLogo;
144
            [osLogo release];
145
             */
146
        }
147
    } else {
148
        UIView *container = [[UIView alloc] init];
149
        container.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
150
        container.backgroundColor = [UIColor iPadTableBackgroundColor];
151

    
152
        UILabel *label = [[UILabel alloc] init];
153
        label.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
154
        label.backgroundColor = [UIColor clearColor];
155
        label.textColor = [UIColor emptyCollectionGrayColor];
156
        label.font = [UIFont boldSystemFontOfSize:18.0];
157
        label.text = @"No server selected";
158
        label.textAlignment = UITextAlignmentCenter;
159
        [container addSubview:label];
160
        [label release];
161
        
162
        tableView.backgroundView = container;
163
        [container release];
164
    }
165
}
166

    
167
- (void)refreshCountdownLabels:(NSTimer *)timer {
168
    [self refreshLimitStrings]; 
169
    
170
    if (actionsExpanded) {
171
        NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
172
        
173
        if (![rebootCountdown isEqualToString:@""]) {
174
            [indexPaths addObject:[NSIndexPath indexPathForRow:kReboot inSection:kActions]];
175
        }
176
        if (![renameCountdown isEqualToString:@""]) {
177
            [indexPaths addObject:[NSIndexPath indexPathForRow:kRename inSection:kActions]];
178
        }
179
        if (![resizeCountdown isEqualToString:@""]) {
180
            [indexPaths addObject:[NSIndexPath indexPathForRow:kResize inSection:kActions]];
181
        }
182
        if (![changePasswordCountdown isEqualToString:@""]) {
183
            [indexPaths addObject:[NSIndexPath indexPathForRow:kChangePassword inSection:kActions]];
184
        }
185
        if (![backupsCountdown isEqualToString:@""]) {
186
            [indexPaths addObject:[NSIndexPath indexPathForRow:kBackups inSection:kActions]];
187
        }
188
        if (![rebuildCountdown isEqualToString:@""]) {
189
            [indexPaths addObject:[NSIndexPath indexPathForRow:kRebuild inSection:kActions]];
190
        }
191
        if (![deleteCountdown isEqualToString:@""]) {
192
            [indexPaths addObject:[NSIndexPath indexPathForRow:kDelete inSection:kActions]];
193
        }
194

    
195
        [self.tableView reloadData];
196
        [indexPaths release];        
197
    }
198

    
199
}
200

    
201
- (void)refreshLimitStrings {
202

    
203
    RateLimit *rebootLimit = [OpenStackRequest softRebootServerLimit:self.account server:self.server];
204
    if (rebootLimit && rebootLimit.remaining == 0) {
205
        rebootCountdown = [ServerViewController timeUntilDate:rebootLimit.resetTime];
206
    }    
207
    
208
    RateLimit *renameLimit = [OpenStackRequest renameServerLimit:self.account server:self.server];
209
    if (renameLimit && renameLimit.remaining == 0) {
210
        renameCountdown = [ServerViewController timeUntilDate:renameLimit.resetTime];
211
    }
212

    
213
    RateLimit *resizeLimit = [OpenStackRequest resizeServerLimit:self.account server:self.server];
214
    if (resizeLimit && resizeLimit.remaining == 0) {
215
        resizeCountdown = [ServerViewController timeUntilDate:resizeLimit.resetTime];
216
    }
217
    
218
    RateLimit *changePasswordLimit = [OpenStackRequest changeServerAdminPasswordLimit:self.account server:self.server];
219
    if (changePasswordLimit && changePasswordLimit.remaining == 0) {
220
        changePasswordCountdown = [ServerViewController timeUntilDate:changePasswordLimit.resetTime];
221
    }
222

    
223
    RateLimit *backupsLimit = [OpenStackRequest updateBackupScheduleLimit:self.account server:self.server];
224
    if (backupsLimit && backupsLimit.remaining == 0) {
225
        backupsCountdown = [ServerViewController timeUntilDate:backupsLimit.resetTime];
226
    }
227

    
228
    RateLimit *rebuildLimit = nil; // [OpenStackRequest rebuildServerLimit:self.account server:self.server];
229
    if (rebuildLimit && rebuildLimit.remaining == 0) {
230
        rebuildCountdown = [ServerViewController timeUntilDate:rebuildLimit.resetTime];
231
    }
232

    
233
    RateLimit *deleteLimit = [OpenStackRequest deleteServerLimit:self.account server:self.server];
234
    if (deleteLimit && deleteLimit.remaining == 0) {
235
        deleteCountdown = [ServerViewController timeUntilDate:deleteLimit.resetTime];
236
    }
237
}
238

    
239
- (void)pollServer {
240
    pollRequest = [OpenStackRequest getServerRequest:self.account serverId:self.server.identifier];
241
    polling = YES;
242
    pollRequest.delegate = self;
243
    pollRequest.didFinishSelector = @selector(getServerSucceeded:);
244
    pollRequest.didFailSelector = @selector(getServerFailed:);
245
    [pollRequest startAsynchronous];
246
    [titleView setNeedsDisplay];
247
}
248

    
249
- (void)getServerSucceeded:(OpenStackRequest *)request {
250
    if ([request isSuccess]) {
251
        self.server = [request server];
252
        
253
        NSLog(@"flavor id: %@", self.server.flavorId);
254
        
255
        self.server.flavor = [self.account.flavors objectForKey:self.server.flavorId];
256
        
257
        NSLog(@"server flavor: %@", self.server.flavor);
258
        
259
        self.server.image = [self.account.images objectForKey:self.server.imageId];
260
        
261
        [self.account.servers setObject:server forKey:self.server.identifier];
262
        [self.account persist];
263
        
264
        NSLog(@"polling server worked. %i, %@", self.server.progress, self.server.status);
265
        [progressView setProgress:self.server.progress animated:YES];
266
        
267
        if ([self.server.status isEqualToString:@"VERIFY_RESIZE"]) {
268
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Resize Complete" message:@"Confirming the resize will destroy the saved copy of your original server." delegate:self cancelButtonTitle:@"Decide Later" otherButtonTitles:@"Confirm Resize", @"Revert to Previous Size", nil];
269
            [alert show];
270
            [alert release];
271
        }
272
    }
273
    if (kOverview >= 0) {
274
        [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:kStatus inSection:kOverview]] withRowAnimation:UITableViewRowAnimationNone];
275
    }
276
    if ([self.server shouldBePolled]) {
277
        [self pollServer];
278
    } else {
279
        polling = NO;        
280
        //[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:kStatus inSection:kOverview]] withRowAnimation:UITableViewRowAnimationNone];
281
        [self.tableView reloadData];
282
        [self setBackgroundView];
283
        if ([self.server.image respondsToSelector:@selector(logoPrefix)]) {
284
            titleView.logoView.image = [UIImage imageNamed:[[self.server.image logoPrefix] stringByAppendingString:@"-icon.png"]];
285
        }
286
        
287
        if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
288
            [self.serversViewController.tableView reloadData];
289
        }
290
        
291
    }
292
    titleView.entity = self.server;
293
    [titleView setNeedsDisplay];
294
    
295
}
296

    
297
- (void)getServerFailed:(OpenStackRequest *)request {
298
    NSLog(@"polling server failed. trying again.");
299
    [self pollServer];
300
}
301

    
302
#pragma mark -
303
#pragma mark View lifecycle
304

    
305
- (void)viewDidLoad {
306
    [super viewDidLoad];
307
    
308
    actionsExpanded = YES;
309

    
310
    previousScrollPoint = CGPointZero;
311
    titleView = nil;
312
    
313
    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
314
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
315

    
316
    UIApplication *application = [UIApplication sharedApplication];
317
    NSURL *url = [NSURL URLWithString:@"ssh://123.123.123.123"];
318
    if ([application canOpenURL:url]) {
319
        ipAddressActionSheet = [[UIActionSheet alloc] initWithTitle:selectedIPAddress delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Ping IP Address", @"Copy to Pasteboard", @"Open in Safari", @"Open in SSH Client", nil];
320
    } else {
321
        ipAddressActionSheet = [[UIActionSheet alloc] initWithTitle:selectedIPAddress delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Ping IP Address", @"Copy to Pasteboard", @"Open in Safari", nil];    
322
    }
323
    
324
    rebootActionSheet = [[UIActionSheet alloc] initWithTitle:@"A soft reboot performs a graceful shutdown of your system.  A hard reboot is the equivalent of unplugging your server." delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Perform Hard Reboot" otherButtonTitles:@"Perform Soft Reboot", nil];
325
    deleteActionSheet = [[UIActionSheet alloc] initWithTitle:@"Are you sure you want to delete this server?  This operation cannot be undone and you will lose all backup images." delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete Server" otherButtonTitles:nil];    
326
    progressView = [[AnimatedProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
327
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
328
        CGRect rect = progressView.frame;
329
        rect.size.width = 440.0;
330
        progressView.frame = rect;
331
    }
332
    
333
    actionsArrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"arrow-up.png"]];
334
    actionsArrow.highlightedImage = [UIImage imageNamed:@"arrow-up-highlighted.png"];
335
    actionsArrow.transform = CGAffineTransformMakeRotation(180.0 * M_PI / 180.0);    
336
}
337

    
338
- (void)viewWillAppear:(BOOL)animated {
339
    [super viewWillAppear:animated];    
340
    self.navigationItem.title = [self.account.provider isRackspace] ? @"Cloud Server" : @"Server";
341
    [self setBackgroundView];
342
    
343
    if (self.server) {
344
        [self setParallaxViews];
345
    } else {
346
        actionView.alpha = 0;
347
        rebootButton.enabled = NO;
348
        pingButton.enabled = NO;
349
    }
350
}
351

    
352
- (void)viewDidAppear:(BOOL)animated {
353
    [super viewDidAppear:animated];
354
    rebootCountdown = @"";
355
    renameCountdown = @"";
356
    resizeCountdown = @"";
357
    changePasswordCountdown = @"";
358
    backupsCountdown = @"";
359
    rebuildCountdown = @"";
360
    deleteCountdown = @"";
361
    
362
    [self refreshLimitStrings];
363
    
364
    // handle success
365
    if (!getLimitsSucceededObserver) {
366
        getLimitsSucceededObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"getLimitsSucceeded" object:self.account
367
                                                                                        queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
368
        {
369
            OpenStackRequest *request = [notification.userInfo objectForKey:@"request"];
370
            self.account.rateLimits = [request rateLimits];
371
            [[NSNotificationCenter defaultCenter] removeObserver:getLimitsSucceededObserver];
372
        }];
373
        
374
        getImageSucceededObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"getImageSucceeded" object:nil
375
                                                                                       queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
376
        {
377
            Image *image = [self.account.images objectForKey:self.server.imageId];
378
            self.server.image = image;
379

    
380
            /*
381
            NSString *logoFilename = [[server.image logoPrefix] stringByAppendingString:@"-large.png"];    
382
            UIImageView *osLogo = [[UIImageView alloc] initWithImage:[UIImage imageNamed:logoFilename]];
383
            osLogo.contentMode = UIViewContentModeScaleAspectFit;
384
            osLogo.frame = CGRectMake(0.0, 0.0, 320.0, 480.0);
385
            tableView.backgroundView = osLogo;
386
            [osLogo release];
387
             */
388
            [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:kImage inSection:kDetails]] withRowAnimation:UITableViewRowAnimationNone];
389
        }];
390
        
391
        getImageFailedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"getImageFailed" object:self.server.imageId
392
                                                                                    queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
393
        {
394
            NSLog(@"loading image failed");
395
        }];
396
        
397
        updateBackupScheduleSucceededObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"updateBackupScheduleSucceeded" object:self.server
398
                                                                                                   queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
399
        {
400
            [self hideToolbarActivityMessage];
401
        }];
402
        
403
        updateBackupScheduleFailedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"updateBackupScheduleFailed" object:self.account
404
                                                                                                queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
405
        {
406
            [self hideToolbarActivityMessage];
407
            [self alert:@"There was a problem updating your backup schedule." request:[notification.userInfo objectForKey:@"request"]];
408
        }];    
409
        
410
        resizeServerSucceededObserver = [[NSNotificationCenter defaultCenter] addObserverForName:[self.account.manager notificationName:@"resizeServerSucceeded" identifier:self.server.identifier] object:nil
411
                                                                                           queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
412
        {
413
            [self hideToolbarActivityMessage];
414
            self.server.status = @"QUEUE_RESIZE";
415
            if (kOverview >= 0) {
416
                [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:kStatus inSection:kOverview]] withRowAnimation:UITableViewRowAnimationNone];
417
            }
418
            [self pollServer];        
419
        }];
420
        
421
        resizeServerFailedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:[self.account.manager notificationName:@"resizeServerFailed" identifier:self.server.identifier] object:nil
422
                                                                                        queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
423
        {
424
            [self hideToolbarActivityMessage];
425
            [self alert:@"There was a problem resizing this server." request:[notification.userInfo objectForKey:@"request"]];
426
        }];
427
        
428
        confirmResizeSucceededObserver = [[NSNotificationCenter defaultCenter] addObserverForName:[self.account.manager notificationName:@"confirmResizeServerSucceeded" identifier:self.server.identifier] object:nil
429
                                                                                            queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
430
        {
431
            [self hideToolbarActivityMessage];
432
            [self pollServer];        
433
        }];
434
        
435
        confirmResizeFailedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:[self.account.manager notificationName:@"confirmResizeServerFailed" identifier:self.server.identifier] object:nil
436
                                                                                         queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
437
        {
438
            [self hideToolbarActivityMessage];
439
            [self alert:@"There was a problem confirming the resize." request:[notification.userInfo objectForKey:@"request"]];
440
        }];
441
        
442
        revertResizeSucceededObserver = [[NSNotificationCenter defaultCenter] addObserverForName:[self.account.manager notificationName:@"revertResizeServerSucceeded" identifier:self.server.identifier] object:nil
443
                                                                                           queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
444
        {
445
            [self hideToolbarActivityMessage];
446
            [self pollServer];        
447
        }];
448
        
449
        revertResizeFailedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:[self.account.manager notificationName:@"revertResizeServerFailed" identifier:self.server.identifier] object:nil
450
                                                                                        queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
451
        {
452
            [self hideToolbarActivityMessage];
453
            [self alert:@"There was a problem reverting the resize." request:[notification.userInfo objectForKey:@"request"]];
454
        }];
455
        
456
        rebuildSucceededObserver = [[NSNotificationCenter defaultCenter] addObserverForName:[self.account.manager notificationName:@"rebuildServerSucceeded" identifier:self.server.identifier] object:nil
457
                                                                                      queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
458
        {
459
            [self hideToolbarActivityMessage];
460
            [self pollServer];        
461
        }];
462
        
463
        rebuildFailedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:[self.account.manager notificationName:@"rebuildServerFailed" identifier:self.server.identifier] object:nil
464
                                                                                   queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
465
        {
466
            [self hideToolbarActivityMessage];
467
            [self alert:@"There was a problem rebuilding this server." request:[notification.userInfo objectForKey:@"request"]];
468
        }];
469
    }
470
    
471
    // if the server has less than 100% progress, we need to poll it.
472
    // normally, we would do this through the account manager, but this is the only
473
    // place we'll need the information.  the downside to this is that we may
474
    // temporarily show a server as building when it's already complete, but that's
475
    // better than the possibility of polling several servers at once when a user
476
    // decides to add multiple nodes
477
    if (self.server && [server shouldBePolled]) {
478
        [self pollServer];
479
    }
480
    
481
    if (!countdownTimer) {        
482
        countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(refreshCountdownLabels:) userInfo:nil repeats:YES];
483
    }    
484
    
485
    if (self.server && [self.server.status isEqualToString:@"VERIFY_RESIZE"]) {
486
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Resize Complete" message:@"Confirming the resize will destroy the saved copy of your original server." delegate:self cancelButtonTitle:@"Decide Later" otherButtonTitles:@"Confirm Resize", @"Revert to Previous Size", nil];
487
        [alert show];
488
        [alert release];
489
    }    
490
}
491

    
492
- (void)viewDidUnload {
493
    [super viewDidUnload];
494
    titleView = nil;
495
    actionView = nil;
496
    rebootButton = nil;
497
    pingButton = nil;
498
    self.tableView = nil;
499
    
500
    [[NSNotificationCenter defaultCenter] removeObserver:rebootSucceededObserver];
501
    [[NSNotificationCenter defaultCenter] removeObserver:rebootFailedObserver];
502
    [[NSNotificationCenter defaultCenter] removeObserver:getLimitsSucceededObserver];
503
    [[NSNotificationCenter defaultCenter] removeObserver:changeAdminPasswordSucceededObserver];
504
    [[NSNotificationCenter defaultCenter] removeObserver:changeAdminPasswordFailedObserver];
505
    [[NSNotificationCenter defaultCenter] removeObserver:deleteServerSucceededObserver];
506
    [[NSNotificationCenter defaultCenter] removeObserver:deleteServerFailedObserver];
507
    [[NSNotificationCenter defaultCenter] removeObserver:getImageSucceededObserver];
508
    [[NSNotificationCenter defaultCenter] removeObserver:getImageFailedObserver];
509
    [[NSNotificationCenter defaultCenter] removeObserver:updateBackupScheduleSucceededObserver];
510
    [[NSNotificationCenter defaultCenter] removeObserver:updateBackupScheduleFailedObserver];
511
    [[NSNotificationCenter defaultCenter] removeObserver:resizeServerSucceededObserver];
512
    [[NSNotificationCenter defaultCenter] removeObserver:resizeServerFailedObserver];
513
    [[NSNotificationCenter defaultCenter] removeObserver:confirmResizeSucceededObserver];
514
    [[NSNotificationCenter defaultCenter] removeObserver:confirmResizeFailedObserver];
515
    [[NSNotificationCenter defaultCenter] removeObserver:revertResizeSucceededObserver];
516
    [[NSNotificationCenter defaultCenter] removeObserver:revertResizeFailedObserver];
517
    [[NSNotificationCenter defaultCenter] removeObserver:rebuildSucceededObserver];
518
    [[NSNotificationCenter defaultCenter] removeObserver:rebuildFailedObserver];
519
}
520

    
521
- (void)viewWillDisappear:(BOOL)animated {
522
    [super viewWillDisappear:animated];
523
    
524
    if (polling && pollRequest != nil) {
525
        pollRequest.delegate = nil;
526
    }
527
    
528
//    [self.serversViewController.tableView deselectRowAtIndexPath:self.selectedServerIndexPath animated:YES];
529
    
530
    [countdownTimer invalidate];
531
    countdownTimer = nil;
532
}
533

    
534
#pragma mark -
535
#pragma mark Table view data source
536

    
537
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
538
    //return self.server ? 5 : 0;
539
    return self.server ? 4 : 0;
540
}
541

    
542
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
543
    if (section == kOverview) {
544
        return 2;
545
    } else if (section == kDetails) {
546
        return 2;
547
    } else if (section == kIPAddresses) {
548
        NSArray *publicIPs = [self.server.addresses objectForKey:@"public"];
549
        NSArray *privateIPs = [self.server.addresses objectForKey:@"private"];
550
        return [publicIPs count] + [privateIPs count];
551
    } else if (section == kActions) {
552
        //return actionsExpanded ? 8 : 1;
553
        return 6;
554
    } else {
555
        return 0;
556
    }
557
}
558

    
559
- (CGFloat)findLabelHeight:(NSString*)text font:(UIFont *)font {
560
    CGSize textLabelSize;
561
    
562
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
563
        textLabelSize = CGSizeMake(530.0, 9000.0f);
564
    } else {
565
        textLabelSize = CGSizeMake(230.0, 9000.0f);
566
    }
567
    
568
    // pad \n\n to fix layout bug
569
    CGSize stringSize = [text sizeWithFont:font constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeCharacterWrap];
570
    return stringSize.height;
571
}
572

    
573
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
574
    
575
    static NSString *CellIdentifier = @"Cell";
576
    
577
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
578
    if (cell == nil) {
579
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
580
        //cell.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.93];
581
        cell.textLabel.backgroundColor = [UIColor clearColor];
582
        cell.detailTextLabel.backgroundColor = [UIColor clearColor];
583
        cell.detailTextLabel.adjustsFontSizeToFitWidth = YES;
584
        cell.detailTextLabel.lineBreakMode = UILineBreakModeCharacterWrap;
585
        cell.detailTextLabel.textAlignment = UITextAlignmentRight;
586

    
587
    }
588
    
589
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
590
        cell.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.8];
591
    }
592
    
593
    cell.textLabel.textColor = [UIColor blackColor];
594
    cell.accessoryView = nil;
595

    
596
    if (indexPath.section == kOverview) {
597
        cell.accessoryType = UITableViewCellAccessoryNone;
598
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
599
        cell.accessoryView = nil;
600

    
601
        if (indexPath.row == kName) {
602
            cell.textLabel.text = @"Name";
603
            cell.detailTextLabel.text = server.name;
604
        } else if (indexPath.row == kStatus) {
605
            if ([server.status isEqualToString:@"BUILD"]) {
606
                cell.textLabel.text = @"Building";
607
                cell.detailTextLabel.text = @"";
608
                cell.accessoryView = progressView;
609
            } else if ([server.status isEqualToString:@"QUEUE_RESIZE"]) {
610
                cell.textLabel.text = @"Queueing";
611
                cell.detailTextLabel.text = @"";
612
                cell.accessoryView = progressView;
613
            } else if ([server.status isEqualToString:@"PREP_RESIZE"]) {
614
                cell.textLabel.text = @"Preparing";
615
                cell.detailTextLabel.text = @"";
616
                cell.accessoryView = progressView;
617
            } else if ([server.status isEqualToString:@"RESIZE"]) {
618
                cell.textLabel.text = @"Resizing";
619
                cell.detailTextLabel.text = @"";
620
                cell.accessoryView = progressView;
621
            } else if ([server.status isEqualToString:@"VERIFY_RESIZE"]) {
622
                cell.textLabel.text = @"Status";
623
                cell.detailTextLabel.text = @"Resize Complete";
624
            } else if ([server.status isEqualToString:@"REBUILD"]) {
625
                cell.textLabel.text = @"Rebuilding";
626
                cell.detailTextLabel.text = @"";
627
                cell.accessoryView = progressView;
628
            } else {
629
                cell.textLabel.text = @"Status";
630
                cell.detailTextLabel.text = server.status;
631
            }
632
            
633
        } else if (indexPath.row == kHostId) {
634
            cell.textLabel.text = @"Host ID";
635
            cell.detailTextLabel.text = server.hostId;
636
            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
637
            cell.selectionStyle = UITableViewCellSelectionStyleBlue;
638
        }
639
    } else if (indexPath.section == kDetails) {
640
        cell.accessoryType = UITableViewCellAccessoryNone;
641
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
642
        cell.accessoryView = nil;
643

    
644
        if (indexPath.row == kImage) {
645
            cell.textLabel.text = @"Image";
646
            cell.detailTextLabel.text = server.image.name; 
647
        } else if (indexPath.row == kMemory) {
648
            cell.textLabel.text = @"Size";
649
            cell.detailTextLabel.text = [NSString stringWithFormat:@"%i MB RAM, %i GB Disk", self.server.flavor.ram, self.server.flavor.disk];
650
        } else if (indexPath.row == kDisk) {
651
            cell.textLabel.text = @"Disk";
652
            cell.detailTextLabel.text = [NSString stringWithFormat:@"%i GB", server.flavor.disk];
653
        }
654
    } else if (indexPath.section == kIPAddresses) {
655
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
656
        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
657
        cell.accessoryView = nil;
658
        
659
        NSArray *publicIPs = [server.addresses objectForKey:@"public"];
660
        NSArray *privateIPs = [server.addresses objectForKey:@"private"];
661
        
662
        if (indexPath.row < [publicIPs count]) {
663
            cell.textLabel.text = @"Public";
664
            cell.detailTextLabel.text = [publicIPs objectAtIndex:indexPath.row];
665
            if ([[publicIPs objectAtIndex:indexPath.row] isKindOfClass:[NSString class]]) {
666
                // v1.0 API
667
                cell.detailTextLabel.text = [publicIPs objectAtIndex:indexPath.row];
668
            } else {
669
                // v1.1 API
670
                cell.detailTextLabel.text = [[publicIPs objectAtIndex:indexPath.row] objectForKey:@"addr"];
671
            }            
672
        } else {
673
            cell.textLabel.text = @"Private";
674
            cell.detailTextLabel.text = [privateIPs objectAtIndex:indexPath.row - [publicIPs count]];
675
        }
676
    } else if (indexPath.section == kActions) {
677
        cell.textLabel.text = @"Actions";
678
        cell.detailTextLabel.text = @"";
679
        cell.accessoryView = nil;
680
        if (indexPath.row == kActionsRow) {
681
            cell.accessoryType = UITableViewCellAccessoryNone;
682
            cell.accessoryView = actionsArrow;
683
            cell.selectionStyle = UITableViewCellSelectionStyleBlue;
684
            cell.textLabel.textColor = actionsExpanded ? cell.detailTextLabel.textColor : [UIColor blackColor];
685
        } else {
686
            if (performingAction) {
687
                cell.textLabel.textColor = [UIColor grayColor];
688
                cell.selectionStyle = UITableViewCellSelectionStyleNone;
689
                cell.accessoryType = UITableViewCellAccessoryNone;
690
            } else {
691
                cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
692
                cell.selectionStyle = UITableViewCellSelectionStyleBlue;
693
            }
694
            
695
            if (indexPath.row == kReboot) {
696
                cell.textLabel.text = @"Reboot Server";
697
                if (![rebootCountdown isEqualToString:@""]) {
698
                    cell.detailTextLabel.text = rebootCountdown;
699
                    cell.accessoryType = UITableViewCellAccessoryNone;
700
                    cell.selectionStyle = UITableViewCellSelectionStyleNone;
701
                } else {
702
                    cell.detailTextLabel.text = @"";
703
                }
704
            } else if (indexPath.row == kRename) {
705
                cell.textLabel.text = @"Rename Server";
706
                if (renameCountdown && ![renameCountdown isEqualToString:@""]) {
707
                    cell.detailTextLabel.text = renameCountdown;
708
                    cell.accessoryType = UITableViewCellAccessoryNone;
709
                    cell.selectionStyle = UITableViewCellSelectionStyleNone;
710
                } else {
711
                    cell.detailTextLabel.text = @"";
712
                }
713
            } else if (indexPath.row == kResize) {
714
                cell.textLabel.text = @"Resize Server";
715
                if (![resizeCountdown isEqualToString:@""]) {
716
                    cell.detailTextLabel.text = resizeCountdown;
717
                    cell.accessoryType = UITableViewCellAccessoryNone;
718
                    cell.selectionStyle = UITableViewCellSelectionStyleNone;
719
                } else {
720
                    cell.detailTextLabel.text = @"";
721
                }
722
            } else if (indexPath.row == kChangePassword) {
723
                cell.textLabel.text = @"Change Root Password";
724
                if (![changePasswordCountdown isEqualToString:@""]) {
725
                    cell.detailTextLabel.text = changePasswordCountdown;
726
                    cell.accessoryType = UITableViewCellAccessoryNone;
727
                    cell.selectionStyle = UITableViewCellSelectionStyleNone;
728
                } else {
729
                    cell.detailTextLabel.text = @"";
730
                }
731
            } else if (indexPath.row == kBackups) {
732
                cell.textLabel.text = @"Manage Backup Schedules";
733
                if (![backupsCountdown isEqualToString:@""]) {
734
                    cell.detailTextLabel.text = backupsCountdown;
735
                    cell.accessoryType = UITableViewCellAccessoryNone;
736
                    cell.selectionStyle = UITableViewCellSelectionStyleNone;
737
                } else {
738
                    cell.detailTextLabel.text = @"";
739
                }
740
            } else if (indexPath.row == kRebuild) {
741
                cell.textLabel.text = @"Rebuild Server";
742
                if (![rebuildCountdown isEqualToString:@""]) {
743
                    cell.detailTextLabel.text = rebuildCountdown;
744
                    cell.accessoryType = UITableViewCellAccessoryNone;
745
                    cell.selectionStyle = UITableViewCellSelectionStyleNone;
746
                } else {
747
                    cell.detailTextLabel.text = @"";
748
                }
749
            } else if (indexPath.row == kDelete) {
750
                cell.textLabel.text = @"Delete Server";
751
                if (![deleteCountdown isEqualToString:@""]) {
752
                    cell.detailTextLabel.text = deleteCountdown;
753
                    cell.accessoryType = UITableViewCellAccessoryNone;
754
                    cell.selectionStyle = UITableViewCellSelectionStyleNone;
755
                } else {
756
                    cell.detailTextLabel.text = @"";
757
                }
758
            }
759
        }
760
    }
761
    
762
    return cell;
763
}
764

    
765
#pragma mark -
766
#pragma mark Table view delegate
767

    
768
- (void)reloadActionsTitleRow {
769
    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:kActions]] withRowAnimation:UITableViewRowAnimationNone];
770
}
771

    
772
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
773

    
774
    if (indexPath.section == kOverview) {
775
        if (indexPath.row == kHostId) {
776
            ServersOnHostViewController *vc = [[ServersOnHostViewController alloc] initWithNibName:@"ServersOnHostViewController" bundle:nil];
777
            vc.account = self.account;
778
            vc.hostID = self.server.hostId;
779
            [self.navigationController pushViewController:vc animated:YES];
780
            [vc release];
781
            
782
            // deselect the row
783
            [NSTimer scheduledTimerWithTimeInterval:1.0 target:self.tableView selector:@selector(reloadData) userInfo:nil repeats:NO];
784
        }
785
    } else if (indexPath.section == kIPAddresses) {
786
        NSArray *publicIPs = [server.addresses objectForKey:@"public"];
787
        NSArray *privateIPs = [server.addresses objectForKey:@"private"];
788
        if (indexPath.row < [publicIPs count]) {
789
            if ([[publicIPs objectAtIndex:indexPath.row] isKindOfClass:[NSString class]]) {
790
                selectedIPAddress = [publicIPs objectAtIndex:indexPath.row];
791
            } else {
792
                selectedIPAddress = [[publicIPs objectAtIndex:indexPath.row] objectForKey:@"addr"];
793
            }
794
        } else {
795
            if ([[privateIPs objectAtIndex:[publicIPs count] - indexPath.row] isKindOfClass:[NSString class]]) {
796
                selectedIPAddress = [privateIPs objectAtIndex:[publicIPs count] - indexPath.row];
797
            } else {
798
                selectedIPAddress = [[privateIPs objectAtIndex:[publicIPs count] - indexPath.row] objectForKey:@"addr"];
799
            }
800
        }
801
        selectedIPAddressIndexPath = indexPath;
802

    
803
        ipAddressActionSheet.title = selectedIPAddress;
804
        [ipAddressActionSheet showInView:self.view];
805
    } else if (indexPath.section == kActions) {
806
        if (indexPath.row == kActionsRow) {
807
            NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:7];
808
            for (int i = 1; i < 8; i++) {
809
                [indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:kActions]];
810
            }
811
            if (actionsExpanded) {
812
                actionsExpanded = NO;
813
                [tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
814
            } else {
815
                actionsExpanded = YES;
816
                [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationBottom];
817
                [tableView scrollToRowAtIndexPath:[indexPaths lastObject] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
818
            }
819
            [indexPaths release];
820
            [tableView deselectRowAtIndexPath:indexPath animated:YES];
821
            [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(reloadActionsTitleRow) userInfo:nil repeats:NO];
822
            
823
            // flip the arrow
824
            [UIView beginAnimations:nil context:NULL];
825
            [UIView setAnimationDuration:0.4];
826
            if (actionsExpanded) {
827
                actionsArrow.transform = CGAffineTransformMakeRotation(0.0);
828
            } else {
829
                actionsArrow.transform = CGAffineTransformMakeRotation(180.0 * M_PI / 180.0);
830
            }
831
            [UIView commitAnimations];            
832
            
833
        } else if (indexPath.row == kReboot) {
834
            if (!performingAction && [rebootCountdown isEqualToString:@""]) {
835
                [rebootActionSheet showInView:self.view];
836
            } else if (![rebootCountdown isEqualToString:@""]) {
837
                [self alert:nil message:@"Reboots are not available because the API rate limit has been reached for this account.  These actions will be available as soon as the countdown reaches zero."];
838
                [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
839
            } else {
840
                [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
841
            }
842
        } else if (indexPath.row == kRename) {
843
            if (!performingAction) {
844
                RenameServerViewController *vc = [[RenameServerViewController alloc] initWithNibName:@"RenameServerViewController" bundle:nil];
845
                vc.serverViewController = self;
846
                vc.actionIndexPath = indexPath;
847
                if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
848
                    vc.modalPresentationStyle = UIModalPresentationFormSheet;
849
                }                
850
                [self.navigationController presentModalViewController:vc animated:YES];
851
                [vc release];
852
            } else {
853
                [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
854
            }
855
        } else if (indexPath.row == kResize) {
856
            if (!performingAction) {
857
                ResizeServerViewController *vc = [[ResizeServerViewController alloc] initWithNibName:@"ResizeServerViewController" bundle:nil];
858
                vc.serverViewController = self;
859
                vc.actionIndexPath = indexPath;
860
                vc.account = account;
861
                vc.server = server;
862
                if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
863
                    vc.modalPresentationStyle = UIModalPresentationFormSheet;
864
                }                
865
                [self.navigationController presentModalViewController:vc animated:YES];
866
                [vc release];
867
            } else {
868
                [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
869
            }
870
        } else if (indexPath.row == kRebuild) {
871
            SimpleImagePickerViewController *vc = [[SimpleImagePickerViewController alloc] initWithNibName:@"SimpleImagePickerViewController" bundle:nil];
872
            vc.mode = kModeRebuildServer;
873
            vc.account = self.account;
874
            vc.selectedImageId = self.server.imageId;
875
            vc.serverViewController = self;
876
            vc.delegate = self;
877
            [self presentModalViewControllerWithNavigation:vc];
878
            [vc release];            
879
        } else if (indexPath.row == kChangePassword) {
880
            if (!performingAction) {
881
                ResetServerAdminPasswordViewController *vc = [[ResetServerAdminPasswordViewController alloc] initWithNibName:@"ResetServerAdminPasswordViewController" bundle:nil];
882
                vc.serverViewController = self;
883
                vc.actionIndexPath = indexPath;
884
                if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
885
                    vc.modalPresentationStyle = UIModalPresentationFormSheet;
886
                }                
887
                [self.navigationController presentModalViewController:vc animated:YES];
888
                [vc release];
889
            } else {
890
                [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
891
            }
892
        } else if (indexPath.row == kBackups) {            
893
            if (!performingAction) {
894
                ManageBackupScheduleViewController *vc = [[ManageBackupScheduleViewController alloc] initWithNibName:@"ManageBackupScheduleViewController" bundle:nil];
895
                vc.serverViewController = self;
896
                vc.actionIndexPath = indexPath;
897
                vc.account = self.account;
898
                vc.server = self.server;
899
                if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
900
                    vc.modalPresentationStyle = UIModalPresentationFormSheet;
901
                }                
902
                [self.navigationController presentModalViewController:vc animated:YES];
903
                [vc release];
904
            } else {
905
                [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
906
            }
907
        } else if (indexPath.row == kDelete) {
908
            if (!performingAction) {
909
                [deleteActionSheet showInView:self.view];
910
            } else {
911
                [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
912
            }
913
        }
914
    }
915
}
916

    
917
#pragma mark -
918
#pragma mark Action Sheet Delegate
919

    
920
- (void)changeAdminPassword:(NSString *)password {
921
    [self showToolbarActivityMessage:@"Changing password..."];        
922
    performingAction = YES;
923
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
924
    
925
    // handle success
926
    changeAdminPasswordSucceededObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"changeAdminPasswordSucceeded" object:self.server
927
                                                                                 queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
928
                               {
929
                                   [self hideToolbarActivityMessage];
930
                                   performingAction = NO;
931
                                   [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
932
                                   [[NSNotificationCenter defaultCenter] removeObserver:changeAdminPasswordSucceededObserver];
933
                                   [[NSNotificationCenter defaultCenter] removeObserver:changeAdminPasswordFailedObserver];
934
                               }];
935
    
936
    // handle failure
937
    changeAdminPasswordFailedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"changeAdminPasswordFailed" object:self.server 
938
                                                                              queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
939
                            {
940
                                [self alert:@"There was a problem changing the root password." request:[notification.userInfo objectForKey:@"request"]];
941
                                [self hideToolbarActivityMessage];
942
                                performingAction = NO;
943
                                [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
944
                                [[NSNotificationCenter defaultCenter] removeObserver:changeAdminPasswordSucceededObserver];
945
                                [[NSNotificationCenter defaultCenter] removeObserver:changeAdminPasswordFailedObserver];
946
                            }];
947
    
948
    [self.account.manager changeAdminPassword:self.server password:password];
949
}
950

    
951
- (void)removeServersListRow {
952
//    if ([self.account.servers count] == 0) {
953
        [self.serversViewController.tableView reloadData];
954
//    } else {
955
//        [self.serversViewController.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:selectedServerIndexPath] withRowAnimation:UITableViewRowAnimationLeft];
956
//    }
957
}
958

    
959
- (void)popViewController {
960
    [self.navigationController popViewControllerAnimated:YES];
961
}
962

    
963
- (void)deleteServer {
964
    [self showToolbarActivityMessage:@"Deleting server..."];        
965
    performingAction = YES;
966
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
967

    
968
    // handle success
969
    deleteServerSucceededObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"deleteServerSucceeded" object:self.server
970
                                                                                              queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
971
        {
972
            [self hideToolbarActivityMessage];
973
            performingAction = NO;
974
            [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
975
            
976
            NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithDictionary:self.account.servers];
977
            [dict removeObjectForKey:self.server.identifier];
978
            self.account.servers = [[[NSMutableDictionary alloc] initWithDictionary:dict] autorelease]; // TODO: release
979
            [self.account persist];
980
            [dict release];
981
             
982
            self.serversViewController.account.servers = self.account.servers;
983
            
984
            if ([self.account.servers count] == 0 && UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
985
                // on ipad, delete needs to get rid of the server on the main view
986
                self.server = nil;
987
                [self setBackgroundView];
988
                [self.tableView reloadData];
989
            } else {
990
                [self.navigationController popViewControllerAnimated:YES];
991
            }
992
            [self.serversViewController.tableView selectRowAtIndexPath:selectedServerIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
993
            [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(removeServersListRow) userInfo:nil repeats:NO];
994

    
995
            [[NSNotificationCenter defaultCenter] removeObserver:deleteServerSucceededObserver];
996
            [[NSNotificationCenter defaultCenter] removeObserver:deleteServerFailedObserver];
997
        }];
998
    
999
    // handle failure
1000
    deleteServerFailedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"deleteServerFailed" object:self.server 
1001
                                                                                           queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
1002
         {
1003
             [self alert:@"There was a problem deleting this server." request:[notification.userInfo objectForKey:@"request"]];
1004
             [self hideToolbarActivityMessage];
1005
             performingAction = NO;
1006
             [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
1007
             [[NSNotificationCenter defaultCenter] removeObserver:deleteServerFailedObserver];
1008
             [[NSNotificationCenter defaultCenter] removeObserver:deleteServerSucceededObserver];
1009
         }];
1010
    
1011
    [self.account.manager deleteServer:self.server];
1012
}
1013

    
1014
- (void)showAction:(NSString *)name {
1015
    [self showToolbarActivityMessage:name];
1016
    //performingAction = YES;
1017
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
1018
}
1019

    
1020
- (void)hideAction {
1021
    [self hideToolbarActivityMessage];
1022
    performingAction = NO;
1023
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
1024
}
1025

    
1026
- (void)renameServer:(NSString *)name {
1027
    [self showAction:@"Renaming server..."];
1028
    [[self.account.manager renameServer:self.server name:name] success:^(OpenStackRequest *request) {
1029
        [self hideAction];
1030
        [self.account refreshCollections];
1031
        self.server.name = name;
1032
        titleView.nameLabel.text = self.server.name;
1033
        [self.serversViewController.tableView reloadData];
1034
    } failure:^(OpenStackRequest *request) {
1035
        [self alert:@"There was a problem renaming this server." request:request];
1036
        [self hideAction];
1037
    }];
1038
}
1039

    
1040
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
1041
    if ([actionSheet isEqual:ipAddressActionSheet]) {
1042

    
1043
        UIApplication *application = [UIApplication sharedApplication];
1044
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"ssh://%@", selectedIPAddress]];
1045
        
1046
        if (buttonIndex == 0) { // Ping IP Address
1047
            
1048
            TrackEvent(CATEGORY_SERVER, EVENT_PINGED);
1049
            
1050
            PingIPAddressViewController *vc = [[PingIPAddressViewController alloc] initWithNibName:@"PingIPAddressViewController" bundle:nil ipAddress:selectedIPAddress];
1051
            vc.serverViewController = self;
1052
            if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
1053
                vc.modalPresentationStyle = UIModalPresentationPageSheet;
1054
            }                
1055
            [self.navigationController presentModalViewController:vc animated:YES];
1056
            [vc release];
1057
            [self.tableView deselectRowAtIndexPath:selectedIPAddressIndexPath animated:YES];
1058
        } else if (buttonIndex == 1) { // Copy to Pasteboard
1059
            UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
1060
            [pasteboard setString:selectedIPAddress];
1061
            [self.tableView deselectRowAtIndexPath:selectedIPAddressIndexPath animated:YES];
1062
        } else if (buttonIndex == 2) {
1063
            UIApplication *application = [UIApplication sharedApplication];
1064
            NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", selectedIPAddress]];
1065
            if ([application canOpenURL:url]) {
1066
                [application openURL:url];
1067
            }
1068
            [self.tableView deselectRowAtIndexPath:selectedIPAddressIndexPath animated:YES];
1069
        } else if (buttonIndex == 3 && [application canOpenURL:url]) {
1070
            [application openURL:url];
1071
        } else {
1072
            [self.tableView deselectRowAtIndexPath:selectedIPAddressIndexPath animated:YES];
1073
        }
1074
    } else if ([actionSheet isEqual:rebootActionSheet]) {
1075
        [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:kReboot inSection:kActions] animated:YES];
1076
        
1077
        if (buttonIndex != 2) {
1078
            [self showToolbarActivityMessage:@"Rebooting..."];        
1079
            performingAction = YES;
1080
            [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
1081
            
1082
            // handle success
1083
            rebootSucceededObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"rebootSucceeded" object:self.server
1084
                                                                                         queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
1085
            {
1086
                [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(pollServer) userInfo:nil repeats:NO];
1087
                [self hideToolbarActivityMessage];
1088
                performingAction = NO;
1089
                [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
1090
                [[NSNotificationCenter defaultCenter] removeObserver:rebootSucceededObserver];
1091
                [[NSNotificationCenter defaultCenter] removeObserver:rebootFailedObserver];
1092
            }];
1093
            
1094
            // handle failure
1095
            rebootFailedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"rebootFailed" object:self.server 
1096
                                                                                      queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
1097
            {
1098
                [self alert:@"There was a problem rebooting this server." request:[notification.userInfo objectForKey:@"request"]];
1099
                [self hideToolbarActivityMessage];
1100
                performingAction = NO;
1101
                [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationFade];
1102
                [[NSNotificationCenter defaultCenter] removeObserver:rebootSucceededObserver];
1103
                [[NSNotificationCenter defaultCenter] removeObserver:rebootFailedObserver];
1104
            }];
1105
            
1106
            if (buttonIndex == 0) {
1107
                [self.account.manager hardRebootServer:self.server];
1108
            } else if (buttonIndex == 1) {
1109
                [self.account.manager softRebootServer:self.server];
1110
            }
1111
        }
1112

    
1113
    } else if ([actionSheet isEqual:deleteActionSheet]) {
1114
        if (buttonIndex == 0) {
1115
            [self deleteServer];
1116
        }
1117
        [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:kDelete inSection:kActions] animated:YES];
1118
    }
1119
}
1120

    
1121
#pragma mark -
1122
#pragma mark Resize Alert Delegate
1123

    
1124
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
1125
    if (buttonIndex == 1) {
1126
        [self.account.manager confirmResizeServer:self.server];
1127
        [self showToolbarActivityMessage:@"Confirming resize..."];
1128
    } else if (buttonIndex == 2) {
1129
        [self.account.manager revertResizeServer:self.server];
1130
        [self showToolbarActivityMessage:@"Reverting resize..."];
1131
    }
1132
}
1133

    
1134
#pragma - Button Handlers
1135

    
1136
- (IBAction)rebootButtonPressed:(id)sender {
1137
    if (!performingAction && [rebootCountdown isEqualToString:@""]) {
1138
        [rebootActionSheet showInView:self.view];
1139
    } else if (![rebootCountdown isEqualToString:@""]) {
1140
        [self alert:nil message:@"Reboots are not available because the API rate limit has been reached for this account.  These actions will be available as soon as the countdown reaches zero."];
1141
    }
1142
}
1143

    
1144
- (IBAction)snapshotButtonPressed:(id)sender {
1145
    
1146
}
1147

    
1148
- (IBAction)pingIPButtonPressed:(id)sender {
1149
    if ([[server.addresses objectForKey:@"public"] count] > 0) {
1150
        
1151
        TrackEvent(CATEGORY_SERVER, EVENT_PINGED);
1152
        
1153
        NSString *ipAddress = [[server.addresses objectForKey:@"public"] objectAtIndex:0];
1154
        PingIPAddressViewController *vc = [[PingIPAddressViewController alloc] initWithNibName:@"PingIPAddressViewController" bundle:nil ipAddress:ipAddress];
1155
        vc.serverViewController = self;
1156
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
1157
            vc.modalPresentationStyle = UIModalPresentationPageSheet;
1158
        }                
1159
        [self.navigationController presentModalViewController:vc animated:YES];
1160
        [vc release];
1161
    }
1162
}
1163

    
1164
#pragma mark -
1165
#pragma mark Memory management
1166

    
1167
- (void)dealloc {
1168
    [server release];
1169
    [account release];
1170
    [tableView release];
1171
    [selectedIPAddressIndexPath release];
1172
    [ipAddressActionSheet release];
1173
    [rebootActionSheet release];
1174
    [deleteActionSheet release];
1175
    [serversViewController release];
1176
    [selectedServerIndexPath release];
1177
    [progressView release];
1178
    [actionsArrow release];
1179
    [accountHomeViewController release];
1180
    [super dealloc];
1181
}
1182

    
1183

    
1184
@end
1185