From 096fc1914a22d749d17ca0e2075e7550b94dacf9 Mon Sep 17 00:00:00 2001 From: Samuel Padgett Date: Wed, 26 Jul 2017 13:02:44 -0400 Subject: [PATCH] Support binding parameters Adds bind parameters to overview "Create Binding" action and in context provision binding. Bumps origin-web-common 0.0.52 -> 0.0.53 Bumps origin-web-catalog 0.0.42 -> to 0.0.43 https://trello.com/c/Sws4p5jU --- app/scripts/directives/bindService.js | 110 +++++-- app/views/directives/bind-service.html | 4 +- .../bind-service/bind-parameters.html | 3 + bower.json | 4 +- dist/scripts/scripts.js | 113 ++++--- dist/scripts/templates.js | 9 +- dist/scripts/vendor.js | 286 +++++++++++------- dist/styles/main.css | 13 +- 8 files changed, 351 insertions(+), 191 deletions(-) create mode 100644 app/views/directives/bind-service/bind-parameters.html diff --git a/app/scripts/directives/bindService.js b/app/scripts/directives/bindService.js index 4c3ec9aebb..0e090d1ba9 100644 --- a/app/scripts/directives/bindService.js +++ b/app/scripts/directives/bindService.js @@ -23,7 +23,11 @@ DataService, BindingService) { var ctrl = this; - var validityWatcher; + var bindFormStep; + var bindParametersStep; + var resultsStep; + var selectionValidityWatcher; + var parametersValidityWatcher; var bindingWatch; var statusCondition = $filter('statusCondition'); var enableTechPreviewFeature = $filter('enableTechPreviewFeature'); @@ -70,18 +74,31 @@ }; var showBind = function() { + ctrl.nextTitle = bindParametersStep.hidden ? 'Bind' : 'Next >'; + if (ctrl.podPresets && !selectionValidityWatcher) { + selectionValidityWatcher = $scope.$watch("ctrl.selectionForm.$valid", function(isValid) { + bindFormStep.valid = isValid; + }); + } + }; + + var showParameters = function() { ctrl.nextTitle = 'Bind'; - if (ctrl.podPresets) { - validityWatcher = $scope.$watch("ctrl.selectionForm.$valid", function(isValid) { - ctrl.steps[0].valid = isValid; + if (!parametersValidityWatcher) { + parametersValidityWatcher = $scope.$watch("ctrl.parametersForm.$valid", function(isValid) { + bindParametersStep.valid = isValid; }); } }; var showResults = function() { - if (validityWatcher) { - validityWatcher(); - validityWatcher = undefined; + if (selectionValidityWatcher) { + selectionValidityWatcher(); + selectionValidityWatcher = undefined; + } + if (parametersValidityWatcher) { + parametersValidityWatcher(); + parametersValidityWatcher = undefined; } ctrl.nextTitle = "Close"; ctrl.wizardComplete = true; @@ -141,27 +158,57 @@ }); }; + bindFormStep = { + id: 'bindForm', + label: 'Binding', + view: 'views/directives/bind-service/bind-service-form.html', + valid: true, + onShow: showBind + }; + + bindParametersStep = { + id: 'bindParameters', + label: 'Parameters', + view: 'views/directives/bind-service/bind-parameters.html', + hidden: true, + onShow: showParameters + }; + + resultsStep = { + id: 'results', + label: 'Results', + view: 'views/directives/bind-service/results.html', + valid: true, + onShow: showResults + }; + + var updateInstance = function() { + if (!ctrl.serviceClasses) { + return; + } + + var instance = ctrl.target.kind === 'Instance' ? ctrl.target : ctrl.serviceToBind; + if (!instance) { + return; + } + + ctrl.serviceClass = ctrl.serviceClasses[instance.spec.serviceClassName]; + ctrl.serviceClassName = instance.spec.serviceClassName; + ctrl.plan = BindingService.getPlanForInstance(instance, ctrl.serviceClass); + ctrl.parameterSchema = _.get(ctrl.plan, 'alphaBindingCreateParameterSchema'); + bindParametersStep.hidden = !_.has(ctrl.parameterSchema, 'properties'); + ctrl.nextTitle = bindParametersStep.hidden ? 'Bind' : 'Next >'; + }; + + $scope.$watch("ctrl.serviceToBind", updateInstance); + ctrl.$onInit = function() { ctrl.serviceSelection = {}; ctrl.projectDisplayName = $filter('displayName')(ctrl.project); ctrl.podPresets = enableTechPreviewFeature('pod_presets'); + ctrl.parameterData = {}; - ctrl.steps = [ - { - id: 'bindForm', - label: "Binding", - view: 'views/directives/bind-service/bind-service-form.html', - valid: true, - onShow: showBind - }, - { - label: 'Results', - id: 'results', - view: 'views/directives/bind-service/results.html', - valid: true, - onShow: showResults - } - ]; + ctrl.steps = [ bindFormStep, bindParametersStep, resultsStep ]; // We will want ServiceClasses either way for display purposes DataService.list({ @@ -169,10 +216,7 @@ resource: 'serviceclasses' }, {}).then(function(serviceClasses) { ctrl.serviceClasses = serviceClasses.by('metadata.name'); - if (ctrl.target.kind === 'Instance') { - ctrl.serviceClass = ctrl.serviceClasses[ctrl.target.spec.serviceClassName]; - ctrl.serviceClassName = ctrl.target.spec.serviceClassName; - } + updateInstance(); sortServiceInstances(); }); @@ -198,9 +242,13 @@ }; ctrl.$onDestroy = function() { - if (validityWatcher) { - validityWatcher(); - validityWatcher = undefined; + if (selectionValidityWatcher) { + selectionValidityWatcher(); + selectionValidityWatcher = undefined; + } + if (parametersValidityWatcher) { + parametersValidityWatcher(); + parametersValidityWatcher = undefined; } if (bindingWatch) { DataService.unwatch(bindingWatch); @@ -216,7 +264,7 @@ }; var serviceClass = BindingService.getServiceClassForInstance(svcToBind, ctrl.serviceClasses); - BindingService.bindService(svcToBind, application, serviceClass).then(function(binding){ + BindingService.bindService(svcToBind, application, serviceClass, ctrl.parameterData).then(function(binding){ ctrl.binding = binding; ctrl.error = null; diff --git a/app/views/directives/bind-service.html b/app/views/directives/bind-service.html index 328f01b976..7e3bf82823 100644 --- a/app/views/directives/bind-service.html +++ b/app/views/directives/bind-service.html @@ -2,14 +2,12 @@ + wizard-done="ctrl.wizardComplete"> + + diff --git a/bower.json b/bower.json index 9d57dcbac9..c153d3bd64 100644 --- a/bower.json +++ b/bower.json @@ -46,8 +46,8 @@ "angular-moment": "1.0.0", "angular-utf8-base64": "0.0.5", "file-saver": "1.3.3", - "origin-web-common": "0.0.52", - "origin-web-catalog": "0.0.42" + "origin-web-common": "0.0.53", + "origin-web-catalog": "0.0.43" }, "devDependencies": { "angular-mocks": "1.5.11", diff --git a/dist/scripts/scripts.js b/dist/scripts/scripts.js index be8564f3ef..3194f853da 100644 --- a/dist/scripts/scripts.js +++ b/dist/scripts/scripts.js @@ -12249,96 +12249,113 @@ templateUrl: "views/directives/route-service-bar-chart.html" }(), function() { angular.module("openshiftConsole").component("bindService", { controller: [ "$scope", "$filter", "DataService", "BindingService", function(e, t, n, a) { -var r, o, i, s, c, l, u, d = this, m = t("statusCondition"), p = t("enableTechPreviewFeature"), f = function() { +var r, o, i, s, c, l, u, d, m, p, f, g = this, h = t("statusCondition"), v = t("enableTechPreviewFeature"), y = function() { var e, t; -_.each(d.serviceInstances, function(n) { -var a = "True" === _.get(m(n, "Ready"), "status"); +_.each(g.serviceInstances, function(n) { +var a = "True" === _.get(h(n, "Ready"), "status"); a && (!e || n.metadata.creationTimestamp > e.metadata.creationTimestamp) && (e = n), a || t && !(n.metadata.creationTimestamp > t.metadata.creationTimestamp) || (t = n); -}), d.serviceToBind = e || t; -}, g = function() { -d.serviceClasses && d.serviceInstances && (d.serviceInstances = a.filterBindableServiceInstances(d.serviceInstances, d.serviceClasses), d.orderedServiceInstances = a.sortServiceInstances(d.serviceInstances, d.serviceClasses), d.serviceToBind || f()); -}, h = function() { -if (i && s && c && l && u) { -var e = [].concat(i).concat(s).concat(c).concat(l).concat(u); -d.applications = _.sortBy(e, [ "metadata.name", "kind" ]), d.bindType = d.applications.length ? "application" : "secret-only"; -} -}, v = function() { -d.nextTitle = "Bind", d.podPresets && (r = e.$watch("ctrl.selectionForm.$valid", function(e) { -d.steps[0].valid = e; -})); -}, y = function() { -r && (r(), r = void 0), d.nextTitle = "Close", d.wizardComplete = !0, d.bindService(); +}), g.serviceToBind = e || t; }, b = function() { +g.serviceClasses && g.serviceInstances && (g.serviceInstances = a.filterBindableServiceInstances(g.serviceInstances, g.serviceClasses), g.orderedServiceInstances = a.sortServiceInstances(g.serviceInstances, g.serviceClasses), g.serviceToBind || y()); +}, C = function() { +if (u && d && m && p && f) { +var e = [].concat(u).concat(d).concat(m).concat(p).concat(f); +g.applications = _.sortBy(e, [ "metadata.name", "kind" ]), g.bindType = g.applications.length ? "application" : "secret-only"; +} +}, S = function() { var e = { -namespace: _.get(d.target, "metadata.namespace") +namespace: _.get(g.target, "metadata.namespace") }; n.list("deploymentconfigs", e).then(function(e) { -i = _.toArray(e.by("metadata.name")), h(); +u = _.toArray(e.by("metadata.name")), C(); }), n.list("replicationcontrollers", e).then(function(e) { -c = _.reject(e.by("metadata.name"), t("hasDeploymentConfig")), h(); +m = _.reject(e.by("metadata.name"), t("hasDeploymentConfig")), C(); }), n.list({ group: "apps", resource: "deployments" }, e).then(function(e) { -s = _.toArray(e.by("metadata.name")), h(); +d = _.toArray(e.by("metadata.name")), C(); }), n.list({ group: "extensions", resource: "replicasets" }, e).then(function(e) { -l = _.reject(e.by("metadata.name"), t("hasDeployment")), h(); +p = _.reject(e.by("metadata.name"), t("hasDeployment")), C(); }), n.list({ group: "apps", resource: "statefulsets" }, e).then(function(e) { -u = _.toArray(e.by("metadata.name")), h(); +f = _.toArray(e.by("metadata.name")), C(); }); -}, C = function() { +}, w = function() { var e = { -namespace: _.get(d.target, "metadata.namespace") +namespace: _.get(g.target, "metadata.namespace") }; n.list({ group: "servicecatalog.k8s.io", resource: "instances" }, e).then(function(e) { -d.serviceInstances = e.by("metadata.name"), g(); +g.serviceInstances = e.by("metadata.name"), b(); }); }; -d.$onInit = function() { -d.serviceSelection = {}, d.projectDisplayName = t("displayName")(d.project), d.podPresets = p("pod_presets"), d.steps = [ { +r = { id: "bindForm", label: "Binding", view: "views/directives/bind-service/bind-service-form.html", valid: !0, -onShow: v -}, { -label: "Results", +onShow: function() { +g.nextTitle = o.hidden ? "Bind" : "Next >", g.podPresets && !s && (s = e.$watch("ctrl.selectionForm.$valid", function(e) { +r.valid = e; +})); +} +}, o = { +id: "bindParameters", +label: "Parameters", +view: "views/directives/bind-service/bind-parameters.html", +hidden: !0, +onShow: function() { +g.nextTitle = "Bind", c || (c = e.$watch("ctrl.parametersForm.$valid", function(e) { +o.valid = e; +})); +} +}, i = { id: "results", +label: "Results", view: "views/directives/bind-service/results.html", valid: !0, -onShow: y -} ], n.list({ +onShow: function() { +s && (s(), s = void 0), c && (c(), c = void 0), g.nextTitle = "Close", g.wizardComplete = !0, g.bindService(); +} +}; +var k = function() { +if (g.serviceClasses) { +var e = "Instance" === g.target.kind ? g.target : g.serviceToBind; +e && (g.serviceClass = g.serviceClasses[e.spec.serviceClassName], g.serviceClassName = e.spec.serviceClassName, g.plan = a.getPlanForInstance(e, g.serviceClass), g.parameterSchema = _.get(g.plan, "alphaBindingCreateParameterSchema"), o.hidden = !_.has(g.parameterSchema, "properties"), g.nextTitle = o.hidden ? "Bind" : "Next >"); +} +}; +e.$watch("ctrl.serviceToBind", k), g.$onInit = function() { +g.serviceSelection = {}, g.projectDisplayName = t("displayName")(g.project), g.podPresets = v("pod_presets"), g.parameterData = {}, g.steps = [ r, o, i ], n.list({ group: "servicecatalog.k8s.io", resource: "serviceclasses" }, {}).then(function(e) { -d.serviceClasses = e.by("metadata.name"), "Instance" === d.target.kind && (d.serviceClass = d.serviceClasses[d.target.spec.serviceClassName], d.serviceClassName = d.target.spec.serviceClassName), g(); -}), "Instance" === d.target.kind ? (d.bindType = "secret-only", d.appToBind = null, d.serviceToBind = d.target, d.podPresets && b()) : (d.bindType = "application", d.appToBind = d.target, C()); -}, d.$onChanges = function(e) { -e.project && !e.project.isFirstChange() && (d.projectDisplayName = t("displayName")(d.project)); -}, d.$onDestroy = function() { -r && (r(), r = void 0), o && n.unwatch(o); -}, d.bindService = function() { -var e = "Instance" === d.target.kind ? d.target : d.serviceToBind, t = "application" === d.bindType ? d.appToBind : void 0, r = { +g.serviceClasses = e.by("metadata.name"), k(), b(); +}), "Instance" === g.target.kind ? (g.bindType = "secret-only", g.appToBind = null, g.serviceToBind = g.target, g.podPresets && S()) : (g.bindType = "application", g.appToBind = g.target, w()); +}, g.$onChanges = function(e) { +e.project && !e.project.isFirstChange() && (g.projectDisplayName = t("displayName")(g.project)); +}, g.$onDestroy = function() { +s && (s(), s = void 0), c && (c(), c = void 0), l && n.unwatch(l); +}, g.bindService = function() { +var e = "Instance" === g.target.kind ? g.target : g.serviceToBind, t = "application" === g.bindType ? g.appToBind : void 0, r = { namespace: _.get(e, "metadata.namespace") -}, i = a.getServiceClassForInstance(e, d.serviceClasses); -a.bindService(e, t, i).then(function(e) { -d.binding = e, d.error = null, o = n.watchObject(a.bindingResource, _.get(d.binding, "metadata.name"), r, function(e) { -d.binding = e; +}, o = a.getServiceClassForInstance(e, g.serviceClasses); +a.bindService(e, t, o, g.parameterData).then(function(e) { +g.binding = e, g.error = null, l = n.watchObject(a.bindingResource, _.get(g.binding, "metadata.name"), r, function(e) { +g.binding = e; }); }, function(e) { -d.error = e; +g.error = e; }); -}, d.closeWizard = function() { -_.isFunction(d.onClose) && d.onClose(); +}, g.closeWizard = function() { +_.isFunction(g.onClose) && g.onClose(); }; } ], controllerAs: "ctrl", diff --git a/dist/scripts/templates.js b/dist/scripts/templates.js index 8bde2792c2..3d7ef7d958 100644 --- a/dist/scripts/templates.js +++ b/dist/scripts/templates.js @@ -5559,7 +5559,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( $templateCache.put('views/directives/bind-service.html', "
\n" + - "\n" + + "\n" + "\n" + "
\n" + "
\n" + @@ -5572,6 +5572,13 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( ); + $templateCache.put('views/directives/bind-service/bind-parameters.html', + "
\n" + + "\n" + + "
" + ); + + $templateCache.put('views/directives/bind-service/bind-service-form.html', "
\n" + "\n" + diff --git a/dist/scripts/vendor.js b/dist/scripts/vendor.js index e08c30ebb2..2bc126237c 100644 --- a/dist/scripts/vendor.js +++ b/dist/scripts/vendor.js @@ -72754,7 +72754,7 @@ e.put("src/components/create-project/createProject.html", '
\n \x3c!-- Avoid whitespace inside the link --\x3e\n Delete Project {{projectName}}\n
\n'), e.put("src/components/delete-project/delete-project-modal.html", '
\n \x3c!-- Use a form so that the enter key submits when typing a project name to confirm. --\x3e\n \n \n \n \n
\n'), e.put("src/components/delete-project/delete-project.html", '{{label || \'Delete\'}}\n'), e.put("src/components/edit-project/editProject.html", '
\n
\n
\n \n \n
\n\n
\n \n \n
\n\n
\n \n \n Cancel\n \n
\n
\n
\n'), e.put("src/components/origin-modal-popup/origin-modal-popup.html", '
\n

\n {{$ctrl.modalTitle}}\n

\n
\n \n \n \n
\n'), e.put("src/components/toast-notifications/toast-notifications.html", '
\n
\n
\n \n \n {{notification.type}}\n {{notification.message}}\n
\n \n \n
\n \n {{link.label}}\n {{link.label}}\n |\n \n
\n
\n
\n'), -e.put("src/components/truncate-long-text/truncateLongText.html", '\x3c!--\n Do not remove class `truncated-content` (here or below) even though it\'s not\n styled directly in origin-web-common. `truncated-content` is used by\n origin-web-console in certain contexts.\n--\x3e\n\n\n \n \n …\n \n See All\n \n \n
\n Collapse\n \n
\n \n Collapse\n \n \n
\n
\n'); +e.put("src/components/truncate-long-text/truncateLongText.html", '\x3c!--\n Do not remove class `truncated-content` (here or below) even though it\'s not\n styled directly in origin-web-common. `truncated-content` is used by\n origin-web-console in certain contexts.\n--\x3e\n\n\n \n \n …\n \n See All\n \n \n
\n Collapse\n \n
\n \n Collapse\n \n \n
\n
\n'); } ]), angular.module("openshiftCommonUI").component("bindApplicationForm", { controllerAs: "ctrl", bindings: { @@ -73141,7 +73141,7 @@ n ? (t.truncatedContent = e(n, t.limit, t.useWordBoundary, t.newlineLimit), t.tr } ]), angular.module("openshiftCommonUI").filter("alertStatus", function() { return function(e) { var t; -switch (e) { +switch (e.toLowerCase()) { case "error": t = "alert-danger"; break; @@ -73154,6 +73154,10 @@ case "success": t = "alert-success"; break; +case "normal": +t = "alert-info"; +break; + default: t = "alert-info"; } @@ -73162,7 +73166,7 @@ return t; }).filter("alertIcon", function() { return function(e) { var t; -switch (e) { +switch (e.toLowerCase()) { case "error": t = "pficon pficon-error-circle-o"; break; @@ -73175,6 +73179,10 @@ case "success": t = "pficon pficon-ok"; break; +case "normal": +t = "pficon pficon-info"; +break; + default: t = "pficon pficon-info"; } @@ -73803,15 +73811,33 @@ var n = _.get(e, "spec.planName"); return _.find(t.plans, { name: n }); -}, l = function(e, i) { -var r = s(e, i); -return _.has(r, [ "alphaBindingCreateParameterSchema", "properties", "template.openshift.io/requester-username" ]) ? n.withUser().then(function(e) { -return { -"template.openshift.io/requester-username": e.metadata.name +}, l = e("generateName"), c = function(e) { +var t = _.truncate(e, { +length: r.maxlength - 5 - 1, +omission: "" +}); +return l(t, 5); +}, u = function(e, t, n) { +var i = { +apiVersion: "v1", +kind: "Secret", +metadata: { +name: e, +ownerReferences: [ { +apiVersion: n.apiVersion, +kind: n.kind, +name: n.metadata.name, +uid: n.metadata.uid, +controller: !1, +blockOwnerDeletion: !1 +} ] +}, +type: "Opaque", +stringData: {} }; -}) : t.when({}); -}, c = e("generateName"), u = function(e, t, n) { -var i = e.metadata.name, o = c(_.truncate(i, r.maxlength - 6) + "-"), a = { +return i.stringData.parameters = JSON.stringify(t), i; +}, d = function(e, t, n) { +var n, i = e.metadata.name, r = { kind: "Binding", apiVersion: "servicecatalog.k8s.io/v1alpha1", metadata: { @@ -73821,24 +73847,29 @@ spec: { instanceRef: { name: i }, -secretName: o +secretName: c(e.metadata.name + "-credentials-") } }; -_.isEmpty(n) || (a.spec.parameters = n); -var s = _.get(t, "spec.selector"); -return s && (s.matchLabels || s.matchExpressions || (s = { -matchLabels: s -}), a.spec.alphaPodPresetTemplate = { -name: o, -selector: s -}), a; -}, d = function(e, t) { +n && (r.spec.parametersFrom = [ { +secretKeyRef: { +name: n, +key: "parameters" +} +} ]); +var o = _.get(t, "spec.selector"); +return o && (o.matchLabels || o.matchExpressions || (o = { +matchLabels: o +}), r.spec.alphaPodPresetTemplate = { +name: relatedObjName, +selector: o +}), r; +}, h = function(e, t) { var n = a(e, t); if (_.get(e, "metadata.deletionTimestamp")) return !1; if (!n) return !!e; var i = s(e, n), r = _.get(i, "bindable"); return !0 === r || !1 !== r && n.bindable; -}, h = function(e) { +}, f = function(e) { var t = {}; return _.each(e, function(e) { var n = _.get(e, "spec.alphaPodPresetTemplate.selector"); @@ -73848,26 +73879,32 @@ n && (t[e.metadata.name] = new LabelSelector(n)); return { bindingResource: o, getServiceClassForInstance: a, -bindService: function(e, t, n) { -return l(e, n).then(function(n) { -var r = u(e, t, n), a = { +getPlanForInstance: s, +bindService: function(e, t, n, r) { +var a; +_.isEmpty(r) || (a = c(e.metadata.name + "-bind-parameters-")); +var s = d(e, t, a), l = { namespace: e.metadata.namespace -}; -return i.create(o, null, r, a); +}, h = i.create(o, null, s, l); +return a ? h.then(function(e) { +var t = u(a, r, e); +return i.create("secrets", null, t, l).then(function() { +return e; }); +}) : h; }, -isServiceBindable: d, -getPodPresetSelectorsForBindings: h, +isServiceBindable: h, +getPodPresetSelectorsForBindings: f, getBindingsForResource: function(e, t) { if ("Instance" === _.get(t, "kind")) return _.filter(e, [ "spec.instanceRef.name", _.get(t, "metadata.name") ]); -var n = h(e), i = new LabelSelector(_.get(t, "spec.selector")), r = []; +var n = f(e), i = new LabelSelector(_.get(t, "spec.selector")), r = []; return _.each(n, function(t, n) { t.covers(i) && r.push(e[n]); }), r; }, filterBindableServiceInstances: function(e, t) { return e || t ? _.filter(e, function(e) { -return d(e, t); +return h(e, t); }) : null; }, sortServiceInstances: function(e, t) { @@ -75012,7 +75049,7 @@ this.dismissDelay = 8e3, this.autoDismissTypes = [ "info", "success" ], this.$ge var t = [], n = this.dismissDelay, i = this.autoDismissTypes, r = function(e, t) { return t ? "hide/notification/" + t + "/" + e : "hide/notification/" + e; }, o = function(n) { -a(n) || s(n) || (t.push(n), e.$emit("NotificationsService.onNotificationAdded", n)); +n.id = n.id || _.uniqueId("notification-"), n.timestamp = new Date().toISOString(), a(n) || s(n) || (t.push(n), e.$emit("NotificationsService.onNotificationAdded", n)); }, a = function(e) { if (!e.id) return !1; var t = r(e.id, e.namespace); @@ -77000,15 +77037,17 @@ e.exports = '\n \n \x3c!-- Wait until users leave the field to avoid flashing errors as they type. --\x3e\n
\n
\n \n Application name is required.\n \n
\n
\n \n Application name consists of lower-case letters, numbers, and dashes. It must start with a letter and can\'t end with a -.\n \n
\n
\n \n Application name must be at least 2 characters.\n \n
\n
\n \n Application name can\'t be more than 24 characters.\n \n
\n
\n
\n
\n\n
\n \n
\n \n \n
\n \n Git repository is required.\n \n
\n
\n \n This might not be a valid Git URL. Check that it is the correct URL to a remote Git repository.\n \n
\n
\n
\n\n \x3c!--\n Only show the link for existing projects. It will be broken for new\n projects. Use class `invisible` when the project list is still loading\n so the dialog doesn\'t resize.\n --\x3e\n
\n If you have a private Git repository or need to change application defaults, view\n advanced options.\n
\n \n
\n'; }, function(e, t) { -e.exports = '
\n
\n

\n \n

\n

\n The application is being created\n

\n
\n
\n \n

\n The application is being created\n

\n
\n
\n
\n
\n \n Success\n

\n \n {{$ctrl.name}} has been created in {{$ctrl.selectedProject.metadata.name}} successfully\n \n

\n
\n
\n
\n \n \n
\n
\n

\n Continue to your project to check the status of your application as it builds and deploys.\n

\n
\n
\n
\n \n

\n Error creating {{$ctrl.name}} in\n {{$ctrl.selectedProject | displayName}}\n

\n
\n
\n \n {{$ctrl.error.data.message | upperFirst}}\n \n \n An error occurred creating the application.\n \n
\n \x3c!-- TODO: Improve error message presentation --\x3e\n
    \n
  • \n {{failure.data.message}}\n
  • \n
\n
\n\n'; +e.exports = '
\n

\n \n

\n

\n The application is being created\n

\n
\n
\n
\n \n Success\n

\n \n {{$ctrl.name}} has been created in {{$ctrl.selectedProject.metadata.name}} successfully\n \n

\n
\n
\n
\n \n \n
\n
\n

\n Continue to your project to check the status of your application as it builds and deploys.\n

\n
\n
\n
\n \n

\n Error creating {{$ctrl.name}} in\n {{$ctrl.selectedProject | displayName}}\n

\n
\n
\n \n {{$ctrl.error.data.message | upperFirst}}\n \n \n An error occurred creating the application.\n \n
\n \x3c!-- TODO: Improve error message presentation --\x3e\n
    \n
  • \n {{failure.data.message}}\n
  • \n
\n
\n\n'; +}, function(e, t) { +e.exports = '
\n
\n \n \n \n
\n'; }, function(e, t) { -e.exports = '\n\n'; +e.exports = '\n\n'; }, function(e, t) { e.exports = '
\n
\n \n \n \n \n
\n {{$ctrl.error}}\n
\n
\n'; }, function(e, t) { e.exports = '
\n
\n

Select a Plan

\n
\n \n
\n
\n
\n'; }, function(e, t) { -e.exports = '
\n
\n
\n

\n \n

\n

\n The service is being provisioned\n

\n
\n
\n \n

\n The service is being provisioned\n

\n
\n
\n
\n
\n
\n \n

\n Error provisioning {{$ctrl.serviceClass.name}} in\n {{$ctrl.projectDisplayName}}\n

\n
\n
\n \n {{$ctrl.error.message}}\n \n \n An error occurred provisioning the service.\n \n
\n
\n
\n
\n \n Success\n

\n \n {{$ctrl.serviceInstance.metadata.name}} has been added to {{$ctrl.projectDisplayName}} successfully\n \n

\n
\n
\n
\n \n \n
\n
\n \n Info\n Continue to your project to bind this service to your application. Binding this service creates a secret containing the information necessary for your application to use the service.\n
\n\n'; +e.exports = '
\n
\n

\n \n

\n

\n The service is being provisioned\n

\n
\n
\n
\n
\n \n

\n Error provisioning {{$ctrl.serviceClass.name}} in\n {{$ctrl.projectDisplayName}}\n

\n
\n
\n \n {{$ctrl.error.message}}\n \n \n An error occurred provisioning the service.\n \n
\n
\n
\n
\n \n Success\n

\n \n {{$ctrl.serviceInstance.metadata.name}} has been added to {{$ctrl.projectDisplayName}} successfully\n \n

\n
\n
\n
\n \n \n
\n
\n \n Info\n Continue to your project to bind this service to your application. Binding this service creates a secret containing the information necessary for your application to use the service.\n
\n\n'; }, function(e, t) { e.exports = '
\n \n
    \n
  1. \n \n
  2. \n
\n
\n
\n\n \n
\n
\n'; }, function(e, t) { @@ -77022,7 +77061,7 @@ e.exports = '
\n
\n \n \n \n \n \n \n
\n
\n
\n
\n
\n
\n
\n
\n'; }, function(e, t) { -e.exports = '
\n \n \n
\n
\n
\n
\n \n \n
\n
\n
\n {{$ctrl.serviceName}}\n
\n
\n \n {{tag}}\n \n
\n \n
\n
\n
\n

\n \n Plan {{$ctrl.selectedPlan.externalMetadata.displayName}}\n \n \n {{$ctrl.selectedPlan.description}}\n

\n

\n

\n
\n
\n
\n
\n
\n
\n \n \n
\n'; +e.exports = '
\n \n \n
\n
\n
\n
\n \n \n
\n
\n
\n {{$ctrl.serviceName}}\n
\n
\n \n {{tag}}\n \n
\n \n
\n
\n
\n

\n \n Plan {{$ctrl.selectedPlan.externalMetadata.displayName}}\n \n \n {{$ctrl.selectedPlan.description}}\n

\n

\n

\n
\n
\n
\n
\n
\n
\n \n \n
\n'; }, function(e, t) { e.exports = '\n'; }, function(e, t) { @@ -78021,7 +78089,7 @@ r.$inject = [ "$rootScope", "$scope", "$q", "Catalog", "KeywordService" ], t.Cat }, function(e, t, n) { "use strict"; t.__esModule = !0; -var i = n(1), r = n(0), o = n(55), a = function() { +var i = n(1), r = n(0), o = n(56), a = function() { function e(e, t, n, i, o, a, s, l, c, u, d) { var h = this; this.ctrl = this, this.watches = [], this.clearValidityWatcher = function() { @@ -78035,7 +78103,7 @@ h.clearValidityWatcher(), h.ctrl.nextTitle = "Create", h.reviewStep.allowed = !0 }, this.showResults = function() { h.clearValidityWatcher(), h.ctrl.nextTitle = "Close", h.ctrl.wizardDone = !0, h.createApp(); }, this.onProjectUpdate = function() { -!h.instancesSupported || h.isNewProject() ? (h.ctrl.serviceInstances = [], h.updateBindability()) : (h.ctrl.updating = !0, h.DataService.list({ +!h.instancesSupported || h.isNewProject() ? (h.ctrl.serviceInstances = [], h.updateBindability()) : h.ctrl.showPodPresets && (h.ctrl.updating = !0, h.DataService.list({ group: "servicecatalog.k8s.io", resource: "instances" }, { @@ -78067,7 +78135,7 @@ id: "bind", view: "create-from-builder/create-from-builder-bind.html", valid: !0, allowed: !1, -hidden: !1, +hidden: !this.ctrl.showPodPresets, onShow: this.showBind }, this.reviewStep = { label: "Results", @@ -78175,16 +78243,16 @@ t.ctrl.error = e; }); }, e.prototype.bindService = function(e) { var t = this; -this.ctrl.bindInProgress = !0, this.ctrl.bindError = !1; +this.ctrl.bindError = !1; var n = { namespace: r.get(this.ctrl.selectedProject, "metadata.name") }, i = this.BindingService.getServiceClassForInstance(this.ctrl.serviceToBind, this.ctrl.serviceClasses); this.BindingService.bindService(this.ctrl.serviceToBind, e, i).then(function(e) { -t.ctrl.binding = e, t.ctrl.bindInProgress = !1, t.ctrl.bindComplete = !0, t.ctrl.bindError = null, t.watches.push(t.DataService.watchObject(t.BindingService.bindingResource, r.get(t.ctrl.binding, "metadata.name"), n, function(e) { +t.ctrl.binding = e, t.watches.push(t.DataService.watchObject(t.BindingService.bindingResource, r.get(t.ctrl.binding, "metadata.name"), n, function(e) { t.ctrl.binding = e; })); }, function(e) { -t.ctrl.bindInProgress = !1, t.ctrl.bindComplete = !0, t.ctrl.bindError = e; +t.ctrl.bindComplete = !0, t.ctrl.bindError = e; }); }, e.prototype.getServiceClasses = function() { var e = this, t = { @@ -78237,8 +78305,12 @@ u.clearValidityWatcher(), u.ctrl.configPageShown = !0, u.reviewStep.allowed = u. u.configStep.valid = e, u.bindStep.allowed = u.configStep.valid, u.reviewStep.allowed = u.bindStep.hidden && u.configStep.valid; }); }, this.showBind = function() { -u.clearValidityWatcher(), u.ctrl.configPageShown = !1, u.ctrl.nextTitle = "Create", u.reviewStep.allowed = u.bindStep.valid, u.isNewProject() ? u.ctrl.projectDisplayName = u.ctrl.selectedProject.metadata.annotations["new-display-name"] || u.ctrl.selectedProject.metadata.name : u.ctrl.projectDisplayName = u.$filter("displayName")(u.ctrl.selectedProject), u.validityWatcher = u.$scope.$watch("$ctrl.forms.bindForm.$valid", function(e, t) { -u.bindStep.valid = e, u.reviewStep.allowed = u.bindStep.valid; +u.clearValidityWatcher(), u.ctrl.configPageShown = !1, u.ctrl.nextTitle = u.bindParametersStep.hidden ? "Create" : "Next >", u.reviewStep.allowed = u.bindParametersStep.hidden && u.bindStep.valid, u.isNewProject() ? u.ctrl.projectDisplayName = u.ctrl.selectedProject.metadata.annotations["new-display-name"] || u.ctrl.selectedProject.metadata.name : u.ctrl.projectDisplayName = u.$filter("displayName")(u.ctrl.selectedProject), u.validityWatcher = u.$scope.$watch("$ctrl.forms.bindForm.$valid", function(e, t) { +u.bindStep.valid = e, u.bindParametersStep.allowed = e, u.reviewStep.allowed = u.bindParametersStep.hidden && u.bindStep.valid; +}); +}, this.showBindParameters = function() { +u.clearValidityWatcher(), u.ctrl.nextTitle = "Create", u.validityWatcher = u.$scope.$watch("$ctrl.forms.bindParametersForm.$valid", function(e, t) { +u.bindParametersStep.valid = e, u.reviewStep.allowed = u.bindParametersStep.valid; }); }, this.showResults = function() { u.clearValidityWatcher(), u.ctrl.configPageShown = !1, u.ctrl.nextTitle = "Close", u.ctrl.wizardDone = !0, u.provisionService(); @@ -78252,7 +78324,7 @@ u.ctrl.error = e.data; }); } else u.ctrl.projectDisplayName = u.$filter("displayName")(u.ctrl.selectedProject), u.createService(); }, this.onProjectUpdate = function() { -u.isNewProject() ? (u.ctrl.applications = [], u.ctrl.updating = !1, u.updateBindability()) : (u.ctrl.updating = !0, u.ProjectsService.get(u.ctrl.selectedProject.metadata.name).then(r.spread(function(e, t) { +u.isNewProject() ? (u.ctrl.applications = [], u.ctrl.updating = !1, u.updateBindability()) : u.ctrl.showPodPresets && (u.ctrl.updating = !0, u.ProjectsService.get(u.ctrl.selectedProject.metadata.name).then(r.spread(function(e, t) { u.ctrl.bindType = "none", u.ctrl.serviceToBind = u.ctrl.serviceClass, u.DataService.list("deploymentconfigs", t).then(function(e) { u.deploymentConfigs = r.toArray(e.by("metadata.name")), u.sortApplications(); }), u.DataService.list("replicationcontrollers", t).then(function(e) { @@ -78288,7 +78360,7 @@ status: "True" } return e.prototype.$onInit = function() { var e = this; -this.ctrl.iconClass = this.ctrl.serviceClass.iconClass || "fa fa-clone", this.ctrl.imageUrl = this.ctrl.serviceClass.imageUrl, this.ctrl.serviceName = this.ctrl.serviceClass.name, this.ctrl.description = this.ctrl.serviceClass.description, this.ctrl.longDescription = this.ctrl.serviceClass.longDescription, this.ctrl.plans = r.get(this, "ctrl.serviceClass.resource.plans", []), this.ctrl.applications = [], this.ctrl.parameterData = {}, this.ctrl.forms = {}, this.ctrl.appToBind = null, this.ctrl.configStepValid = !0, this.planStep = { +this.ctrl.iconClass = this.ctrl.serviceClass.iconClass || "fa fa-clone", this.ctrl.imageUrl = this.ctrl.serviceClass.imageUrl, this.ctrl.serviceName = this.ctrl.serviceClass.name, this.ctrl.description = this.ctrl.serviceClass.description, this.ctrl.longDescription = this.ctrl.serviceClass.longDescription, this.ctrl.plans = r.get(this, "ctrl.serviceClass.resource.plans", []), this.ctrl.applications = [], this.ctrl.parameterData = {}, this.ctrl.bindParameterData = {}, this.ctrl.forms = {}, this.ctrl.appToBind = null, this.ctrl.configStepValid = !0, this.planStep = { id: "plans", label: "Plan", view: "order-service/order-service-plans.html", @@ -78312,6 +78384,14 @@ hidden: !1, allowed: !1, valid: !0, onShow: this.showBind +}, this.bindParametersStep = { +label: "Parameters", +id: "bind-parameters", +view: "order-service/order-service-bind-parameters.html", +hidden: !1, +allowed: !1, +valid: !0, +onShow: this.showBindParameters }, this.reviewStep = { label: "Results", id: "results", @@ -78321,9 +78401,11 @@ allowed: !1, valid: !0, prevEnabled: !1, onShow: this.showResults -}, this.ctrl.steps = [ this.planStep, this.configStep, this.bindStep, this.reviewStep ], this.ctrl.nameTaken = !1, this.ctrl.wizardDone = !1, this.ctrl.bindType = "none", this.selectPlan(r.head(this.ctrl.plans)), this.ctrl.planIndex = 0, this.ctrl.updating = !0, this.selectedProjectWatch = this.$scope.$watch(function() { +}, this.ctrl.steps = [ this.planStep, this.configStep, this.bindStep, this.bindParametersStep, this.reviewStep ], this.ctrl.nameTaken = !1, this.ctrl.wizardDone = !1, this.ctrl.bindType = "none", this.selectPlan(r.head(this.ctrl.plans)), this.ctrl.planIndex = 0, this.ctrl.updating = !0, this.selectedProjectWatch = this.$scope.$watch(function() { return e.ctrl.selectedProject; -}, this.onProjectUpdate), this.AuthService.withUser().then(function(t) { +}, this.onProjectUpdate), this.bindTypeWatch = this.$scope.$watch("$ctrl.bindType", function(t, n) { +t !== n && (e.updateBindParametersStepVisibility(), e.ctrl.nextTitle = e.bindParametersStep.hidden ? "Create" : "Next >", e.reviewStep.allowed = e.bindParametersStep.hidden && e.bindStep.valid); +}), this.AuthService.withUser().then(function(t) { e.user = t, e.ctrl.wizardReady = !0; }); }, e.prototype.selectPlan = function(e) { @@ -78348,29 +78430,31 @@ e.ctrl.error = r.get(t, "data"); }); }, e.prototype.bindService = function() { var e = this; -this.ctrl.bindInProgress = !0, this.ctrl.bindError = !1; +this.ctrl.bindError = !1; var t = { namespace: r.get(this.ctrl.selectedProject, "metadata.name") }, n = "application" === this.ctrl.bindType ? this.ctrl.appToBind : void 0; -this.BindingService.bindService(this.ctrl.serviceInstance, n, this.ctrl.serviceClass.resource).then(function(n) { -e.ctrl.binding = n, e.ctrl.bindInProgress = !1, e.ctrl.bindComplete = !0, e.ctrl.bindError = null, e.watches.push(e.DataService.watchObject(e.BindingService.bindingResource, r.get(e.ctrl.binding, "metadata.name"), t, function(t) { +this.BindingService.bindService(this.ctrl.serviceInstance, n, this.ctrl.serviceClass.resource, this.ctrl.bindParameterData).then(function(n) { +e.ctrl.binding = n, e.watches.push(e.DataService.watchObject(e.BindingService.bindingResource, r.get(e.ctrl.binding, "metadata.name"), t, function(t) { e.ctrl.binding = t; })); }, function(t) { -e.ctrl.bindInProgress = !1, e.ctrl.bindComplete = !0, e.ctrl.bindError = t; +e.ctrl.bindError = t; }); }, e.prototype.$onDestroy = function() { -this.DataService.unwatchAll(this.watches), this.selectedProjectWatch(), this.clearValidityWatcher(); +this.DataService.unwatchAll(this.watches), this.selectedProjectWatch(), this.bindTypeWatch(), this.clearValidityWatcher(); }, e.prototype.closePanel = function() { i.isFunction(this.ctrl.handleClose) && this.ctrl.handleClose(); }, e.prototype.updateBindability = function() { if (!this.ctrl.wizardDone) { var e = r.get(this.ctrl.selectedPlan, "bindable"); -this.bindStep.hidden = !0 !== e && (!1 === e || !r.get(this.ctrl.serviceClass, "resource.bindable")), this.ctrl.configPageShown && (this.reviewStep.allowed = this.bindStep.hidden, this.bindStep.hidden ? this.ctrl.nextTitle = "Create" : this.ctrl.nextTitle = "Next >"); +this.bindStep.hidden = !0 !== e && (!1 === e || !r.get(this.ctrl.serviceClass, "resource.bindable")), this.updateBindParametersStepVisibility(), this.ctrl.configPageShown && (this.reviewStep.allowed = this.bindStep.hidden, this.bindStep.hidden ? this.ctrl.nextTitle = "Create" : this.ctrl.nextTitle = "Next >"); } +}, e.prototype.updateBindParametersStepVisibility = function() { +this.bindParametersStep.hidden = this.bindStep.hidden || "none" === this.ctrl.bindType || !r.has(this.ctrl, "bindParameterSchema.properties"), this.bindParametersStep.allowed = this.bindStep.valid; }, e.prototype.updateParameterSchema = function(t) { var n = r.get(t, "alphaInstanceCreateParameterSchema"); -r.has(n, [ "properties", e.REQUESTER_USERNAME_PARAM_NAME ]) ? (n = i.copy(n), delete n.properties[e.REQUESTER_USERNAME_PARAM_NAME], this.sendRequesterUsername = !0) : this.sendRequesterUsername = !1, this.ctrl.parameterSchema = n, this.ctrl.parameterFormDefinition = r.get(this, "ctrl.selectedPlan.externalMetadata.schemas.service_instance.create.openshift_form_definition"); +r.has(n, [ "properties", e.REQUESTER_USERNAME_PARAM_NAME ]) ? (n = i.copy(n), delete n.properties[e.REQUESTER_USERNAME_PARAM_NAME], this.sendRequesterUsername = !0) : this.sendRequesterUsername = !1, this.ctrl.parameterSchema = n, this.ctrl.parameterFormDefinition = r.get(this, "ctrl.selectedPlan.externalMetadata.schemas.service_instance.create.openshift_form_definition"), this.ctrl.bindParameterSchema = r.get(t, "alphaBindingCreateParameterSchema"); }, e.prototype.sortApplications = function() { if (this.deploymentConfigs && this.deployments && this.replicationControllers && this.replicaSets && this.statefulSets) { var e = this.deploymentConfigs.concat(this.deployments).concat(this.replicationControllers).concat(this.replicaSets).concat(this.statefulSets); @@ -78738,9 +78822,9 @@ e.exports = URI; "use strict"; t.__esModule = !0; var i = n(1); -n(3), n(28); -var r = n(29), o = n(30), a = n(18), s = n(19), l = n(31), c = n(20), u = n(21), d = n(22), h = n(23), f = n(24), p = n(25), g = n(26), m = n(27), v = n(32), b = n(17); +n(3), n(29); +var r = n(30), o = n(31), a = n(19), s = n(20), l = n(32), c = n(21), u = n(22), d = n(23), h = n(24), f = n(25), p = n(26), g = n(27), m = n(28), v = n(33), b = n(18); t.webCatalog = "webCatalog", i.module(t.webCatalog, [ "patternfly", "ngAnimate", "ui.bootstrap", "angularMoment", "ui.select", "schemaForm" ]).service("BuilderAppService", o.BuilderAppService).service("Catalog", l.CatalogService).service("RecentlyViewedServiceItems", v.RecentlyViewedServiceItems).filter("projectUrl", r.projectUrlFilter).component("catalogParameters", a.catalogParameters).component("catalogSearch", s.catalogSearch).component("createFromBuilder", c.createFromBuilder).component("landingPage", u.landingPage).component("orderService", d.orderService).component("overlayPanel", h.overlayPanel).component("projectsSummary", f.projectsSummary).component("saasList", p.saasList).component("selectProject", g.selectProject).component("servicesView", m.servicesView).component("catalogFilter", b.catalogFilter).run([ "$templateCache", function(e) { -e.put("catalog-search/catalog-search-result.html", n(4)), e.put("create-from-builder/create-from-builder-configure.html", n(6)), e.put("create-from-builder/create-from-builder-bind.html", n(5)), e.put("create-from-builder/create-from-builder-results.html", n(7)), e.put("order-service/order-service-plans.html", n(10)), e.put("order-service/order-service-configure.html", n(9)), e.put("order-service/order-service-bind.html", n(8)), e.put("order-service/order-service-review.html", n(11)), e.put("decorators/bootstrap/array.html", n(12)), e.put("decorators/bootstrap/checkbox.html", n(13)), e.put("decorators/bootstrap/checkboxes.html", n(14)), e.put("decorators/bootstrap/default.html", n(15)), e.put("decorators/bootstrap/select.html", n(16)); +e.put("catalog-search/catalog-search-result.html", n(4)), e.put("create-from-builder/create-from-builder-configure.html", n(6)), e.put("create-from-builder/create-from-builder-bind.html", n(5)), e.put("create-from-builder/create-from-builder-results.html", n(7)), e.put("order-service/order-service-plans.html", n(11)), e.put("order-service/order-service-configure.html", n(10)), e.put("order-service/order-service-bind.html", n(9)), e.put("order-service/order-service-bind-parameters.html", n(8)), e.put("order-service/order-service-review.html", n(12)), e.put("decorators/bootstrap/array.html", n(13)), e.put("decorators/bootstrap/checkbox.html", n(14)), e.put("decorators/bootstrap/checkboxes.html", n(15)), e.put("decorators/bootstrap/default.html", n(16)), e.put("decorators/bootstrap/select.html", n(17)); } ]); -} ], [ 56 ]); \ No newline at end of file +} ], [ 57 ]); \ No newline at end of file diff --git a/dist/styles/main.css b/dist/styles/main.css index 4dc6fe7ce0..26c4278b02 100644 --- a/dist/styles/main.css +++ b/dist/styles/main.css @@ -3919,9 +3919,9 @@ div.hopscotch-bubble .hopscotch-bubble-arrow-container.left,div.hopscotch-bubble .bind-info{margin-bottom:20px;margin-top:30px} .bind-service-selection{margin-bottom:5px} .bind-status{display:flex;font-size:16px;margin-top:0} +.bind-status.show-progress,.table-responsive .truncation-expand-link{display:block} .bind-status .bind-message{flex:1;line-height:1.4;margin-top:0;padding-left:10px} .bind-status .pficon{margin-top:3px} -.bind-status.show-progress{display:block} .bind-status.show-progress .bind-message{padding-left:0} .bind-status .spinner.spinner-sm{height:16px;margin-right:0;margin-top:3px;width:16px} .bind-pending-message{margin-top:15px} @@ -3932,9 +3932,12 @@ div.hopscotch-bubble .hopscotch-bubble-arrow-container.left,div.hopscotch-bubble .delete-resource-modal .help-block{margin-top:0px;margin-bottom:10px} .dialog-btn{float:right!important;margin-right:10px} .dialog-btn:first-of-type{margin-right:0} -.truncation-block{margin-right:10px} -.truncation-collapse-link{margin-left:10px;white-space:nowrap} .pretty-json{font-family:Menlo,Monaco,Consolas,monospace;white-space:pre-wrap} +.truncation-block{margin-right:10px} +.truncation-collapse-link{float:right;margin-left:10px;white-space:nowrap} +.table-responsive .truncation-collapse-link{display:block;float:none;margin-left:0} +.well .truncation-collapse-link{margin-top:-10px} +.truncation-expand-link{white-space:nowrap} label.required:before{content:'*';position:absolute;left:10px} @media (min-width:768px){.bind-form .application-select .ui-select-bootstrap>.ui-select-choices{max-height:170px} .form-horizontal label.required:before{position:relative;left:-3px} @@ -4265,13 +4268,13 @@ body.overlay-open,body.overlay-open .landing,body.overlay-open .landing-side-bar .services-view .services-item-icon{align-items:center;display:flex;font-size:48px;height:92px;justify-content:center;position:relative} .services-view .services-item-icon img{max-height:50px;max-width:50px} .services-view .services-item-name{flex:1 1 0%;font-size:14px;font-weight:600;display:-webkit-box;line-height:1.4em;max-height:4.2em;overflow:hidden;padding:0!important;-webkit-box-orient:vertical;-webkit-line-clamp:3;position:relative;word-wrap:break-word} -.services-view .services-items{align-content:flex-start;background-color:#fff;display:flex;flex:1 1 auto;flex-wrap:wrap;padding-bottom:30px;width:100%;z-index:9} +.services-view .services-items{align-content:flex-start;background-color:#fff;display:flex;flex:1 1 auto;flex-wrap:wrap;padding-bottom:30px;z-index:9} .services-view .services-items-filter{flex:1 1 100%;margin:20px 20px 10px} @media (min-width:480px){.services-view .services-sub-category .services-sub-category-tab:focus .services-sub-category-tab-image img,.services-view .services-sub-category .services-sub-category-tab:hover .services-sub-category-tab-image img,.services-view .services-sub-category.active .services-sub-category-tab .services-sub-category-tab-image img{opacity:1} .services-view .services-no-sub-categories{display:flex;flex:1 1 auto;padding:15px 20px 12px} .services-view .services-no-sub-categories .services-items{left:0;margin:0 0 8px;right:0} .services-view .services-sub-categories{align-content:flex-start;align-items:flex-start;display:flex;flex:1 1 auto;flex-wrap:wrap;padding:15px 20px 12px;position:relative} -.services-view .services-sub-categories .services-items{left:0;margin:10px 20px;position:absolute;right:0} +.services-view .services-sub-categories .services-items{left:20px;margin:10px 0;position:absolute;right:20px} .services-view .services-sub-category{background-color:#fff;display:inline-block;margin:0 3px 3px 0} .services-view .services-sub-category.active .services-sub-category-tab{color:#363636!important} .services-view .services-sub-category.active .services-sub-category-tab:after{animation:catalogItemFade .2s ease-out;border-bottom-color:#fff;border-bottom-style:solid;border-bottom-width:10px;border-left:10px solid transparent;border-right:10px solid transparent;bottom:-13px;content:"";display:block;left:50%;margin-left:-10px;opacity:1;position:absolute}