diff --git a/app/index.html b/app/index.html index d48c845b7b..bda63a3297 100644 --- a/app/index.html +++ b/app/index.html @@ -231,6 +231,8 @@

JavaScript Required

+ + diff --git a/app/scripts/app.js b/app/scripts/app.js index 7c86eb3914..3c63d73dfb 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -174,6 +174,16 @@ angular templateUrl: 'views/edit/deployment-config.html', controller: 'EditDeploymentConfigController' }) + .when('/project/:project/browse/stateful-sets/', { + templateUrl: 'views/browse/stateful-sets.html', + controller: 'StatefulSetsController', + reloadOnSearch: false + }) + .when('/project/:project/browse/stateful-sets/:statefulset', { + templateUrl: 'views/browse/stateful-set.html', + controller: 'StatefulSetController', + reloadOnSearch: false + }) .when('/project/:project/browse/rs/:replicaSet', { templateUrl: 'views/browse/replica-set.html', resolve: { diff --git a/app/scripts/constants.js b/app/scripts/constants.js index 72ac1dbb56..fdddd3d5b1 100644 --- a/app/scripts/constants.js +++ b/app/scripts/constants.js @@ -45,6 +45,7 @@ window.OPENSHIFT_CONSTANTS = { "config-maps": "dev_guide/configmaps.html", "secrets": "dev_guide/secrets.html", "deployments": "dev_guide/deployments/how_deployments_work.html", + //"stateful-sets": "", // TODO: docs are in progress "pods": "architecture/core_concepts/pods_and_services.html#pods", "services": "architecture/core_concepts/pods_and_services.html#services", "routes": "architecture/core_concepts/routes.html", @@ -115,6 +116,13 @@ window.OPENSHIFT_CONSTANTS = { "/browse/rc/" ] }, + { + label: "Stateful Sets", + href: "/browse/stateful-sets", + prefixes: [ + "/browse/stateful-sets/" + ] + }, { label: "Pods", href: "/browse/pods", diff --git a/app/scripts/controllers/statefulSet.js b/app/scripts/controllers/statefulSet.js new file mode 100644 index 0000000000..6beaacf338 --- /dev/null +++ b/app/scripts/controllers/statefulSet.js @@ -0,0 +1,101 @@ +'use strict'; + +angular + .module('openshiftConsole') + .controller('StatefulSetController', function( + $filter, + $scope, + $routeParams, + AlertMessageService, + BreadcrumbsService, + DataService, + ProjectsService) { + + $scope.projectName = $routeParams.project; + $scope.statefulSetName = $routeParams.statefulset; + $scope.forms = {}; + $scope.alerts = {}; + $scope.breadcrumbs = BreadcrumbsService.getBreadcrumbs({ + name: $scope.statefulSetName, + kind: 'statefulSet', + namespace: $routeParams.project + }); + $scope.emptyMessage = "Loading..."; + + var altTextForValueFrom = $filter('altTextForValueFrom'); + var updateEnvVars = function(statefulSet) { + _.each(statefulSet.spec.template.spec.containers, function(container) { + _.each(container.env, altTextForValueFrom); + }); + return statefulSet; + }; + + AlertMessageService.getAlerts().forEach(function(alert) { + $scope.alerts[alert.name] = alert.data; + }); + AlertMessageService.clearAlerts(); + + var watches = []; + var projectContext; + + var updatePods = function(pods, selector) { + if (!pods || !selector) { + return; + } + return selector.select(pods); + }; + + var resourceGroupVersion = { + resource: 'statefulsets', + group: 'apps', + version: 'v1beta1' + }; + + ProjectsService + .get($routeParams.project) + .then(_.spread(function(project, context) { + projectContext = context; + + DataService + .get(resourceGroupVersion, $scope.statefulSetName, context) + .then(function(statefulSet) { + + angular.extend($scope, { + statefulSet: updateEnvVars(statefulSet), + project: project, + projectContext: context, + loaded: true, + // TODO: support scaling(?). currently no scale subresource. + isScalable: function() { + return false; + }, + scale: function() {} + }); + + watches.push(DataService.watchObject(resourceGroupVersion, $scope.statefulSetName, context, function(statefulSet) { + angular.extend($scope, { + resourceGroupVersion: resourceGroupVersion, + statefulSet: updateEnvVars(statefulSet) + }); + })); + + var pods; + var selector; + $scope.$watch('statefulSet.spec.selector', function() { + selector = new LabelSelector($scope.statefulSet.spec.selector); + $scope.podsForStatefulSet = updatePods(pods, selector); + }, true); + + watches.push(DataService.watch('pods', context, function(podData) { + pods = podData.by('metadata.name'); + $scope.podsForStatefulSet = updatePods(pods, selector); + })); + + }); + })); + + $scope.$on('$destroy', function(){ + DataService.unwatchAll(watches); + }); + + }); diff --git a/app/scripts/controllers/statefulSets.js b/app/scripts/controllers/statefulSets.js new file mode 100644 index 0000000000..623673a349 --- /dev/null +++ b/app/scripts/controllers/statefulSets.js @@ -0,0 +1,37 @@ +'use strict'; + +angular.module('openshiftConsole') + .controller('StatefulSetsController', function($scope, $routeParams, AlertMessageService, DataService, ProjectsService) { + $scope.projectName = $routeParams.project; + $scope.alerts = $scope.alerts || {}; + $scope.emptyMessage = "Loading..."; + + // get and clear any alerts + AlertMessageService.getAlerts().forEach(function(alert) { + $scope.alerts[alert.name] = alert.data; + }); + AlertMessageService.clearAlerts(); + + var watches = []; + ProjectsService + .get($routeParams.project) + .then(_.spread(function(project, context) { + $scope.project = project; + + watches.push(DataService.watch({ + resource: 'statefulsets', + group: 'apps', + version: 'v1beta1' + }, context, function(statefulSets) { + angular.extend($scope, { + loaded: true, + statefulSets: statefulSets.by('metadata.name') + }); + })); + + $scope.$on('$destroy', function(){ + DataService.unwatchAll(watches); + }); + + })); + }); diff --git a/app/scripts/directives/resources.js b/app/scripts/directives/resources.js index db97020ff6..e8dd692180 100644 --- a/app/scripts/directives/resources.js +++ b/app/scripts/directives/resources.js @@ -127,6 +127,15 @@ angular.module('openshiftConsole') templateUrl: 'views/_volumes.html' }; }) + .directive('volumeClaimTemplates', function() { + return { + restrict: 'E', + scope: { + templates: '=', + }, + templateUrl: 'views/_volume-claim-templates.html' + }; + }) .directive('environment', function() { return { restrict: 'E', diff --git a/app/scripts/filters/canI.js b/app/scripts/filters/canI.js index dfdaed5163..1885a09cd4 100644 --- a/app/scripts/filters/canI.js +++ b/app/scripts/filters/canI.js @@ -64,6 +64,9 @@ angular ], 'projects': [ {group: '', resource: 'projects', verbs: ['delete', 'update']} + ], + 'statefulsets': [ + {group: 'apps', resource: 'statefulsets', verbs: ['update', 'delete']} ] }; return function(resource) { diff --git a/app/scripts/services/navigate.js b/app/scripts/services/navigate.js index 624e82ee80..44a1233c8d 100644 --- a/app/scripts/services/navigate.js +++ b/app/scripts/services/navigate.js @@ -196,6 +196,10 @@ angular.module("openshiftConsole") .segmentCoded(name.substring(0, ind)) .segmentCoded(name.substring(ind + 1)); break; + case "StatefulSet": + url.segment("stateful-sets") + .segmentCoded(name); + break; case "PersistentVolumeClaim": case "Pod": case "Route": diff --git a/app/styles/_components.less b/app/styles/_components.less index 2d4a34a7aa..ea131de36b 100644 --- a/app/styles/_components.less +++ b/app/styles/_components.less @@ -153,6 +153,8 @@ margin-bottom: 10px; } +// TODO: also using this style for a volume claim template. +// consider a slightly more generic name that isn't limited to 'pod' .pod-template-block { + .pod-template-block { margin-top: 15px; diff --git a/app/views/_volume-claim-templates.html b/app/views/_volume-claim-templates.html new file mode 100644 index 0000000000..cbd589f7c7 --- /dev/null +++ b/app/views/_volume-claim-templates.html @@ -0,0 +1,46 @@ +
+
+
Storage claim: {{template.metadata.name}}
+ +
+
+ +
+
+ Access Modes: + + {{mode | sentenceCase }}, + +
+
+ +
+
+ +
+
+ Capacity: + + {{template.spec.resources.requests.storage}} + +
+
+ +
+
+ +
+
+ Selector: + + {{key}}={{value}}, + +
+
+ +
+
diff --git a/app/views/browse/stateful-set.html b/app/views/browse/stateful-set.html new file mode 100644 index 0000000000..d0812ee26b --- /dev/null +++ b/app/views/browse/stateful-set.html @@ -0,0 +1,199 @@ + + +
+
+ +
+
+ + +
+

+ {{statefulSet.metadata.name}} + +

+ +
+
+
+ +
+
+
Loading...
+ +
+
+ + + Details + +
+ +
+ + + +
+ +
+
+
Status:
+
+ + {{statefulSet | deploymentStatus}} +
+
Replicas:
+
+ + + +
+
+ +

Template

+ + + +

+ none +

+ + + +
+ +
+ +
+
+

Volumes

+

+ none +

+ + + +

Pods

+ + + +
+
+
+ + + Environment +
+ +
+

Container {{container.name}} Environment Variables

+

+ Environment variables for stateful sets are readonly. + + There are no environment variables for this container. + +

+ +
+
+
+
+ + + Metrics +
+ + +
+
+ + + Events +
+ + +
+
+ +
+
+
+
+
+
+
+
diff --git a/app/views/browse/stateful-sets.html b/app/views/browse/stateful-sets.html new file mode 100644 index 0000000000..04ca352da1 --- /dev/null +++ b/app/views/browse/stateful-sets.html @@ -0,0 +1,66 @@ + + + +
+
+ +
+
+ + + +
+
+ +
+
+
Loading...
+
+
+ + + + + + + + + + + + + + + + + + + + +
NameReplicasCreated
No stateful sets
+ {{statefulSet.metadata.name}} + + {{statefulSet.status.replicas}}/{{statefulSet.spec.replicas}} replicas + + +
+
+
+
+
+ +
+
+ +
diff --git a/dist/scripts/scripts.js b/dist/scripts/scripts.js index 208e8ce5a9..86c68384c0 100644 --- a/dist/scripts/scripts.js +++ b/dist/scripts/scripts.js @@ -78,6 +78,10 @@ label:"Deployments", href:"/browse/deployments", prefixes:[ "/browse/deployment/", "/browse/dc/", "/browse/rs/", "/browse/rc/" ] }, { +label:"Stateful Sets", +href:"/browse/stateful-sets", +prefixes:[ "/browse/stateful-sets/" ] +}, { label:"Pods", href:"/browse/pods", prefixes:[ "/browse/pods/" ] @@ -362,6 +366,14 @@ reloadOnSearch:!1 }).when("/project/:project/edit/dc/:deploymentconfig", { templateUrl:"views/edit/deployment-config.html", controller:"EditDeploymentConfigController" +}).when("/project/:project/browse/stateful-sets/", { +templateUrl:"views/browse/stateful-sets.html", +controller:"StatefulSetsController", +reloadOnSearch:!1 +}).when("/project/:project/browse/stateful-sets/:statefulset", { +templateUrl:"views/browse/stateful-set.html", +controller:"StatefulSetController", +reloadOnSearch:!1 }).when("/project/:project/browse/rs/:replicaSet", { templateUrl:"views/browse/replica-set.html", resolve:{ @@ -2238,6 +2250,10 @@ var m = h.indexOf(":"); i.segment("images").segmentCoded(h.substring(0, m)).segmentCoded(h.substring(m + 1)); break; +case "StatefulSet": +i.segment("stateful-sets").segmentCoded(h); +break; + case "PersistentVolumeClaim": case "Pod": case "Route": @@ -6439,6 +6455,73 @@ e.then(g); g.unwatchAll(w); }); })); +} ]), angular.module("openshiftConsole").controller("StatefulSetsController", [ "$scope", "$routeParams", "AlertMessageService", "DataService", "ProjectsService", function(a, b, c, d, e) { +a.projectName = b.project, a.alerts = a.alerts || {}, a.emptyMessage = "Loading...", c.getAlerts().forEach(function(b) { +a.alerts[b.name] = b.data; +}), c.clearAlerts(); +var f = []; +e.get(b.project).then(_.spread(function(b, c) { +a.project = b, f.push(d.watch({ +resource:"statefulsets", +group:"apps", +version:"v1beta1" +}, c, function(b) { +angular.extend(a, { +loaded:!0, +statefulSets:b.by("metadata.name") +}); +})), a.$on("$destroy", function() { +d.unwatchAll(f); +}); +})); +} ]), angular.module("openshiftConsole").controller("StatefulSetController", [ "$filter", "$scope", "$routeParams", "AlertMessageService", "BreadcrumbsService", "DataService", "ProjectsService", function(a, b, c, d, e, f, g) { +b.projectName = c.project, b.statefulSetName = c.statefulset, b.forms = {}, b.alerts = {}, b.breadcrumbs = e.getBreadcrumbs({ +name:b.statefulSetName, +kind:"statefulSet", +namespace:c.project +}), b.emptyMessage = "Loading..."; +var h = a("altTextForValueFrom"), i = function(a) { +return _.each(a.spec.template.spec.containers, function(a) { +_.each(a.env, h); +}), a; +}; +d.getAlerts().forEach(function(a) { +b.alerts[a.name] = a.data; +}), d.clearAlerts(); +var j, k = [], l = function(a, b) { +if (a && b) return b.select(a); +}, m = { +resource:"statefulsets", +group:"apps", +version:"v1beta1" +}; +g.get(c.project).then(_.spread(function(a, c) { +j = c, f.get(m, b.statefulSetName, c).then(function(d) { +angular.extend(b, { +statefulSet:i(d), +project:a, +projectContext:c, +loaded:!0, +isScalable:function() { +return !1; +}, +scale:function() {} +}), k.push(f.watchObject(m, b.statefulSetName, c, function(a) { +angular.extend(b, { +resourceGroupVersion:m, +statefulSet:i(a) +}); +})); +var e, g; +b.$watch("statefulSet.spec.selector", function() { +g = new LabelSelector(b.statefulSet.spec.selector), b.podsForStatefulSet = l(e, g); +}, !0), k.push(f.watch("pods", c, function(a) { +e = a.by("metadata.name"), b.podsForStatefulSet = l(e, g); +})); +}); +})), b.$on("$destroy", function() { +f.unwatchAll(k); +}); } ]), angular.module("openshiftConsole").controller("ServicesController", [ "$routeParams", "$scope", "AlertMessageService", "DataService", "ProjectsService", "$filter", "LabelFilter", "Logger", function(a, b, c, d, e, f, g, h) { b.projectName = a.project, b.services = {}, b.unfilteredServices = {}, b.routesByService = {}, b.routes = {}, b.labelSuggestions = {}, b.alerts = b.alerts || {}, b.emptyMessage = "Loading...", b.emptyMessageRoutes = "Loading...", c.getAlerts().forEach(function(a) { b.alerts[a.name] = a.data; @@ -10353,6 +10436,14 @@ removeFn:"&?" }, templateUrl:"views/_volumes.html" }; +}).directive("volumeClaimTemplates", function() { +return { +restrict:"E", +scope:{ +templates:"=" +}, +templateUrl:"views/_volume-claim-templates.html" +}; }).directive("environment", function() { return { restrict:"E", @@ -14198,6 +14289,11 @@ projects:[ { group:"", resource:"projects", verbs:[ "delete", "update" ] +} ], +statefulsets:[ { +group:"apps", +resource:"statefulsets", +verbs:[ "update", "delete" ] } ] }; return function(c) { diff --git a/dist/scripts/templates.js b/dist/scripts/templates.js index ad180db3fc..84b11847a9 100644 --- a/dist/scripts/templates.js +++ b/dist/scripts/templates.js @@ -797,6 +797,48 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( ); + $templateCache.put('views/_volume-claim-templates.html', + "
\n" + + "
\n" + + "
Storage claim: {{template.metadata.name}}
\n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "Access Modes:\n" + + "\n" + + "{{mode | sentenceCase }}, \n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "Capacity:\n" + + "\n" + + "{{template.spec.resources.requests.storage}}\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "Selector:\n" + + "\n" + + "{{key}}={{value}}, \n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
" + ); + + $templateCache.put('views/_volumes.html', "
\n" + "

\n" + @@ -3995,6 +4037,194 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( ); + $templateCache.put('views/browse/stateful-set.html', + "\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + "
\n" + + "

\n" + + "{{statefulSet.metadata.name}}\n" + + "
\n" + + "\n" + + "\n" + + "Actions\n" + + "\n" + + "
    \n" + + "
  • \n" + + "Edit YAML\n" + + "
  • \n" + + "
  • \n" + + "\n" + + "\n" + + "
  • \n" + + "
\n" + + "
\n" + + "

\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
Loading...
\n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + "Details\n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
Status:
\n" + + "
\n" + + "\n" + + "{{statefulSet | deploymentStatus}}\n" + + "
\n" + + "
Replicas:
\n" + + "
\n" + + "\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "

Template

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

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

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

Volumes

\n" + + "

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

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

Pods

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

Container {{container.name}} Environment Variables

\n" + + "

\n" + + "Environment variables for stateful sets are readonly.\n" + + "\n" + + "There are no environment variables for this container.\n" + + "\n" + + "

\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "Metrics\n" + + "
\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "\n" + + "Events\n" + + "
\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
" + ); + + + $templateCache.put('views/browse/stateful-sets.html', + "\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + "
\n" + + "

\n" + + "Stateful Sets\n" + + "\n" + + "

\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
Loading...
\n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
NameReplicasCreated
No stateful sets
\n" + + "{{statefulSet.metadata.name}}\n" + + "\n" + + "{{statefulSet.status.replicas}}/{{statefulSet.spec.replicas}} replicas\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
" + ); + + $templateCache.put('views/builds.html', "\n" + "\n" +