diff --git a/app/index.html b/app/index.html index 38f7c55735..809dc903be 100644 --- a/app/index.html +++ b/app/index.html @@ -259,6 +259,7 @@

JavaScript Required

+ @@ -277,6 +278,7 @@

JavaScript Required

+ diff --git a/app/scripts/app.js b/app/scripts/app.js index d528c53f17..5435182af2 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -381,6 +381,10 @@ angular templateUrl: 'views/util/logout.html', controller: 'LogoutController' }) + .when('/create', { + templateUrl: 'views/create-from-url.html', + controller: 'CreateFromURLController' + }) // legacy redirects .when('/createProject', { redirectTo: '/create-project' diff --git a/app/scripts/constants.js b/app/scripts/constants.js index b93b06acd9..5d8201672b 100644 --- a/app/scripts/constants.js +++ b/app/scripts/constants.js @@ -78,6 +78,10 @@ window.OPENSHIFT_CONSTANTS = { namespace: "openshift" }, + // only resources from the namespaces listed below can be utilized with create from url (/create) + // 'openshift' should always be included + CREATE_FROM_URL_WHITELIST: ['openshift'], + // href's will be prefixed with /project/{{projectName}} unless they are absolute URLs PROJECT_NAVIGATION: [ { diff --git a/app/scripts/controllers/create/createFromImage.js b/app/scripts/controllers/create/createFromImage.js index eb1393f48a..6791305fc6 100644 --- a/app/scripts/controllers/create/createFromImage.js +++ b/app/scripts/controllers/create/createFromImage.js @@ -29,7 +29,7 @@ angular.module("openshiftConsole") $scope.projectName = $routeParams.project; $scope.sourceURLPattern = SOURCE_URL_PATTERN; - var imageName = $routeParams.imageName; + var imageName = $routeParams.imageStream; if(!imageName){ Navigate.toErrorPage("Cannot create from source: a base image was not specified"); @@ -68,9 +68,13 @@ angular.module("openshiftConsole") $scope.project = project; // Update project breadcrumb with display name. $scope.breadcrumbs[0].title = $filter('displayName')(project); + if($routeParams.sourceURI) { + $scope.sourceURIinParams = true; + } function initAndValidate(scope){ scope.emptyMessage = "Loading..."; + scope.name = $routeParams.name; scope.imageName = imageName; scope.imageTag = $routeParams.imageTag; scope.namespace = $routeParams.namespace; @@ -80,7 +84,10 @@ angular.module("openshiftConsole") buildOnConfigChange: true, secrets: { gitSecret: [{name: ""}] - } + }, + sourceUrl: $routeParams.sourceURI, + gitRef: $routeParams.sourceRef, + contextDir: $routeParams.contextDir }; scope.buildConfigEnvVars = []; scope.deploymentConfig = { diff --git a/app/scripts/controllers/create/nextSteps.js b/app/scripts/controllers/create/nextSteps.js index 64aaa04fd3..af4ad218b7 100644 --- a/app/scripts/controllers/create/nextSteps.js +++ b/app/scripts/controllers/create/nextSteps.js @@ -20,17 +20,17 @@ angular.module("openshiftConsole") $scope.showParamsTable = false; $scope.projectName = $routeParams.project; - var imageName = $routeParams.imageName; + var imageName = $routeParams.imageStream; var imageTag = $routeParams.imageTag; var namespace = $routeParams.namespace; $scope.fromSampleRepo = $routeParams.fromSample; - var name = $routeParams.name; + var template = $routeParams.template; var nameLink = ""; if (creatingFromImage()) { nameLink = "project/" + $scope.projectName + "/create/fromimage?imageName=" + imageName + "&imageTag=" + imageTag + "&namespace=" + namespace + "&name=" + name; } else if (creatingFromTemplate()) { - nameLink = "project/" + $scope.projectName + "/create/fromtemplate?name=" + name + "&namespace=" + namespace; + nameLink = "project/" + $scope.projectName + "/create/fromtemplate?template=" + template + "&namespace=" + namespace; } $scope.breadcrumbs = [ diff --git a/app/scripts/controllers/createFromURL.js b/app/scripts/controllers/createFromURL.js new file mode 100644 index 0000000000..c72748b0a1 --- /dev/null +++ b/app/scripts/controllers/createFromURL.js @@ -0,0 +1,183 @@ +'use strict'; + +/** + * @ngdoc function + * @name openshiftConsole.controller:CreateFromURLController + * @description + * Controller of the openshiftConsole + */ +angular.module('openshiftConsole') + .controller('CreateFromURLController', function ($scope, $routeParams, $location, $filter, AuthService, DataService, AlertMessageService, Navigate, ProjectsService ) { + AuthService.withUser(); + + AlertMessageService.getAlerts().forEach(function(alert) { + $scope.alerts[alert.name] = alert.data; + }); + AlertMessageService.clearAlerts(); + + $scope.alerts = {}; + $scope.selected = {}; + + var alertInvalidImageStream = function(imageStream) { + $scope.alerts.invalidImageStream = { + type: "error", + message: "The requested image stream \"" + imageStream + "\" could not be loaded." + }; + }; + + var alertInvalidImageTag = function(imageTag) { + $scope.alerts.invalidImageTag = { + type: "error", + message: "The requested image stream tag \"" + imageTag + "\" could not be loaded." + }; + }; + + var alertInvalidName = function(name) { + $scope.alerts.invalidImageStream = { + type: "error", + message: "The app name \"" + name + "\" is not valid. An app name is an alphanumeric (a-z, and 0-9) string with a maximum length of 24 characters, where the first character is a letter (a-z), and the '-' character is allowed anywhere except the first or last character." + }; + }; + + var alertInvalidNamespace = function(namespace) { + $scope.alerts.invalidNamespace = { + type: "error", + message: "Resources from the namespace \"" + namespace + "\" are not permitted." + }; + }; + + var alertInvalidTemplate = function(template) { + $scope.alerts.invalidTemplate = { + type: "error", + message: "The requested template \"" + template + "\" could not be loaded." + }; + }; + + var alertResourceRequired = function() { + $scope.alerts.resourceRequired = { + type: "error", + message: "An image stream or template is required." + }; + }; + + var showInvalidResource = function() { + $scope.alerts.invalidResource = { + type: "error", + message: "Image streams and templates cannot be combined." + }; + }; + + var getTemplateParamsMap = function () { + try { + return $routeParams.templateParamsMap && JSON.parse($routeParams.templateParamsMap) || {}; + } + catch (e) { + $scope.alerts.invalidTemplateParams = { + type: "error", + message: "The templateParamsMap is not valid JSON. " + e + }; + } + }; + + var namespaceWhitelist = window.OPENSHIFT_CONSTANTS.CREATE_FROM_URL_WHITELIST; + var whiteListedCreateDetailsKeys = ['namespace', 'name', 'imageStream', 'imageTag', 'sourceURI', 'sourceRef', 'contextDir', 'template', 'templateParamsMap']; + var createDetails = _.pick($routeParams, function(value, key) { + // routeParams without a value (e.g., ?name&) return true, which results in "true" displaying in the UI + return _.contains(whiteListedCreateDetailsKeys, key) && _.isString(value); + }); + // if no namespace is specified, set it to 'openshift' + createDetails.namespace = createDetails.namespace || 'openshift'; + + var validateName = function (name) { + return name.length < 25 && /^[a-z]([-a-z0-9]*[a-z0-9])?$/.test(name); + }; + + var getResources = function() { + if (createDetails.imageStream) { + DataService + .get("imagestreams", createDetails.imageStream, {namespace: createDetails.namespace}, { + errorNotification: false + }) + .then(function(imageStream) { + $scope.imageStream = imageStream; + DataService + .get("imagestreamtags", imageStream.metadata.name + ":" + createDetails.imageTag, {namespace: createDetails.namespace}, { + errorNotification: false + }) + .then(function(imageStreamTag){ + $scope.imageStreamTag = imageStreamTag; + $scope.validationPassed = true; + $scope.resource = imageStreamTag; + createDetails.displayName = $filter('displayName')(imageStreamTag); + }, function(){ + alertInvalidImageTag(createDetails.imageTag); + }); + }, function() { + alertInvalidImageStream(createDetails.imageStream); + }); + } + if (createDetails.template) { + DataService + .get("templates", createDetails.template, {namespace: createDetails.namespace}, { + errorNotification: false + }) + .then(function(template) { + $scope.template = template; + if(getTemplateParamsMap()) { + $scope.validationPassed = true; + $scope.resource = template; + } + }, function() { + alertInvalidTemplate(createDetails.template); + }); + } + }; + + if (!(_.includes(namespaceWhitelist, createDetails.namespace))) { + alertInvalidNamespace(createDetails.namespace); + } else { + if (createDetails.imageStream && createDetails.template) { + showInvalidResource(); + } else if (!(createDetails.imageStream) && !(createDetails.template)) { + alertResourceRequired(); + } else if (createDetails.name && !(validateName(createDetails.name))) { + alertInvalidName(createDetails.name); + } else { + getResources(); + } + } + + angular.extend($scope, { + createDetails: createDetails, + createWithProject: function(projectName) { + projectName = projectName || $scope.selected.project.metadata.name; + var url = $routeParams.imageStream ? + Navigate.createFromImageURL($scope.imageStream, createDetails.imageTag, projectName, createDetails) : + Navigate.createFromTemplateURL($scope.template, projectName, createDetails); + $location.url(url); + } + }); + + $scope.projects = {}; + $scope.canCreateProject = undefined; + + DataService + .list("projects", $scope) + .then(function(items) { + $scope.loaded = true; + $scope.projects = $filter('orderByDisplayName')(items.by("metadata.name")); + $scope.noProjects = (_.isEmpty($scope.projects)); + }); + + + // Test if the user can submit project requests. Handle error notifications + // ourselves because 403 responses are expected. + ProjectsService + .canCreate() + .then(function() { + $scope.canCreateProject = true; + }, function() { + $scope.canCreateProject = false; + }); + + }); diff --git a/app/scripts/controllers/createProject.js b/app/scripts/controllers/createProject.js index 457be0c72f..7fbb802a34 100644 --- a/app/scripts/controllers/createProject.js +++ b/app/scripts/controllers/createProject.js @@ -18,30 +18,4 @@ angular.module('openshiftConsole') }); AlertMessageService.clearAlerts(); - $scope.createProject = function() { - $scope.disableInputs = true; - if ($scope.createProjectForm.$valid) { - DataService.create('projectrequests', null, { - apiVersion: "v1", - kind: "ProjectRequest", - metadata: { - name: $scope.name - }, - displayName: $scope.displayName, - description: $scope.description - }, $scope).then(function(data) { // Success - // Take the user directly to the create page to add content. - $location.path("project/" + encodeURIComponent(data.metadata.name) + "/create"); - }, function(result) { // Failure - $scope.disableInputs = false; - var data = result.data || {}; - if (data.reason === 'AlreadyExists') { - $scope.nameTaken = true; - } else { - var msg = data.message || 'An error occurred creating the project.'; - $scope.alerts['error-creating-project'] = {type: 'error', message: msg}; - } - }); - } - }; }); diff --git a/app/scripts/controllers/newfromtemplate.js b/app/scripts/controllers/newfromtemplate.js index d4a00cd110..27c9afa750 100644 --- a/app/scripts/controllers/newfromtemplate.js +++ b/app/scripts/controllers/newfromtemplate.js @@ -31,8 +31,7 @@ angular.module('openshiftConsole') keyValueEditorUtils, Constants) { - - var name = $routeParams.name; + var name = $routeParams.template; // If the namespace is not defined, that indicates that the processed Template should be obtained from the 'CachedTemplateService' var namespace = $routeParams.namespace || ""; @@ -44,6 +43,7 @@ angular.module('openshiftConsole') $scope.emptyMessage = "Loading..."; $scope.alerts = {}; + $scope.alertsTop = {}; $scope.projectName = $routeParams.project; $scope.projectPromise = $.Deferred(); $scope.labels = []; @@ -80,6 +80,18 @@ angular.module('openshiftConsole') var builderImage = $parse('spec.strategy.sourceStrategy.from || spec.strategy.dockerStrategy.from || spec.strategy.customStrategy.from'); var outputImage = $parse('spec.output.to'); + var getValidTemplateParamsMap = function () { + try { + return JSON.parse($routeParams.templateParamsMap); + } + catch (e) { + $scope.alertsTop.invalidTemplateParams = { + type: "error", + message: "The templateParamsMap is not valid JSON. " + e + }; + } + }; + ProjectsService .get($routeParams.project) .then(_.spread(function(project, context) { @@ -356,6 +368,15 @@ angular.module('openshiftConsole') $scope.parameterDisplayNames[parameter.name] = parameter.displayName || parameter.name; }); + if($routeParams.templateParamsMap) { + var templateParams = getValidTemplateParamsMap(); + _.each($scope.template.parameters, function(parameter) { + if (templateParams[parameter.name]) { + parameter.value = templateParams[parameter.name]; + } + }); + } + findTemplateImages($scope.template); var imageUsesParameters = function(image) { return !_.isEmpty(image.usesParameters); diff --git a/app/scripts/controllers/projects.js b/app/scripts/controllers/projects.js index 80f16318e6..a53baeeb67 100644 --- a/app/scripts/controllers/projects.js +++ b/app/scripts/controllers/projects.js @@ -17,7 +17,8 @@ angular.module('openshiftConsole') AuthService, DataService, KeywordService, - Logger) { + Logger, + ProjectsService) { var projects, sortedProjects; var watches = []; var filterKeywords = []; @@ -129,7 +130,8 @@ angular.module('openshiftConsole') // Test if the user can submit project requests. Handle error notifications // ourselves because 403 responses are expected. - DataService.get("projectrequests", null, $scope, { errorNotification: false}) + ProjectsService + .canCreate() .then(function() { $scope.canCreate = true; }, function(result) { diff --git a/app/scripts/directives/createProject.js b/app/scripts/directives/createProject.js new file mode 100644 index 0000000000..0309143b71 --- /dev/null +++ b/app/scripts/directives/createProject.js @@ -0,0 +1,53 @@ +"use strict"; + +angular.module("openshiftConsole") + + .directive("createProject", function() { + return { + restrict: 'E', + scope: { + alerts: '=', + submitButtonLabel: '@', + redirectAction: '&' + }, + templateUrl: 'views/directives/_create-project-form.html', + controller: function($scope, $filter, $location, DataService) { + if(!($scope.submitButtonLabel)) { + $scope.submitButtonLabel = 'Create'; + } + $scope.createProject = function() { + $scope.disableInputs = true; + if ($scope.createProjectForm.$valid) { + DataService + .create('projectrequests', null, { + apiVersion: "v1", + kind: "ProjectRequest", + metadata: { + name: $scope.name + }, + displayName: $scope.displayName, + description: $scope.description + }, $scope) + .then(function(data) { + // angular is actually wrapping the redirect action :/ + var cb = $scope.redirectAction(); + if(cb) { + cb(encodeURIComponent(data.metadata.name)); + } else { + $location.path("project/" + encodeURIComponent(data.metadata.name) + "/create"); + } + }, function(result) { + $scope.disableInputs = false; + var data = result.data || {}; + if (data.reason === 'AlreadyExists') { + $scope.nameTaken = true; + } else { + var msg = data.message || 'An error occurred creating the project.'; + $scope.alerts['error-creating-project'] = {type: 'error', message: msg}; + } + }); + } + }; + }, + }; + }); diff --git a/app/scripts/directives/fromFile.js b/app/scripts/directives/fromFile.js index a2c7a94269..f102b63e48 100644 --- a/app/scripts/directives/fromFile.js +++ b/app/scripts/directives/fromFile.js @@ -248,7 +248,7 @@ angular.module("openshiftConsole") var path; if ($scope.resourceKind === "Template" && $scope.templateOptions.process && !$scope.errorOccured) { var namespace = ($scope.templateOptions.add || $scope.updateResources.length > 0) ? $scope.projectName : ""; - path = Navigate.fromTemplateURL($scope.projectName, $scope.resourceName, namespace); + path = Navigate.createFromTemplateURL($scope.resourceName, $scope.projectName, {namespace: namespace}); } else { path = Navigate.projectOverviewURL($scope.projectName); } diff --git a/app/scripts/filters/resources.js b/app/scripts/filters/resources.js index f3396dce80..17f3eca3cd 100644 --- a/app/scripts/filters/resources.js +++ b/app/scripts/filters/resources.js @@ -121,6 +121,9 @@ angular.module('openshiftConsole') return nameCount; } return function (resource, projects){ + if (!resource) { + return ''; + } var displayName = displayNameFilter(resource); var name = resource.metadata.name; if (displayName !== name && countNames(projects)[displayName] > 1 ){ @@ -129,6 +132,14 @@ angular.module('openshiftConsole') return displayName; }; }) + .filter('searchProjects', function(annotationNameFilter) { + return function(projects, text) { + return _.filter(projects, function(project) { + return _.includes(project.metadata.name, text) || + _.includes(project.metadata.annotations[annotationNameFilter('displayName')], text); + }); + }; + }) .filter('tags', function(annotationFilter) { return function(resource, /* optional */ annotationKey) { annotationKey = annotationKey || "tags"; @@ -365,7 +376,7 @@ angular.module('openshiftConsole') if(omitPath) { return label; } - + if (route.spec.path) { label += route.spec.path; } @@ -620,30 +631,14 @@ angular.module('openshiftConsole') return createURI.toString(); }; }) - .filter('createFromImageURL', function(displayNameFilter) { - return function(imageStream, imageTag, projectName) { - var createURI = URI.expand("project/{project}/create/fromimage{?q*}", { - project: projectName, - q: { - imageName: imageStream.metadata.name, - imageTag: imageTag, - namespace: imageStream.metadata.namespace, - displayName: displayNameFilter(imageStream) - } - }); - return createURI.toString(); + .filter('createFromImageURL', function(Navigate) { + return function(imageStream, imageTag, projectName, queryParams) { + return Navigate.createFromImageURL(imageStream, imageTag, projectName, queryParams); }; }) - .filter('createFromTemplateURL', function() { - return function(template, projectName) { - var createURI = URI.expand("project/{project}/create/fromtemplate{?q*}", { - project: projectName, - q: { - name: template.metadata.name, - namespace: template.metadata.namespace - } - }); - return createURI.toString(); + .filter('createFromTemplateURL', function(Navigate) { + return function(template, projectName, queryParams) { + return Navigate.createFromTemplateURL(template, projectName, queryParams); }; }) .filter('failureObjectName', function() { diff --git a/app/scripts/services/navigate.js b/app/scripts/services/navigate.js index 991716eb92..4ae35bcab6 100644 --- a/app/scripts/services/navigate.js +++ b/app/scripts/services/navigate.js @@ -11,6 +11,7 @@ angular.module("openshiftConsole") var annotation = $filter('annotation'); var buildConfigForBuild = $filter('buildConfigForBuild'); var isPipeline = $filter('isJenkinsPipelineStrategy'); + var displayNameFilter = $filter('displayName'); // Get the type segment for build URLs. `resource` can be a build or build config. var getBuildURLType = function(resource, opts) { @@ -69,18 +70,28 @@ angular.module("openshiftConsole") return "project/" + encodeURIComponent(projectName) + "/overview"; }, - /** - * Return the URL for the fromTemplate page for the picked template - * - * @param {String} projectName Project name - * @param {String} name Template name - * @param {String} namespace Namespace from which the Template should be loaded - * @returns {String} a URL string for the fromTemplate page. If the namespace is not set - * read the template from TemplateService. - */ - fromTemplateURL: function(projectName, name, namespace){ - namespace = namespace || ""; - return "project/" + encodeURIComponent(projectName) + "/create/fromtemplate?name=" + name + "&namespace=" + namespace; + createFromImageURL: function(imageStream, imageTag, projectName, queryParams) { + var createURI = URI.expand("project/{project}/create/fromimage{?q*}", { + project: projectName, + q: angular.extend ({ + imageStream: imageStream.metadata.name, + imageTag: imageTag, + namespace: imageStream.metadata.namespace, + displayName: displayNameFilter(imageStream) + }, queryParams || {}) + }); + return createURI.toString(); + }, + + createFromTemplateURL: function(template, projectName, queryParams) { + var createURI = URI.expand("project/{project}/create/fromtemplate{?q*}", { + project: projectName, + q: angular.extend ({ + template: template.metadata.name, + namespace: template.metadata.namespace + }, queryParams || {}) + }); + return createURI.toString(); }, /** diff --git a/app/scripts/services/projects.js b/app/scripts/services/projects.js index 4e58c30e1c..1e924a5018 100644 --- a/app/scripts/services/projects.js +++ b/app/scripts/services/projects.js @@ -32,7 +32,8 @@ angular.module('openshiftConsole') return DataService .get('projects', projectName, context, {errorNotification: false}) .then(function(project) { - return AuthorizationService.getProjectRules(projectName) + return AuthorizationService + .getProjectRules(projectName) .then(function() { context.project = project; context.projectPromise.resolve(project); @@ -64,6 +65,9 @@ angular.module('openshiftConsole') update: function(projectName, data) { return DataService .update('projects', projectName, cleanEditableAnnotations(data), {projectName: projectName}, {errorNotification: false}); + }, + canCreate: function() { + return DataService.get("projectrequests", null, {}, { errorNotification: false}); } }; }); diff --git a/app/styles/_projects.less b/app/styles/_projects.less index 525169cf7f..eaa5a25e5e 100644 --- a/app/styles/_projects.less +++ b/app/styles/_projects.less @@ -8,6 +8,10 @@ margin-top: 20px; } +.empty-state-message .projects-instructions code { + display: inline-block; +} + .project-actions { margin: 20px 0 0; @media (min-width: @screen-md-min) { diff --git a/app/views/_cannot-create-project.html b/app/views/_cannot-create-project.html new file mode 100644 index 0000000000..b57c88f7ac --- /dev/null +++ b/app/views/_cannot-create-project.html @@ -0,0 +1,3 @@ +A cluster admin can create a project for you by running the command +oadm new-project <projectname> --admin={{user.metadata.name || '<YourUsername>'}} + diff --git a/app/views/create-from-url.html b/app/views/create-from-url.html new file mode 100644 index 0000000000..8038b4d8b4 --- /dev/null +++ b/app/views/create-from-url.html @@ -0,0 +1,76 @@ + +
+ +
+ +
+
+
+
+
+
+
Loading...
+
+ + +

Source code from {{createDetails.sourceURI}} will be built and deployed unless otherwise specified in the next step.

+
+
+

Create a New Project

+ +
+
+

Choose a Project

+ + + {{$select.selected | uniqueDisplayName : projects}} + + +
+
+
+
+
+ + + Choose Existing Project + + + {{$select.selected | uniqueDisplayName : projects}} + + +
+
+
+
+ + Cancel +
+
+ + Create a New Project + + +
+
+

+ A project is required in order to complete the installation. + +

+
+
+
+
+
+
+
+
+
+
diff --git a/app/views/create-project.html b/app/views/create-project.html index d0299f529c..5322fef734 100644 --- a/app/views/create-project.html +++ b/app/views/create-project.html @@ -12,79 +12,7 @@

New Project

-
-
-
- - - - -
- A unique name for the project. -
-
- - Name must have at least two characters. - -
-
- - Project names may only contain lower-case letters, numbers, and dashes. - They may not start or end with a dash. - -
-
- - This name is already in use. Please choose a different name. - -
-
- -
- - -
- -
- - -
- -
- - Cancel -
-
-
+
diff --git a/app/views/create/fromimage.html b/app/views/create/fromimage.html index c6a9e48c5e..ba6c0058ce 100644 --- a/app/views/create/fromimage.html +++ b/app/views/create/fromimage.html @@ -88,7 +88,7 @@ autocapitalize="off" spellcheck="false"> -
+
Sample repository for {{imageName}}: {{image.metadata.annotations.sampleRepo}}, ref: {{image.metadata.annotations.sampleRef}}, context dir: {{image.metadata.annotations.sampleContextDir}} diff --git a/app/views/directives/_create-project-form.html b/app/views/directives/_create-project-form.html new file mode 100644 index 0000000000..80449d6fe0 --- /dev/null +++ b/app/views/directives/_create-project-form.html @@ -0,0 +1,73 @@ +
+
+
+ + + + +
+ A unique name for the project. +
+
+ + Name must have at least two characters. + +
+
+ + Project names may only contain lower-case letters, numbers, and dashes. + They may not start or end with a dash. + +
+
+ + This name is already in use. Please choose a different name. + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + Cancel +
+
+
diff --git a/app/views/newfromtemplate.html b/app/views/newfromtemplate.html index b49040b44e..1140b51b6b 100644 --- a/app/views/newfromtemplate.html +++ b/app/views/newfromtemplate.html @@ -16,6 +16,7 @@ {{ emptyMessage }}
+
-

- A cluster admin can create a project for you by running the command - oadm new-project <projectname> --admin={{user.metadata.name || '<YourUsername>'}} - -

+

@@ -138,11 +134,7 @@

Welcome to OpenShift.

New Project

To learn more, visit the OpenShift documentation.

-

- A cluster admin can create a project for you by running the command
- oadm new-project <projectname> --admin={{user.metadata.name || '<YourUsername>'}}
- -

+

diff --git a/dist/scripts/scripts.js b/dist/scripts/scripts.js index 82355c0742..5cdc205ac4 100644 --- a/dist/scripts/scripts.js +++ b/dist/scripts/scripts.js @@ -54,6 +54,7 @@ SAMPLE_PIPELINE_TEMPLATE:{ name:"jenkins-pipeline-example", namespace:"openshift" }, +CREATE_FROM_URL_WHITELIST:[ "openshift" ], PROJECT_NAVIGATION:[ { label:"Overview", iconClass:"fa fa-dashboard", @@ -509,6 +510,9 @@ controller:"ErrorController" }).when("/logout", { templateUrl:"views/util/logout.html", controller:"LogoutController" +}).when("/create", { +templateUrl:"views/create-from-url.html", +controller:"CreateFromURLController" }).when("/createProject", { redirectTo:"/create-project" }).when("/project/:project/createRoute", { @@ -1697,6 +1701,11 @@ projectName:a }, { errorNotification:!1 }); +}, +canCreate:function() { +return e.get("projectrequests", null, {}, { +errorNotification:!1 +}); } }; } ]), angular.module("openshiftConsole").service("ApplicationGenerator", [ "DataService", "APIService", "Logger", "$parse", "$q", function(a, b, c, d, e) { @@ -2127,7 +2136,7 @@ return f["delete"]("oauthaccesstokens", e, {}, g); }; } ]; }), angular.module("openshiftConsole").service("Navigate", [ "$location", "$window", "$timeout", "annotationFilter", "LabelFilter", "$filter", "APIService", function(a, b, c, d, e, f, g) { -var h = f("annotation"), i = f("buildConfigForBuild"), j = f("isJenkinsPipelineStrategy"), k = function(a, b) { +var h = f("annotation"), i = f("buildConfigForBuild"), j = f("isJenkinsPipelineStrategy"), k = f("displayName"), l = function(a, b) { return _.get(b, "isPipeline") ? "pipelines" :_.isObject(a) && j(a) ? "pipelines" :"builds"; }; return { @@ -2144,8 +2153,27 @@ a.path(this.projectOverviewURL(b)); projectOverviewURL:function(a) { return "project/" + encodeURIComponent(a) + "/overview"; }, -fromTemplateURL:function(a, b, c) { -return c = c || "", "project/" + encodeURIComponent(a) + "/create/fromtemplate?name=" + b + "&namespace=" + c; +createFromImageURL:function(a, b, c, d) { +var e = URI.expand("project/{project}/create/fromimage{?q*}", { +project:c, +q:angular.extend({ +imageStream:a.metadata.name, +imageTag:b, +namespace:a.metadata.namespace, +displayName:k(a) +}, d || {}) +}); +return e.toString(); +}, +createFromTemplateURL:function(a, b, c) { +var d = URI.expand("project/{project}/create/fromtemplate{?q*}", { +project:b, +q:angular.extend({ +template:a.metadata.name, +namespace:a.metadata.namespace +}, c || {}) +}); +return d.toString(); }, toNextSteps:function(b, c, d) { var e = a.search(); @@ -2164,12 +2192,12 @@ a.metadata && (h = a.metadata.name); var i = URI("").segment("project").segmentCoded(c).segment(d); switch (b) { case "Build": -var j = f("buildConfigForBuild")(a), l = k(a, e); -j ? i.segment(l).segmentCoded(j).segmentCoded(h) :i.segment(l + "-noconfig").segmentCoded(h); +var j = f("buildConfigForBuild")(a), k = l(a, e); +j ? i.segment(k).segmentCoded(j).segmentCoded(h) :i.segment(k + "-noconfig").segmentCoded(h); break; case "BuildConfig": -i.segment(k(a, e)).segmentCoded(h); +i.segment(l(a, e)).segmentCoded(h); break; case "ConfigMap": @@ -3930,34 +3958,34 @@ controller:"JenkinsfileExamplesModalController" }); } }; -} ]), angular.module("openshiftConsole").controller("ProjectsController", [ "$scope", "$filter", "$location", "$route", "$timeout", "AlertMessageService", "AuthService", "DataService", "KeywordService", "Logger", function(a, b, c, d, e, f, g, h, i, j) { -var k, l, m = [], n = []; +} ]), angular.module("openshiftConsole").controller("ProjectsController", [ "$scope", "$filter", "$location", "$route", "$timeout", "AlertMessageService", "AuthService", "DataService", "KeywordService", "Logger", "ProjectsService", function(a, b, c, d, e, f, g, h, i, j, k) { +var l, m, n = [], o = []; a.alerts = a.alerts || {}, a.loading = !0, a.showGetStarted = !1, a.canCreate = void 0, a.search = { text:"" }; -var o, p = [ "metadata.name", 'metadata.annotations["openshift.io/display-name"]', 'metadata.annotations["openshift.io/description"]', 'metadata.annotations["openshift.io/requester"]' ], q = function() { -a.projects = i.filterForKeywords(l, p, n); -}, r = b("displayName"), s = function() { +var p, q = [ "metadata.name", 'metadata.annotations["openshift.io/display-name"]', 'metadata.annotations["openshift.io/description"]', 'metadata.annotations["openshift.io/requester"]' ], r = function() { +a.projects = i.filterForKeywords(m, q, o); +}, s = b("displayName"), t = function() { var b = _.get(a, "sortConfig.currentField.id"); -o !== b && (a.sortConfig.isAscending = "metadata.creationTimestamp" !== b); +p !== b && (a.sortConfig.isAscending = "metadata.creationTimestamp" !== b); var c = function(a) { -return r(a).toLowerCase(); +return s(a).toLowerCase(); }, d = a.sortConfig.isAscending ? "asc" :"desc"; switch (b) { case 'metadata.annotations["openshift.io/display-name"]': -l = _.sortByOrder(k, [ c ], [ d ]); +m = _.sortByOrder(l, [ c ], [ d ]); break; case 'metadata.annotations["openshift.io/requester"]': -l = _.sortByOrder(k, [ b, c ], [ d, "asc" ]); +m = _.sortByOrder(l, [ b, c ], [ d, "asc" ]); break; default: -l = _.sortByOrder(k, [ b ], [ d ]); +m = _.sortByOrder(l, [ b ], [ d ]); } -o = b; -}, t = function() { -s(), q(); +p = b; +}, u = function() { +t(), r(); }; a.sortConfig = { fields:[ { @@ -3978,20 +4006,18 @@ title:"Creation Date", sortType:"alpha" } ], isAscending:!0, -onSortChange:t +onSortChange:u }, f.getAlerts().forEach(function(b) { a.alerts[b.name] = b.data; }), f.clearAlerts(), a.$watch("search.text", _.debounce(function(b) { -a.keywords = n = i.generateKeywords(b), a.$apply(q); +a.keywords = o = i.generateKeywords(b), a.$apply(r); }, 50, { maxWait:250 })), g.withUser().then(function() { -m.push(h.watch("projects", a, function(b) { -k = _.toArray(b.by("metadata.name")), a.loading = !1, a.showGetStarted = _.isEmpty(k), t(); +n.push(h.watch("projects", a, function(b) { +l = _.toArray(b.by("metadata.name")), a.loading = !1, a.showGetStarted = _.isEmpty(l), u(); })); -}), h.get("projectrequests", null, a, { -errorNotification:!1 -}).then(function() { +}), k.canCreate().then(function() { a.canCreate = !0; }, function(b) { a.canCreate = !1; @@ -4007,7 +4033,7 @@ a.message && e.push(a.message); }), e.length > 0 && (a.newProjectMessage = e.join("\n")); } }), a.$on("$destroy", function() { -h.unwatchAll(m); +h.unwatchAll(n); }); } ]), angular.module("openshiftConsole").controller("PodsController", [ "$routeParams", "$scope", "DataService", "ProjectsService", "AlertMessageService", "$filter", "LabelFilter", "Logger", function(a, b, c, d, e, f, g, h) { b.projectName = a.project, b.pods = {}, b.unfilteredPods = {}, b.labelSuggestions = {}, b.alerts = b.alerts || {}, b.emptyMessage = "Loading...", e.getAlerts().forEach(function(a) { @@ -7486,7 +7512,7 @@ a.projectTemplates = b.by("metadata.name"); } ]), angular.module("openshiftConsole").controller("CreateFromImageController", [ "$scope", "Logger", "$q", "$routeParams", "APIService", "DataService", "ProjectsService", "Navigate", "ApplicationGenerator", "LimitRangesService", "MetricsService", "HPAService", "QuotaService", "SecretsService", "ImagesService", "TaskList", "failureObjectNameFilter", "$filter", "$parse", "$uibModal", "SOURCE_URL_PATTERN", "keyValueEditorUtils", function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v) { var w = r("displayName"), x = r("humanize"); a.projectName = d.project, a.sourceURLPattern = u; -var y = d.imageName; +var y = d.imageStream; if (!y) return void h.toErrorPage("Cannot create from source: a base image was not specified"); if (!d.imageTag) return void h.toErrorPage("Cannot create from source: a base image tag was not specified"); a.displayName = d.displayName, a.breadcrumbs = [ { @@ -7507,7 +7533,7 @@ value:"" }; g.get(d.project).then(_.spread(function(e, g) { function q(b) { -b.emptyMessage = "Loading...", b.imageName = y, b.imageTag = d.imageTag, b.namespace = d.namespace, b.buildConfig = { +b.emptyMessage = "Loading...", b.name = d.name, b.imageName = y, b.imageTag = d.imageTag, b.namespace = d.namespace, b.buildConfig = { buildOnSourceChange:!0, buildOnImageChange:!0, buildOnConfigChange:!0, @@ -7515,7 +7541,10 @@ secrets:{ gitSecret:[ { name:"" } ] -} +}, +sourceUrl:d.sourceURI, +gitRef:d.sourceRef, +contextDir:d.contextDir }, b.buildConfigEnvVars = [], b.deploymentConfig = { deployOnNewImage:!0, deployOnConfigChange:!0 @@ -7572,7 +7601,7 @@ h.toErrorPage("Cannot create from source: the specified image could not be retri h.toErrorPage("Cannot create from source: the specified image could not be retrieved."); }); } -a.project = e, a.breadcrumbs[0].title = r("displayName")(e); +a.project = e, a.breadcrumbs[0].title = r("displayName")(e), d.sourceURI && (a.sourceURIinParams = !0); var s = function() { a.hideCPU || (a.cpuProblems = j.validatePodLimits(a.limitRanges, "cpu", [ a.container ], e)), a.memoryProblems = j.validatePodLimits(a.limitRanges, "memory", [ a.container ], e); }; @@ -7669,24 +7698,24 @@ f.then(j, j).then(F, F); })); } ]), angular.module("openshiftConsole").controller("NextStepsController", [ "$scope", "$http", "$routeParams", "DataService", "$q", "$location", "ProcessedTemplateService", "TaskList", "$parse", "Navigate", "Logger", "$filter", "imageObjectRefFilter", "failureObjectNameFilter", "ProjectsService", function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) { function p() { -return v && u; +return name && u; } function q() { return s && t && u; } var r = (l("displayName"), []); a.emptyMessage = "Loading...", a.alerts = [], a.loginBaseUrl = d.openshiftAPIBaseUrl(), a.buildConfigs = {}, a.showParamsTable = !1, a.projectName = c.project; -var s = c.imageName, t = c.imageTag, u = c.namespace; +var s = c.imageStream, t = c.imageTag, u = c.namespace; a.fromSampleRepo = c.fromSample; -var v = c.name, w = ""; -q() ? w = "project/" + a.projectName + "/create/fromimage?imageName=" + s + "&imageTag=" + t + "&namespace=" + u + "&name=" + v :p() && (w = "project/" + a.projectName + "/create/fromtemplate?name=" + v + "&namespace=" + u), a.breadcrumbs = [ { +var v = c.template, w = ""; +q() ? w = "project/" + a.projectName + "/create/fromimage?imageName=" + s + "&imageTag=" + t + "&namespace=" + u + "&name=" + name :p() && (w = "project/" + a.projectName + "/create/fromtemplate?template=" + v + "&namespace=" + u), a.breadcrumbs = [ { title:a.projectName, link:"project/" + a.projectName }, { title:"Add to Project", link:"project/" + a.projectName + "/create" }, { -title:v, +title:name, link:w }, { title:"Next Steps" @@ -7707,8 +7736,8 @@ return angular.forEach(a, function(a) { "completed" !== a.status && b.push(a); }), b; } -return a.project = b, a.breadcrumbs[0].title = l("displayName")(b), v ? (r.push(d.watch("buildconfigs", c, function(b) { -a.buildConfigs = b.by("metadata.name"), a.createdBuildConfig = a.buildConfigs[v], k.log("buildconfigs (subscribe)", a.buildConfigs); +return a.project = b, a.breadcrumbs[0].title = l("displayName")(b), name ? (r.push(d.watch("buildconfigs", c, function(b) { +a.buildConfigs = b.by("metadata.name"), a.createdBuildConfig = a.buildConfigs[name], k.log("buildconfigs (subscribe)", a.buildConfigs); })), a.createdBuildConfigWithGitHubTrigger = function() { return _.some(_.get(a, "createdBuildConfig.spec.triggers"), { type:"GitHub" @@ -7726,9 +7755,9 @@ d.unwatchAll(r); })) :void j.toProjectOverview(a.projectName); })); } ]), angular.module("openshiftConsole").controller("NewFromTemplateController", [ "$scope", "$http", "$routeParams", "DataService", "ProcessedTemplateService", "AlertMessageService", "ProjectsService", "QuotaService", "$q", "$location", "TaskList", "$parse", "Navigate", "$filter", "$uibModal", "imageObjectRefFilter", "failureObjectNameFilter", "CachedTemplateService", "keyValueEditorUtils", "Constants", function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t) { -var u = c.name, v = c.namespace || ""; +var u = c.template, v = c.namespace || ""; if (!u) return void m.toErrorPage("Cannot create from template: a template name was not specified."); -a.emptyMessage = "Loading...", a.alerts = {}, a.projectName = c.project, a.projectPromise = $.Deferred(), a.labels = [], a.systemLabels = [], a.breadcrumbs = [ { +a.emptyMessage = "Loading...", a.alerts = {}, a.alertsTop = {}, a.projectName = c.project, a.projectPromise = $.Deferred(), a.labels = [], a.systemLabels = [], a.breadcrumbs = [ { title:a.projectName, link:"project/" + a.projectName }, { @@ -7742,7 +7771,16 @@ title:u } ], a.alerts = a.alerts || {}, f.getAlerts().forEach(function(b) { a.alerts[b.name] = b.data; }), f.clearAlerts(); -var w = n("displayName"), x = n("humanize"), y = l("spec.template.spec.containers"), z = l("spec.strategy.sourceStrategy.from || spec.strategy.dockerStrategy.from || spec.strategy.customStrategy.from"), A = l("spec.output.to"); +var w = n("displayName"), x = n("humanize"), y = l("spec.template.spec.containers"), z = l("spec.strategy.sourceStrategy.from || spec.strategy.dockerStrategy.from || spec.strategy.customStrategy.from"), A = l("spec.output.to"), B = function() { +try { +return JSON.parse(c.templateParamsMap); +} catch (b) { +a.alertsTop.invalidTemplateParams = { +type:"error", +message:"The templateParamsMap is not valid JSON. " + b +}; +} +}; g.get(c.project).then(_.spread(function(b, f) { function g(a, b) { var c = _.get(a, "spec.triggers", []), d = _.find(c, function(a) { @@ -7753,7 +7791,7 @@ return _.includes(c, b.name); return _.get(d, "imageChangeParams.from.name"); } function l(a) { -for (var b = [], c = F.exec(a); c; ) b.push(c[1]), c = F.exec(a); +for (var b = [], c = G.exec(a); c; ) b.push(c[1]), c = G.exec(a); return b; } function q() { @@ -7764,10 +7802,10 @@ b[a.name] = a.value; } function t() { var b = q(); -a.templateImages = _.map(G, function(a) { +a.templateImages = _.map(H, function(a) { if (_.isEmpty(a.usesParameters)) return a; var c = _.template(a.name, { -interpolate:F +interpolate:G }); return { name:c(b), @@ -7775,35 +7813,35 @@ usesParameters:a.usesParameters }; }); } -function B(a) { +function C(a) { var b = [], c = y(a); return c && angular.forEach(c, function(c) { var d = c.image, e = g(a, c); e && (d = e), d && b.push(d); }), b; } -function C(b) { -G = []; +function D(b) { +H = []; var c = [], d = {}; angular.forEach(b.objects, function(b) { if ("BuildConfig" === b.kind) { var e = p(z(b), a.projectName); -e && G.push({ +e && H.push({ name:e, usesParameters:l(e) }); var f = p(A(b), a.projectName); f && (d[f] = !0); } -"DeploymentConfig" === b.kind && (c = c.concat(B(b))); +"DeploymentConfig" === b.kind && (c = c.concat(C(b))); }), c.forEach(function(a) { -d[a] || G.push({ +d[a] || H.push({ name:a, usesParameters:l(a) }); -}), G = _.uniq(G, !1, "name"); +}), H = _.uniq(H, !1, "name"); } -function D(a) { +function E(a) { var b = /^helplink\.(.*)\.title$/, c = /^helplink\.(.*)\.url$/, d = {}; for (var e in a.annotations) { var f, g = e.match(b); @@ -7811,43 +7849,49 @@ g ? (f = d[g[1]] || {}, f.title = a.annotations[e], d[g[1]] = f) :(g = e.match(c } return d; } -function E(b) { -a.parameterDisplayNames = {}, _.each(a.template.parameters, function(b) { +function F(b) { +if (a.parameterDisplayNames = {}, _.each(a.template.parameters, function(b) { a.parameterDisplayNames[b.name] = b.displayName || b.name; -}), C(a.template); -var c = function(a) { +}), c.templateParamsMap) { +var d = B(); +_.each(a.template.parameters, function(a) { +d[a.name] && (a.value = d[a.name]); +}); +} +D(a.template); +var e = function(a) { return !_.isEmpty(a.usesParameters); }; -_.some(G, c) ? a.$watch("template.parameters", _.debounce(function(b) { +_.some(H, e) ? a.$watch("template.parameters", _.debounce(function(b) { a.$apply(t); }, 50, { maxWait:250 -}), !0) :a.templateImages = G, a.systemLabels = _.map(a.template.labels, function(a, b) { +}), !0) :a.templateImages = H, a.systemLabels = _.map(a.template.labels, function(a, b) { return { name:b, value:a }; -}), L() && a.systemLabels.push({ +}), M() && a.systemLabels.push({ name:"app", value:a.template.metadata.name }); } a.project = b, a.breadcrumbs[0].title = n("displayName")(b); -var F = /\${([a-zA-Z0-9\_]+)}/g, G = []; +var G = /\${([a-zA-Z0-9\_]+)}/g, H = []; a.projectDisplayName = function() { return w(this.project) || this.projectName; }, a.templateDisplayName = function() { return w(this.template); }; -var H, I = function() { +var I, J = function() { var b = { started:"Creating " + a.templateDisplayName() + " in project " + a.projectDisplayName(), success:"Created " + a.templateDisplayName() + " in project " + a.projectDisplayName(), failure:"Failed to create " + a.templateDisplayName() + " in project " + a.projectDisplayName() -}, e = D(a.template); +}, e = E(a.template); k.clear(), k.add(b, e, c.project, function() { var b = i.defer(); -return d.batch(H, f).then(function(c) { +return d.batch(I, f).then(function(c) { var d = [], e = !1; c.failure.length > 0 ? (e = !0, c.failure.forEach(function(a) { d.push({ @@ -7869,7 +7913,7 @@ hasErrors:e }); }), b.promise; }), m.toNextSteps(c.name, a.projectName); -}, J = function(a) { +}, K = function(a) { var b = o.open({ animation:!0, templateUrl:"views/modals/confirm.html", @@ -7886,18 +7930,18 @@ cancelButtonText:"Cancel" } } }); -b.result.then(I); -}, K = function(b) { +b.result.then(J); +}, L = function(b) { var c = b.quotaAlerts || [], d = _.filter(c, { type:"error" }); -d.length ? (a.disableInputs = !1, a.alerts = c) :c.length ? (J(c), a.disableInputs = !1) :I(); +d.length ? (a.disableInputs = !1, a.alerts = c) :c.length ? (K(c), a.disableInputs = !1) :J(); }; a.createFromTemplate = function() { a.disableInputs = !0; var b = s.mapEntries(s.compactEntries(a.labels)), c = s.mapEntries(s.compactEntries(a.systemLabels)); a.template.labels = _.extend(c, b), d.create("processedtemplates", null, a.template, f).then(function(b) { -e.setTemplateData(b.parameters, a.template.parameters, b.message), H = b.objects, h.getLatestQuotaAlerts(H, f).then(K); +e.setTemplateData(b.parameters, a.template.parameters, b.message), I = b.objects, h.getLatestQuotaAlerts(I, f).then(L); }, function(b) { a.disableInputs = !1; var c; @@ -7908,24 +7952,24 @@ details:c }; }); }; -var L = function() { +var M = function() { return !_.get(a.template, "labels.app") && !_.some(a.template.objects, "metadata.labels.app"); }; if (v) d.get("templates", u, { namespace:v || a.projectName }).then(function(b) { -a.template = b, E(), a.breadcrumbs[3].title = n("displayName")(b); +a.template = b, F(), a.breadcrumbs[3].title = n("displayName")(b); }, function() { m.toErrorPage("Cannot create from template: the specified template could not be retrieved."); }); else { if (a.template = r.getTemplate(), _.isEmpty(a.template)) { -var M = URI("error").query({ +var N = URI("error").query({ error:"not_found", error_description:"Template wasn't found in cache." }).toString(); -j.url(M); +j.url(N); } -r.clearTemplate(), E(); +r.clearTemplate(), F(); } })); } ]), angular.module("openshiftConsole").controller("LabelsController", [ "$scope", function(a) { @@ -8052,32 +8096,105 @@ a.projectImageStreams = b.by("metadata.name"); a.projectTemplates = b.by("metadata.name"); })); })); -} ]), angular.module("openshiftConsole").controller("CreateProjectController", [ "$scope", "$location", "AuthService", "DataService", "AlertMessageService", function(a, b, c, d, e) { -a.alerts = {}, c.withUser(), e.getAlerts().forEach(function(b) { +} ]), angular.module("openshiftConsole").controller("CreateFromURLController", [ "$scope", "$routeParams", "$location", "$filter", "AuthService", "DataService", "AlertMessageService", "Navigate", "ProjectsService", function(a, b, c, d, e, f, g, h, i) { +e.withUser(), g.getAlerts().forEach(function(b) { a.alerts[b.name] = b.data; -}), e.clearAlerts(), a.createProject = function() { -a.disableInputs = !0, a.createProjectForm.$valid && d.create("projectrequests", null, { -apiVersion:"v1", -kind:"ProjectRequest", -metadata:{ -name:a.name -}, -displayName:a.displayName, -description:a.description -}, a).then(function(a) { -b.path("project/" + encodeURIComponent(a.metadata.name) + "/create"); -}, function(b) { -a.disableInputs = !1; -var c = b.data || {}; -if ("AlreadyExists" === c.reason) a.nameTaken = !0; else { -var d = c.message || "An error occurred creating the project."; -a.alerts["error-creating-project"] = { +}), g.clearAlerts(), a.alerts = {}, a.selected = {}; +var j = function(b) { +a.alerts.invalidImageStream = { type:"error", -message:d +message:'The requested image stream "' + b + '" could not be loaded.' +}; +}, k = function(b) { +a.alerts.invalidImageTag = { +type:"error", +message:'The requested image stream tag "' + b + '" could not be loaded.' +}; +}, l = function(b) { +a.alerts.invalidImageStream = { +type:"error", +message:'The app name "' + b + "\" is not valid. An app name is an alphanumeric (a-z, and 0-9) string with a maximum length of 24 characters, where the first character is a letter (a-z), and the '-' character is allowed anywhere except the first or last character." +}; +}, m = function(b) { +a.alerts.invalidNamespace = { +type:"error", +message:'Resources from the namespace "' + b + '" are not permitted.' +}; +}, n = function(b) { +a.alerts.invalidTemplate = { +type:"error", +message:'The requested template "' + b + '" could not be loaded.' +}; +}, o = function() { +a.alerts.resourceRequired = { +type:"error", +message:"An image stream or template is required." +}; +}, p = function() { +a.alerts.invalidResource = { +type:"error", +message:"Image streams and templates cannot be combined." +}; +}, q = function() { +try { +return b.templateParamsMap && JSON.parse(b.templateParamsMap) || {}; +} catch (c) { +a.alerts.invalidTemplateParams = { +type:"error", +message:"The templateParamsMap is not valid JSON. " + c }; } +}, r = window.OPENSHIFT_CONSTANTS.CREATE_FROM_URL_WHITELIST, s = [ "namespace", "name", "imageStream", "imageTag", "sourceURI", "sourceRef", "contextDir", "template", "templateParamsMap" ], t = _.pick(b, function(a, b) { +return _.contains(s, b) && _.isString(a); +}); +t.namespace = t.namespace || "openshift"; +var u = function(a) { +return a.length < 25 && /^[a-z]([-a-z0-9]*[a-z0-9])?$/.test(a); +}, v = function() { +t.imageStream && f.get("imagestreams", t.imageStream, { +namespace:t.namespace +}, { +errorNotification:!1 +}).then(function(b) { +a.imageStream = b, f.get("imagestreamtags", b.metadata.name + ":" + t.imageTag, { +namespace:t.namespace +}, { +errorNotification:!1 +}).then(function(b) { +a.imageStreamTag = b, a.validationPassed = !0, a.resource = b, t.displayName = d("displayName")(b); +}, function() { +k(t.imageTag); +}); +}, function() { +j(t.imageStream); +}), t.template && f.get("templates", t.template, { +namespace:t.namespace +}, { +errorNotification:!1 +}).then(function(b) { +a.template = b, q() && (a.validationPassed = !0, a.resource = b); +}, function() { +n(t.template); }); }; +_.includes(r, t.namespace) ? t.imageStream && t.template ? p() :t.imageStream || t.template ? t.name && !u(t.name) ? l(t.name) :v() :o() :m(t.namespace), angular.extend(a, { +createDetails:t, +createWithProject:function(d) { +d = d || a.selected.project.metadata.name; +var e = b.imageStream ? h.createFromImageURL(a.imageStream, t.imageTag, d, t) :h.createFromTemplateURL(a.template, d, t); +c.url(e); +} +}), a.projects = {}, a.canCreateProject = void 0, f.list("projects", a).then(function(b) { +a.loaded = !0, a.projects = d("orderByDisplayName")(b.by("metadata.name")), a.noProjects = _.isEmpty(a.projects); +}), i.canCreate().then(function() { +a.canCreateProject = !0; +}, function() { +a.canCreateProject = !1; +}); +} ]), angular.module("openshiftConsole").controller("CreateProjectController", [ "$scope", "$location", "AuthService", "DataService", "AlertMessageService", function(a, b, c, d, e) { +a.alerts = {}, c.withUser(), e.getAlerts().forEach(function(b) { +a.alerts[b.name] = b.data; +}), e.clearAlerts(); } ]), angular.module("openshiftConsole").controller("EditProjectController", [ "$scope", "$routeParams", "$filter", "$location", "DataService", "AlertMessageService", "ProjectsService", "Navigate", function(a, b, c, d, e, f, g, h) { a.alerts = {}, f.getAlerts().forEach(function(b) { a.alerts[b.name] = b.data; @@ -8498,7 +8615,43 @@ a.hideBuild = c(b); }, templateUrl:"views/directives/_build-close.html" }; -} ]), angular.module("openshiftConsole").directive("createSecret", [ "DataService", "AuthorizationService", "$filter", function(a, b, c) { +} ]), angular.module("openshiftConsole").directive("createProject", function() { +return { +restrict:"E", +scope:{ +alerts:"=", +submitButtonLabel:"@", +redirectAction:"&" +}, +templateUrl:"views/directives/_create-project-form.html", +controller:[ "$scope", "$filter", "$location", "DataService", function(a, b, c, d) { +a.submitButtonLabel || (a.submitButtonLabel = "Create"), a.createProject = function() { +a.disableInputs = !0, a.createProjectForm.$valid && d.create("projectrequests", null, { +apiVersion:"v1", +kind:"ProjectRequest", +metadata:{ +name:a.name +}, +displayName:a.displayName, +description:a.description +}, a).then(function(b) { +var d = a.redirectAction(); +d ? d(encodeURIComponent(b.metadata.name)) :c.path("project/" + encodeURIComponent(b.metadata.name) + "/create"); +}, function(b) { +a.disableInputs = !1; +var c = b.data || {}; +if ("AlreadyExists" === c.reason) a.nameTaken = !0; else { +var d = c.message || "An error occurred creating the project."; +a.alerts["error-creating-project"] = { +type:"error", +message:d +}; +} +}); +}; +} ] +}; +}), angular.module("openshiftConsole").directive("createSecret", [ "DataService", "AuthorizationService", "$filter", function(a, b, c) { return { restrict:"E", scope:{ @@ -9038,7 +9191,9 @@ function q() { var a; if ("Template" === l.resourceKind && l.templateOptions.process && !l.errorOccured) { var b = l.templateOptions.add || l.updateResources.length > 0 ? l.projectName :""; -a = g.fromTemplateURL(l.projectName, l.resourceName, b); +a = g.createFromTemplateURL(l.resourceName, l.projectName, { +namespace:b +}); } else a = g.projectOverviewURL(l.projectName); c.url(a); } @@ -13001,9 +13156,16 @@ c[e] = (c[e] || 0) + 1; }), c; } return function(c, d) { +if (!c) return ""; var e = a(c), f = c.metadata.name; return e !== f && b(d)[e] > 1 ? e + " (" + f + ")" :e; }; +} ]).filter("searchProjects", [ "annotationNameFilter", function(a) { +return function(b, c) { +return _.filter(b, function(b) { +return _.includes(b.metadata.name, c) || _.includes(b.metadata.annotations[a("displayName")], c); +}); +}; } ]).filter("tags", [ "annotationFilter", function(a) { return function(b, c) { c = c || "tags"; @@ -13264,31 +13426,15 @@ builderfor:b }); return c.toString(); }; -}).filter("createFromImageURL", [ "displayNameFilter", function(a) { -return function(b, c, d) { -var e = URI.expand("project/{project}/create/fromimage{?q*}", { -project:d, -q:{ -imageName:b.metadata.name, -imageTag:c, -namespace:b.metadata.namespace, -displayName:a(b) -} -}); -return e.toString(); +}).filter("createFromImageURL", [ "Navigate", function(a) { +return function(b, c, d, e) { +return a.createFromImageURL(b, c, d, e); }; -} ]).filter("createFromTemplateURL", function() { -return function(a, b) { -var c = URI.expand("project/{project}/create/fromtemplate{?q*}", { -project:b, -q:{ -name:a.metadata.name, -namespace:a.metadata.namespace -} -}); -return c.toString(); +} ]).filter("createFromTemplateURL", [ "Navigate", function(a) { +return function(b, c, d) { +return a.createFromTemplateURL(b, c, d); }; -}).filter("failureObjectName", function() { +} ]).filter("failureObjectName", function() { return function(a) { if (!a.data || !a.data.details) return null; var b = a.data.details; diff --git a/dist/scripts/templates.js b/dist/scripts/templates.js index b8f43431e5..a82d2705aa 100644 --- a/dist/scripts/templates.js +++ b/dist/scripts/templates.js @@ -61,6 +61,13 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( ); + $templateCache.put('views/_cannot-create-project.html', + "A cluster admin can create a project for you by running the command\n" + + "oadm new-project <projectname> --admin={{user.metadata.name || '<YourUsername>'}}\n" + + "" + ); + + $templateCache.put('views/_compute-resource.html', "\n" + "
\n" + @@ -4340,6 +4347,81 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( ); + $templateCache.put('views/create-from-url.html', + "\n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
Loading...
\n" + + "
\n" + + "\n" + + "\n" + + "

Source code from {{createDetails.sourceURI}} will be built and deployed unless otherwise specified in the next step.

\n" + + "
\n" + + "
\n" + + "

Create a New Project

\n" + + "\n" + + "
\n" + + "
\n" + + "

Choose a Project

\n" + + "\n" + + "\n" + + "{{$select.selected | uniqueDisplayName : projects}}\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + "Choose Existing Project\n" + + "\n" + + "\n" + + "{{$select.selected | uniqueDisplayName : projects}}\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "Cancel\n" + + "
\n" + + "
\n" + + "\n" + + "Create a New Project\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "

\n" + + "A project is required in order to complete the installation.\n" + + "\n" + + "

\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
" + ); + + $templateCache.put('views/create-persistent-volume-claim.html', "\n" + "
\n" + @@ -4397,46 +4479,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "
\n" + "

New Project

\n" + "\n" + - "
\n" + - "
\n" + - "
\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
\n" + - "A unique name for the project.\n" + - "
\n" + - "
\n" + - "\n" + - "Name must have at least two characters.\n" + - "\n" + - "
\n" + - "
\n" + - "\n" + - "Project names may only contain lower-case letters, numbers, and dashes. They may not start or end with a dash.\n" + - "\n" + - "
\n" + - "
\n" + - "\n" + - "This name is already in use. Please choose a different name.\n" + - "\n" + - "
\n" + - "
\n" + - "
\n" + - "\n" + - "\n" + - "
\n" + - "
\n" + - "\n" + - "\n" + - "
\n" + - "
\n" + - "\n" + - "Cancel\n" + - "
\n" + - "
\n" + - "
\n" + + "\n" + "
\n" + "
\n" + "\n" + @@ -4676,7 +4719,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "\n" + "\n" + "\n" + - "
\n" + + "
\n" + "Sample repository for {{imageName}}: {{image.metadata.annotations.sampleRepo}}, ref: {{image.metadata.annotations.sampleRef}}, context dir: {{image.metadata.annotations.sampleContextDir}}\n" + "Try It\n" + "
\n" + @@ -5301,6 +5344,50 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( ); + $templateCache.put('views/directives/_create-project-form.html', + "
\n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
\n" + + "A unique name for the project.\n" + + "
\n" + + "
\n" + + "\n" + + "Name must have at least two characters.\n" + + "\n" + + "
\n" + + "
\n" + + "\n" + + "Project names may only contain lower-case letters, numbers, and dashes. They may not start or end with a dash.\n" + + "\n" + + "
\n" + + "
\n" + + "\n" + + "This name is already in use. Please choose a different name.\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "\n" + + "Cancel\n" + + "
\n" + + "
\n" + + "
" + ); + + $templateCache.put('views/directives/_custom-icon.html', "\n" + "\"\"" @@ -9913,6 +10000,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "{{ emptyMessage }}\n" + "
\n" + "
\n" + + "\n" + "
\n" + "
\n" + "\n" + @@ -11033,11 +11121,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "
\n" + "
\n" + "
\n" + - "

\n" + - "A cluster admin can create a project for you by running the command\n" + - "oadm new-project <projectname> --admin={{user.metadata.name || '<YourUsername>'}}\n" + - "\n" + - "

\n" + + "

\n" + "\n" + "\n" + "
\n" + @@ -11049,11 +11133,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "

\n" + "New Project\n" + "

To learn more, visit the OpenShift documentation.

\n" + - "

\n" + - "A cluster admin can create a project for you by running the command
\n" + - "oadm new-project <projectname> --admin={{user.metadata.name || '<YourUsername>'}}
\n" + - "\n" + - "

\n" + + "

\n" + "
\n" + "\n" + "\n" + diff --git a/dist/styles/main.css b/dist/styles/main.css index d61c813cc9..c50504209f 100644 --- a/dist/styles/main.css +++ b/dist/styles/main.css @@ -5154,7 +5154,8 @@ kubernetes-topology-graph{height:700px} } .settings-item{margin:4px 8px 4px 0} .hide-if-empty:empty{display:none!important} -.appended-icon{display:inline-block;white-space:nowrap} +.appended-icon,.empty-state-message .projects-instructions code{display:inline-block} +.appended-icon{white-space:nowrap} .donut-title-med-pf{font-size:22.5px;font-weight:300} .col-md-12>.alerts:first-child{margin-top:20px} .project-actions{margin:20px 0 0} diff --git a/test/spec/controllers/create/createFromImageSpec.js b/test/spec/controllers/create/createFromImageSpec.js index e6b7623a21..6b7e961c51 100644 --- a/test/spec/controllers/create/createFromImageSpec.js +++ b/test/spec/controllers/create/createFromImageSpec.js @@ -7,7 +7,7 @@ describe("CreateFromImageController", function(){ projectName: "aProjectName" }; var $routeParams = { - imageName: "anImageName", + imageStream: "anImageName", imageTag: "latest", namespace: "aNamespace" }; diff --git a/test/spec/controllers/projects.js b/test/spec/controllers/projects.js index 25fa4990dc..7d67c0e124 100644 --- a/test/spec/controllers/projects.js +++ b/test/spec/controllers/projects.js @@ -64,6 +64,13 @@ describe('Controller: ProjectsController', function () { deferred.resolve({}); return deferred.promise; } + }, + ProjectsService: { + canCreate: function() { + var deferred = $q.defer(); + deferred.resolve({}); + return deferred.promise; + } } }); }));