Merge branch '38019-hide-runner-token' into 'master'
Hide runner token in CI/CD settings page Closes #38019 See merge request gitlab-org/gitlab-ce!15918
This commit is contained in:
commit
258633a6ba
10 changed files with 223 additions and 49 deletions
42
app/assets/javascripts/behaviors/secret_values.js
Normal file
42
app/assets/javascripts/behaviors/secret_values.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { n__ } from '../locale';
|
||||||
|
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
|
||||||
|
|
||||||
|
export default class SecretValues {
|
||||||
|
constructor(container) {
|
||||||
|
this.container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.values = this.container.querySelectorAll('.js-secret-value');
|
||||||
|
this.placeholders = this.container.querySelectorAll('.js-secret-value-placeholder');
|
||||||
|
this.revealButton = this.container.querySelector('.js-secret-value-reveal-button');
|
||||||
|
|
||||||
|
this.revealText = n__('Reveal value', 'Reveal values', this.values.length);
|
||||||
|
this.hideText = n__('Hide value', 'Hide values', this.values.length);
|
||||||
|
|
||||||
|
const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
|
||||||
|
this.updateDom(isRevealed);
|
||||||
|
|
||||||
|
this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onRevealButtonClicked() {
|
||||||
|
const previousIsRevealed = convertPermissionToBoolean(
|
||||||
|
this.revealButton.dataset.secretRevealStatus,
|
||||||
|
);
|
||||||
|
this.updateDom(!previousIsRevealed);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDom(isRevealed) {
|
||||||
|
this.values.forEach((value) => {
|
||||||
|
value.classList.toggle('hide', !isRevealed);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.placeholders.forEach((placeholder) => {
|
||||||
|
placeholder.classList.toggle('hide', isRevealed);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.revealButton.textContent = isRevealed ? this.hideText : this.revealText;
|
||||||
|
this.revealButton.dataset.secretRevealStatus = isRevealed;
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ import Flash from './flash';
|
||||||
import CommitsList from './commits';
|
import CommitsList from './commits';
|
||||||
import Issue from './issue';
|
import Issue from './issue';
|
||||||
import BindInOut from './behaviors/bind_in_out';
|
import BindInOut from './behaviors/bind_in_out';
|
||||||
|
import SecretValues from './behaviors/secret_values';
|
||||||
import DeleteModal from './branches/branches_delete_modal';
|
import DeleteModal from './branches/branches_delete_modal';
|
||||||
import Group from './group';
|
import Group from './group';
|
||||||
import GroupsList from './groups_list';
|
import GroupsList from './groups_list';
|
||||||
|
@ -90,7 +91,6 @@ import memberExpirationDate from './member_expiration_date';
|
||||||
import DueDateSelectors from './due_date_select';
|
import DueDateSelectors from './due_date_select';
|
||||||
import Diff from './diff';
|
import Diff from './diff';
|
||||||
import ProjectLabelSubscription from './project_label_subscription';
|
import ProjectLabelSubscription from './project_label_subscription';
|
||||||
import ProjectVariables from './project_variables';
|
|
||||||
import SearchAutocomplete from './search_autocomplete';
|
import SearchAutocomplete from './search_autocomplete';
|
||||||
import Activities from './activities';
|
import Activities from './activities';
|
||||||
|
|
||||||
|
@ -527,8 +527,18 @@ import Activities from './activities';
|
||||||
case 'projects:settings:ci_cd:show':
|
case 'projects:settings:ci_cd:show':
|
||||||
// Initialize expandable settings panels
|
// Initialize expandable settings panels
|
||||||
initSettingsPanels();
|
initSettingsPanels();
|
||||||
|
|
||||||
|
const runnerToken = document.querySelector('.js-secret-runner-token');
|
||||||
|
if (runnerToken) {
|
||||||
|
const runnerTokenSecretValue = new SecretValues(runnerToken);
|
||||||
|
runnerTokenSecretValue.init();
|
||||||
|
}
|
||||||
case 'groups:settings:ci_cd:show':
|
case 'groups:settings:ci_cd:show':
|
||||||
new ProjectVariables();
|
const secretVariableTable = document.querySelector('.js-secret-variable-table');
|
||||||
|
if (secretVariableTable) {
|
||||||
|
const secretVariableTableValues = new SecretValues(secretVariableTable);
|
||||||
|
secretVariableTableValues.init();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'ci:lints:create':
|
case 'ci:lints:create':
|
||||||
case 'ci:lints:show':
|
case 'ci:lints:show':
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
|
|
||||||
const HIDDEN_VALUE_TEXT = '******';
|
|
||||||
|
|
||||||
export default class ProjectVariables {
|
|
||||||
constructor() {
|
|
||||||
this.$revealBtn = $('.js-btn-toggle-reveal-values');
|
|
||||||
this.$revealBtn.on('click', this.toggleRevealState.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleRevealState(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const oldStatus = this.$revealBtn.attr('data-status');
|
|
||||||
let newStatus = 'hidden';
|
|
||||||
let newAction = 'Reveal Values';
|
|
||||||
|
|
||||||
if (oldStatus === 'hidden') {
|
|
||||||
newStatus = 'revealed';
|
|
||||||
newAction = 'Hide Values';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$revealBtn.attr('data-status', newStatus);
|
|
||||||
|
|
||||||
const $variables = $('.variable-value');
|
|
||||||
|
|
||||||
$variables.each((_, variable) => {
|
|
||||||
const $variable = $(variable);
|
|
||||||
let newText = HIDDEN_VALUE_TEXT;
|
|
||||||
|
|
||||||
if (newStatus === 'revealed') {
|
|
||||||
newText = $variable.attr('data-value');
|
|
||||||
}
|
|
||||||
|
|
||||||
$variable.text(newText);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$revealBtn.text(newAction);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,5 +10,7 @@
|
||||||
%p.settings-message.text-center.append-bottom-0
|
%p.settings-message.text-center.append-bottom-0
|
||||||
No variables found, add one with the form above.
|
No variables found, add one with the form above.
|
||||||
- else
|
- else
|
||||||
|
.js-secret-variable-table
|
||||||
= render "ci/variables/table"
|
= render "ci/variables/table"
|
||||||
%button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values
|
%button.btn.btn-info.js-secret-value-reveal-button{ data: { secret_reveal_status: 'false' } }
|
||||||
|
= n_('Reveal value', 'Reveal values', @variables.size)
|
||||||
|
|
|
@ -15,7 +15,11 @@
|
||||||
- if variable.id?
|
- if variable.id?
|
||||||
%tr
|
%tr
|
||||||
%td.variable-key= variable.key
|
%td.variable-key= variable.key
|
||||||
%td.variable-value{ "data-value" => variable.value }******
|
%td.variable-value
|
||||||
|
%span.js-secret-value-placeholder
|
||||||
|
= '*' * 6
|
||||||
|
%span.hide.js-secret-value
|
||||||
|
= variable.value
|
||||||
%td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected)
|
%td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected)
|
||||||
%td.variable-menu
|
%td.variable-menu
|
||||||
= link_to variable.edit_path, class: "btn btn-transparent btn-variable-edit" do
|
= link_to variable.edit_path, class: "btn btn-transparent btn-variable-edit" do
|
||||||
|
|
|
@ -40,10 +40,14 @@
|
||||||
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
|
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
|
||||||
|
|
||||||
%hr
|
%hr
|
||||||
.form-group.append-bottom-default
|
.form-group.append-bottom-default.js-secret-runner-token
|
||||||
= f.label :runners_token, "Runner token", class: 'label-light'
|
= f.label :runners_token, "Runner token", class: 'label-light'
|
||||||
= f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
|
.form-control.js-secret-value-placeholder
|
||||||
|
= '*' * 20
|
||||||
|
= f.text_field :runners_token, class: "form-control hide js-secret-value", placeholder: 'xEeFCaDAB89'
|
||||||
%p.help-block The secure token used by the Runner to checkout the project
|
%p.help-block The secure token used by the Runner to checkout the project
|
||||||
|
%button.btn.btn-info.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: 'false' } }
|
||||||
|
= _('Reveal value')
|
||||||
|
|
||||||
%hr
|
%hr
|
||||||
.form-group
|
.form-group
|
||||||
|
|
5
changelogs/unreleased/38019-hide-runner-token.yml
Normal file
5
changelogs/unreleased/38019-hide-runner-token.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Hide runner token in CI/CD settings page
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -24,7 +24,7 @@ feature 'Group variables', :js do
|
||||||
expect(find(".variable-value")).to have_content('******')
|
expect(find(".variable-value")).to have_content('******')
|
||||||
expect(find(".variable-protected")).to have_content('Yes')
|
expect(find(".variable-protected")).to have_content('Yes')
|
||||||
end
|
end
|
||||||
click_on 'Reveal Values'
|
click_on 'Reveal value'
|
||||||
page.within('.variables-table') do
|
page.within('.variables-table') do
|
||||||
expect(find(".variable-value")).to have_content('AAA123')
|
expect(find(".variable-value")).to have_content('AAA123')
|
||||||
end
|
end
|
||||||
|
|
|
@ -65,14 +65,14 @@ describe 'Project variables', :js do
|
||||||
expect(page).to have_content('******')
|
expect(page).to have_content('******')
|
||||||
end
|
end
|
||||||
|
|
||||||
click_button('Reveal Values')
|
click_button('Reveal values')
|
||||||
|
|
||||||
page.within('.variables-table') do
|
page.within('.variables-table') do
|
||||||
expect(page).to have_content('key')
|
expect(page).to have_content('key')
|
||||||
expect(page).to have_content('key value')
|
expect(page).to have_content('key value')
|
||||||
end
|
end
|
||||||
|
|
||||||
click_button('Hide Values')
|
click_button('Hide values')
|
||||||
|
|
||||||
page.within('.variables-table') do
|
page.within('.variables-table') do
|
||||||
expect(page).to have_content('key')
|
expect(page).to have_content('key')
|
||||||
|
|
146
spec/javascripts/behaviors/secret_values_spec.js
Normal file
146
spec/javascripts/behaviors/secret_values_spec.js
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
import SecretValues from '~/behaviors/secret_values';
|
||||||
|
|
||||||
|
function generateFixtureMarkup(secrets, isRevealed) {
|
||||||
|
return `
|
||||||
|
<div class="js-secret-container">
|
||||||
|
${secrets.map(secret => `
|
||||||
|
<div class="js-secret-value-placeholder">
|
||||||
|
***
|
||||||
|
</div>
|
||||||
|
<div class="hide js-secret-value">
|
||||||
|
${secret}
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
<button
|
||||||
|
class="js-secret-value-reveal-button"
|
||||||
|
data-secret-reveal-status="${isRevealed}"
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupSecretFixture(secrets, isRevealed) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.innerHTML = generateFixtureMarkup(secrets, isRevealed);
|
||||||
|
|
||||||
|
const secretValues = new SecretValues(wrapper.querySelector('.js-secret-container'));
|
||||||
|
secretValues.init();
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('setupSecretValues', () => {
|
||||||
|
describe('with a single secret', () => {
|
||||||
|
const secrets = ['mysecret123'];
|
||||||
|
|
||||||
|
it('should have correct "Reveal" label when values are hidden', () => {
|
||||||
|
const wrapper = setupSecretFixture(secrets, false);
|
||||||
|
const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
|
||||||
|
|
||||||
|
expect(revealButton.textContent).toEqual('Reveal value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have correct "Hide" label when values are shown', () => {
|
||||||
|
const wrapper = setupSecretFixture(secrets, true);
|
||||||
|
const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
|
||||||
|
|
||||||
|
expect(revealButton.textContent).toEqual('Hide value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should value hidden initially', () => {
|
||||||
|
const wrapper = setupSecretFixture(secrets, false);
|
||||||
|
const values = wrapper.querySelectorAll('.js-secret-value');
|
||||||
|
const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder');
|
||||||
|
|
||||||
|
expect(values.length).toEqual(1);
|
||||||
|
expect(values[0].classList.contains('hide')).toEqual(true);
|
||||||
|
expect(placeholders.length).toEqual(1);
|
||||||
|
expect(placeholders[0].classList.contains('hide')).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should toggle value and placeholder', () => {
|
||||||
|
const wrapper = setupSecretFixture(secrets, false);
|
||||||
|
const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
|
||||||
|
const values = wrapper.querySelectorAll('.js-secret-value');
|
||||||
|
const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder');
|
||||||
|
|
||||||
|
revealButton.click();
|
||||||
|
|
||||||
|
expect(values.length).toEqual(1);
|
||||||
|
expect(values[0].classList.contains('hide')).toEqual(false);
|
||||||
|
expect(placeholders.length).toEqual(1);
|
||||||
|
expect(placeholders[0].classList.contains('hide')).toEqual(true);
|
||||||
|
|
||||||
|
revealButton.click();
|
||||||
|
|
||||||
|
expect(values.length).toEqual(1);
|
||||||
|
expect(values[0].classList.contains('hide')).toEqual(true);
|
||||||
|
expect(placeholders.length).toEqual(1);
|
||||||
|
expect(placeholders[0].classList.contains('hide')).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with a multiple secrets', () => {
|
||||||
|
const secrets = ['mysecret123', 'happygoat456', 'tanuki789'];
|
||||||
|
|
||||||
|
it('should have correct "Reveal" label when values are hidden', () => {
|
||||||
|
const wrapper = setupSecretFixture(secrets, false);
|
||||||
|
const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
|
||||||
|
|
||||||
|
expect(revealButton.textContent).toEqual('Reveal values');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have correct "Hide" label when values are shown', () => {
|
||||||
|
const wrapper = setupSecretFixture(secrets, true);
|
||||||
|
const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
|
||||||
|
|
||||||
|
expect(revealButton.textContent).toEqual('Hide values');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have all values hidden initially', () => {
|
||||||
|
const wrapper = setupSecretFixture(secrets, false);
|
||||||
|
const values = wrapper.querySelectorAll('.js-secret-value');
|
||||||
|
const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder');
|
||||||
|
|
||||||
|
expect(values.length).toEqual(3);
|
||||||
|
values.forEach((value) => {
|
||||||
|
expect(value.classList.contains('hide')).toEqual(true);
|
||||||
|
});
|
||||||
|
expect(placeholders.length).toEqual(3);
|
||||||
|
placeholders.forEach((placeholder) => {
|
||||||
|
expect(placeholder.classList.contains('hide')).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should toggle values and placeholders', () => {
|
||||||
|
const wrapper = setupSecretFixture(secrets, false);
|
||||||
|
const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
|
||||||
|
const values = wrapper.querySelectorAll('.js-secret-value');
|
||||||
|
const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder');
|
||||||
|
|
||||||
|
revealButton.click();
|
||||||
|
|
||||||
|
expect(values.length).toEqual(3);
|
||||||
|
values.forEach((value) => {
|
||||||
|
expect(value.classList.contains('hide')).toEqual(false);
|
||||||
|
});
|
||||||
|
expect(placeholders.length).toEqual(3);
|
||||||
|
placeholders.forEach((placeholder) => {
|
||||||
|
expect(placeholder.classList.contains('hide')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
revealButton.click();
|
||||||
|
|
||||||
|
expect(values.length).toEqual(3);
|
||||||
|
values.forEach((value) => {
|
||||||
|
expect(value.classList.contains('hide')).toEqual(true);
|
||||||
|
});
|
||||||
|
expect(placeholders.length).toEqual(3);
|
||||||
|
placeholders.forEach((placeholder) => {
|
||||||
|
expect(placeholder.classList.contains('hide')).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue