You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@storm.apache.org by bo...@apache.org on 2014/06/04 02:33:25 UTC

[02/13] git commit: STORM-205. Add REST API to Storm UI.

STORM-205. Add REST API to Storm UI.


Project: http://git-wip-us.apache.org/repos/asf/incubator-storm/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-storm/commit/a06fc90e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-storm/tree/a06fc90e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-storm/diff/a06fc90e

Branch: refs/heads/master
Commit: a06fc90e5ddb15a62c5e8214c94f0d59333fecc7
Parents: 22215b5
Author: Sriharsha Chintalapani <ma...@harsha.io>
Authored: Sat May 3 20:31:31 2014 -0700
Committer: Sriharsha Chintalapani <ma...@harsha.io>
Committed: Sat May 3 20:31:31 2014 -0700

----------------------------------------------------------------------
 .gitignore                                      |    4 +-
 storm-core/src/clj/backtype/storm/ui/core.clj   | 1078 ++++++------------
 storm-core/src/ui/public/component.html         |   88 ++
 storm-core/src/ui/public/index.html             |   73 ++
 storm-core/src/ui/public/js/jquery.mustache.js  |  592 ++++++++++
 storm-core/src/ui/public/js/mustache.js         |  570 +++++++++
 storm-core/src/ui/public/js/purl.js             |  267 +++++
 storm-core/src/ui/public/js/script.js           |   51 +-
 .../templates/component-page-template.html      |  152 +++
 .../src/ui/public/templates/error-template.html |    4 +
 .../public/templates/index-page-template.html   |   62 +
 .../templates/topology-page-template.html       |   97 ++
 storm-core/src/ui/public/topology.html          |   81 ++
 13 files changed, 2393 insertions(+), 726 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-storm/blob/a06fc90e/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index b2a37f9..b575a02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,4 +27,6 @@ target
 /.lein-plugins/
 *.ipr
 *.iws
-.idea
\ No newline at end of file
+.idea
+.*
+!/.gitignore

http://git-wip-us.apache.org/repos/asf/incubator-storm/blob/a06fc90e/storm-core/src/clj/backtype/storm/ui/core.clj
----------------------------------------------------------------------
diff --git a/storm-core/src/clj/backtype/storm/ui/core.clj b/storm-core/src/clj/backtype/storm/ui/core.clj
index ad1a038..7943677 100644
--- a/storm-core/src/clj/backtype/storm/ui/core.clj
+++ b/storm-core/src/clj/backtype/storm/ui/core.clj
@@ -48,92 +48,6 @@
        (map #(.get_stats ^ExecutorSummary %))
        (filter not-nil?)))
 
-(def tips
-  "Defines a mapping of help texts for elements of the UI pages."
-  {:sys-stats "Use this to toggle inclusion of storm system components."
-   :version (str "The version of storm installed on the UI node. (Hopefully, "
-                 "this is the same on all storm nodes!)")
-   :nimbus-uptime (str "The duration the current Nimbus instance has been "
-                       "running. (Note that the storm cluster may have been "
-                       "deployed and available for a much longer period than "
-                       "the current Nimbus process has been running.)")
-   :num-supervisors "The number of nodes in the cluster currently."
-   :num-slots "Slots are Workers (processes)."
-   :num-execs "Executors are threads in a Worker process."
-   :num-tasks (str "A Task is an instance of a Bolt or Spout. The number of "
-                   "Tasks is almost always equal to the number of Executors.")
-   :name "The name given to the topology by when it was submitted."
-   :name-link "Click the name to view the Topology's information."
-   :topo-id "The unique ID given to a Topology each time it is launched."
-   :status "The status can be one of ACTIVE, INACTIVE, KILLED, or REBALANCING."
-   :topo-uptime "The time since the Topology was submitted."
-   :num-workers "The number of Workers (processes)."
-   :sup-id (str "A unique identifier given to a Supervisor when it joins the "
-                "cluster.")
-   :sup-host (str "The hostname reported by the remote host. (Note that this "
-                  "hostname is not the result of a reverse lookup at the "
-                  "Nimbus node.)")
-   :sup-uptime (str "The length of time a Supervisor has been registered to the "
-                    "cluster.")
-   :window (str "The past period of time for which the statistics apply. "
-                "Click on a value to set the window for this page.")
-   :emitted "The number of Tuples emitted."
-   :transferred "The number of Tuples emitted that sent to one or more bolts."
-   :complete-lat (str "The average time a Tuple \"tree\" takes to be completely "
-                      "processed by the Topology. A value of 0 is expected "
-                      "if no acking is done.")
-   :spout-acked (str "The number of Tuple \"trees\" successfully processed. A "
-                     "value of 0 is expected if no acking is done.")
-   :spout-failed (str "The number of Tuple \"trees\" that were explicitly "
-                      "failed or timed out before acking was completed. A value "
-                      "of 0 is expected if no acking is done.")
-   :comp-id "The ID assigned to a the Component by the Topology."
-   :comp-id-link "Click on the name to view the Component's page."
-   :capacity (str "If this is around 1.0, the corresponding Bolt is running as "
-                  "fast as it can, so you may want to increase the Bolt's "
-                  "parallelism. This is (number executed * average execute "
-                  "latency) / measurement time.")
-   :exec-lat (str "The average time a Tuple spends in the execute method. The "
-                  "execute method may complete without sending an Ack for the "
-                  "tuple.")
-   :num-executed "The number of incoming Tuples processed."
-   :proc-lat (str "The average time it takes to Ack a Tuple after it is first "
-                  "received.  Bolts that join, aggregate or batch may not Ack a "
-                  "tuple until a number of other Tuples have been received.")
-   :bolt-acked "The number of Tuples acknowledged by this Bolt."
-   :bolt-failed "The number of tuples Failed by this Bolt."
-   :stream (str "The name of the Tuple stream given in the Topolgy, or \""
-                Utils/DEFAULT_STREAM_ID "\" if none was given.")
-   :exec-id "The unique executor ID."
-   :exec-uptime "The length of time an Executor (thread) has been alive."
-   :port (str "The port number used by the Worker to which an Executor is "
-              "assigned. Click on the port number to open the logviewer page "
-              "for this Worker.")})
-
-(defn mk-system-toggle-button [include-sys?]
-  [:p {:class "js-only"}
-   [:span.tip.right {:title (:sys-stats tips)}
-    [:input {:type "button"
-             :value (str (if include-sys? "Hide" "Show") " System Stats")
-             :onclick "toggleSys()"}]]])
-
-(defn ui-template [body]
-  (html4
-   [:head
-    [:title "Storm UI"]
-    (include-css "/css/bootstrap-1.4.0.css")
-    (include-css "/css/style.css")
-    (include-js "/js/jquery-1.6.2.min.js")
-    (include-js "/js/jquery.tablesorter.min.js")
-    (include-js "/js/jquery.cookies.2.2.0.min.js")
-    (include-js "/js/bootstrap-twipsy.js")
-    (include-js "/js/script.js")
-    ]
-   [:body
-    [:h1 (link-to "/" "Storm UI")]
-    (seq body)
-    ]))
-
 (defn read-storm-version []
   (let [storm-home (System/getProperty "storm.home")
         release-path (format "%s/RELEASE" storm-home)
@@ -142,115 +56,6 @@
       (trim (slurp release-path))
       "Unknown")))
 
-(defn cluster-summary-table [^ClusterSummary summ]
-  (let [sups (.get_supervisors summ)
-        used-slots (reduce + (map #(.get_num_used_workers ^SupervisorSummary %) sups))
-        total-slots (reduce + (map #(.get_num_workers ^SupervisorSummary %) sups))
-        free-slots (- total-slots used-slots)
-        total-tasks (->> (.get_topologies summ)
-                         (map #(.get_num_tasks ^TopologySummary %))
-                         (reduce +))
-        total-executors (->> (.get_topologies summ)
-                             (map #(.get_num_executors ^TopologySummary %))
-                             (reduce +))]
-    (table [{:text "Version" :attr {:class "tip right"
-                                    :title (:version tips)}}
-            {:text "Nimbus uptime" :attr {:class "tip right"
-                                          :title (:nimbus-uptime tips)}}
-            {:text "Supervisors" :attr {:class "tip above"
-                                        :title (:num-supervisors tips)}}
-            {:text "Used slots" :attr {:class "tip above"
-                                       :title (:num-slots tips)}}
-            {:text "Free slots" :attr {:class "tip above"
-                                       :title (:num-slots tips)}}
-            {:text "Total slots" :attr {:class "tip above"
-                                       :title (:num-slots tips)}}
-            {:text  "Executors" :attr {:class "tip above"
-                                       :title (:num-execs tips)}}
-            {:text "Tasks" :attr {:class "tip left"
-                                  :title (:num-tasks tips)}}]
-           [[(read-storm-version)
-             (pretty-uptime-sec (.get_nimbus_uptime_secs summ))
-             (count sups)
-             used-slots
-             free-slots
-             total-slots
-             total-executors
-             total-tasks]])
-    ))
-
-(defn topology-link
-  ([id] (topology-link id id))
-  ([id content]
-     (link-to (url-format "/topology/%s" id) (escape-html content))))
-
-(defn main-topology-summary-table [summs]
-  (sorted-table
-   [{:text "Name" :attr {:class "tip right"
-                         :title (str (:name tips) " " (:name-link tips))}}
-    {:text "Id" :attr {:class "tip right"
-                       :title (:topo-id tips)}}
-    {:text "Status" :attr {:class "tip above"
-                           :title (:status tips)}}
-    {:text "Uptime" :attr {:class "tip above"
-                           :title (:topo-uptime tips)}}
-    {:text "Num workers" :attr {:class "tip above"
-                                :title (:num-workers tips)}}
-    {:text "Num executors" :attr {:class "tip above"
-                                  :title (:num-execs tips)}}
-    {:text "Num tasks" :attr {:class "tip above"
-                              :title (:num-tasks tips)}}]
-   (for [^TopologySummary t summs]
-     [(topology-link (.get_id t) (.get_name t))
-      (escape-html (.get_id t))
-      (.get_status t)
-      (pretty-uptime-sec (.get_uptime_secs t))
-      (.get_num_workers t)
-      (.get_num_executors t)
-      (.get_num_tasks t)
-      ])
-   :time-cols [3]
-   :sort-list "[[0,0]]"
-   ))
-
-(defn supervisor-summary-table [summs]
-  (sorted-table
-   [{:text "Id" :attr {:class "tip right"
-                       :title (:sup-id tips)}}
-    {:text "Host" :attr {:class "tip above"
-                         :title (:sup-host tips)}}
-    {:text "Uptime" :attr {:class "tip above"
-                         :title (:sup-uptime tips)}}
-    {:text "Slots" :attr {:class "tip above"
-                          :title (:num-slots tips)}}
-    {:text "Used slots" :attr {:class "tip left"
-                               :title (:num-slots tips)}}]
-   (for [^SupervisorSummary s summs]
-     [(.get_supervisor_id s)
-      (.get_host s)
-      (pretty-uptime-sec (.get_uptime_secs s))
-      (.get_num_workers s)
-      (.get_num_used_workers s)])
-   :time-cols [2]))
-
-(defn configuration-table [conf]
-  (sorted-table ["Key" "Value"]
-    (map #(vector (key %) (str (val %))) conf)))
-
-(defn main-page []
-  (with-nimbus nimbus
-    (let [summ (.getClusterInfo ^Nimbus$Client nimbus)]
-      (concat
-       [[:h2 "Cluster Summary"]]
-       [(cluster-summary-table summ)]
-       [[:h2 "Topology summary"]]
-       (main-topology-summary-table (.get_topologies summ))
-       [[:h2 "Supervisor summary"]]
-       (supervisor-summary-table (.get_supervisors summ))
-       [[:h2 "Nimbus Configuration"]]
-       (configuration-table (from-json (.getNimbusConf ^Nimbus$Client nimbus)))
-       ))))
-
 (defn component-type [^StormTopology topology id]
   (let [bolts (.get_bolts topology)
         spouts (.get_spouts topology)]
@@ -281,7 +86,6 @@
                      ))]
             ))))
 
-
 (defn expand-averages-seq [average-seq counts-seq]
   (->> (map vector average-seq counts-seq)
        (map #(apply expand-averages %))
@@ -414,88 +218,6 @@
 (defn bolt-summary? [topology s]
   (= :bolt (executor-summary-type topology s)))
 
-(defn topology-summary-table [^TopologyInfo summ]
-  (let [executors (.get_executors summ)
-        workers (set (for [^ExecutorSummary e executors] [(.get_host e) (.get_port e)]))]
-    (table [{:text "Name" :attr {:class "tip right"
-                                 :title (:name tips)}}
-            {:text "Id" :attr {:class "tip right"
-                               :title (:topo-id tips)}}
-            {:text "Status" :attr {:class "tip above"
-                                   :title (:status tips)}}
-            {:text "Uptime" :attr {:class "tip above"
-                                   :title (:topo-uptime tips)}}
-            {:text "Num workers" :attr {:class "tip above"
-                                        :title (:num-workers tips)}}
-            {:text "Num executors" :attr {:class "tip above"
-                                          :title (:num-execs tips)}}
-            {:text "Num tasks" :attr {:class "tip above"
-                                      :title (:num-tasks tips)}}]
-           [[(escape-html (.get_name summ))
-             (escape-html (.get_id summ))
-             (.get_status summ)
-             (pretty-uptime-sec (.get_uptime_secs summ))
-             (count workers)
-             (count executors)
-             (sum-tasks executors)
-             ]]
-           )))
-
-(defn total-aggregate-stats [spout-summs bolt-summs include-sys?]
-  (let [spout-stats (get-filled-stats spout-summs)
-        bolt-stats (get-filled-stats bolt-summs)
-        agg-spout-stats (-> spout-stats
-                            (aggregate-spout-stats include-sys?)
-                            aggregate-spout-streams)
-        agg-bolt-stats (-> bolt-stats
-                           (aggregate-bolt-stats include-sys?)
-                           aggregate-bolt-streams)]
-    (merge-with
-     (fn [s1 s2]
-       (merge-with + s1 s2))
-     (select-keys agg-bolt-stats [:emitted :transferred])
-     agg-spout-stats
-     )))
-
-(defn stats-times [stats-map]
-  (sort-by #(Integer/parseInt %)
-           (-> stats-map
-               clojurify-structure
-               (dissoc ":all-time")
-               keys)))
-
-(defn topology-stats-table [id window stats]
-  (let [times (stats-times (:emitted stats))
-        display-map (into {} (for [t times] [t pretty-uptime-sec]))
-        display-map (assoc display-map ":all-time" (fn [_] "All time"))]
-    (sorted-table
-     [{:text "Window" :attr {:class "tip right"
-                             :title (:window tips)}}
-      {:text "Emitted" :attr {:class "tip above"
-                              :title (:emitted tips)}}
-      {:text "Transferred" :attr {:class "tip above"
-                                  :title (:transferred tips)}}
-      {:text "Complete latency (ms)" :attr {:class "tip above"
-                                            :title (:complete-lat tips)}}
-      {:text "Acked" :attr {:class "tip above"
-                            :title (:spout-acked tips)}}
-      {:text "Failed" :attr {:class "tip left"
-                            :title (:spout-failed tips)}}]
-     (for [k (concat times [":all-time"])
-           :let [disp ((display-map k) k)]]
-       [(link-to (if (= k window) {:class "red"} {})
-                 (url-format "/topology/%s?window=%s" id k)
-                 (escape-html disp))
-        (get-in stats [:emitted k])
-        (get-in stats [:transferred k])
-        (float-str (get-in stats [:complete-latencies k]))
-        (get-in stats [:acked k])
-        (get-in stats [:failed k])
-        ]
-       )
-     :time-cols [0]
-     )))
-
 (defn group-by-comp [summs]
   (let [ret (group-by #(.get_component_id ^ExecutorSummary %) summs)]
     (into (sorted-map) ret )))
@@ -516,19 +238,20 @@
        (error-subset (.get_error ^ErrorInfo error))]
       )))
 
-(defn component-link [storm-id id]
-  (link-to (url-format "/topology/%s/component/%s" storm-id id) (escape-html id)))
+(defn component-task-summs [^TopologyInfo summ topology id]
+  (let [spout-summs (filter (partial spout-summary? topology) (.get_executors summ))
+        bolt-summs (filter (partial bolt-summary? topology) (.get_executors summ))
+        spout-comp-summs (group-by-comp spout-summs)
+        bolt-comp-summs (group-by-comp bolt-summs)
+        ret (if (contains? spout-comp-summs id)
+              (spout-comp-summs id)
+              (bolt-comp-summs id))]
+    (sort-by #(-> ^ExecutorSummary % .get_executor_info .get_task_start) ret)
+    ))
 
 (defn worker-log-link [host port]
-  (link-to (url-format "http://%s:%s/log?file=worker-%s.log"
-              host (*STORM-CONF* LOGVIEWER-PORT) port) (str port)))
-
-(defn render-capacity [capacity]
-  (let [capacity (nil-to-zero capacity)]
-    [:span (if (> capacity 0.9)
-                 {:class "red"}
-                 {})
-           (float-str capacity)]))
+  (url-format "http://%s:%s/log?file=worker-%s.log"
+              host (*STORM-CONF* LOGVIEWER-PORT) port))
 
 (defn compute-executor-capacity [^ExecutorSummary e]
   (let [stats (.get_stats e)
@@ -553,87 +276,28 @@
        (map nil-to-zero)
        (apply max)))
 
-(defn spout-comp-table [top-id summ-map errors window include-sys?]
-  (sorted-table
-   [{:text "Id" :attr {:class "tip right"
-                       :title (str (:comp-id tips) " " (:comp-id-link tips))}}
-    {:text "Executors" :attr {:class "tip right"
-                       :title (:num-execs tips)}}
-    {:text "Tasks" :attr {:class "tip above"
-                   :title (:num-tasks tips)}}
-    {:text "Emitted" :attr {:class "tip above"
-                     :title (:emitted tips)}}
-    {:text "Transferred" :attr {:class "tip above"
-                         :title (:transferred tips)}}
-    {:text "Complete latency (ms)" :attr {:class "tip above"
-                                   :title (:complete-lat tips)}}
-    {:text "Acked" :attr {:class "tip above"
-                          :title (:spout-acked tips)}}
-    {:text "Failed" :attr {:class "tip above"
-                           :title (:spout-failed tips)}}
-    "Last error"]
-   (for [[id summs] summ-map
-         :let [stats-seq (get-filled-stats summs)
-               stats (aggregate-spout-streams
-                      (aggregate-spout-stats
-                       stats-seq include-sys?))]]
-     [(component-link top-id id)
-      (count summs)
-      (sum-tasks summs)
-      (get-in stats [:emitted window])
-      (get-in stats [:transferred window])
-      (float-str (get-in stats [:complete-latencies window]))
-      (get-in stats [:acked window])
-      (get-in stats [:failed window])
-      (most-recent-error (get errors id))
-      ]
+(defn total-aggregate-stats [spout-summs bolt-summs include-sys?]
+  (let [spout-stats (get-filled-stats spout-summs)
+        bolt-stats (get-filled-stats bolt-summs)
+        agg-spout-stats (-> spout-stats
+                            (aggregate-spout-stats include-sys?)
+                            aggregate-spout-streams)
+        agg-bolt-stats (-> bolt-stats
+                           (aggregate-bolt-stats include-sys?)
+                           aggregate-bolt-streams)]
+    (merge-with
+     (fn [s1 s2]
+       (merge-with + s1 s2))
+     (select-keys agg-bolt-stats [:emitted :transferred])
+     agg-spout-stats
      )))
 
-(defn bolt-comp-table [top-id summ-map errors window include-sys?]
-  (sorted-table
-   [{:text "Id" :attr {:class "tip right"
-                       :title (str (:comp-id tips) " " (:comp-id-link tips))}}
-    {:text "Executors" :attr {:class "tip right"
-                              :title (:num-execs tips)}}
-    {:text "Tasks" :attr {:class "tip above"
-                          :title (:num-tasks tips)}}
-    {:text "Emitted" :attr {:class "tip above"
-                            :title (:emitted tips)}}
-    {:text "Transferred" :attr {:class "tip above"
-                                :title (:transferred tips)}}
-    {:text "Capacity (last 10m)" :attr {:class "tip above"
-                                        :title (:capacity tips)}}
-    {:text "Execute latency (ms)" :attr {:class "tip above"
-                                         :title (:exec-lat tips)}}
-    {:text "Executed" :attr {:class "tip above"
-                             :title (:num-executed tips)}}
-    {:text "Process latency (ms)":attr {:class "tip above"
-                                        :title (:proc-lat tips)}}
-    {:text "Acked" :attr {:class "tip above"
-                          :title (:bolt-acked tips)}}
-    {:text "Failed" :attr {:class "tip left"
-                           :title (:bolt-failed tips)}}
-    "Last error"]
-   (for [[id summs] summ-map
-         :let [stats-seq (get-filled-stats summs)
-               stats (aggregate-bolt-streams
-                      (aggregate-bolt-stats
-                       stats-seq include-sys?))
-               ]]
-     [(component-link top-id id)
-      (count summs)
-      (sum-tasks summs)
-      (get-in stats [:emitted window])
-      (get-in stats [:transferred window])
-      (render-capacity (compute-bolt-capacity summs))
-      (float-str (get-in stats [:execute-latencies window]))
-      (get-in stats [:executed window])
-      (float-str (get-in stats [:process-latencies window]))
-      (get-in stats [:acked window])
-      (get-in stats [:failed window])
-      (most-recent-error (get errors id))
-      ]
-     )))
+(defn stats-times [stats-map]
+  (sort-by #(Integer/parseInt %)
+           (-> stats-map
+               clojurify-structure
+               (dissoc ":all-time")
+               keys)))
 
 (defn window-hint [window]
   (if (= window ":all-time")
@@ -644,11 +308,153 @@
   [:input {:type "button"
            :value action
            (if enabled :enabled :disabled) ""
-           :onclick (str "confirmAction('" 
-                         (StringEscapeUtils/escapeJavaScript id) "', '" 
+           :onclick (str "confirmAction('"
+                         (StringEscapeUtils/escapeJavaScript id) "', '"
                          (StringEscapeUtils/escapeJavaScript name) "', '"
                          command "', " is-wait ", " default-wait ")")}])
 
+(defn cluster-configuration []
+  (with-nimbus nimbus
+    (.getNimbusConf ^Nimbus$Client nimbus)))
+
+(defn cluster-summary
+  ([]
+     (with-nimbus nimbus
+        (cluster-summary (.getClusterInfo ^Nimbus$Client nimbus))))
+  ([^ClusterSummary summ]
+     (let [sups (.get_supervisors summ)
+        used-slots (reduce + (map #(.get_num_used_workers ^SupervisorSummary %) sups))
+        total-slots (reduce + (map #(.get_num_workers ^SupervisorSummary %) sups))
+        free-slots (- total-slots used-slots)
+        total-tasks (->> (.get_topologies summ)
+                         (map #(.get_num_tasks ^TopologySummary %))
+                         (reduce +))
+        total-executors (->> (.get_topologies summ)
+                             (map #(.get_num_executors ^TopologySummary %))
+                             (reduce +))]
+       { "stormVersion" (read-storm-version)
+         "nimbusUptime" (pretty-uptime-sec (.get_nimbus_uptime_secs summ))
+         "supervisors" (count sups)
+         "topologies" ""
+         "slotsTotal" total-slots
+         "slotsUsed"  used-slots
+         "slotsFree" free-slots
+         "executorsTotal" total-executors
+         "tasksTotal" total-tasks })))
+
+(defn supervisor-summary
+  ([]
+     (with-nimbus nimbus
+       (supervisor-summary (.get_supervisors (.getClusterInfo ^Nimbus$Client nimbus)))
+       ))
+  ([summs]
+     {"supervisors"
+      (for [^SupervisorSummary s summs]
+            {"id" (.get_supervisor_id s)
+             "host" (.get_host s)
+             "uptime" (pretty-uptime-sec (.get_uptime_secs s))
+             "slotsTotal" (.get_num_workers s)
+             "slotsUsed" (.get_num_used_workers s)})}))
+
+(defn all-topologies-summary
+  ([]
+     (with-nimbus nimbus
+       (all-topologies-summary (.get_topologies (.getClusterInfo ^Nimbus$Client nimbus)))))
+  ([summs]
+     {"topologies"
+      (for [^TopologySummary t summs]
+        {"id" (.get_id t)
+         "name" (.get_name t)
+         "status" (.get_status t)
+         "uptime" (.get_uptime_secs t)
+         "tasksTotal" (.get_num_tasks t)
+         "workersTotal" (.get_num_workers t)
+         "executorsTotal" (.get_num_executors t)})
+      }))
+
+(defn topology-stats [id window stats]
+  (let [times (stats-times (:emitted stats))
+        display-map (into {} (for [t times] [t pretty-uptime-sec]))
+        display-map (assoc display-map ":all-time" (fn [_] "All time"))]
+    (for [k (concat times [":all-time"])
+           :let [disp ((display-map k) k)]]
+      { "windowPretty" disp
+        "window" k
+        "emitted" (get-in stats [:emitted k])
+        "transferred" (get-in stats [:transferred k])
+        "completeLatency" (float-str (get-in stats [:complete-latencies k]))
+        "acked" (get-in stats [:acked k])
+        "failed" (get-in stats [:failed k])
+        }
+      )))
+
+(defn spout-comp [top-id summ-map errors window include-sys?]
+  (for [[id summs] summ-map
+        :let [stats-seq (get-filled-stats summs)
+              stats (aggregate-spout-streams
+                     (aggregate-spout-stats
+                      stats-seq include-sys?))]]
+    {"spoutId" id
+     "executors" (count summs)
+     "tasks" (sum-tasks summs)
+     "emitted" (get-in stats [:emitted window])
+     "transferred" (get-in stats [:transferred window])
+     "complete_latency" (float-str (get-in stats [:complete-latencies window]))
+     "acked" (get-in stats [:acked window])
+     "failed" (get-in stats [:failed window])
+     "last_error" (most-recent-error (get errors id))
+     }))
+
+(defn bolt-comp [top-id summ-map errors window include-sys?]
+  (for [[id summs] summ-map
+        :let [stats-seq (get-filled-stats summs)
+              stats (aggregate-bolt-streams
+                     (aggregate-bolt-stats
+                      stats-seq include-sys?))
+              ]]
+    {"boltId" id
+     "executors" (count summs)
+     "tasks" (sum-tasks summs)
+     "emitted" (get-in stats [:emitted window])
+     "trasnferred" (get-in stats [:transferred window])
+     "capacity" (compute-bolt-capacity summs)
+     "execute_latency" (float-str (get-in stats [:execute-latencies window]))
+     "executed" (get-in stats [:executed window])
+     "process_latency" (float-str (get-in stats [:process-latencies window]))
+     "acked" (get-in stats [:acked window])
+     "failed" (get-in stats [:failed window])
+     "last_error" (most-recent-error (get errors id))
+     }
+    ))
+
+(defn topology-summary [^TopologyInfo summ]
+  (let [executors (.get_executors summ)
+        workers (set (for [^ExecutorSummary e executors] [(.get_host e) (.get_port e)]))]
+      {"id" (.get_id summ)
+       "name" (.get_name summ)
+       "status" (.get_status summ)
+       "uptime" (pretty-uptime-sec (.get_uptime_secs summ))
+       "tasksTotal" (sum-tasks executors)
+       "workersTotal" (count workers)
+       "executorsTotal" (count executors)}
+      ))
+
+(defn spout-summary [topology-id id stats window]
+  (let [times (stats-times (:emitted stats))
+        display-map (into {} (for [t times] [t pretty-uptime-sec]))
+        display-map (assoc display-map ":all-time" (fn [_] "All time"))]
+     (for [k (concat times [":all-time"])
+           :let [disp ((display-map k) k)]]
+       {"windowPretty" disp
+        "window" k
+        "emitted" (get-in stats [:emitted k])
+        "transferred" (get-in stats [:transferred k])
+        "completeLatency" (float-str (get-in stats [:complete-latencies k]))
+        "acked" (get-in stats [:acked k])
+        "failed" (get-in stats [:failed k])
+        }
+       )))
+
 (defn topology-page [id window include-sys?]
   (with-nimbus nimbus
     (let [window (if window window ":all-time")
@@ -665,317 +471,150 @@
           status (.get_status summ)
           msg-timeout (topology-conf TOPOLOGY-MESSAGE-TIMEOUT-SECS)
           ]
-      (concat
-       [[:h2 "Topology summary"]]
-       [(topology-summary-table summ)]
-       [[:h2 {:class "js-only"} "Topology actions"]]
-       [[:p {:class "js-only"} (concat
-         [(topology-action-button id name "Activate" "activate" false 0 (= "INACTIVE" status))]
-         [(topology-action-button id name "Deactivate" "deactivate" false 0 (= "ACTIVE" status))]
-         [(topology-action-button id name "Rebalance" "rebalance" true msg-timeout (or (= "ACTIVE" status) (= "INACTIVE" status)))]
-         [(topology-action-button id name "Kill" "kill" true msg-timeout (not= "KILLED" status))]
-       )]]
-       [[:h2 "Topology stats"]]
-       (topology-stats-table id window (total-aggregate-stats spout-summs bolt-summs include-sys?))
-       [[:h2 "Spouts (" window-hint ")"]]
-       (spout-comp-table id spout-comp-summs (.get_errors summ) window include-sys?)
-       [[:h2 "Bolts (" window-hint ")"]]
-       (bolt-comp-table id bolt-comp-summs (.get_errors summ) window include-sys?)
-       [[:h2 "Topology Configuration"]]
-       (configuration-table topology-conf)
-       ))))
-
-(defn component-task-summs [^TopologyInfo summ topology id]
-  (let [spout-summs (filter (partial spout-summary? topology) (.get_executors summ))
-        bolt-summs (filter (partial bolt-summary? topology) (.get_executors summ))
-        spout-comp-summs (group-by-comp spout-summs)
-        bolt-comp-summs (group-by-comp bolt-summs)
-        ret (if (contains? spout-comp-summs id)
-              (spout-comp-summs id)
-              (bolt-comp-summs id))]
-    (sort-by #(-> ^ExecutorSummary % .get_executor_info .get_task_start) ret)
-    ))
+      (merge
+       (topology-summary summ)
+       {"window" window
+        "windowHint" window-hint
+        "msgTimeout" msg-timeout
+        "topologyStats" (topology-stats id window (total-aggregate-stats spout-summs bolt-summs include-sys?))
+        "spouts" (spout-comp id spout-comp-summs (.get_errors summ) window include-sys?)
+        "bolts" (bolt-comp id bolt-comp-summs (.get_errors summ) window include-sys?)
+        "configuration" topology-conf})
+      )))
 
-(defn spout-summary-table [topology-id id stats window]
-  (let [times (stats-times (:emitted stats))
-        display-map (into {} (for [t times] [t pretty-uptime-sec]))
-        display-map (assoc display-map ":all-time" (fn [_] "All time"))]
-    (sorted-table
-     [{:text "Window" :attr {:class "tip right"
-                             :title (:window tips)}}
-      {:text "Emitted" :attr {:class "tip above"
-                              :title (:emitted tips)}}
-      {:text "Transferred" :attr {:class "tip above"
-                                  :title (:transferred tips)}}
-      {:text "Complete latency (ms)" :attr {:class "tip above"
-                                            :title (:complete-lat tips)}}
-      {:text "Acked" :attr {:class "tip above"
-                            :title (:spout-acked tips)}}
-      {:text "Failed" :attr {:class "tip left"
-                            :title (:spout-failed tips)}}]
-     (for [k (concat times [":all-time"])
-           :let [disp ((display-map k) k)]]
-       [(link-to (if (= k window) {:class "red"} {})
-                 (url-format "/topology/%s/component/%s?window=%s" topology-id id k)
-                 (escape-html disp))
-        (get-in stats [:emitted k])
-        (get-in stats [:transferred k])
-        (float-str (get-in stats [:complete-latencies k]))
-        (get-in stats [:acked k])
-        (get-in stats [:failed k])
-        ])
-     :time-cols [0])))
-
-(defn spout-output-summary-table [stream-summary window]
+(defn spout-output-stats [stream-summary window]
   (let [stream-summary (map-val swap-map-order (swap-map-order stream-summary))]
-    (sorted-table
-     [{:text "Stream" :attr {:class "tip right"
-                             :title (:stream tips)}}
-      {:text "Emitted" :attr {:class "tip above"
-                              :title (:emitted tips)}}
-      {:text "Transferred" :attr {:class "tip above"
-                                  :title (:transferred tips)}}
-      {:text "Complete latency (ms)" :attr {:class "tip above"
-                                            :title (:complete-lat tips)}}
-      {:text "Acked" :attr {:class "tip above"
-                            :title (:spout-acked tips)}}
-      {:text "Failed" :attr {:class "tip left"
-                            :title (:spout-failed tips)}}]
-     (for [[s stats] (stream-summary window)]
-       [s
-        (nil-to-zero (:emitted stats))
-        (nil-to-zero (:transferred stats))
-        (float-str (:complete-latencies stats))
-        (nil-to-zero (:acked stats))
-        (nil-to-zero (:failed stats))])
-     )))
+    (for [[s stats] (stream-summary window)]
+      {"stream" s
+       "emitted" (nil-to-zero (:emitted stats))
+       "transferred" (nil-to-zero (:transferred stats))
+       "complete_latency" (float-str (:complete-latencies stats))
+       "acked" (nil-to-zero (:acked stats))
+       "failed" (nil-to-zero (:failed stats))
+       }
+      )))
 
-(defn spout-executor-table [topology-id executors window include-sys?]
-  (sorted-table
-   [{:text "Id" :attr {:class "tip right"
-                       :title (:exec-id tips)}}
-    {:text "Uptime" :attr {:class "tip right"
-                           :title (:exec-uptime tips)}}
-    {:text "Host" :attr {:class "tip above"
-                         :title (:sup-host tips)}}
-    {:text "Port" :attr {:class "tip above"
-                         :title (:port tips)}}
-    {:text "Emitted" :attr {:class "tip above"
-                            :title (:emitted tips)}}
-    {:text "Transferred" :attr {:class "tip above"
-                                :title (:transferred tips)}}
-    {:text "Complete latency (ms)" :attr {:class "tip above"
-                                          :title (:complete-lat tips)}}
-    {:text "Acked" :attr {:class "tip above"
-                          :title (:spout-acked tips)}}
-    {:text "Failed" :attr {:class "tip left"
-                          :title (:spout-failed tips)}}]
-   (for [^ExecutorSummary e executors
-         :let [stats (.get_stats e)
-               stats (if stats
-                       (-> stats
-                           (aggregate-spout-stats include-sys?)
-                           aggregate-spout-streams
-                           swap-map-order
-                           (get window)))]]
-     [(pretty-executor-info (.get_executor_info e))
-      (pretty-uptime-sec (.get_uptime_secs e))
-      (.get_host e)
-      (worker-log-link (.get_host e) (.get_port e))
-      (nil-to-zero (:emitted stats))
-      (nil-to-zero (:transferred stats))
-      (float-str (:complete-latencies stats))
-      (nil-to-zero (:acked stats))
-      (nil-to-zero (:failed stats))
-      ]
-     )
-   :time-cols [1]
-   ))
-
-(defn spout-page [window ^TopologyInfo topology-info component executors include-sys?]
+(defn spout-executor-stats [topology-id executors window include-sys?]
+  (for [^ExecutorSummary e executors
+        :let [stats (.get_stats e)
+              stats (if stats
+                      (-> stats
+                          (aggregate-spout-stats include-sys?)
+                          aggregate-spout-streams
+                          swap-map-order
+                          (get window)))]]
+    {"id" (pretty-executor-info (.get_executor_info e))
+     "uptime" (pretty-uptime-sec (.get_uptime_secs e))
+     "host" (.get_host e)
+     "port" (.get_port e)
+     "emitted" (nil-to-zero (:emitted stats))
+     "transferred" (nil-to-zero (:transferred stats))
+     "completeLatency" (float-str (:complete-latencies stats))
+     "acked" (nil-to-zero (:acked stats))
+     "failed" (nil-to-zero (:failed stats))
+     "workerLogLink" (worker-log-link (.get_host e) (.get_port e))
+     }
+  ))
+
+(defn component-errors [errors-list]
+  (let [errors (->> errors-list
+                    (sort-by #(.get_error_time_secs ^ErrorInfo %))
+                    reverse)]
+    {"errors"
+     (for [^ErrorInfo e errors]
+       [{"time" (date-str (.get_error_time_secs e))
+         "error" (.get_error e)
+         }])}
+     ))
+
+(defn spout-stats [window ^TopologyInfo topology-info component executors include-sys?]
   (let [window-hint (str " (" (window-hint window) ")")
         stats (get-filled-stats executors)
         stream-summary (-> stats (aggregate-spout-stats include-sys?))
         summary (-> stream-summary aggregate-spout-streams)]
-    (concat
-     [[:h2 "Spout stats"]]
-     (spout-summary-table (.get_id topology-info) component summary window)
-     [[:h2 "Output stats" window-hint]]
-     (spout-output-summary-table stream-summary window)
-     [[:h2 "Executors" window-hint]]
-     (spout-executor-table (.get_id topology-info) executors window include-sys?)
-     ;; task id, task uptime, stream aggregated stats, last error
-     )))
+    {"spoutSummary" (spout-summary (.get_id topology-info) component summary window)
+     "outputStats" (spout-output-stats stream-summary window)
+     "executorStats" (spout-executor-stats (.get_id topology-info) executors window include-sys?)}
+     ))
 
-(defn bolt-output-summary-table [stream-summary window]
+(defn bolt-summary [topology-id id stats window]
+  (let [times (stats-times (:emitted stats))
+        display-map (into {} (for [t times] [t pretty-uptime-sec]))
+        display-map (assoc display-map ":all-time" (fn [_] "All time"))]
+    (for [k (concat times [":all-time"])
+          :let [disp ((display-map k) k)]]
+      {"window" k
+       "windowPretty" disp
+       "emitted" (get-in stats [:emitted k])
+       "transferred" (get-in stats [:transferred k])
+       "executeLatency" (float-str (get-in stats [:execute-latencies k]))
+       "executed" (get-in stats [:executed k])
+       "processLatency" (float-str (get-in stats [:process-latencies k]))
+       "acked" (get-in stats [:acked k])
+       "failed" (get-in stats [:failed k])})))
+
+(defn bolt-output-stats [stream-summary window]
   (let [stream-summary (-> stream-summary
                            swap-map-order
                            (get window)
                            (select-keys [:emitted :transferred])
                            swap-map-order)]
-    (sorted-table
-     [{:text "Stream" :attr {:class "tip right"
-                             :title (:stream tips)}}
-      {:text "Emitted" :attr {:class "tip above"
-                              :title (:emitted tips)}}
-      {:text "Transferred" :attr {:class "tip above"
-                                  :title (:transferred tips)}}]
-     (for [[s stats] stream-summary]
-       [s
-        (nil-to-zero (:emitted stats))
-        (nil-to-zero (:transferred stats))
-        ])
-     )))
+    (for [[s stats] stream-summary]
+      {"stream" s
+        "emitted" (nil-to-zero (:emitted stats))
+        "transferred" (nil-to-zero (:transferred stats))}
+    )))
 
-(defn bolt-input-summary-table [stream-summary window]
+(defn bolt-input-stats [stream-summary window]
   (let [stream-summary (-> stream-summary
                            swap-map-order
                            (get window)
                            (select-keys [:acked :failed :process-latencies :executed :execute-latencies])
                            swap-map-order)]
-    (sorted-table
-     [{:text "Component" :attr {:class "tip right"
-                         :title (:comp-id tips)}}
-      {:text "Stream" :attr {:class "tip right"
-                             :title (:stream tips)}}
-      {:text "Execute latency (ms)" :attr {:class "tip above"
-                                           :title (:exec-lat tips)}}
-      {:text "Executed" :attr {:class "tip above"
-                               :title (:num-executed tips)}}
-      {:text "Process latency (ms)":attr {:class "tip above"
-                                          :title (:proc-lat tips)}}
-      {:text "Acked" :attr {:class "tip above"
-                            :title (:bolt-acked tips)}}
-      {:text "Failed" :attr {:class "tip left"
-                             :title (:bolt-failed tips)}}]
-     (for [[^GlobalStreamId s stats] stream-summary]
-       [(escape-html (.get_componentId s))
-        (.get_streamId s)
-        (float-str (:execute-latencies stats))
-        (nil-to-zero (:executed stats))
-        (float-str (:process-latencies stats))
-        (nil-to-zero (:acked stats))
-        (nil-to-zero (:failed stats))
-        ])
-     )))
-
-(defn bolt-executor-table [topology-id executors window include-sys?]
-  (sorted-table
-   [{:text "Id" :attr {:class "tip right"
-                       :title (:exec-id tips)}}
-    {:text "Uptime" :attr {:class "tip right"
-                           :title (:exec-uptime tips)}}
-    {:text "Host" :attr {:class "tip above"
-                         :title (:sup-host tips)}}
-    {:text "Port" :attr {:class "tip above"
-                         :title (:port tips)}}
-    {:text "Emitted" :attr {:class "tip above"
-                            :title (:emitted tips)}}
-    {:text "Transferred" :attr {:class "tip above"
-                                :title (:transferred tips)}}
-    {:text "Capacity (last 10m)" :attr {:class "tip above"
-                                        :title (:capacity tips)}}
-    {:text "Execute latency (ms)" :attr {:class "tip above"
-                                         :title (:exec-lat tips)}}
-    {:text "Executed" :attr {:class "tip above"
-                             :title (:num-executed tips)}}
-    {:text "Process latency (ms)":attr {:class "tip above"
-                                        :title (:proc-lat tips)}}
-    {:text "Acked" :attr {:class "tip above"
-                          :title (:bolt-acked tips)}}
-    {:text "Failed" :attr {:class "tip left"
-                           :title (:bolt-failed tips)}}]
-   (for [^ExecutorSummary e executors
-         :let [stats (.get_stats e)
-               stats (if stats
-                       (-> stats
-                           (aggregate-bolt-stats include-sys?)
-                           (aggregate-bolt-streams)
-                           swap-map-order
-                           (get window)))]]
-     [(pretty-executor-info (.get_executor_info e))
-      (pretty-uptime-sec (.get_uptime_secs e))
-      (.get_host e)
-      (worker-log-link (.get_host e) (.get_port e))
-      (nil-to-zero (:emitted stats))
-      (nil-to-zero (:transferred stats))
-      (render-capacity (compute-executor-capacity e))
-      (float-str (:execute-latencies stats))
-      (nil-to-zero (:executed stats))
-      (float-str (:process-latencies stats))
-      (nil-to-zero (:acked stats))
-      (nil-to-zero (:failed stats))
-      ]
-     )
-   :time-cols [1]
-   ))
-
-(defn bolt-summary-table [topology-id id stats window]
-  (let [times (stats-times (:emitted stats))
-        display-map (into {} (for [t times] [t pretty-uptime-sec]))
-        display-map (assoc display-map ":all-time" (fn [_] "All time"))]
-    (sorted-table
-     [{:text "Window" :attr {:class "tip right"
-                             :title (:window tips)}}
-      {:text "Emitted" :attr {:class "tip above"
-                              :title (:emitted tips)}}
-      {:text "Transferred" :attr {:class "tip above"
-                                  :title (:transferred tips)}}
-      {:text "Execute latency (ms)" :attr {:class "tip above"
-                                           :title (:exec-lat tips)}}
-      {:text "Executed" :attr {:class "tip above"
-                               :title (:num-executed tips)}}
-      {:text "Process latency (ms)":attr {:class "tip above"
-                                          :title (:proc-lat tips)}}
-      {:text "Acked" :attr {:class "tip above"
-                            :title (:bolt-acked tips)}}
-      {:text "Failed" :attr {:class "tip left"
-                             :title (:bolt-failed tips)}}]
-     (for [k (concat times [":all-time"])
-           :let [disp ((display-map k) k)]]
-       [(link-to (if (= k window) {:class "red"} {})
-                 (url-format "/topology/%s/component/%s?window=%s" topology-id id k)
-                 (escape-html disp))
-        (get-in stats [:emitted k])
-        (get-in stats [:transferred k])
-        (float-str (get-in stats [:execute-latencies k]))
-        (get-in stats [:executed k])
-        (float-str (get-in stats [:process-latencies k]))
-        (get-in stats [:acked k])
-        (get-in stats [:failed k])
-        ])
-     :time-cols [0])))
-
-(defn bolt-page [window ^TopologyInfo topology-info component executors include-sys?]
+    (for [[^GlobalStreamId s stats] stream-summary]
+      {"component" (.get_componentId s)
+        "stream" (.get_streamId s)
+        "executeLatency" (float-str (:execute-latencies stats))
+        "processLatency" (float-str (:execute-latencies stats))
+        "executed" (nil-to-zero (:executed stats))
+        "acked" (nil-to-zero (:acked stats))
+        "failed" (nil-to-zero (:failed stats))
+        })))
+
+(defn bolt-executor-stats [topology-id executors window include-sys?]
+  (for [^ExecutorSummary e executors
+        :let [stats (.get_stats e)
+              stats (if stats
+                      (-> stats
+                          (aggregate-bolt-stats include-sys?)
+                          (aggregate-bolt-streams)
+                          swap-map-order
+                          (get window)))]]
+    {"id" (pretty-executor-info (.get_executor_info e))
+      "uptime" (pretty-uptime-sec (.get_uptime_secs e))
+      "host" (.get_host e)
+      "port" (.get_port e)
+      "emitted" (nil-to-zero (:emitted stats))
+      "transferred" (nil-to-zero (:transferred stats))
+      "capacity" (compute-executor-capacity e)
+      "executeLatency" (float-str (:execute-latencies stats))
+      "executed" (nil-to-zero (:executed stats))
+      "processLatency" (float-str (:process-latencies stats))
+      "acked" (nil-to-zero (:acked stats))
+      "failed" (nil-to-zero (:failed stats))
+      "workerLogLink" (worker-log-link (.get_host e) (.get_port e))
+      }))
+
+(defn bolt-stats [window ^TopologyInfo topology-info component executors include-sys?]
   (let [window-hint (str " (" (window-hint window) ")")
         stats (get-filled-stats executors)
         stream-summary (-> stats (aggregate-bolt-stats include-sys?))
         summary (-> stream-summary aggregate-bolt-streams)]
-    (concat
-     [[:h2 "Bolt stats"]]
-     (bolt-summary-table (.get_id topology-info) component summary window)
-
-     [[:h2 "Input stats" window-hint]]
-     (bolt-input-summary-table stream-summary window)
-
-     [[:h2 "Output stats" window-hint]]
-     (bolt-output-summary-table stream-summary window)
-
-     [[:h2 "Executors"]]
-     (bolt-executor-table (.get_id topology-info) executors window include-sys?)
-     )))
-
-(defn errors-table [errors-list]
-  (let [errors (->> errors-list
-                    (sort-by #(.get_error_time_secs ^ErrorInfo %))
-                    reverse)]
-    (sorted-table
-     ["Time" "Error"]
-     (for [^ErrorInfo e errors]
-       [(date-str (.get_error_time_secs e))
-        [:pre (.get_error e)]])
-     :sort-list "[[0,1]]"
-     )))
+    {"boltStats" (bolt-summary (.get_id topology-info) component summary window)
+     "inputStats" (bolt-input-stats stream-summary window)
+     "outputStats" (bolt-output-stats stream-summary window)
+     "executorStats" (bolt-executor-stats (.get_id topology-info) executors window include-sys?)}
+    ))
 
 (defn component-page [topology-id component window include-sys?]
   (with-nimbus nimbus
@@ -984,106 +623,101 @@
           topology (.getTopology ^Nimbus$Client nimbus topology-id)
           type (component-type topology component)
           summs (component-task-summs summ topology component)
-          spec (cond (= type :spout) (spout-page window summ component summs include-sys?)
-                     (= type :bolt) (bolt-page window summ component summs include-sys?))]
-      (concat
-       [[:h2 "Component summary"]
-        (table [{:text "Id" :attr {:class "tip right"
-                                   :title (:comp-id tips)}}
-                {:text "Topology" :attr {:class "tip above"
-                                   :title (str (:name tips) " " (:name-link tips))}}
-                {:text "Executors" :attr {:class "tip above"
-                                   :title (:num-execs tips)}}
-                {:text "Tasks" :attr {:class "tip above"
-                               :title (:num-tasks tips)}}]
-               [[(escape-html component)
-                 (topology-link (.get_id summ) (.get_name summ))
-                 (count summs)
-                 (sum-tasks summs)
-                 ]])]
-       spec
-       [[:h2 "Errors"]
-        (errors-table (get (.get_errors summ) component))]
-       ))))
-
-(defn get-include-sys? [cookies]
-  (let [sys? (get cookies "sys")
-        sys? (if (or (nil? sys?) (= "false" (:value sys?))) false true)]
-    sys?))
+          spec (cond (= type :spout) (spout-stats window summ component summs include-sys?)
+                     (= type :bolt) (bolt-stats window summ component summs include-sys?))
+          errors (component-errors (get (.get_errors summ) component))]
+      (merge
+       {"id" component
+         "name" (.get_name summ)
+         "executors" (count summs)
+         "tasks" (sum-tasks summs)
+         "topologyId" topology-id
+         "window" window
+         "componentType" (name type)
+         "windowHint" (window-hint window)
+         } spec errors))))
+
+(defn check-include-sys? [sys?]
+  (if (or (nil? sys?) (= "false" sys?)) false true))
+
+(defn json-response [data & [status]]
+  {:status (or status 200)
+   :headers {"Content-Type" "application/json"}
+   :body (to-json data)
+   })
 
 (defroutes main-routes
-  (GET "/" [:as {cookies :cookies}]
-       (-> (main-page)
-           ui-template))
-  (GET "/topology/:id" [:as {cookies :cookies} id & m]
-       (let [include-sys? (get-include-sys? cookies)
-            id (url-decode id)]
-         (try
-           (-> (topology-page (url-decode id) (:window m) include-sys?)
-             (concat [(mk-system-toggle-button include-sys?)])
-             ui-template)
-           (catch Exception e (resp/redirect "/")))))
-  (GET "/topology/:id/component/:component" [:as {cookies :cookies} id component & m]
-       (let [include-sys? (get-include-sys? cookies)
-            id (url-decode id)
-            component (url-decode component)]
-         (-> (component-page id component (:window m) include-sys?)
-             (concat [(mk-system-toggle-button include-sys?)])
-             ui-template)))
-  (POST "/topology/:id/activate" [id]
+  (GET "/api/cluster/configuration" []
+       (cluster-configuration))
+  (GET "/api/cluster/summary" []
+       (json-response (cluster-summary)))
+  (GET "/api/supervisor/summary" []
+       (json-response (supervisor-summary)))
+  (GET "/api/topology/summary" []
+       (json-response (all-topologies-summary)))
+  (GET  "/api/topology/:id" [id & m]
+        (let [id (java.net.URLDecoder/decode id)]
+          (json-response (topology-page id (:window m) (check-include-sys? (:sys m))))))
+  (GET "/api/topology/:id/component/:component" [id component & m]
+       (let [id (java.net.URLDecoder/decode id)
+             component (java.net.URLDecoder/decode component)]
+         (json-response (component-page id component (:window m) (check-include-sys? (:sys m))))))
+  (POST "/api/topology/:id/activate" [id]
     (with-nimbus nimbus
-      (let [id (url-decode id)
+      (let [id (java.net.URLDecoder/decode id)
             tplg (.getTopologyInfo ^Nimbus$Client nimbus id)
             name (.get_name tplg)]
         (.activate nimbus name)
         (log-message "Activating topology '" name "'")))
-    (resp/redirect (str "/topology/" id)))
-  (POST "/topology/:id/deactivate" [id]
+    (resp/redirect (str "/api/topology/" id)))
+
+  (POST "/api/topology/:id/deactivate" [id]
     (with-nimbus nimbus
-      (let [id (url-decode id)
+      (let [id (java.net.URLDecoder/decode id)
             tplg (.getTopologyInfo ^Nimbus$Client nimbus id)
             name (.get_name tplg)]
         (.deactivate nimbus name)
         (log-message "Deactivating topology '" name "'")))
-    (resp/redirect (str "/topology/" id)))
-  (POST "/topology/:id/rebalance/:wait-time" [id wait-time]
+    (resp/redirect (str "/api/topology/" id)))
+  (POST "/api/topology/:id/rebalance/:wait-time" [id wait-time]
     (with-nimbus nimbus
-      (let [id (url-decode id)
+      (let [id (java.net.URLDecoder/decode id)
             tplg (.getTopologyInfo ^Nimbus$Client nimbus id)
             name (.get_name tplg)
             options (RebalanceOptions.)]
         (.set_wait_secs options (Integer/parseInt wait-time))
         (.rebalance nimbus name options)
         (log-message "Rebalancing topology '" name "' with wait time: " wait-time " secs")))
-    (resp/redirect (str "/topology/" id)))
-  (POST "/topology/:id/kill/:wait-time" [id wait-time]
+    (resp/redirect (str "/api/topology/" id)))
+  (POST "/api/topology/:id/kill/:wait-time" [id wait-time]
     (with-nimbus nimbus
-      (let [id (url-decode id)
+      (let [id (java.net.URLDecoder/decode id)
             tplg (.getTopologyInfo ^Nimbus$Client nimbus id)
             name (.get_name tplg)
             options (KillOptions.)]
         (.set_wait_secs options (Integer/parseInt wait-time))
         (.killTopologyWithOpts nimbus name options)
         (log-message "Killing topology '" name "' with wait time: " wait-time " secs")))
-    (resp/redirect (str "/topology/" id)))
+    (resp/redirect (str "/api/topology/" id)))
+
+  (GET "/" [:as {cookies :cookies}]
+       (resp/redirect "/index.html"))
   (route/resources "/")
   (route/not-found "Page not found"))
 
-(defn exception->html [ex]
-  (concat
-    [[:h2 "Internal Server Error"]]
-    [[:pre (let [sw (java.io.StringWriter.)]
+(defn exception->json [ex]
+  { "error" "Internal Server Error"
+     "errorMessage" (let [sw (java.io.StringWriter.)]
       (.printStackTrace ex (java.io.PrintWriter. sw))
-      (.toString sw))]]))
+      (.toString sw))
+    })
 
 (defn catch-errors [handler]
   (fn [request]
     (try
       (handler request)
       (catch Exception ex
-        (-> (resp/response (ui-template (exception->html ex)))
-          (resp/status 500)
-          (resp/content-type "text/html"))
+        (json-response (exception->json ex) 500)
         ))))
 
 (def app

http://git-wip-us.apache.org/repos/asf/incubator-storm/blob/a06fc90e/storm-core/src/ui/public/component.html
----------------------------------------------------------------------
diff --git a/storm-core/src/ui/public/component.html b/storm-core/src/ui/public/component.html
new file mode 100644
index 0000000..1286e45
--- /dev/null
+++ b/storm-core/src/ui/public/component.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head>
+<title>Storm UI</title>
+<link href="/css/bootstrap-1.4.0.css" rel="stylesheet" type="text/css">
+<link href="/css/style.css" rel="stylesheet" type="text/css">
+<script src="/js/jquery-1.6.2.min.js" type="text/javascript"></script>
+<script src="/js/jquery.tablesorter.min.js" type="text/javascript"></script>
+<script src="/js/jquery.cookies.2.2.0.min.js" type="text/javascript"></script>
+<script src="/js/jquery.mustache.js" type="text/javascript"></script>
+<script src="/js/purl.js" type="text/javascript"></script>
+<script src="/js/bootstrap-twipsy.js" type="text/javascript"></script>
+<script src="/js/script.js" type="text/javascript"></script>
+</head>
+<body>
+<h1><a href="/">Storm UI</a></h1>
+<div id="component-summary">
+</div>
+<div id="component-stats-detail">
+</div>
+<div id="component-input-stats">
+</div>
+<div id="component-output-stats">
+</div>
+<div id="component-executor-stats">
+</div>
+<div id="component-errors">
+</div>
+<div id="error">
+</div>
+<p id="toggle-switch" style="display: block;" class="js-only"></p>
+<script>
+$(document).ready(function() {
+    var componentId = $.url().param("id");
+    var topologyId = $.url().param("topology_id");
+    var window = $.url().param("window");
+    var sys = $.cookies.get("sys") || "false";
+    var url = "/api/topology/"+topologyId+"/component/"+componentId+"?sys="+sys;
+    if(window) url += "&window="+window;
+    renderToggleSys($("#toggle-switch"));
+    $.ajaxSetup({
+        "error":function(jqXHR,textStatus,response) {
+            var errorJson = jQuery.parseJSON(jqXHR.responseText);
+            $.get("/templates/error-template.html", function(template) {
+                $("#error").append(Mustache.render($(template).filter("#error-template").html(),errorJson));
+            });
+        }
+    });
+
+    $.getJSON(url,function(response,status,jqXHR) {
+        var componentSummary = $("#component-summary");
+        var componentStatsDetail = $("#component-stats-detail")
+        var inputStats = $("#component-input-stats");
+        var outputStats = $("#component-output-stats");
+        var executorStats = $("#component-executor-stats");
+        var errors = $("#component-errors");
+        $.get("/templates/component-page-template.html", function(template) {
+            componentSummary.append(Mustache.render($(template).filter("#component-summary-template").html(),response));
+            if(response["componentType"] == "spout") {
+                componentStatsDetail.append(Mustache.render($(template).filter("#spout-stats-detail-template").html(),response));
+                $("#spout-stats-table").tablesorter({ sortList: [[0,0]], headers: {0: { sorter: "stormtimestr"}}});
+                outputStats.append(Mustache.render($(template).filter("#output-stats-template").html(),response));
+                $("#output-stats-table").tablesorter({ sortList: [[0,0]], headers: {0: { sorter: "stormtimestr"}}});
+                executorStats.append(Mustache.render($(template).filter("#executor-stats-template").html(),response));
+                $("#executor-stats-table").tablesorter({ sortList: [[0,0]], headers: {1: { sorter: "stormtimestr"}}});
+            } else {
+                componentStatsDetail.append(Mustache.render($(template).filter("#bolt-stats-template").html(),response));
+                $("#bolt-stats-table").tablesorter({ sortList: [[0,0]], headers: {0: { sorter: "stormtimestr"}}});
+                inputStats.append(Mustache.render($(template).filter("#bolt-input-stats-template").html(),response));
+                if (response["inputStats"].length > 0) {
+                    $("#bolt-input-stats-table").tablesorter({ sortList: [[0,0]], headers: {}});
+                }
+                outputStats.append(Mustache.render($(template).filter("#bolt-output-stats-template").html(),response));
+                $("#bolt-output-stats-table").tablesorter({ sortList: [[0,0]], headers: {}});
+                executorStats.append(Mustache.render($(template).filter("#bolt-executor-template").html(),response));
+                if(response["outputStats"].length > 0) {
+                    $("#bolt-executor-table").tablesorter({ sortList: [[0,0]], headers: {}});
+                }
+            }
+            errors.append(Mustache.render($(template).filter("#errors-template").html(),response));
+            if(response["errors"].length > 0) {
+                $("#errors-table").tablesorter({ sortList: [[0,0]], headers: {1: { sorter: "stormtimestr"}}});
+            }
+        });
+    });
+});
+</script>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-storm/blob/a06fc90e/storm-core/src/ui/public/index.html
----------------------------------------------------------------------
diff --git a/storm-core/src/ui/public/index.html b/storm-core/src/ui/public/index.html
new file mode 100644
index 0000000..dc20645
--- /dev/null
+++ b/storm-core/src/ui/public/index.html
@@ -0,0 +1,73 @@
+<html><head>
+<title>Storm UI</title>
+<link href="/css/bootstrap-1.4.0.css" rel="stylesheet" type="text/css">
+<link href="/css/style.css" rel="stylesheet" type="text/css">
+<script src="/js/jquery-1.6.2.min.js" type="text/javascript"></script>
+<script src="/js/jquery.tablesorter.min.js" type="text/javascript"></script>
+<script src="/js/jquery.cookies.2.2.0.min.js" type="text/javascript"></script>
+<script src="/js/jquery.mustache.js" type="text/javascript"></script>
+<script src="/js/bootstrap-twipsy.js" type="text/javascript"></script>
+<script src="/js/script.js" type="text/javascript"></script>
+</head>
+<body>
+<h1><a href="/">Storm UI</a></h1>
+<h2>Cluster Summary</h2>
+<div id="cluster-summary">
+</div>
+<h2>Topology summary</h2>
+<div id="topology-summary">
+</div>
+<h2>Supervisor summary</h2>
+<div id="supervisor-summary">
+</div>
+<h2>Nimbus Configuration</h2>
+<div id="nimbus-configuration"></div>
+<div id="error"></div>
+</body>
+<script>
+$(document).ready(function() {
+    $.ajaxSetup({
+        "error":function(jqXHR,textStatus,response) {
+            var errorJson = jQuery.parseJSON(jqXHR.responseText);
+            $.get("/templates/error-template.html", function(template) {
+                $("#error").append(Mustache.render($(template).filter("#error-template").html(),errorJson));
+            });
+        }
+    });
+    var template = $.get("/templates/index-page-template.html");
+    var clusterSummary = $("#cluster-summary");
+    var topologySummary = $("#topology-summary");
+    var supervisorSummary = $("#supervisor-summary");
+    var config = $("#nimbus-configuration");
+
+    $.getJSON("/api/cluster/summary",function(response,status,jqXHR) {
+        $.get("/templates/index-page-template.html", function(template) {
+            clusterSummary.append(Mustache.render($(template).filter("#cluster-summary-template").html(),response));
+        });
+    });
+    $.getJSON("/api/topology/summary",function(response,status,jqXHR) {
+      $.get("/templates/index-page-template.html", function(template) {
+          topologySummary.append(Mustache.render($(template).filter("#topology-summary-template").html(),response));
+          if(response["topologies"].length > 0) {
+              $("#topology-summary-table").tablesorter({ sortList: [[0,0]], headers: {3: { sorter: "stormtimestr"}}});
+              }
+      });
+    });
+    $.getJSON("/api/supervisor/summary",function(response,status,jqXHR) {
+      $.get("/templates/index-page-template.html", function(template) {
+          supervisorSummary.append(Mustache.render($(template).filter("#supervisor-summary-template").html(),response));
+          if(response["supervisors"].length > 0) {
+              $("#supervisor-summary-table").tablesorter({ sortList: [[0,0]], headers: {3: { sorter: "stormtimestr"}}});
+          }
+      });
+    });
+    $.getJSON("/api/cluster/configuration",function(response,status,jqXHR) {
+      var formattedResponse = formatConfigData(response);
+      $.get("/templates/index-page-template.html", function(template) {
+          config.append(Mustache.render($(template).filter("#configuration-template").html(),formattedResponse));
+          $("#nimbus-configuration-table").tablesorter({ sortList: [[0,0]], headers: {}});
+      });
+    });
+  });
+</script>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-storm/blob/a06fc90e/storm-core/src/ui/public/js/jquery.mustache.js
----------------------------------------------------------------------
diff --git a/storm-core/src/ui/public/js/jquery.mustache.js b/storm-core/src/ui/public/js/jquery.mustache.js
new file mode 100644
index 0000000..14925bf
--- /dev/null
+++ b/storm-core/src/ui/public/js/jquery.mustache.js
@@ -0,0 +1,592 @@
+/*
+Shameless port of a shameless port
+@defunkt => @janl => @aq
+ 
+See http://github.com/defunkt/mustache for more info.
+*/
+ 
+;(function($) {
+
+/*!
+ * mustache.js - Logic-less {{mustache}} templates with JavaScript
+ * http://github.com/janl/mustache.js
+ */
+
+/*global define: false*/
+
+(function (root, factory) {
+  if (typeof exports === "object" && exports) {
+    factory(exports); // CommonJS
+  } else {
+    var mustache = {};
+    factory(mustache);
+    if (typeof define === "function" && define.amd) {
+      define(mustache); // AMD
+    } else {
+      root.Mustache = mustache; // <script>
+    }
+  }
+}(this, function (mustache) {
+
+  var whiteRe = /\s*/;
+  var spaceRe = /\s+/;
+  var nonSpaceRe = /\S/;
+  var eqRe = /\s*=/;
+  var curlyRe = /\s*\}/;
+  var tagRe = /#|\^|\/|>|\{|&|=|!/;
+
+  // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
+  // See https://github.com/janl/mustache.js/issues/189
+  var RegExp_test = RegExp.prototype.test;
+  function testRegExp(re, string) {
+    return RegExp_test.call(re, string);
+  }
+
+  function isWhitespace(string) {
+    return !testRegExp(nonSpaceRe, string);
+  }
+
+  var Object_toString = Object.prototype.toString;
+  var isArray = Array.isArray || function (object) {
+    return Object_toString.call(object) === '[object Array]';
+  };
+
+  function isFunction(object) {
+    return typeof object === 'function';
+  }
+
+  function escapeRegExp(string) {
+    return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
+  }
+
+  var entityMap = {
+    "&": "&amp;",
+    "<": "&lt;",
+    ">": "&gt;",
+    '"': '&quot;',
+    "'": '&#39;',
+    "/": '&#x2F;'
+  };
+
+  function escapeHtml(string) {
+    return String(string).replace(/[&<>"'\/]/g, function (s) {
+      return entityMap[s];
+    });
+  }
+
+  function escapeTags(tags) {
+    if (!isArray(tags) || tags.length !== 2) {
+      throw new Error('Invalid tags: ' + tags);
+    }
+
+    return [
+      new RegExp(escapeRegExp(tags[0]) + "\\s*"),
+      new RegExp("\\s*" + escapeRegExp(tags[1]))
+    ];
+  }
+
+  /**
+   * Breaks up the given `template` string into a tree of tokens. If the `tags`
+   * argument is given here it must be an array with two string values: the
+   * opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of
+   * course, the default is to use mustaches (i.e. mustache.tags).
+   *
+   * A token is an array with at least 4 elements. The first element is the
+   * mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag
+   * did not contain a symbol (i.e. {{myValue}}) this element is "name". For
+   * all template text that appears outside a symbol this element is "text".
+   *
+   * The second element of a token is its "value". For mustache tags this is
+   * whatever else was inside the tag besides the opening symbol. For text tokens
+   * this is the text itself.
+   *
+   * The third and fourth elements of the token are the start and end indices
+   * in the original template of the token, respectively.
+   *
+   * Tokens that are the root node of a subtree contain two more elements: an
+   * array of tokens in the subtree and the index in the original template at which
+   * the closing tag for that section begins.
+   */
+  function parseTemplate(template, tags) {
+    tags = tags || mustache.tags;
+    template = template || '';
+
+    if (typeof tags === 'string') {
+      tags = tags.split(spaceRe);
+    }
+
+    var tagRes = escapeTags(tags);
+    var scanner = new Scanner(template);
+
+    var sections = [];     // Stack to hold section tokens
+    var tokens = [];       // Buffer to hold the tokens
+    var spaces = [];       // Indices of whitespace tokens on the current line
+    var hasTag = false;    // Is there a {{tag}} on the current line?
+    var nonSpace = false;  // Is there a non-space char on the current line?
+
+    // Strips all whitespace tokens array for the current line
+    // if there was a {{#tag}} on it and otherwise only space.
+    function stripSpace() {
+      if (hasTag && !nonSpace) {
+        while (spaces.length) {
+          delete tokens[spaces.pop()];
+        }
+      } else {
+        spaces = [];
+      }
+
+      hasTag = false;
+      nonSpace = false;
+    }
+
+    var start, type, value, chr, token, openSection;
+    while (!scanner.eos()) {
+      start = scanner.pos;
+
+      // Match any text between tags.
+      value = scanner.scanUntil(tagRes[0]);
+      if (value) {
+        for (var i = 0, len = value.length; i < len; ++i) {
+          chr = value.charAt(i);
+
+          if (isWhitespace(chr)) {
+            spaces.push(tokens.length);
+          } else {
+            nonSpace = true;
+          }
+
+          tokens.push(['text', chr, start, start + 1]);
+          start += 1;
+
+          // Check for whitespace on the current line.
+          if (chr === '\n') {
+            stripSpace();
+          }
+        }
+      }
+
+      // Match the opening tag.
+      if (!scanner.scan(tagRes[0])) break;
+      hasTag = true;
+
+      // Get the tag type.
+      type = scanner.scan(tagRe) || 'name';
+      scanner.scan(whiteRe);
+
+      // Get the tag value.
+      if (type === '=') {
+        value = scanner.scanUntil(eqRe);
+        scanner.scan(eqRe);
+        scanner.scanUntil(tagRes[1]);
+      } else if (type === '{') {
+        value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1])));
+        scanner.scan(curlyRe);
+        scanner.scanUntil(tagRes[1]);
+        type = '&';
+      } else {
+        value = scanner.scanUntil(tagRes[1]);
+      }
+
+      // Match the closing tag.
+      if (!scanner.scan(tagRes[1])) {
+        throw new Error('Unclosed tag at ' + scanner.pos);
+      }
+
+      token = [ type, value, start, scanner.pos ];
+      tokens.push(token);
+
+      if (type === '#' || type === '^') {
+        sections.push(token);
+      } else if (type === '/') {
+        // Check section nesting.
+        openSection = sections.pop();
+
+        if (!openSection) {
+          throw new Error('Unopened section "' + value + '" at ' + start);
+        }
+        if (openSection[1] !== value) {
+          throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
+        }
+      } else if (type === 'name' || type === '{' || type === '&') {
+        nonSpace = true;
+      } else if (type === '=') {
+        // Set the tags for the next time around.
+        tagRes = escapeTags(tags = value.split(spaceRe));
+      }
+    }
+
+    // Make sure there are no open sections when we're done.
+    openSection = sections.pop();
+    if (openSection) {
+      throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
+    }
+
+    return nestTokens(squashTokens(tokens));
+  }
+
+  /**
+   * Combines the values of consecutive text tokens in the given `tokens` array
+   * to a single token.
+   */
+  function squashTokens(tokens) {
+    var squashedTokens = [];
+
+    var token, lastToken;
+    for (var i = 0, len = tokens.length; i < len; ++i) {
+      token = tokens[i];
+
+      if (token) {
+        if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
+          lastToken[1] += token[1];
+          lastToken[3] = token[3];
+        } else {
+          squashedTokens.push(token);
+          lastToken = token;
+        }
+      }
+    }
+
+    return squashedTokens;
+  }
+
+  /**
+   * Forms the given array of `tokens` into a nested tree structure where
+   * tokens that represent a section have two additional items: 1) an array of
+   * all tokens that appear in that section and 2) the index in the original
+   * template that represents the end of that section.
+   */
+  function nestTokens(tokens) {
+    var nestedTokens = [];
+    var collector = nestedTokens;
+    var sections = [];
+
+    var token, section;
+    for (var i = 0, len = tokens.length; i < len; ++i) {
+      token = tokens[i];
+
+      switch (token[0]) {
+      case '#':
+      case '^':
+        collector.push(token);
+        sections.push(token);
+        collector = token[4] = [];
+        break;
+      case '/':
+        section = sections.pop();
+        section[5] = token[2];
+        collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
+        break;
+      default:
+        collector.push(token);
+      }
+    }
+
+    return nestedTokens;
+  }
+
+  /**
+   * A simple string scanner that is used by the template parser to find
+   * tokens in template strings.
+   */
+  function Scanner(string) {
+    this.string = string;
+    this.tail = string;
+    this.pos = 0;
+  }
+
+  /**
+   * Returns `true` if the tail is empty (end of string).
+   */
+  Scanner.prototype.eos = function () {
+    return this.tail === "";
+  };
+
+  /**
+   * Tries to match the given regular expression at the current position.
+   * Returns the matched text if it can match, the empty string otherwise.
+   */
+  Scanner.prototype.scan = function (re) {
+    var match = this.tail.match(re);
+
+    if (match && match.index === 0) {
+      var string = match[0];
+      this.tail = this.tail.substring(string.length);
+      this.pos += string.length;
+      return string;
+    }
+
+    return "";
+  };
+
+  /**
+   * Skips all text until the given regular expression can be matched. Returns
+   * the skipped string, which is the entire tail if no match can be made.
+   */
+  Scanner.prototype.scanUntil = function (re) {
+    var index = this.tail.search(re), match;
+
+    switch (index) {
+    case -1:
+      match = this.tail;
+      this.tail = "";
+      break;
+    case 0:
+      match = "";
+      break;
+    default:
+      match = this.tail.substring(0, index);
+      this.tail = this.tail.substring(index);
+    }
+
+    this.pos += match.length;
+
+    return match;
+  };
+
+  /**
+   * Represents a rendering context by wrapping a view object and
+   * maintaining a reference to the parent context.
+   */
+  function Context(view, parentContext) {
+    this.view = view == null ? {} : view;
+    this.cache = { '.': this.view };
+    this.parent = parentContext;
+  }
+
+  /**
+   * Creates a new context using the given view with this context
+   * as the parent.
+   */
+  Context.prototype.push = function (view) {
+    return new Context(view, this);
+  };
+
+  /**
+   * Returns the value of the given name in this context, traversing
+   * up the context hierarchy if the value is absent in this context's view.
+   */
+  Context.prototype.lookup = function (name) {
+    var value;
+    if (name in this.cache) {
+      value = this.cache[name];
+    } else {
+      var context = this;
+
+      while (context) {
+        if (name.indexOf('.') > 0) {
+          value = context.view;
+
+          var names = name.split('.'), i = 0;
+          while (value != null && i < names.length) {
+            value = value[names[i++]];
+          }
+        } else {
+          value = context.view[name];
+        }
+
+        if (value != null) break;
+
+        context = context.parent;
+      }
+
+      this.cache[name] = value;
+    }
+
+    if (isFunction(value)) {
+      value = value.call(this.view);
+    }
+
+    return value;
+  };
+
+  /**
+   * A Writer knows how to take a stream of tokens and render them to a
+   * string, given a context. It also maintains a cache of templates to
+   * avoid the need to parse the same template twice.
+   */
+  function Writer() {
+    this.cache = {};
+  }
+
+  /**
+   * Clears all cached templates in this writer.
+   */
+  Writer.prototype.clearCache = function () {
+    this.cache = {};
+  };
+
+  /**
+   * Parses and caches the given `template` and returns the array of tokens
+   * that is generated from the parse.
+   */
+  Writer.prototype.parse = function (template, tags) {
+    var cache = this.cache;
+    var tokens = cache[template];
+
+    if (tokens == null) {
+      tokens = cache[template] = parseTemplate(template, tags);
+    }
+
+    return tokens;
+  };
+
+  /**
+   * High-level method that is used to render the given `template` with
+   * the given `view`.
+   *
+   * The optional `partials` argument may be an object that contains the
+   * names and templates of partials that are used in the template. It may
+   * also be a function that is used to load partial templates on the fly
+   * that takes a single argument: the name of the partial.
+   */
+  Writer.prototype.render = function (template, view, partials) {
+    var tokens = this.parse(template);
+    var context = (view instanceof Context) ? view : new Context(view);
+    return this.renderTokens(tokens, context, partials, template);
+  };
+
+  /**
+   * Low-level method that renders the given array of `tokens` using
+   * the given `context` and `partials`.
+   *
+   * Note: The `originalTemplate` is only ever used to extract the portion
+   * of the original template that was contained in a higher-order section.
+   * If the template doesn't use higher-order sections, this argument may
+   * be omitted.
+   */
+  Writer.prototype.renderTokens = function (tokens, context, partials, originalTemplate) {
+    var buffer = '';
+
+    // This function is used to render an arbitrary template
+    // in the current context by higher-order sections.
+    var self = this;
+    function subRender(template) {
+      return self.render(template, context, partials);
+    }
+
+    var token, value;
+    for (var i = 0, len = tokens.length; i < len; ++i) {
+      token = tokens[i];
+
+      switch (token[0]) {
+      case '#':
+        value = context.lookup(token[1]);
+        if (!value) continue;
+
+        if (isArray(value)) {
+          for (var j = 0, jlen = value.length; j < jlen; ++j) {
+            buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
+          }
+        } else if (typeof value === 'object' || typeof value === 'string') {
+          buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);
+        } else if (isFunction(value)) {
+          if (typeof originalTemplate !== 'string') {
+            throw new Error('Cannot use higher-order sections without the original template');
+          }
+
+          // Extract the portion of the original template that the section contains.
+          value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);
+
+          if (value != null) buffer += value;
+        } else {
+          buffer += this.renderTokens(token[4], context, partials, originalTemplate);
+        }
+
+        break;
+      case '^':
+        value = context.lookup(token[1]);
+
+        // Use JavaScript's definition of falsy. Include empty arrays.
+        // See https://github.com/janl/mustache.js/issues/186
+        if (!value || (isArray(value) && value.length === 0)) {
+          buffer += this.renderTokens(token[4], context, partials, originalTemplate);
+        }
+
+        break;
+      case '>':
+        if (!partials) continue;
+        value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
+        if (value != null) buffer += this.renderTokens(this.parse(value), context, partials, value);
+        break;
+      case '&':
+        value = context.lookup(token[1]);
+        if (value != null) buffer += value;
+        break;
+      case 'name':
+        value = context.lookup(token[1]);
+        if (value != null) buffer += mustache.escape(value);
+        break;
+      case 'text':
+        buffer += token[1];
+        break;
+      }
+    }
+
+    return buffer;
+  };
+
+  mustache.name = "mustache.js";
+  mustache.version = "0.8.1";
+  mustache.tags = [ "{{", "}}" ];
+
+  // All high-level mustache.* functions use this writer.
+  var defaultWriter = new Writer();
+
+  /**
+   * Clears all cached templates in the default writer.
+   */
+  mustache.clearCache = function () {
+    return defaultWriter.clearCache();
+  };
+
+  /**
+   * Parses and caches the given template in the default writer and returns the
+   * array of tokens it contains. Doing this ahead of time avoids the need to
+   * parse templates on the fly as they are rendered.
+   */
+  mustache.parse = function (template, tags) {
+    return defaultWriter.parse(template, tags);
+  };
+
+  /**
+   * Renders the `template` with the given `view` and `partials` using the
+   * default writer.
+   */
+  mustache.render = function (template, view, partials) {
+    return defaultWriter.render(template, view, partials);
+  };
+
+  // This is here for backwards compatibility with 0.4.x.
+  mustache.to_html = function (template, view, partials, send) {
+    var result = mustache.render(template, view, partials);
+
+    if (isFunction(send)) {
+      send(result);
+    } else {
+      return result;
+    }
+  };
+
+  // Export the escaping function so that the user may override it.
+  // See https://github.com/janl/mustache.js/issues/244
+  mustache.escape = escapeHtml;
+
+  // Export these mainly for testing, but also for advanced usage.
+  mustache.Scanner = Scanner;
+  mustache.Context = Context;
+  mustache.Writer = Writer;
+
+}));
+  $.mustache = function (template, view, partials) {
+    return Mustache.render(template, view, partials);
+  };
+
+  $.fn.mustache = function (view, partials) {
+    return $(this).map(function (i, elm) {
+      var template = $.trim($(elm).html());
+      var output = $.mustache(template, view, partials);
+      return $(output).get();
+    });
+  };
+
+})(jQuery);