Skip to content
Snippets Groups Projects
Commit 1d20d436 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@12-9-stable-ee

parent 2774ddc3
No related branches found
No related tags found
No related merge requests found
Showing
with 978 additions and 41 deletions
<script>
import { GlTable, GlButton, GlModalDirective, GlIcon } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { mapState, mapActions } from 'vuex';
import { ADD_CI_VARIABLE_MODAL_ID } from '../constants';
import CiVariablePopover from './ci_variable_popover.vue';
export default {
modalId: ADD_CI_VARIABLE_MODAL_ID,
trueIcon: 'mobile-issue-close',
falseIcon: 'close',
iconSize: 16,
fields: [
{
key: 'variable_type',
label: s__('CiVariables|Type'),
customStyle: { width: '70px' },
},
{
key: 'key',
label: s__('CiVariables|Key'),
tdClass: 'text-plain',
sortable: true,
customStyle: { width: '40%' },
},
{
key: 'value',
label: s__('CiVariables|Value'),
tdClass: 'qa-ci-variable-input-value',
customStyle: { width: '40%' },
},
{
key: 'protected',
label: s__('CiVariables|Protected'),
customStyle: { width: '100px' },
},
{
key: 'masked',
label: s__('CiVariables|Masked'),
customStyle: { width: '100px' },
},
{
key: 'environment_scope',
label: s__('CiVariables|Environments'),
customStyle: { width: '20%' },
},
{
key: 'actions',
label: '',
customStyle: { width: '35px' },
},
],
components: {
GlTable,
GlButton,
GlIcon,
CiVariablePopover,
},
directives: {
GlModalDirective,
},
computed: {
...mapState(['variables', 'valuesHidden', 'isGroup', 'isLoading', 'isDeleting']),
valuesButtonText() {
return this.valuesHidden ? __('Reveal values') : __('Hide values');
},
tableIsNotEmpty() {
return this.variables && this.variables.length > 0;
},
fields() {
if (this.isGroup) {
return this.$options.fields.filter(field => field.key !== 'environment_scope');
}
return this.$options.fields;
},
},
mounted() {
this.fetchVariables();
},
methods: {
...mapActions(['fetchVariables', 'toggleValues', 'editVariable']),
},
};
</script>
<template>
<div class="ci-variable-table">
<gl-table
:fields="fields"
:items="variables"
tbody-tr-class="js-ci-variable-row"
sort-by="key"
sort-direction="asc"
stacked="lg"
fixed
show-empty
sort-icon-left
no-sort-reset
>
<template #table-colgroup="scope">
<col v-for="field in scope.fields" :key="field.key" :style="field.customStyle" />
</template>
<template #cell(key)="{ item }">
<div class="d-flex truncated-container">
<span :id="`ci-variable-key-${item.id}`" class="d-inline-block mw-100 text-truncate">{{
item.key
}}</span>
<ci-variable-popover
:target="`ci-variable-key-${item.id}`"
:value="item.key"
:tooltip-text="__('Copy key')"
/>
</div>
</template>
<template #cell(value)="{ item }">
<span v-if="valuesHidden">*********************</span>
<div v-else class="d-flex truncated-container">
<span :id="`ci-variable-value-${item.id}`" class="d-inline-block mw-100 text-truncate">{{
item.value
}}</span>
<ci-variable-popover
:target="`ci-variable-value-${item.id}`"
:value="item.value"
:tooltip-text="__('Copy value')"
/>
</div>
</template>
<template #cell(protected)="{ item }">
<gl-icon v-if="item.protected" :size="$options.iconSize" :name="$options.trueIcon" />
<gl-icon v-else :size="$options.iconSize" :name="$options.falseIcon" />
</template>
<template #cell(masked)="{ item }">
<gl-icon v-if="item.masked" :size="$options.iconSize" :name="$options.trueIcon" />
<gl-icon v-else :size="$options.iconSize" :name="$options.falseIcon" />
</template>
<template #cell(environment_scope)="{ item }">
<div class="d-flex truncated-container">
<span :id="`ci-variable-env-${item.id}`" class="d-inline-block mw-100 text-truncate">{{
item.environment_scope
}}</span>
<ci-variable-popover
:target="`ci-variable-env-${item.id}`"
:value="item.environment_scope"
:tooltip-text="__('Copy environment')"
/>
</div>
</template>
<template #cell(actions)="{ item }">
<gl-button
ref="edit-ci-variable"
v-gl-modal-directive="$options.modalId"
@click="editVariable(item)"
>
<gl-icon :size="$options.iconSize" name="pencil" />
</gl-button>
</template>
<template #empty>
<p ref="empty-variables" class="text-center empty-variables text-plain">
{{ __('There are no variables yet.') }}
</p>
</template>
</gl-table>
<div
class="ci-variable-actions d-flex justify-content-end"
:class="{ 'justify-content-center': !tableIsNotEmpty }"
>
<gl-button
v-if="tableIsNotEmpty"
ref="secret-value-reveal-button"
data-qa-selector="reveal_ci_variable_value"
class="append-right-8"
@click="toggleValues(!valuesHidden)"
>{{ valuesButtonText }}</gl-button
>
<gl-button
ref="add-ci-variable"
v-gl-modal-directive="$options.modalId"
data-qa-selector="add_ci_variable"
variant="success"
>{{ __('Add Variable') }}</gl-button
>
</div>
</div>
</template>
import { __ } from '~/locale';
// eslint-disable import/prefer-default-export
export const ADD_CI_VARIABLE_MODAL_ID = 'add-ci-variable';
export const displayText = {
variableText: __('Var'),
fileText: __('File'),
allEnvironmentsText: __('All'),
};
export const types = {
variableType: 'env_var',
fileType: 'file',
allEnvironmentsType: '*',
};
import Vue from 'vue';
import CiVariableSettings from './components/ci_variable_settings.vue';
import createStore from './store';
import { parseBoolean } from '~/lib/utils/common_utils';
export default () => {
const el = document.getElementById('js-ci-project-variables');
const { endpoint, projectId, group, maskableRegex } = el.dataset;
const isGroup = parseBoolean(group);
const store = createStore({
endpoint,
projectId,
isGroup,
maskableRegex,
});
return new Vue({
el,
store,
render(createElement) {
return createElement(CiVariableSettings);
},
});
};
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import Api from '~/api';
import createFlash from '~/flash';
import { __ } from '~/locale';
import { prepareDataForApi, prepareDataForDisplay, prepareEnvironments } from './utils';
export const toggleValues = ({ commit }, valueState) => {
commit(types.TOGGLE_VALUES, valueState);
};
export const clearModal = ({ commit }) => {
commit(types.CLEAR_MODAL);
};
export const resetEditing = ({ commit, dispatch }) => {
// fetch variables again if modal is being edited and then hidden
// without saving changes, to cover use case of reactivity in the table
dispatch('fetchVariables');
commit(types.RESET_EDITING);
};
export const requestAddVariable = ({ commit }) => {
commit(types.REQUEST_ADD_VARIABLE);
};
export const receiveAddVariableSuccess = ({ commit }) => {
commit(types.RECEIVE_ADD_VARIABLE_SUCCESS);
};
export const receiveAddVariableError = ({ commit }, error) => {
commit(types.RECEIVE_ADD_VARIABLE_ERROR, error);
};
export const addVariable = ({ state, dispatch }) => {
dispatch('requestAddVariable');
return axios
.patch(state.endpoint, {
variables_attributes: [prepareDataForApi(state.variable)],
})
.then(() => {
dispatch('receiveAddVariableSuccess');
dispatch('fetchVariables');
})
.catch(error => {
createFlash(error.response.data[0]);
dispatch('receiveAddVariableError', error);
});
};
export const requestUpdateVariable = ({ commit }) => {
commit(types.REQUEST_UPDATE_VARIABLE);
};
export const receiveUpdateVariableSuccess = ({ commit }) => {
commit(types.RECEIVE_UPDATE_VARIABLE_SUCCESS);
};
export const receiveUpdateVariableError = ({ commit }, error) => {
commit(types.RECEIVE_UPDATE_VARIABLE_ERROR, error);
};
export const updateVariable = ({ state, dispatch }, variable) => {
dispatch('requestUpdateVariable');
const updatedVariable = prepareDataForApi(variable);
updatedVariable.secrect_value = updateVariable.value;
return axios
.patch(state.endpoint, { variables_attributes: [updatedVariable] })
.then(() => {
dispatch('receiveUpdateVariableSuccess');
dispatch('fetchVariables');
})
.catch(error => {
createFlash(error.response.data[0]);
dispatch('receiveUpdateVariableError', error);
});
};
export const editVariable = ({ commit }, variable) => {
const variableToEdit = variable;
variableToEdit.secret_value = variableToEdit.value;
commit(types.VARIABLE_BEING_EDITED, variableToEdit);
};
export const requestVariables = ({ commit }) => {
commit(types.REQUEST_VARIABLES);
};
export const receiveVariablesSuccess = ({ commit }, variables) => {
commit(types.RECEIVE_VARIABLES_SUCCESS, variables);
};
export const fetchVariables = ({ dispatch, state }) => {
dispatch('requestVariables');
return axios
.get(state.endpoint)
.then(({ data }) => {
dispatch('receiveVariablesSuccess', prepareDataForDisplay(data.variables));
})
.catch(() => {
createFlash(__('There was an error fetching the variables.'));
});
};
export const requestDeleteVariable = ({ commit }) => {
commit(types.REQUEST_DELETE_VARIABLE);
};
export const receiveDeleteVariableSuccess = ({ commit }) => {
commit(types.RECEIVE_DELETE_VARIABLE_SUCCESS);
};
export const receiveDeleteVariableError = ({ commit }, error) => {
commit(types.RECEIVE_DELETE_VARIABLE_ERROR, error);
};
export const deleteVariable = ({ dispatch, state }, variable) => {
dispatch('requestDeleteVariable');
const destroy = true;
return axios
.patch(state.endpoint, { variables_attributes: [prepareDataForApi(variable, destroy)] })
.then(() => {
dispatch('receiveDeleteVariableSuccess');
dispatch('fetchVariables');
})
.catch(error => {
createFlash(error.response.data[0]);
dispatch('receiveDeleteVariableError', error);
});
};
export const requestEnvironments = ({ commit }) => {
commit(types.REQUEST_ENVIRONMENTS);
};
export const receiveEnvironmentsSuccess = ({ commit }, environments) => {
commit(types.RECEIVE_ENVIRONMENTS_SUCCESS, environments);
};
export const fetchEnvironments = ({ dispatch, state }) => {
dispatch('requestEnvironments');
return Api.environments(state.projectId)
.then(res => {
dispatch('receiveEnvironmentsSuccess', prepareEnvironments(res.data));
})
.catch(() => {
createFlash(__('There was an error fetching the environments information.'));
});
};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
export default (initialState = {}) =>
new Vuex.Store({
actions,
mutations,
state: {
...state(),
...initialState,
},
});
export const TOGGLE_VALUES = 'TOGGLE_VALUES';
export const VARIABLE_BEING_EDITED = 'VARIABLE_BEING_EDITED';
export const RESET_EDITING = 'RESET_EDITING';
export const CLEAR_MODAL = 'CLEAR_MODAL';
export const REQUEST_VARIABLES = 'REQUEST_VARIABLES';
export const RECEIVE_VARIABLES_SUCCESS = 'RECEIVE_VARIABLES_SUCCESS';
export const REQUEST_DELETE_VARIABLE = 'REQUEST_DELETE_VARIABLE';
export const RECEIVE_DELETE_VARIABLE_SUCCESS = 'RECEIVE_DELETE_VARIABLE_SUCCESS';
export const RECEIVE_DELETE_VARIABLE_ERROR = 'RECEIVE_DELETE_VARIABLE_ERROR';
export const REQUEST_ADD_VARIABLE = 'REQUEST_ADD_VARIABLE';
export const RECEIVE_ADD_VARIABLE_SUCCESS = 'RECEIVE_ADD_VARIABLE_SUCCESS';
export const RECEIVE_ADD_VARIABLE_ERROR = 'RECEIVE_ADD_VARIABLE_ERROR';
export const REQUEST_UPDATE_VARIABLE = 'REQUEST_UPDATE_VARIABLE';
export const RECEIVE_UPDATE_VARIABLE_SUCCESS = 'RECEIVE_UPDATE_VARIABLE_SUCCESS';
export const RECEIVE_UPDATE_VARIABLE_ERROR = 'RECEIVE_UPDATE_VARIABLE_ERROR';
export const REQUEST_ENVIRONMENTS = 'REQUEST_ENVIRONMENTS';
export const RECEIVE_ENVIRONMENTS_SUCCESS = 'RECEIVE_ENVIRONMENTS_SUCCESS';
import * as types from './mutation_types';
import { displayText } from '../constants';
export default {
[types.REQUEST_VARIABLES](state) {
state.isLoading = true;
},
[types.RECEIVE_VARIABLES_SUCCESS](state, variables) {
state.isLoading = false;
state.variables = variables;
},
[types.REQUEST_DELETE_VARIABLE](state) {
state.isDeleting = true;
},
[types.RECEIVE_DELETE_VARIABLE_SUCCESS](state) {
state.isDeleting = false;
},
[types.RECEIVE_DELETE_VARIABLE_ERROR](state, error) {
state.isDeleting = false;
state.error = error;
},
[types.REQUEST_ADD_VARIABLE](state) {
state.isLoading = true;
},
[types.RECEIVE_ADD_VARIABLE_SUCCESS](state) {
state.isLoading = false;
},
[types.RECEIVE_ADD_VARIABLE_ERROR](state, error) {
state.isLoading = false;
state.error = error;
},
[types.REQUEST_UPDATE_VARIABLE](state) {
state.isLoading = true;
},
[types.RECEIVE_UPDATE_VARIABLE_SUCCESS](state) {
state.isLoading = false;
},
[types.RECEIVE_UPDATE_VARIABLE_ERROR](state, error) {
state.isLoading = false;
state.error = error;
},
[types.TOGGLE_VALUES](state, valueState) {
state.valuesHidden = valueState;
},
[types.REQUEST_ENVIRONMENTS](state) {
state.isLoading = true;
},
[types.RECEIVE_ENVIRONMENTS_SUCCESS](state, environments) {
state.isLoading = false;
state.environments = environments;
state.environments.unshift(displayText.allEnvironmentsText);
},
[types.VARIABLE_BEING_EDITED](state, variable) {
state.variableBeingEdited = variable;
},
[types.CLEAR_MODAL](state) {
state.variable = {
variable_type: displayText.variableText,
key: '',
secret_value: '',
protected: false,
masked: false,
environment_scope: displayText.allEnvironmentsText,
};
},
[types.RESET_EDITING](state) {
state.variableBeingEdited = null;
state.showInputValue = false;
},
};
import { displayText } from '../constants';
export default () => ({
endpoint: null,
projectId: null,
isGroup: null,
maskableRegex: null,
isLoading: false,
isDeleting: false,
variable: {
variable_type: displayText.variableText,
key: '',
secret_value: '',
protected: false,
masked: false,
environment_scope: displayText.allEnvironmentsText,
},
variables: null,
valuesHidden: true,
error: null,
environments: [],
typeOptions: [displayText.variableText, displayText.fileText],
variableBeingEdited: null,
});
import { cloneDeep } from 'lodash';
import { displayText, types } from '../constants';
const variableTypeHandler = type =>
type === displayText.variableText ? types.variableType : types.fileType;
export const prepareDataForDisplay = variables => {
const variablesToDisplay = [];
variables.forEach(variable => {
const variableCopy = variable;
if (variableCopy.variable_type === types.variableType) {
variableCopy.variable_type = displayText.variableText;
} else {
variableCopy.variable_type = displayText.fileText;
}
variableCopy.secret_value = variableCopy.value;
if (variableCopy.environment_scope === types.allEnvironmentsType) {
variableCopy.environment_scope = displayText.allEnvironmentsText;
}
variablesToDisplay.push(variableCopy);
});
return variablesToDisplay;
};
export const prepareDataForApi = (variable, destroy = false) => {
const variableCopy = cloneDeep(variable);
variableCopy.protected = variableCopy.protected.toString();
variableCopy.masked = variableCopy.masked.toString();
variableCopy.variable_type = variableTypeHandler(variableCopy.variable_type);
if (variableCopy.environment_scope === displayText.allEnvironmentsText) {
variableCopy.environment_scope = types.allEnvironmentsType;
}
if (destroy) {
// eslint-disable-next-line
variableCopy._destroy = destroy;
}
return variableCopy;
};
export const prepareEnvironments = environments => environments.map(e => e.name);
Loading
Loading
@@ -255,6 +255,8 @@ export default class Clusters {
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data));
eventHub.$on('setIngressModSecurityEnabled', data => this.setIngressModSecurityEnabled(data));
eventHub.$on('resetIngressModSecurityEnabled', id => this.resetIngressModSecurityEnabled(id));
// Add event listener to all the banner close buttons
this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
Loading
Loading
@@ -268,6 +270,8 @@ export default class Clusters {
eventHub.$off('setKnativeHostname');
eventHub.$off('setCrossplaneProviderStack');
eventHub.$off('uninstallApplication');
eventHub.$off('setIngressModSecurityEnabled');
eventHub.$off('resetIngressModSecurityEnabled');
}
 
initPolling(method, successCallback, errorCallback) {
Loading
Loading
@@ -313,10 +317,13 @@ export default class Clusters {
 
this.checkForNewInstalls(prevApplicationMap, this.store.state.applications);
this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
this.toggleIngressDomainHelpText(
prevApplicationMap[INGRESS],
this.store.state.applications[INGRESS],
);
if (this.ingressDomainHelpText) {
this.toggleIngressDomainHelpText(
prevApplicationMap[INGRESS],
this.store.state.applications[INGRESS],
);
}
}
 
showToken() {
Loading
Loading
@@ -513,6 +520,15 @@ export default class Clusters {
this.store.updateAppProperty(appId, 'validationError', null);
}
 
setIngressModSecurityEnabled({ id, modSecurityEnabled }) {
this.store.updateAppProperty(id, 'isEditingModSecurityEnabled', true);
this.store.updateAppProperty(id, 'modsecurity_enabled', modSecurityEnabled);
}
resetIngressModSecurityEnabled(id) {
this.store.updateAppProperty(id, 'isEditingModSecurityEnabled', false);
}
destroy() {
this.destroyed = true;
 
Loading
Loading
Loading
Loading
@@ -21,6 +21,7 @@ import KnativeDomainEditor from './knative_domain_editor.vue';
import { CLUSTER_TYPE, PROVIDER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants';
import eventHub from '~/clusters/event_hub';
import CrossplaneProviderStack from './crossplane_provider_stack.vue';
import IngressModsecuritySettings from './ingress_modsecurity_settings.vue';
 
export default {
components: {
Loading
Loading
@@ -29,6 +30,7 @@ export default {
GlLoadingIcon,
KnativeDomainEditor,
CrossplaneProviderStack,
IngressModsecuritySettings,
},
props: {
type: {
Loading
Loading
@@ -117,9 +119,6 @@ export default {
ingressInstalled() {
return this.applications.ingress.status === APPLICATION_STATUS.INSTALLED;
},
ingressEnableModsecurity() {
return this.applications.ingress.modsecurity_enabled;
},
ingressExternalEndpoint() {
return this.applications.ingress.externalIp || this.applications.ingress.externalHostname;
},
Loading
Loading
@@ -129,18 +128,6 @@ export default {
crossplaneInstalled() {
return this.applications.crossplane.status === APPLICATION_STATUS.INSTALLED;
},
ingressModSecurityDescription() {
const escapedUrl = _.escape(this.ingressModSecurityHelpPath);
return sprintf(
s__('ClusterIntegration|Learn more about %{startLink}ModSecurity%{endLink}'),
{
startLink: `<a href="${escapedUrl}" target="_blank" rel="noopener noreferrer">`,
endLink: '</a>',
},
false,
);
},
ingressDescription() {
return sprintf(
_.escape(
Loading
Loading
@@ -241,6 +228,9 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
}
return null;
},
ingress() {
return this.applications.ingress;
},
},
created() {
this.helmInstallIllustration = helmInstallIllustration;
Loading
Loading
@@ -270,7 +260,6 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
 
<template>
<section id="cluster-applications">
<h4>{{ s__('ClusterIntegration|Applications') }}</h4>
<p class="append-bottom-0">
{{
s__(`ClusterIntegration|Choose which applications to install on your Kubernetes cluster.
Loading
Loading
@@ -329,6 +318,7 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
:uninstall-successful="applications.ingress.uninstallSuccessful"
:uninstall-failed="applications.ingress.uninstallFailed"
:disabled="!helmInstalled"
:updateable="false"
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
>
<div slot="description">
Loading
Loading
@@ -340,25 +330,10 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
}}
</p>
 
<template>
<div class="form-group">
<div class="form-check form-check-inline">
<input
v-model="applications.ingress.modsecurity_enabled"
:disabled="ingressInstalled"
type="checkbox"
autocomplete="off"
class="form-check-input"
/>
<label class="form-check-label label-bold" for="ingress-enable-modsecurity">
{{ s__('ClusterIntegration|Enable Web Application Firewall') }}
</label>
</div>
<p class="form-text text-muted">
<strong v-html="ingressModSecurityDescription"></strong>
</p>
</div>
</template>
<ingress-modsecurity-settings
:ingress="ingress"
:ingress-mod-security-help-path="ingressModSecurityHelpPath"
/>
 
<template v-if="ingressInstalled">
<div class="form-group">
Loading
Loading
<script>
import _ from 'lodash';
import { __ } from '../../locale';
import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants';
import { GlAlert, GlSprintf, GlLink, GlToggle, GlButton } from '@gitlab/ui';
import eventHub from '~/clusters/event_hub';
import modSecurityLogo from 'images/cluster_app_logos/modsecurity.png';
const { UPDATING, UNINSTALLING, INSTALLING, INSTALLED, UPDATED } = APPLICATION_STATUS;
export default {
title: 'ModSecurity Web Application Firewall',
modsecurityUrl: 'https://modsecurity.org/about.html',
components: {
GlAlert,
GlSprintf,
GlLink,
GlToggle,
GlButton,
},
props: {
ingress: {
type: Object,
required: true,
},
ingressModSecurityHelpPath: {
type: String,
required: false,
default: '',
},
},
data: () => ({
modSecurityLogo,
hasValueChanged: false,
}),
computed: {
modSecurityEnabled: {
get() {
return this.ingress.modsecurity_enabled;
},
set(isEnabled) {
eventHub.$emit('setIngressModSecurityEnabled', {
id: INGRESS,
modSecurityEnabled: isEnabled,
});
if (this.hasValueChanged) {
this.resetStatus();
} else {
this.hasValueChanged = true;
}
},
},
ingressModSecurityDescription() {
return _.escape(this.ingressModSecurityHelpPath);
},
saving() {
return [UPDATING].includes(this.ingress.status);
},
saveButtonDisabled() {
return [UNINSTALLING, UPDATING, INSTALLING].includes(this.ingress.status);
},
saveButtonLabel() {
return this.saving ? __('Saving') : __('Save changes');
},
/**
* Returns true either when:
* - The application is getting updated.
* - The user has changed some of the settings for an application which is
* neither getting installed nor updated.
*/
showButtons() {
return (
this.saving || (this.hasValueChanged && [INSTALLED, UPDATED].includes(this.ingress.status))
);
},
},
methods: {
updateApplication() {
eventHub.$emit('updateApplication', {
id: INGRESS,
params: { modsecurity_enabled: this.ingress.modsecurity_enabled },
});
this.resetStatus();
},
resetStatus() {
eventHub.$emit('resetIngressModSecurityEnabled', INGRESS);
this.hasValueChanged = false;
},
},
};
</script>
<template>
<div>
<gl-alert
v-if="ingress.updateFailed"
class="mb-3"
variant="danger"
:dismissible="false"
@dismiss="alert = null"
>
{{
s__(
'ClusterIntegration|Something went wrong while trying to save your settings. Please try again.',
)
}}
</gl-alert>
<div class="gl-responsive-table-row-layout" role="row">
<div class="table-section append-right-8 section-align-top" role="gridcell">
<img
:src="modSecurityLogo"
:alt="`${$options.title} logo`"
class="cluster-application-logo avatar s40"
/>
</div>
<div class="table-section section-wrap" role="gridcell">
<strong>
<gl-link :href="$options.modsecurityUrl" target="_blank">{{ $options.title }} </gl-link>
</strong>
<div class="form-group">
<p class="form-text text-muted">
<strong>
<gl-sprintf
:message="
s__(
'ClusterIntegration|Real-time web application monitoring, logging and access control. %{linkStart}More information%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link :href="ingressModSecurityDescription" target="_blank"
>{{ content }}
</gl-link>
</template>
</gl-sprintf>
</strong>
</p>
<div class="form-check form-check-inline mt-3">
<gl-toggle
v-model="modSecurityEnabled"
:label-on="__('Enabled')"
:label-off="__('Disabled')"
:disabled="saveButtonDisabled"
label-position="right"
/>
</div>
<div v-if="showButtons">
<gl-button
class="btn-success inline mr-1"
:loading="saving"
:disabled="saveButtonDisabled"
@click="updateApplication"
>
{{ saveButtonLabel }}
</gl-button>
<gl-button :disabled="saveButtonDisabled" @click="resetStatus">
{{ __('Cancel') }}
</gl-button>
</div>
</div>
</div>
</div>
</div>
</template>
Loading
Loading
@@ -12,6 +12,7 @@ import {
INSTALL_EVENT,
UPDATE_EVENT,
UNINSTALL_EVENT,
ELASTIC_STACK,
} from '../constants';
import transitionApplicationState from '../services/application_state_machine';
 
Loading
Loading
@@ -54,6 +55,8 @@ export default class ClusterStore {
modsecurity_enabled: false,
externalIp: null,
externalHostname: null,
isEditingModSecurityEnabled: false,
updateFailed: false,
},
cert_manager: {
...applicationInitialState,
Loading
Loading
@@ -208,8 +211,9 @@ export default class ClusterStore {
if (appId === INGRESS) {
this.state.applications.ingress.externalIp = serverAppEntry.external_ip;
this.state.applications.ingress.externalHostname = serverAppEntry.external_hostname;
this.state.applications.ingress.modsecurity_enabled =
serverAppEntry.modsecurity_enabled || this.state.applications.ingress.modsecurity_enabled;
if (!this.state.applications.ingress.isEditingModSecurityEnabled) {
this.state.applications.ingress.modsecurity_enabled = serverAppEntry.modsecurity_enabled;
}
} else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email;
Loading
Loading
@@ -234,6 +238,9 @@ export default class ClusterStore {
} else if (appId === RUNNER) {
this.state.applications.runner.version = version;
this.state.applications.runner.updateAvailable = updateAvailable;
} else if (appId === ELASTIC_STACK) {
this.state.applications.elastic_stack.version = version;
this.state.applications.elastic_stack.updateAvailable = updateAvailable;
}
});
}
Loading
Loading
<script>
import { mapState, mapActions } from 'vuex';
import { GlTable, GlLoadingIcon, GlBadge } from '@gitlab/ui';
import tooltip from '~/vue_shared/directives/tooltip';
import { CLUSTER_TYPES, STATUSES } from '../constants';
import { __, sprintf } from '~/locale';
export default {
components: {
GlTable,
GlLoadingIcon,
GlBadge,
},
directives: {
tooltip,
},
fields: [
{
key: 'name',
label: __('Kubernetes cluster'),
},
{
key: 'environmentScope',
label: __('Environment scope'),
},
{
key: 'size',
label: __('Size'),
},
{
key: 'cpu',
label: __('Total cores (vCPUs)'),
},
{
key: 'memory',
label: __('Total memory (GB)'),
},
{
key: 'clusterType',
label: __('Cluster level'),
formatter: value => CLUSTER_TYPES[value],
},
],
computed: {
...mapState(['clusters', 'loading']),
},
mounted() {
// TODO - uncomment this once integrated with BE
// this.fetchClusters();
},
methods: {
...mapActions(['fetchClusters']),
statusClass(status) {
return STATUSES[status].className;
},
statusTitle(status) {
const { title } = STATUSES[status];
return sprintf(__('Status: %{title}'), { title }, false);
},
},
};
</script>
<template>
<gl-loading-icon v-if="loading" size="md" class="mt-3" />
<gl-table
v-else
:items="clusters"
:fields="$options.fields"
stacked="md"
variant="light"
class="qa-clusters-table"
>
<template #cell(name)="{ item }">
<div class="d-flex flex-row-reverse flex-md-row js-status">
{{ item.name }}
<gl-loading-icon
v-if="item.status === 'deleting'"
v-tooltip
:title="statusTitle(item.status)"
size="sm"
class="mr-2 ml-md-2"
/>
<div
v-else
v-tooltip
class="cluster-status-indicator rounded-circle align-self-center gl-w-8 gl-h-8 mr-2 ml-md-2"
:class="statusClass(item.status)"
:title="statusTitle(item.status)"
></div>
</div>
</template>
<template #cell(clusterType)="{value}">
<gl-badge variant="light">
{{ value }}
</gl-badge>
</template>
</gl-table>
</template>
import { __ } from '~/locale';
export const CLUSTER_TYPES = {
project_type: __('Project'),
group_type: __('Group'),
instance_type: __('Instance'),
};
export const STATUSES = {
disabled: { className: 'disabled', title: __('Disabled') },
connected: { className: 'bg-success', title: __('Connected') },
unreachable: { className: 'bg-danger', title: __('Unreachable') },
authentication_failure: { className: 'bg-warning', title: __('Authentication Failure') },
deleting: { title: __('Deleting') },
};
import Vue from 'vue';
import Clusters from './components/clusters.vue';
import { createStore } from './store';
export default () => {
const entryPoint = document.querySelector('#js-clusters-list-app');
if (!entryPoint) {
return;
}
const { endpoint } = entryPoint.dataset;
// eslint-disable-next-line no-new
new Vue({
el: '#js-clusters-list-app',
store: createStore({ endpoint }),
render(createElement) {
return createElement(Clusters);
},
});
};
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import Poll from '~/lib/utils/poll';
import axios from '~/lib/utils/axios_utils';
import Visibility from 'visibilityjs';
import flash from '~/flash';
import { __ } from '~/locale';
import * as types from './mutation_types';
export const fetchClusters = ({ state, commit }) => {
const poll = new Poll({
resource: {
fetchClusters: endpoint => axios.get(endpoint),
},
data: state.endpoint,
method: 'fetchClusters',
successCallback: ({ data }) => {
commit(types.SET_CLUSTERS_DATA, convertObjectPropsToCamelCase(data, { deep: true }));
commit(types.SET_LOADING_STATE, false);
},
errorCallback: () => flash(__('An error occurred while loading clusters')),
});
if (!Visibility.hidden()) {
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import mutations from './mutations';
import * as actions from './actions';
Vue.use(Vuex);
export const createStore = initialState =>
new Vuex.Store({
actions,
mutations,
state: state(initialState),
});
export default createStore;
export const SET_CLUSTERS_DATA = 'SET_CLUSTERS_DATA';
export const SET_LOADING_STATE = 'SET_LOADING_STATE';
import * as types from './mutation_types';
export default {
[types.SET_LOADING_STATE](state, value) {
state.loading = value;
},
[types.SET_CLUSTERS_DATA](state, clusters) {
Object.assign(state, {
clusters,
});
},
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment