diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7da2915a45e387114ef8254b9cf66fcc1099e68c
--- /dev/null
+++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue
@@ -0,0 +1,53 @@
+<script>
+  import eventHub from '../eventhub';
+
+  export default {
+    data() {
+      return {
+        isLoading: false,
+      };
+    },
+    props: {
+      deployKey: {
+        type: Object,
+        required: true,
+      },
+      type: {
+        type: String,
+        required: true,
+      },
+      btnCssClass: {
+        type: String,
+        required: false,
+        default: 'btn-default',
+      },
+    },
+    methods: {
+      doAction() {
+        this.isLoading = true;
+
+        eventHub.$emit(`${this.type}.key`, this.deployKey);
+      },
+    },
+    computed: {
+      text() {
+        return `${this.type.charAt(0).toUpperCase()}${this.type.slice(1)}`;
+      },
+    },
+  };
+</script>
+
+<template>
+  <button
+    class="btn btn-sm prepend-left-10"
+    :class="[{ disabled: isLoading }, btnCssClass]"
+    :disabled="isLoading"
+    @click="doAction">
+    {{ text }}
+    <i
+      v-if="isLoading"
+      class="fa fa-spinner fa-spin"
+      aria-hidden="true">
+    </i>
+  </button>
+</template>
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ff54a0f241a2f2497c6d6dbd21eef7c4a45ea675
--- /dev/null
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -0,0 +1,105 @@
+<script>
+  /* global Flash */
+  import eventHub from '../eventhub';
+  import DeployKeysService from '../service';
+  import DeployKeysStore from '../store';
+  import keysPanel from './keys.vue';
+
+  export default {
+    data() {
+      const store = new DeployKeysStore();
+
+      return {
+        isLoading: false,
+        store,
+      };
+    },
+    props: {
+      endpoint: {
+        type: String,
+        required: true,
+      },
+    },
+    computed: {
+      hasKeys() {
+        return Object.keys(this.keys).length;
+      },
+      keys() {
+        return this.store.keys;
+      },
+    },
+    components: {
+      keysPanel,
+    },
+    methods: {
+      fetchKeys() {
+        this.isLoading = true;
+        this.store.keys = {};
+
+        this.service.getKeys()
+          .then((data) => {
+            this.isLoading = false;
+            this.store.keys = data;
+          })
+          .catch(() => new Flash('Error getting deploy keys'));
+      },
+      enableKey(deployKey) {
+        this.service.enableKey(deployKey.id)
+          .then(() => this.fetchKeys())
+          .catch(() => new Flash('Error enabling deploy key'));
+      },
+      removeKey(deployKey) {
+        this.disableKey(deployKey);
+      },
+      disableKey(deployKey) {
+        // eslint-disable-next-line no-alert
+        if (confirm('You are going to remove this deploy key. Are you sure?')) {
+          this.service.disableKey(deployKey.id)
+            .then(() => this.fetchKeys())
+            .catch(() => new Flash('Error removing deploy key'));
+        }
+      },
+    },
+    created() {
+      this.service = new DeployKeysService(this.endpoint);
+
+      eventHub.$on('enable.key', this.enableKey);
+      eventHub.$on('remove.key', this.removeKey);
+      eventHub.$on('disable.key', this.disableKey);
+    },
+    mounted() {
+      this.fetchKeys();
+    },
+    beforeDestroy() {
+      eventHub.$off('enable.key', this.enableKey);
+      eventHub.$off('remove.key', this.removeKey);
+      eventHub.$off('disable.key', this.disableKey);
+    },
+  };
+</script>
+
+<template>
+  <div class="col-lg-9 col-lg-offset-3 append-bottom-default">
+    <div
+      class="text-center"
+      v-if="isLoading && !hasKeys">
+      <i
+        class="fa fa-spinner fa-spin fa-2x">
+      </i>
+    </div>
+    <div v-else-if="hasKeys">
+      <keys-panel
+        title="Enabled deploy keys for this project"
+        :keys="keys.enabled_keys"
+        :store="store" />
+      <keys-panel
+        title="Deploy keys from projects you have access to"
+        :keys="keys.available_project_keys"
+        :store="store" />
+      <keys-panel
+        title="Public deploy keys available to any project"
+        :keys="keys.public_keys"
+        :store="store" />
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
new file mode 100644
index 0000000000000000000000000000000000000000..af842a3bb391c4bba1082bd52e3b3e74626f22e1
--- /dev/null
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -0,0 +1,85 @@
+<script>
+  import actionBtn from './action_btn.vue';
+
+  export default {
+    props: {
+      deployKey: {
+        type: Object,
+        required: true,
+      },
+      enabled: {
+        type: Boolean,
+        required: false,
+        default: true,
+      },
+      store: {
+        type: Object,
+        required: true,
+      },
+    },
+    components: {
+      actionBtn,
+    },
+    computed: {
+      timeagoDate() {
+        return gl.utils.getTimeago().format(this.deployKey.created_at);
+      },
+    },
+    methods: {
+      isEnabled(id) {
+        return this.store.findEnabledKey(id) !== undefined;
+      },
+    },
+  };
+</script>
+
+<template>
+  <div>
+    <div class="pull-left append-right-10 hidden-xs">
+      <i
+        aria-hidden="true"
+        class="fa fa-key key-icon">
+      </i>
+    </div>
+    <div class="deploy-key-content key-list-item-info">
+      <strong class="title">
+        {{ deployKey.title }}
+      </strong>
+      <div class="description">
+        {{ deployKey.fingerprint }}
+      </div>
+      <div
+        v-if="deployKey.can_push"
+        class="write-access-allowed">
+        Write access allowed
+      </div>
+    </div>
+    <div class="deploy-key-content prepend-left-default deploy-key-projects">
+      <a
+        class="label deploy-project-label"
+        :href="project.full_path"
+        v-for="project in deployKey.projects">
+        {{ project.full_name }}
+      </a>
+    </div>
+    <div class="deploy-key-content">
+      <span class="key-created-at">
+        created {{ timeagoDate }}
+      </span>
+      <action-btn
+        v-if="!isEnabled(deployKey.id)"
+        :deploy-key="deployKey"
+        type="enable"/>
+      <action-btn
+        v-else-if="deployKey.destroyed_when_orphaned && deployKey.almost_orphaned"
+        :deploy-key="deployKey"
+        btn-css-class="btn-warning"
+        type="remove" />
+      <action-btn
+        v-else
+        :deploy-key="deployKey"
+        btn-css-class="btn-warning"
+        type="disable" />
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/deploy_keys/components/keys.vue b/app/assets/javascripts/deploy_keys/components/keys.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2470831eddfef3e8f3b02d022907fd112fd7241a
--- /dev/null
+++ b/app/assets/javascripts/deploy_keys/components/keys.vue
@@ -0,0 +1,52 @@
+<script>
+  import key from './key.vue';
+
+  export default {
+    props: {
+      title: {
+        type: String,
+        required: true,
+      },
+      keys: {
+        type: Array,
+        required: true,
+      },
+      showHelpBox: {
+        type: Boolean,
+        required: false,
+        default: true,
+      },
+      store: {
+        type: Object,
+        required: true,
+      },
+    },
+    components: {
+      key,
+    },
+  };
+</script>
+
+<template>
+  <div>
+    <h5>
+      {{ title }}
+      ({{ keys.length }})
+    </h5>
+    <ul class="well-list"
+      v-if="keys.length">
+      <li
+        v-for="deployKey in keys"
+        key="deployKey.id">
+        <key
+          :deploy-key="deployKey"
+          :store="store" />
+      </li>
+    </ul>
+    <div
+      class="settings-message text-center"
+      v-else-if="showHelpBox">
+      No deploy keys found. Create one with the form above.
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/deploy_keys/eventhub.js b/app/assets/javascripts/deploy_keys/eventhub.js
new file mode 100644
index 0000000000000000000000000000000000000000..0948c2e53524a736a55c060600868ce89ee7687a
--- /dev/null
+++ b/app/assets/javascripts/deploy_keys/eventhub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/deploy_keys/index.js b/app/assets/javascripts/deploy_keys/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..a5f232f950a61d2cffa552e395c9ad665d90a6ec
--- /dev/null
+++ b/app/assets/javascripts/deploy_keys/index.js
@@ -0,0 +1,21 @@
+import Vue from 'vue';
+import deployKeysApp from './components/app.vue';
+
+document.addEventListener('DOMContentLoaded', () => new Vue({
+  el: document.getElementById('js-deploy-keys'),
+  data() {
+    return {
+      endpoint: this.$options.el.dataset.endpoint,
+    };
+  },
+  components: {
+    deployKeysApp,
+  },
+  render(createElement) {
+    return createElement('deploy-keys-app', {
+      props: {
+        endpoint: this.endpoint,
+      },
+    });
+  },
+}));
diff --git a/app/assets/javascripts/deploy_keys/service/index.js b/app/assets/javascripts/deploy_keys/service/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..fe6dbaa949809f011dfedf1b909de476e8525ea3
--- /dev/null
+++ b/app/assets/javascripts/deploy_keys/service/index.js
@@ -0,0 +1,34 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+Vue.use(VueResource);
+
+export default class DeployKeysService {
+  constructor(endpoint) {
+    this.endpoint = endpoint;
+
+    this.resource = Vue.resource(`${this.endpoint}{/id}`, {}, {
+      enable: {
+        method: 'PUT',
+        url: `${this.endpoint}{/id}/enable`,
+      },
+      disable: {
+        method: 'PUT',
+        url: `${this.endpoint}{/id}/disable`,
+      },
+    });
+  }
+
+  getKeys() {
+    return this.resource.get()
+      .then(response => response.json());
+  }
+
+  enableKey(id) {
+    return this.resource.enable({ id }, {});
+  }
+
+  disableKey(id) {
+    return this.resource.disable({ id }, {});
+  }
+}
diff --git a/app/assets/javascripts/deploy_keys/store/index.js b/app/assets/javascripts/deploy_keys/store/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..7177d9bed7fbcfaf6820e863a5529019fd325072
--- /dev/null
+++ b/app/assets/javascripts/deploy_keys/store/index.js
@@ -0,0 +1,13 @@
+export default class DeployKeysStore {
+  constructor() {
+    this.keys = {};
+  }
+
+  findEnabledKey(id) {
+    return this.keys.enabled_keys.find(key => key.id === id);
+  }
+
+  removeKeyForType(deployKey, type) {
+    this.keys[type] = this.keys[type].filter(key => key.id !== deployKey.id);
+  }
+}
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index f7402792c59eb13bcd7375f1799023b4c629187e..d3d75c4bf4a1582f76d11348add6eec982dd0598 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -48,7 +48,6 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
 import UserCallout from './user_callout';
 import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
 import ShortcutsWiki from './shortcuts_wiki';
-import SettingsDeployKeys from './settings/settings_repository';
 import BlobViewer from './blob/viewer/index';
 
 const ShortcutsBlob = require('./shortcuts_blob');
@@ -346,8 +345,6 @@ const ShortcutsBlob = require('./shortcuts_blob');
           // Initialize Protected Tag Settings
           new ProtectedTagCreate();
           new ProtectedTagEditList();
-          // Initialize deploy key ajax call
-          new SettingsDeployKeys();
           break;
         case 'projects:ci_cd:show':
           new gl.ProjectVariables();
diff --git a/app/assets/javascripts/settings/settings_repository.js b/app/assets/javascripts/settings/settings_repository.js
deleted file mode 100644
index 9d6a60baa3ad4801d657c85b070e59c323bafa70..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/settings/settings_repository.js
+++ /dev/null
@@ -1,96 +0,0 @@
-/* eslint-disable no-new */
-import Vue from 'vue';
-import VueResource from 'vue-resource';
-
-Vue.use(VueResource);
-
-export default class SettingsDeployKeys {
-  constructor() {
-    this.initVue();
-  }
-
-  deployKeyRowTemplate() {
-    return `
-      <li>
-        <div class="pull-left append-right-10 hidden-xs">
-          <i aria-hidden="true" class="fa fa-key key-icon"></i>
-        </div>
-        <div class="deploy-key-content key-list-item-info">
-          <strong class="title">
-            {{deployKey.title}}
-          </strong>
-          <div class="description">
-            {{deployKey.fingerprint}}
-          </div>
-        </div>
-        <div class="deploy-key-content prepend-left-default deploy-key-projects">
-          <a class="label deploy-project-label" :href="project.full_path" v-for="project in deployKey.projects">{{project.full_name}}</a>
-        </div>
-        <div class="deploy-key-content">
-          <span class="key-created-at">
-            created {{timeagoDate}}
-          </span>
-          <div class="visible-xs-block visible-sm-block"></div>
-          <a v-if="!enabled" class="btn btn-sm prepend-left-10" rel="nofollow" data-method="put" href="enableURL">Enable
-</a>
-          <a v-else-if="deployKey.destroyed_when_orphaned && deployKey.almost_orphaned" class="btn btn-warning btn-sm prepend-left-10" rel="nofollow" data-method="put" :href="disableURL">Remove</a>
-          <a v-else class="btn btn-warning btn-sm prepend-left-10" rel="nofollow" data-method="put" :href="disableURL">Disable</a>
-        </div>
-      </li>`
-  }
-
-  deployKeyRowComponent() {
-    const self = this;
-    return {
-      props: {
-        deployKey: Object,
-        enabled: Boolean
-      },
-
-      computed: {
-        timeagoDate() {
-          return gl.utils.getTimeago().format(this.deployKey.createdAt, 'gl_en');
-        },
-
-        disableURL() {
-          return self.disableEndpoint.replace(':id', this.deployKey.id);
-        },
-
-        enableURL() {
-         return self.enableEndpoint.replace(':id', this.deployKey.id); 
-        }
-      },
-
-      template: this.deployKeyRowTemplate()
-    }
-  }
-
-  initVue() {
-    const self = this;
-    const el = document.getElementById('js-deploy-keys');
-    const endpoint = el.dataset.endpoint;
-    this.jsonEndpoint = `${endpoint}.json`;
-    this.disableEndpoint = `${endpoint}/:id/disable`;
-    this.enableEndpoint = `${endpoint}/:id/enable`;
-    new Vue({
-      el: el,
-      components: {
-        deployKeyRow: self.deployKeyRowComponent()
-      },
-      data () {
-        return {
-          enabledKeys: [],
-          availableKeys: []
-        }
-      },
-      created () {
-        this.$http.get(self.jsonEndpoint)
-          .then((res) => {
-            const keys = JSON.parse(res.body);
-            this.enabledKeys = keys.enabled_keys;
-            this.availableKeys = keys.available_project_keys;
-          });
-      }
-    })
-  }
-} 
\ No newline at end of file
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index a47e15a192bfffe795a281f28cd78eb4d60c8ca2..f27089b8590d0618664e63b4d30a1d9a57578804 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -32,7 +32,10 @@ class Projects::DeployKeysController < Projects::ApplicationController
   def enable
     Projects::EnableDeployKeyService.new(@project, current_user, params).execute
 
-    redirect_to_repository_settings(@project)
+    respond_to do |format|
+      format.html { redirect_to_repository_settings(@project) }
+      format.json { head :ok }
+    end
   end
 
   def disable
@@ -40,7 +43,11 @@ class Projects::DeployKeysController < Projects::ApplicationController
     return render_404 unless deploy_key_project
 
     deploy_key_project.destroy!
-    redirect_to_repository_settings(@project)
+
+    respond_to do |format|
+      format.html { redirect_to_repository_settings(@project) }
+      format.json { head :ok }
+    end
   end
 
   protected
diff --git a/app/serializers/project_entity.rb b/app/serializers/project_entity.rb
index 6f8061f75309533cc40b259e87dcafa9819be654..a471a7e6a882540be3d0232af0abcf89233a3eee 100644
--- a/app/serializers/project_entity.rb
+++ b/app/serializers/project_entity.rb
@@ -1,9 +1,11 @@
 class ProjectEntity < Grape::Entity
+  include RequestAwareEntity
+  
   expose :id
   expose :name
 
   expose :full_path do |project|
-    project.full_path
+    namespace_project_path(project.namespace, project)
   end
 
   expose :full_name do |project|
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
index b35cd356aa50723932c830bd6b373ab71bdddfbb..74756b58439a097cfccf7b1622c7ce904526d23e 100644
--- a/app/views/projects/deploy_keys/_index.html.haml
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -10,16 +10,4 @@
     = render @deploy_keys.form_partial_path
   .col-lg-9.col-lg-offset-3
     %hr
-  #js-deploy-keys.col-lg-9.col-lg-offset-3.append-bottom-default{data:{endpoint: namespace_project_deploy_keys_path}}
-    %h5.prepend-top-0
-      Enabled deploy keys for this project ({{enabledKeys.length}})
-    %ul.well-list{"v-if" => "enabledKeys.length"}
-      %deploy-key-row{"v-for" => "deployKey in enabledKeys", ":deploy-key" => "deployKey", ":enabled" =>"true"}
-    .settings-message.text-center{"v-else" => true}
-      No deploy keys found. Create one with the form above.
-    %h5.prepend-top-0
-      Deploy keys from projects you have access to ({{availableKeys.length}})
-    %ul.well-list{"v-if" => "availableKeys.length"}
-      %deploy-key-row{"v-for" => "deployKey in availableKeys", ":deploy-key" => "deployKey", ":enabled" =>"false"}
-    .settings-message.text-center{"v-else" => true}
-      No deploy keys found. Create one with the form above.
+  #js-deploy-keys{ data: { endpoint: namespace_project_deploy_keys_path } }
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index 5402320cb66d004869a3602e8c4f6a5d1978c78f..4e59033c4a34eb7021a50dd06bb158b1c9f3477a 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -1,6 +1,10 @@
 - page_title "Repository"
 = render "projects/settings/head"
 
+- content_for :page_specific_javascripts do
+  = page_specific_javascript_bundle_tag('common_vue')
+  = page_specific_javascript_bundle_tag('deploy_keys')
+
 = render @deploy_keys
 = render "projects/protected_branches/index"
 = render "projects/protected_tags/index"
diff --git a/config/webpack.config.js b/config/webpack.config.js
index cb0a57a3a410fe77a0e7045a20b22ab04881abc1..1721d275685fb041cf973183ca86437242f299f8 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -26,6 +26,7 @@ var config = {
     common_d3:            ['d3'],
     cycle_analytics:      './cycle_analytics/cycle_analytics_bundle.js',
     commit_pipelines:     './commit/pipelines/pipelines_bundle.js',
+    deploy_keys:          './deploy_keys/index.js',
     diff_notes:           './diff_notes/diff_notes_bundle.js',
     environments:         './environments/environments_bundle.js',
     environments_folder:  './environments/folder/environments_folder_bundle.js',