diff options
-rw-r--r-- | app/assets/javascripts/deploy_keys/components/action_btn.vue | 53 | ||||
-rw-r--r-- | app/assets/javascripts/deploy_keys/components/app.vue | 105 | ||||
-rw-r--r-- | app/assets/javascripts/deploy_keys/components/key.vue | 85 | ||||
-rw-r--r-- | app/assets/javascripts/deploy_keys/components/keys.vue | 52 | ||||
-rw-r--r-- | app/assets/javascripts/deploy_keys/eventhub.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/deploy_keys/index.js | 21 | ||||
-rw-r--r-- | app/assets/javascripts/deploy_keys/service/index.js | 34 | ||||
-rw-r--r-- | app/assets/javascripts/deploy_keys/store/index.js | 13 | ||||
-rw-r--r-- | app/assets/javascripts/dispatcher.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/settings/settings_repository.js | 96 | ||||
-rw-r--r-- | app/controllers/projects/deploy_keys_controller.rb | 11 | ||||
-rw-r--r-- | app/serializers/project_entity.rb | 4 | ||||
-rw-r--r-- | app/views/projects/deploy_keys/_index.html.haml | 14 | ||||
-rw-r--r-- | app/views/projects/settings/repository/show.html.haml | 4 | ||||
-rw-r--r-- | config/webpack.config.js | 1 |
15 files changed, 384 insertions, 115 deletions
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 00000000000..7da2915a45e --- /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 00000000000..ff54a0f241a --- /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 00000000000..af842a3bb39 --- /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 00000000000..2470831eddf --- /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 00000000000..0948c2e5352 --- /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 00000000000..a5f232f950a --- /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 00000000000..fe6dbaa9498 --- /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 00000000000..7177d9bed7f --- /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 f7402792c59..d3d75c4bf4a 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 9d6a60baa3a..00000000000 --- 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 a47e15a192b..f27089b8590 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 6f8061f7530..a471a7e6a88 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 b35cd356aa5..74756b58439 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 5402320cb66..4e59033c4a3 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 cb0a57a3a41..1721d275685 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', |