summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/blob/balsamiq_viewer.js4
-rw-r--r--app/assets/javascripts/blob/notebook_viewer.js2
-rw-r--r--app/assets/javascripts/blob/pdf_viewer.js2
-rw-r--r--app/assets/javascripts/blob/sketch_viewer.js4
-rw-r--r--app/assets/javascripts/blob/stl_viewer.js4
-rw-r--r--app/assets/javascripts/blob/viewer/index.js33
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js4
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue6
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue135
-rw-r--r--app/assets/javascripts/clusters/constants.js1
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js10
-rw-r--r--app/assets/javascripts/u2f/authenticate.js26
-rw-r--r--app/assets/javascripts/u2f/register.js27
-rw-r--r--app/assets/javascripts/u2f/util.js42
-rw-r--r--app/assets/javascripts/vue_shared/components/clipboard_button.vue7
-rw-r--r--app/controllers/application_controller.rb12
-rw-r--r--app/controllers/import/bitbucket_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb22
-rw-r--r--app/controllers/projects/clusters_controller.rb5
-rw-r--r--app/controllers/sessions_controller.rb2
-rw-r--r--app/helpers/application_settings_helper.rb2
-rw-r--r--app/helpers/auth_helper.rb6
-rw-r--r--app/helpers/profiles_helper.rb2
-rw-r--r--app/helpers/u2f_helper.rb5
-rw-r--r--app/models/clusters/applications/ingress.rb19
-rw-r--r--app/models/clusters/concerns/application_core.rb5
-rw-r--r--app/models/cycle_analytics.rb6
-rw-r--r--app/models/identity.rb6
-rw-r--r--app/models/repository.rb10
-rw-r--r--app/models/user.rb2
-rw-r--r--app/models/user_synced_attributes_metadata.rb2
-rw-r--r--app/serializers/analytics_stage_entity.rb3
-rw-r--r--app/serializers/cluster_application_entity.rb1
-rw-r--r--app/services/clusters/applications/check_ingress_ip_address_service.rb36
-rw-r--r--app/views/admin/application_settings/_form.html.haml2
-rw-r--r--app/views/admin/identities/_form.html.haml2
-rw-r--r--app/views/admin/identities/_identity.html.haml2
-rw-r--r--app/views/devise/sessions/two_factor.html.haml4
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml2
-rw-r--r--app/views/projects/blob/_viewer.html.haml3
-rw-r--r--app/views/projects/blob/viewers/_balsamiq.html.haml3
-rw-r--r--app/views/projects/blob/viewers/_notebook.html.haml4
-rw-r--r--app/views/projects/blob/viewers/_pdf.html.haml4
-rw-r--r--app/views/projects/blob/viewers/_sketch.html.haml4
-rw-r--r--app/views/projects/blob/viewers/_stl.html.haml3
-rw-r--r--app/views/projects/clusters/show.html.haml1
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/cluster_wait_for_ingress_ip_address_worker.rb11
48 files changed, 362 insertions, 139 deletions
diff --git a/app/assets/javascripts/blob/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq_viewer.js
index 062577af385..06ef86ecb77 100644
--- a/app/assets/javascripts/blob/balsamiq_viewer.js
+++ b/app/assets/javascripts/blob/balsamiq_viewer.js
@@ -7,7 +7,7 @@ function onError() {
return flash;
}
-function loadBalsamiqFile() {
+export default function loadBalsamiqFile() {
const viewer = document.getElementById('js-balsamiq-viewer');
if (!(viewer instanceof Element)) return;
@@ -17,5 +17,3 @@ function loadBalsamiqFile() {
const balsamiqViewer = new BalsamiqViewer(viewer);
balsamiqViewer.loadFile(endpoint).catch(onError);
}
-
-$(loadBalsamiqFile);
diff --git a/app/assets/javascripts/blob/notebook_viewer.js b/app/assets/javascripts/blob/notebook_viewer.js
index b7a0a195a92..226ae69893e 100644
--- a/app/assets/javascripts/blob/notebook_viewer.js
+++ b/app/assets/javascripts/blob/notebook_viewer.js
@@ -1,3 +1,3 @@
import renderNotebook from './notebook';
-document.addEventListener('DOMContentLoaded', renderNotebook);
+export default renderNotebook;
diff --git a/app/assets/javascripts/blob/pdf_viewer.js b/app/assets/javascripts/blob/pdf_viewer.js
index 91abe9dd699..cabbb396ea7 100644
--- a/app/assets/javascripts/blob/pdf_viewer.js
+++ b/app/assets/javascripts/blob/pdf_viewer.js
@@ -1,3 +1,3 @@
import renderPDF from './pdf';
-document.addEventListener('DOMContentLoaded', renderPDF);
+export default renderPDF;
diff --git a/app/assets/javascripts/blob/sketch_viewer.js b/app/assets/javascripts/blob/sketch_viewer.js
index 0640dd26855..2c1c6339fdb 100644
--- a/app/assets/javascripts/blob/sketch_viewer.js
+++ b/app/assets/javascripts/blob/sketch_viewer.js
@@ -1,8 +1,8 @@
/* eslint-disable no-new */
import SketchLoader from './sketch';
-document.addEventListener('DOMContentLoaded', () => {
+export default () => {
const el = document.getElementById('js-sketch-viewer');
new SketchLoader(el);
-});
+};
diff --git a/app/assets/javascripts/blob/stl_viewer.js b/app/assets/javascripts/blob/stl_viewer.js
index f611c4fe640..63236b6477f 100644
--- a/app/assets/javascripts/blob/stl_viewer.js
+++ b/app/assets/javascripts/blob/stl_viewer.js
@@ -1,6 +1,6 @@
import Renderer from './3d_viewer';
-document.addEventListener('DOMContentLoaded', () => {
+export default () => {
const viewer = new Renderer(document.getElementById('js-stl-viewer'));
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => {
@@ -16,4 +16,4 @@ document.addEventListener('DOMContentLoaded', () => {
viewer.changeObjectMaterials(target.dataset.type);
});
});
-});
+};
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js
index 612f604e725..92ea91c45a8 100644
--- a/app/assets/javascripts/blob/viewer/index.js
+++ b/app/assets/javascripts/blob/viewer/index.js
@@ -5,6 +5,7 @@ import axios from '../../lib/utils/axios_utils';
export default class BlobViewer {
constructor() {
BlobViewer.initAuxiliaryViewer();
+ BlobViewer.initRichViewer();
this.initMainViewers();
}
@@ -16,6 +17,38 @@ export default class BlobViewer {
BlobViewer.loadViewer(auxiliaryViewer);
}
+ static initRichViewer() {
+ const viewer = document.querySelector('.blob-viewer[data-type="rich"]');
+ if (!viewer || !viewer.dataset.richType) return;
+
+ const initViewer = promise => promise
+ .then(module => module.default(viewer))
+ .catch((error) => {
+ Flash('Error loading file viewer.');
+ throw error;
+ });
+
+ switch (viewer.dataset.richType) {
+ case 'balsamiq':
+ initViewer(import(/* webpackChunkName: 'balsamiq_viewer' */ '../balsamiq_viewer'));
+ break;
+ case 'notebook':
+ initViewer(import(/* webpackChunkName: 'notebook_viewer' */ '../notebook_viewer'));
+ break;
+ case 'pdf':
+ initViewer(import(/* webpackChunkName: 'pdf_viewer' */ '../pdf_viewer'));
+ break;
+ case 'sketch':
+ initViewer(import(/* webpackChunkName: 'sketch_viewer' */ '../sketch_viewer'));
+ break;
+ case 'stl':
+ initViewer(import(/* webpackChunkName: 'stl_viewer' */ '../stl_viewer'));
+ break;
+ default:
+ break;
+ }
+ }
+
initMainViewers() {
this.$fileHolder = $('.file-holder');
if (!this.$fileHolder.length) return;
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index b070a59cf15..01aec4f36af 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -37,10 +37,11 @@ export default class Clusters {
clusterStatusReason,
helpPath,
ingressHelpPath,
+ ingressDnsHelpPath,
} = document.querySelector('.js-edit-cluster-form').dataset;
this.store = new ClustersStore();
- this.store.setHelpPaths(helpPath, ingressHelpPath);
+ this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
@@ -98,6 +99,7 @@ export default class Clusters {
helpPath: this.state.helpPath,
ingressHelpPath: this.state.ingressHelpPath,
managePrometheusPath: this.state.managePrometheusPath,
+ ingressDnsHelpPath: this.state.ingressDnsHelpPath,
},
});
},
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index 50e35bbbba5..c2a35341eb2 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -36,10 +36,6 @@
type: String,
required: false,
},
- description: {
- type: String,
- required: true,
- },
status: {
type: String,
required: false,
@@ -148,7 +144,7 @@
class="table-section section-wrap"
role="gridcell"
>
- <div v-html="description"></div>
+ <slot name="description"></slot>
</div>
<div
class="table-section table-button-footer section-align-top"
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 978881a4831..35618398468 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -2,10 +2,16 @@
import _ from 'underscore';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
+ import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
+ import {
+ APPLICATION_INSTALLED,
+ INGRESS,
+ } from '../constants';
export default {
components: {
applicationRow,
+ clipboardButton,
},
props: {
applications: {
@@ -23,6 +29,11 @@
required: false,
default: '',
},
+ ingressDnsHelpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
managePrometheusPath: {
type: String,
required: false,
@@ -43,19 +54,16 @@
false,
);
},
- helmTillerDescription() {
- return _.escape(s__(
- `ClusterIntegration|Helm streamlines installing and managing Kubernetes applications.
- Tiller runs inside of your Kubernetes Cluster, and manages
- releases of your charts.`,
- ));
+ ingressId() {
+ return INGRESS;
+ },
+ ingressInstalled() {
+ return this.applications.ingress.status === APPLICATION_INSTALLED;
+ },
+ ingressExternalIp() {
+ return this.applications.ingress.externalIp;
},
ingressDescription() {
- const descriptionParagraph = _.escape(s__(
- `ClusterIntegration|Ingress gives you a way to route requests to services based on the
- request host or path, centralizing a number of services into a single entrypoint.`,
- ));
-
const extraCostParagraph = sprintf(
_.escape(s__(
`ClusterIntegration|%{boldNotice} This will add some extra resources
@@ -84,9 +92,6 @@
return `
<p>
- ${descriptionParagraph}
- </p>
- <p>
${extraCostParagraph}
</p>
<p class="settings-message append-bottom-0">
@@ -136,33 +141,121 @@
id="helm"
:title="applications.helm.title"
title-link="https://docs.helm.sh/"
- :description="helmTillerDescription"
:status="applications.helm.status"
:status-reason="applications.helm.statusReason"
:request-status="applications.helm.requestStatus"
:request-reason="applications.helm.requestReason"
- />
+ >
+ <div slot="description">
+ {{ s__(`ClusterIntegration|Helm streamlines installing
+ and managing Kubernetes applications.
+ Tiller runs inside of your Kubernetes Cluster,
+ and manages releases of your charts.`) }}
+ </div>
+ </application-row>
<application-row
- id="ingress"
+ :id="ingressId"
:title="applications.ingress.title"
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
- :description="ingressDescription"
:status="applications.ingress.status"
:status-reason="applications.ingress.statusReason"
:request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason"
- />
+ >
+ <div slot="description">
+ <p>
+ {{ s__(`ClusterIntegration|Ingress gives you a way to route
+ requests to services based on the request host or path,
+ centralizing a number of services into a single entrypoint.`) }}
+ </p>
+
+ <template v-if="ingressInstalled">
+ <div class="form-group">
+ <label for="ingress-ip-address">
+ {{ s__('ClusterIntegration|Ingress IP Address') }}
+ </label>
+ <div
+ v-if="ingressExternalIp"
+ class="input-group"
+ >
+ <input
+ type="text"
+ id="ingress-ip-address"
+ class="form-control js-ip-address"
+ :value="ingressExternalIp"
+ readonly
+ />
+ <span class="input-group-btn">
+ <clipboard-button
+ :text="ingressExternalIp"
+ :title="s__('ClusterIntegration|Copy Ingress IP Address to clipboard')"
+ css-class="btn btn-default js-clipboard-btn"
+ />
+ </span>
+ </div>
+ <input
+ v-else
+ type="text"
+ class="form-control js-ip-address"
+ readonly
+ value="?"
+ />
+ </div>
+
+ <p
+ v-if="!ingressExternalIp"
+ class="settings-message js-no-ip-message"
+ >
+ {{ s__(`ClusterIntegration|The IP address is in
+ the process of being assigned. Please check your Kubernetes
+ cluster or Quotas on GKE if it takes a long time.`) }}
+
+ <a
+ :href="ingressHelpPath"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {{ __('More information') }}
+ </a>
+ </p>
+
+ <p>
+ {{ s__(`ClusterIntegration|Point a wildcard DNS to this
+ generated IP address in order to access
+ your application after it has been deployed.`) }}
+ <a
+ :href="ingressDnsHelpPath"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {{ __('More information') }}
+ </a>
+ </p>
+
+ </template>
+ <div
+ v-else
+ v-html="ingressDescription"
+ >
+ </div>
+ </div>
+ </application-row>
<application-row
id="prometheus"
:title="applications.prometheus.title"
title-link="https://prometheus.io/docs/introduction/overview/"
:manage-link="managePrometheusPath"
- :description="prometheusDescription"
:status="applications.prometheus.status"
:status-reason="applications.prometheus.statusReason"
:request-status="applications.prometheus.requestStatus"
:request-reason="applications.prometheus.requestReason"
- />
+ >
+ <div
+ slot="description"
+ v-html="prometheusDescription"
+ >
+ </div>
+ </application-row>
<!--
NOTE: Don't forget to update `clusters.scss`
min-height for this block and uncomment `application_spec` tests
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
index 93223aefff8..b7179f52bb3 100644
--- a/app/assets/javascripts/clusters/constants.js
+++ b/app/assets/javascripts/clusters/constants.js
@@ -10,3 +10,4 @@ export const APPLICATION_ERROR = 'errored';
export const REQUEST_LOADING = 'request-loading';
export const REQUEST_SUCCESS = 'request-success';
export const REQUEST_FAILURE = 'request-failure';
+export const INGRESS = 'ingress';
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index 904ee5fd475..348bbec3b25 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -1,4 +1,5 @@
import { s__ } from '../../locale';
+import { INGRESS } from '../constants';
export default class ClusterStore {
constructor() {
@@ -21,6 +22,7 @@ export default class ClusterStore {
statusReason: null,
requestStatus: null,
requestReason: null,
+ externalIp: null,
},
runner: {
title: s__('ClusterIntegration|GitLab Runner'),
@@ -40,9 +42,10 @@ export default class ClusterStore {
};
}
- setHelpPaths(helpPath, ingressHelpPath) {
+ setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath) {
this.state.helpPath = helpPath;
this.state.ingressHelpPath = ingressHelpPath;
+ this.state.ingressDnsHelpPath = ingressDnsHelpPath;
}
setManagePrometheusPath(managePrometheusPath) {
@@ -64,6 +67,7 @@ export default class ClusterStore {
updateStateFromServer(serverState = {}) {
this.state.status = serverState.status;
this.state.statusReason = serverState.status_reason;
+
serverState.applications.forEach((serverAppEntry) => {
const {
name: appId,
@@ -76,6 +80,10 @@ export default class ClusterStore {
status,
statusReason,
};
+
+ if (appId === INGRESS) {
+ this.state.applications.ingress.externalIp = serverAppEntry.external_ip;
+ }
});
}
}
diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js
index a3cc04e35fe..fd42f9c3baa 100644
--- a/app/assets/javascripts/u2f/authenticate.js
+++ b/app/assets/javascripts/u2f/authenticate.js
@@ -1,7 +1,5 @@
-/* eslint-disable func-names, wrap-iife */
-/* global u2f */
import _ from 'underscore';
-import isU2FSupported from './util';
+import importU2FLibrary from './util';
import U2FError from './error';
// Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
@@ -10,6 +8,7 @@ import U2FError from './error';
// State Flow #2: setup -> in_progress -> error -> setup
export default class U2FAuthenticate {
constructor(container, form, u2fParams, fallbackButton, fallbackUI) {
+ this.u2fUtils = null;
this.container = container;
this.renderNotSupported = this.renderNotSupported.bind(this);
this.renderAuthenticated = this.renderAuthenticated.bind(this);
@@ -50,22 +49,23 @@ export default class U2FAuthenticate {
}
start() {
- if (isU2FSupported()) {
- return this.renderInProgress();
- }
- return this.renderNotSupported();
+ return importU2FLibrary()
+ .then((utils) => {
+ this.u2fUtils = utils;
+ this.renderInProgress();
+ })
+ .catch(() => this.renderNotSupported());
}
authenticate() {
- return u2f.sign(this.appId, this.challenge, this.signRequests, (function (_this) {
- return function (response) {
+ return this.u2fUtils.sign(this.appId, this.challenge, this.signRequests,
+ (response) => {
if (response.errorCode) {
const error = new U2FError(response.errorCode, 'authenticate');
- return _this.renderError(error);
+ return this.renderError(error);
}
- return _this.renderAuthenticated(JSON.stringify(response));
- };
- })(this), 10);
+ return this.renderAuthenticated(JSON.stringify(response));
+ }, 10);
}
renderTemplate(name, params) {
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
index cc3f02e75f6..869fac658e8 100644
--- a/app/assets/javascripts/u2f/register.js
+++ b/app/assets/javascripts/u2f/register.js
@@ -1,8 +1,5 @@
-/* eslint-disable func-names, wrap-iife */
-/* global u2f */
-
import _ from 'underscore';
-import isU2FSupported from './util';
+import importU2FLibrary from './util';
import U2FError from './error';
// Register U2F (universal 2nd factor) devices for users to authenticate with.
@@ -11,6 +8,7 @@ import U2FError from './error';
// State Flow #2: setup -> in_progress -> error -> setup
export default class U2FRegister {
constructor(container, u2fParams) {
+ this.u2fUtils = null;
this.container = container;
this.renderNotSupported = this.renderNotSupported.bind(this);
this.renderRegistered = this.renderRegistered.bind(this);
@@ -34,22 +32,23 @@ export default class U2FRegister {
}
start() {
- if (isU2FSupported()) {
- return this.renderSetup();
- }
- return this.renderNotSupported();
+ return importU2FLibrary()
+ .then((utils) => {
+ this.u2fUtils = utils;
+ this.renderSetup();
+ })
+ .catch(() => this.renderNotSupported());
}
register() {
- return u2f.register(this.appId, this.registerRequests, this.signRequests, (function (_this) {
- return function (response) {
+ return this.u2fUtils.register(this.appId, this.registerRequests, this.signRequests,
+ (response) => {
if (response.errorCode) {
const error = new U2FError(response.errorCode, 'register');
- return _this.renderError(error);
+ return this.renderError(error);
}
- return _this.renderRegistered(JSON.stringify(response));
- };
- })(this), 10);
+ return this.renderRegistered(JSON.stringify(response));
+ }, 10);
}
renderTemplate(name, params) {
diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js
index 9771ff935c2..5778f00332d 100644
--- a/app/assets/javascripts/u2f/util.js
+++ b/app/assets/javascripts/u2f/util.js
@@ -1,3 +1,41 @@
-export default function isU2FSupported() {
- return window.u2f;
+function isOpera(userAgent) {
+ return userAgent.indexOf('Opera') >= 0 || userAgent.indexOf('OPR') >= 0;
+}
+
+function getOperaVersion(userAgent) {
+ const match = userAgent.match(/OPR[^0-9]*([0-9]+)[^0-9]+/);
+ return match ? parseInt(match[1], 10) : false;
+}
+
+function isChrome(userAgent) {
+ return userAgent.indexOf('Chrom') >= 0 && !isOpera(userAgent);
+}
+
+function getChromeVersion(userAgent) {
+ const match = userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\./);
+ return match ? parseInt(match[1], 10) : false;
+}
+
+export function canInjectU2fApi(userAgent) {
+ const isSupportedChrome = isChrome(userAgent) && getChromeVersion(userAgent) >= 41;
+ const isSupportedOpera = isOpera(userAgent) && getOperaVersion(userAgent) >= 40;
+ const isMobile = (
+ userAgent.indexOf('droid') >= 0 ||
+ userAgent.indexOf('CriOS') >= 0 ||
+ /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent)
+ );
+ return (isSupportedChrome || isSupportedOpera) && !isMobile;
+}
+
+export default function importU2FLibrary() {
+ if (window.u2f) {
+ return Promise.resolve(window.u2f);
+ }
+
+ const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';
+ if (canInjectU2fApi(userAgent) || (gon && gon.test_env)) {
+ return import(/* webpackMode: "eager" */ 'vendor/u2f').then(() => window.u2f);
+ }
+
+ return Promise.reject();
}
diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
index e855ec3c098..3b6c2da1664 100644
--- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue
+++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
@@ -28,6 +28,11 @@
required: false,
default: false,
},
+ cssClass: {
+ type: String,
+ required: false,
+ default: 'btn btn-default btn-transparent btn-clipboard',
+ },
},
};
</script>
@@ -35,7 +40,7 @@
<template>
<button
type="button"
- class="btn btn-transparent btn-clipboard"
+ :class="cssClass"
:title="title"
:data-clipboard-text="text"
v-tooltip
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e6a41202f04..7f83bd10e93 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -191,7 +191,7 @@ class ApplicationController < ActionController::Base
return unless signed_in? && session[:service_tickets]
valid = session[:service_tickets].all? do |provider, ticket|
- Gitlab::OAuth::Session.valid?(provider, ticket)
+ Gitlab::Auth::OAuth::Session.valid?(provider, ticket)
end
unless valid
@@ -215,7 +215,7 @@ class ApplicationController < ActionController::Base
if current_user && current_user.requires_ldap_check?
return unless current_user.try_obtain_ldap_lease
- unless Gitlab::LDAP::Access.allowed?(current_user)
+ unless Gitlab::Auth::LDAP::Access.allowed?(current_user)
sign_out current_user
flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path
@@ -230,7 +230,7 @@ class ApplicationController < ActionController::Base
end
def gitlab_ldap_access(&block)
- Gitlab::LDAP::Access.open { |access| yield(access) }
+ Gitlab::Auth::LDAP::Access.open { |access| yield(access) }
end
# JSON for infinite scroll via Pager object
@@ -284,7 +284,7 @@ class ApplicationController < ActionController::Base
end
def github_import_configured?
- Gitlab::OAuth::Provider.enabled?(:github)
+ Gitlab::Auth::OAuth::Provider.enabled?(:github)
end
def gitlab_import_enabled?
@@ -292,7 +292,7 @@ class ApplicationController < ActionController::Base
end
def gitlab_import_configured?
- Gitlab::OAuth::Provider.enabled?(:gitlab)
+ Gitlab::Auth::OAuth::Provider.enabled?(:gitlab)
end
def bitbucket_import_enabled?
@@ -300,7 +300,7 @@ class ApplicationController < ActionController::Base
end
def bitbucket_import_configured?
- Gitlab::OAuth::Provider.enabled?(:bitbucket)
+ Gitlab::Auth::OAuth::Provider.enabled?(:bitbucket)
end
def google_code_import_enabled?
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index 13ea736688d..61d81ad8a71 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -71,7 +71,7 @@ class Import::BitbucketController < Import::BaseController
end
def provider
- Gitlab::OAuth::Provider.config_for('bitbucket')
+ Gitlab::Auth::OAuth::Provider.config_for('bitbucket')
end
def options
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 83c9a3f035e..8440945ab43 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -10,8 +10,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
- if Gitlab::LDAP::Config.enabled?
- Gitlab::LDAP::Config.available_servers.each do |server|
+ if Gitlab::Auth::LDAP::Config.enabled?
+ Gitlab::Auth::LDAP::Config.available_servers.each do |server|
define_method server['provider_name'] do
ldap
end
@@ -31,7 +31,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# We only find ourselves here
# if the authentication to LDAP was successful.
def ldap
- ldap_user = Gitlab::LDAP::User.new(oauth)
+ ldap_user = Gitlab::Auth::LDAP::User.new(oauth)
ldap_user.save if ldap_user.changed? # will also save new users
@user = ldap_user.gl_user
@@ -62,13 +62,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to after_sign_in_path_for(current_user)
end
else
- saml_user = Gitlab::Saml::User.new(oauth)
+ saml_user = Gitlab::Auth::Saml::User.new(oauth)
saml_user.save if saml_user.changed?
@user = saml_user.gl_user
continue_login_process
end
- rescue Gitlab::OAuth::SignupDisabledError
+ rescue Gitlab::Auth::OAuth::User::SignupDisabledError
handle_signup_error
end
@@ -106,20 +106,20 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated'
else
- oauth_user = Gitlab::OAuth::User.new(oauth)
+ oauth_user = Gitlab::Auth::OAuth::User.new(oauth)
oauth_user.save
@user = oauth_user.gl_user
continue_login_process
end
- rescue Gitlab::OAuth::SigninDisabledForProviderError
+ rescue Gitlab::Auth::OAuth::User::SigninDisabledForProviderError
handle_disabled_provider
- rescue Gitlab::OAuth::SignupDisabledError
+ rescue Gitlab::Auth::OAuth::User::SignupDisabledError
handle_signup_error
end
def handle_service_ticket(provider, ticket)
- Gitlab::OAuth::Session.create provider, ticket
+ Gitlab::Auth::OAuth::Session.create provider, ticket
session[:service_tickets] ||= {}
session[:service_tickets][provider] = ticket
end
@@ -142,7 +142,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
def handle_signup_error
- label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
+ label = Gitlab::Auth::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
if Gitlab::CurrentSettings.allow_signup?
@@ -171,7 +171,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
def handle_disabled_provider
- label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
+ label = Gitlab::Auth::OAuth::Provider.label_for(oauth['provider'])
flash[:alert] = "Signing in using #{label} has been disabled"
redirect_to new_user_session_path
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index 142e8b6e4bc..aeaba3a0acf 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -4,6 +4,7 @@ class Projects::ClustersController < Projects::ApplicationController
before_action :authorize_create_cluster!, only: [:new]
before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy]
+ before_action :update_applications_status, only: [:status]
STATUS_POLLING_INTERVAL = 10_000
@@ -114,4 +115,8 @@ class Projects::ClustersController < Projects::ApplicationController
def authorize_admin_cluster!
access_denied! unless can?(current_user, :admin_cluster, cluster)
end
+
+ def update_applications_status
+ @cluster.applications.each(&:schedule_status_update)
+ end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index c73306a6b66..f3a4aa849c7 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -16,7 +16,7 @@ class SessionsController < Devise::SessionsController
def new
set_minimum_password_length
- @ldap_servers = Gitlab::LDAP::Config.available_servers
+ @ldap_servers = Gitlab::Auth::LDAP::Config.available_servers
super
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index ab68ecad2ba..4c4d7cca8a5 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -77,7 +77,7 @@ module ApplicationSettingsHelper
label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, !disabled,
- autocomplete: 'off') + Gitlab::OAuth::Provider.label_for(source)
+ autocomplete: 'off') + Gitlab::Auth::OAuth::Provider.label_for(source)
end
end
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index f909f664034..c109954f3a3 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -3,7 +3,7 @@ module AuthHelper
FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
def ldap_enabled?
- Gitlab::LDAP::Config.enabled?
+ Gitlab::Auth::LDAP::Config.enabled?
end
def omniauth_enabled?
@@ -15,11 +15,11 @@ module AuthHelper
end
def auth_providers
- Gitlab::OAuth::Provider.providers
+ Gitlab::Auth::OAuth::Provider.providers
end
def label_for_provider(name)
- Gitlab::OAuth::Provider.label_for(name)
+ Gitlab::Auth::OAuth::Provider.label_for(name)
end
def form_based_provider?(name)
diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb
index 5a4fda0724c..e7aa92e6e5c 100644
--- a/app/helpers/profiles_helper.rb
+++ b/app/helpers/profiles_helper.rb
@@ -3,7 +3,7 @@ module ProfilesHelper
user_synced_attributes_metadata = current_user.user_synced_attributes_metadata
if user_synced_attributes_metadata&.synced?(attribute)
if user_synced_attributes_metadata.provider
- Gitlab::OAuth::Provider.label_for(user_synced_attributes_metadata.provider)
+ Gitlab::Auth::OAuth::Provider.label_for(user_synced_attributes_metadata.provider)
else
'LDAP'
end
diff --git a/app/helpers/u2f_helper.rb b/app/helpers/u2f_helper.rb
deleted file mode 100644
index 81bfe5d4eeb..00000000000
--- a/app/helpers/u2f_helper.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module U2fHelper
- def inject_u2f_api?
- ((browser.chrome? && browser.version.to_i >= 41) || (browser.opera? && browser.version.to_i >= 40)) && !browser.device.mobile?
- end
-end
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index aa5cf97756f..9f583342c19 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -5,6 +5,7 @@ module Clusters
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
+ include AfterCommitQueue
default_value_for :ingress_type, :nginx
default_value_for :version, :nginx
@@ -13,6 +14,17 @@ module Clusters
nginx: 1
}
+ FETCH_IP_ADDRESS_DELAY = 30.seconds
+
+ state_machine :status do
+ before_transition any => [:installed] do |application|
+ application.run_after_commit do
+ ClusterWaitForIngressIpAddressWorker.perform_in(
+ FETCH_IP_ADDRESS_DELAY, application.name, application.id)
+ end
+ end
+ end
+
def chart
'stable/nginx-ingress'
end
@@ -24,6 +36,13 @@ module Clusters
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end
+
+ def schedule_status_update
+ return unless installed?
+ return if external_ip
+
+ ClusterWaitForIngressIpAddressWorker.perform_async(name, id)
+ end
end
end
end
diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb
index a98fa85a5ff..623b836c0ed 100644
--- a/app/models/clusters/concerns/application_core.rb
+++ b/app/models/clusters/concerns/application_core.rb
@@ -23,6 +23,11 @@ module Clusters
def name
self.class.application_name
end
+
+ def schedule_status_update
+ # Override if you need extra data synchronized
+ # from K8s after installation
+ end
end
end
end
diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
index d2e626c22e8..b34d1382d43 100644
--- a/app/models/cycle_analytics.rb
+++ b/app/models/cycle_analytics.rb
@@ -6,6 +6,12 @@ class CycleAnalytics
@options = options
end
+ def all_medians_per_stage
+ STAGES.each_with_object({}) do |stage_name, medians_per_stage|
+ medians_per_stage[stage_name] = self[stage_name].median
+ end
+ end
+
def summary
@summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(@project,
from: @options[:from],
diff --git a/app/models/identity.rb b/app/models/identity.rb
index 2b433e9b988..1011b9f1109 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -17,12 +17,12 @@ class Identity < ActiveRecord::Base
end
def ldap?
- Gitlab::OAuth::Provider.ldap_provider?(provider)
+ Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
end
def self.normalize_uid(provider, uid)
- if Gitlab::OAuth::Provider.ldap_provider?(provider)
- Gitlab::LDAP::Person.normalize_dn(uid)
+ if Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
+ Gitlab::Auth::LDAP::Person.normalize_dn(uid)
else
uid.to_s
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 7888c1019e6..242d9d5f125 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -590,15 +590,7 @@ class Repository
def license_key
return unless exists?
- # The licensee gem creates a Rugged object from the path:
- # https://github.com/benbalter/licensee/blob/v8.7.0/lib/licensee/projects/git_project.rb
- begin
- Licensee.license(path).try(:key)
- # Normally we would rescue Rugged::Error, but that is banned by lint-rugged
- # and we need to migrate this endpoint to Gitaly:
- # https://gitlab.com/gitlab-org/gitaly/issues/1026
- rescue
- end
+ raw_repository.license_short_name
end
cache_method :license_key
diff --git a/app/models/user.rb b/app/models/user.rb
index 982080763d2..9547506d33d 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -728,7 +728,7 @@ class User < ActiveRecord::Base
def ldap_user?
if identities.loaded?
- identities.find { |identity| Gitlab::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? }
+ identities.find { |identity| Gitlab::Auth::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? }
else
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end
diff --git a/app/models/user_synced_attributes_metadata.rb b/app/models/user_synced_attributes_metadata.rb
index 548b99b69d9..688432a9d67 100644
--- a/app/models/user_synced_attributes_metadata.rb
+++ b/app/models/user_synced_attributes_metadata.rb
@@ -26,6 +26,6 @@ class UserSyncedAttributesMetadata < ActiveRecord::Base
private
def sync_profile_from_provider?
- Gitlab::OAuth::Provider.sync_profile_from_provider?(provider)
+ Gitlab::Auth::OAuth::Provider.sync_profile_from_provider?(provider)
end
end
diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb
index 564612202b5..3e355a13e06 100644
--- a/app/serializers/analytics_stage_entity.rb
+++ b/app/serializers/analytics_stage_entity.rb
@@ -7,6 +7,7 @@ class AnalyticsStageEntity < Grape::Entity
expose :description
expose :median, as: :value do |stage|
- stage.median && !stage.median.zero? ? distance_of_time_in_words(stage.median) : nil
+ # median returns a BatchLoader instance which we first have to unwrap by using to_i
+ !stage.median.to_i.zero? ? distance_of_time_in_words(stage.median) : nil
end
end
diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb
index 3f9a275ad08..b22a0b666ef 100644
--- a/app/serializers/cluster_application_entity.rb
+++ b/app/serializers/cluster_application_entity.rb
@@ -2,4 +2,5 @@ class ClusterApplicationEntity < Grape::Entity
expose :name
expose :status_name, as: :status
expose :status_reason
+ expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) }
end
diff --git a/app/services/clusters/applications/check_ingress_ip_address_service.rb b/app/services/clusters/applications/check_ingress_ip_address_service.rb
new file mode 100644
index 00000000000..e572b1e5d99
--- /dev/null
+++ b/app/services/clusters/applications/check_ingress_ip_address_service.rb
@@ -0,0 +1,36 @@
+module Clusters
+ module Applications
+ class CheckIngressIpAddressService < BaseHelmService
+ include Gitlab::Utils::StrongMemoize
+
+ Error = Class.new(StandardError)
+
+ LEASE_TIMEOUT = 15.seconds.to_i
+
+ def execute
+ return if app.external_ip
+ return unless try_obtain_lease
+
+ app.update!(external_ip: ingress_ip) if ingress_ip
+ end
+
+ private
+
+ def try_obtain_lease
+ Gitlab::ExclusiveLease
+ .new("check_ingress_ip_address_service:#{app.id}", timeout: LEASE_TIMEOUT)
+ .try_obtain
+ end
+
+ def ingress_ip
+ service.status.loadBalancer.ingress&.first&.ip
+ end
+
+ def service
+ strong_memoize(:ingress_service) do
+ kubeclient.get_service('ingress-nginx-ingress-controller', Gitlab::Kubernetes::Helm::NAMESPACE)
+ end
+ end
+ end
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 20527d31870..b89b7a9ff85 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -173,7 +173,7 @@
Password authentication enabled for Git over HTTP(S)
.help-block
When disabled, a Personal Access Token
- - if Gitlab::LDAP::Config.enabled?
+ - if Gitlab::Auth::LDAP::Config.enabled?
or LDAP password
must be used to authenticate.
- if omniauth_enabled? && button_based_providers.any?
diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml
index 112a201fafa..5381b854f5c 100644
--- a/app/views/admin/identities/_form.html.haml
+++ b/app/views/admin/identities/_form.html.haml
@@ -4,7 +4,7 @@
.form-group
= f.label :provider, class: 'control-label'
.col-sm-10
- - values = Gitlab::OAuth::Provider.providers.map { |name| ["#{Gitlab::OAuth::Provider.label_for(name)} (#{name})", name] }
+ - values = Gitlab::Auth::OAuth::Provider.providers.map { |name| ["#{Gitlab::Auth::OAuth::Provider.label_for(name)} (#{name})", name] }
= f.select :provider, values, { allow_blank: false }, class: 'form-control'
.form-group
= f.label :extern_uid, "Identifier", class: 'control-label'
diff --git a/app/views/admin/identities/_identity.html.haml b/app/views/admin/identities/_identity.html.haml
index 8c658905bd6..ef5a3f1d969 100644
--- a/app/views/admin/identities/_identity.html.haml
+++ b/app/views/admin/identities/_identity.html.haml
@@ -1,6 +1,6 @@
%tr
%td
- #{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})
+ #{Gitlab::Auth::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})
%td
= identity.extern_uid
%td
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 56ec1b3db0d..6e54b9b5645 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -1,7 +1,3 @@
-- if inject_u2f_api?
- - content_for :page_specific_javascripts do
- = webpack_bundle_tag('u2f')
-
%div
= render 'devise/shared/tab_single', tab_title: 'Two-Factor Authentication'
.login-box
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 8707af36e2e..329bf16895f 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -4,8 +4,6 @@
- content_for :page_specific_javascripts do
- - if inject_u2f_api?
- = webpack_bundle_tag('u2f')
= webpack_bundle_tag('two_factor_auth')
.js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path }
diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml
index cc85e5de40f..3124443b4e4 100644
--- a/app/views/projects/blob/_viewer.html.haml
+++ b/app/views/projects/blob/_viewer.html.haml
@@ -1,9 +1,10 @@
- hidden = local_assigns.fetch(:hidden, false)
- render_error = viewer.render_error
+- rich_type = viewer.type == :rich ? viewer.partial_name : nil
- load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?)
- viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async
-.blob-viewer{ data: { type: viewer.type, url: viewer_url }, class: ('hidden' if hidden) }
+.blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) }
- if render_error
= render 'projects/blob/render_error', viewer: viewer
- elsif load_async
diff --git a/app/views/projects/blob/viewers/_balsamiq.html.haml b/app/views/projects/blob/viewers/_balsamiq.html.haml
index 15349387eb2..b20106e8c3a 100644
--- a/app/views/projects/blob/viewers/_balsamiq.html.haml
+++ b/app/views/projects/blob/viewers/_balsamiq.html.haml
@@ -1,4 +1 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('balsamiq_viewer')
-
.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: blob_raw_path } }
diff --git a/app/views/projects/blob/viewers/_notebook.html.haml b/app/views/projects/blob/viewers/_notebook.html.haml
index d1ffaca35b9..eb4ca1b9816 100644
--- a/app/views/projects/blob/viewers/_notebook.html.haml
+++ b/app/views/projects/blob/viewers/_notebook.html.haml
@@ -1,5 +1 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('common_vue')
- = webpack_bundle_tag('notebook_viewer')
-
.file-content#js-notebook-viewer{ data: { endpoint: blob_raw_path } }
diff --git a/app/views/projects/blob/viewers/_pdf.html.haml b/app/views/projects/blob/viewers/_pdf.html.haml
index fc3f0d922b1..95d837a57dc 100644
--- a/app/views/projects/blob/viewers/_pdf.html.haml
+++ b/app/views/projects/blob/viewers/_pdf.html.haml
@@ -1,5 +1 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('common_vue')
- = webpack_bundle_tag('pdf_viewer')
-
.file-content#js-pdf-viewer{ data: { endpoint: blob_raw_path } }
diff --git a/app/views/projects/blob/viewers/_sketch.html.haml b/app/views/projects/blob/viewers/_sketch.html.haml
index 8fb67c819c1..b4b6492b92f 100644
--- a/app/views/projects/blob/viewers/_sketch.html.haml
+++ b/app/views/projects/blob/viewers/_sketch.html.haml
@@ -1,7 +1,3 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('common_vue')
- = webpack_bundle_tag('sketch_viewer')
-
.file-content#js-sketch-viewer{ data: { endpoint: blob_raw_path } }
.js-loading-icon.text-center.prepend-top-default.append-bottom-default.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' }
= icon('spinner spin 2x', 'aria-hidden' => 'true');
diff --git a/app/views/projects/blob/viewers/_stl.html.haml b/app/views/projects/blob/viewers/_stl.html.haml
index e58809ec008..55dd8cba7fe 100644
--- a/app/views/projects/blob/viewers/_stl.html.haml
+++ b/app/views/projects/blob/viewers/_stl.html.haml
@@ -1,6 +1,3 @@
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag('stl_viewer')
-
.file-content.is-stl-loading
.text-center#js-stl-viewer{ data: { endpoint: blob_raw_path } }
= icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml
index 2b1b23ba198..179c45a9867 100644
--- a/app/views/projects/clusters/show.html.haml
+++ b/app/views/projects/clusters/show.html.haml
@@ -15,6 +15,7 @@
cluster_status_reason: @cluster.status_reason,
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address'),
+ ingress_dns_help_path: help_page_path('topics/autodevops/quick_start_guide.md', anchor: 'point-dns-at-cluster-ip'),
manage_prometheus_path: edit_project_service_path(@cluster.project, 'prometheus') } }
.js-cluster-application-notice
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index a9415410f8a..328db19be29 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -24,6 +24,7 @@
- gcp_cluster:cluster_wait_for_app_installation
- gcp_cluster:wait_for_cluster_creation
- gcp_cluster:check_gcp_project_billing
+- gcp_cluster:cluster_wait_for_ingress_ip_address
- github_import_advance_stage
- github_importer:github_import_import_diff_note
diff --git a/app/workers/cluster_wait_for_ingress_ip_address_worker.rb b/app/workers/cluster_wait_for_ingress_ip_address_worker.rb
new file mode 100644
index 00000000000..8ba5951750c
--- /dev/null
+++ b/app/workers/cluster_wait_for_ingress_ip_address_worker.rb
@@ -0,0 +1,11 @@
+class ClusterWaitForIngressIpAddressWorker
+ include ApplicationWorker
+ include ClusterQueue
+ include ClusterApplications
+
+ def perform(app_name, app_id)
+ find_application(app_name, app_id) do |app|
+ Clusters::Applications::CheckIngressIpAddressService.new(app).execute
+ end
+ end
+end