diff --git a/app/scripts/controllers/service.js b/app/scripts/controllers/service.js index 121b746dd9..94d8605238 100644 --- a/app/scripts/controllers/service.js +++ b/app/scripts/controllers/service.js @@ -14,6 +14,7 @@ angular.module('openshiftConsole') $filter) { $scope.projectName = $routeParams.project; $scope.service = null; + $scope.services = null; $scope.alerts = {}; $scope.renderOptions = $scope.renderOptions || {}; $scope.renderOptions.hideFilterWidget = true; @@ -27,11 +28,60 @@ angular.module('openshiftConsole') } ]; + $scope.podFailureReasons = { + "Pending": "This pod will not receive traffic until all of its containers have been created." + }; + + var allPods = {}; var watches = []; + // receives routes for the current service and maps service ports to each route name + var getPortsByRoute = function() { + if(!$scope.service) { + return; + } + + $scope.portsByRoute = {}; + + _.each($scope.service.spec.ports, function(port) { + var reachedByRoute = false; + if(port.nodePort) { + $scope.showNodePorts = true; + } + + _.each($scope.routesForService, function(route) { + if(!route.spec.port || route.spec.port.targetPort === port.name || + route.spec.port.targetPort === port.targetPort) { + $scope.portsByRoute[route.metadata.name] = $scope.portsByRoute[route.metadata.name] || []; + $scope.portsByRoute[route.metadata.name].push(port); + reachedByRoute = true; + } + }); + + if(!reachedByRoute) { + $scope.portsByRoute[''] = $scope.portsByRoute[''] || []; + $scope.portsByRoute[''].push(port); + } + }); + }; + + // receive pods for the current service scope only when the service object is available + var getPodsForService = function() { + $scope.podsForService = {}; + if (!$scope.service) { + return; + } + + var ls = new LabelSelector($scope.service.spec.selector); + $scope.podsForService = ls.select(allPods); + }; var serviceResolved = function(service, action) { $scope.loaded = true; $scope.service = service; + + getPodsForService(); + getPortsByRoute(); + if (action === "DELETED") { $scope.alerts["deleted"] = { type: "warning", @@ -60,15 +110,41 @@ angular.module('openshiftConsole') } ); + watches.push(DataService.watch("services", context, function(services) { + $scope.services = services.by("metadata.name"); + })); + + watches.push(DataService.watch("pods", context, function(pods) { + allPods = pods.by("metadata.name"); + getPodsForService(); + })); + + watches.push(DataService.watch("endpoints", context, function(endpoints) { + $scope.podsWithEndpoints = {}; + var svcEndpoint = endpoints.by("metadata.name")[$routeParams.service]; + if (!svcEndpoint) { + return; + } + + _.each(svcEndpoint.subsets, function(subset) { + _.each(subset.addresses, function(address) { + if (_.get(address, "targetRef.kind") === "Pod") { + $scope.podsWithEndpoints[address.targetRef.name] = true; + } + }); + }); + })); + watches.push(DataService.watch("routes", context, function(routes) { - $scope.routesForService = []; + $scope.routesForService = {}; angular.forEach(routes.by("metadata.name"), function(route) { if (route.spec.to.kind === "Service" && route.spec.to.name === $routeParams.service) { - $scope.routesForService.push(route); + $scope.routesForService[route.metadata.name] = route; } }); + getPortsByRoute(); Logger.log("routes (subscribe)", $scope.routesByService); })); diff --git a/app/scripts/directives/resources.js b/app/scripts/directives/resources.js index df78e524c4..db97020ff6 100644 --- a/app/scripts/directives/resources.js +++ b/app/scripts/directives/resources.js @@ -165,9 +165,31 @@ angular.module('openshiftConsole') restrict: 'E', scope: { pods: '=', + // Optional active pods map to display whether or not pods have endpoints + activePods: '=?', // Optional empty message to display when there are no pods. - emptyMessage: '=?' + emptyMessage: '=?', + // Alternative header text to display in the 'Name' column. + customNameHeader: '=?', + // Optional map of explanations or warnings for each phase of a pod + podFailureReasons: '=?' }, templateUrl: 'views/directives/pods-table.html' }; + }) + .directive('trafficTable', function() { + return { + restrict: 'E', + scope: { + routes: '=', + services: '=', + portsByRoute: '=', + showNodePorts: '=?', + // Optional empty message to display when there are no pods. + emptyMessage: '=?', + // Alternative header text to display in the 'Name' column. + customNameHeader: '=?', + }, + templateUrl: 'views/directives/traffic-table.html' + }; }); diff --git a/app/views/browse/routes.html b/app/views/browse/routes.html index 33490f5c4c..de37cdc767 100644 --- a/app/views/browse/routes.html +++ b/app/views/browse/routes.html @@ -30,11 +30,11 @@

-
+
- + @@ -42,11 +42,11 @@

- + -
Name{{customNameHeader || 'Name'}} Hostname Routes To Target Port
{{emptyMessage}}
{{emptyMessage || 'No routes to show'}}
+ {{route.metadata.name}}
{{service.spec.type}}
IP:
{{service.spec.clusterIP}}
+
Hostname:
+
+ {{service.metadata.name}}.{{service.metadata.namespace}}.svc + + + +
+
External Hostname:
+
{{service.spec.externalName}}
Session affinity:
{{service.spec.sessionAffinity}}
-
Ingress points
-
- Ingress Points: +
+ {{ingress.ip}},
-
Routes:
-
- +
External IPs:
+
+ {{externalIP}}, +
+
Routes:
+
+ Create route - - - {{route | routeLabel}} - {{route | routeLabel}} - , + None
-
- - - - - - - - - - - - - - - - - - - -
Node PortService PortTarget Port
- {{portMapping.nodePort}} - none - {{portMapping.port}}/{{portMapping.protocol}} - ({{portMapping.name}}){{portMapping.targetPort}}
+

+ Traffic +

+
+ +
+
+ Learn more about routes and services +
+

Pods

+
+
diff --git a/app/views/directives/pods-table.html b/app/views/directives/pods-table.html index 83d46af699..c487e731f5 100644 --- a/app/views/directives/pods-table.html +++ b/app/views/directives/pods-table.html @@ -1,19 +1,20 @@ - + + - + - +
Name{{customNameHeader || 'Name'}} Status Containers Ready Container Restarts AgeReceiving Traffic
{{emptyMessage || 'No pods to show'}}
{{emptyMessage || 'No pods to show'}}
+ {{pod.metadata.name}} {{pod | numContainersReady}}/{{pod.spec.containers.length}} {{pod | numContainerRestarts}} + + + Yes + + + + + No + + +
diff --git a/app/views/directives/traffic-table.html b/app/views/directives/traffic-table.html new file mode 100644 index 0000000000..97a04349b2 --- /dev/null +++ b/app/views/directives/traffic-table.html @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{customNameHeader || 'Route'}} / Node PortService PortTarget PortHostnameTLS Termination
{{emptyMessage || 'No routes or ports to show'}}
+ {{routes[routeName].metadata.name}} + + + + / {{port.nodePort}} + + + {{port.port}}/{{port.protocol}} + ({{port.name}}) + + {{port.targetPort}} + + + {{routes[routeName] | routeLabel}} + + + {{routes[routeName] | routeLabel}} + + + + Pending + + + {{routes[routeName].spec.tls.termination}} +   +
+ none + {{port.nodePort}} + + {{port.port}}/{{port.protocol}} + ({{port.name}}) + + {{port.targetPort}} + none + none +
\ No newline at end of file diff --git a/dist/scripts/scripts.js b/dist/scripts/scripts.js index ab08924f8d..f11af85bc2 100644 --- a/dist/scripts/scripts.js +++ b/dist/scripts/scripts.js @@ -6258,33 +6258,59 @@ d.unwatchAll(i); }); })); } ]), angular.module("openshiftConsole").controller("ServiceController", [ "$scope", "$routeParams", "DataService", "ProjectsService", "$filter", function(a, b, c, d, e) { -a.projectName = b.project, a.service = null, a.alerts = {}, a.renderOptions = a.renderOptions || {}, a.renderOptions.hideFilterWidget = !0, a.breadcrumbs = [ { +a.projectName = b.project, a.service = null, a.services = null, a.alerts = {}, a.renderOptions = a.renderOptions || {}, a.renderOptions.hideFilterWidget = !0, a.breadcrumbs = [ { title:"Services", link:"project/" + b.project + "/browse/services" }, { title:b.service -} ]; -var f = [], g = function(b, c) { -a.loaded = !0, a.service = b, "DELETED" === c && (a.alerts.deleted = { +} ], a.podFailureReasons = { +Pending:"This pod will not receive traffic until all of its containers have been created." +}; +var f = {}, g = [], h = function() { +a.service && (a.portsByRoute = {}, _.each(a.service.spec.ports, function(b) { +var c = !1; +b.nodePort && (a.showNodePorts = !0), _.each(a.routesForService, function(d) { +d.spec.port && d.spec.port.targetPort !== b.name && d.spec.port.targetPort !== b.targetPort || (a.portsByRoute[d.metadata.name] = a.portsByRoute[d.metadata.name] || [], a.portsByRoute[d.metadata.name].push(b), c = !0); +}), c || (a.portsByRoute[""] = a.portsByRoute[""] || [], a.portsByRoute[""].push(b)); +})); +}, i = function() { +if (a.podsForService = {}, a.service) { +var b = new LabelSelector(a.service.spec.selector); +a.podsForService = b.select(f); +} +}, j = function(b, c) { +a.loaded = !0, a.service = b, i(), h(), "DELETED" === c && (a.alerts.deleted = { type:"warning", message:"This service has been deleted." }); }; -d.get(b.project).then(_.spread(function(d, h) { -a.project = d, a.projectContext = h, c.get("services", b.service, h).then(function(a) { -g(a), f.push(c.watchObject("services", b.service, h, g)); +d.get(b.project).then(_.spread(function(d, k) { +a.project = d, a.projectContext = k, c.get("services", b.service, k).then(function(a) { +j(a), g.push(c.watchObject("services", b.service, k, j)); }, function(b) { a.loaded = !0, a.alerts.load = { type:"error", message:"The service details could not be loaded.", details:"Reason: " + e("getErrorDetails")(b) }; -}), f.push(c.watch("routes", h, function(c) { -a.routesForService = [], angular.forEach(c.by("metadata.name"), function(c) { -"Service" === c.spec.to.kind && c.spec.to.name === b.service && a.routesForService.push(c); -}), Logger.log("routes (subscribe)", a.routesByService); +}), g.push(c.watch("services", k, function(b) { +a.services = b.by("metadata.name"); +})), g.push(c.watch("pods", k, function(a) { +f = a.by("metadata.name"), i(); +})), g.push(c.watch("endpoints", k, function(c) { +a.podsWithEndpoints = {}; +var d = c.by("metadata.name")[b.service]; +d && _.each(d.subsets, function(b) { +_.each(b.addresses, function(b) { +"Pod" === _.get(b, "targetRef.kind") && (a.podsWithEndpoints[b.targetRef.name] = !0); +}); +}); +})), g.push(c.watch("routes", k, function(c) { +a.routesForService = {}, angular.forEach(c.by("metadata.name"), function(c) { +"Service" === c.spec.to.kind && c.spec.to.name === b.service && (a.routesForService[c.metadata.name] = c); +}), h(), Logger.log("routes (subscribe)", a.routesByService); })), a.$on("$destroy", function() { -c.unwatchAll(f); +c.unwatchAll(g); }); })); } ]), angular.module("openshiftConsole").controller("SecretsController", [ "$routeParams", "$scope", "AlertMessageService", "DataService", "ProjectsService", "SecretsService", function(a, b, c, d, e, f) { @@ -10110,10 +10136,26 @@ return { restrict:"E", scope:{ pods:"=", -emptyMessage:"=?" +activePods:"=?", +emptyMessage:"=?", +customNameHeader:"=?", +podFailureReasons:"=?" }, templateUrl:"views/directives/pods-table.html" }; +}).directive("trafficTable", function() { +return { +restrict:"E", +scope:{ +routes:"=", +services:"=", +portsByRoute:"=", +showNodePorts:"=?", +emptyMessage:"=?", +customNameHeader:"=?" +}, +templateUrl:"views/directives/traffic-table.html" +}; }), angular.module("openshiftConsole").directive("topologyDeployment", function() { return { restrict:"E", diff --git a/dist/scripts/templates.js b/dist/scripts/templates.js index abcc355166..fabd7fced3 100644 --- a/dist/scripts/templates.js +++ b/dist/scripts/templates.js @@ -3707,11 +3707,11 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "
\n" + "\n" + "
\n" + - "
\n" + + "
\n" + "\n" + "\n" + "\n" + - "\n" + + "\n" + "\n" + "\n" + "\n" + @@ -3719,11 +3719,11 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "\n" + "\n" + "\n" + - "\n" + + "\n" + "\n" + "\n" + "\n" + - "
Name{{customNameHeader || 'Name'}}HostnameRoutes ToTarget Port
{{emptyMessage}}
{{emptyMessage || 'No routes to show'}}
\n" + + "\n" + "{{route.metadata.name}}\n" + "\n" + "\n" + @@ -3909,49 +3909,45 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "
{{service.spec.type}}
\n" + "
IP:
\n" + "
{{service.spec.clusterIP}}
\n" + + "
Hostname:
\n" + + "
\n" + + "{{service.metadata.name}}.{{service.metadata.namespace}}.svc\n" + + "\n" + + "\n" + + "\n" + + "
\n" + + "
External Hostname:
\n" + + "
{{service.spec.externalName}}
\n" + "
Session affinity:
\n" + "
{{service.spec.sessionAffinity}}
\n" + - "
Ingress points
\n" + - "
\n" + - "{{ingress.ip}}, \n" + + "
Ingress Points:
\n" + + "
\n" + + "{{ingress.ip}}, \n" + "
\n" + - "
Routes:
\n" + - "
\n" + - "\n" + + "
External IPs:
\n" + + "
\n" + + "{{externalIP}}, \n" + + "
\n" + + "
Routes:
\n" + + "
\n" + + "\n" + "Create route\n" + - "\n" + - "\n" + - "{{route | routeLabel}}\n" + - "{{route | routeLabel}}\n" + - ", \n" + + "None\n" + "\n" + "
\n" + "\n" + - "
\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
Node PortService PortTarget Port
\n" + - "{{portMapping.nodePort}}\n" + - "none\n" + - "{{portMapping.port}}/{{portMapping.protocol}}\n" + - "({{portMapping.name}}){{portMapping.targetPort}}
\n" + + "

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

\n" + + "
\n" + + "\n" + + "
\n" + + "
\n" + + "Learn more about routes and services\n" + + "
\n" + + "

Pods

\n" + + "
\n" + + "\n" + "
\n" + "\n" + "
\n" + @@ -8021,19 +8017,20 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "\n" + "\n" + "\n" + - "\n" + + "\n" + "\n" + "\n" + "\n" + "\n" + + "\n" + "\n" + "\n" + "\n" + - "\n" + + "\n" + "\n" + "\n" + "\n" + - "\n" + "\n" + "\n" + + "\n" + "\n" + "\n" + "
Name{{customNameHeader || 'Name'}}StatusContainers ReadyContainer RestartsAgeReceiving Traffic
{{emptyMessage || 'No pods to show'}}
{{emptyMessage || 'No pods to show'}}
\n" + + "\n" + "{{pod.metadata.name}}\n" + "\n" + "\n" + @@ -8049,6 +8046,18 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "{{pod | numContainersReady}}/{{pod.spec.containers.length}}{{pod | numContainerRestarts}}\n" + + "\n" + + "\n" + + "Yes\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "No\n" + + "\n" + + "\n" + + "
" @@ -8145,6 +8154,85 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( ); + $templateCache.put('views/directives/traffic-table.html', + " \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + " 0\">\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
{{customNameHeader || 'Route'}} / Node PortService PortTarget PortHostnameTLS Termination
{{emptyMessage || 'No routes or ports to show'}}
\n" + + "{{routes[routeName].metadata.name}}\n" + + "\n" + + "\n" + + "\n" + + " / {{port.nodePort}}\n" + + "\n" + + "\n" + + "{{port.port}}/{{port.protocol}}\n" + + "({{port.name}})\n" + + "\n" + + "{{port.targetPort}}\n" + + "\n" + + "\n" + + "{{routes[routeName] | routeLabel}}\n" + + "\n" + + "\n" + + "{{routes[routeName] | routeLabel}}\n" + + "\n" + + "\n" + + "\n" + + "Pending\n" + + "\n" + + "\n" + + "{{routes[routeName].spec.tls.termination}}\n" + + " \n" + + "
\n" + + "none\n" + + "{{port.nodePort}}\n" + + "\n" + + "{{port.port}}/{{port.protocol}}\n" + + "({{port.name}})\n" + + "\n" + + "{{port.targetPort}}\n" + + "none\n" + + "none\n" + + "
" + ); + + $templateCache.put('views/directives/truncate-long-text.html', "{{content}}\n" + "\n" +