Commit 3db5b703 authored by Fatih Acet's avatar Fatih Acet Committed by Nick Thomas

Add terminal UI and controller actions

parent c3d972f4
...@@ -76,6 +76,7 @@ ...@@ -76,6 +76,7 @@
helpPagePath: environmentsData.helpPagePath, helpPagePath: environmentsData.helpPagePath,
commitIconSvg: environmentsData.commitIconSvg, commitIconSvg: environmentsData.commitIconSvg,
playIconSvg: environmentsData.playIconSvg, playIconSvg: environmentsData.playIconSvg,
terminalIconSvg: environmentsData.terminalIconSvg,
}; };
}, },
...@@ -230,6 +231,7 @@ ...@@ -230,6 +231,7 @@
:can-create-deployment="canCreateDeploymentParsed" :can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed" :can-read-environment="canReadEnvironmentParsed"
:play-icon-svg="playIconSvg" :play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg"></tr> :commit-icon-svg="commitIconSvg"></tr>
<tr v-if="model.isOpen && model.children && model.children.length > 0" <tr v-if="model.isOpen && model.children && model.children.length > 0"
...@@ -240,6 +242,7 @@ ...@@ -240,6 +242,7 @@
:can-create-deployment="canCreateDeploymentParsed" :can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed" :can-read-environment="canReadEnvironmentParsed"
:play-icon-svg="playIconSvg" :play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg"> :commit-icon-svg="commitIconSvg">
</tr> </tr>
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
/*= require ./environment_external_url */ /*= require ./environment_external_url */
/*= require ./environment_stop */ /*= require ./environment_stop */
/*= require ./environment_rollback */ /*= require ./environment_rollback */
/*= require ./environment_terminal_button */
(() => { (() => {
/** /**
...@@ -33,6 +34,7 @@ ...@@ -33,6 +34,7 @@
'external-url-component': window.gl.environmentsList.ExternalUrlComponent, 'external-url-component': window.gl.environmentsList.ExternalUrlComponent,
'stop-component': window.gl.environmentsList.StopComponent, 'stop-component': window.gl.environmentsList.StopComponent,
'rollback-component': window.gl.environmentsList.RollbackComponent, 'rollback-component': window.gl.environmentsList.RollbackComponent,
'terminal-button-component': window.gl.environmentsList.TerminalButtonComponent,
}, },
props: { props: {
...@@ -68,6 +70,12 @@ ...@@ -68,6 +70,12 @@
type: String, type: String,
required: false, required: false,
}, },
terminalIconSvg: {
type: String,
required: false,
},
}, },
data() { data() {
...@@ -506,6 +514,14 @@ ...@@ -506,6 +514,14 @@
</stop-component> </stop-component>
</div> </div>
<div v-if="model.terminal_path"
class="inline js-terminal-button-container">
<terminal-button-component
:terminal-icon-svg="terminalIconSvg"
:terminal-path="model.terminal_path">
</terminal-button-component>
</div>
<div v-if="canRetry && canCreateDeployment" <div v-if="canRetry && canCreateDeployment"
class="inline js-rollback-component-container"> class="inline js-rollback-component-container">
<rollback-component <rollback-component
......
/*= require vue */
/* global Vue */
(() => {
window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {};
window.gl.environmentsList.TerminalButtonComponent = Vue.component('terminal-button-component', {
props: {
terminalPath: {
type: String,
default: '',
},
terminalIconSvg: {
type: String,
default: '',
},
},
template: `
<a class="btn terminal-button"
:href="terminalPath">
<span class="js-terminal-icon-container" v-html="terminalIconSvg"></span>
</a>
`,
});
})();
...@@ -230,6 +230,13 @@ ...@@ -230,6 +230,13 @@
} }
} }
.btn-terminal {
svg {
height: 14px;
width: 18px;
}
}
.btn-lg { .btn-lg {
padding: 12px 20px; padding: 12px 20px;
} }
......
...@@ -734,3 +734,23 @@ ...@@ -734,3 +734,23 @@
padding: 5px 5px 5px 7px; padding: 5px 5px 5px 7px;
} }
} }
.terminal-icon {
margin-left: 3px;
}
.terminal-container {
.content-block {
border-bottom: none;
}
#terminal {
margin-top: 10px;
min-height: 450px;
box-sizing: border-box;
> div {
min-height: 450px;
}
}
}
...@@ -4,7 +4,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -4,7 +4,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :authorize_create_environment!, only: [:new, :create] before_action :authorize_create_environment!, only: [:new, :create]
before_action :authorize_create_deployment!, only: [:stop] before_action :authorize_create_deployment!, only: [:stop]
before_action :authorize_update_environment!, only: [:edit, :update] before_action :authorize_update_environment!, only: [:edit, :update]
before_action :environment, only: [:show, :edit, :update, :stop] before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize]
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
def index def index
@scope = params[:scope] @scope = params[:scope]
...@@ -14,7 +16,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -14,7 +16,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
format.html format.html
format.json do format.json do
render json: EnvironmentSerializer render json: EnvironmentSerializer
.new(project: @project) .new(project: @project, user: current_user)
.represent(@environments) .represent(@environments)
end end
end end
...@@ -65,11 +67,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -65,11 +67,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController
# GET .../terminal.ws : implemented in gitlab-workhorse # GET .../terminal.ws : implemented in gitlab-workhorse
def terminal_websocket_authorize def terminal_websocket_authorize
Gitlab::Workhorse.verify_api_request!(request.headers)
# Just return the first terminal for now. If the list is in the process of # Just return the first terminal for now. If the list is in the process of
# being looked up, this may result in a 404 response, so the frontend # being looked up, this may result in a 404 response, so the frontend
# should retry # should retry those errors
terminal = environment.terminals.try(:first) terminal = environment.terminals.try(:first)
if terminal if terminal
set_workhorse_internal_api_content_type set_workhorse_internal_api_content_type
...@@ -81,6 +81,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -81,6 +81,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController
private private
def verify_api_request!
Gitlab::Workhorse.verify_api_request!(request.headers)
end
def environment_params def environment_params
params.require(:environment).permit(:name, :external_url) params.require(:environment).permit(:name, :external_url)
end end
......
...@@ -8,7 +8,6 @@ class EnvironmentEntity < Grape::Entity ...@@ -8,7 +8,6 @@ class EnvironmentEntity < Grape::Entity
expose :environment_type expose :environment_type
expose :last_deployment, using: DeploymentEntity expose :last_deployment, using: DeploymentEntity
expose :stoppable? expose :stoppable?
expose :has_terminals?, as: :has_terminals
expose :environment_path do |environment| expose :environment_path do |environment|
namespace_project_environment_path( namespace_project_environment_path(
...@@ -25,6 +24,7 @@ class EnvironmentEntity < Grape::Entity ...@@ -25,6 +24,7 @@ class EnvironmentEntity < Grape::Entity
end end
expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment| expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment|
can?(request.user, :admin_environment, environment.project) &&
terminal_namespace_project_environment_path( terminal_namespace_project_environment_path(
environment.project.namespace, environment.project.namespace,
environment.project, environment.project,
......
...@@ -8,4 +8,8 @@ module RequestAwareEntity ...@@ -8,4 +8,8 @@ module RequestAwareEntity
def request def request
@options.fetch(:request) @options.fetch(:request)
end end
def can?(object, action, subject)
Ability.allowed?(object, action, subject)
end
end end
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
= link_to terminal_namespace_project_environment_path(@project.namespace, @project, environment), class: 'btn terminal-button' do
= icon('terminal')
...@@ -15,4 +15,5 @@ ...@@ -15,4 +15,5 @@
"help-page-path" => help_page_path("ci/environments"), "help-page-path" => help_page_path("ci/environments"),
"css-class" => container_class, "css-class" => container_class,
"commit-icon-svg" => custom_icon("icon_commit"), "commit-icon-svg" => custom_icon("icon_commit"),
"terminal-icon-svg" => custom_icon("icon_terminal"),
"play-icon-svg" => custom_icon("icon_play")}} "play-icon-svg" => custom_icon("icon_play")}}
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
%h3.page-title= @environment.name.capitalize %h3.page-title= @environment.name.capitalize
.col-md-3 .col-md-3
.nav-controls .nav-controls
= render 'projects/environments/terminal_button', environment: @environment
= render 'projects/environments/external_url', environment: @environment = render 'projects/environments/external_url', environment: @environment
- if can?(current_user, :update_environment, @environment) - if can?(current_user, :update_environment, @environment)
= link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
......
- @no_container = true
- page_title "Terminal for environment", @environment.name
= render "projects/pipelines/head"
- content_for :page_specific_javascripts do
= stylesheet_link_tag "xterm/xterm"
= page_specific_javascript_tag("terminal/terminal_bundle.js")
%div{class: container_class}
.top-area
.row
.col-sm-6
%h3.page-title
Terminal for environment
= @environment.name
.col-sm-6
.nav-controls
= render 'projects/deployments/actions', deployment: @environment.last_deployment
.terminal-container{class: container_class}
#terminal{data:{project_path: "#{terminal_namespace_project_environment_path(@project.namespace, @project, @environment)}.ws"}}
<svg width="19" height="14" viewBox="0 0 19 14" xmlns="http://www.w3.org/2000/svg"><rect fill="#848484" x="7.2" y="9.25" width="6.46" height="1.5" rx=".5"/><path d="M5.851 7.016L3.81 9.103a.503.503 0 0 0 .017.709l.35.334c.207.198.524.191.717-.006l2.687-2.748a.493.493 0 0 0 .137-.376.493.493 0 0 0-.137-.376L4.894 3.892a.507.507 0 0 0-.717-.006l-.35.334a.503.503 0 0 0-.017.709L5.85 7.016z"/><path d="M1.25 11.497c0 .691.562 1.253 1.253 1.253h13.994c.694 0 1.253-.56 1.253-1.253V2.503c0-.691-.562-1.253-1.253-1.253H2.503c-.694 0-1.253.56-1.253 1.253v8.994zM2.503 0h13.994A2.504 2.504 0 0 1 19 2.503v8.994A2.501 2.501 0 0 1 16.497 14H2.503A2.504 2.504 0 0 1 0 11.497V2.503A2.501 2.501 0 0 1 2.503 0z"/></svg>
...@@ -148,6 +148,8 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -148,6 +148,8 @@ constraints(ProjectUrlConstrainer.new) do
resources :environments, except: [:destroy] do resources :environments, except: [:destroy] do
member do member do
post :stop post :stop
get :terminal
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil }
end end
end end
......
...@@ -123,6 +123,7 @@ describe Projects::EnvironmentsController do ...@@ -123,6 +123,7 @@ describe Projects::EnvironmentsController do
context 'and invalid id' do context 'and invalid id' do
it 'returns 404' do it 'returns 404' do
get :terminal_websocket_authorize, environment_params(id: 666) get :terminal_websocket_authorize, environment_params(id: 666)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
......
...@@ -42,6 +42,12 @@ FactoryGirl.define do ...@@ -42,6 +42,12 @@ FactoryGirl.define do
end end
end end
trait :test_repo do
after :create do |project|
TestEnv.copy_repo(project)
end
end
# Nest Project Feature attributes # Nest Project Feature attributes
transient do transient do
wiki_access_level ProjectFeature::ENABLED wiki_access_level ProjectFeature::ENABLED
...@@ -91,9 +97,7 @@ FactoryGirl.define do ...@@ -91,9 +97,7 @@ FactoryGirl.define do
factory :project, parent: :empty_project do factory :project, parent: :empty_project do
path { 'gitlabhq' } path { 'gitlabhq' }
after :create do |project| test_repo
TestEnv.copy_repo(project)
end
end end
factory :forked_project_with_submodules, parent: :empty_project do factory :forked_project_with_submodules, parent: :empty_project do
......
...@@ -38,6 +38,10 @@ feature 'Environment', :feature do ...@@ -38,6 +38,10 @@ feature 'Environment', :feature do
scenario 'does not show a re-deploy button for deployment without build' do scenario 'does not show a re-deploy button for deployment without build' do
expect(page).not_to have_link('Re-deploy') expect(page).not_to have_link('Re-deploy')
end end
scenario 'does not show terminal button' do
expect(page).not_to have_terminal_button
end
end end
context 'with related deployable present' do context 'with related deployable present' do
...@@ -60,6 +64,10 @@ feature 'Environment', :feature do ...@@ -60,6 +64,10 @@ feature 'Environment', :feature do
expect(page).not_to have_link('Stop') expect(page).not_to have_link('Stop')
end end
scenario 'does not show terminal button' do
expect(page).not_to have_terminal_button
end
context 'with manual action' do context 'with manual action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
...@@ -84,6 +92,26 @@ feature 'Environment', :feature do ...@@ -84,6 +92,26 @@ feature 'Environment', :feature do
end end
end end
context 'with terminal' do
let(:project) { create(:kubernetes_project, :test_repo) }
context 'for project master' do
let(:role) { :master }
scenario 'it shows the terminal button' do
expect(page).to have_terminal_button
end
end
context 'for developer' do
let(:role) { :developer }
scenario 'does not show terminal button' do
expect(page).not_to have_terminal_button
end
end
end
context 'with stop action' do context 'with stop action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') }
...@@ -158,4 +186,8 @@ feature 'Environment', :feature do ...@@ -158,4 +186,8 @@ feature 'Environment', :feature do
environment.project, environment.project,
environment) environment)
end end
def have_terminal_button
have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
end
end end
...@@ -113,6 +113,10 @@ feature 'Environments page', :feature, :js do ...@@ -113,6 +113,10 @@ feature 'Environments page', :feature, :js do
expect(page).not_to have_css('external-url') expect(page).not_to have_css('external-url')
end end
scenario 'does not show terminal button' do
expect(page).not_to have_terminal_button
end
context 'with external_url' do context 'with external_url' do
given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') }
given(:build) { create(:ci_build, pipeline: pipeline) } given(:build) { create(:ci_build, pipeline: pipeline) }
...@@ -145,6 +149,26 @@ feature 'Environments page', :feature, :js do ...@@ -145,6 +149,26 @@ feature 'Environments page', :feature, :js do
end end
end end
end end
context 'with terminal' do
let(:project) { create(:kubernetes_project, :test_repo) }
context 'for project master' do
let(:role) { :master }
scenario 'it shows the terminal button' do
expect(page).to have_terminal_button
end
end
context 'for developer' do
let(:role) { :developer }
scenario 'does not show terminal button' do
expect(page).not_to have_terminal_button
end
end
end
end end
end end
end end
...@@ -195,6 +219,10 @@ feature 'Environments page', :feature, :js do ...@@ -195,6 +219,10 @@ feature 'Environments page', :feature, :js do
end end
end end
def have_terminal_button
have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
end
def visit_environments(project) def visit_environments(project)
visit namespace_project_environments_path(project.namespace, project) visit namespace_project_environments_path(project.namespace, project)
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment