Explorar o código

Merge remote-tracking branch 'origin/trunk' into trunk

Oleg Nechiporenko %!s(int64=11) %!d(string=hai) anos
pai
achega
fe7f25b542
Modificáronse 32 ficheiros con 911 adicións e 97 borrados
  1. 7 4
      ambari-agent/src/main/python/ambari_agent/Heartbeat.py
  2. 32 0
      ambari-agent/src/main/python/ambari_agent/HostInfo.py
  3. 6 3
      ambari-agent/src/test/python/ambari_agent/TestHeartbeat.py
  4. 16 0
      ambari-agent/src/test/python/ambari_agent/TestHostInfo.py
  5. 17 6
      ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
  6. 7 2
      ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatMonitor.java
  7. 25 2
      ambari-server/src/main/java/org/apache/ambari/server/agent/HostStatus.java
  8. 36 8
      ambari-server/src/main/java/org/apache/ambari/server/controller/nagios/NagiosPropertyProvider.java
  9. 53 1
      ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java
  10. 16 1
      ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatHandler.java
  11. 9 0
      ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatMonitor.java
  12. 20 0
      ambari-server/src/test/java/org/apache/ambari/server/controller/nagios/NagiosPropertyProviderTest.java
  13. 213 0
      ambari-web/app/assets/data/configurations/service_versions.json
  14. 1 0
      ambari-web/app/controllers.js
  15. 73 0
      ambari-web/app/controllers/main/dashboard/config_history_controller.js
  16. 6 0
      ambari-web/app/controllers/main/service/item.js
  17. 2 1
      ambari-web/app/mappers.js
  18. 44 0
      ambari-web/app/mappers/service_config_version_mapper.js
  19. 10 0
      ambari-web/app/messages.js
  20. 1 0
      ambari-web/app/models.js
  21. 40 0
      ambari-web/app/models/service_config_version.js
  22. 14 3
      ambari-web/app/routes/main.js
  23. 2 51
      ambari-web/app/styles/application.less
  24. 1 1
      ambari-web/app/templates/main/dashboard.hbs
  25. 83 0
      ambari-web/app/templates/main/dashboard/config_history.hbs
  26. 3 3
      ambari-web/app/templates/main/host.hbs
  27. 1 0
      ambari-web/app/views.js
  28. 7 1
      ambari-web/app/views/common/filter_view.js
  29. 20 1
      ambari-web/app/views/common/table_view.js
  30. 9 5
      ambari-web/app/views/main/dashboard.js
  31. 133 0
      ambari-web/app/views/main/dashboard/config_history_view.js
  32. 4 4
      ambari-web/app/views/main/service/menu.js

+ 7 - 4
ambari-agent/src/main/python/ambari_agent/Heartbeat.py

@@ -47,7 +47,10 @@ class Heartbeat:
 
     
     nodeStatus = { "status" : "HEALTHY",
-                   "cause" : "NONE"}
+                   "cause" : "NONE" }
+    nodeStatus["alerts"] = []
+    
+    
     
     heartbeat = { 'responseId'        : int(id),
                   'timestamp'         : timestamp,
@@ -77,13 +80,12 @@ class Heartbeat:
     if logger.isEnabledFor(logging.DEBUG):
       logger.debug("Heartbeat: %s", pformat(heartbeat))
 
+    hostInfo = HostInfo(self.config)
     if (int(id) >= 0) and state_interval > 0 and (int(id) % state_interval) == 0:
-      hostInfo = HostInfo(self.config)
       nodeInfo = { }
-      
       # for now, just do the same work as registration
       # this must be the last step before returning heartbeat
-      hostInfo.register(nodeInfo, componentsMapped, commandsInProgress)
+      hostInfo.register(nodeInfo, componentsMapped, commandsInProgress)      
       heartbeat['agentEnv'] = nodeInfo
       mounts = Hardware.osdisks()
       heartbeat['mounts'] = mounts
@@ -92,6 +94,7 @@ class Heartbeat:
         logger.debug("agentEnv: %s", str(nodeInfo))
         logger.debug("mounts: %s", str(mounts))
 
+    nodeStatus["alerts"] = hostInfo.createAlerts(nodeStatus["alerts"])
     return heartbeat
 
 def main(argv=None):

+ 32 - 0
ambari-agent/src/main/python/ambari_agent/HostInfo.py

@@ -28,6 +28,7 @@ import subprocess
 import threading
 import shlex
 import platform
+import hostname
 from PackagesAnalyzer import PackagesAnalyzer
 from HostCheckReportFileHandler import HostCheckReportFileHandler
 from Hardware import Hardware
@@ -221,6 +222,37 @@ class HostInfo:
       pass
     return diskInfo
 
+  def createAlerts(self, alerts):
+    existingUsers = []
+    self.checkUsers(self.DEFAULT_USERS, existingUsers)
+    dirs = []
+    self.checkFolders(self.DEFAULT_DIRS, self.DEFAULT_PROJECT_NAMES, existingUsers, dirs)
+    alert = {
+      'name': 'host_alert',
+      'instance': None,
+      'service': 'AMBARI',
+      'component': 'host',
+      'host': hostname.hostname(),
+      'state': 'OK',
+      'label': 'Disk space',
+      'text': 'Used disk space less than 80%'}
+    message = ""
+    mountinfoSet = []
+    for dir in dirs:
+      if dir["type"] == 'directory':
+        mountinfo = self.osdiskAvailableSpace(dir['name'])
+        if int(mountinfo["percent"].strip('%')) >= 80:
+          if not mountinfo in mountinfoSet:
+            mountinfoSet.append(mountinfo)
+          message += str(dir['name']) + ";\n"
+
+    if message != "":
+      message = "These discs have low space:\n" + str(mountinfoSet) + "\n They include following critical directories:\n" + message
+      alert['state'] = 'WARNING'
+      alert['text'] = message
+    alerts.append(alert)
+    return alerts
+
   def checkFolders(self, basePaths, projectNames, existingUsers, dirs):
     foldersToIgnore = []
     for user in existingUsers:

+ 6 - 3
ambari-agent/src/test/python/ambari_agent/TestHeartbeat.py

@@ -27,6 +27,7 @@ from mock.mock import patch, MagicMock, call
 import StringIO
 import sys
 
+
 with patch("platform.linux_distribution", return_value = ('Suse','11','Final')):
   from ambari_agent.Heartbeat import Heartbeat
   from ambari_agent.ActionQueue import ActionQueue
@@ -64,7 +65,7 @@ class TestHeartbeat(TestCase):
     self.assertEquals(result['componentStatus'] is not None, True, "Heartbeat should contain componentStatus")
     self.assertEquals(result['reports'] is not None, True, "Heartbeat should contain reports")
     self.assertEquals(result['timestamp'] >= 1353679373880L, True)
-    self.assertEquals(len(result['nodeStatus']), 2)
+    self.assertEquals(len(result['nodeStatus']), 3)
     self.assertEquals(result['nodeStatus']['cause'], "NONE")
     self.assertEquals(result['nodeStatus']['status'], "HEALTHY")
     # result may or may NOT have an agentEnv structure in it
@@ -102,9 +103,10 @@ class TestHeartbeat(TestCase):
     hb = heartbeat.build(id = 0, state_interval=1, componentsMapped=True)
     self.assertEqual(register_mock.call_args_list[0][0][1], False)
 
-
+  @patch.object(HostInfo, "createAlerts")
   @patch.object(ActionQueue, "result")
-  def test_build_long_result(self, result_mock):
+  def test_build_long_result(self, result_mock, createAlerts_mock):
+    createAlerts_mock.return_value = []
     config = AmbariConfig.AmbariConfig().getConfig()
     config.set('agent', 'prefix', 'tmp')
     config.set('agent', 'cache_dir', "/var/lib/ambari-agent/cache")
@@ -169,6 +171,7 @@ class TestHeartbeat(TestCase):
     hb['timestamp'] = 'timestamp'
     expected = {'nodeStatus':
                   {'status': 'HEALTHY',
+                   'alerts': [],
                    'cause': 'NONE'},
                 'timestamp': 'timestamp', 'hostname': 'hostname',
                 'responseId': 10, 'reports': [

+ 16 - 0
ambari-agent/src/test/python/ambari_agent/TestHostInfo.py

@@ -523,6 +523,22 @@ class TestHostInfo(TestCase):
       run_os_command_mock.return_value = firewall.get_running_result()
       self.assertTrue(firewall.check_iptables())
 
+  @patch.object(HostInfo, "osdiskAvailableSpace")
+  def test_createAlerts(self, osdiskAvailableSpace_mock):
+    hostInfo = HostInfo()
+    osdiskAvailableSpace_mock.return_value = {
+      'size': '100',
+      'used': '50',
+      'available': '50',
+      'percent': '50%',
+      'mountpoint': '/testmount',
+      'type': 'ext4',
+      'device': 'device'}
+    result = hostInfo.createAlerts([])
+    self.assertEquals(1, len(result))
+
+
+
   @patch("subprocess.Popen")
   def test_run_os_command_exception(self, popen_mock):
     def base_test():

+ 17 - 6
ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java

@@ -17,14 +17,18 @@
  */
 package org.apache.ambari.server.agent;
 
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
-
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.HostNotFoundException;
 import org.apache.ambari.server.RoleCommand;
@@ -38,6 +42,7 @@ import org.apache.ambari.server.controller.MaintenanceStateHelper;
 import org.apache.ambari.server.metadata.ActionMetadata;
 import org.apache.ambari.server.state.AgentVersion;
 import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertState;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.ComponentInfo;
@@ -69,11 +74,6 @@ import org.apache.ambari.server.utils.VersionUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import com.google.gson.Gson;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.Singleton;
-
 
 /**
  * This class handles the heartbeats coming from the agent, passes on the information
@@ -195,6 +195,8 @@ public class HeartBeatHandler {
     // Calculate host status
     // NOTE: This step must be after processing command/status reports
     processHostStatus(heartbeat, hostname);
+    
+    calculateHostAlerts(heartbeat, hostname);
 
     // Send commands if node is active
     if (hostObject.getState().equals(HostState.HEALTHY)) {
@@ -204,6 +206,15 @@ public class HeartBeatHandler {
     return response;
   }
 
+  protected void calculateHostAlerts(HeartBeat heartbeat, String hostname)
+          throws AmbariException {
+      if (heartbeat != null && hostname != null) {
+        for (Cluster cluster : clusterFsm.getClustersForHost(hostname)) {
+          cluster.addAlerts(heartbeat.getNodeStatus().getAlerts());
+        }
+      }
+  }
+   
   protected void processHostStatus(HeartBeat heartbeat, String hostname) throws AmbariException {
 
     Host host = clusterFsm.getHost(hostname);

+ 7 - 2
ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatMonitor.java

@@ -62,6 +62,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import com.google.inject.Injector;
+import java.util.HashSet;
 
 /**
  * Monitors the node state and heartbeats.
@@ -294,8 +295,12 @@ public class HeartbeatMonitor implements Runnable {
     if (sch.getServiceComponentName().equals("NAGIOS_SERVER")) {
       // this requires special treatment
 
-      Collection<Alert> alerts = cluster.getAlerts();
-      if (null != alerts && alerts.size() > 0) {
+      Collection<Alert> clusterAlerts = cluster.getAlerts();
+      Collection<Alert> alerts = new HashSet<Alert>();
+      for (Alert alert : clusterAlerts) {
+        if (!alert.getName().equals("host_alert")) alerts.add(alert);
+      }
+      if (alerts.size() > 0) {
         statusCmd = new NagiosAlertCommand();
         ((NagiosAlertCommand) statusCmd).setAlerts(alerts);
       }

+ 25 - 2
ambari-server/src/main/java/org/apache/ambari/server/agent/HostStatus.java

@@ -17,6 +17,10 @@
  */
 package org.apache.ambari.server.agent;
 
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ambari.server.state.Alert;
+import org.codehaus.jackson.annotate.JsonProperty;
 
 /**
  * Status of the host as described by the agent.
@@ -38,24 +42,43 @@ public class HostStatus {
   }
   Status status;
   String cause;
+  List<Alert> alerts = new ArrayList<Alert>();
+      
+  @JsonProperty("status")
   public Status getStatus() {
     return status;
   }
+    
+  @JsonProperty("status")
   public void setStatus(Status status) {
     this.status = status;
   }
+  
+  @JsonProperty("cause")  
   public String getCause() {
     return cause;
   }
+  
+  @JsonProperty("cause")
   public void setCause(String cause) {
     this.cause = cause;
   }
-
+  
+  @JsonProperty("alerts")
+  public List<Alert> getAlerts() {
+    return alerts;
+  }
+  
+  @JsonProperty("alerts")
+  public void setAlerts(List<Alert> alerts) {
+    this.alerts = alerts;
+  }
+ 
   @Override
   public String toString() {
     return "HostStatus{" +
             "status=" + status +
             ", cause='" + cause + '\'' +
-            '}';
+            ", alerts=" + alerts.size() + '}';
   }
 }

+ 36 - 8
ambari-server/src/main/java/org/apache/ambari/server/controller/nagios/NagiosPropertyProvider.java

@@ -17,15 +17,20 @@
  */
 package org.apache.ambari.server.controller.nagios;
 
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -37,8 +42,8 @@ import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
 import java.util.regex.Pattern;
-
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
 import org.apache.ambari.server.configuration.Configuration;
@@ -49,6 +54,7 @@ import org.apache.ambari.server.controller.spi.Request;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.utilities.StreamProvider;
+import org.apache.ambari.server.state.Alert;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.Service;
@@ -57,10 +63,6 @@ import org.apache.commons.io.IOUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.gson.Gson;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-
 /**
  * Used to populate resources that have Nagios alertable properties.
  */
@@ -147,17 +149,43 @@ public class NagiosPropertyProvider extends BaseProvider implements PropertyProv
       @Override
       public void run() {
         for (String clusterName : CLUSTER_NAMES) {
+          List<NagiosAlert> alerts = new LinkedList<NagiosAlert>();
           try {
-            List<NagiosAlert> alerts = populateAlerts(clusterName);
-            
-            CLUSTER_ALERTS.put(clusterName, alerts);
+            alerts = populateAlerts(clusterName);
           } catch (Exception e) {
             LOG.error("Could not load Nagios alerts: " + e.getMessage());
           }
+          alerts.addAll(convertAlerts(clusterName));
+          CLUSTER_ALERTS.put(clusterName, alerts);
         }
       }
     }, 0L, 30L, TimeUnit.SECONDS);    
   }
+
+  
+  /**
+   * Convert Alert from cluster to NagiosAlert
+   * @param clusterName the cluster name
+   * @return Collection of NagiosAlerts
+   * @throws AmbariException 
+   */
+  public List<NagiosAlert> convertAlerts(String clusterName) {
+    Cluster cluster;
+    try {
+      cluster = clusters.getCluster(clusterName);
+    } catch (AmbariException ex) {
+      return new ArrayList<NagiosAlert>();
+    }
+    Collection<Alert> clusterAlerts = cluster.getAlerts();
+    List<NagiosAlert> results = new ArrayList<NagiosAlert>();
+    if (clusterAlerts != null) {
+      for (Alert alert : clusterAlerts) {
+        NagiosAlert a = new NagiosAlert(alert);
+        results.add(a);
+      }
+    }
+    return results;
+  }
   
   /**
    * Use only for testing to remove all cached alerts.

+ 53 - 1
ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java

@@ -17,6 +17,7 @@
  */
 package org.apache.ambari.server.state;
 
+import org.codehaus.jackson.annotate.JsonProperty;
 /**
  * An alert represents a problem or notice for a cluster.
  */
@@ -50,10 +51,15 @@ public class Alert {
     host = hostName;
     state = alertState;
   }
+
+  public Alert() {
+  }
  
   /**
    * @return the name
    */
+
+  @JsonProperty("name")     
   public String getName() {
     return name;
   }
@@ -61,6 +67,7 @@ public class Alert {
   /**
    * @return the service
    */
+  @JsonProperty("service")    
   public String getService() {
     return service;
   }
@@ -68,6 +75,7 @@ public class Alert {
   /**
    * @return the component
    */
+  @JsonProperty("component")  
   public String getComponent() {
     return component;
   }
@@ -75,6 +83,7 @@ public class Alert {
   /**
    * @return the host
    */
+  @JsonProperty("host")
   public String getHost() {
     return host;
   }
@@ -82,6 +91,7 @@ public class Alert {
   /**
    * @return the state
    */
+  @JsonProperty("state")
   public AlertState getState() {
     return state;
   }
@@ -89,6 +99,7 @@ public class Alert {
   /**
    * @return a short descriptive label for the alert
    */
+  @JsonProperty("label")  
   public String getLabel() {
     return label;
   }
@@ -96,6 +107,7 @@ public class Alert {
   /**
    * @param alertLabel a short descriptive label for the alert
    */
+  @JsonProperty("label")   
   public void setLabel(String alertLabel) {
     label = alertLabel;
   }
@@ -103,6 +115,7 @@ public class Alert {
   /**
    * @return detail text about the alert
    */
+  @JsonProperty("text")   
   public String getText() {
     return text;
   }
@@ -110,9 +123,47 @@ public class Alert {
   /**
    * @param alertText detail text about the alert
    */
+  @JsonProperty("text")   
   public void setText(String alertText) {
     text = alertText;
   }
+
+  @JsonProperty("instance")  
+  public String getInstance() {
+    return instance;
+  }
+  
+  @JsonProperty("instance")
+  public void setInstance(String instance) {
+    this.instance = instance;
+  }
+
+  @JsonProperty("name")
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  @JsonProperty("service")
+  public void setService(String service) {
+    this.service = service;
+  }
+
+  @JsonProperty("component")
+  public void setComponent(String component) {
+    this.component = component;
+  }
+
+  @JsonProperty("host")
+  public void setHost(String host) {
+    this.host = host;
+  }
+
+  @JsonProperty("state")
+  public void setState(AlertState state) {
+    this.state = state;
+  }
+
+  
   
   @Override
   public int hashCode() {
@@ -148,7 +199,8 @@ public class Alert {
     sb.append("service=").append(service).append(", ");
     sb.append("component=").append(component).append(", ");
     sb.append("host=").append(host).append(", ");
-    sb.append("instance=").append(instance);
+    sb.append("instance=").append(instance).append(", ");
+    sb.append("text='").append(text).append("'");
     sb.append('}');
     return sb.toString();
   }

+ 16 - 1
ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatHandler.java

@@ -165,10 +165,25 @@ public class TestHeartbeatHandler {
     aq.enqueue(DummyHostname1, new ExecutionCommand());
     HeartBeat hb = new HeartBeat();
     hb.setResponseId(0);
-    hb.setNodeStatus(new HostStatus(Status.HEALTHY, DummyHostStatus));
+    HostStatus hs = new HostStatus(Status.HEALTHY, DummyHostStatus);
+    List<Alert> al = new ArrayList<Alert>();
+    al.add(new Alert());
+    hs.setAlerts(al);
+    hb.setNodeStatus(hs);
     hb.setHostname(DummyHostname1);
+    
+    for (Map.Entry<String, Cluster> entry : clusters.getClusters().entrySet()) {
+      Cluster cl = entry.getValue();
+      assertEquals(0, cl.getAlerts().size());
+    }
 
     handler.handleHeartBeat(hb);
+    
+    for (Map.Entry<String, Cluster> entry : clusters.getClusters().entrySet()) {
+      Cluster cl = entry.getValue();
+      assertEquals(1, cl.getAlerts().size());
+    }
+    
     assertEquals(HostState.HEALTHY, hostObject.getState());
     assertEquals(0, aq.dequeueAll(DummyHostname1).size());
   }

+ 9 - 0
ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatMonitor.java

@@ -66,6 +66,7 @@ import org.slf4j.LoggerFactory;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.persist.PersistService;
+import java.util.ArrayList;
 
 public class TestHeartbeatMonitor {
 
@@ -164,6 +165,14 @@ public class TestHeartbeatMonitor {
     hb.setTimestamp(System.currentTimeMillis());
     hb.setResponseId(12);
     handler.handleHeartBeat(hb);
+    
+    List<Alert> al = new ArrayList<Alert>();
+    Alert alert = new Alert("host_alert", null, "AMBARI", null, hostname1, AlertState.OK);
+    al.add(alert);
+    for (Map.Entry<String, Cluster> entry : clusters.getClusters().entrySet()) {
+      Cluster cl = entry.getValue();
+      cl.addAlerts(al);
+    }
 
     List<StatusCommand> cmds = hm.generateStatusCommands(hostname1);
     assertTrue("HeartbeatMonitor should generate StatusCommands for host1", cmds.size() == 3);

+ 20 - 0
ambari-server/src/test/java/org/apache/ambari/server/controller/nagios/NagiosPropertyProviderTest.java

@@ -51,6 +51,8 @@ import com.google.inject.Binder;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Module;
+import java.util.Collection;
+import java.util.LinkedList;
 
 /**
  * Tests the nagios property provider
@@ -439,6 +441,24 @@ public class NagiosPropertyProviderTest {
     Assert.assertTrue(summary.get("WARNING").equals(Integer.valueOf(0)));
     Assert.assertTrue(summary.get("CRITICAL").equals(Integer.valueOf(1)));
   }
+
+  @Test
+  public void testConvertAlerts() throws Exception {
+    Injector inj = Guice.createInjector(new GuiceModule());
+    
+    Clusters clusters = inj.getInstance(Clusters.class);
+    Cluster cluster = createMock(Cluster.class);
+    expect(cluster.getAlerts()).andReturn(Collections.<Alert>emptySet()).anyTimes();
+    expect(clusters.getCluster("c1")).andReturn(cluster);
+    replay(clusters, cluster);
+    TestStreamProvider streamProvider = new TestStreamProvider("nagios_alerts.txt");
+    NagiosPropertyProvider npp = new NagiosPropertyProvider(Resource.Type.Service,
+    streamProvider, "ServiceInfo/cluster_name", "ServiceInfo/service_name");
+    List<NagiosAlert> list = npp.convertAlerts("c1");
+    Assert.assertNotNull(list);
+    Assert.assertEquals(0, list.size());
+  }  
+  
   
   @Test
   public void testNagiosServiceAlertsWithPassive() throws Exception {

+ 213 - 0
ambari-web/app/assets/data/configurations/service_versions.json

@@ -0,0 +1,213 @@
+{
+  "items": [
+    {
+      "serviceconfigversion": "1",
+      "servicename": "HDFS",
+      "createtime": "43800000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    },
+    {
+      "serviceconfigversion": "1",
+      "servicename": "YARN",
+      "createtime": "43300000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    },
+    {
+      "serviceconfigversion": "2",
+      "servicename": "HDFS",
+      "createtime": "43500000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    },
+    {
+      "serviceconfigversion": "2",
+      "servicename": "YARN",
+      "createtime": "13800000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    },
+    {
+      "serviceconfigversion": "3",
+      "servicename": "HDFS",
+      "createtime": "23800000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    },
+    {
+      "serviceconfigversion": "3",
+      "servicename": "YARN",
+      "createtime": "47800000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    },
+    {
+      "serviceconfigversion": "4",
+      "servicename": "HDFS",
+      "createtime": "43900000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    },
+    {
+      "serviceconfigversion": "4",
+      "servicename": "YARN",
+      "createtime": "33800000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    },
+    {
+      "serviceconfigversion": "5",
+      "servicename": "HDFS",
+      "createtime": "41800000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    },
+    {
+      "serviceconfigversion": "5",
+      "servicename": "YARN",
+      "createtime": "46800000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    },
+    {
+      "serviceconfigversion": "6",
+      "servicename": "YARN",
+      "createtime": "44800000000",
+      "appliedtime": "58600000000",
+      "author": "admin",
+      "notes": "Notes should be here",
+      "configurations": [
+        {
+          "type": "core-site",
+          "tag": "1",
+          "version": "1",
+          "Config": {
+            "cluster_name": "c1"
+          },
+          "properties": {}
+        }
+      ]
+    }
+  ]
+}

+ 1 - 0
ambari-web/app/controllers.js

@@ -26,6 +26,7 @@ require('controllers/installer');
 require('controllers/global/background_operations_controller');
 require('controllers/main');
 require('controllers/main/dashboard');
+require('controllers/main/dashboard/config_history_controller');
 require('controllers/main/admin');
 require('controllers/main/admin/highAvailability_controller');
 require('controllers/main/admin/highAvailability/nameNode/wizard_controller');

+ 73 - 0
ambari-web/app/controllers/main/dashboard/config_history_controller.js

@@ -0,0 +1,73 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.MainConfigHistoryController = Em.ArrayController.extend({
+  name: 'mainConfigHistoryController',
+
+  dataSource: App.ServiceConfigVersion.find(),
+  content: function () {
+    return this.get('dataSource').toArray();
+  }.property('dataSource.@each.isLoaded'),
+  isLoaded: false,
+  isPolling: false,
+
+  /**
+   * initial data load
+   */
+  load: function () {
+    var self = this;
+
+    this.set('isLoaded', false);
+    this.loadHistoryToModel().done(function () {
+      self.set('isLoaded', true);
+      self.doPolling();
+    });
+  },
+
+  /**
+   * get data from server and push it to model
+   * @return {*}
+   */
+  loadHistoryToModel: function () {
+    var dfd = $.Deferred();
+
+    var url = '/data/configurations/service_versions.json';
+
+    App.HttpClient.get(url, App.serviceConfigVersionsMapper, {
+      complete: function () {
+        dfd.resolve();
+      }
+    });
+    return dfd.promise();
+  },
+
+  /**
+   * request latest data from server and update content
+   */
+  doPolling: function () {
+    var self = this;
+
+    setTimeout(function () {
+      if (self.get('isPolling')) {
+        self.loadHistoryToModel().done(function () {
+          self.doPolling();
+        })
+      }
+    }, App.componentsUpdateInterval);
+  }
+});

+ 6 - 0
ambari-web/app/controllers/main/service/item.js

@@ -44,6 +44,12 @@ App.MainServiceItemController = Em.Controller.extend({
     }
   },
 
+  /**
+   * flag to control router switch between service summary and configs
+   * @type {boolean}
+   */
+  routeToConfigs: false,
+
   isClientsOnlyService: function() {
     return App.get('services.clientOnly').contains(this.get('content.serviceName'));
   }.property('content.serviceName'),

+ 2 - 1
ambari-web/app/mappers.js

@@ -31,4 +31,5 @@ require('mappers/target_cluster_mapper');
 require('mappers/dataset_mapper');
 require('mappers/component_config_mapper');
 require('mappers/components_state_mapper');
-require('mappers/jobs/hive_jobs_mapper');
+require('mappers/jobs/hive_jobs_mapper');
+require('mappers/service_config_version_mapper');

+ 44 - 0
ambari-web/app/mappers/service_config_version_mapper.js

@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.serviceConfigVersionsMapper = App.QuickDataMapper.create({
+  model: App.ServiceConfigVersion,
+  config: {
+    service_name: 'servicename',
+    version: "serviceconfigversion",
+    create_time: 'createtime',
+    applied_time: 'appliedtime',
+    author: 'author',
+    notes: 'notes'
+  },
+  map: function (json) {
+    var result = [];
+
+    if (json && json.items) {
+      json.items.forEach(function (item) {
+        var parsedItem = this.parseIt(item, this.get('config'));
+        parsedItem.id = parsedItem.service_name + '_' + parsedItem.version;
+        result.push(parsedItem);
+      }, this);
+
+      App.store.loadMany(this.get('model'), result);
+    }
+  }
+});

+ 10 - 0
ambari-web/app/messages.js

@@ -210,6 +210,8 @@ Em.I18n.translations = {
   'common.free': 'free',
   'common.type.string': 'string',
   'common.type.number': 'number',
+  'common.author': 'Author',
+  'common.notes': 'Notes',
 
   'passiveState.turnOn':'Turn On Maintenance Mode',
   'passiveState.turnOff':'Turn Off Maintenance Mode',
@@ -1917,6 +1919,14 @@ Em.I18n.translations = {
   'dashboard.services.configs.popup.stopService.body' : 'Service needs to be stopped for reconfiguration',
   'dashboard.services.configs.popup.restartService.header' : 'Restart service',
   'dashboard.services.configs.popup.restartService.body' : 'Service needs to be restarted for reconfiguration',
+
+  'dashboard.configHistory.title': 'Configs',
+  'dashboard.configHistory.table.version.title' : 'Service: version',
+  'dashboard.configHistory.table.modified.title' : 'Modified',
+  'dashboard.configHistory.table.empty' : 'No history to display',
+  'dashboard.configHistory.table.filteredHostsInfo': '{0} of {1} versions showing',
+  'dashboard.configHistory.table.current': '(Current)',
+
   'timeRange.presets.1hour':'1h',
   'timeRange.presets.12hour':'12h',
   'timeRange.presets.1day':'1d',

+ 1 - 0
ambari-web/app/models.js

@@ -57,3 +57,4 @@ require('models/config_group');
 require('models/jobs/tez_dag');
 require('models/jobs/job');
 require('models/jobs/hive_job');
+require('models/service_config_version');

+ 40 - 0
ambari-web/app/models/service_config_version.js

@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+var App = require('app');
+var dateUtil = require('utils/date');
+
+
+App.ServiceConfigVersion = DS.Model.extend({
+  serviceName: DS.attr('string'),
+  version: DS.attr('number'),
+  createTime: DS.attr('number'),
+  appliedTime: DS.attr('number'),
+  author: DS.attr('string'),
+  notes: DS.attr('string'),
+  serviceVersion: function(){
+    return this.get('serviceName') + ': '+ this.get('version');
+  }.property('serviceName', 'version'),
+  modifiedDate: function() {
+    return dateUtil.dateFormat(this.get('createTime'));
+  }.property('createTime'),
+  isCurrent: true
+});
+
+App.ServiceConfigVersion.FIXTURES = [];

+ 14 - 3
ambari-web/app/routes/main.js

@@ -118,7 +118,6 @@ module.exports = Em.Route.extend({
         });
       }
     }),
-    //on click nav tabs events, go to widgets view or heatmap view
     goToDashboardView: function (router, event) {
       router.transitionTo(event.context);
     },
@@ -161,7 +160,19 @@ module.exports = Em.Route.extend({
         event.view.set('active', "active");
         router.transitionTo(event.context);
       }
-    })
+    }),
+    configHistory: Em.Route.extend({
+      route: '/config_history',
+      connectOutlets: function (router, context) {
+        router.set('mainDashboardController.selectedCategory', 'configHistory');
+        router.get('mainDashboardController').connectOutlet('mainConfigHistory');
+      }
+    }),
+    goToServiceConfigs: function (router, event) {
+      router.get('mainServiceItemController').set('routeToConfigs', true);
+      App.router.transitionTo('main.services.service.configs', App.Service.find(event.context));
+      router.get('mainServiceItemController').set('routeToConfigs', false);
+    }
   }),
 
   apps: Em.Route.extend({
@@ -656,7 +667,7 @@ module.exports = Em.Route.extend({
             if (!service || !service.get('isLoaded')) {
               service = App.Service.find().objectAt(0); // getting the first service to display
             }
-            if (service.get('routeToConfigs')) {
+            if (router.get('mainServiceItemController').get('routeToConfigs')) {
               router.transitionTo('service.configs', service);
             }
             else {

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 2 - 51
ambari-web/app/styles/application.less


+ 1 - 1
ambari-web/app/templates/main/dashboard.hbs

@@ -30,7 +30,7 @@
         {{/each}}
       </ul>
 
-      <!--show widgets or heapmaps in the content-->
+      <!--show widgets, heatmaps or configs in the content-->
       {{outlet}}
     </div>
 </div>

+ 83 - 0
ambari-web/app/templates/main/dashboard/config_history.hbs

@@ -0,0 +1,83 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<div id="config_history">
+    <table class="table table-bordered table-striped" >
+        <thead>
+        {{#view view.sortView classNames="label-row"}}
+            {{view view.parentView.versionSort}}
+            {{view view.parentView.modifiedSort}}
+            {{view view.parentView.authorSort}}
+            {{view view.parentView.notesSort}}
+        {{/view}}
+
+        <tr class="filter-row">
+            <th class="first">{{view view.versionFilterView}}</th>
+            <th>{{view view.modifiedFilterView}}</th>
+            <th>{{view view.authorFilterView}}</th>
+            <th>{{view view.notesFilterView}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        {{#if view.pageContent}}
+          {{#each item in view.pageContent}}
+              <tr>
+                  <td class="first"><a {{action goToServiceConfigs item.serviceName}}>
+                    {{item.serviceVersion}}{{#if item.isCurrent}}&nbsp;{{t dashboard.configHistory.table.current}}{{/if}}
+                  </a></td>
+                  <td>{{item.modifiedDate}}</td>
+                  <td>{{item.author}}</td>
+                  <td>{{item.notes}}</td>
+              </tr>
+          {{/each}}
+        {{else}}
+            <tr>
+                <td class="first" colspan="4">
+                  {{t dashboard.configHistory.table.empty}}
+                </td>
+            </tr>
+        {{/if}}
+        </tbody>
+    </table>
+
+    <div class="page-bar">
+        <div class="filtered-info span4">
+            <label>{{view.filteredContentInfo}} - <a {{action clearFilters target="view"}}
+                    href="#">{{t tableView.filters.clearAllFilters}}</a></label>
+        </div>
+        <div class="selected-hosts-info span4">
+          {{#if view.showSelectedFilter}}
+              <div>
+                  <a {{action filterSelected target="view"}} href="#">
+                    {{view.selectedHosts.length}}
+                    {{pluralize view.selectedHostsCount singular="t:hosts.filters.selectedHostInfo" plural="t:hosts.filters.selectedHostsInfo"}}
+                  </a>
+              </div>
+              - <a {{action clearSelection target="view"}} href="#">{{t hosts.filters.clearSelection}}</a>
+          {{/if}}
+        </div>
+        <div class="items-on-page">
+            <label>{{t common.show}}: {{view view.rowsPerPageSelectView selectionBinding="view.displayLength"}}</label>
+        </div>
+        <div class="info">{{view.paginationInfo}}</div>
+        <div class="paging_two_button">
+          {{view view.paginationLeft}}
+          {{view view.paginationRight}}
+        </div>
+    </div>
+</div>

+ 3 - 3
ambari-web/app/templates/main/host.hbs

@@ -55,7 +55,7 @@
     </div>
   </div>
 
-  <table class="datatable table table-bordered table-striped" id="hosts-table">
+  <table class="table table-bordered table-striped" id="hosts-table">
     <thead>
       {{#view view.sortView classNames="label-row" contentBinding="view.filteredContent"}}
         <th class="first"> </th>
@@ -70,7 +70,7 @@
         {{view view.parentView.loadAvgSort}}
         <th class="sort-view-6">{{t common.components}}</th>
       {{/view}}
-      <tr id="filter-row">
+      <tr class="filter-row">
         <th class="first"><div class="ember-view view-wrapper">{{view Ember.Checkbox checkedBinding="view.selectAllHosts"}}</div></th>
         <th> </th>
         <th>{{view view.nameFilterView}}</th>
@@ -147,7 +147,7 @@
   </div>
 
   <div class="page-bar">
-    <div class="filtered-hosts-info span4">
+    <div class="filtered-info span4">
       <label>{{view.filteredContentInfo}} - <a {{action clearFilters target="view"}} href="#">{{t tableView.filters.clearAllFilters}}</a></label>
     </div>
     <div class="selected-hosts-info span4">

+ 1 - 0
ambari-web/app/views.js

@@ -148,6 +148,7 @@ require('views/main/dashboard/widgets/node_managers_live');
 require('views/main/dashboard/widgets/yarn_memory');
 require('views/main/dashboard/widgets/supervisor_live');
 require('views/main/dashboard/widgets/flume_agent_live');
+require('views/main/dashboard/config_history_view');
 
 
 require('views/main/service');

+ 7 - 1
ambari-web/app/views/common/filter_view.js

@@ -271,7 +271,7 @@ module.exports = {
       attributeBindings: ['disabled','multiple'],
       disabled: false
     });
-    config.emptyValue = Em.I18n.t('any');
+    config.emptyValue = config.emptyValue || 'Any';
 
     return wrapperView.extend(config);
   },
@@ -480,6 +480,12 @@ module.exports = {
           return origin === compareValue;
         };
         break;
+      case 'select':
+        return function (origin, compareValue){
+          //TODO add filter by select value
+          return true;
+        };
+        break;
       case 'string':
       default:
         return function(origin, compareValue){

+ 20 - 1
ambari-web/app/views/common/table_view.js

@@ -18,7 +18,6 @@
 
 var App = require('app');
 var filters = require('views/common/filter_view');
-var sort = require('views/common/sort_view');
 
 App.TableView = Em.View.extend(App.UserPref, {
 
@@ -411,6 +410,26 @@ App.TableView = Em.View.extend(App.UserPref, {
     }
   }.observes('content.length'),
 
+  /**
+   * sort content by active sort column
+   */
+  sortContent: function() {
+    var activeSort = App.db.getSortingStatuses(this.get('controller.name')).find(function (sort) {
+      return (sort.status === 'sorting_asc' || sort.status === 'sorting_desc');
+    });
+    var sortIndexes = {
+      'sorting_asc': 1,
+      'sorting_desc': -1
+    };
+
+    this.get('content').sort(function (a, b) {
+      if (a.get(activeSort.name) > b.get(activeSort.name)) return sortIndexes[activeSort.status];
+      if (a.get(activeSort.name) < b.get(activeSort.name)) return -(sortIndexes[activeSort.status]);
+      return 0;
+    });
+    this.filter();
+  },
+
   /**
    * Does any filter is used on the page
    * @type {Boolean}

+ 9 - 5
ambari-web/app/views/main/dashboard.js

@@ -24,8 +24,8 @@ App.MainDashboardView = Em.View.extend({
   templateName: require('templates/main/dashboard'),
 
   selectedBinding: 'controller.selectedCategory',
-  categories: function() {
-    var items = [{
+  categories: [
+    {
       name: 'widgets',
       url: 'dashboard.index',
       label: Em.I18n.t('dashboard.widgets.title')
@@ -34,9 +34,13 @@ App.MainDashboardView = Em.View.extend({
       name: 'charts',
       url: 'dashboard.charts.index',
       label: Em.I18n.t('dashboard.heatmaps.title')
-    }];
-    return items;
-  }.property(''),
+    },
+    {
+      name: 'configHistory',
+      url: 'dashboard.configHistory',
+      label: Em.I18n.t('dashboard.configHistory.title')
+    }
+  ],
   NavItemView: Ember.View.extend({
     tagName: 'li',
     classNameBindings: 'isActive:active'.w(),

+ 133 - 0
ambari-web/app/views/main/dashboard/config_history_view.js

@@ -0,0 +1,133 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var filters = require('views/common/filter_view');
+var sort = require('views/common/sort_view');
+
+App.MainConfigHistoryView = App.TableView.extend({
+  templateName: require('templates/main/dashboard/config_history'),
+
+  controllerBinding: 'App.router.mainConfigHistoryController',
+
+  content: function () {
+    return this.get('controller.content');
+  }.property('controller.content'),
+
+  /**
+   * return filtered number of all content number information displayed on the page footer bar
+   * @returns {String}
+   */
+  filteredContentInfo: function () {
+    return this.t('hosts.filters.filteredHostsInfo').format(this.get('filteredCount'), this.get('content.length'));
+  }.property('filteredCount', 'totalCount'),
+
+
+  didInsertElement: function () {
+    this.set('controller.isPolling', true);
+    this.get('controller').load();
+  },
+
+  /**
+   * stop polling after leaving config history page
+   */
+  willDestroyElement: function () {
+    this.set('controller.isPolling', false);
+  },
+
+  sortView: sort.serverWrapperView,
+  versionSort: sort.fieldView.extend({
+    column: 1,
+    name: 'serviceVersion',
+    displayName: Em.I18n.t('dashboard.configHistory.table.version.title'),
+    classNames: ['first']
+  }),
+  modifiedSort: sort.fieldView.extend({
+    column: 2,
+    name: 'createTime',
+    displayName: Em.I18n.t('dashboard.configHistory.table.modified.title')
+  }),
+  authorSort: sort.fieldView.extend({
+    column: 3,
+    name: 'author',
+    displayName: Em.I18n.t('common.author')
+  }),
+  notesSort: sort.fieldView.extend({
+    column: 4,
+    name: 'notes',
+    displayName: Em.I18n.t('common.notes')
+  }),
+
+  versionFilterView: filters.createSelectView({
+    column: 1,
+    fieldType: 'filter-input-width',
+    content: ['All'],
+    valueBinding: "controller.filterObject.version",
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'select');
+    },
+    emptyValue: Em.I18n.t('common.all')
+  }),
+
+  modifiedFilterView: filters.createSelectView({
+    column: 2,
+    fieldType: 'filter-input-width',
+    content: ['Any'],
+    valueBinding: "controller.filterObject.modified",
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'select');
+    }
+  }),
+
+  authorFilterView: filters.createTextView({
+    column: 3,
+    fieldType: 'filter-input-width',
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'string');
+    }
+  }),
+
+  notesFilterView: filters.createTextView({
+    column: 4,
+    fieldType: 'filter-input-width',
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'string');
+    }
+  }),
+
+  /**
+   * sort content
+   */
+  refresh: function () {
+    this.sortContent();
+  },
+
+  /**
+   * associations between host property and column index
+   * @type {Array}
+   */
+  colPropAssoc: function () {
+    var associations = [];
+    associations[1] = 'serviceVersion';
+    associations[2] = 'createTime';
+    associations[3] = 'author';
+    associations[4] = 'notes';
+    return associations;
+  }.property()
+
+});

+ 4 - 4
ambari-web/app/views/main/service/menu.js

@@ -86,9 +86,9 @@ App.MainServiceMenuView = Em.CollectionView.extend({
     }.property('App.router.currentState.name', 'parentView.activeServiceId', 'isConfigurable'),
 
     goToConfigs: function () {
-      this.set('content.routeToConfigs', true);
+      App.router.set('mainServiceItemController.routeToConfigs', true);
       App.router.transitionTo('services.service.configs', this.get('content'));
-      this.set('content.routeToConfigs', false);
+      App.router.set('mainServiceItemController.routeToConfigs', false);
     },
 
     refreshRestartRequiredMessage: function() {
@@ -184,9 +184,9 @@ App.TopNavServiceMenuView = Em.CollectionView.extend({
     }.property('App.router.currentState.name', 'parentView.activeServiceId','isConfigurable'),
 
     goToConfigs: function () {
-      this.set('content.routeToConfigs', true);
+      App.router.set('mainServiceItemController.routeToConfigs', true);
       App.router.transitionTo('services.service.configs', this.get('content'));
-      this.set('content.routeToConfigs', false);
+      App.router.set('mainServiceItemController.routeToConfigs', false);
     },
 
     refreshRestartRequiredMessage: function() {

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio