Add terminal UI and controller actions
This commit is contained in:
parent
c3d972f4e8
commit
3db5b7033b
18 changed files with 189 additions and 13 deletions
|
@ -76,6 +76,7 @@
|
|||
helpPagePath: environmentsData.helpPagePath,
|
||||
commitIconSvg: environmentsData.commitIconSvg,
|
||||
playIconSvg: environmentsData.playIconSvg,
|
||||
terminalIconSvg: environmentsData.terminalIconSvg,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -230,6 +231,7 @@
|
|||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
:play-icon-svg="playIconSvg"
|
||||
:terminal-icon-svg="terminalIconSvg"
|
||||
:commit-icon-svg="commitIconSvg"></tr>
|
||||
|
||||
<tr v-if="model.isOpen && model.children && model.children.length > 0"
|
||||
|
@ -240,6 +242,7 @@
|
|||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"
|
||||
:play-icon-svg="playIconSvg"
|
||||
:terminal-icon-svg="terminalIconSvg"
|
||||
:commit-icon-svg="commitIconSvg">
|
||||
</tr>
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
/*= require ./environment_external_url */
|
||||
/*= require ./environment_stop */
|
||||
/*= require ./environment_rollback */
|
||||
/*= require ./environment_terminal_button */
|
||||
|
||||
(() => {
|
||||
/**
|
||||
|
@ -33,6 +34,7 @@
|
|||
'external-url-component': window.gl.environmentsList.ExternalUrlComponent,
|
||||
'stop-component': window.gl.environmentsList.StopComponent,
|
||||
'rollback-component': window.gl.environmentsList.RollbackComponent,
|
||||
'terminal-button-component': window.gl.environmentsList.TerminalButtonComponent,
|
||||
},
|
||||
|
||||
props: {
|
||||
|
@ -68,6 +70,12 @@
|
|||
type: String,
|
||||
required: false,
|
||||
},
|
||||
|
||||
terminalIconSvg: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
data() {
|
||||
|
@ -506,6 +514,14 @@
|
|||
</stop-component>
|
||||
</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"
|
||||
class="inline js-rollback-component-container">
|
||||
<rollback-component
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*= 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 @@
|
|||
}
|
||||
}
|
||||
|
||||
.btn-terminal {
|
||||
svg {
|
||||
height: 14px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
|
|
@ -734,3 +734,23 @@
|
|||
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
|
|||
before_action :authorize_create_environment!, only: [:new, :create]
|
||||
before_action :authorize_create_deployment!, only: [:stop]
|
||||
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
|
||||
@scope = params[:scope]
|
||||
|
@ -14,7 +16,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
format.html
|
||||
format.json do
|
||||
render json: EnvironmentSerializer
|
||||
.new(project: @project)
|
||||
.new(project: @project, user: current_user)
|
||||
.represent(@environments)
|
||||
end
|
||||
end
|
||||
|
@ -65,11 +67,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
|
||||
# GET .../terminal.ws : implemented in gitlab-workhorse
|
||||
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
|
||||
# being looked up, this may result in a 404 response, so the frontend
|
||||
# should retry
|
||||
# should retry those errors
|
||||
terminal = environment.terminals.try(:first)
|
||||
if terminal
|
||||
set_workhorse_internal_api_content_type
|
||||
|
@ -81,6 +81,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def verify_api_request!
|
||||
Gitlab::Workhorse.verify_api_request!(request.headers)
|
||||
end
|
||||
|
||||
def environment_params
|
||||
params.require(:environment).permit(:name, :external_url)
|
||||
end
|
||||
|
|
|
@ -8,7 +8,6 @@ class EnvironmentEntity < Grape::Entity
|
|||
expose :environment_type
|
||||
expose :last_deployment, using: DeploymentEntity
|
||||
expose :stoppable?
|
||||
expose :has_terminals?, as: :has_terminals
|
||||
|
||||
expose :environment_path do |environment|
|
||||
namespace_project_environment_path(
|
||||
|
@ -25,10 +24,11 @@ class EnvironmentEntity < Grape::Entity
|
|||
end
|
||||
|
||||
expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment|
|
||||
terminal_namespace_project_environment_path(
|
||||
environment.project.namespace,
|
||||
environment.project,
|
||||
environment)
|
||||
can?(request.user, :admin_environment, environment.project) &&
|
||||
terminal_namespace_project_environment_path(
|
||||
environment.project.namespace,
|
||||
environment.project,
|
||||
environment)
|
||||
end
|
||||
|
||||
expose :created_at, :updated_at
|
||||
|
|
|
@ -8,4 +8,8 @@ module RequestAwareEntity
|
|||
def request
|
||||
@options.fetch(:request)
|
||||
end
|
||||
|
||||
def can?(object, action, subject)
|
||||
Ability.allowed?(object, action, subject)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
- 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 @@
|
|||
"help-page-path" => help_page_path("ci/environments"),
|
||||
"css-class" => container_class,
|
||||
"commit-icon-svg" => custom_icon("icon_commit"),
|
||||
"terminal-icon-svg" => custom_icon("icon_terminal"),
|
||||
"play-icon-svg" => custom_icon("icon_play")}}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
%h3.page-title= @environment.name.capitalize
|
||||
.col-md-3
|
||||
.nav-controls
|
||||
= render 'projects/environments/terminal_button', environment: @environment
|
||||
= render 'projects/environments/external_url', environment: @environment
|
||||
- if can?(current_user, :update_environment, @environment)
|
||||
= link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
|
||||
|
|
22
app/views/projects/environments/terminal.html.haml
Normal file
22
app/views/projects/environments/terminal.html.haml
Normal file
|
@ -0,0 +1,22 @@
|
|||
- @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"}}
|
1
app/views/shared/icons/_icon_terminal.svg
Normal file
1
app/views/shared/icons/_icon_terminal.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 708 B |
|
@ -148,6 +148,8 @@ constraints(ProjectUrlConstrainer.new) do
|
|||
resources :environments, except: [:destroy] do
|
||||
member do
|
||||
post :stop
|
||||
get :terminal
|
||||
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@ describe Projects::EnvironmentsController do
|
|||
context 'and invalid id' do
|
||||
it 'returns 404' do
|
||||
get :terminal_websocket_authorize, environment_params(id: 666)
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,6 +42,12 @@ FactoryGirl.define do
|
|||
end
|
||||
end
|
||||
|
||||
trait :test_repo do
|
||||
after :create do |project|
|
||||
TestEnv.copy_repo(project)
|
||||
end
|
||||
end
|
||||
|
||||
# Nest Project Feature attributes
|
||||
transient do
|
||||
wiki_access_level ProjectFeature::ENABLED
|
||||
|
@ -91,9 +97,7 @@ FactoryGirl.define do
|
|||
factory :project, parent: :empty_project do
|
||||
path { 'gitlabhq' }
|
||||
|
||||
after :create do |project|
|
||||
TestEnv.copy_repo(project)
|
||||
end
|
||||
test_repo
|
||||
end
|
||||
|
||||
factory :forked_project_with_submodules, parent: :empty_project do
|
||||
|
|
|
@ -38,6 +38,10 @@ feature 'Environment', :feature do
|
|||
scenario 'does not show a re-deploy button for deployment without build' do
|
||||
expect(page).not_to have_link('Re-deploy')
|
||||
end
|
||||
|
||||
scenario 'does not show terminal button' do
|
||||
expect(page).not_to have_terminal_button
|
||||
end
|
||||
end
|
||||
|
||||
context 'with related deployable present' do
|
||||
|
@ -60,6 +64,10 @@ feature 'Environment', :feature do
|
|||
expect(page).not_to have_link('Stop')
|
||||
end
|
||||
|
||||
scenario 'does not show terminal button' do
|
||||
expect(page).not_to have_terminal_button
|
||||
end
|
||||
|
||||
context 'with manual action' do
|
||||
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
|
||||
|
||||
|
@ -84,6 +92,26 @@ feature 'Environment', :feature do
|
|||
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
|
||||
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
|
||||
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') }
|
||||
|
@ -158,4 +186,8 @@ feature 'Environment', :feature do
|
|||
environment.project,
|
||||
environment)
|
||||
end
|
||||
|
||||
def have_terminal_button
|
||||
have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -113,6 +113,10 @@ feature 'Environments page', :feature, :js do
|
|||
expect(page).not_to have_css('external-url')
|
||||
end
|
||||
|
||||
scenario 'does not show terminal button' do
|
||||
expect(page).not_to have_terminal_button
|
||||
end
|
||||
|
||||
context 'with external_url' do
|
||||
given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') }
|
||||
given(:build) { create(:ci_build, pipeline: pipeline) }
|
||||
|
@ -145,6 +149,26 @@ feature 'Environments page', :feature, :js do
|
|||
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
|
||||
|
@ -195,6 +219,10 @@ feature 'Environments page', :feature, :js do
|
|||
end
|
||||
end
|
||||
|
||||
def have_terminal_button
|
||||
have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
|
||||
end
|
||||
|
||||
def visit_environments(project)
|
||||
visit namespace_project_environments_path(project.namespace, project)
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue