[CORD-873] CRUD for Core and Service model from Chameleon
Change-Id: I45c533feba6720b82de3681d862773047e7fd6f8
diff --git a/conf/app/app.config.dev.js b/conf/app/app.config.dev.js
index 5c620d7..1b74b5d 100644
--- a/conf/app/app.config.dev.js
+++ b/conf/app/app.config.dev.js
@@ -1,5 +1,5 @@
 angular.module('app')
   .constant('AppConfig', {
-    apiEndpoint: 'http://xos.dev:3000/api',
-    websocketClient: 'http://xos.dev:3000'
+    apiEndpoint: '/xosapi/v1',
+    websocketClient: 'http://xos.dev'
   });
diff --git a/conf/app/style.config.cord.js b/conf/app/style.config.cord.js
index 6f7ebab..9419489 100644
--- a/conf/app/style.config.cord.js
+++ b/conf/app/style.config.cord.js
@@ -8,15 +8,15 @@
     routes: [
         {
             label: 'Slices',
-            state: 'xos.core.slices'
+            state: 'xos.core.slice'
         },
         {
             label: 'Instances',
-            state: 'xos.core.instances'
+            state: 'xos.core.instance'
         },
         {
             label: 'Nodes',
-            state: 'xos.core.nodes'
+            state: 'xos.core.node'
         }
     ]
 });
diff --git a/conf/app/style.config.opencloud.js b/conf/app/style.config.opencloud.js
index 9693c5d..5a23a77 100644
--- a/conf/app/style.config.opencloud.js
+++ b/conf/app/style.config.opencloud.js
@@ -8,7 +8,7 @@
     routes: [
         {
             label: 'Slices',
-            state: 'xos.core.slices'
+            state: 'xos.core.slice'
         }
     ]
 });
diff --git a/conf/browsersync.conf.js b/conf/browsersync.conf.js
index 49936d4..e8c38fa 100644
--- a/conf/browsersync.conf.js
+++ b/conf/browsersync.conf.js
@@ -1,4 +1,18 @@
 const conf = require('./gulp.conf');
+const httpProxy = require('http-proxy');
+
+// TODO move the proxy config in a separate file and share with browsersync.dist.js
+
+const proxy = httpProxy.createProxyServer({
+  target: 'http://xos.dev:9101'
+});
+
+proxy.on('error', function(error, req, res) {
+  res.writeHead(500, {
+    'Content-Type': 'text/plain'
+  });
+  console.error('[Proxy]', error);
+});
 
 module.exports = function () {
   return {
@@ -6,8 +20,16 @@
       baseDir: [
         conf.paths.tmp,
         conf.paths.src
-      ]
+      ],
+      middleware: function(req, res, next){
+        if (req.url.indexOf('xosapi') !== -1) {
+          proxy.web(req, res);
+        }
+        else{
+          next();
+        }
+      }
     },
-    open: false
+    open: true
   };
 };
diff --git a/package.json b/package.json
index d9bc915..e97f9a0 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
     "gulp-util": "^3.0.7",
     "html-loader": "^0.4.3",
     "html-webpack-plugin": "^2.9.0",
+    "http-proxy": "^1.16.2",
     "jasmine": "^2.4.1",
     "jasmine-jquery": "^2.1.1",
     "json-loader": "^0.5.4",
diff --git a/src/app/core/form/form.html b/src/app/core/form/form.html
index 4c7485a..5da8c4e 100644
--- a/src/app/core/form/form.html
+++ b/src/app/core/form/form.html
@@ -1,3 +1,4 @@
+<!--<pre>{{vm.config.inputs | json}}</pre>-->
 <form name="vm.{{vm.config.formName || 'form'}}" novalidate>
     <!--<div class="form-group" ng-repeat="(name, field) in vm.formField">-->
         <!--<xos-field name="name" field="field" ng-model="vm.ngModel[name]"></xos-field>-->
diff --git a/src/app/core/form/form.ts b/src/app/core/form/form.ts
index c0bd206..9de27ea 100644
--- a/src/app/core/form/form.ts
+++ b/src/app/core/form/form.ts
@@ -44,7 +44,7 @@
   options?: IXosFormInputOptions[];
 }
 
-export interface IXosFormConfig {
+export interface IXosFormCfg {
   exclude?: string[];
   actions: IXosFormAction[];
   feedback?: IXosFeedback;
diff --git a/src/app/core/header/header.spec.ts b/src/app/core/header/header.spec.ts
index c1d6062..73ba587 100644
--- a/src/app/core/header/header.spec.ts
+++ b/src/app/core/header/header.spec.ts
@@ -55,7 +55,7 @@
       .value('toastr', MockToastr)
       .value('toastrConfig', MockToastrConfig)
       .value('AuthService', MockAuth)
-      .value('NavigationService', {})
+      .value('XosNavigationService', {})
       .value('XosKeyboardShortcut', MockXosKeyboardShortcut)
       .value('StyleConfig', {
         logo: 'cord-logo.png',
diff --git a/src/app/core/header/header.ts b/src/app/core/header/header.ts
index 8926080..62e3ce9 100644
--- a/src/app/core/header/header.ts
+++ b/src/app/core/header/header.ts
@@ -14,7 +14,7 @@
 }
 
 class HeaderController {
-  static $inject = ['$scope', '$rootScope', '$state', 'AuthService', 'SynchronizerStore', 'toastr', 'toastrConfig', 'NavigationService', 'StyleConfig', 'SearchService', 'XosKeyboardShortcut'];
+  static $inject = ['$scope', '$rootScope', '$state', 'AuthService', 'SynchronizerStore', 'toastr', 'toastrConfig', 'XosNavigationService', 'StyleConfig', 'SearchService', 'XosKeyboardShortcut'];
   public notifications: INotification[] = [];
   public newNotifications: INotification[] = [];
   public version: string;
diff --git a/src/app/core/index.ts b/src/app/core/index.ts
index 318ffeb..d275a9f 100644
--- a/src/app/core/index.ts
+++ b/src/app/core/index.ts
@@ -4,8 +4,8 @@
 import routesConfig from './routes';
 import {xosLogin} from './login/login';
 import {xosTable} from './table/table';
-import {RuntimeStates} from './services/runtime-states';
-import {NavigationService} from './services/navigation';
+import {XosRuntimeStates} from './services/runtime-states';
+import {IXosNavigationService} from './services/navigation';
 import {PageTitle} from './services/page-title';
 import {ConfigHelpers} from './services/helpers/config.helpers';
 import {xosLinkWrapper} from './link-wrapper/link-wrapper';
@@ -15,7 +15,6 @@
 import 'angular-toastr';
 import {xosAlert} from './alert/alert';
 import {xosValidation} from './validation/validation';
-import {ModelSetup} from './services/helpers/model-setup.helpers';
 import {xosSidePanel} from './side-panel/side-panel';
 import {XosSidePanel} from './side-panel/side-panel.service';
 import {XosComponentInjector} from './services/helpers/component-injector.helpers';
@@ -33,12 +32,11 @@
     'ui.bootstrap.typeahead'
   ])
   .config(routesConfig)
-  .provider('RuntimeStates', RuntimeStates)
-  .service('NavigationService', NavigationService)
+  .provider('XosRuntimeStates', XosRuntimeStates)
+  .service('XosNavigationService', IXosNavigationService)
   .service('PageTitle', PageTitle)
   .service('XosFormHelpers', XosFormHelpers)
   .service('ConfigHelpers', ConfigHelpers)
-  .service('ModelSetup', ModelSetup)
   .service('XosSidePanel', XosSidePanel)
   .service('XosKeyboardShortcut', XosKeyboardShortcut)
   .service('XosComponentInjector', XosComponentInjector)
diff --git a/src/app/core/login/login.ts b/src/app/core/login/login.ts
index 673d9a8..183980a 100644
--- a/src/app/core/login/login.ts
+++ b/src/app/core/login/login.ts
@@ -1,18 +1,19 @@
 import {AuthService} from '../../datasources/rest/auth.rest';
 import './login.scss';
-import {IXosModelSetupService} from '../services/helpers/model-setup.helpers';
+
 import {IXosStyleConfig} from '../../../index';
+import {IXosModelDiscovererService} from '../../datasources/helpers/model-discoverer.service';
 
 class LoginCtrl {
-  static $inject = ['$log', 'AuthService', '$state', 'ModelSetup', 'StyleConfig'];
+  static $inject = ['$log', 'AuthService', '$state', 'XosModelDiscoverer', 'StyleConfig'];
   public loginStyle: any;
   public img: string;
-  /** @ngInject */
+
   constructor(
     private $log: ng.ILogService,
     private authService: AuthService,
     private $state: angular.ui.IStateService,
-    private ModelSetup: IXosModelSetupService,
+    private XosModelDiscoverer: IXosModelDiscovererService,
     private StyleConfig: IXosStyleConfig
   ) {
 
@@ -34,7 +35,7 @@
     })
       .then(res => {
         // after login set up models
-        return this.ModelSetup.setup();
+        return this.XosModelDiscoverer.discover();
       })
       .then(() => {
         this.$state.go('xos.dashboard');
diff --git a/src/app/core/nav/nav.spec.ts b/src/app/core/nav/nav.spec.ts
index 5101ba8..1d41f4b 100644
--- a/src/app/core/nav/nav.spec.ts
+++ b/src/app/core/nav/nav.spec.ts
@@ -29,7 +29,7 @@
     angular
       .module('xosNav', ['app/core/nav/nav.html', 'ui.router'])
       .component('xosNav', xosNav)
-      .service('NavigationService', NavigationService)
+      .service('XosNavigationService', NavigationService)
       .value('AuthService', AuthMock)
       .value('StyleConfig', {})
       .value('XosSidePanel', {})
diff --git a/src/app/core/nav/nav.ts b/src/app/core/nav/nav.ts
index 08ad48a..f537e5e 100644
--- a/src/app/core/nav/nav.ts
+++ b/src/app/core/nav/nav.ts
@@ -6,7 +6,7 @@
 import {IXosComponentInjectorService} from '../services/helpers/component-injector.helpers';
 
 class NavCtrl {
-  static $inject = ['$scope', '$state', 'NavigationService', 'AuthService', 'StyleConfig', 'XosSidePanel', 'XosComponentInjector'];
+  static $inject = ['$scope', '$state', 'XosNavigationService', 'AuthService', 'StyleConfig', 'XosSidePanel', 'XosComponentInjector'];
   public routes: IXosNavigationRoute[];
   public navSelected: string;
   public appName: string;
diff --git a/src/app/core/services/helpers/config.helpers.spec.ts b/src/app/core/services/helpers/config.helpers.spec.ts
index 3e90d33..ed41228 100644
--- a/src/app/core/services/helpers/config.helpers.spec.ts
+++ b/src/app/core/services/helpers/config.helpers.spec.ts
@@ -3,47 +3,60 @@
 import 'angular-ui-router';
 
 import {IXosConfigHelpersService, ConfigHelpers, IXosModelDefsField} from './config.helpers';
-import {IModeldef} from '../../../datasources/rest/modeldefs.rest';
+import {IXosModeldef} from '../../../datasources/rest/modeldefs.rest';
 import {IXosTableCfg} from '../../table/table';
-import {IXosFormInput, IXosFormConfig} from '../../form/form';
+import {IXosFormInput, IXosFormCfg} from '../../form/form';
 import {BehaviorSubject} from 'rxjs';
 
 let service: IXosConfigHelpersService;
 
-const model: IModeldef = {
+const model: IXosModeldef = {
   name: 'Test',
+  app: 'test',
   fields: [
     {
       type: 'number',
       name: 'id',
-      validators: {}
+      validators: []
     },
     {
       type: 'string',
       name: 'name',
-      validators: {
-        required: true
-      }
+      validators: [
+        {
+          bool_value: true,
+          name: 'required'
+        }
+      ]
     },
     {
       type: 'string',
       name: 'something',
-      validators: {
-        maxlength: 30
-      }
+      validators: [
+        {
+          int_value: 30,
+          name: 'maxlength'
+        }
+      ]
     },
     {
       type: 'number',
       name: 'else',
-      validators: {
-        min: 20,
-        max: 40
-      }
+      validators: [
+        {
+          int_value: 20,
+          name: 'min'
+        },
+        {
+          int_value: 40,
+          name: 'max'
+        }
+      ]
     },
     {
       type: 'date',
       name: 'updated',
-      validators: {}
+      validators: []
     },
   ]
 };
@@ -59,7 +72,7 @@
           return {id: 1};
         }
       })
-      .value('ModelStore', {
+      .value('XosModelStore', {
 
       })
       .value('$state', {
@@ -131,11 +144,6 @@
   });
 
   describe('the navigation methods', () => {
-    describe('urlFromCoreModels', () => {
-      it('should return the URL for a given model', () => {
-        expect(service.urlFromCoreModel('Test')).toBe('/core/tests');
-      });
-    });
     describe('stateFromCoreModels', () => {
 
       let state: ng.ui.IStateService;
@@ -156,7 +164,7 @@
 
   describe('the modelFieldsToColumnsCfg method', () => {
     it('should return an array of columns', () => {
-      const cols = service.modelFieldsToColumnsCfg(model.fields, 'testUrl/:id?');
+      const cols = service.modelFieldsToColumnsCfg({fields: model.fields, name: 'testUrl', app: 'test'});
       expect(cols[0].label).toBe('Id');
       expect(cols[0].prop).toBe('id');
       expect(cols[0].link).toBeDefined();
@@ -214,7 +222,7 @@
 
   describe('the modelToFormCfg method', () => {
     it('should return a form config', () => {
-      const config: IXosFormConfig = service.modelToFormCfg(model);
+      const config: IXosFormCfg = service.modelToFormCfg(model);
       expect(config.formName).toBe('TestForm');
       expect(config.actions.length).toBe(1);
       expect(config.actions[0].label).toBe('Save');
diff --git a/src/app/core/services/helpers/config.helpers.ts b/src/app/core/services/helpers/config.helpers.ts
index 28a16a3..2ac9394 100644
--- a/src/app/core/services/helpers/config.helpers.ts
+++ b/src/app/core/services/helpers/config.helpers.ts
@@ -1,16 +1,22 @@
 import * as _ from 'lodash';
 import * as pluralize from 'pluralize';
 import {IXosTableColumn, IXosTableCfg} from '../../table/table';
-import {IModeldef} from '../../../datasources/rest/modeldefs.rest';
-import {IXosFormConfig, IXosFormInput} from '../../form/form';
+import {IXosModeldef} from '../../../datasources/rest/modeldefs.rest';
+import {IXosFormCfg, IXosFormInput, IXosFormInputValidator} from '../../form/form';
 import {IXosAuthService} from '../../../datasources/rest/auth.rest';
 import {IXosModelStoreService} from '../../../datasources/stores/model.store';
-import {IXosState} from '../../../../index';
+import {IXosState} from '../runtime-states';
+
+export interface IXosModelDefsFieldValidators {
+  name: string;
+  bool_value?: boolean;
+  int_value?: number;
+}
 
 export interface IXosModelDefsField {
   name: string;
   type: string;
-  validators?: any;
+  validators?: IXosModelDefsFieldValidators[];
   hint?: string;
   relation?: {
     model: string;
@@ -20,23 +26,26 @@
 
 export interface IXosConfigHelpersService {
   excluded_fields: string[];
-  modelFieldsToColumnsCfg(fields: IXosModelDefsField[], baseUrl: string): IXosTableColumn[]; // TODO use a proper interface
-  modelToTableCfg(model: IModeldef, modelName: string): IXosTableCfg;
+  modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[];
+  modelToTableCfg(model: IXosModeldef, modelName: string): IXosTableCfg;
   modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[];
-  modelToFormCfg(model: IModeldef): IXosFormConfig;
+  modelToFormCfg(model: IXosModeldef): IXosFormCfg;
   pluralize(string: string, quantity?: number, count?: boolean): string;
   toLabel(string: string, pluralize?: boolean): string;
   toLabels(string: string[], pluralize?: boolean): string[];
-  urlFromCoreModel(model: string): string;
   stateFromCoreModel(name: string): string;
   stateWithParams(name: string, model: any): string;
   stateWithParamsForJs(name: string, model: any): any;
 }
 
 export class ConfigHelpers implements IXosConfigHelpersService {
-  static $inject = ['$state', 'toastr', 'AuthService', 'ModelStore'];
+  static $inject = [
+    '$state',
+    'toastr',
+    'AuthService',
+    'XosModelStore'];
 
-  excluded_fields = [
+  public excluded_fields = [
     'created',
     'updated',
     'enacted',
@@ -59,11 +68,15 @@
     private $state: ng.ui.IStateService,
     private toastr: ng.toastr.IToastrService,
     private AuthService: IXosAuthService,
-    private ModelStore: IXosModelStoreService
+    private XosModelStore: IXosModelStoreService
   ) {
     pluralize.addIrregularRule('xos', 'xoses');
     pluralize.addPluralRule(/slice$/i, 'slices');
     pluralize.addSingularRule(/slice$/i, 'slice');
+    pluralize.addPluralRule(/library$/i, 'librarys');
+    pluralize.addPluralRule(/imagedeployments/i, 'imagedeploymentses');
+    pluralize.addPluralRule(/controllerimages/i, 'controllerimageses');
+
   }
 
   public pluralize(string: string, quantity?: number, count?: boolean): string {
@@ -91,9 +104,9 @@
     return this.capitalizeFirst(string);
   }
 
-  public modelToTableCfg(model: IModeldef, baseUrl: string): IXosTableCfg {
+  public modelToTableCfg(model: IXosModeldef, baseUrl: string): IXosTableCfg {
     const cfg = {
-      columns: this.modelFieldsToColumnsCfg(model.fields, model.name),
+      columns: this.modelFieldsToColumnsCfg(model),
       filter: 'fulltext',
       order: {field: 'id', reverse: false},
       pagination: {
@@ -125,10 +138,11 @@
     return cfg;
   }
 
-  public modelFieldsToColumnsCfg(fields: IXosModelDefsField[], modelName: string): IXosTableColumn[] {
-
+  public modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[] {
+    const fields: IXosModelDefsField[] = model.fields;
+    const modelName: string = model.name;
     const columns =  _.map(fields, (f) => {
-      if (this.excluded_fields.indexOf(f.name) > -1) {
+      if (!angular.isDefined(f) || this.excluded_fields.indexOf(f.name) > -1) {
         return;
       }
       const col: IXosTableColumn =  {
@@ -147,7 +161,9 @@
           this.populateRelated(item, item[f.name], f);
           return item[f.name];
         };
-        col.link = item => this.stateWithParams(f.relation.model, item);
+        col.link = item => {
+          return this.stateWithParams(f.relation.model, item);
+        };
       }
 
       if (f.name === 'backend_status') {
@@ -175,11 +191,6 @@
     return columns;
   };
 
-  public urlFromCoreModel(name: string): string {
-
-    return `/core/${this.pluralize(name.toLowerCase())}`;
-  }
-
   public stateFromCoreModel(name: string): string {
     const state: ng.ui.IState = _.find(this.$state.get(), (s: IXosState) => {
       if (s.data) {
@@ -187,7 +198,7 @@
       }
       return false;
     });
-    return state.name;
+    return state ? state.name : null;
   }
 
   public stateWithParams(name: string, model: any): string {
@@ -204,32 +215,27 @@
   public modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[] {
 
     return _.map(fields, (f: IXosModelDefsField) => {
-      if (f.relation) {
-        const input: IXosFormInput = {
-          name: f.name,
-          label: this.toLabel(f.name),
-          type: 'select',
-          validators: f.validators,
-          hint: f.hint
-        };
-        this.populateSelectField(f, input);
-        return input;
-      }
-
-      return {
+      const input: IXosFormInput = {
         name: f.name,
         label: this.toLabel(f.name),
         type: f.type,
-        validators: f.validators
+        validators: this.formatValidators(f.validators),
+        hint: f.hint
       };
+      if (f.relation) {
+        input.type = 'select';
+        this.populateSelectField(f, input);
+        return input;
+      }
+      return input;
     })
       .filter(f => this.excluded_fields.indexOf(f.name) === -1);
   }
 
-  public modelToFormCfg(model: IModeldef): IXosFormConfig {
-    const formCfg: IXosFormConfig = {
+  public modelToFormCfg(model: IXosModeldef): IXosFormCfg {
+    const formCfg: IXosFormCfg = {
       formName: `${model.name}Form`,
-      exclude: ['backend_status', 'creator'],
+      exclude: ['backend_status', 'creator', 'id'],
       actions: [{
         label: 'Save',
         class: 'success',
@@ -258,12 +264,19 @@
       delete item.networks;
 
       // adding userId as creator
-      item.creator = this.AuthService.getUser().id;
+      // item.creator = this.AuthService.getUser().id;
+
+      // remove field added by xosTable
+      _.forEach(Object.keys(item), prop => {
+        if (prop.indexOf('-formatted') > -1) {
+          delete item[prop];
+        }
+      });
 
       item.$save()
         .then((res) => {
           if (res.status === 403 || res.status === 405 || res.status === 500) {
-            // TODO understand why 405 does not go directly in catch (it may be realted to ng-rest-gw)
+            // TODO understand why 405 does not go directly in catch (it may be related to ng-rest-gw)
             throw new Error();
           }
           formCfg.feedback = {
@@ -283,6 +296,15 @@
     return formCfg;
   }
 
+  private formatValidators(validators: IXosModelDefsFieldValidators[]): IXosFormInputValidator {
+    // convert validators as expressed from modelDefs,
+    // to the object required by xosForm
+    return _.reduce(validators, (formValidators: IXosFormInputValidator, v: IXosModelDefsFieldValidators) => {
+      formValidators[v.name] = v.bool_value ? v.bool_value : v.int_value;
+      return formValidators;
+    }, {});
+  }
+
   private fromCamelCase(string: string): string {
     return string.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join(' ');
   }
@@ -304,12 +326,20 @@
     if (!fk || angular.isUndefined(fk) || fk === null) {
       return;
     }
-    this.ModelStore.query(field.relation.model)
+    this.XosModelStore.query(field.relation.model)
       .subscribe(res => {
         if (angular.isDefined(res) && angular.isDefined(fk)) {
           let ri = _.find(res, {id: fk});
           if (angular.isDefined(ri)) {
-            item[`${field.name}-formatted`] = angular.isDefined(ri.name) ? ri.name : ri.humanReadableName;
+            if (angular.isDefined(ri.name)) {
+              item[`${field.name}-formatted`] = ri.name;
+            }
+            else if (angular.isDefined(ri.humanReadableName)) {
+              item[`${field.name}-formatted`] = ri.humanReadableName;
+            }
+            else {
+              item[`${field.name}-formatted`] = ri.id;
+            }
           }
         }
       });
@@ -317,10 +347,14 @@
 
   // augment a select field with related model informations
   private populateSelectField(field: IXosModelDefsField, input: IXosFormInput): void {
-    this.ModelStore.query(field.relation.model)
+    this.XosModelStore.query(field.relation.model)
       .subscribe(res => {
         input.options = _.map(res, item => {
-          return {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
+          let opt = {id: item.id, label: item.humanReadableName ? item.humanReadableName : item.name};
+          if (!angular.isDefined(item.humanReadableName) && !angular.isDefined(item.name)) {
+            opt.label = item.id;
+          }
+          return opt;
         });
       });
   }
diff --git a/src/app/core/services/helpers/model-setup.helpers.ts b/src/app/core/services/helpers/model-setup.helpers.ts
deleted file mode 100644
index e79899d..0000000
--- a/src/app/core/services/helpers/model-setup.helpers.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import {ModeldefsService, IModeldef} from '../../../datasources/rest/modeldefs.rest';
-import {IXosConfigHelpersService} from './config.helpers';
-import {IRuntimeStatesService} from '../runtime-states';
-import {NavigationService} from '../navigation';
-import {IXosState} from '../../../../index';
-import * as _ from 'lodash';
-import IPromise = angular.IPromise;
-
-export interface IXosModelSetupService {
-  setup(): IPromise<null>;
-}
-
-export class ModelSetup {
-  static $inject = ['$rootScope', '$q', 'ModelDefs', 'ConfigHelpers', 'RuntimeStates', 'NavigationService'];
-
-  constructor(
-    private $rootScope: ng.IScope,
-    private $q: ng.IQService,
-    private ModelDefs: ModeldefsService,
-    private ConfigHelpers: IXosConfigHelpersService,
-    private RuntimeStates: IRuntimeStatesService,
-    private NavigationService: NavigationService
-  ) {
-
-  }
-
-  public setup(): IPromise<null> {
-    const d = this.$q.defer();
-    this.ModelDefs.get()
-      .then((models: IModeldef[]) => {
-        _.forEach(models, (m: IModeldef) => {
-          const stateUrl = `/${this.ConfigHelpers.pluralize(m.name.toLowerCase())}/:id?`;
-          const stateName = `xos.core.${this.ConfigHelpers.pluralize(m.name.toLowerCase())}`;
-          const state: IXosState = {
-            parent: 'core',
-            url: stateUrl,
-            component: 'xosCrud',
-            params: {
-              id: null
-            },
-            data: {
-              model: m.name,
-              related: m.relations,
-              xosTableCfg: this.ConfigHelpers.modelToTableCfg(m, stateUrl),
-              xosFormCfg: this.ConfigHelpers.modelToFormCfg(m)
-            }
-          };
-
-          this.RuntimeStates.addState(stateName, state);
-          this.NavigationService.add({
-            label: this.ConfigHelpers.pluralize(m.name),
-            state: stateName,
-            parent: 'xos.core'
-          });
-        });
-
-        d.resolve();
-      })
-      .catch(d.reject);
-
-    return d.promise;
-  }
-}
diff --git a/src/app/core/services/navigation.spec.ts b/src/app/core/services/navigation.spec.ts
index fbfb91c..82a235e 100644
--- a/src/app/core/services/navigation.spec.ts
+++ b/src/app/core/services/navigation.spec.ts
@@ -35,10 +35,10 @@
   });
 
   beforeEach(angular.mock.inject((
-    NavigationService: IXosNavigationService,
+    XosNavigationService: IXosNavigationService,
     _$log_: ng.ILogService
   ) => {
-    service = NavigationService;
+    service = XosNavigationService;
     $log = _$log_;
     spyOn($log, 'warn');
     defaultRoutes = [
diff --git a/src/app/core/services/navigation.ts b/src/app/core/services/navigation.ts
index e35c9f8..9869b5a 100644
--- a/src/app/core/services/navigation.ts
+++ b/src/app/core/services/navigation.ts
@@ -17,7 +17,9 @@
   add(route: IXosNavigationRoute): void;
 }
 
-export class NavigationService {
+// TODO support 3rd level to group service model under "Services"
+
+export class IXosNavigationService {
   static $inject = ['$log', 'StyleConfig'];
   private routes: IXosNavigationRoute[];
 
@@ -34,6 +36,10 @@
         label: 'Core',
         state: 'xos.core'
       },
+      // {
+      //   label: 'Service',
+      //   state: 'xos.services'
+      // },
     ];
     // adding configuration defined routes
     // this.routes = StyleConfig.routes.concat(defaultRoutes).reverse();
@@ -64,16 +70,20 @@
       return;
     }
 
-
     if (angular.isDefined(route.parent)) {
       // route parent should be a state for now
       const parentRoute = _.find(this.routes, {state: route.parent});
-
-      if (angular.isArray(parentRoute.children)) {
-        parentRoute.children.push(route);
+      if (angular.isDefined(parentRoute)) {
+        if (angular.isArray(parentRoute.children)) {
+          parentRoute.children.push(route);
+        }
+        else {
+          parentRoute.children = [route];
+        }
       }
       else {
-        parentRoute.children = [route];
+        this.$log.warn(`[XosNavigation] Parent State (${route.parent}) for state: ${route.state} does not exists`);
+        return;
       }
     }
     else {
diff --git a/src/app/core/services/runtime-states.spec.ts b/src/app/core/services/runtime-states.spec.ts
index 5dd44db..8b9015d 100644
--- a/src/app/core/services/runtime-states.spec.ts
+++ b/src/app/core/services/runtime-states.spec.ts
@@ -2,19 +2,19 @@
 import 'angular-mocks';
 import 'angular-ui-router';
 import {xosCore} from '../index';
-import {IRuntimeStatesService} from './runtime-states';
+import {IXosRuntimeStatesService} from './runtime-states';
 
-let service: IRuntimeStatesService, $state: ng.ui.IStateService;
+let service: IXosRuntimeStatesService, $state: ng.ui.IStateService;
 
 describe('The Navigation service', () => {
 
   beforeEach(angular.mock.module(xosCore));
 
   beforeEach(angular.mock.inject((
-    RuntimeStates: IRuntimeStatesService,
+    XosRuntimeStates: IXosRuntimeStatesService,
     _$state_: ng.ui.IStateService
   ) => {
-    service = RuntimeStates;
+    service = XosRuntimeStates;
     $state = _$state_;
   }));
 
diff --git a/src/app/core/services/runtime-states.ts b/src/app/core/services/runtime-states.ts
index 652b554..1f495ba 100644
--- a/src/app/core/services/runtime-states.ts
+++ b/src/app/core/services/runtime-states.ts
@@ -1,9 +1,14 @@
-import {IXosState} from '../../../index';
-export interface IRuntimeStatesService {
+export interface IXosState extends angular.ui.IState {
+  data: {
+    model: string
+  };
+};
+
+export interface IXosRuntimeStatesService {
   addState(name: string, state: ng.ui.IState): void;
 }
 
-export function RuntimeStates($stateProvider: ng.ui.IStateProvider): ng.IServiceProvider {
+export function XosRuntimeStates($stateProvider: ng.ui.IStateProvider): ng.IServiceProvider {
   this.$get = function($state: ng.ui.IStateService) {
     return {
       addState: function(name: string, state: IXosState) {
diff --git a/src/app/datasources/helpers/model-discoverer.service.ts b/src/app/datasources/helpers/model-discoverer.service.ts
new file mode 100644
index 0000000..deaed33
--- /dev/null
+++ b/src/app/datasources/helpers/model-discoverer.service.ts
@@ -0,0 +1,258 @@
+// TODO test me hard!!!
+
+import * as _ from 'lodash';
+import {IXosModeldefsService, IXosModeldef, IXosModelDefsField, IXosModelDefsRelation} from '../rest/modeldefs.rest';
+import {IXosTableCfg} from '../../core/table/table';
+import {IXosFormCfg} from '../../core/form/form';
+import {IXosNavigationService} from '../../core/services/navigation';
+import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
+import {IXosRuntimeStatesService, IXosState} from '../../core/services/runtime-states';
+import {IXosModelStoreService} from '../stores/model.store';
+
+export interface IXosModel {
+  name: string; // the model name
+  app: string; // the service to wich it belong
+  fields: IXosModelDefsField[];
+  relations?: IXosModelDefsRelation[];
+  backendUrl?: string; // the api endpoint
+  clientUrl?: string; // the view url
+  tableCfg?: IXosTableCfg;
+  formCfg?: IXosFormCfg;
+}
+
+// Service
+export interface IXosModelDiscovererService {
+  discover(): ng.IPromise<boolean>;
+  get(modelName: string): IXosModel;
+}
+
+export class XosModelDiscovererService implements IXosModelDiscovererService {
+  static $inject = [
+    '$log',
+    '$q',
+    'XosModelDefs',
+    'ConfigHelpers',
+    'XosRuntimeStates',
+    'XosNavigationService',
+    'XosModelStore'
+  ];
+  private xosModels: IXosModel[] = []; // list of augmented model definitions;
+  private xosServices: string[] = []; // list of loaded services
+
+  constructor (
+    private $log: ng.ILogService,
+    private $q: ng.IQService,
+    private XosModelDefs: IXosModeldefsService,
+    private ConfigHelpers: IXosConfigHelpersService,
+    private XosRuntimeStates: IXosRuntimeStatesService,
+    private XosNavigationService: IXosNavigationService,
+    private XosModelStore: IXosModelStoreService
+  ) {
+  }
+
+  public get(modelName: string): IXosModel|null {
+    return _.find(this.xosModels, m => m.name === modelName);
+  }
+
+  public discover() {
+    const d = this.$q.defer();
+
+    this.XosModelDefs.get()
+      .then((modelsDef: IXosModeldef[]) => {
+
+        const pArray = [];
+        _.forEach(modelsDef, (model: IXosModeldef) => {
+          let p = this.cacheModelEntries(model)
+            .then(model => {
+              return this.addState(model);
+            })
+            .then(model => {
+              return this.addNavItem(model);
+            })
+            .then(model => {
+              return this.getTableCfg(model);
+            })
+            .then(model => {
+              return this.getFormCfg(model);
+            })
+            .then(model => {
+              return this.storeModel(model);
+            })
+            .then(model => {
+              this.$log.debug(`[XosModelDiscovererService] Model ${model.name} stored`);
+              return this.$q.resolve();
+            })
+            .catch(err => {
+              this.$log.error(`[XosModelDiscovererService] Model ${model.name} NOT stored`);
+              // NOTE why this does not resolve?????
+              // return this.$q.resolve();
+              return this.$q.reject();
+            });
+            pArray.push(p);
+        });
+        this.$q.all(pArray)
+          .then(() => {
+            d.resolve(true);
+            this.$log.info('[XosModelDiscovererService] All models loaded!');
+          })
+          .catch(() => {
+            d.resolve(false);
+          });
+      });
+    return d.promise;
+  }
+
+  private serviceNameFromAppName(appName: string): string {
+    return appName.replace('services.', '');
+  }
+
+  private stateNameFromModel(model: IXosModel): string {
+    return `xos.${this.serviceNameFromAppName(model.app)}.${model.name.toLowerCase()}`;
+  }
+
+  private getParentStateFromModel(model: IXosModel): string {
+    let parentState: string;
+    if (model.app === 'core') {
+      parentState = 'xos.core';
+    }
+    else {
+      const serviceName = this.addService(model);
+      parentState = `xos.${serviceName}`;
+    }
+    return parentState;
+  }
+
+  private getApiUrlFromModel(model: IXosModel): string {
+    if (model.app === 'core') {
+      return `/core/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
+    }
+    else {
+      const serviceName = this.serviceNameFromAppName(model.app);
+      return `/${serviceName}/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
+    }
+  }
+
+  // add a service state and navigation item if it is not already there
+  private addService(model: IXosModel): string {
+    const serviceName: string = this.serviceNameFromAppName(model.app);
+    if (!_.find(this.xosServices, n => n === serviceName)) {
+      const serviceState = {
+        url: serviceName,
+        parent: 'xos',
+        abstract: true,
+        template: '<div ui-view></div>'
+      };
+      this.XosRuntimeStates.addState(`xos.${serviceName}`, serviceState);
+
+      this.XosNavigationService.add({
+        label: this.ConfigHelpers.toLabel(serviceName),
+        state: `xos.${serviceName}`,
+      });
+      this.xosServices.push(serviceName);
+    }
+    return serviceName;
+  }
+
+  private addState(model: IXosModel): ng.IPromise<IXosModel> {
+    const d = this.$q.defer();
+    const clientUrl = `/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}/:id?`;
+    const state: IXosState = {
+      parent: this.getParentStateFromModel(model),
+      url: clientUrl,
+      params: {
+        id: null
+      },
+      data: {
+        model: model.name
+      },
+      component: 'xosCrud',
+    };
+    this.XosRuntimeStates.addState(
+      this.stateNameFromModel(model),
+      state
+    );
+
+    // extend model
+    model.clientUrl = `${this.serviceNameFromAppName(model.app)}${clientUrl}`;
+
+    d.resolve(model);
+    return d.promise;
+  }
+
+  private addNavItem(model: IXosModel): ng.IPromise<IXosModel> {
+    const d = this.$q.defer();
+
+    const stateName = this.stateNameFromModel(model);
+
+    const parentState: string = this.getParentStateFromModel(model);
+
+    this.XosNavigationService.add({
+      label: this.ConfigHelpers.pluralize(model.name),
+      state: stateName,
+      parent: parentState
+    });
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+
+  private cacheModelEntries(model: IXosModel): ng.IPromise<IXosModel> {
+    const d = this.$q.defer();
+
+    let called = false;
+    const apiUrl = this.getApiUrlFromModel(model);
+    this.XosModelStore.query(model.name, apiUrl)
+      .subscribe(
+        () => {
+          // skipping the first response as the observable gets created as an empty array
+          if (called) {
+            return d.resolve(model);
+          }
+          called = true;
+        },
+        err => {
+          d.reject(err);
+        }
+      );
+
+    return d.promise;
+  }
+
+  private getTableCfg(model: IXosModel): ng.IPromise<IXosModel> {
+
+    const d = this.$q.defer();
+
+    const stateUrl = this.stateNameFromModel(model);
+
+    model.tableCfg = this.ConfigHelpers.modelToTableCfg(model, stateUrl);
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+
+  private getFormCfg(model: IXosModel): ng.IPromise<IXosModel> {
+
+    const d = this.$q.defer();
+
+    model.formCfg = this.ConfigHelpers.modelToFormCfg(model);
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+
+  private storeModel(model: IXosModel): ng.IPromise<IXosModel> {
+
+    const d = this.$q.defer();
+
+    if (!_.find(this.xosModels, m => m.name === model.name)) {
+      this.xosModels.push(model);
+    }
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+}
diff --git a/src/app/datasources/helpers/search.service.ts b/src/app/datasources/helpers/search.service.ts
index 5565b2c..8cee4d1 100644
--- a/src/app/datasources/helpers/search.service.ts
+++ b/src/app/datasources/helpers/search.service.ts
@@ -1,8 +1,8 @@
 import * as _ from 'lodash';
 import {IXosNavigationService} from '../../core/services/navigation';
-import {IXosState} from '../../../index';
 import {IXosModelStoreService} from '../stores/model.store';
 import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
+import {IXosState} from '../../core/services/runtime-states';
 
 export interface IXosSearchResult {
   label: string;
@@ -15,16 +15,18 @@
 }
 
 export class SearchService {
-  static $inject = ['$rootScope', 'NavigationService', 'ModelStore', 'ConfigHelpers'];
+  static $inject = ['$rootScope', '$log', 'XosNavigationService', 'XosModelStore', 'ConfigHelpers'];
   private states: IXosState[];
 
   constructor (
     private $rootScope: ng.IScope,
+    private $log: ng.ILogService,
     private NavigationService: IXosNavigationService,
-    private ModelStore: IXosModelStoreService,
+    private XosModelStore: IXosModelStoreService,
     private ConfigHelpers: IXosConfigHelpersService
   ) {
     this.$rootScope.$on('xos.core.modelSetup', () => {
+      this.$log.info(`[XosSearchService] Loading views`);
       this.states = this.NavigationService.query().reduce((list, state) => {
         // if it does not have child (otherwise it is abstract)
         if (!state.children || state.children.length === 0) {
@@ -39,18 +41,21 @@
         return list;
       }, []);
       this.states = _.uniqBy(this.states, 'state');
+      this.$log.debug(`[XosSearchService] Views Loaded: `, this.states);
     });
   }
 
   public search(query: string): IXosSearchResult[] {
+    this.$log.info(`[XosSearchService] Searching for: ${query}`);
     const routes: IXosSearchResult[] = _.filter(this.states, s => {
       return s.label.toLowerCase().indexOf(query) > -1;
     }).map(r => {
       r.type = 'View';
       return r;
     });
-
-    const models = _.map(this.ModelStore.search(query), m => {
+    // TODO XosModelStore.search throws an error,
+    // probably there is something wrong saved in the cache!!
+    const models = _.map(this.XosModelStore.search(query), m => {
       return {
         label: m.humanReadableName ? m.humanReadableName : m.name,
         state: this.ConfigHelpers.stateWithParamsForJs(m.modelName, m),
diff --git a/src/app/datasources/helpers/store.helpers.spec.ts b/src/app/datasources/helpers/store.helpers.spec.ts
index 2cb1eaf..9f4370c 100644
--- a/src/app/datasources/helpers/store.helpers.spec.ts
+++ b/src/app/datasources/helpers/store.helpers.spec.ts
@@ -44,11 +44,9 @@
   it('should convert a core model name in an URL', () => {
     expect(service.urlFromCoreModel('Slice')).toBe('/core/slices');
     expect(service.urlFromCoreModel('Xos')).toBe('/core/xoses');
-
-    // handling exceptions
-    expect(service.urlFromCoreModel('SiteRole')).toBe('/core/site_roles');
-    expect(service.urlFromCoreModel('SliceRole')).toBe('/core/slice_roles');
-    expect(service.urlFromCoreModel('SlicePrivilege')).toBe('/core/slice_privileges');
+    expect(service.urlFromCoreModel('SiteRole')).toBe('/core/siteroles');
+    expect(service.urlFromCoreModel('SliceRole')).toBe('/core/sliceroles');
+    expect(service.urlFromCoreModel('SlicePrivilege')).toBe('/core/sliceprivileges');
   });
 
   describe('when updating a collection', () => {
diff --git a/src/app/datasources/helpers/store.helpers.ts b/src/app/datasources/helpers/store.helpers.ts
index 7792590..c26e235 100644
--- a/src/app/datasources/helpers/store.helpers.ts
+++ b/src/app/datasources/helpers/store.helpers.ts
@@ -9,7 +9,7 @@
   updateCollection(event: IWSEvent, subject: BehaviorSubject<any>): BehaviorSubject<any>;
 }
 
-export class StoreHelpers {
+export class StoreHelpers implements IStoreHelpersService {
   static $inject = ['ModelRest'];
 
   constructor (
@@ -18,15 +18,7 @@
   }
 
   public urlFromCoreModel(name: string): string {
-    switch (name) {
-      // FIXME handling exceptions, understand why these 3 endpoints are autogenerated with an _f
-      case 'SiteRole':
-      case 'SliceRole':
-      case 'SlicePrivilege':
-        return `/core/${pluralize(name.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join('_'))}`;
-      default:
-        return `/core/${pluralize(name.toLowerCase())}`;
-    }
+    return `/core/${pluralize(name.toLowerCase())}`;
   }
 
   public updateCollection(event: IWSEvent, subject: BehaviorSubject<any>): BehaviorSubject<any> {
diff --git a/src/app/datasources/index.ts b/src/app/datasources/index.ts
index 04c58db..80d348a 100644
--- a/src/app/datasources/index.ts
+++ b/src/app/datasources/index.ts
@@ -1,22 +1,27 @@
 import {ModelRest} from './rest/model.rest';
 import {AuthService} from './rest/auth.rest';
 import {WebSocketEvent} from './websocket/global';
-import {ModelStore} from './stores/model.store';
+import {XosModelStore} from './stores/model.store';
 import {StoreHelpers} from './helpers/store.helpers';
 import {SynchronizerStore} from './stores/synchronizer.store';
-import {ModeldefsService} from './rest/modeldefs.rest';
+import {XosModeldefsService} from './rest/modeldefs.rest';
 import {xosCore} from '../core/index';
 import {SearchService} from './helpers/search.service';
+import {XosModelDiscovererService} from './helpers/model-discoverer.service';
 
 export const xosDataSources = 'xosDataSources';
 
 angular
-  .module('xosDataSources', ['ngCookies', 'ngResource', xosCore])
+  .module(xosDataSources, ['ngCookies', 'ngResource', xosCore])
   .service('ModelRest', ModelRest)
   .service('AuthService', AuthService)
   .service('WebSocket', WebSocketEvent)
   .service('StoreHelpers', StoreHelpers)
   .service('SynchronizerStore', SynchronizerStore)
-  .service('ModelStore', ModelStore)
-  .service('ModelDefs', ModeldefsService)
+  .service('XosModelStore', XosModelStore)
+  .service('XosModelDefs', XosModeldefsService)
   .service('SearchService', SearchService);
+
+angular
+  .module(xosDataSources)
+  .service('XosModelDiscoverer', XosModelDiscovererService);
diff --git a/src/app/datasources/rest/auth.rest.ts b/src/app/datasources/rest/auth.rest.ts
index 2979bf1..edc2a65 100644
--- a/src/app/datasources/rest/auth.rest.ts
+++ b/src/app/datasources/rest/auth.rest.ts
@@ -7,9 +7,9 @@
 
 export interface IAuthResponseData extends IHttpPromiseCallbackArg<any> {
   data: {
-    user: string;
-    xoscsrftoken: string;
-    xossessionid: string;
+    // user: string;
+    // xoscsrftoken: string;
+    sessionid: string;
   };
 }
 
@@ -37,12 +37,12 @@
 
   public login(data: IAuthRequestData): Promise<any> {
     const d = this.$q.defer();
-    this.$http.post(`${this.AppConfig.apiEndpoint}/utility/login/`, data)
+    this.$http.post(`${this.AppConfig.apiEndpoint}/utility/login`, data)
       .then((res: IAuthResponseData) => {
-        this.$cookies.put('xoscsrftoken', res.data.xoscsrftoken, {path: '/'});
-        this.$cookies.put('xossessionid', res.data.xossessionid, {path: '/'});
-        this.$cookies.put('xosuser', res.data.user, {path: '/'});
-        res.data.user = JSON.parse(res.data.user);
+        // this.$cookies.put('xoscsrftoken', res.data.xoscsrftoken, {path: '/'});
+        this.$cookies.put('sessionid', res.data.sessionid, {path: '/'});
+        // this.$cookies.put('xosuser', res.data.user, {path: '/'});
+        // res.data.user = JSON.parse(res.data.user);
         d.resolve(res.data);
       })
       .catch(e => {
@@ -53,9 +53,9 @@
 
   public logout(): Promise<any> {
     const d = this.$q.defer();
-    this.$http.post(`${this.AppConfig.apiEndpoint}/utility/logout/`, {
-      xoscsrftoken: this.$cookies.get('xoscsrftoken'),
-      xossessionid: this.$cookies.get('xossessionid')
+    this.$http.post(`${this.AppConfig.apiEndpoint}/utility/logout`, {
+      // xoscsrftoken: this.$cookies.get('xoscsrftoken'),
+      // sessionid: this.$cookies.get('sessionid')
     })
       .then(() => {
         this.clearUser();
@@ -68,9 +68,9 @@
   }
 
   public clearUser(): void {
-    this.$cookies.remove('xoscsrftoken', {path: '/'});
-    this.$cookies.remove('xossessionid', {path: '/'});
-    this.$cookies.remove('xosuser', {path: '/'});
+    // this.$cookies.remove('xoscsrftoken', {path: '/'});
+    this.$cookies.remove('sessionid', {path: '/'});
+    // this.$cookies.remove('xosuser', {path: '/'});
   }
 
   public getUser(): IXosUser {
@@ -82,8 +82,8 @@
   }
 
   public isAuthenticated(): boolean {
-    const token = this.$cookies.get('xoscsrftoken');
-    const session = this.$cookies.get('xossessionid');
-    return angular.isDefined(token) && angular.isDefined(session);
+    // const token = this.$cookies.get('xoscsrftoken');
+    const session = this.$cookies.get('sessionid');
+    return angular.isDefined(session);
   }
 }
diff --git a/src/app/datasources/rest/model.rest.ts b/src/app/datasources/rest/model.rest.ts
index 12590af..4aa862c 100644
--- a/src/app/datasources/rest/model.rest.ts
+++ b/src/app/datasources/rest/model.rest.ts
@@ -16,7 +16,15 @@
 
   public getResource(url: string): ng.resource.IResourceClass<ng.resource.IResource<any>> {
     const resource: angular.resource.IResourceClass<any> = this.$resource(`${this.AppConfig.apiEndpoint}${url}/:id/`, {id: '@id'}, {
-      update: { method: 'PUT' }
+      update: { method: 'PUT' },
+      query: {
+        method: 'GET',
+        isArray: true,
+        transformResponse: (res) => {
+          // FIXME chameleon return everything inside "items"
+          return res.items ? res.items : res;
+        }
+      }
     });
 
     resource.prototype.$save = function() {
diff --git a/src/app/datasources/rest/modeldefs.rest.spec.ts b/src/app/datasources/rest/modeldefs.rest.spec.ts
index 9dc4025..677463b 100644
--- a/src/app/datasources/rest/modeldefs.rest.spec.ts
+++ b/src/app/datasources/rest/modeldefs.rest.spec.ts
@@ -3,9 +3,9 @@
 import 'angular-resource';
 import 'angular-cookies';
 import {xosDataSources} from '../index';
-import {IModeldefsService} from './modeldefs.rest';
+import {IXosModeldefsService} from './modeldefs.rest';
 
-let service: IModeldefsService;
+let service: IXosModeldefsService;
 let httpBackend: ng.IHttpBackendService;
 let $scope;
 
@@ -14,7 +14,7 @@
   websocketClient: 'http://xos-test:3000'
 };
 
-describe('The ModelDefs service', () => {
+describe('The XosModelDefs service', () => {
 
   beforeEach(angular.mock.module(xosDataSources));
 
@@ -26,14 +26,13 @@
     angular.mock.module(xosDataSources);
   });
 
-
   beforeEach(angular.mock.inject((
-    ModelDefs: IModeldefsService,
+    XosModelDefs: IXosModeldefsService,
     $httpBackend: ng.IHttpBackendService,
     _$resource_: ng.resource.IResourceService,
     _$rootScope_: ng.IRootScopeService
   ) => {
-    service = ModelDefs;
+    service = XosModelDefs;
     httpBackend = $httpBackend;
     $scope = _$rootScope_;
   }));
diff --git a/src/app/datasources/rest/modeldefs.rest.ts b/src/app/datasources/rest/modeldefs.rest.ts
index d927f8c..fdb4b99 100644
--- a/src/app/datasources/rest/modeldefs.rest.ts
+++ b/src/app/datasources/rest/modeldefs.rest.ts
@@ -1,17 +1,35 @@
 import {IXosModelDefsField} from '../../core/services/helpers/config.helpers';
 import {IXosAppConfig} from '../../../index';
 
-export interface IModeldef {
-  fields: IXosModelDefsField[];
-  relations?: string[];
+// Models interfaces
+export interface IXosModelDefsField {
   name: string;
+  type: string;
+  validators?: any;
+  hint?: string;
+  relation?: {
+    model: string;
+    type: string;
+  };
 }
 
-export interface IModeldefsService {
-  get(): Promise<IModeldef[]>;
+export interface IXosModelDefsRelation {
+  model: string; // model name
+  type: string; // relation type
 }
 
-export class ModeldefsService {
+export interface IXosModeldef {
+  fields: IXosModelDefsField[];
+  relations?: IXosModelDefsRelation[];
+  name: string;
+  app: string;
+}
+
+export interface IXosModeldefsService {
+  get(): Promise<IXosModeldef[]>;
+}
+
+export class XosModeldefsService implements IXosModeldefsService {
 
   static $inject = ['$http', '$q', 'AppConfig'];
 
@@ -24,9 +42,9 @@
 
   public get(): Promise<any> {
     const d = this.$q.defer();
-    this.$http.get(`${this.AppConfig.apiEndpoint}/utility/modeldefs/`)
-      .then((res) => {
-        d.resolve(res.data);
+    this.$http.get(`${this.AppConfig.apiEndpoint}/modeldefs`)
+      .then((res: any) => {
+        d.resolve(res.data.items);
       })
       .catch(e => {
         d.reject(e);
diff --git a/src/app/datasources/stores/model.store.spec.ts b/src/app/datasources/stores/model.store.spec.ts
index 7173658..533a53d 100644
--- a/src/app/datasources/stores/model.store.spec.ts
+++ b/src/app/datasources/stores/model.store.spec.ts
@@ -1,7 +1,7 @@
 import * as angular from 'angular';
 import 'angular-mocks';
 import 'angular-resource';
-import {IXosModelStoreService, ModelStore} from './model.store';
+import {IXosModelStoreService, XosModelStore} from './model.store';
 import {Subject} from 'rxjs';
 import {IWSEvent} from '../websocket/global';
 import {StoreHelpers} from '../helpers/store.helpers';
@@ -46,7 +46,7 @@
       .service('WebSocket', MockWs)
       .service('StoreHelpers', StoreHelpers) // TODO mock
       .service('ModelRest', ModelRest) // TODO mock
-      .service('ModelStore', ModelStore)
+      .service('XosModelStore', XosModelStore)
       .service('ConfigHelpers', ConfigHelpers) // TODO mock
       .service('AuthService', AuthService)
       .constant('AppConfig', MockAppCfg);
@@ -55,12 +55,12 @@
   });
 
   beforeEach(angular.mock.inject((
-    ModelStore: IXosModelStoreService,
+    XosModelStore: IXosModelStoreService,
     $httpBackend: ng.IHttpBackendService,
     _$rootScope_: ng.IRootScopeService,
     _WebSocket_: any
   ) => {
-    service = ModelStore;
+    service = XosModelStore;
     httpBackend = $httpBackend;
     $scope = _$rootScope_;
     WebSocket = _WebSocket_;
diff --git a/src/app/datasources/stores/model.store.ts b/src/app/datasources/stores/model.store.ts
index 291e7c0..4958015 100644
--- a/src/app/datasources/stores/model.store.ts
+++ b/src/app/datasources/stores/model.store.ts
@@ -6,11 +6,11 @@
 import {IStoreHelpersService} from '../helpers/store.helpers';
 
 export interface  IXosModelStoreService {
-  query(model: string): Observable<any>;
+  query(model: string, apiUrl?: string): Observable<any>;
   search(modelName: string): any[];
 }
 
-export class ModelStore implements IXosModelStoreService {
+export class XosModelStore implements IXosModelStoreService {
   static $inject = ['$log', 'WebSocket', 'StoreHelpers', 'ModelRest'];
   private _collections: any; // NOTE contains a map of {model: BehaviourSubject}
   constructor(
@@ -22,59 +22,76 @@
     this._collections = {};
   }
 
-  public query(model: string): Observable<any> {
+  public query(modelName: string, apiUrl: string): Observable<any> {
     // if there isn't already an observable for that item
-    if (!this._collections[model]) {
-      this._collections[model] = new BehaviorSubject([]); // NOTE maybe this can be created when we get response from the resource
-      this.loadInitialData(model);
+    if (!this._collections[modelName]) {
+      this._collections[modelName] = new BehaviorSubject([]); // NOTE maybe this can be created when we get response from the resource
+      this.loadInitialData(modelName, apiUrl);
     }
 
     this.webSocket.list()
-      .filter((e: IWSEvent) => e.model === model)
+      .filter((e: IWSEvent) => e.model === modelName)
       .subscribe(
         (event: IWSEvent) => {
-          this.storeHelpers.updateCollection(event, this._collections[model]);
+          this.storeHelpers.updateCollection(event, this._collections[modelName]);
         },
         err => console.error
       );
 
-    return this._collections[model].asObservable();
+    return this._collections[modelName].asObservable();
   }
 
   public search(modelName: string): any[] {
-    return _.reduce(Object.keys(this._collections), (results, k) => {
-      // console.log(k, this._collections[k].value)
-      const partialRes = _.filter(this._collections[k].value, i => {
-        if (i.humanReadableName) {
-          return i.humanReadableName.toLowerCase().indexOf(modelName) > -1;
+    try {
+      const res =  _.reduce(Object.keys(this._collections), (results, k) => {
+        let partialRes;
+        // NOTE wrapped in a try catch as some subject may be errored, due to not available REST endpoint
+        try {
+          partialRes = _.filter(this._collections[k].value, i => {
+            if (i && i.humanReadableName) {
+              return i.humanReadableName.toLowerCase().indexOf(modelName) > -1;
+            }
+            else if (i && i.name) {
+              return i.name.toLowerCase().indexOf(modelName) > -1;
+            }
+            return false;
+          });
+        } catch (e) {
+          partialRes = [];
         }
-        else if (i.name) {
-          return i.name.toLowerCase().indexOf(modelName) > -1;
-        }
-        return false;
-      })
-        .map(m => {
+        partialRes.map(m => {
           m.modelName = k;
           return m;
         });
-      return results.concat(partialRes);
-    }, []);
+        return results.concat(partialRes);
+      }, []);
+      return res;
+    } catch (e) {
+      return [];
+    }
   }
 
   public get(model: string, id: number) {
     // TODO implement a get method
   }
 
-  private loadInitialData(model: string) {
-    // NOTE check what is the correct pattern to pluralize this
-    const endpoint = this.storeHelpers.urlFromCoreModel(model);
-    this.ModelRest.getResource(endpoint).query().$promise
+  private loadInitialData(model: string, apiUrl?: string) {
+    // TODO provide alway the apiUrl togheter with the query() params
+    if (!angular.isDefined(apiUrl)) {
+      // NOTE check what is the correct pattern to pluralize this
+      apiUrl = this.storeHelpers.urlFromCoreModel(model);
+    }
+    this.ModelRest.getResource(apiUrl).query().$promise
       .then(
         res => {
           this._collections[model].next(res);
         })
       .catch(
-        err => this.$log.log(`Error retrieving ${model}`, err)
+        // TODO understand how to send an error to an observable
+        err => {
+          this._collections[model].error(err);
+          // this.$log.log(`Error retrieving ${model}`, err);
+        }
       );
   }
 }
diff --git a/src/app/extender/services/onboard.service.spec.ts b/src/app/extender/services/onboard.service.spec.ts
index 375aadb..17a7543 100644
--- a/src/app/extender/services/onboard.service.spec.ts
+++ b/src/app/extender/services/onboard.service.spec.ts
@@ -50,7 +50,7 @@
       .module('XosOnboarder', [])
       .value('WebSocket', MockWs)
       .value('$ocLazyLoad', MockLoad)
-      .value('ModelStore', MockModelStore)
+      .value('XosModelStore', MockModelStore)
       .service('XosOnboarder', XosOnboarder);
 
     angular.mock.module('XosOnboarder');
diff --git a/src/app/extender/services/onboard.service.ts b/src/app/extender/services/onboard.service.ts
index 402557c..2f85028 100644
--- a/src/app/extender/services/onboard.service.ts
+++ b/src/app/extender/services/onboard.service.ts
@@ -8,7 +8,7 @@
 }
 
 export class XosOnboarder implements IXosOnboarder {
-  static $inject = ['$timeout', '$log', '$q', 'WebSocket', '$ocLazyLoad', 'ModelStore'];
+  static $inject = ['$timeout', '$log', '$q', 'WebSocket', '$ocLazyLoad', 'XosModelStore'];
 
   constructor(
     private $timeout: ng.ITimeoutService,
@@ -16,7 +16,7 @@
     private $q: ng.IQService,
     private webSocket: IWSEventService,
     private $ocLazyLoad: any, // TODO add definition
-    private ModelStore: IXosModelStoreService
+    private XosModelStore: IXosModelStoreService
   ) {
     this.$log.info('[XosOnboarder] Setup');
 
@@ -45,7 +45,7 @@
 
     // Load previously onboarded app (containers are already running, so we don't need to wait)
     let componentsLoaded = false;
-    const ComponentObservable: Observable<any> = this.ModelStore.query('XOSComponent');
+    const ComponentObservable: Observable<any> = this.XosModelStore.query('XOSComponent');
     ComponentObservable.subscribe(
         (component) => {
           if (componentsLoaded) {
diff --git a/src/app/views/crud/crud.html b/src/app/views/crud/crud.html
index 8d457e2..adb0500 100644
--- a/src/app/views/crud/crud.html
+++ b/src/app/views/crud/crud.html
@@ -26,11 +26,11 @@
         </div>
         <div class="col-lg-8 text-right">
             <div class="btn-group">
-                <a ng-if="vm.list" ng-repeat="r in vm.related" href="#/core/{{r.toLowerCase()}}s/" class="btn btn-default">
-                    {{r}}
+                <a ng-if="vm.list" ng-repeat="r in vm.related" href="#/core/{{r.model.toLowerCase()}}s/" class="btn btn-default">
+                    {{r.model}}
                 </a>
-                <a ng-if="!vm.list && vm.getRelatedItem(r, vm.model)" ng-repeat="r in vm.related" href="#/core/{{r.toLowerCase()}}s/{{vm.getRelatedItem(r, vm.model)}}" class="btn btn-default">
-                    {{r}}
+                <a ng-if="!vm.list && vm.getRelatedItem(r, vm.model)" ng-repeat="r in vm.related" href="#/core/{{r.model.toLowerCase()}}s/{{vm.getRelatedItem(r, vm.model)}}" class="btn btn-default">
+                    {{r.model}}
                 </a>
             </div>
         </div>
diff --git a/src/app/views/crud/crud.ts b/src/app/views/crud/crud.ts
index 846a840..6b30edc 100644
--- a/src/app/views/crud/crud.ts
+++ b/src/app/views/crud/crud.ts
@@ -2,23 +2,38 @@
 import {IXosModelStoreService} from '../../datasources/stores/model.store';
 import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
 import * as _ from 'lodash';
-import {IXosFormConfig} from '../../core/form/form';
+import {IXosFormCfg} from '../../core/form/form';
 import {IXosResourceService} from '../../datasources/rest/model.rest';
 import {IStoreHelpersService} from '../../datasources/helpers/store.helpers';
+import {IXosModelDiscovererService} from '../../datasources/helpers/model-discoverer.service';
+
 export interface IXosCrudData {
   model: string;
-  related: string[];
+  related: IXosModelRelation[];
   xosTableCfg: IXosTableCfg;
-  xosFormCfg: IXosFormConfig;
+  xosFormCfg: IXosFormCfg;
+}
+
+export interface IXosModelRelation {
+  model: string;
+  type: string;
 }
 
 class CrudController {
-  static $inject = ['$scope', '$state', '$stateParams', 'ModelStore', 'ConfigHelpers', 'ModelRest', 'StoreHelpers'];
+  static $inject = [
+    '$scope',
+    '$state',
+    '$stateParams',
+    'XosModelStore',
+    'ConfigHelpers',
+    'ModelRest',
+    'StoreHelpers',
+    'XosModelDiscoverer'
+  ];
 
-  public data: IXosCrudData;
+  public data: {model: string};
   public tableCfg: IXosTableCfg;
   public formCfg: any;
-  public stateName: string;
   public baseUrl: string;
   public list: boolean;
   public title: string;
@@ -33,19 +48,23 @@
     private store: IXosModelStoreService,
     private ConfigHelpers: IXosConfigHelpersService,
     private ModelRest: IXosResourceService,
-    private StoreHelpers: IStoreHelpersService
+    private StoreHelpers: IStoreHelpersService,
+    private XosModelDiscovererService: IXosModelDiscovererService
   ) {
     this.data = this.$state.current.data;
-    this.tableCfg = this.data.xosTableCfg;
+    this.model = this.XosModelDiscovererService.get(this.data.model);
     this.title = this.ConfigHelpers.pluralize(this.data.model);
 
     this.list = true;
-    this.stateName = $state.current.name;
-    this.baseUrl = '#/core' + $state.current.url.toString().replace(':id?', '');
+
+    // TODO get the proper URL from model discoverer
+    this.baseUrl = '#/' + this.model.clientUrl.replace(':id?', '');
+
 
     this.related = $state.current.data.related;
 
-    this.formCfg = $state.current.data.xosFormCfg;
+    this.tableCfg = this.model.tableCfg;
+    this.formCfg = this.model.formCfg;
 
     this.store.query(this.data.model)
       .subscribe(
@@ -70,6 +89,7 @@
       // if it is the create page
       if ($stateParams['id'] === 'add') {
         // generate a resource for an empty model
+        // TODO get the proper URL from model discoverer
         const endpoint = this.StoreHelpers.urlFromCoreModel(this.data.model);
         const resource = this.ModelRest.getResource(endpoint);
         this.model = new resource({});
@@ -77,9 +97,9 @@
     }
   }
 
-  public getRelatedItem(relation: string, item: any): number {
-    if (item && angular.isDefined(item[relation.toLowerCase()])) {
-      return item[relation.toLowerCase()];
+  public getRelatedItem(relation: IXosModelRelation, item: any): number {
+    if (item && angular.isDefined(item[`${relation.model.toLowerCase()}_id`])) {
+      return item[`${relation.model.toLowerCase()}_id`];
     }
     return 0;
   }
diff --git a/src/app/views/dashboard/dashboard.ts b/src/app/views/dashboard/dashboard.ts
index 00fc1b4..21fbbde 100644
--- a/src/app/views/dashboard/dashboard.ts
+++ b/src/app/views/dashboard/dashboard.ts
@@ -1,7 +1,7 @@
 import {IXosModelStoreService} from '../../datasources/stores/model.store';
 import {IXosAuthService} from '../../datasources/rest/auth.rest';
 class DashboardController {
-  static $inject = ['$scope', '$state', 'ModelStore', 'AuthService'];
+  static $inject = ['$scope', '$state', 'XosModelStore', 'AuthService'];
 
   public nodes: number;
   public slices: number;
diff --git a/src/decorators.ts b/src/decorators.ts
index 6f2628d..c4543e2 100644
--- a/src/decorators.ts
+++ b/src/decorators.ts
@@ -1,7 +1,9 @@
 export default function XosLogDecorator($provide: ng.auto.IProvideService) {
   $provide.decorator('$log', function($delegate: any) {
     const isLogEnabled = () => {
-      return window.location.href.indexOf('debug=true') >= 0;
+      // NOTE to enable debug, in the broser console set: localStorage.debug = 'true'
+      // NOTE to disable debug, in the broser console set: localStorage.debug = 'false'
+      return window.localStorage.getItem('debug') === 'true';
     };
     // Save the original $log.debug()
     let logFn = $delegate.log;
diff --git a/src/index.ts b/src/index.ts
index 74b0c04..de9d9bf 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -20,18 +20,13 @@
   interceptorConfig, userStatusInterceptor, CredentialsInterceptor,
   NoHyperlinksInterceptor
 } from './interceptors';
-import {IXosCrudData} from './app/views/crud/crud';
 import {IXosPageTitleService} from './app/core/services/page-title';
 import {IXosAuthService} from './app/datasources/rest/auth.rest';
-import {IXosModelSetupService} from './app/core/services/helpers/model-setup.helpers';
 import {IXosNavigationRoute} from './app/core/services/navigation';
 import XosLogDecorator from './decorators';
 import {xosExtender} from './app/extender/index';
 import {IXosKeyboardShortcutService} from './app/core/services/keyboard-shortcut';
-
-export interface IXosState extends angular.ui.IState {
-  data: IXosCrudData;
-};
+import {IXosModelDiscovererService} from './app/datasources/helpers/model-discoverer.service';
 
 export interface IXosAppConfig {
   apiEndpoint: string;
@@ -78,9 +73,10 @@
   .run((
     $rootScope: ng.IRootScopeService,
     $transitions: any,
+    $log: ng.ILogService,
     $location: ng.ILocationService,
     $state: ng.ui.IStateService,
-    ModelSetup: IXosModelSetupService,
+    XosModelDiscoverer: IXosModelDiscovererService,
     AuthService: IXosAuthService,
     XosKeyboardShortcut: IXosKeyboardShortcutService,
     toastr: ng.toastr.IToastrService,
@@ -89,7 +85,7 @@
 
     // check the user login
     $transitions.onSuccess({ to: '**' }, (transtion) => {
-      if (!AuthService.getUser()) {
+      if (!AuthService.isAuthenticated()) {
         $state.go('login');
       }
     });
@@ -112,9 +108,17 @@
     const lastQueryString = $location.search();
 
     // if the user is authenticated
-    if (AuthService.getUser()) {
-      ModelSetup.setup()
-        .then(() => {
+    if (AuthService.isAuthenticated()) {
+      // ModelSetup.setup()
+      XosModelDiscoverer.discover()
+        .then((res) => {
+          if (res) {
+            $log.info('[XOS] All models loaded');
+          }
+          else {
+            $log.info('[XOS] Failed to load some models, moving on.');
+          }
+
           // after setting up dynamic routes, redirect to previous state
           $location.path(lastRoute).search(lastQueryString);
           $rootScope.$emit('xos.core.modelSetup');
@@ -124,5 +128,21 @@
     // register keyboard shortcut
     XosKeyboardShortcut.setup();
 
+    XosKeyboardShortcut.registerKeyBinding({
+      key: 'd',
+      // modifiers: ['Command'],
+      cb: () => {
+        if (window.localStorage.getItem('debug') === 'true') {
+          $log.info(`[XosKeyboardShortcut] Disabling debug`);
+          window.localStorage.setItem('debug', 'false');
+        }
+        else {
+          window.localStorage.setItem('debug', 'true');
+          $log.info(`[XosKeyboardShortcut] Enabling debug`);
+        }
+      },
+      description: 'Toggle debug messages in browser console'
+    }, 'global');
+
   });
 
diff --git a/src/interceptors.ts b/src/interceptors.ts
index 9e3ba65..1ba6423 100644
--- a/src/interceptors.ts
+++ b/src/interceptors.ts
@@ -6,16 +6,13 @@
   $httpProvider.interceptors.push('UserStatusInterceptor');
   $httpProvider.interceptors.push('CredentialsInterceptor');
   $httpProvider.interceptors.push('NoHyperlinksInterceptor');
-  $resourceProvider.defaults.stripTrailingSlashes = false;
 }
 
 export function userStatusInterceptor($state: angular.ui.IStateService, $cookies: ng.cookies.ICookiesService) {
 
   const checkLogin = (res) => {
     if (res.status === 401 || res.status === -1) {
-      $cookies.remove('xoscsrftoken', {path: '/'});
-      $cookies.remove('xossessionid', {path: '/'});
-      $cookies.remove('xosuser', {path: '/'});
+      $cookies.remove('sessionid', {path: '/'});
       $state.go('login');
     }
     return res;
@@ -30,12 +27,11 @@
 export function CredentialsInterceptor($cookies: angular.cookies.ICookiesService) {
   return {
     request: (req) => {
-      if (!$cookies.get('xoscsrftoken') || !$cookies.get('xossessionid')) {
+      if (!$cookies.get('sessionid')) {
         return req;
       }
-      // req.headers['X-CSRFToken'] = $cookies.get('xoscsrftoken');
-      req.headers['x-csrftoken'] = $cookies.get('xoscsrftoken');
-      req.headers['x-sessionid'] = $cookies.get('xossessionid');
+      req.headers['x-sessionid'] = $cookies.get('sessionid');
+      req.headers['x-xossession'] = $cookies.get('sessionid');
       return req;
     }
   };
@@ -44,15 +40,26 @@
 export function NoHyperlinksInterceptor() {
   return {
     request: (req) => {
-      if (
-        req.url.indexOf('.html') === -1
-        // && req.url.indexOf('login') === -1
-        // && req.url.indexOf('logout') === -1
-      ) {
-        // NOTE this may fail if there are already query params
-        req.url += '?no_hyperlinks=1';
+      if (req.url.indexOf('.html') === -1) {
+        // NOTE  force content type to be JSON
+        req.headers['Content-Type'] = 'application/json';
       }
       return req;
+    },
+    response: (res) => {
+      try {
+        // NOTE convert res.data from string to JSON
+        res.data = JSON.parse(res.data);
+        try {
+          // NOTE chameleon return everything inside an "items" field
+          res.data = res.data.items;
+        } catch (_e) {
+          res.data = res.data;
+        }
+      } catch (e) {
+        res.data = res.data;
+      }
+      return res;
     }
   };
 }
diff --git a/src/routes.ts b/src/routes.ts
index 3e9274e..8685ce7 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -24,11 +24,6 @@
       url: 'core',
       parent: 'xos',
       abstract: true,
-      template: '<div ui-view=></div>'
-    })
-    .state('test', {
-      url: '/test/:id?',
-      parent: 'xos.core',
-      template: '<h1>Child</h1>'
+      template: '<div ui-view></div>'
     });
 }