diff --git a/app/collins/graphs/GangliaGraphConfig.scala b/app/collins/graphs/GangliaGraphConfig.scala
new file mode 100644
index 000000000..10f41dfd3
--- /dev/null
+++ b/app/collins/graphs/GangliaGraphConfig.scala
@@ -0,0 +1,38 @@
+package collins.graphs
+
+import util.config.{Configurable, ConfigValue}
+
+// todo: support by role custom metrics/graphs
+object GangliaGraphConfig extends Configurable {
+
+  override val namespace = "graph.GangliaGraphs"
+  override val referenceConfigFilename = "ganglia_graph_reference.conf"
+
+  def url = getString("url")(ConfigValue.Required).get
+
+  def timeRange = getString("timeRange", "1hr");
+
+  def hostSuffix = getString("hostSuffix", "");
+
+  // Ganglia has a distinction between graphs of individual metrics
+  // "load_one", and some pre-created reports that pulls things
+  // together in one graph image (show load_one, and load_five).
+  // Confusingly, these are sometimes called graphs, and sometimes
+  // reports.
+  def defaultGraphs = getStringList("defaultMetrics", List(
+    "load_all_report", "load_report", "mem_report", "cpu_report", "network_report", "packet_report"
+  ))
+
+  def defaultMetrics = getStringList("defaultMetrics", List(
+    "disk_free", "disk_total"
+  ))
+
+  override def validateConfig() {
+    url
+    hostSuffix
+    timeRange
+    defaultGraphs
+    defaultMetrics
+  }
+
+}
diff --git a/app/collins/graphs/GangliaGraphs.scala b/app/collins/graphs/GangliaGraphs.scala
new file mode 100644
index 000000000..3583270e0
--- /dev/null
+++ b/app/collins/graphs/GangliaGraphs.scala
@@ -0,0 +1,54 @@
+package collins.graphs
+
+import models.asset.AssetView
+
+import java.net.URLEncoder
+import play.api.libs.json._
+import com.codahale.jerkson.Json._
+
+import play.api.Application
+import play.api.mvc.Content
+import play.api.templates.Html
+
+case class GangliaGraphs(override val app: Application) extends GraphView {
+
+  override def get(asset: AssetView): Option[Content] = {
+    if (asset.isServerNode && asset.getHostnameMetaValue.isDefined) {
+      Some(getIframe(generate_dynamic_view_json(asset.getHostnameMetaValue.get)))
+    } else {
+      None
+    }
+  }
+
+  override def validateConfig() {
+    GangliaGraphConfig.pluginInitialize(app.configuration)
+  }
+
+  protected def getIframe(view_json: String) = {
+    collins.graphs.templates.html.ganglia(URLEncoder.encode(view_json, "UTF-8"))
+  }
+
+  def hostKey(hostname: String): String = {
+    hostname + GangliaGraphConfig.hostSuffix
+  }
+
+  def generate_dynamic_view_json(hostname: String): String = {
+    generate(
+      Map(
+        "view_name" -> "ad-hoc",
+        "view_type" -> "standard",
+        "items" -> (GangliaGraphConfig.defaultGraphs.map(g =>
+          Map(
+            "hostname" -> hostKey(hostname),
+            "graph" -> g
+          )) ++ GangliaGraphConfig.defaultMetrics.map(m =>
+            Map(
+              "hostname" -> hostKey(hostname),
+              "metric" -> m
+            )
+        ))
+      )
+    )
+  }
+
+}
diff --git a/app/collins/graphs/templates/ganglia.scala.html b/app/collins/graphs/templates/ganglia.scala.html
new file mode 100644
index 000000000..82293005b
--- /dev/null
+++ b/app/collins/graphs/templates/ganglia.scala.html
@@ -0,0 +1,29 @@
+@(ad_hoc_view: String)
+
+@genURL() = @{
+  Seq(
+    "%s?hide-hf=true".format(collins.graphs.GangliaGraphConfig.url),
+    "tab=v",
+    "r=%s".format(collins.graphs.GangliaGraphConfig.timeRange),
+    "ad-hoc-view=%s".format(ad_hoc_view)
+  ).mkString("&")
+}
+
+<style type="text/css">
+#ganglia-graph {
+  width: 100%;
+  height: 1600px;
+}
+</style>
+
+<iframe id="ganglia-graph" src="" frameborder="0"></iframe>
+
+<script type="text/javascript">
+  $('a[data-toggle="pill"]').on('show', function(e) {
+    var target = $(e.target)
+    var iframe = $('#ganglia-graph')
+    if (target.attr('href') === '#graph-info' && iframe.attr('src') === "") {
+      iframe.attr('src', "@Html(genURL())")
+    }
+  })
+</script>
diff --git a/app/util/config/ConfigAccessor.scala b/app/util/config/ConfigAccessor.scala
index 5781ba4ad..329078da8 100644
--- a/app/util/config/ConfigAccessor.scala
+++ b/app/util/config/ConfigAccessor.scala
@@ -89,6 +89,14 @@ trait ConfigAccessor {
 
   protected def getStringList(key: String): List[String] =
     getValue(key, _.getStringList(key).asScala.toList).getOrElse(List())
+  protected def getStringList(key: String, default: List[String]): List[String] = {
+    val list = getStringList(key)
+    if (list.isEmpty)
+      default
+    else
+      list
+  }
+
 
   protected def getStringSet(key: String): Set[String] = getStringList(key).toSet
   protected def getStringSet(key: String, default: Set[String]): Set[String] = {
diff --git a/conf/reference/ganglia_graph_reference.conf b/conf/reference/ganglia_graph_reference.conf
new file mode 100644
index 000000000..56b1d4dbf
--- /dev/null
+++ b/conf/reference/ganglia_graph_reference.conf
@@ -0,0 +1,9 @@
+graph {
+
+  class = "collins.graphs.GangliaGraphs"
+  enabled = false
+  GangliaGraphs {
+    url = "http://my-ganglia.domain.local/"
+  }
+
+}