diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
index 9d4574c4590200c5212d8104b359272ee6125af8..6aafa85e99a720bb0d137944f1f7f23ac004605a 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -8,3 +8,5 @@
.col-lg-9
= render 'projects/services/prometheus/metrics', project: @project
+
+= render_if_exists 'projects/services/prometheus/external_alerts', project: @project
diff --git a/ee/app/assets/javascripts/pages/projects/services/edit/index.js b/ee/app/assets/javascripts/pages/projects/services/edit/index.js
index 42139b804ffd9cc6b161db5d251941359f81b59d..9e1a16174b417ec7c3fa92659bb2b76c84bda376 100644
--- a/ee/app/assets/javascripts/pages/projects/services/edit/index.js
+++ b/ee/app/assets/javascripts/pages/projects/services/edit/index.js
@@ -1,5 +1,6 @@
import IntegrationSettingsForm from '~/integrations/integration_settings_form';
import PrometheusMetrics from 'ee/prometheus_metrics/prometheus_metrics';
+import PrometheusAlerts from 'ee/prometheus_alerts';
document.addEventListener('DOMContentLoaded', () => {
const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
@@ -14,4 +15,6 @@ document.addEventListener('DOMContentLoaded', () => {
prometheusMetrics.setNoIntegrationActiveState();
}
}
+
+ PrometheusAlerts();
});
diff --git a/ee/app/assets/javascripts/prometheus_alerts/components/reset_key.vue b/ee/app/assets/javascripts/prometheus_alerts/components/reset_key.vue
new file mode 100644
index 0000000000000000000000000000000000000000..72ae4d7585da6af36d0462098a898a118dbbe9ac
--- /dev/null
+++ b/ee/app/assets/javascripts/prometheus_alerts/components/reset_key.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+ {{ __('Alerts') }}
+
+
+ {{ __('Receive alerts from manually configured Prometheus servers.') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ __(
+ 'Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key.',
+ )
+ }}
+
+ {{
+ __('Reset key')
+ }}
+
+
{{
+ __('Generate key')
+ }}
+
+
+
diff --git a/ee/app/assets/javascripts/prometheus_alerts/index.js b/ee/app/assets/javascripts/prometheus_alerts/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..10ff65dfddde8cc5af85a18b6aee5403f01eb8db
--- /dev/null
+++ b/ee/app/assets/javascripts/prometheus_alerts/index.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import ResetKey from './components/reset_key.vue';
+
+export default () => {
+ const el = document.querySelector('#js-settings-prometheus-alerts');
+
+ const { authorizationKey, changeKeyUrl, notifyUrl, learnMoreUrl } = el.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ render(createElement) {
+ return createElement(ResetKey, {
+ props: {
+ initialAuthorizationKey: authorizationKey,
+ changeKeyUrl,
+ notifyUrl,
+ learnMoreUrl,
+ },
+ });
+ },
+ });
+};
diff --git a/ee/app/views/projects/services/prometheus/_external_alerts.html.haml b/ee/app/views/projects/services/prometheus/_external_alerts.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..ab2cc84ddbf2ae93ead5758c9737568be150de03
--- /dev/null
+++ b/ee/app/views/projects/services/prometheus/_external_alerts.html.haml
@@ -0,0 +1,7 @@
+- return unless can?(current_user, :read_prometheus_alerts, @project)
+
+- notify_url = notify_project_prometheus_alerts_url(@project, format: :json)
+- authorization_key = @project.alerting_setting.try(:token)
+- learn_more_url = help_page_path('administration/monitoring/prometheus/index', anchor: 'configuring-prometheus')
+
+#js-settings-prometheus-alerts{ data: { notify_url: notify_url, authorization_key: authorization_key, change_key_url: reset_alerting_token_project_settings_operations_path(@project), learn_more_url: learn_more_url } }
diff --git a/ee/spec/javascripts/prometheus_alerts/components/reset_key_spec.js b/ee/spec/javascripts/prometheus_alerts/components/reset_key_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..87c006bc4cc232c617c140dd9e0db3a4c4426018
--- /dev/null
+++ b/ee/spec/javascripts/prometheus_alerts/components/reset_key_spec.js
@@ -0,0 +1,103 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import ResetKey from 'ee/prometheus_alerts/components/reset_key.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { GlModal } from '@gitlab/ui';
+
+describe('ResetKey', () => {
+ let Component;
+ let mock;
+ let vm;
+ const localVue = createLocalVue();
+
+ const propsData = {
+ initialAuthorizationKey: 'abcd1234',
+ changeKeyUrl: '/updateKeyUrl',
+ notifyUrl: '/root/autodevops-deploy/prometheus/alerts/notify.json',
+ learnMoreUrl: '/learnMore',
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ Component = localVue.extend(ResetKey);
+ setFixtures('
');
+ });
+
+ afterEach(() => {
+ mock.restore();
+ vm.destroy();
+ });
+
+ describe('authorization key exists', () => {
+ beforeEach(() => {
+ propsData.initialAuthorizationKey = 'abcd1234';
+ vm = shallowMount(Component, {
+ propsData,
+ });
+ });
+
+ it('shows fields and buttons', () => {
+ expect(vm.find('#notify-url').attributes('value')).toEqual(propsData.notifyUrl);
+ expect(vm.find('#authorization-key').attributes('value')).toEqual(
+ propsData.initialAuthorizationKey,
+ );
+
+ expect(vm.findAll(ClipboardButton).length).toBe(2);
+ expect(vm.find('.js-reset-auth-key').text()).toEqual('Reset key');
+ });
+
+ it('reset updates key', done => {
+ mock.onPost(propsData.changeKeyUrl).replyOnce(200, { token: 'newToken' });
+
+ vm.find(GlModal).vm.$emit('ok');
+
+ setTimeout(() => {
+ expect(vm.find('#authorization-key').attributes('value')).toEqual('newToken');
+ done();
+ });
+ });
+
+ it('reset key failure shows error', done => {
+ mock.onPost(propsData.changeKeyUrl).replyOnce(500);
+
+ vm.find(GlModal).vm.$emit('ok');
+
+ setTimeout(() => {
+ expect(vm.find('#authorization-key').attributes('value')).toEqual(
+ propsData.initialAuthorizationKey,
+ );
+
+ expect(document.querySelector('.flash-container').innerText.trim()).toEqual(
+ 'Failed to reset key. Please try again.',
+ );
+ done();
+ });
+ });
+ });
+
+ describe('authorization key has not been set', () => {
+ beforeEach(() => {
+ propsData.initialAuthorizationKey = '';
+ vm = shallowMount(Component, {
+ propsData,
+ });
+ });
+
+ it('shows Generate Key button', () => {
+ expect(vm.find('.js-reset-auth-key').text()).toEqual('Generate key');
+ expect(vm.find('#authorization-key').attributes('value')).toEqual('');
+ });
+
+ it('Generate key button triggers key change', done => {
+ mock.onPost(propsData.changeKeyUrl).replyOnce(200, { token: 'newToken' });
+
+ vm.find('.js-reset-auth-key').vm.$emit('click');
+
+ setTimeout(() => {
+ expect(vm.find('#authorization-key').attributes('value')).toEqual('newToken');
+ done();
+ });
+ });
+ });
+});
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a666a28fae071b6502a1d75c625a23b6fa5573ab..b63ec541f69079e956f8ebf40d3a954a1c15284c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -607,6 +607,9 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
+msgid "Alerts"
+msgstr ""
+
msgid "All"
msgstr ""
@@ -1051,6 +1054,9 @@ msgstr ""
msgid "Authorization code:"
msgstr ""
+msgid "Authorization key"
+msgstr ""
+
msgid "Authorization was granted by entering your username and password in the application."
msgstr ""
@@ -3797,6 +3803,9 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to reset key. Please try again."
+msgstr ""
+
msgid "Failed to signing using smartcard authentication"
msgstr ""
@@ -4145,6 +4154,9 @@ msgstr ""
msgid "Generate a default set of labels"
msgstr ""
+msgid "Generate key"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -7607,6 +7619,9 @@ msgstr ""
msgid "Real-time features"
msgstr ""
+msgid "Receive alerts from manually configured Prometheus servers."
+msgstr ""
+
msgid "Recent searches"
msgstr ""
@@ -7822,12 +7837,24 @@ msgstr ""
msgid "Resend invite"
msgstr ""
+msgid "Reset authorization key"
+msgstr ""
+
+msgid "Reset authorization key?"
+msgstr ""
+
msgid "Reset health check access token"
msgstr ""
+msgid "Reset key"
+msgstr ""
+
msgid "Reset runners registration token"
msgstr ""
+msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
+msgstr ""
+
msgid "Resolve all discussions in new issue"
msgstr ""
@@ -9777,6 +9804,9 @@ msgstr ""
msgid "To preserve performance only %{display_size} of %{real_size} files are displayed."
msgstr ""
+msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
+msgstr ""
+
msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
msgstr ""
@@ -9921,6 +9951,9 @@ msgstr ""
msgid "Type"
msgstr ""
+msgid "URL"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""