Added slices

Change-Id: I9dfaa9348fa82da844a04c0c2a58ce07e9fa3a28
diff --git a/src/app/directives/protected.directive.ts b/src/app/directives/protected.directive.ts
index 9ebe89e..8eac4fd 100644
--- a/src/app/directives/protected.directive.ts
+++ b/src/app/directives/protected.directive.ts
@@ -6,24 +6,16 @@
 import {Router} from '@angular/router';
 
 @Directive({
-  selector: '[xos-protected]',
+  selector: '[xosProtected]',
   providers: [AuthService]
 })
 export class ProtectedDirective implements OnDestroy {
-  private sub:any = null;
 
-  constructor(private authService:AuthService, private router:Router) {
-    console.log('protected directive');
-    if (!authService.isAuthenticated()) {
-      console.log('redirect');
+  constructor(private authService: AuthService, private router: Router) {
+    if (!this.authService.isAuthenticated()) {
       this.router.navigate(['/login']);
     }
-
-    // this.sub = this.authService.subscribe((val) => {
-    //   if (!val.authenticated) {
-    //     this.router.navigate(['LoggedoutPage']); // tells them they've been logged out (somehow)
-    //   }
-    // });
+    // TODO listen for logout
   }
 
   ngOnDestroy() {
diff --git a/src/app/hello.html b/src/app/hello.html
index 5652063..4c639ca 100644
--- a/src/app/hello.html
+++ b/src/app/hello.html
@@ -4,9 +4,7 @@
 
 <xos-logout></xos-logout>
 
-<div *ngFor="let instance of instances">
-  <b>{{instance.name}}</b>
-  <i>{{instance.isolation}}</i>
-  {{instance.slice}}
-  {{instance.node}}
+<div *ngFor="let slice of slices">
+  <b>{{slice.name}}</b>
+  <i>{{slice.backend_status}}</i>
 </div>
diff --git a/src/app/hello.spec.ts b/src/app/hello.spec.ts
index a0cc5a4..d127a2a 100644
--- a/src/app/hello.spec.ts
+++ b/src/app/hello.spec.ts
@@ -1,18 +1,19 @@
 /// <reference path="../../typings/index.d.ts"/>
 
+import { MockBackend } from '@angular/http/testing';
+import {TestBed, async} from '@angular/core/testing';
+import {Router} from '@angular/router';
 import {HelloComponent} from './hello';
 import {LogoutComponent} from './components/logout/logout.component';
-import {TestBed, async} from '@angular/core/testing';
 import {StyleConfig} from './config/style.config';
 import { Http, BaseRequestOptions } from '@angular/http';
-import { MockBackend } from '@angular/http/testing';
 import {CookieService} from 'angular2-cookie/services/cookies.service';
-import {Router} from '@angular/router';
 import {XosHttp} from './services/rest/xoshttp.service';
 import {InstanceStore} from './services/stores/instance.store';
 import {GlobalEvent} from './services/websockets/websocket.global';
 import {AuthService} from './services/rest/auth.service';
 import {InstanceService} from './services/rest/instance.service';
+import {SliceService} from './services/rest/slices.service';
 
 describe('hello component', () => {
   beforeEach(async(() => {
@@ -40,7 +41,8 @@
         InstanceStore,
         GlobalEvent,
         AuthService,
-        InstanceService
+        InstanceService,
+        SliceService
       ]
     });
     TestBed.compileComponents();
diff --git a/src/app/hello.ts b/src/app/hello.ts
index 8757cc3..fb51740 100644
--- a/src/app/hello.ts
+++ b/src/app/hello.ts
@@ -1,36 +1,34 @@
 /// <reference path="../../typings/index.d.ts"/>
 import {Component, OnInit} from '@angular/core';
 import {StyleConfig} from './config/style.config';
-import {CoreService} from './services/rest/core.service';
-import {InstanceStore} from './services/stores/instance.store';
-import {IInstance} from './interfaces/instance.interface';
+import {ISlice} from './interfaces/models.interface';
+import {SliceStore} from './services/stores/slice.store';
 
 @Component({
   selector: 'xos-app',
   template: require('./hello.html'),
-  providers: [CoreService, InstanceStore],
+  providers: [SliceStore],
 })
 export class HelloComponent implements OnInit {
 
   // declare class properties
   public hello: string;
-  public instances: IInstance[];
+  public slices: ISlice[];
 
   constructor(
-    private coreService: CoreService,
-    private instanceStore: InstanceStore
+    private sliceStore: SliceStore
   ) {
     this.hello = `Hello ${StyleConfig.projectName}!`;
-    this.instances = [];
+    this.slices = [];
   }
 
   ngOnInit() {
     console.log('on init');
-    this.instanceStore.query()
+    this.sliceStore.query()
       .subscribe(
-        instances => {
-          console.log(instances);
-          this.instances = instances;
+        (slices: ISlice[]) => {
+          console.log(slices);
+          this.slices = slices;
         },
         err => {
           console.warn(err);
diff --git a/src/app/index.ts b/src/app/index.ts
index b44673a..cc6e2bc 100644
--- a/src/app/index.ts
+++ b/src/app/index.ts
@@ -8,7 +8,7 @@
 
 // registering components
 import {HelloComponent} from './hello';
-import {LoginComponent} from './components/login/login.component';
+import {LoginComponent} from './views/login/login.component';
 import {LogoutComponent} from './components/logout/logout.component';
 
 // registering directives
@@ -19,6 +19,7 @@
 import {XosHttp} from './services/rest/xoshttp.service';
 import {InstanceService} from './services/rest/instance.service';
 import {GlobalEvent} from './services/websockets/websocket.global';
+import {SliceService} from './services/rest/slices.service';
 
 @NgModule({
   imports: [
@@ -39,6 +40,7 @@
     AuthService,
     XosHttp,
     InstanceService,
+    SliceService,
     GlobalEvent
   ],
   bootstrap: [RootComponent]
diff --git a/src/app/interfaces/instance.interface.ts b/src/app/interfaces/instance.interface.ts
deleted file mode 100644
index 29a37f2..0000000
--- a/src/app/interfaces/instance.interface.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export interface IInstance {
-  id: number;
-  name: string;
-  backend_status: string;
-  created: string;
-}
diff --git a/src/app/interfaces/models.interface.ts b/src/app/interfaces/models.interface.ts
new file mode 100644
index 0000000..77c0534
--- /dev/null
+++ b/src/app/interfaces/models.interface.ts
@@ -0,0 +1,18 @@
+export interface IInstance {
+  id: number;
+  name: string;
+  backend_status: string;
+  created: string;
+}
+
+/**
+ * @whatItDoes Describes the slice model.
+ * @stable
+ */
+export interface ISlice {
+  id: number;
+  name: string;
+  backend_status: string;
+  created: string;
+}
+
diff --git a/src/app/interfaces/ws.interface.ts b/src/app/interfaces/ws.interface.ts
index 05de51b..cadf44c 100644
--- a/src/app/interfaces/ws.interface.ts
+++ b/src/app/interfaces/ws.interface.ts
@@ -4,5 +4,5 @@
    changed_fields: string[],
    object?: any,
    pk?: number
- }
+ };
 }
diff --git a/src/app/routes.ts b/src/app/routes.ts
index 724aa65..53fa2cc 100644
--- a/src/app/routes.ts
+++ b/src/app/routes.ts
@@ -3,7 +3,7 @@
 import {Component} from '@angular/core';
 import {RouterModule, Routes} from '@angular/router';
 import {HelloComponent} from './hello';
-import {LoginComponent} from './components/login/login.component';
+import {LoginComponent} from './views/login/login.component';
 
 @Component({
   selector: 'xos-root',
diff --git a/src/app/services/helpers/store.service.spec.ts b/src/app/services/helpers/store.service.spec.ts
new file mode 100644
index 0000000..d70d649
--- /dev/null
+++ b/src/app/services/helpers/store.service.spec.ts
@@ -0,0 +1,88 @@
+
+import {ObservableCollectionHandler} from './store.service';
+import {IWSEvent} from '../../interfaces/ws.interface';
+import {BehaviorSubject} from 'rxjs';
+
+describe('Service: Observable Collection Handler', () => {
+
+  let subject: BehaviorSubject<any>;
+  let observable;
+
+  beforeEach(() => {
+    subject = new BehaviorSubject([]);
+    observable = subject.asObservable();
+  });
+
+  it('Should have an update method', () => {
+    expect(ObservableCollectionHandler.update).toBeDefined();
+  });
+
+  it('should add an element to the observable', (done) => {
+    const event: IWSEvent = {
+      model: 'Test',
+      msg: {
+        pk: 1,
+        changed_fields: [],
+        object: {id: 1, foo: 'bar'}
+      }
+    };
+
+    ObservableCollectionHandler.update(event, subject);
+
+    subject.subscribe(
+      (collection: any[]) => {
+        expect(collection.length).toBe(1);
+        expect(collection[0].foo).toEqual('bar');
+        done();
+      }
+    );
+  });
+
+  describe('when the subject already have content', () => {
+    beforeEach(() => {
+      subject.next([{id: 1, foo: 'bar'}, {id: 2, foo: 'baz'}]);
+    });
+
+    it('should update an element', (done) => {
+      const event: IWSEvent = {
+        model: 'Test',
+        msg: {
+          pk: 1,
+          changed_fields: [],
+          object: {id: 1, foo: 'updated'}
+        }
+      };
+
+      ObservableCollectionHandler.update(event, subject);
+
+      subject.subscribe(
+        (collection: any[]) => {
+          expect(collection.length).toBe(2);
+          expect(collection[0].foo).toEqual('updated');
+          done();
+        }
+      );
+    });
+
+    it('should delete an element', (done) => {
+      const event: IWSEvent = {
+        model: 'Test',
+        msg: {
+          pk: 1,
+          changed_fields: ['deleted'],
+          object: {id: 1, foo: 'deleted'}
+        }
+      };
+
+      ObservableCollectionHandler.update(event, subject);
+
+      subject.subscribe(
+        (collection: any[]) => {
+          expect(collection.length).toBe(1);
+          expect(collection[0].foo).toEqual('baz');
+          done();
+        }
+      );
+    });
+  });
+});
diff --git a/src/app/services/helpers/store.service.ts b/src/app/services/helpers/store.service.ts
new file mode 100644
index 0000000..80e8e05
--- /dev/null
+++ b/src/app/services/helpers/store.service.ts
@@ -0,0 +1,40 @@
+import {Injectable} from '@angular/core';
+import {IWSEvent} from '../../interfaces/ws.interface';
+import {BehaviorSubject} from 'rxjs';
+import * as _ from 'lodash';
+
+/**
+ * @whatItDoes Update a BehaviorSubject after receiving an event from an Observable
+ * @stable
+ */
+
+@Injectable()
+export class ObservableCollectionHandler {
+
+    static update(event: IWSEvent, subject: BehaviorSubject<any>): BehaviorSubject<any> {
+      const collection: any[] = subject.value;
+
+      const index: number = _.findIndex(collection, (i) => {
+        return i.id === event.msg.object.id;
+      });
+      const exist: boolean = index > -1;
+      const isDeleted: boolean = _.includes(event.msg.changed_fields, 'deleted');
+
+      // remove
+      if (exist && isDeleted) {
+        _.remove(collection, {id: event.msg.object.id});
+      }
+      // Replace item at index using native splice
+      else if (exist && !isDeleted) {
+        collection.splice(index, 1, event.msg.object);
+      }
+      // if the element is not deleted add it
+      else if (!exist && !isDeleted) {
+        collection.push(event.msg.object);
+      }
+
+      subject.next(collection);
+
+      return subject;
+    }
+}
diff --git a/src/app/services/rest/auth.service.ts b/src/app/services/rest/auth.service.ts
index b5b00fb..978825f 100644
--- a/src/app/services/rest/auth.service.ts
+++ b/src/app/services/rest/auth.service.ts
@@ -2,7 +2,7 @@
 
 // Imports
 import {AppConfig} from '../../config/app.config';
-import {Injectable}     from '@angular/core';
+import {Injectable} from '@angular/core';
 import {Http, Response, Headers} from '@angular/http';
 import {Observable} from 'rxjs/Rx';
 import {IAuthRequest, IAuthResponse} from '../../interfaces/auth.interface';
@@ -28,13 +28,35 @@
   }
 
   // get auth info to authenticate API
-  getUserHeaders(): Headers{
+  getUserHeaders(): Headers {
     const headers = new Headers();
     headers.append('x-csrftoken', this.cookieService.get('xoscsrftoken'));
     headers.append('x-sessionid', this.cookieService.get('xossessionid'));
     return headers;
   }
 
+  // log the user in
+  login(auth: IAuthRequest): Observable<IAuthResponse> {
+    return this.http.post(`${AppConfig.apiEndpoint}/utility/login/`, auth)
+      .map((res: Response) => res.json())
+      .map((auth: IAuthResponse) => {
+        this.storeAuth(auth);
+        auth.user = JSON.parse(auth.user);
+        return auth;
+      })
+      .catch((error: any) => Observable.throw(error.json().error || 'Server error'));
+  }
+
+  // logout the user
+  logout(): Observable<any> {
+    return this.http.post(`${AppConfig.apiEndpoint}/utility/logout/`, {xossessionid: this.xosSessionId})
+      .map((res: Response) => {
+        this.removeAuth();
+        return res.text();
+      })
+      .catch((error: any) => Observable.throw(error.json().error || 'Server error'));
+  }
+
   // save cookies
   private storeAuth(auth: IAuthResponse): void {
     this.cookieService.put('xoscsrftoken', auth.xoscsrftoken);
@@ -46,27 +68,5 @@
     this.cookieService.remove('xoscsrftoken');
     this.cookieService.remove('xossessionid');
   }
-
-  // log the user in
-  login(auth: IAuthRequest): Observable<IAuthResponse> {
-    return this.http.post(`${AppConfig.apiEndpoint}/utility/login/`, auth)
-      .map((res: Response) => res.json())
-      .map((auth: IAuthResponse) => {
-        this.storeAuth(auth);
-        auth.user = JSON.parse(auth.user);
-        return auth;
-      })
-      .catch((error:any) => Observable.throw(error.json().error || 'Server error'));
-  }
-
-  // logout the user
-  logout(): Observable<any> {
-    return this.http.post(`${AppConfig.apiEndpoint}/utility/logout/`, {xossessionid: this.xosSessionId})
-      .map((res: Response) => {
-        this.removeAuth();
-        return res.text();
-      })
-      .catch((error: any) => Observable.throw(error.json().error || 'Server error'));
-  }
 }
 
diff --git a/src/app/services/rest/core.service.ts b/src/app/services/rest/core.service.ts
index 3890c47..a283024 100644
--- a/src/app/services/rest/core.service.ts
+++ b/src/app/services/rest/core.service.ts
@@ -20,7 +20,7 @@
   constructor (private http: XosHttp) {}
 
   // Fetch all existing comments
-  getCoreEndpoints() : Observable<any[]> {
+  getCoreEndpoints(): Observable<any[]> {
 
     const search = 'some=param';
 
@@ -29,7 +29,7 @@
     // ...and calling .json() on the response to return data
       .map((res: Response) => res.json())
       // ...errors if any
-      .catch((error:any) => Observable.throw(error.json().error || 'Server error'));
+      .catch((error: any) => Observable.throw(error.json().error || 'Server error'));
 
   }
 }
diff --git a/src/app/services/rest/instance.service.ts b/src/app/services/rest/instance.service.ts
index c864e54..899b484 100644
--- a/src/app/services/rest/instance.service.ts
+++ b/src/app/services/rest/instance.service.ts
@@ -3,8 +3,8 @@
 // Imports
 import {AppConfig} from '../../config/app.config';
 import {AuthService} from './auth.service';
-import { Injectable }     from '@angular/core';
-import { Http, Response, Headers } from '@angular/http';
+import {Injectable}     from '@angular/core';
+import {Response} from '@angular/http';
 import {XosHttp} from './xoshttp.service';
 import {Observable} from 'rxjs/Rx';
 
diff --git a/src/app/services/rest/slices.service.ts b/src/app/services/rest/slices.service.ts
new file mode 100644
index 0000000..2b71d7a
--- /dev/null
+++ b/src/app/services/rest/slices.service.ts
@@ -0,0 +1,24 @@
+/// <reference path="../../../../typings/index.d.ts"/>
+
+// Imports
+import {AppConfig} from '../../config/app.config';
+import { Injectable }     from '@angular/core';
+import { Response} from '@angular/http';
+import {XosHttp} from './xoshttp.service';
+import {Observable} from 'rxjs/Rx';
+
+// Import RxJs required methods
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/catch';
+
+@Injectable()
+export class SliceService {
+  private baseUrl = AppConfig.apiEndpoint;
+  constructor (private http: XosHttp) {}
+  // Fetch all existing instances
+  query() : Observable<any[]> {
+    return this.http.get(`${this.baseUrl}/core/slices/`)
+      .map((res: Response) => res.json())
+      .catch((error: any) => Observable.throw(error.response.json().error || 'Server error'));
+  }
+}
diff --git a/src/app/services/rest/xoshttp.service.ts b/src/app/services/rest/xoshttp.service.ts
index 5572683..a300ee3 100644
--- a/src/app/services/rest/xoshttp.service.ts
+++ b/src/app/services/rest/xoshttp.service.ts
@@ -13,9 +13,20 @@
   ) {
   }
 
+  // TODO intercept non authenticated calls and send to login (remove cookies)
+  // TODO add POST, PUT, DELETE declaration
+  get(url: string, options?: RequestOptionsArgs): Observable<Response> {
+
+    options = this.checkOptions(options);
+    options = this.getHeaders(options);
+    options = this.getParams(options);
+
+    return this.http.get(url, options);
+  }
+
   private checkOptions(options?: RequestOptionsArgs): RequestOptionsArgs {
     // if options are not there, create them
-    if(!options){
+    if (!options) {
       options = {};
     }
     return options;
@@ -41,15 +52,4 @@
     }
     return options;
   }
-
-  get(url: string, options?: RequestOptionsArgs): Observable<Response> {
-
-    options = this.checkOptions(options);
-    options = this.getHeaders(options);
-    options = this.getParams(options);
-
-    return this.http.get(url, options)
-  }
-
-  // TODO add POST, PUT, DELETE declaration
 }
diff --git a/src/app/services/stores/instance.store.ts b/src/app/services/stores/instance.store.ts
index a87630c..9a07095 100644
--- a/src/app/services/stores/instance.store.ts
+++ b/src/app/services/stores/instance.store.ts
@@ -2,11 +2,11 @@
 
 import {Injectable} from '@angular/core';
 import {BehaviorSubject} from 'rxjs/Rx';
-import {IInstance} from '../../interfaces/instance.interface';
+import {IInstance} from '../../interfaces/models.interface';
 import {InstanceService} from '../rest/instance.service';
-import * as _ from 'lodash';
 import {IWSEvent} from '../../interfaces/ws.interface';
 import {GlobalEvent} from '../websockets/websocket.global';
+import {ObservableCollectionHandler} from '../helpers/store.service';
 
 @Injectable()
 export class InstanceStore {
@@ -14,25 +14,10 @@
   constructor(private instanceService: InstanceService, private globalEvent: GlobalEvent) {
     this.loadInitialData();
     this.globalEvent.list()
-      .filter((e: IWSEvent) => {
-        console.log('filter', e);
-        return e.model === 'Instance';
-      })
+      .filter((e: IWSEvent) => e.model === 'Instance')
       .subscribe(
         (event: IWSEvent) => {
-
-          const collection = this._instances.value;
-
-          const exist = _.find(collection, (i) => {
-            return i.id === event.msg.object.id;
-          });
-
-          // remove in order to update
-          if (exist) {
-            _.remove(collection, {id: event.msg.object.id});
-          }
-          collection.push(event.msg.object);
-          this._instances.next(collection);
+          ObservableCollectionHandler.update(event, this._instances);
         }
       );
   }
diff --git a/src/app/services/stores/slice.store.ts b/src/app/services/stores/slice.store.ts
new file mode 100644
index 0000000..aeab9f3
--- /dev/null
+++ b/src/app/services/stores/slice.store.ts
@@ -0,0 +1,39 @@
+/// <reference path="../../../../typings/index.d.ts"/>
+
+import {Injectable} from '@angular/core';
+import {BehaviorSubject} from 'rxjs/Rx';
+import {ISlice} from '../../interfaces/models.interface';
+import {IWSEvent} from '../../interfaces/ws.interface';
+import {GlobalEvent} from '../websockets/websocket.global';
+import {SliceService} from '../rest/slices.service';
+import {ObservableCollectionHandler} from '../helpers/store.service';
+
+@Injectable()
+export class SliceStore {
+  private _slices: BehaviorSubject<ISlice[]> = new BehaviorSubject([]);
+  constructor(private sliceService: SliceService, private globalEvent: GlobalEvent) {
+    this.loadInitialData();
+    this.globalEvent.list()
+      .filter((e: IWSEvent) => e.model === 'Slice')
+      .subscribe(
+        (event: IWSEvent) => {
+          ObservableCollectionHandler.update(event, this._slices);
+        }
+      );
+  }
+
+  loadInitialData() {
+    this.sliceService.query()
+      .subscribe(
+        res => {
+          this._slices.next(res);
+        },
+        err => console.log('Error retrieving Instances', err)
+      );
+  }
+
+  query() {
+    return this._slices.asObservable();
+  }
+
+}
diff --git a/src/app/services/websockets/websocket.global.ts b/src/app/services/websockets/websocket.global.ts
index ee62c12..46bfbe1 100644
--- a/src/app/services/websockets/websocket.global.ts
+++ b/src/app/services/websockets/websocket.global.ts
@@ -1,29 +1,23 @@
 /// <reference path="../../../../typings/index.d.ts"/>
 
 import {Injectable} from '@angular/core';
-import {BehaviorSubject} from 'rxjs/Rx';
+import {Subject} from 'rxjs/Rx';
 import * as io from 'socket.io-client';
 import {AppConfig} from '../../config/app.config';
 import {IWSEvent} from '../../interfaces/ws.interface';
 
 @Injectable()
 export class GlobalEvent {
-  private _events: BehaviorSubject<IWSEvent> = new BehaviorSubject<IWSEvent>({
-    model: 'XOS',
-    msg: {
-      changed_fields: []
-    }
-  });
+  private _events: Subject<IWSEvent> = new Subject<IWSEvent>();
   private socket;
   constructor() {
     this.socket = io(AppConfig.websocketClient);
-    this.socket.on('event', (data: IWSEvent) => {
+    this.socket.on('event', (data: IWSEvent): void => {
+      console.log('event', data);
       this._events.next(data);
     });
   }
-
   list() {
     return this._events.asObservable();
   }
-
 }
diff --git a/src/app/components/login/login.component.ts b/src/app/views/login/login.component.ts
similarity index 72%
rename from src/app/components/login/login.component.ts
rename to src/app/views/login/login.component.ts
index 05cc11d..96492e3 100644
--- a/src/app/components/login/login.component.ts
+++ b/src/app/views/login/login.component.ts
@@ -5,13 +5,11 @@
 import {StyleConfig} from '../../config/style.config';
 import {AuthService} from '../../services/rest/auth.service';
 
-import {AppConfig} from '../../config/app.config';
-
 export class Auth {
   constructor(
-    public username,
-    public password
-  ){
+    public username: string,
+    public password: string
+  ) {
   }
 }
 
@@ -21,20 +19,20 @@
   providers: [AuthService],
 })
 export class LoginComponent {
-  public auth:IAuthRequest;
+  public auth: IAuthRequest;
   public brandName;
-  constructor(private AuthService: AuthService, private router:Router) {
+  constructor(private AuthService: AuthService, private router: Router) {
     this.auth = new Auth('', '');
     this.brandName = StyleConfig.projectName;
   }
 
-  onSubmit(auth:IAuthRequest){
+  onSubmit(auth: IAuthRequest) {
     this.AuthService.login(auth)
       .subscribe(
-        (user:IAuthResponse) => {
+        (user: IAuthResponse) => {
           this.router.navigate(['/']);
         }
-      )
+      );
   }
 }
 
diff --git a/src/app/components/login/login.html b/src/app/views/login/login.html
similarity index 100%
rename from src/app/components/login/login.html
rename to src/app/views/login/login.html