Repository: jhipster/jhipster-control-center (ts)

Issue:
Refresh button
Similar to what is done for Registry, it would be interesting to have this, in the important page:
- refresh on click
- refresh each 5sec, 10sec, 30sec, 60sec

Pull request:
add refresh button
add refresh button which allow to select refresh times for applications instances list
(todo : test + fix refresh button-content instantly after select refresh time)

Fix #12

Patch:
diff --git a/src/main/webapp/app/applications/applications-modal.component.ts b/src/main/webapp/app/applications/applications-modal.component.ts
deleted file mode 100644
index 82754dc..0000000
--- a/src/main/webapp/app/applications/applications-modal.component.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import ApplicationsService, { Application } from './applications.service';
-import { Component, Inject, Prop, Vue } from 'vue-property-decorator';
-
-@Component
-export default class JhiApplicationsModal extends Vue {
-  public activeProfiles: any = null;
-  @Prop() currentApplication!: Application;
-  @Prop() currentRoute!: any;
-  @Inject('applicationsService') private applicationsService: () => ApplicationsService;
-
-  public mounted(): void {
-    this.refreshProfile();
-  }
-
-  /** Update profile of current application */
-  public refreshProfile(): void {
-    /* istanbul ignore else */
-    if (this.currentApplication.serviceId !== 'consul') {
-      this.applicationsService()
-        .findActiveProfiles(this.currentRoute)
-        .then(res => {
-          this.activeProfiles = res.data;
-        })
-        .catch(error => {
-          this.activeProfiles = error.error;
-        });
-    } else {
-      this.activeProfiles = {};
-    }
-  }
-}
diff --git a/src/main/webapp/app/applications/applications.component.ts b/src/main/webapp/app/applications/applications.component.ts
deleted file mode 100644
index 3e127fd..0000000
--- a/src/main/webapp/app/applications/applications.component.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import ApplicationsService, { Application, Instance } from './applications.service';
-import { Component, Inject, Vue } from 'vue-property-decorator';
-import JhiApplicationsModal from './applications-modal.vue';
-import { Subject } from 'rxjs';
-import { takeUntil } from 'rxjs/operators';
-
-@Component({
-  components: {
-    'applications-modal': JhiApplicationsModal
-  }
-})
-export default class JhiApplications extends Vue {
-  public applications?: Array<Application> = null;
-  public applicationsRoute: Array<any> = null;
-  public currentApplication: Application = null;
-  public currentRoute: any = null;
-  public applicationModal: any = null;
-  private unsubscribe$ = new Subject();
-  @Inject('applicationsService') private applicationsService: () => ApplicationsService;
-
-  public mounted(): void {
-    this.refreshApplicationsData();
-    this.refreshApplicationsRoute();
-  }
-
-  /** Update applicationsData which contains list of applications */
-  public refreshApplicationsData(): void {
-    this.applicationsService()
-      .findAll()
-      .pipe(takeUntil(this.unsubscribe$))
-      .subscribe(applications => {
-        this.applications = applications;
-      });
-  }
-
-  /** Update applicationsRoute which contains list of routes */
-  public refreshApplicationsRoute(): void {
-    this.applicationsService()
-      .findAllGatewayRoute()
-      .then(res => {
-        this.applicationsRoute = res.data;
-      })
-      .catch(error => {
-        this.applicationsRoute = error.error;
-      });
-  }
-
-  /** Display details about an application in a b-modal */
-  public showApplication(application: any, uri: string): void {
-    this.currentApplication = application;
-    for (let i = 0; i < this.applicationsRoute.length; i++) {
-      if (this.applicationsRoute[i].uri === uri) {
-        this.currentRoute = this.applicationsRoute[i].route_id;
-        break;
-      }
-    }
-    this.applicationModal = <any>this.$refs.applicationModal;
-    this.applicationModal.show();
-  }
-
-  /* istanbul ignore next */
-  beforeDestroy(): any {
-    // prevent memory leak when component destroyed
-    this.unsubscribe$.next();
-    this.unsubscribe$.complete();
-  }
-}
diff --git a/src/main/webapp/app/applications/applications.service.ts b/src/main/webapp/app/applications/applications.service.ts
deleted file mode 100644
index 08e6288..0000000
--- a/src/main/webapp/app/applications/applications.service.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import axios, { AxiosPromise } from 'axios';
-import Vue from 'vue';
-import Component from 'vue-class-component';
-import { Observable } from 'rxjs';
-
-export interface Application {
-  constructor: Function;
-  serviceId: string;
-  instance: Instance;
-}
-
-export type Instance = { [key in InstanceKey]?: any };
-
-export type InstanceKey = 'instanceId' | 'uri' | 'serviceId' | 'port' | 'secure' | 'metadata';
-
-@Component
-export default class ApplicationsService extends Vue {
-  /** get all applications instances */
-  public findAll(): Observable<Array<Application>> {
-    return Observable.create(observer => {
-      axios
-        .get('api/services/instances')
-        .then(res => {
-          // generate application list from http request
-          const applications = this.parseJsonArrayApplicationsToArray(res.data);
-          observer.next(applications);
-          observer.complete();
-        })
-        .catch(error => {
-          observer.error(error);
-        });
-    });
-  }
-
-  /** get all gateway routes of applications instances */
-  public findAllGatewayRoute(): AxiosPromise<any> {
-    return axios.get('management/gateway/routes');
-  }
-
-  /** get active profiles of an application instance */
-  public findActiveProfiles(gatewayRoute: string): AxiosPromise<any> {
-    return axios.get('gateway/' + String(gatewayRoute) + '/management/info');
-  }
-
-  /** convert json array of applications to array of applications */
-  parseJsonArrayApplicationsToArray(data: any): Array<Application> {
-    const applications: Array<Application> = [];
-    data.map(application => {
-      const app = new class implements Application {
-        serviceId: string;
-        instance: Instance;
-      }();
-      app.serviceId = application.serviceId;
-      app.instance = application;
-      applications.push(app);
-    });
-    return applications;
-  }
-}
diff --git a/src/main/webapp/app/applications/instance/instance-modal.component.ts b/src/main/webapp/app/applications/instance/instance-modal.component.ts
new file mode 100644
index 0000000..bbcb52c
--- /dev/null
+++ b/src/main/webapp/app/applications/instance/instance-modal.component.ts
@@ -0,0 +1,31 @@
+import InstanceService, { Instance } from './instance.service';
+import { Component, Inject, Prop, Vue } from 'vue-property-decorator';
+
+@Component
+export default class JhiInstanceModal extends Vue {
+  public activeProfiles: any = null;
+  @Prop() selectedInstance!: Instance;
+  @Prop() selectedInstanceRoute!: any;
+  @Inject('instanceService') private instanceService: () => InstanceService;
+
+  public mounted(): void {
+    this.refreshProfile();
+  }
+
+  /** Update profile of current application instance */
+  public refreshProfile(): void {
+    /* istanbul ignore else */
+    if (this.selectedInstance.serviceId !== 'consul') {
+      this.instanceService()
+        .findActiveProfiles(this.selectedInstanceRoute)
+        .then(res => {
+          this.activeProfiles = res.data;
+        })
+        .catch(error => {
+          this.activeProfiles = error.error;
+        });
+    } else {
+      this.activeProfiles = {};
+    }
+  }
+}
diff --git a/src/main/webapp/app/applications/applications-modal.vue b/src/main/webapp/app/applications/instance/instance-modal.vue
similarity index 74%
rename from src/main/webapp/app/applications/applications-modal.vue
rename to src/main/webapp/app/applications/instance/instance-modal.vue
index df5e62b..83f0a78 100644
--- a/src/main/webapp/app/applications/applications-modal.vue
+++ b/src/main/webapp/app/applications/instance/instance-modal.vue
@@ -1,6 +1,6 @@
 <template>
     <div class="modal-body pad">
-        <div v-if="currentApplication && currentRoute">
+        <div v-if="selectedInstance && selectedInstanceRoute">
             <div class="table">
                 <table class="table table-bordered">
                     <tbody>
@@ -9,8 +9,8 @@
                                 <div>URI</div>
                             </td>
                             <td class="text-left">
-                                <a :href="currentApplication.instance.uri" target="_blank">
-                                    {{currentApplication.instance.uri}}
+                                <a :href="selectedInstance.uri" target="_blank">
+                                    {{selectedInstance.uri}}
                                 </a>
                             </td>
                         </tr>
@@ -19,7 +19,7 @@
                                 Port
                             </td>
                             <td class="text-left">
-                                {{currentApplication.instance.port}}
+                                {{selectedInstance.port}}
                             </td>
                         </tr>
                         <tr>
@@ -27,7 +27,7 @@
                                 Secure
                             </td>
                             <td class="text-left">
-                                {{currentApplication.instance.secure}}
+                                {{selectedInstance.secure}}
                             </td>
                         </tr>
                         <tr>
@@ -36,7 +36,7 @@
                             </td>
                             <td class="text-left">
                                 <span class="badge badge-pill badge-info">
-                                    {{currentRoute}}
+                                    {{selectedInstanceRoute}}
                                 </span>
                             </td>
                         </tr>
@@ -50,32 +50,32 @@
                                 </span>
                             </td>
                         </tr>
-                        <tr v-if="currentApplication.instance.metadata && currentApplication.instance.metadata.profile">
+                        <tr v-if="selectedInstance.metadata && selectedInstance.metadata.profile">
                             <td class="text-left">
                                 Profile
                             </td>
                             <td class="text-left">
-                                <span class="badge badge-pill badge-primary">{{currentApplication.instance.metadata.profile}}</span>
+                                <span class="badge badge-pill badge-primary">{{selectedInstance.metadata.profile}}</span>
                             </td>
                         </tr>
-                        <tr v-if="currentApplication.instance.metadata && currentApplication.instance.metadata.version">
+                        <tr v-if="selectedInstance.metadata && selectedInstance.metadata.version">
                             <td class="text-left">
                                 Version
                             </td>
                             <td class="text-left">
-                                <span class="badge badge-pill badge-primary">{{currentApplication.instance.metadata.version}}</span>
+                                <span class="badge badge-pill badge-primary">{{selectedInstance.metadata.version}}</span>
                             </td>
                         </tr>
                     </tbody>
                 </table>
             </div>
         </div>
-        <div v-if="currentRoute && currentRoute.error">
+        <div v-if="selectedInstanceRoute && selectedInstanceRoute.error">
             <h4>Error</h4>
-            <pre>{{currentRoute.error}}</pre>
+            <pre>{{selectedInstanceRoute.error}}</pre>
         </div>
     </div>
 </template>
 
-<script lang="ts" src="./applications-modal.component.ts">
+<script lang="ts" src="./instance-modal.component.ts">
 </script>
diff --git a/src/main/webapp/app/applications/instance/instance.component.ts b/src/main/webapp/app/applications/instance/instance.component.ts
new file mode 100644
index 0000000..2675057
--- /dev/null
+++ b/src/main/webapp/app/applications/instance/instance.component.ts
@@ -0,0 +1,73 @@
+import { Component, Inject, Mixins, Vue } from 'vue-property-decorator';
+import InstanceService, { Instance } from './instance.service';
+import InstanceModalVue from './instance-modal.vue';
+import { takeUntil } from 'rxjs/operators';
+import { Subject } from 'rxjs';
+import RefreshSelectorMixin from '@/shared/refresh/refresh-selector.mixin.ts';
+import RefreshSelectorVue from '@/shared/refresh/refresh-selector.mixin.vue';
+
+@Component({
+  components: {
+    'instance-modal': InstanceModalVue,
+    'refresh-selector': RefreshSelectorVue
+  }
+})
+export default class JhiInstance extends Mixins(RefreshSelectorMixin) {
+  public instances?: Array<Instance> = null;
+  public instancesRoute: Array<any> = null;
+  public selectedInstance: Instance = null;
+  public selectedInstanceRoute: string = null;
+  public instanceModal: any = null;
+  private unsubscribe$ = new Subject();
+  @Inject('instanceService') private instanceService: () => InstanceService;
+
+  public mounted(): void {
+    this.refreshService()
+      .refreshReload$.pipe(takeUntil(this.unsubscribe$))
+      .subscribe(() => this.refreshInstancesData());
+    this.refreshInstancesData();
+    this.refreshInstancesRoute();
+  }
+
+  /** Update instancesData which contains list of applications instances */
+  public refreshInstancesData(): void {
+    this.instanceService()
+      .findAll()
+      .pipe(takeUntil(this.unsubscribe$))
+      .subscribe(instances => {
+        this.instances = instances;
+      });
+  }
+
+  /** Update instancesRoute which contains list of routes */
+  public refreshInstancesRoute(): void {
+    this.instanceService()
+      .findAllGatewayRoute()
+      .then(res => {
+        this.instancesRoute = res.data;
+      })
+      .catch(error => {
+        this.instancesRoute = error.error;
+      });
+  }
+
+  /** Display details about an instance in a b-modal */
+  public showInstance(instance: Instance, uri: string): void {
+    this.selectedInstance = instance;
+    for (let i = 0; i < this.instancesRoute.length; i++) {
+      if (this.instancesRoute[i].uri === uri) {
+        this.selectedInstanceRoute = this.instancesRoute[i].route_id;
+        break;
+      }
+    }
+    this.instanceModal = <any>this.$refs.instanceModal;
+    this.instanceModal.show();
+  }
+
+  /* istanbul ignore next */
+  beforeDestroy(): any {
+    // prevent memory leak when component destroyed
+    this.unsubscribe$.next();
+    this.unsubscribe$.complete();
+  }
+}
diff --git a/src/main/webapp/app/applications/instance/instance.service.ts b/src/main/webapp/app/applications/instance/instance.service.ts
new file mode 100644
index 0000000..1e837b1
--- /dev/null
+++ b/src/main/webapp/app/applications/instance/instance.service.ts
@@ -0,0 +1,74 @@
+import axios, { AxiosPromise } from 'axios';
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import { Observable } from 'rxjs';
+
+export interface Instance {
+  serviceId: string;
+  instanceId: string;
+  uri: string;
+  host: string;
+  port: number;
+  secure: boolean;
+  metadata: Metadata;
+}
+
+export type Metadata = { [key in MetadataKey]?: any };
+
+/** TODO check key from json result to add or modify */
+export type MetadataKey = 'profile' | 'version' | 'display-ribbon-on-profiles';
+
+@Component
+export default class InstanceService extends Vue {
+  /** get all instances */
+  public findAll(): Observable<Array<Instance>> {
+    return Observable.create(observer => {
+      axios
+        .get('api/services/instances')
+        .then(res => {
+          // generate instance list from http request
+          const applications = this.parseJsonArrayInstancesToArray(res.data);
+          observer.next(applications);
+          observer.complete();
+        })
+        .catch(error => {
+          observer.error(error);
+        });
+    });
+  }
+
+  /** get all gateway routes of instances */
+  public findAllGatewayRoute(): AxiosPromise<any> {
+    return axios.get('management/gateway/routes');
+  }
+
+  /** get active profiles of an instance */
+  public findActiveProfiles(gatewayRoute: string): AxiosPromise<any> {
+    return axios.get('gateway/' + String(gatewayRoute) + '/management/info');
+  }
+
+  /** convert json array of instances to array of instances */
+  parseJsonArrayInstancesToArray(data: any): Array<Instance> {
+    const instances: Array<Instance> = [];
+    data.map(instance => {
+      const inst = new class implements Instance {
+        serviceId: string;
+        instanceId: string;
+        uri: string;
+        host: string;
+        port: number;
+        secure: boolean;
+        metadata: Metadata;
+      }();
+      inst.serviceId = instance.serviceId;
+      inst.instanceId = instance.instanceId;
+      inst.uri = instance.uri;
+      inst.host = instance.host;
+      inst.port = instance.port;
+      inst.secure = instance.secure;
+      inst.metadata = instance.metadata;
+      instances.push(inst);
+    });
+    return instances;
+  }
+}
diff --git a/src/main/webapp/app/applications/applications.vue b/src/main/webapp/app/applications/instance/instance.vue
similarity index 52%
rename from src/main/webapp/app/applications/applications.vue
rename to src/main/webapp/app/applications/instance/instance.vue
index 631e204..2e79d1d 100644
--- a/src/main/webapp/app/applications/applications.vue
+++ b/src/main/webapp/app/applications/instance/instance.vue
@@ -1,8 +1,9 @@
 <template>
     <div>
-        <h2>
-            <span id="applications-page-heading">Applications Instances</span>
-        </h2>
+        <div class="head">
+            <h2 class="d-inline-block">Application Instances</h2>
+            <refresh-selector class="float-right refresh-left-side">button</refresh-selector>
+        </div>
         <div class="table-responsive">
             <table class="table table-striped table-bordered table-responsive d-table">
                 <thead>
@@ -13,15 +14,15 @@
                 </tr>
                 </thead>
                 <tbody>
-                <tr v-for="content in applications" :key="content.instance.instanceId">
+                <tr v-for="instance in instances" :key="instance.instanceId">
                     <td class="table-hover">
-                        <a :href="content.instance.uri" target="_blank">{{ content.instance.serviceId }}</a>
+                        <a :href="instance.uri" target="_blank">{{ instance.serviceId }}</a>
                     </td>
                     <td class="table-hover">
-                        <span class="badge badge-pill badge-light">{{ content.instance.instanceId }}</span>
+                        <span class="badge badge-pill badge-light">{{ instance.instanceId }}</span>
                     </td>
                     <td class="text-center">
-                        <a id="showDetail" class="hand" v-on:click="showApplication(content, content.instance.uri)" v-if="content">
+                        <a id="showDetail" class="hand" v-on:click="showInstance(instance, instance.uri)" v-if="instance">
                             <font-awesome-icon icon="eye"></font-awesome-icon>
                         </a>
                     </td>
@@ -29,15 +30,14 @@
                 </tbody>
             </table>
         </div>
-        <b-modal ref="applicationModal">
-            <h4 slot="modal-title" v-if="currentApplication && currentRoute" class="modal-title" id="showApplicationLabel">
-                <span class="text-capitalize">{{ currentApplication.serviceId }}</span>
+        <b-modal ref="instanceModal">
+            <h4 slot="modal-title" v-if="selectedInstance && selectedInstanceRoute" class="modal-title" id="showInstanceLabel">
+                <span class="text-capitalize">{{ selectedInstance.serviceId }}</span>
             </h4>
-            <applications-modal :current-application="currentApplication" :current-route="currentRoute"></applications-modal>
+            <instance-modal :selected-instance="selectedInstance" :selected-instance-route="selectedInstanceRoute"></instance-modal>
         </b-modal>
     </div>
 </template>
 
-<script lang="ts" src="./applications.component.ts">
+<script lang="ts" src="./instance.component.ts">
 </script>
-
diff --git a/src/main/webapp/app/core/jhi-navbar/jhi-navbar.vue b/src/main/webapp/app/core/jhi-navbar/jhi-navbar.vue
index e1f4fe0..946c806 100644
--- a/src/main/webapp/app/core/jhi-navbar/jhi-navbar.vue
+++ b/src/main/webapp/app/core/jhi-navbar/jhi-navbar.vue
@@ -68,9 +68,9 @@
                         <font-awesome-icon icon="cloud" />
                         <span>Applications</span>
                     </span>
-                    <b-dropdown-item to="/applications/jhi-applications">
+                    <b-dropdown-item to="/applications/instances">
                         <font-awesome-icon icon="th-list" />
-                        <span>Services</span>
+                        <span>Instances</span>
                     </b-dropdown-item>
                 </b-nav-item-dropdown>
                 <b-nav-item-dropdown
diff --git a/src/main/webapp/app/main.ts b/src/main/webapp/app/main.ts
index 0f1e0f5..66302aa 100644
--- a/src/main/webapp/app/main.ts
+++ b/src/main/webapp/app/main.ts
@@ -21,7 +21,8 @@ import AccountService from './account/account.service';
 import '../content/scss/vendor.scss';
 import AlertService from '@/shared/alert/alert.service';
 import ConfigurationService from '@/admin/configuration/configuration.service';
-import ApplicationsService from '@/applications/applications.service';
+import { RefreshService } from '@/shared/refresh/refresh.service';
+import InstanceService from '@/applications/instance/instance.service';
 
 /* tslint:disable */
 
@@ -43,6 +44,7 @@ const store = config.initVueXStore(Vue);
 const alertService = new AlertService(store);
 const loginService = new LoginService();
 const accountService = new AccountService(store, router);
+const refreshService = new RefreshService(store);
 
 router.beforeEach((to, from, next) => {
   if (!to.matched.length) {
@@ -80,7 +82,8 @@ new Vue({
     // jhipster-needle-add-entity-service-to-main - JHipster will import entities services here
     accountService: () => accountService,
 
-    applicationsService: () => new ApplicationsService()
+    instanceService: () => new InstanceService(),
+    refreshService: () => refreshService
   },
   store
 });
diff --git a/src/main/webapp/app/router/index.ts b/src/main/webapp/app/router/index.ts
index 99b7b74..cb4e03e 100644
--- a/src/main/webapp/app/router/index.ts
+++ b/src/main/webapp/app/router/index.ts
@@ -14,7 +14,7 @@ const JhiDocsComponent = () => import('../admin/docs/docs.vue');
 const JhiHealthComponent = () => import('../admin/health/health.vue');
 const JhiLogsComponent = () => import('../admin/logs/logs.vue');
 const JhiMetricsComponent = () => import('../admin/metrics/metrics.vue');
-const JhiApplicationsComponent = () => import('../applications/applications.vue');
+const InstanceComponent = () => import('../applications/instance/instance.vue');
 
 /* tslint:disable */
 // jhipster-needle-add-entity-to-router-import - JHipster will import entities to the router here
@@ -74,9 +74,9 @@ export default new Router({
       meta: { authorities: [Authority.ADMIN] }
     },
     {
-      path: '/applications/jhi-applications',
-      name: 'JhiApplicationsComponent',
-      component: JhiApplicationsComponent,
+      path: '/applications/instances',
+      name: 'InstanceComponent',
+      component: InstanceComponent,
       meta: { authorities: [Authority.ADMIN] }
     }
     // jhipster-needle-add-entity-to-router - JHipster will add entities to the router here
diff --git a/src/main/webapp/app/shared/config/config.ts b/src/main/webapp/app/shared/config/config.ts
index ec54994..7ba6108 100644
--- a/src/main/webapp/app/shared/config/config.ts
+++ b/src/main/webapp/app/shared/config/config.ts
@@ -44,6 +44,7 @@ import Vue2Filters from 'vue2-filters';
 import * as filters from '@/shared/date/filters';
 import { accountStore } from '@/shared/config/store/account-store';
 import { alertStore } from '@/shared/config/store/alert-store';
+import { refreshStore } from '@/shared/config/store/refresh-store';
 
 export function initVueApp(vue) {
   vue.use(VueCookie);
@@ -97,7 +98,8 @@ export function initVueXStore(vue) {
   return new Vuex.Store({
     modules: {
       accountStore,
-      alertStore
+      alertStore,
+      refreshStore
     }
   });
 }
diff --git a/src/main/webapp/app/shared/config/store/refresh-store.ts b/src/main/webapp/app/shared/config/store/refresh-store.ts
new file mode 100644
index 0000000..9351a73
--- /dev/null
+++ b/src/main/webapp/app/shared/config/store/refresh-store.ts
@@ -0,0 +1,15 @@
+import { Module } from 'vuex';
+
+export const refreshStore: Module<any, any> = {
+  state: {
+    refreshTime: 0
+  },
+  getters: {
+    refreshTime: state => state.refreshTime
+  },
+  mutations: {
+    setRefreshTime(state, refreshTime) {
+      state.refreshTime = refreshTime;
+    }
+  }
+};
diff --git a/src/main/webapp/app/shared/refresh/refresh-selector.mixin.scss b/src/main/webapp/app/shared/refresh/refresh-selector.mixin.scss
new file mode 100644
index 0000000..da72be7
--- /dev/null
+++ b/src/main/webapp/app/shared/refresh/refresh-selector.mixin.scss
@@ -0,0 +1,40 @@
+/* ==========================================================================
+Refresh selector styles
+========================================================================== */
+
+.dropdown {
+  .times {
+    font-weight: normal;
+
+    .times-text {
+      font-family: sans-serif;
+    }
+  }
+
+  .dropdown-menu {
+    border-color: darkgrey;
+
+    .dropdown-item {
+      /* refresh times */
+      word-break: break-all;
+
+      &:hover {
+        color: #fff;
+        background-color: #80bed8;
+      }
+
+      &:active {
+        color: #fff;
+        background-color: #0275d8;
+      }
+
+      &.manual-refresh {
+        display: initial;
+      }
+    }
+
+    .dropdown-divider {
+      background-color: #dcdcdc;
+    }
+  }
+}
diff --git a/src/main/webapp/app/shared/refresh/refresh-selector.mixin.ts b/src/main/webapp/app/shared/refresh/refresh-selector.mixin.ts
new file mode 100644
index 0000000..b9a32df
--- /dev/null
+++ b/src/main/webapp/app/shared/refresh/refresh-selector.mixin.ts
@@ -0,0 +1,90 @@
+import { Component, Inject, Vue } from 'vue-property-decorator';
+import { RefreshService } from '@/shared/refresh/refresh.service';
+import { Subject, Subscription } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';
+import { interval } from 'rxjs/observable/interval';
+
+@Component
+export default class RefreshSelectorMixin extends Vue {
+  @Inject('refreshService') protected refreshService: () => RefreshService;
+
+  // htmlActiveRefreshTime allow us to update dynamically refresh button content html
+  htmlActiveRefreshTime = '';
+  activeRefreshTime: number;
+  refreshTimes: number[] = [0, 5, 10, 30, 60, 300];
+  refreshTimer?: Subscription;
+  unsubscribeFromRefreshMixin$ = new Subject();
+
+  public mounted(): void {
+    this.activeRefreshTime = this.refreshService().getSelectedRefreshTime();
+    this.htmlActiveRefreshTime = this.getActiveRefreshTime();
+    this.refreshService()
+      .refreshChanged$.pipe(takeUntil(this.unsubscribeFromRefreshMixin$))
+      .subscribe(() => this.launchTimer(true));
+    this.launchTimer(false);
+  }
+
+  manualRefresh(): void {
+    this.refreshService().refreshReload();
+  }
+
+  /** Change active time only if exists, else 0 **/
+  setActiveRefreshTime(time: number): void {
+    if (time && this.refreshTimes.findIndex(t => t === time) !== -1) {
+      this.activeRefreshTime = time;
+    } else {
+      this.activeRefreshTime = this.refreshTimes[0];
+    }
+    this.refreshService().storeSelectedRefreshTime(time);
+    this.refreshService().refreshChanged();
+  }
+
+  /** Init the timer **/
+  subscribe(): void {
+    if (this.activeRefreshTime && this.activeRefreshTime > 0) {
+      this.refreshTimer = interval(this.activeRefreshTime * 1000)
+        .pipe(takeUntil(this.unsubscribeFromRefreshMixin$))
+        .subscribe(() => {
+          this.refreshService().refreshReload();
+        });
+    }
+  }
+
+  /** Launch (or relaunch if true) the timer. **/
+  launchTimer(relaunch: boolean): void {
+    if (relaunch && this.refreshTimer) {
+      this.refreshTimer.unsubscribe();
+    }
+    this.subscribe();
+  }
+
+  /* istanbul ignore next */
+  beforeDestroy(): any {
+    // prevent memory leak when component destroyed
+    this.unsubscribeFromRefreshMixin$.next();
+    this.unsubscribeFromRefreshMixin$.complete();
+  }
+
+  /* ==========================================================================
+                                    UI PART
+ ========================================================================== */
+  classTime(): string {
+    if (this.activeRefreshTime <= 0) {
+      return 'fa fa-pause';
+    }
+    return 'fa fa-repeat';
+  }
+
+  stateTime(time: number): string | void {
+    if (time === this.activeRefreshTime) {
+      return 'active';
+    }
+  }
+
+  getActiveRefreshTime(): string {
+    if (this.activeRefreshTime <= 0) {
+      return 'disabled';
+    }
+    return this.activeRefreshTime + ' sec.';
+  }
+}
diff --git a/src/main/webapp/app/shared/refresh/refresh-selector.mixin.vue b/src/main/webapp/app/shared/refresh/refresh-selector.mixin.vue
new file mode 100644
index 0000000..c085010
--- /dev/null
+++ b/src/main/webapp/app/shared/refresh/refresh-selector.mixin.vue
@@ -0,0 +1,22 @@
+<template>
+    <div class="dropdown">
+        <button class="btn btn-outline-primary" v-on:click="manualRefresh()">
+            <span class="times-text">Refresh now <i class="fa fa-refresh"></i></span>
+        </button>
+        <b-dropdown ref="dd" variant="outline-primary" right>
+            <span slot="button-content"><i v-bind:class="classTime()" ></i>{{ htmlActiveRefreshTime }} &nbsp;</span>
+            <span class="dropdown-header">Refresh times (in seconds)</span>
+            <div v-for="time in refreshTimes" :key="time">
+                <button class="dropdown-item"
+                                 v-on:click="[setActiveRefreshTime(time),htmlActiveRefreshTime = getActiveRefreshTime(),$refs.dd.hide()]"
+                                 v-bind:class="stateTime(time)">
+                    <span v-if="time > 0">{{ time }}</span>
+                    <span v-else>disabled</span>
+                </button>
+            </div>
+        </b-dropdown>
+    </div>
+</template>
+
+<script lang="ts" src="./refresh-selector.mixin.ts"></script>
+<style lang="scss" scoped src="./refresh-selector.mixin.scss"></style>
diff --git a/src/main/webapp/app/shared/refresh/refresh.service.ts b/src/main/webapp/app/shared/refresh/refresh.service.ts
new file mode 100644
index 0000000..0a72804
--- /dev/null
+++ b/src/main/webapp/app/shared/refresh/refresh.service.ts
@@ -0,0 +1,36 @@
+import { Store } from 'vuex';
+import { Subject, Observable } from 'rxjs';
+
+export class RefreshService {
+  private store: Store<{}>;
+
+  // Observable sources
+  private refreshChangedSource = new Subject<any>();
+  private refreshReloadSource = new Subject<any>();
+  refreshChanged$: Observable<any>;
+  refreshReload$: Observable<any>;
+
+  constructor(store: Store<{}>) {
+    this.store = store;
+    this.refreshChanged$ = this.refreshChangedSource.asObservable();
+    this.refreshReload$ = this.refreshReloadSource.asObservable();
+  }
+
+  /* istanbul ignore next */
+  refreshChanged(): void {
+    this.refreshChangedSource.next();
+  }
+
+  /* istanbul ignore next */
+  refreshReload(): void {
+    this.refreshReloadSource.next();
+  }
+
+  getSelectedRefreshTime(): number {
+    return this.store.getters.refreshTime;
+  }
+
+  storeSelectedRefreshTime(time: number): void {
+    this.store.commit('setRefreshTime', time);
+  }
+}


Test patch:
diff --git a/src/test/javascript/spec/app/applications/applications-modal.component.spec.ts b/src/test/javascript/spec/app/applications/applications-modal.component.spec.ts
deleted file mode 100644
index b9671b2..0000000
--- a/src/test/javascript/spec/app/applications/applications-modal.component.spec.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { createLocalVue, Wrapper, shallowMount } from '@vue/test-utils';
-import * as config from '@/shared/config/config';
-import ApplicationsModal from '@/applications/applications-modal.vue';
-import ApplicationsModalClass from '@/applications/applications-modal.component';
-import axios from 'axios';
-import { Application } from '@/applications/applications.service';
-
-const localVue = createLocalVue();
-config.initVueApp(localVue);
-localVue.component('font-awesome-icon', {});
-const mockedApplicationsService = { findActiveProfiles: jest.spyOn(axios, 'get') };
-const app = {
-  serviceId: 'app1',
-  instance: {
-    instanceId: 'app1-id',
-    serviceId: 'app1',
-    port: 8080,
-    secure: false,
-    metadata: { someData: 'test' },
-    uri: 'http://127.0.0.01:8080'
-  }
-};
-
-describe('Applications Modal Component', () => {
-  let wrapper: Wrapper<ApplicationsModalClass>;
-  let applicationsModal: ApplicationsModalClass;
-
-  beforeEach(async () => {
-    wrapper = shallowMount<ApplicationsModalClass>(ApplicationsModal, {
-      propsData: {
-        currentApplication: app,
-        currentRoute: {}
-      },
-      localVue,
-      provide: {
-        applicationsService: () => mockedApplicationsService
-      }
-    });
-    applicationsModal = wrapper.vm;
-  });
-
-  it('should be a Vue instance', () => {
-    expect(wrapper.isVueInstance()).toBeTruthy();
-  });
-
-  describe('active profiles', () => {
-    it('should refresh profile', async () => {
-      // GIVEN
-      mockedApplicationsService.findActiveProfiles.mockReturnValue(Promise.resolve({}));
-      const route = mockedApplicationsService.findActiveProfiles.mock.calls[0][0];
-
-      // WHEN
-      applicationsModal.refreshProfile();
-      await applicationsModal.$nextTick();
-
-      // THEN
-      expect(mockedApplicationsService.findActiveProfiles).toHaveBeenCalledWith(route);
-    });
-
-    it('should get an error when refresh profile', async () => {
-      // GIVEN
-      mockedApplicationsService.findActiveProfiles.mockReturnValue(Promise.reject({}));
-      const route = mockedApplicationsService.findActiveProfiles.mock.calls[0][0];
-
-      // WHEN
-      applicationsModal.refreshProfile();
-      await applicationsModal.$nextTick();
-
-      // THEN
-      expect(mockedApplicationsService.findActiveProfiles).toHaveBeenCalledWith(route);
-    });
-  });
-});
diff --git a/src/test/javascript/spec/app/applications/applications.component.spec.ts b/src/test/javascript/spec/app/applications/applications.component.spec.ts
deleted file mode 100644
index a8ef1ad..0000000
--- a/src/test/javascript/spec/app/applications/applications.component.spec.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import { shallowMount, createLocalVue, Wrapper, mount } from '@vue/test-utils';
-import axios from 'axios';
-import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
-import * as config from '@/shared/config/config';
-import Applications from '@/applications/applications.vue';
-import ApplicationsModal from '@/applications/applications-modal.vue';
-import ApplicationsClass from '@/applications/applications.component';
-import ApplicationsService from '@/applications/applications.service';
-
-const localVue = createLocalVue();
-const mockedAxios: any = axios;
-
-config.initVueApp(localVue);
-localVue.component('font-awesome-icon', FontAwesomeIcon);
-localVue.component('applications-modal', ApplicationsModal);
-localVue.directive('b-modal', {});
-
-jest.mock('axios', () => ({
-  get: jest.fn()
-}));
-
-const stubbedModal = {
-  template: '<div></div>',
-  methods: {
-    show() {}
-  }
-};
-
-describe('Applications Component', () => {
-  let wrapper: Wrapper<ApplicationsClass>;
-  let applications: ApplicationsClass;
-
-  beforeEach(() => {
-    mockedAxios.get.mockReturnValue(Promise.resolve({}));
-    wrapper = mount<ApplicationsClass>(Applications, {
-      localVue,
-      stubs: {
-        bModal: stubbedModal
-      },
-      provide: {
-        applicationsService: () => new ApplicationsService()
-      }
-    });
-    applications = wrapper.vm;
-  });
-
-  it('should be a Vue instance', () => {
-    expect(wrapper.isVueInstance()).toBeTruthy();
-  });
-
-  describe('should refresh list of applications data', () => {
-    it('should call refreshApplicationsData on init', async () => {
-      // GIVEN
-      mockedAxios.get.mockReturnValue(Promise.resolve({}));
-
-      // WHEN
-      applications.refreshApplicationsData();
-      await applications.$nextTick();
-
-      // THEN
-      expect(mockedAxios.get).toHaveBeenCalledWith('api/services/instances');
-    });
-    it('should handle error on refreshing applications data', async () => {
-      // GIVEN
-      mockedAxios.get.mockReturnValue(Promise.reject({}));
-
-      // WHEN
-      applications.refreshApplicationsData();
-      await applications.$nextTick();
-
-      // THEN
-      expect(mockedAxios.get).toHaveBeenCalledWith('api/services/instances');
-    });
-  });
-
-  describe('should refresh list of applications route data', () => {
-    it('should call refreshApplicationsRoute on init', async () => {
-      // GIVEN
-      mockedAxios.get.mockReturnValue(Promise.resolve({}));
-
-      // WHEN
-      applications.refreshApplicationsRoute();
-      await applications.$nextTick();
-
-      // THEN
-      expect(mockedAxios.get).toHaveBeenCalledWith('management/gateway/routes');
-    });
-    it('should handle error on refreshing applications route data', async () => {
-      // GIVEN
-      mockedAxios.get.mockReturnValue(Promise.reject({}));
-
-      // WHEN
-      applications.refreshApplicationsRoute();
-      await applications.$nextTick();
-
-      // THEN
-      expect(mockedAxios.get).toHaveBeenCalledWith('management/gateway/routes');
-    });
-  });
-
-  // TODO fix test for method showApplication
-  describe('should call showApplication', () => {
-    it('should show selected application', async () => {
-      // GIVEN
-      const spy = jest.spyOn(applications, 'showApplication');
-      const app = {
-        app1: [
-          {
-            instanceId: 'app1-id',
-            serviceId: 'app1',
-            host: '127.0.0.1',
-            port: 8080,
-            secure: false,
-            metadata: { someData: 'test' },
-            uri: 'http://127.0.0.01:8080',
-            scheme: null
-          }
-        ]
-      };
-      const uri = 'http://127.0.0.01:8080';
-      applications.applicationsRoute = [
-        { uri: 'http://127.0.0.01:8081', route_id: 'test2/test2-id' },
-        { uri: 'http://127.0.0.01:8080', route_id: 'test/test-id' }
-      ];
-
-      // WHEN
-      applications.showApplication(app, uri);
-      await applications.$nextTick();
-      // THEN
-      expect(spy).toHaveBeenCalled();
-      spy.mockRestore();
-    });
-  });
-});
diff --git a/src/test/javascript/spec/app/applications/instance/instance-modal.component.spec.ts b/src/test/javascript/spec/app/applications/instance/instance-modal.component.spec.ts
new file mode 100644
index 0000000..3f22d94
--- /dev/null
+++ b/src/test/javascript/spec/app/applications/instance/instance-modal.component.spec.ts
@@ -0,0 +1,70 @@
+import { createLocalVue, Wrapper, shallowMount } from '@vue/test-utils';
+import * as config from '@/shared/config/config';
+import axios from 'axios';
+import InstanceModalClass from '@/applications/instance/instance-modal.component';
+import InstanceModal from '@/applications/instance/instance-modal.vue';
+
+const localVue = createLocalVue();
+config.initVueApp(localVue);
+localVue.component('font-awesome-icon', {});
+const mockedInstanceService = { findActiveProfiles: jest.spyOn(axios, 'get') };
+const app = {
+  serviceId: 'app1',
+  instanceId: 'app1-id',
+  uri: 'http://127.0.0.1:8080',
+  host: '127.0.0.1',
+  port: 8080,
+  secure: false,
+  metadata: {}
+};
+
+describe('Instance Modal Component', () => {
+  let wrapper: Wrapper<InstanceModalClass>;
+  let instanceModal: InstanceModalClass;
+
+  beforeEach(async () => {
+    wrapper = shallowMount<InstanceModalClass>(InstanceModal, {
+      propsData: {
+        selectedInstance: app,
+        selectedInstanceRoute: {}
+      },
+      localVue,
+      provide: {
+        instanceService: () => mockedInstanceService
+      }
+    });
+    instanceModal = wrapper.vm;
+  });
+
+  it('should be a Vue instance', () => {
+    expect(wrapper.isVueInstance()).toBeTruthy();
+  });
+
+  describe('active profiles', () => {
+    it('should refresh profile', async () => {
+      // GIVEN
+      mockedInstanceService.findActiveProfiles.mockReturnValue(Promise.resolve({}));
+      const route = mockedInstanceService.findActiveProfiles.mock.calls[0][0];
+
+      // WHEN
+      instanceModal.refreshProfile();
+      await instanceModal.$nextTick();
+
+      // THEN
+      expect(mockedInstanceService.findActiveProfiles).toHaveBeenCalledWith(route);
+    });
+
+    it('should get an error when refresh profile', async () => {
+      // GIVEN
+      mockedInstanceService.findActiveProfiles.mockReturnValue(Promise.reject({}));
+      const route = mockedInstanceService.findActiveProfiles.mock.calls[0][0];
+
+      // WHEN
+      instanceModal.refreshProfile();
+      await instanceModal.$nextTick();
+
+      // THEN
+      expect(mockedInstanceService.findActiveProfiles).toHaveBeenCalledWith(route);
+    });
+  });
+});
diff --git a/src/test/javascript/spec/app/applications/instance/instance.component.spec.ts b/src/test/javascript/spec/app/applications/instance/instance.component.spec.ts
new file mode 100644
index 0000000..f51ac73
--- /dev/null
+++ b/src/test/javascript/spec/app/applications/instance/instance.component.spec.ts
@@ -0,0 +1,146 @@
+import { createLocalVue, Wrapper, mount } from '@vue/test-utils';
+import axios from 'axios';
+import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
+import * as config from '@/shared/config/config';
+import InstanceVue from '@/applications/instance/instance.vue';
+import InstanceModal from '@/applications/instance/instance-modal.vue';
+import InstanceClass from '@/applications/instance/instance.component';
+import InstanceService, { Instance, Metadata } from '@/applications/instance/instance.service';
+import { RefreshService } from '@/shared/refresh/refresh.service';
+import { BootstrapVue } from 'bootstrap-vue';
+
+const localVue = createLocalVue();
+const mockedAxios: any = axios;
+
+config.initVueApp(localVue);
+localVue.component('font-awesome-icon', FontAwesomeIcon);
+localVue.component('instance-modal', InstanceModal);
+localVue.directive('b-modal', {});
+localVue.use(BootstrapVue);
+
+const store = config.initVueXStore(localVue);
+
+jest.mock('axios', () => ({
+  get: jest.fn()
+}));
+
+const stubbedModal = {
+  template: '<div></div>',
+  methods: {
+    show() {}
+  }
+};
+
+const RefreshSelectorMixin = {
+  inject: ['refreshService']
+};
+
+describe('Instance Component', () => {
+  let wrapper: Wrapper<InstanceClass>;
+  let instance: InstanceClass;
+
+  beforeEach(() => {
+    mockedAxios.get.mockReturnValue(Promise.resolve({}));
+    wrapper = mount<InstanceClass>(InstanceVue, {
+      localVue,
+      mixins: [RefreshSelectorMixin],
+      stubs: {
+        bModal: stubbedModal
+      },
+      provide: {
+        instanceService: () => new InstanceService(),
+        refreshService: () => new RefreshService(store)
+      }
+    });
+    instance = wrapper.vm;
+  });
+
+  it('should be a Vue instance', () => {
+    expect(wrapper.isVueInstance()).toBeTruthy();
+  });
+
+  describe('should refresh list of instance data', () => {
+    it('should call refreshInstancesData on init', async () => {
+      // GIVEN
+      mockedAxios.get.mockReturnValue(Promise.resolve({}));
+
+      // WHEN
+      instance.refreshInstancesData();
+      await instance.$nextTick();
+
+      // THEN
+      expect(mockedAxios.get).toHaveBeenCalledWith('api/services/instances');
+    });
+    it('should handle error on refreshing instance data', async () => {
+      // GIVEN
+      mockedAxios.get.mockReturnValue(Promise.reject({}));
+
+      // WHEN
+      instance.refreshInstancesData();
+      await instance.$nextTick();
+
+      // THEN
+      expect(mockedAxios.get).toHaveBeenCalledWith('api/services/instances');
+    });
+  });
+
+  describe('should refresh list of instance route data', () => {
+    it('should call refreshInstancesRoute on init', async () => {
+      // GIVEN
+      mockedAxios.get.mockReturnValue(Promise.resolve({}));
+
+      // WHEN
+      instance.refreshInstancesRoute();
+      await instance.$nextTick();
+
+      // THEN
+      expect(mockedAxios.get).toHaveBeenCalledWith('management/gateway/routes');
+    });
+    it('should handle error on refreshing instance route data', async () => {
+      // GIVEN
+      mockedAxios.get.mockReturnValue(Promise.reject({}));
+
+      // WHEN
+      instance.refreshInstancesRoute();
+      await instance.$nextTick();
+
+      // THEN
+      expect(mockedAxios.get).toHaveBeenCalledWith('management/gateway/routes');
+    });
+  });
+
+  describe('should call showInstance', () => {
+    it('should show selected instance', async () => {
+      // GIVEN
+      const spy = jest.spyOn(instance, 'showInstance');
+      const inst: Instance = new class implements Instance {
+        serviceId: string;
+        instanceId: string;
+        uri: string;
+        host: string;
+        port: number;
+        secure: boolean;
+        metadata: Metadata;
+      }();
+      inst.serviceId = 'app1';
+      inst.instanceId = 'app1-id';
+      inst.uri = 'http://127.0.0.01:8080';
+      inst.host = '127.0.0.1';
+      inst.port = 8080;
+      inst.secure = false;
+      inst.metadata = {};
+      const uri = 'http://127.0.0.01:8080';
+      instance.instancesRoute = [
+        { uri: 'http://127.0.0.01:8081', route_id: 'test2/test2-id' },
+        { uri: 'http://127.0.0.01:8080', route_id: 'test/test-id' }
+      ];
+
+      // WHEN
+      instance.showInstance(inst, uri);
+      await instance.$nextTick();
+      // THEN
+      expect(spy).toHaveBeenCalled();
+      spy.mockRestore();
+    });
+  });
+});
diff --git a/src/test/javascript/spec/app/applications/applications.service.spec.ts b/src/test/javascript/spec/app/applications/instance/instance.service.spec.ts
similarity index 59%
rename from src/test/javascript/spec/app/applications/applications.service.spec.ts
rename to src/test/javascript/spec/app/applications/instance/instance.service.spec.ts
index f8f6f03..edb7d9a 100644
--- a/src/test/javascript/spec/app/applications/applications.service.spec.ts
+++ b/src/test/javascript/spec/app/applications/instance/instance.service.spec.ts
@@ -1,6 +1,6 @@
-import ApplicationsService, { Application } from '@/applications/applications.service';
-import axios from 'axios';
+import InstanceService, { Instance } from '@/applications/instance/instance.service';
 import { Observable } from 'rxjs';
+import axios from 'axios';
 
 const mockedAxios: any = axios;
 jest.mock('axios', () => ({
@@ -10,45 +10,41 @@ jest.mock('axios', () => ({
 const data: Array<any> = [
   {
     serviceId: 'app1',
-    instance: {
-      instanceId: 'app1-id',
-      serviceId: 'app1',
-      port: 8080,
-      secure: false,
-      metadata: { someData: 'test' },
-      uri: 'http://127.0.0.01:8080'
-    }
+    instanceId: 'app1-id',
+    uri: 'http://127.0.0.1:8080',
+    host: '127.0.0.1',
+    port: 8080,
+    secure: false,
+    metadata: {}
   },
   {
     serviceId: 'app2',
-    instance: {
-      instanceId: 'app2-id',
-      serviceId: 'app2',
-      port: 8081,
-      secure: false,
-      metadata: { someData: 'test' },
-      uri: 'http://127.0.0.02:8081'
-    }
+    instanceId: 'app2-id',
+    uri: 'http://127.0.0.1:8081',
+    host: '127.0.0.1',
+    port: 8080,
+    secure: false,
+    metadata: {}
   }
 ];
 
-describe('Applications Service', () => {
-  let applicationsService: ApplicationsService;
+describe('Instance Service', () => {
+  let instanceService: InstanceService;
 
   beforeEach(() => {
-    applicationsService = new ApplicationsService();
+    instanceService = new InstanceService();
     mockedAxios.get.mockReset();
   });
 
-  it('should get list of applications', async () => {
-    const spy = jest.spyOn(applicationsService, 'findAll').mockReturnValue(Observable.of(data));
+  it('should get list of instances', async () => {
+    const spy = jest.spyOn(instanceService, 'findAll').mockReturnValue(Observable.of(data));
 
     expect(
-      applicationsService.findAll().subscribe(res => {
+      instanceService.findAll().subscribe(res => {
         expect(res).toEqual(data);
       })
     );
-    expect(applicationsService.parseJsonArrayApplicationsToArray(data)).toHaveLength(2);
+    expect(instanceService.parseJsonArrayInstancesToArray(data)).toHaveLength(2);
     expect(spy).toHaveBeenCalled();
   });
 
@@ -67,7 +63,7 @@ describe('Applications Service', () => {
     ];
     mockedAxios.get.mockImplementationOnce(() => Promise.resolve(routes));
 
-    await expect(applicationsService.findAllGatewayRoute()).resolves.toEqual(routes);
+    await expect(instanceService.findAllGatewayRoute()).resolves.toEqual(routes);
     expect(mockedAxios.get).toHaveBeenCalledWith('management/gateway/routes');
   });
 
@@ -75,7 +71,7 @@ describe('Applications Service', () => {
     const error = 'an error';
     mockedAxios.get.mockImplementationOnce(() => Promise.reject(error));
 
-    await expect(applicationsService.findAllGatewayRoute()).rejects.toEqual(error);
+    await expect(instanceService.findAllGatewayRoute()).rejects.toEqual(error);
     expect(mockedAxios.get).toHaveBeenCalledWith('management/gateway/routes');
   });
 
@@ -84,7 +80,7 @@ describe('Applications Service', () => {
     const route = 'test/test-id';
     mockedAxios.get.mockImplementationOnce(() => Promise.resolve(profiles));
 
-    await expect(applicationsService.findActiveProfiles(route)).resolves.toEqual(profiles);
+    await expect(instanceService.findActiveProfiles(route)).resolves.toEqual(profiles);
     expect(mockedAxios.get).toHaveBeenCalledWith('gateway/test/test-id/management/info');
   });
 
@@ -93,7 +89,7 @@ describe('Applications Service', () => {
     const route = 'test/test-id';
     mockedAxios.get.mockImplementationOnce(() => Promise.reject(error));
 
-    await expect(applicationsService.findActiveProfiles(route)).rejects.toEqual(error);
+    await expect(instanceService.findActiveProfiles(route)).rejects.toEqual(error);
     expect(mockedAxios.get).toHaveBeenCalledWith('gateway/test/test-id/management/info');
   });
 });
diff --git a/src/test/javascript/spec/app/shared/refresh/refresh-selector.mixin.spec.ts b/src/test/javascript/spec/app/shared/refresh/refresh-selector.mixin.spec.ts
new file mode 100644
index 0000000..372cde1
--- /dev/null
+++ b/src/test/javascript/spec/app/shared/refresh/refresh-selector.mixin.spec.ts
@@ -0,0 +1,105 @@
+import { createLocalVue, shallowMount, Wrapper } from '@vue/test-utils';
+import RefreshSelectorMixinClass from '@/shared/refresh/refresh-selector.mixin.ts';
+import RefreshSelectorMixinVue from '@/shared/refresh/refresh-selector.mixin.vue';
+import { RefreshService } from '@/shared/refresh/refresh.service.ts';
+import * as config from '@/shared/config/config';
+import { BootstrapVue } from 'bootstrap-vue';
+
+const localVue = createLocalVue();
+localVue.use(BootstrapVue);
+config.initVueApp(localVue);
+const store = config.initVueXStore(localVue);
+
+describe('Refresh', () => {
+  let refreshSelectorMixin: RefreshSelectorMixinClass;
+  let wrapper: Wrapper<RefreshSelectorMixinClass>;
+
+  beforeEach(() => {
+    wrapper = shallowMount<RefreshSelectorMixinClass>(RefreshSelectorMixinVue, {
+      store,
+      localVue,
+      provide: {
+        refreshService: () => new RefreshService(store)
+      }
+    });
+    refreshSelectorMixin = wrapper.vm;
+  });
+
+  it('should be a Vue instance', () => {
+    expect(wrapper.isVueInstance()).toBeTruthy();
+  });
+
+  it('should refresh manually refresh subject', async () => {
+    const spy = jest.spyOn(refreshSelectorMixin, 'manualRefresh');
+    refreshSelectorMixin.manualRefresh();
+    await refreshSelectorMixin.$nextTick();
+    expect(spy).toHaveBeenCalled();
+    spy.mockRestore();
+  });
+
+  it('should set active refresh time', async () => {
+    const spy = jest.spyOn(refreshSelectorMixin, 'setActiveRefreshTime');
+    const time = 5;
+    refreshSelectorMixin.setActiveRefreshTime(time);
+    await refreshSelectorMixin.$nextTick();
+    expect(spy).toHaveBeenCalled();
+    expect(refreshSelectorMixin.activeRefreshTime).toBe(time);
+
+    const wrongTime = -1;
+    refreshSelectorMixin.setActiveRefreshTime(wrongTime);
+    await refreshSelectorMixin.$nextTick();
+    expect(spy).toHaveBeenCalled();
+    expect(refreshSelectorMixin.activeRefreshTime).toBe(refreshSelectorMixin.refreshTimes[0]);
+    spy.mockRestore();
+  });
+
+  it('should init the timer', async () => {
+    const spy = jest.spyOn(refreshSelectorMixin, 'subscribe');
+    refreshSelectorMixin.activeRefreshTime = 5;
+    refreshSelectorMixin.subscribe();
+    await refreshSelectorMixin.$nextTick();
+    expect(spy).toHaveBeenCalled();
+    spy.mockRestore();
+  });
+
+  it('should launch (or relaunch if true) the timer', async () => {
+    const spy = jest.spyOn(refreshSelectorMixin, 'launchTimer');
+    refreshSelectorMixin.activeRefreshTime = 5;
+    refreshSelectorMixin.subscribe();
+    await refreshSelectorMixin.$nextTick();
+    const relaunch = true;
+    refreshSelectorMixin.launchTimer(relaunch);
+    await refreshSelectorMixin.$nextTick();
+    expect(spy).toHaveBeenCalled();
+    spy.mockRestore();
+  });
+
+  it('classTime should return the correct class for refresh button', () => {
+    refreshSelectorMixin.activeRefreshTime = 5;
+    expect(refreshSelectorMixin.classTime()).toBe('fa fa-repeat');
+
+    refreshSelectorMixin.activeRefreshTime = 0;
+    expect(refreshSelectorMixin.classTime()).toBe('fa fa-pause');
+
+    refreshSelectorMixin.activeRefreshTime = -1;
+    expect(refreshSelectorMixin.classTime()).toBe('fa fa-pause');
+  });
+
+  it('should set state of time', () => {
+    const time = 5;
+    refreshSelectorMixin.activeRefreshTime = time;
+    expect(refreshSelectorMixin.stateTime(time)).toBe('active');
+  });
+
+  it('should get active refresh time', () => {
+    const time = 5;
+    refreshSelectorMixin.activeRefreshTime = time;
+    expect(refreshSelectorMixin.getActiveRefreshTime()).toBe(String(time) + ' sec.');
+
+    refreshSelectorMixin.activeRefreshTime = 0;
+    expect(refreshSelectorMixin.getActiveRefreshTime()).toBe('disabled');
+
+    refreshSelectorMixin.activeRefreshTime = -1;
+    expect(refreshSelectorMixin.getActiveRefreshTime()).toBe('disabled');
+  });
+});
diff --git a/src/test/javascript/spec/app/shared/refresh/refresh.service.spec.ts b/src/test/javascript/spec/app/shared/refresh/refresh.service.spec.ts
new file mode 100644
index 0000000..01cd7af
--- /dev/null
+++ b/src/test/javascript/spec/app/shared/refresh/refresh.service.spec.ts
@@ -0,0 +1,31 @@
+import { createLocalVue } from '@vue/test-utils';
+import * as config from '@/shared/config/config';
+import { RefreshService } from '@/shared/refresh/refresh.service';
+
+const localVue = createLocalVue();
+const store = config.initVueXStore(localVue);
+
+describe('Refresh service', () => {
+  let refreshService: RefreshService;
+
+  it('should init service', () => {
+    refreshService = new RefreshService(store);
+
+    expect(store.getters.refreshTime).toBe(0);
+  });
+
+  it('should get selected refresh time from store', () => {
+    refreshService = new RefreshService(store);
+    const time = refreshService.getSelectedRefreshTime();
+
+    expect(time).toBe(0);
+  });
+
+  it('should set refresh time in the store', () => {
+    refreshService = new RefreshService(store);
+    const time = 5;
+    refreshService.storeSelectedRefreshTime(time);
+
+    expect(store.getters.refreshTime).toBe(5);
+  });
+});