changeset 29:8b9113d4f428

add client gui
author Omair Majid <omajid@redhat.com>
date Mon, 09 Jan 2012 18:27:54 -0500
parents 3c64459fb729
children 973244c2aa30
files src/com/redhat/thermostat/client/AgentRef.java src/com/redhat/thermostat/client/ApplicationInfo.java src/com/redhat/thermostat/client/ClientArgs.java src/com/redhat/thermostat/client/ConnectionInfo.java src/com/redhat/thermostat/client/DummyFacade.java src/com/redhat/thermostat/client/HostInformationFacade.java src/com/redhat/thermostat/client/Main.java src/com/redhat/thermostat/client/MemoryType.java src/com/redhat/thermostat/client/ThermostatFacade.java src/com/redhat/thermostat/client/Translate.java src/com/redhat/thermostat/client/VmInformationFacade.java src/com/redhat/thermostat/client/VmRef.java src/com/redhat/thermostat/client/strings.properties src/com/redhat/thermostat/client/ui/AboutDialog.java src/com/redhat/thermostat/client/ui/Components.java src/com/redhat/thermostat/client/ui/ConnectionSelectionDialog.java src/com/redhat/thermostat/client/ui/HomePanel.java src/com/redhat/thermostat/client/ui/HostPanel.java src/com/redhat/thermostat/client/ui/HtmlTextBuilder.java src/com/redhat/thermostat/client/ui/IconResource.java src/com/redhat/thermostat/client/ui/LayoutDebugHelper.java src/com/redhat/thermostat/client/ui/MainWindow.java src/com/redhat/thermostat/client/ui/SimpleTable.java src/com/redhat/thermostat/client/ui/VerticalOnlyScrollingPanel.java src/com/redhat/thermostat/client/ui/VmPanel.java src/com/redhat/thermostat/client/ui/WrapLayout.java
diffstat 26 files changed, 2453 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/AgentRef.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,25 @@
+package com.redhat.thermostat.client;
+
+public class AgentRef {
+
+    private final String uid;
+    private final String name;
+
+    public AgentRef(String id, String name) {
+        this.uid = id;
+        this.name = name;
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+
+    public String getId() {
+        return uid;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ApplicationInfo.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,55 @@
+package com.redhat.thermostat.client;
+
+import java.util.Date;
+
+import javax.swing.Icon;
+
+import com.redhat.thermostat.client.ui.IconResource;
+
+public class ApplicationInfo {
+
+    /*
+     * TODO All of these fields should be generated on build.
+     */
+
+    public String getName() {
+        return "thermostat";
+    }
+
+    public String getVersion() {
+        return "0.0.1";
+    }
+
+    public String getDescription() {
+        return "A monitoring and servicability tool for OpenJDK";
+    }
+
+    public Icon getIcon() {
+        return IconResource.QUESTION.getIcon();
+    }
+
+    public String getReleaseDate() {
+        return "2012-02-02";
+    }
+
+    public String getBuildDate() {
+        return new Date().toString();
+    }
+
+    public String getCopyright() {
+        return "(C) Copyright Red Hat, Inc";
+    }
+
+    public String getLicense() {
+        return "GPL2 + Classpath";
+    }
+
+    public String getEmail() {
+        return "an@example.com";
+    }
+
+    public String getWebsite() {
+        return "http://an.example.com";
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ClientArgs.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,51 @@
+package com.redhat.thermostat.client;
+
+import java.awt.Window;
+
+import com.redhat.thermostat.client.ui.LayoutDebugHelper;
+
+public class ClientArgs {
+
+    private static boolean isDebugLayout =
+            Boolean.getBoolean("thermostat.debug-layout");
+
+    // private static boolean isDebugLayout = true;
+
+    public ClientArgs(String[] initialArgs) {
+        // remove 'unused' warnings
+        for (String arg : initialArgs) {
+            if (arg.equals("--debug-layout")) {
+                isDebugLayout = true;
+            }
+        }
+        // TODO what arguments do we care about?
+        // perhaps skipping the mode selection?
+
+        if (isDebugLayout()) {
+            Thread layoutDebugger = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    LayoutDebugHelper helper = new LayoutDebugHelper();
+                    while (true) {
+                        try {
+                            Thread.sleep(5000);
+                        } catch (InterruptedException e) {
+                            System.err.println("Layout Debug Helper exiting");
+                        }
+                        Window[] windows = Window.getWindows();
+                        for (Window w : windows) {
+                            helper.debugLayout(w);
+                            w.invalidate();
+                            w.repaint();
+                        }
+                    }
+                }
+            });
+            layoutDebugger.start();
+        }
+    }
+
+    public static boolean isDebugLayout() {
+        return isDebugLayout;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ConnectionInfo.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,57 @@
+package com.redhat.thermostat.client;
+
+public class ConnectionInfo {
+
+    public enum ConnectionType {
+        LOCAL(false),
+        REMOTE(true),
+        CLUSTER(true),
+        NONE(false, false), ;
+
+        boolean isDisplayable = false;
+        boolean needsUrl = false;
+
+        private ConnectionType(boolean needsUrl) {
+            this.needsUrl = needsUrl;
+        }
+
+        private ConnectionType(boolean isDisplayable, boolean needsUrl) {
+            this.isDisplayable = isDisplayable;
+        }
+
+        public boolean isDisplayable() {
+            return isDisplayable;
+        }
+
+        public boolean needsUrl() {
+            return needsUrl;
+        }
+    }
+
+    private ConnectionType type;
+    private String url;
+
+    public void setType(ConnectionType type) {
+        this.type = type;
+    }
+
+    public ConnectionType getType() {
+        return type;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    @Override
+    public String toString() {
+        if (url == null) {
+            return type.toString();
+        }
+        return type.toString() + " to " + url;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/DummyFacade.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,258 @@
+package com.redhat.thermostat.client;
+
+import static com.redhat.thermostat.client.Translate._;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import com.redhat.thermostat.common.HostInfo;
+import com.redhat.thermostat.common.NetworkInfo;
+import com.redhat.thermostat.common.NetworkInterfaceInfo;
+import com.redhat.thermostat.common.VmInfo;
+import com.redhat.thermostat.common.VmMemoryStat;
+import com.redhat.thermostat.common.VmMemoryStat.Generation;
+import com.redhat.thermostat.common.VmMemoryStat.Space;
+
+public class DummyFacade implements ThermostatFacade, HostInformationFacade, VmInformationFacade {
+
+    private final Random r = new Random();
+    private List<MemoryType> toDisplay = new ArrayList<MemoryType>();
+
+    private AgentRef onlyAgent = new AgentRef("a-random-string-of-letters-and-numbers", "agent on localhost");
+    private VmRef onlyVm = new VmRef(onlyAgent, "a-random-string-of-letters-and-numbers-or-perhaps-a-process-id", "super crazy awesome java app");
+
+    public DummyFacade() {
+        toDisplay.addAll(Arrays.asList(MemoryType.values()));
+    }
+
+    @Override
+    public AgentRef[] getConnectedAgents() {
+        return new AgentRef[] { onlyAgent };
+    }
+
+    @Override
+    public VmRef[] getConnectedVms() {
+        return new VmRef[] { onlyVm };
+    }
+
+    @Override
+    public VmRef[] getVms() {
+        return new VmRef[] { onlyVm };
+    }
+
+    @Override
+    public HostInformationFacade getHost(AgentRef ref) {
+        return this;
+    }
+
+    @Override
+    public HostInfo getHostInfo() {
+        String hostname = "host.example.com";
+        String osName = "Fedora 99";
+        String osKernel = "Linux 9.9.9.9";
+        String cpuModel = "Some CPU @ some speed GHz";
+        int cpuCount = 99;
+        long totalMemory = 1;
+        return new HostInfo(hostname, osName, osKernel, cpuModel, cpuCount, totalMemory);
+    }
+
+    @Override
+    public NetworkInfo getNetworkInfo() {
+        NetworkInfo info = new NetworkInfo();
+
+        NetworkInterfaceInfo eth0 = new NetworkInterfaceInfo("eth0");
+        eth0.setIp4Addr("1.1.1.1");
+        eth0.setIp6Addr("1:::::::::1");
+        info.addNetworkInterfaceInfo(eth0);
+
+        NetworkInterfaceInfo em0 = new NetworkInterfaceInfo("em0");
+        em0.setIp4Addr("256.256.256.256");
+        info.addNetworkInterfaceInfo(em0);
+
+        return info;
+    }
+
+    @Override
+    public VmInformationFacade getVm(VmRef vmRef) {
+        return this;
+    }
+
+    @Override
+    public VmInfo getVmInfo() {
+
+        // TODO hook into storage and return the actual VmInfo object
+        int vmPid = 0;
+        long startTime = System.currentTimeMillis() - 10000;
+        long stopTime = Integer.MIN_VALUE;
+        String javaVersion = "2.9.9";
+        String javaHome = "/usr/lib/jvm/java/jre/";
+        String mainClass = "com.foo.bar";
+        String commandLine = "some java program";
+        String vmName = "hotspot usb compiler";
+        String vmInfo = "future predictive mode";
+        String vmVersion = "99b99";
+        String vmArguments = "-XX:+EvenFasterPlease --X:+UNROLL_ALL_THE_LOOPS!";
+        Map<String, String> properties = new HashMap<String, String>();
+        Map<String, String> environment = new HashMap<String, String>();
+        List<String> loadedNativeLibraries = new ArrayList<String>();
+        return new VmInfo(vmPid, startTime, stopTime, javaVersion, javaHome, mainClass, commandLine, vmName, vmInfo, vmVersion, vmArguments, properties, environment, loadedNativeLibraries);
+    }
+
+    @Override
+    public double[][] getCpuLoad() {
+        double[][] cpuData = new double[][] {
+                new double[] { 10000, r.nextDouble() },
+                new double[] { 10010, r.nextDouble() },
+                new double[] { 10020, r.nextDouble() },
+                new double[] { 10030, r.nextDouble() },
+                new double[] { 10040, r.nextDouble() },
+                new double[] { 10050, r.nextDouble() },
+                new double[] { 10060, r.nextDouble() },
+        };
+
+        return cpuData;
+    }
+
+    @Override
+    public long[][] getMemoryUsage(MemoryType type) {
+        long[][] data = new long[][] {
+                new long[] { 100010, r.nextLong() },
+                new long[] { 100020, r.nextLong() },
+                new long[] { 100030, r.nextLong() },
+                new long[] { 100040, r.nextLong() },
+                new long[] { 100050, r.nextLong() },
+                new long[] { 100060, r.nextLong() },
+                new long[] { 100070, r.nextLong() },
+                new long[] { 100080, r.nextLong() },
+                new long[] { 100090, r.nextLong() },
+                new long[] { 100110, r.nextLong() },
+        };
+        return data;
+    }
+
+    @Override
+    public MemoryType[] getMemoryTypesToDisplay() {
+        return toDisplay.toArray(new MemoryType[0]);
+    }
+
+    @Override
+    public boolean isMemoryTypeDisplayed(MemoryType type) {
+        return toDisplay.contains(type);
+    }
+
+    @Override
+    public void setDisplayMemoryType(MemoryType type, boolean selected) {
+        if (selected) {
+            if (!toDisplay.contains(type)) {
+                toDisplay.add(type);
+            }
+        } else {
+            toDisplay.remove(type);
+        }
+
+    }
+
+    @Override
+    public String[] getCollectorNames() {
+        return new String[] { "PSScavenge", "PSMarkSweep" };
+    }
+
+    @Override
+    public long getTotalInvocations() {
+        return 11;
+    }
+
+    @Override
+    public long[][] getCollectorData(String collectorName) {
+        List<long[]> data = new ArrayList<long[]>();
+        long last = 2;
+        data.add(new long[] { 100000, (last = last + r.nextInt(10)) });
+        data.add(new long[] { 100010, (last = last + r.nextInt(10)) });
+        data.add(new long[] { 100020, (last = last + r.nextInt(10)) });
+        data.add(new long[] { 100030, (last = last + r.nextInt(10)) });
+        data.add(new long[] { 100040, (last = last + r.nextInt(10)) });
+        data.add(new long[] { 100050, (last = last + r.nextInt(10)) });
+        data.add(new long[] { 100060, (last = last + r.nextInt(10)) });
+        data.add(new long[] { 100070, (last = last + r.nextInt(10)) });
+        data.add(new long[] { 100080, (last = last + r.nextInt(10)) });
+        data.add(new long[] { 100090, (last = last + r.nextInt(10)) });
+        data.add(new long[] { 100110, (last = last + r.nextInt(10)) });
+
+        return data.toArray(new long[0][0]);
+    }
+
+    @Override
+    public String getCollectorGeneration(String collectorName) {
+        if (collectorName.equals("PSScavenge")) {
+            return _("YOUNG_GEN");
+        } else if (collectorName.equals("PSMarkSweep")) {
+            return _("OLD_GEN");
+        }
+        return ("UNKNOWN_GEN");
+    }
+
+    @Override
+    public VmMemoryStat getMemoryInfo() {
+        long timestamp = -1;
+        List<Generation> generations = new ArrayList<Generation>();
+
+        Generation youngGen = new Generation();
+        youngGen.name = "eden";
+        generations.add(youngGen);
+
+        List<Space> youngSpaces = new ArrayList<Space>();
+
+        Space eden = new Space();
+        eden.name = _("EDEN_GEN");
+        eden.used = 100;
+        eden.capacity = 200;
+        eden.maxCapacity = 300;
+        youngSpaces.add(eden);
+
+        Space s0 = new Space();
+        s0.name = _("S0_GEN");
+        s0.used = 100;
+        s0.capacity = 200;
+        s0.maxCapacity = 400;
+        youngSpaces.add(s0);
+
+        Space s1 = new Space();
+        s1.name = _("S1_GEN");
+        s1.used = 150;
+        s1.capacity = 200;
+        s1.maxCapacity = 400;
+        youngSpaces.add(s1);
+
+        youngGen.spaces = youngSpaces;
+
+        Generation oldGen = new Generation();
+        generations.add(oldGen);
+
+        Space oldSpace = new Space();
+        oldSpace.name = _("OLD_GEN");
+        oldSpace.used = 400;
+        oldSpace.capacity = 500;
+        oldSpace.maxCapacity = 600;
+
+        oldGen.spaces = Arrays.asList(new Space[] { oldSpace });
+
+        Generation permGen = new Generation();
+        generations.add(permGen);
+
+        Space permSpace = new Space();
+        permSpace.name = _("PERM_GEN");
+        permSpace.used = 50;
+        permSpace.capacity = 200;
+        permSpace.maxCapacity = 200;
+
+        permGen.spaces = Arrays.asList(new Space[] { permSpace });
+
+        VmMemoryStat stat = new VmMemoryStat(timestamp, 0, generations);
+        return stat;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/HostInformationFacade.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,25 @@
+package com.redhat.thermostat.client;
+
+import com.redhat.thermostat.common.HostInfo;
+import com.redhat.thermostat.common.NetworkInfo;
+
+public interface HostInformationFacade {
+    public abstract VmRef[] getVms();
+
+    public abstract VmInformationFacade getVm(VmRef vmRef);
+
+    public abstract HostInfo getHostInfo();
+
+    public abstract NetworkInfo getNetworkInfo();
+
+    public abstract double[][] getCpuLoad();
+
+    public abstract long[][] getMemoryUsage(MemoryType type);
+
+    public abstract MemoryType[] getMemoryTypesToDisplay();
+
+    public abstract boolean isMemoryTypeDisplayed(MemoryType type);
+
+    public abstract void setDisplayMemoryType(MemoryType type, boolean selected);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/Main.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,45 @@
+package com.redhat.thermostat.client;
+
+import javax.swing.JFrame;
+import javax.swing.JPopupMenu;
+import javax.swing.SwingUtilities;
+
+import com.redhat.thermostat.client.ConnectionInfo.ConnectionType;
+import com.redhat.thermostat.client.ui.ConnectionSelectionDialog;
+import com.redhat.thermostat.client.ui.MainWindow;
+
+public class Main {
+
+    private static void showGui() {
+        JPopupMenu.setDefaultLightWeightPopupEnabled(false);
+
+        ConnectionInfo model = new ConnectionInfo();
+
+        ConnectionSelectionDialog dialog = new ConnectionSelectionDialog((JFrame) null, model);
+        dialog.pack();
+        dialog.setModal(true);
+        dialog.setVisible(true);
+
+        if (model.getType() == ConnectionType.NONE) {
+            return;
+        }
+
+        ThermostatFacade facade = new DummyFacade();
+
+        MainWindow gui = new MainWindow(facade);
+        gui.setStartupMode(model.getType());
+        gui.pack();
+        gui.setVisible(true);
+    }
+
+    public static void main(String[] args) {
+        ClientArgs arguments = new ClientArgs(args);
+
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                showGui();
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/MemoryType.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,28 @@
+package com.redhat.thermostat.client;
+
+import static com.redhat.thermostat.client.Translate._;
+
+public enum MemoryType {
+    MEMORY_TOTAL("total", _("HOST_MEMORY_TOTAL")),
+    MEMORY_FREE("free", _("HOST_MEMORY_FREE")),
+    MEMORY_USED("used", _("HOST_MEMORY_USED")),
+    SWAP_TOTAL("swap-total", _("HOST_SWAP_TOTAL")),
+    SWAP_FREE("swap-free", _("HOST_SWAP_FREE")),
+    SWAP_BUFFERS("swap-buffers", _("HOST_BUFFERS"));
+
+    private String humanReadable;
+
+    private MemoryType(String key, String humanReadable) {
+        this.humanReadable = humanReadable;
+    }
+
+    public String getLabel() {
+        return humanReadable;
+    }
+
+    @Override
+    public String toString() {
+        return humanReadable;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ThermostatFacade.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,10 @@
+package com.redhat.thermostat.client;
+
+public interface ThermostatFacade {
+    public abstract AgentRef[] getConnectedAgents();
+
+    public abstract VmRef[] getConnectedVms();
+
+    public abstract HostInformationFacade getHost(AgentRef ref);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/Translate.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,21 @@
+package com.redhat.thermostat.client;
+
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+public class Translate {
+
+    private static ResourceBundle resourceBundle = null;
+
+    static {
+        resourceBundle = ResourceBundle.getBundle("com.redhat.thermostat.client.strings");
+    }
+
+    public static String _(String toTranslate) {
+        return resourceBundle.getString(toTranslate);
+    }
+
+    public static String _(String toTranslate, String... params) {
+        return MessageFormat.format(_(toTranslate), (Object[]) params);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/VmInformationFacade.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,20 @@
+package com.redhat.thermostat.client;
+
+import com.redhat.thermostat.common.VmInfo;
+import com.redhat.thermostat.common.VmMemoryStat;
+
+public interface VmInformationFacade {
+
+    public VmInfo getVmInfo();
+
+    public String[] getCollectorNames();
+
+    public long getTotalInvocations();
+
+    public long[][] getCollectorData(String collectorName);
+
+    public String getCollectorGeneration(String collectorName);
+
+    public VmMemoryStat getMemoryInfo();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/VmRef.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,27 @@
+package com.redhat.thermostat.client;
+
+public class VmRef {
+
+    private final AgentRef agentRef;
+    private final String uid;
+    private final String name;
+
+    public VmRef(AgentRef agentRef, String id, String name) {
+        this.agentRef = agentRef;
+        this.uid = id;
+        this.name = name;
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+
+    public String getId() {
+        return uid;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/strings.properties	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,116 @@
+MAIN_WINDOW_TITLE = Thermostat
+
+MAIN_WINDOW_TREE_ROOT_NAME = Thermostat
+
+WELCOME_MESSAGE = Welcome to thermostat!<br>\
+		Thermostat is awesome++<br>\
+		if you love it, contribute code!<br>\
+		THAT IS ALL.<br><br>\
+		A subtle thought that is in error may yet give rise to fruitful inquiry<br>\
+		that can establish truths of great value<br>\
+		-- Isaac Asimov<br>
+
+BUTTON_CLOSE = Close
+BUTTON_NEXT = Next
+BUTTON_CANCEL = Cancel
+BUTTON_OK = OK
+
+MENU_FILE = File
+MENU_FILE_CONNECT = Connect
+MENU_FILE_IMPORT = Import
+MENU_FILE_EXPORT = Export
+MENU_FILE_EXIT = Exit
+MENU_HELP = Help
+MENU_HELP_ABOUT = About
+
+GARBAGE_COLLECTION = Garbage Collection
+YOUNG_GEN = Young
+EDEN_GEN = Eden
+S0_GEN = Survivor 0
+S1_GEN = Survivor 1
+OLD_GEN = Tenured
+PERM_GEN = Permanent
+UNKNOWN_GEN = Unknown
+SOME_GENERATION = {0} Generation
+
+STARTUP_MODE_SELECTION_DIALOG_TITLE = Welcome to Thermostat!
+STARTUP_MODE_SELECTION_INTRO = Which JVMs to do you want to monitor?
+STARTUP_MODE_SELECTION_TYPE_LOCAL = Local
+STARTUP_MODE_SELECTION_TYPE_REMOTE = Remote
+STARTUP_MODE_SELECTION_TYPE_CLUSTER = Cluster
+STARTUP_MODE_SELECTION_URL_LABEL = Host Location
+
+ABOUT_DIALOG_VERSION_AND_RELEASE = Version {0} (released {1})
+ABOUT_DIALOG_LICENSE = Licensed under the {0} license.
+ABOUT_DIALOG_EMAIL = Email: {0}
+ABOUT_DIALOG_WEBSITE = Website: {0}
+
+HOME_PANEL_SECTION_SUMMARY = Summary
+HOME_PANEL_TOTAL_MACHINES = Total Machines
+HOME_PANEL_TOTAL_JVMS = Total Java Virtual Machines
+HOME_PANEL_SECTION_ISSUES = Issues
+HOME_PANEL_NO_ISSUES = No Issues
+
+HOST_INFO_TAB_OVERVIEW = Overview
+HOST_INFO_TAB_MEMORY = Memory
+HOST_INFO_TAB_CPU = Processor
+HOST_INFO_TAB_IO = IO
+
+HOST_OVERVIEW_SECTION_BASICS = Basics
+HOST_OVERVIEW_SECTION_HARDWARE = Hardware
+HOST_OVERVIEW_SECTION_SOFTWARE = Software
+
+HOST_INFO_HOSTNAME = Host
+HOST_INFO_CPU_COUNT = Processor Count
+HOST_INFO_CPU_MODEL = Processor Model
+HOST_INFO_OS_NAME = OS Name
+HOST_INFO_OS_KERNEL = OS Kernel
+HOST_INFO_MEMORY_TOTAL = Total Memory
+HOST_INFO_NETWORK = Network
+HOST_INFO_NETWORK_INTERFACE_ADDDRESS = {0} ({1} or {2}) 
+
+HOST_CPU_SECTION_OVERVIEW = Processor
+HOST_CPU_USAGE_CHART_TITLE = Cpu Usage
+HOST_CPU_USAGE_CHART_TIME_LABEL = Time
+HOST_CPU_USAGE_CHART_VALUE_LABEL = Usage
+
+HOST_MEMORY_SECTION_OVERVIEW = Memory
+HOST_MEMORY_CHART_TITLE = Memory
+HOST_MEMORY_CHART_TIME_LABEL = Time
+HOST_MEMORY_CHART_SIZE_LABEL = Size (bytes)
+
+HOST_MEMORY_TOTAL = Total Memory
+HOST_MEMORY_FREE = Free Memory
+HOST_MEMORY_USED = Used Memory
+HOST_SWAP_TOTAL = Total Swap
+HOST_SWAP_FREE = Free Swap
+HOST_BUFFERS = Buffers
+
+VM_INFO_TAB_OVERVIEW = Overview
+VM_INFO_TAB_MEMORY = Memory
+VM_INFO_TAB_GC = GC
+
+VM_INFO_SECTION_PROCESS = Process Information
+VM_INFO_SECTION_JAVA = Java Information
+
+VM_INFO_PROCESS_ID = Process Id
+VM_INFO_START_TIME = Start time
+VM_INFO_STOP_TIME = Stop time
+VM_INFO_MAIN_CLASS = Main Class
+VM_INFO_COMMAND_LINE = Command Line
+VM_INFO_JAVA_VERSION = Java Version
+VM_INFO_VM = Virtual Machine
+VM_INFO_VM_NAME_AND_VERSION = {0} version {1}
+VM_INFO_PROPERTIES = Properties
+VM_INFO_ENVIRONMENT = Environment
+VM_INFO_LIBRARIES = Native Libraries
+
+VM_CURRENT_MEMORY_CHART_USED = Used
+VM_CURRENT_MEMORY_CHART_CAPACITY = Capacity
+VM_CURRENT_MEMORY_CHART_MAX_CAPACITY = Max Capacity
+VM_CURRENT_MEMORY_CHART_SPACE = Memory Region
+VM_CURRENT_MEMORY_CHART_SIZE = Size
+
+VM_GC_COLLECTOR_OVER_GENERATION = Collector {0} running on {1}
+VM_GC_COLLECTOR_CHART_REAL_TIME_LABEL = Time
+VM_GC_COLLECTOR_CHART_GC_TIME_LABEL = Total Time Spent on GC
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/AboutDialog.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,83 @@
+package com.redhat.thermostat.client.ui;
+
+import static com.redhat.thermostat.client.Translate._;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.Border;
+
+import com.redhat.thermostat.client.ApplicationInfo;
+
+public class AboutDialog extends JDialog {
+
+    private static final long serialVersionUID = -2459715525658426058L;
+
+    private static final Border emptySpace = BorderFactory.createEmptyBorder(10, 10, 10, 10);
+
+    private final ApplicationInfo appInfo;
+
+    public AboutDialog(ApplicationInfo info) {
+        this.appInfo = info;
+        setupUi();
+    }
+
+    private void setupUi() {
+
+        String name = appInfo.getName();
+        String description = appInfo.getDescription();
+        String version = appInfo.getVersion();
+        Icon icon = appInfo.getIcon();
+        String releaseDate = appInfo.getReleaseDate();
+        String buildDate = appInfo.getBuildDate();
+        String copyright = appInfo.getCopyright();
+        String license = appInfo.getLicense();
+        String email = appInfo.getEmail();
+        String website = appInfo.getWebsite();
+
+        JPanel iconContainer = new JPanel(new BorderLayout());
+        JLabel iconLabel = new JLabel(icon);
+        iconLabel.setBorder(emptySpace);
+        iconContainer.add(iconLabel);
+
+        add(iconContainer, BorderLayout.LINE_START);
+
+        JPanel descriptionContainer = new JPanel();
+        descriptionContainer.setLayout(new BoxLayout(descriptionContainer, BoxLayout.PAGE_AXIS));
+        descriptionContainer.setBorder(emptySpace);
+
+        descriptionContainer.add(Box.createGlue());
+        descriptionContainer.add(new JLabel(new HtmlTextBuilder().larger(name).toHtml()));
+        descriptionContainer.add(new JLabel(_("ABOUT_DIALOG_VERSION_AND_RELEASE", version, releaseDate)));
+        descriptionContainer.add(new JLabel(description));
+        descriptionContainer.add(new JLabel(copyright));
+        descriptionContainer.add(new JLabel(_("ABOUT_DIALOG_LICENSE", license)));
+        descriptionContainer.add(new JLabel(_("ABOUT_DIALOG_EMAIL", email)));
+        JLabel websiteLink = new JLabel(_("ABOUT_DIALOG_WEBSITE", website));
+        descriptionContainer.add(websiteLink);
+        descriptionContainer.add(Box.createGlue());
+
+        add(descriptionContainer, BorderLayout.CENTER);
+
+        JPanel buttonContainer = new JPanel();
+        JButton closeButton = new JButton(_("BUTTON_CLOSE"));
+        closeButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                AboutDialog.this.dispose();
+            }
+        });
+        buttonContainer.add(closeButton);
+        add(buttonContainer, BorderLayout.PAGE_END);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/Components.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,30 @@
+package com.redhat.thermostat.client.ui;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.SwingConstants;
+import javax.swing.border.Border;
+
+public class Components {
+    public static JLabel header(String text) {
+        JLabel label = new JLabel(HtmlTextBuilder.boldHtml(text));
+        label.setHorizontalAlignment(SwingConstants.LEADING);
+        return label;
+    }
+
+    public static JLabel label(String string) {
+        JLabel label = new JLabel(string);
+        label.setHorizontalAlignment(SwingConstants.TRAILING);
+        return label;
+    }
+
+    public static JLabel value(String value) {
+        JLabel toDisplay = new JLabel(value);
+        toDisplay.setHorizontalAlignment(SwingConstants.LEADING);
+        return toDisplay;
+    }
+
+    public static Border smallBorder() {
+        return BorderFactory.createEmptyBorder(5, 5, 5, 5);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/ConnectionSelectionDialog.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,134 @@
+package com.redhat.thermostat.client.ui;
+
+import static com.redhat.thermostat.client.Translate._;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import com.redhat.thermostat.client.ConnectionInfo;
+import com.redhat.thermostat.client.ConnectionInfo.ConnectionType;
+
+public class ConnectionSelectionDialog extends JDialog {
+
+    private static final long serialVersionUID = -3149845673473434408L;
+
+    private static final int ICON_LABEL_GAP = 5;
+
+    private final ConnectionInfo model;
+
+    public ConnectionSelectionDialog(JFrame owner, ConnectionInfo model) {
+        super(owner);
+        setTitle(_("STARTUP_MODE_SELECTION_DIALOG_TITLE"));
+        this.model = model;
+        setupUi();
+    }
+
+    private void setupUi() {
+        BorderLayout layout = new BorderLayout();
+        setLayout(layout);
+        add(createModeSelectionUi(), BorderLayout.CENTER);
+
+        FlowLayout bottomPanelLayout = new FlowLayout(FlowLayout.TRAILING);
+        JPanel bottomPanel = new JPanel(bottomPanelLayout);
+        add(bottomPanel, BorderLayout.PAGE_END);
+        bottomPanel.add(Box.createGlue());
+
+        JPanel buttonsPanel = new JPanel(new GridLayout(1, 5, ICON_LABEL_GAP, ICON_LABEL_GAP));
+        buttonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+        bottomPanel.add(buttonsPanel);
+
+        JButton cancelButton = new JButton(_("BUTTON_CANCEL"));
+        cancelButton.setMargin(new Insets(0, 15, 0, 15));
+        cancelButton.addActionListener(new SetStartupModeListener(this, ConnectionType.NONE));
+        buttonsPanel.add(cancelButton);
+    }
+
+    private JPanel createModeSelectionUi() {
+        JPanel container = new JPanel();
+        container.setBorder(BorderFactory.createEmptyBorder(ICON_LABEL_GAP, ICON_LABEL_GAP, ICON_LABEL_GAP, ICON_LABEL_GAP));
+        GridBagLayout layout = new GridBagLayout();
+        container.setLayout(layout);
+
+        GridBagConstraints c = new GridBagConstraints();
+        c.fill = GridBagConstraints.BOTH;
+        Insets normalInsets = new Insets(ICON_LABEL_GAP, ICON_LABEL_GAP, ICON_LABEL_GAP, ICON_LABEL_GAP);
+        c.insets = normalInsets;
+        c.gridy++;
+        c.gridx = 0;
+        c.gridwidth = GridBagConstraints.REMAINDER;
+        c.weighty = 0;
+
+        JLabel info = new JLabel(_("STARTUP_MODE_SELECTION_INTRO"));
+        container.add(info, c);
+
+        c.gridy++;
+        String localButtonHtml = buildHtml(_("STARTUP_MODE_SELECTION_TYPE_LOCAL"), IconResource.COMPUTER.getUrl());
+        JButton localButton = new JButton(localButtonHtml);
+        container.add(localButton, c);
+
+        c.gridy++;
+        String remoteButtonHtml = buildHtml(_("STARTUP_MODE_SELECTION_TYPE_REMOTE"), IconResource.NETWORK_SERVER.getUrl());
+        JButton remoteButton = new JButton(remoteButtonHtml);
+        container.add(remoteButton, c);
+
+        c.gridy++;
+        String clusterButtonHtml = buildHtml(_("STARTUP_MODE_SELECTION_TYPE_CLUSTER"), IconResource.NETWORK_GROUP.getUrl());
+        JButton clusterButton = new JButton(clusterButtonHtml);
+        container.add(clusterButton, c);
+
+        localButton.addActionListener(new SetStartupModeListener(this, ConnectionType.LOCAL));
+        remoteButton.addActionListener(new SetStartupModeListener(this, ConnectionType.REMOTE));
+        clusterButton.addActionListener(new SetStartupModeListener(this, ConnectionType.CLUSTER));
+        return container;
+    }
+
+    private String buildHtml(String text, String imageUrl) {
+        /* build a table to vertically align image and text properly */
+        // TODO does not deal correctly with right-to-left languages
+        String html = "" +
+                "<html>" +
+                " <table>" +
+                "  <tr> " +
+                "   <td> " + "<img src='" + imageUrl + "'>" + "</td>" +
+                "   <td>" + new HtmlTextBuilder().huge(text).toHtml() + "</td>" +
+                "  </tr>" +
+                " </table>" +
+                "</html>";
+        return html;
+    }
+
+    public ConnectionInfo getModel() {
+        return model;
+    }
+
+    private static class SetStartupModeListener implements ActionListener {
+        private final ConnectionType mode;
+        private final ConnectionSelectionDialog window;
+
+        public SetStartupModeListener(ConnectionSelectionDialog frame, ConnectionType mode) {
+            this.mode = mode;
+            this.window = frame;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            window.getModel().setType(mode);
+            window.setVisible(false);
+            window.dispose();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/HomePanel.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,105 @@
+package com.redhat.thermostat.client.ui;
+
+import static com.redhat.thermostat.client.Translate._;
+
+import java.awt.BorderLayout;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractListModel;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListModel;
+
+import com.redhat.thermostat.client.ThermostatFacade;
+import com.redhat.thermostat.client.ui.SimpleTable.Section;
+import com.redhat.thermostat.client.ui.SimpleTable.TableEntry;
+
+public class HomePanel extends JPanel {
+
+    private static final long serialVersionUID = -5953027789947771737L;
+
+    private final ThermostatFacade facade;
+
+    public HomePanel(ThermostatFacade facade) {
+        this.facade = facade;
+
+        setLayout(new BorderLayout());
+
+        JPanel intro = new JPanel();
+        intro.setLayout(new BorderLayout());
+        JLabel homeIcon = new JLabel(IconResource.USER_HOME.getIcon());
+        intro.add(homeIcon, BorderLayout.LINE_START);
+
+        JLabel welcomeMessageLabel = new JLabel(new HtmlTextBuilder(_("WELCOME_MESSAGE")).toHtml());
+        intro.add(welcomeMessageLabel, BorderLayout.CENTER);
+
+        add(intro, BorderLayout.PAGE_START);
+
+        List<Section> sections = new ArrayList<Section>();
+        TableEntry entry;
+
+        Section summarySection = new Section(_("HOME_PANEL_SECTION_SUMMARY"));
+        sections.add(summarySection);
+
+        entry = new TableEntry(_("HOME_PANEL_TOTAL_MACHINES"), String.valueOf(facade.getConnectedAgents().length));
+        summarySection.add(entry);
+        entry = new TableEntry(_("HOME_PANEL_TOTAL_JVMS"), String.valueOf(facade.getConnectedVms().length));
+        summarySection.add(entry);
+
+        JPanel summaryPanel = SimpleTable.createTable(sections);
+        summaryPanel.setBorder(Components.smallBorder());
+        add(summaryPanel, BorderLayout.CENTER);
+
+        JPanel issuesPanel = createIssuesPanel();
+        issuesPanel.setBorder(Components.smallBorder());
+        add(issuesPanel, BorderLayout.PAGE_END);
+
+    }
+
+    public JPanel createIssuesPanel() {
+        JPanel result = new JPanel(new BorderLayout());
+
+        result.add(Components.header(_("HOME_PANEL_SECTION_ISSUES")), BorderLayout.PAGE_START);
+
+        ListModel model = new IssuesListModel(new ArrayList<Object>());
+
+        JList issuesList = new JList(model);
+        result.add(new JScrollPane(issuesList), BorderLayout.CENTER);
+
+        return result;
+    }
+
+    private static class IssuesListModel extends AbstractListModel {
+
+        private static final long serialVersionUID = 7131506292620902850L;
+
+        private List<? extends Object> delegate;
+
+        private String emptyElement = new String(_("HOME_PANEL_NO_ISSUES"));
+
+        public IssuesListModel(List<? extends Object> actualList) {
+            this.delegate = actualList;
+            // TODO observe the delegate for changes
+        }
+
+        @Override
+        public int getSize() {
+            if (delegate.isEmpty()) {
+                return 1;
+            }
+            return delegate.size();
+        }
+
+        @Override
+        public Object getElementAt(int index) {
+            if (delegate.isEmpty()) {
+                return emptyElement;
+            }
+            return delegate.get(index);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/HostPanel.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,269 @@
+package com.redhat.thermostat.client.ui;
+
+import static com.redhat.thermostat.client.Translate._;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.AbstractButton;
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+import com.redhat.thermostat.client.HostInformationFacade;
+import com.redhat.thermostat.client.MemoryType;
+import com.redhat.thermostat.client.ui.SimpleTable.Key;
+import com.redhat.thermostat.client.ui.SimpleTable.Section;
+import com.redhat.thermostat.client.ui.SimpleTable.TableEntry;
+import com.redhat.thermostat.client.ui.SimpleTable.Value;
+import com.redhat.thermostat.common.HostInfo;
+import com.redhat.thermostat.common.NetworkInterfaceInfo;
+
+public class HostPanel extends JPanel {
+
+    /*
+     * This entire class needs to be more dynamic. We should try to avoid
+     * creating objects and should just update them when necessary
+     */
+
+    private static final long serialVersionUID = 4835316442841009133L;
+
+    private final HostInformationFacade facade;
+    private final HostInfo hostInfo;
+
+    public HostPanel(HostInformationFacade facade) {
+        this.facade = facade;
+        this.hostInfo = facade.getHostInfo();
+        init();
+    }
+
+    private void init() {
+        setLayout(new BorderLayout());
+
+        JTabbedPane tabPane = new JTabbedPane();
+
+        tabPane.insertTab(_("HOST_INFO_TAB_OVERVIEW"), null, createOverviewPanel(), null, 0);
+        tabPane.insertTab(_("HOST_INFO_TAB_CPU"), null, createCpuStatisticsPanel(), null, 1);
+        tabPane.insertTab(_("HOST_INFO_TAB_MEMORY"), null, createMemoryStatisticsPanel(), null, 2);
+
+        // TODO additional tabs provided by plugins
+        // tabPane.insertTab(title, icon, component, tip, 3)
+
+        this.add(tabPane);
+
+    }
+
+    private JPanel createOverviewPanel() {
+
+        TableEntry entry;
+        List<Section> allSections = new ArrayList<Section>();
+
+        Section basics = new Section(_("HOST_OVERVIEW_SECTION_BASICS"));
+        allSections.add(basics);
+
+        entry = new TableEntry(_("HOST_INFO_HOSTNAME"), hostInfo.getHostname());
+        basics.add(entry);
+
+        Section hardware = new Section(_("HOST_OVERVIEW_SECTION_HARDWARE"));
+        allSections.add(hardware);
+
+        entry = new TableEntry(_("HOST_INFO_CPU_MODEL"), hostInfo.getCpuModel());
+        hardware.add(entry);
+        entry = new TableEntry(_("HOST_INFO_CPU_COUNT"), String.valueOf(hostInfo.getCpuCount()));
+        hardware.add(entry);
+        entry = new TableEntry(_("HOST_INFO_MEMORY_TOTAL"), String.valueOf(hostInfo.getTotalMemory()));
+        hardware.add(entry);
+
+        Key key = new Key(_("HOST_INFO_NETWORK"));
+        List<Value> values = new ArrayList<Value>();
+        for (Iterator<NetworkInterfaceInfo> iter = facade.getNetworkInfo().getInterfacesIterator(); iter.hasNext();) {
+            NetworkInterfaceInfo networkInfo = iter.next();
+            String ifaceName = networkInfo.getInterfaceName();
+            String ipv4 = networkInfo.getIp4Addr();
+            String ipv6 = networkInfo.getIp6Addr();
+            values.add(new Value(_("HOST_INFO_NETWORK_INTERFACE_ADDDRESS", ifaceName, ipv4, ipv6)));
+        }
+        hardware.add(new TableEntry(key, values));
+
+        Section software = new Section(_("HOST_OVERVIEW_SECTION_SOFTWARE"));
+        allSections.add(software);
+
+        entry = new TableEntry(_("HOST_INFO_OS_NAME"), hostInfo.getOsName());
+        software.add(entry);
+        entry = new TableEntry(_("HOST_INFO_OS_KERNEL"), hostInfo.getOsKernel());
+        software.add(entry);
+
+        JPanel table = SimpleTable.createTable(allSections);
+        table.setBorder(Components.smallBorder());
+        return table;
+    }
+
+    private JPanel createCpuStatisticsPanel() {
+
+        JPanel contentArea = new JPanel();
+        contentArea.setLayout(new GridBagLayout());
+        GridBagConstraints c = new GridBagConstraints();
+        c.anchor = GridBagConstraints.FIRST_LINE_START;
+        c.fill = GridBagConstraints.NONE;
+        c.gridx = 0;
+        c.gridy = 0;
+
+        List<Section> allSections = new ArrayList<Section>();
+
+        Section cpuBasics = new Section(_("HOST_CPU_SECTION_OVERVIEW"));
+        allSections.add(cpuBasics);
+
+        TableEntry entry;
+        entry = new TableEntry(_("HOST_INFO_CPU_MODEL"), hostInfo.getCpuModel());
+        cpuBasics.add(entry);
+        entry = new TableEntry(_("HOST_INFO_CPU_COUNT"), String.valueOf(hostInfo.getCpuCount()));
+        cpuBasics.add(entry);
+
+        JPanel table = SimpleTable.createTable(allSections);
+        table.setBorder(Components.smallBorder());
+        contentArea.add(table, c);
+
+        double[][] cpuData = facade.getCpuLoad();
+        XYSeries series = new XYSeries("cpu-load");
+        for (double[] data : cpuData) {
+            series.add(data[0], data[1]);
+        }
+        XYSeriesCollection dataset = new XYSeriesCollection();
+        dataset.addSeries(series);
+        JFreeChart chart = ChartFactory.createTimeSeriesChart(
+                _("HOST_CPU_USAGE_CHART_TITLE"),
+                _("HOST_CPU_USAGE_CHART_TIME_LABEL"),
+                _("HOST_CPU_USAGE_CHART_VALUE_LABEL"),
+                dataset,
+                false, false, false);
+
+        ChartPanel chartPanel = new ChartPanel(chart);
+        // make this chart non-interactive
+        chartPanel.setDisplayToolTips(true);
+        chartPanel.setDoubleBuffered(true);
+        chartPanel.setMouseZoomable(false);
+        chartPanel.setPopupMenu(null);
+
+        c.gridy++;
+        c.fill = GridBagConstraints.BOTH;
+        c.weightx = 1;
+        c.weighty = 1;
+
+        contentArea.add(chartPanel, c);
+        return contentArea;
+    }
+
+    private JPanel createMemoryStatisticsPanel() {
+        JPanel contentArea = new JPanel();
+        // contentArea.setLayout(new GridBagLayout());
+        contentArea.setLayout(new BorderLayout());
+        GridBagConstraints c = new GridBagConstraints();
+        c.anchor = GridBagConstraints.FIRST_LINE_START;
+        c.fill = GridBagConstraints.HORIZONTAL;
+        c.gridx = 0;
+        c.gridy = 0;
+
+        List<Section> allSections = new ArrayList<Section>();
+
+        Section memoryBasics = new Section(_("HOST_MEMORY_SECTION_OVERVIEW"));
+        allSections.add(memoryBasics);
+
+        TableEntry entry;
+        entry = new TableEntry(_("HOST_INFO_MEMORY_TOTAL"), String.valueOf(hostInfo.getTotalMemory()));
+        memoryBasics.add(entry);
+
+
+        JPanel table = SimpleTable.createTable(allSections);
+        table.setBorder(Components.smallBorder());
+        contentArea.add(table, BorderLayout.PAGE_START);
+
+        JFreeChart chart = createMemoryChart(facade);
+
+        ChartPanel chartPanel = new ChartPanel(chart);
+        // make this chart non-interactive
+        chartPanel.setDisplayToolTips(true);
+        chartPanel.setDoubleBuffered(true);
+        chartPanel.setMouseZoomable(false);
+        chartPanel.setPopupMenu(null);
+
+        c.gridy++;
+        c.fill = GridBagConstraints.BOTH;
+        c.gridwidth = GridBagConstraints.REMAINDER;
+        c.weightx = 1;
+        c.weighty = 1;
+        contentArea.add(chartPanel, BorderLayout.CENTER);
+
+        JPanel memoryPanel = new JPanel(new WrapLayout(FlowLayout.LEADING));
+        contentArea.add(memoryPanel, BorderLayout.PAGE_END);
+
+        for (MemoryType type : MemoryType.values()) {
+            JCheckBox checkBox = new JCheckBox(type.getLabel(), facade.isMemoryTypeDisplayed(type));
+            checkBox.addActionListener(new UpdateMemoryGraph(facade, chartPanel, type));
+            memoryPanel.add(checkBox);
+        }
+
+        return contentArea;
+    }
+
+    private static JFreeChart createMemoryChart(HostInformationFacade facade) {
+        XYSeriesCollection dataset = new XYSeriesCollection();
+
+        // FIXME associate a fixed color with each type
+
+        for (MemoryType type : facade.getMemoryTypesToDisplay()) {
+            XYSeries series = new XYSeries(type.name());
+            long[][] data = facade.getMemoryUsage(type);
+            for (long[] point : data) {
+                series.add(point[0], point[1]);
+            }
+            dataset.addSeries(series);
+        }
+
+        JFreeChart chart = ChartFactory.createTimeSeriesChart(
+                _("HOST_MEMORY_CHART_TITLE"), // Title
+                _("HOST_MEMORY_CHART_TIME_LABEL"), // x-axis Label
+                _("HOST_MEMORY_CHART_SIZE_LABEL"), // y-axis Label
+                dataset, // Dataset
+                false, // Show Legend
+                false, // Use tooltips
+                false // Configure chart to generate URLs?
+                );
+        return chart;
+    }
+
+    private static class UpdateMemoryGraph implements ActionListener {
+
+        private final HostInformationFacade facade;
+        private final MemoryType type;
+        private final ChartPanel chartPanel;
+
+        public UpdateMemoryGraph(HostInformationFacade facade, ChartPanel chartPanel, MemoryType type) {
+            this.facade = facade;
+            this.chartPanel = chartPanel;
+            this.type = type;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            AbstractButton abstractButton = (AbstractButton) e.getSource();
+            boolean selected = abstractButton.getModel().isSelected();
+            facade.setDisplayMemoryType(type, selected);
+            chartPanel.setChart(createMemoryChart(facade));
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/HtmlTextBuilder.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,72 @@
+package com.redhat.thermostat.client.ui;
+
+public class HtmlTextBuilder {
+
+    /*
+     * The api provided by this class needs to be cleaned up.
+     */
+
+    private final StringBuilder text = new StringBuilder();
+
+    public HtmlTextBuilder() {
+        // do nothing
+    }
+
+    public HtmlTextBuilder(String text) {
+        text = escape(text);
+        this.text.append(text);
+    }
+
+    public HtmlTextBuilder bold(boolean on) {
+        if (on) {
+            this.text.append("<b>");
+        } else {
+            this.text.append("</b>");
+        }
+        return this;
+    }
+
+    public HtmlTextBuilder bold(String toBold) {
+        text.append("<b>").append(toBold).append("</b>");
+        return this;
+    }
+
+    public HtmlTextBuilder larger(String toAppend) {
+        text.append("<font size='+2'>").append(escape(toAppend)).append("</font>");
+        return this;
+    }
+
+    public HtmlTextBuilder huge(String toAppend) {
+        text.append("<font size='+6'>").append(escape(toAppend)).append("</font>");
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        // FIXME
+        return null;
+    }
+
+    public String toHtml() {
+        return "<html>" + text.toString() + "</html>";
+    }
+
+    public String toPartialHtml() {
+        return text.toString();
+    }
+
+    private static String escape(String text) {
+        // FIXME implement this
+        return text;
+    }
+
+    public HtmlTextBuilder append(String value) {
+        text.append(value);
+        return this;
+    }
+
+    public static String boldHtml(String toBold) {
+        return new HtmlTextBuilder().bold(toBold).toHtml();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/IconResource.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,49 @@
+package com.redhat.thermostat.client.ui;
+
+import java.io.File;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+public class IconResource {
+    /* FIXME we need to pick up the icons dynamically */
+
+    private static final String ICON_PREFIX = "/usr/share/icons/gnome/";
+
+    // an icon that should always be available and indicate that the actual icon
+    // is missing.
+    public static final IconResource MISSING_ICON = null;
+
+    public static final IconResource ERROR = new IconResource(ICON_PREFIX + "48x48/status/dialog-error.png");
+    public static final IconResource QUESTION = new IconResource(ICON_PREFIX + "256x256/status/dialog-question.png");
+    public static final IconResource WARNING = new IconResource(ICON_PREFIX + "48x48/status/dialog-warning.png");
+
+    public static final IconResource COMPUTER = new IconResource(ICON_PREFIX + "48x48/devices/computer.png");
+    public static final IconResource NETWORK_SERVER = new IconResource(ICON_PREFIX + "48x48/places/network-server.png");
+    public static final IconResource NETWORK_GROUP = new IconResource(ICON_PREFIX + "48x48/places/network-workgroup.png");
+
+    public static final IconResource SEARCH = new IconResource(ICON_PREFIX + "16x16/actions/search.png");
+
+    public static final IconResource USER_HOME = new IconResource(ICON_PREFIX + "256x256/places/user-home.png");
+
+    private final String path;
+
+    private IconResource(String descriptor) {
+        this.path = descriptor;
+    }
+
+    public Icon getIcon() {
+        if (new File(path).exists()) {
+            return new ImageIcon(path);
+        }
+        return null;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getUrl() {
+        return "file:" + getPath();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/LayoutDebugHelper.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,43 @@
+package com.redhat.thermostat.client.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Window;
+
+import javax.swing.BorderFactory;
+import javax.swing.JComponent;
+
+/**
+ * This class goes through the swing container hierarchy and adds random colored
+ * borders to the components themselves. it makes it easier to see what the
+ * {@code LayoutManager}s are doing
+ */
+public class LayoutDebugHelper {
+
+    private Color[] colors = new Color[] { Color.BLACK, Color.BLUE, Color.CYAN, Color.GREEN, Color.MAGENTA, Color.PINK, Color.ORANGE, Color.RED, Color.YELLOW };
+    private int colorIndex = 0;
+
+    public void debugLayout(Window w) {
+        Component[] children = w.getComponents();
+        debugLayout(children);
+    }
+
+    public void debugLayout(Component c) {
+        if (c instanceof JComponent) {
+            JComponent panel = (JComponent) c;
+            try {
+                panel.setBorder(BorderFactory.createLineBorder(colors[colorIndex % colors.length]));
+            } catch (IllegalArgumentException iae) {
+                // never mind then
+            }
+            colorIndex++;
+            debugLayout(panel.getComponents());
+        }
+    }
+
+    public void debugLayout(Component[] components) {
+        for (Component aComponent : components) {
+            debugLayout(aComponent);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/MainWindow.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,327 @@
+package com.redhat.thermostat.client.ui;
+
+import static com.redhat.thermostat.client.Translate._;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JLayeredPane;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JSplitPane;
+import javax.swing.JTextField;
+import javax.swing.JTree;
+import javax.swing.KeyStroke;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.plaf.synth.SynthConstants;
+import javax.swing.plaf.synth.SynthContext;
+import javax.swing.plaf.synth.SynthLookAndFeel;
+import javax.swing.plaf.synth.SynthStyle;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+import com.redhat.thermostat.client.AgentRef;
+import com.redhat.thermostat.client.ThermostatFacade;
+import com.redhat.thermostat.client.ApplicationInfo;
+import com.redhat.thermostat.client.ClientArgs;
+import com.redhat.thermostat.client.ConnectionInfo.ConnectionType;
+import com.redhat.thermostat.client.VmRef;
+
+public class MainWindow extends JFrame {
+
+    private static final long serialVersionUID = 5608972421496808177L;
+
+    private final DefaultMutableTreeNode root = new DefaultMutableTreeNode(_("MAIN_WINDOW_TREE_ROOT_NAME"));
+    private final DefaultTreeModel treeModel = new DefaultTreeModel(root);
+
+    private final ThermostatFacade facade;
+
+    private JPanel contentArea = null;
+    private JTree agentVmTree = null;
+    private JTextField searchField = null;
+
+    public MainWindow(ThermostatFacade facade) {
+        super();
+        setTitle(_("MAIN_WINDOW_TITLE"));
+
+        this.facade = facade;
+
+        searchField = new JTextField();
+        agentVmTree = new AgentVmTree(treeModel);
+        contentArea = new VerticalOnlyScrollingPanel();
+        contentArea.setLayout(new BorderLayout());
+
+        setupMenus();
+        setupPanels();
+
+        agentVmTree.setSelectionPath(new TreePath(root.getPath()));
+
+        buildTree("");
+
+        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+    }
+
+    private void setupMenus() {
+        JMenuBar mainMenuBar = new JMenuBar();
+
+        JMenu fileMenu = new JMenu(_("MENU_FILE"));
+        fileMenu.getPopupMenu().setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
+        mainMenuBar.add(fileMenu);
+
+        JMenuItem fileConnectMenu = new JMenuItem(_("MENU_FILE_CONNECT"));
+        fileConnectMenu.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                // TODO present a connection dialog
+            }
+        });
+        fileMenu.add(fileConnectMenu);
+
+        fileMenu.add(new Separator());
+
+        JMenuItem fileImportMenu = new JMenuItem(_("MENU_FILE_IMPORT"));
+        fileMenu.add(fileImportMenu);
+
+        JMenuItem fileExportMenu = new JMenuItem(_("MENU_FILE_EXPORT"));
+        fileMenu.add(fileExportMenu);
+
+        fileMenu.add(new Separator());
+
+        JMenuItem fileExitMenu = new JMenuItem(_("MENU_FILE_EXIT"));
+        fileExitMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_DOWN_MASK));
+        fileExitMenu.addActionListener(new ShtudownClient(this));
+        fileMenu.add(fileExitMenu);
+
+        JMenu helpMenu = new JMenu(_("MENU_HELP"));
+        helpMenu.getPopupMenu().setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
+        mainMenuBar.add(helpMenu);
+
+        JMenuItem helpAboutMenu = new JMenuItem(_("MENU_HELP_ABOUT"));
+        helpAboutMenu.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                AboutDialog aboutDialog = new AboutDialog(new ApplicationInfo());
+                aboutDialog.setModal(true);
+                aboutDialog.pack();
+                aboutDialog.setVisible(true);
+            }
+        });
+        helpMenu.add(helpAboutMenu);
+        setJMenuBar(mainMenuBar);
+    }
+
+    private void setupPanels() {
+        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+
+        JPanel navigationPanel = new JPanel(new BorderLayout());
+
+        JPanel searchPanel = new JPanel(new BorderLayout());
+
+        navigationPanel.add(searchPanel, BorderLayout.PAGE_START);
+
+        /* the insets are so we can place the actual icon inside the searchField */
+        searchField.setMargin(new Insets(0, 0, 0, 30));
+        searchField.getDocument().addDocumentListener(new DocumentListener() {
+            @Override
+            public void removeUpdate(DocumentEvent event) {
+                changed(event.getDocument());
+            }
+
+            @Override
+            public void insertUpdate(DocumentEvent event) {
+                changed(event.getDocument());
+            }
+
+            @Override
+            public void changedUpdate(DocumentEvent event) {
+                changed(event.getDocument());
+            }
+
+            private void changed(Document doc) {
+                String filter = null;
+                try {
+                    filter = doc.getText(0, doc.getLength());
+                } catch (BadLocationException ble) {
+                    // ignore
+                }
+                buildTree(filter);
+            }
+        });
+
+        searchPanel.add(searchField);
+        // TODO move this icon inside the search field
+        JLabel searchIcon = new JLabel(IconResource.SEARCH.getIcon());
+        searchIcon.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+        searchPanel.add(searchIcon, BorderLayout.LINE_END);
+
+        agentVmTree.addTreeSelectionListener(new TreeSelectionListener() {
+            @Override
+            public void valueChanged(TreeSelectionEvent e) {
+                if (e.isAddedPath()) {
+                    contentArea.removeAll();
+                    TreePath path = e.getPath();
+                    if (path.getPathCount() == 1) {/* root */
+                        contentArea.add(new HomePanel(facade));
+                    } else if (path.getPathCount() == 2) { /* agent */
+                        AgentRef agentRef = (AgentRef) ((DefaultMutableTreeNode) path.getLastPathComponent()).getUserObject();
+                        HostPanel panel = new HostPanel(facade.getHost(agentRef));
+                        contentArea.add(panel);
+                    } else { /* vm */
+                        AgentRef agentRef = (AgentRef) ((DefaultMutableTreeNode) path.getParentPath().getLastPathComponent()).getUserObject();
+                        VmRef vmRef = (VmRef) ((DefaultMutableTreeNode) path.getLastPathComponent()).getUserObject();
+                        VmPanel panel = new VmPanel(facade.getHost(agentRef).getVm(vmRef));
+                        contentArea.add(panel);
+                    }
+                    // Fixes some 'ghosting' caused by the previous components
+                    // to stay painted on the JViewPort
+                    ((JScrollPane) contentArea.getParent().getParent()).repaint();
+                    contentArea.revalidate();
+                }
+            }
+
+        });
+
+        JScrollPane treeScrollPane = new JScrollPane(agentVmTree);
+
+        navigationPanel.add(treeScrollPane);
+
+        JPanel detailsPanel = createDetailsPanel();
+
+        splitPane.add(navigationPanel);
+        splitPane.add(detailsPanel);
+
+        add(splitPane);
+    }
+
+    private JPanel createDetailsPanel() {
+        JPanel result = new JPanel(new BorderLayout());
+        if (ClientArgs.isDebugLayout()) {
+            contentArea.setBorder(BorderFactory.createLineBorder(Color.GREEN));
+        }
+        JScrollPane contentScrollPane = new JScrollPane(contentArea, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+        result.add(contentScrollPane, BorderLayout.CENTER);
+        if (ClientArgs.isDebugLayout()) {
+            result.setBorder(BorderFactory.createLineBorder(Color.PINK));
+        }
+        return result;
+    }
+
+    public void setStartupMode(ConnectionType type) {
+        // TODO use type to set up connection
+    }
+
+    private void buildTree(String filter) {
+        root.removeAllChildren();
+        treeModel.setRoot(null);
+        // paths to expand. only expand paths when a vm matches (to ensure it is
+        // visible)
+        List<TreeNode[]> pathsToExpand = new ArrayList<TreeNode[]>();
+        if (filter == null || filter.trim().equals("")) {
+            DefaultMutableTreeNode agentNode;
+            AgentRef[] agentRefs = facade.getConnectedAgents();
+            for (AgentRef agentRef : agentRefs) {
+                agentNode = new DefaultMutableTreeNode(agentRef);
+                root.add(agentNode);
+                VmRef[] vmRefs = facade.getHost(agentRef).getVms();
+                for (VmRef vmRef : vmRefs) {
+                    agentNode.add(new DefaultMutableTreeNode(vmRef));
+                }
+            }
+            treeModel.setRoot(root);
+        } else {
+            DefaultMutableTreeNode agentNode;
+            for (AgentRef agentRef : facade.getConnectedAgents()) {
+                if (agentRef.getName().contains(filter) || agentRef.getId().contains(filter)) {
+                    agentNode = new DefaultMutableTreeNode(agentRef);
+                    root.add(agentNode);
+                    VmRef[] vmRefs = facade.getHost(agentRef).getVms();
+                    for (VmRef vmRef : vmRefs) {
+                        agentNode.add(new DefaultMutableTreeNode(vmRef));
+                    }
+                } else {
+                    agentNode = null;
+                    for (VmRef vmRef : facade.getHost(agentRef).getVms()) {
+                        if (vmRef.getName().contains(filter) || vmRef.getId().contains(filter)) {
+                            if (agentNode == null) {
+                                agentNode = new DefaultMutableTreeNode(agentRef);
+                                root.add(agentNode);
+                            }
+                            DefaultMutableTreeNode vmNode = new DefaultMutableTreeNode(vmRef);
+                            agentNode.add(vmNode);
+                            pathsToExpand.add(vmNode.getPath());
+                        }
+                    }
+                }
+            }
+            if (root.getChildCount() > 0) {
+                treeModel.setRoot(root);
+            }
+
+        }
+        for (TreeNode[] path : pathsToExpand) {
+            agentVmTree.expandPath(new TreePath(path).getParentPath());
+        }
+        agentVmTree.expandRow(0);
+    }
+
+    public static class ShtudownClient implements ActionListener {
+
+        private JFrame toDispose;
+
+        public ShtudownClient(JFrame toDispose) {
+            this.toDispose = toDispose;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            toDispose.dispose();
+        }
+    }
+
+    private static class AgentVmTree extends JTree {
+        private static final long serialVersionUID = 8894141735861100579L;
+
+        public AgentVmTree(TreeModel model) {
+            super(model);
+            getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+        }
+    }
+
+    private static class Separator extends JPopupMenu.Separator {
+        @Override
+        public Dimension getPreferredSize() {
+            Dimension result = super.getPreferredSize();
+            if (result.height < 1) {
+                result.height = 5;
+            }
+            return result;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/SimpleTable.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,151 @@
+package com.redhat.thermostat.client.ui;
+
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.Box;
+import javax.swing.JPanel;
+
+public class SimpleTable {
+
+    public static class Section {
+        private final String sectionName;
+        private final List<TableEntry> tableEntries = new ArrayList<TableEntry>();
+
+        public Section(String name) {
+            this.sectionName = name;
+        }
+
+        public String getText() {
+            return sectionName;
+        }
+
+        public void add(TableEntry entry) {
+            tableEntries.add(entry);
+        }
+
+        public void add(Key key, List<Value> values) {
+            tableEntries.add(new TableEntry(key, values));
+        }
+
+        public void add(Key key, Value value) {
+            tableEntries.add(new TableEntry(key, value));
+        }
+
+        public TableEntry[] getEntries() {
+            return tableEntries.toArray(new TableEntry[0]);
+        }
+    }
+
+    public static class TableEntry {
+        private final Key key;
+        private final List<Value> values;
+
+        public TableEntry(String key, String value) {
+            this(new Key(key), new Value(value));
+        }
+
+        public TableEntry(Key key, Value value) {
+            this.key = key;
+            this.values = new ArrayList<Value>();
+            this.values.add(value);
+        }
+
+        public TableEntry(Key key, List<Value> values) {
+            this.key = key;
+            this.values = new ArrayList<Value>(values);
+        }
+
+        public Key getKey() {
+            return key;
+        }
+
+        public Value[] getValues() {
+            return values.toArray(new Value[0]);
+        }
+
+    }
+
+    public static class Key {
+        private final String text;
+
+        public Key(String text) {
+            this.text = text;
+        }
+
+        public String getText() {
+            return text;
+        }
+    }
+
+    public static class Value {
+        private final String text;
+
+        public Value(String text) {
+            this.text = text;
+        }
+
+        public String getText() {
+            return text;
+        }
+    }
+
+    public static JPanel createTable(List<Section> sections) {
+        final int SECTION_TOP_GAP = 10;
+        final int ROW_VERTICAL_GAP = 0;
+        final int ROW_HORIZONTAL_GAP = 10;
+
+        Insets sectionHeaderInsets = new Insets(SECTION_TOP_GAP, 0, 0, 0);
+        Insets rowInsets = new Insets(ROW_VERTICAL_GAP, ROW_HORIZONTAL_GAP, ROW_VERTICAL_GAP, ROW_HORIZONTAL_GAP);
+
+        JPanel container = new JPanel();
+        container.setLayout(new GridBagLayout());
+
+        GridBagConstraints keyConstraints = new GridBagConstraints();
+        GridBagConstraints valueConstraints = new GridBagConstraints();
+        GridBagConstraints sectionHeaderConstraints = new GridBagConstraints();
+
+        keyConstraints.insets = valueConstraints.insets = rowInsets;
+        keyConstraints.gridy = valueConstraints.gridy = 0;
+        keyConstraints.gridx = 0;
+        valueConstraints.gridx = 1;
+        keyConstraints.fill = valueConstraints.fill = GridBagConstraints.HORIZONTAL;
+
+        sectionHeaderConstraints.gridx = 0;
+        sectionHeaderConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        sectionHeaderConstraints.fill = GridBagConstraints.HORIZONTAL;
+        sectionHeaderConstraints.insets = sectionHeaderInsets;
+
+        for (Section section : sections) {
+            sectionHeaderConstraints.gridy = keyConstraints.gridy = ++valueConstraints.gridy;
+            container.add(Components.header(section.getText()), sectionHeaderConstraints);
+            for (TableEntry tableEntry : section.getEntries()) {
+                keyConstraints.gridy = ++valueConstraints.gridy;
+                container.add(Components.label(tableEntry.getKey().getText()), keyConstraints);
+
+                for (Value value : tableEntry.getValues()) {
+                    container.add(Components.value(value.getText()), valueConstraints);
+                    keyConstraints.gridy = ++valueConstraints.gridy;
+                }
+            }
+        }
+
+        GridBagConstraints glueConstraints = new GridBagConstraints();
+        glueConstraints.gridy = keyConstraints.gridy + 1;
+        glueConstraints.gridx = 0;
+        glueConstraints.weightx = 1;
+        glueConstraints.weighty = 1;
+        glueConstraints.fill = GridBagConstraints.BOTH;
+        glueConstraints.gridheight = GridBagConstraints.REMAINDER;
+        glueConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        Component filler = Box.createGlue();
+        container.add(filler, glueConstraints);
+
+        return container;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/VerticalOnlyScrollingPanel.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,52 @@
+package com.redhat.thermostat.client.ui;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+
+import javax.swing.JPanel;
+import javax.swing.JViewport;
+import javax.swing.Scrollable;
+
+/**
+ * A JPanel, that when added to a JScrollPane shows allows vertical scrolling.
+ */
+public class VerticalOnlyScrollingPanel extends JPanel implements Scrollable {
+
+    private static final long serialVersionUID = -1039658895594239585L;
+
+    public VerticalOnlyScrollingPanel() {
+        super();
+    }
+
+    @Override
+    public Dimension getPreferredScrollableViewportSize() {
+        return getPreferredSize();
+    }
+
+    @Override
+    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
+        // FIXME
+        return 5;
+    }
+
+    @Override
+    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
+        // FIXME
+        return 100;
+    }
+
+    @Override
+    public boolean getScrollableTracksViewportWidth() {
+        return true;
+    }
+
+    @Override
+    public boolean getScrollableTracksViewportHeight() {
+        if (getParent() instanceof JViewport) {
+            JViewport viewport = (JViewport) getParent();
+            return getPreferredSize().getHeight() < viewport.getHeight();
+        }
+        return false;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/VmPanel.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,207 @@
+package com.redhat.thermostat.client.ui;
+
+import static com.redhat.thermostat.client.Translate._;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.data.category.DefaultCategoryDataset;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+import com.redhat.thermostat.client.VmInformationFacade;
+import com.redhat.thermostat.client.ui.SimpleTable.Section;
+import com.redhat.thermostat.client.ui.SimpleTable.TableEntry;
+import com.redhat.thermostat.common.VmInfo;
+import com.redhat.thermostat.common.VmMemoryStat;
+import com.redhat.thermostat.common.VmMemoryStat.Generation;
+import com.redhat.thermostat.common.VmMemoryStat.Space;
+
+public class VmPanel extends JPanel {
+
+    private static final long serialVersionUID = 2816226547554943368L;
+
+    private final VmInformationFacade facade;
+
+    private final VmInfo vmInfo;
+
+    public VmPanel(VmInformationFacade facade) {
+        this.facade = facade;
+        this.vmInfo = facade.getVmInfo();
+        createUI();
+    }
+
+    public void createUI() {
+        setLayout(new BorderLayout());
+
+        JTabbedPane tabPane = new JTabbedPane();
+
+        tabPane.insertTab(_("VM_INFO_TAB_OVERVIEW"), null, createOverviewPanel(), null, 0);
+        tabPane.insertTab(_("VM_INFO_TAB_MEMORY"), null, createMemoryPanel(), null, 1);
+        tabPane.insertTab(_("VM_INFO_TAB_GC"), null, createGcPanel(), _("GARBAGE_COLLECTION"), 2);
+
+        // TODO additional tabs provided by plugins
+        // tabPane.insertTab(title, icon, component, tip, 3)
+
+        this.add(tabPane);
+    }
+
+    public JPanel createOverviewPanel() {
+        JPanel panel = new JPanel();
+        panel.setBorder(Components.smallBorder());
+        panel.setLayout(new BorderLayout());
+
+        TableEntry entry;
+        List<Section> allSections = new ArrayList<Section>();
+
+        Section processSection = new Section(_("VM_INFO_SECTION_PROCESS"));
+        allSections.add(processSection);
+
+        entry = new TableEntry(_("VM_INFO_PROCESS_ID"), String.valueOf(vmInfo.getVmPid()));
+        processSection.add(entry);
+        entry = new TableEntry(_("VM_INFO_START_TIME"), String.valueOf(vmInfo.getStartTimeStamp()));
+        processSection.add(entry);
+        entry = new TableEntry(_("VM_INFO_STOP_TIME"), String.valueOf(vmInfo.getStopTimeStamp()));
+        processSection.add(entry);
+
+        Section javaSection = new Section(_("VM_INFO_SECTION_JAVA"));
+        allSections.add(javaSection);
+
+        entry = new TableEntry(_("VM_INFO_MAIN_CLASS"), vmInfo.getMainClass());
+        javaSection.add(entry);
+        entry = new TableEntry(_("VM_INFO_COMMAND_LINE"), vmInfo.getJavaCommandLine());
+        javaSection.add(entry);
+        entry = new TableEntry(_("VM_INFO_JAVA_VERSION"), vmInfo.getJavaVersion());
+        javaSection.add(entry);
+        entry = new TableEntry(_("VM_INFO_VM"), _("VM_INFO_VM_NAME_AND_VERSION", vmInfo.getVmName(), vmInfo.getVmVersion()));
+        javaSection.add(entry);
+
+        JPanel table = SimpleTable.createTable(allSections);
+        table.setBorder(Components.smallBorder());
+        panel.add(table, BorderLayout.PAGE_START);
+
+        return panel;
+    }
+
+    private Component createMemoryPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new GridBagLayout());
+        GridBagConstraints c = new GridBagConstraints();
+        c.gridx = 0;
+        c.gridy = 0;
+        c.fill = GridBagConstraints.BOTH;
+        c.weightx = 1;
+        c.weighty = 1;
+        panel.add(createCurrentMemoryDisplay(), c);
+        c.gridy++;
+        panel.add(createMemoryHistoryPanel(), c);
+        return panel;
+    }
+
+    private Component createCurrentMemoryDisplay() {
+        DefaultCategoryDataset data = new DefaultCategoryDataset();
+
+        VmMemoryStat info = facade.getMemoryInfo();
+        List<Generation> generations = info.getGenerations();
+        for (Generation generation : generations) {
+            List<Space> spaces = generation.spaces;
+            for (Space space : spaces) {
+                data.addValue(space.used, _("VM_CURRENT_MEMORY_CHART_USED"), space.name);
+                data.addValue(space.capacity - space.used, _("VM_CURRENT_MEMORY_CHART_CAPACITY"), space.name);
+                data.addValue(space.maxCapacity - space.capacity, _("VM_CURRENT_MEMORY_CHART_MAX_CAPACITY"), space.name);
+            }
+        }
+
+        JFreeChart chart = ChartFactory.createStackedBarChart(
+                null,
+                _("VM_CURRENT_MEMORY_CHART_SPACE"),
+                _("VM_CURRENT_MEMORY_CHART_SIZE"),
+                data,
+                PlotOrientation.HORIZONTAL, true, false, false);
+
+        ChartPanel chartPanel = new ChartPanel(chart);
+        // make this chart non-interactive
+        chartPanel.setDisplayToolTips(true);
+        chartPanel.setDoubleBuffered(true);
+        chartPanel.setMouseZoomable(false);
+        chartPanel.setPopupMenu(null);
+
+        return chartPanel;
+    }
+
+    private Component createMemoryHistoryPanel() {
+        JPanel historyPanel = new JPanel();
+
+        return historyPanel;
+    }
+
+    private Component createGcPanel() {
+        JPanel gcPanel = new JPanel();
+        gcPanel.setLayout(new GridBagLayout());
+
+        GridBagConstraints c = new GridBagConstraints();
+        c.gridx = 0;
+        c.gridy = 0;
+        c.fill = GridBagConstraints.BOTH;
+        c.weightx = 1;
+        c.weighty = 1;
+
+        String[] collectorNames = facade.getCollectorNames();
+        for (int i = 0; i < collectorNames.length; i++) {
+            String collectorName = collectorNames[i];
+            gcPanel.add(createCollectorDetailsPanel(collectorName), c);
+            c.gridy++;
+        }
+
+        return gcPanel;
+    }
+
+    private Component createCollectorDetailsPanel(String collectorName) {
+        JPanel detailsPanel = new JPanel();
+        detailsPanel.setBorder(Components.smallBorder());
+        detailsPanel.setLayout(new BorderLayout());
+
+        GridBagConstraints c = new GridBagConstraints();
+        c.gridx = 0;
+        c.fill = GridBagConstraints.BOTH;
+
+        detailsPanel.add(Components.header(_("VM_GC_COLLECTOR_OVER_GENERATION", collectorName, facade.getCollectorGeneration(collectorName))), BorderLayout.NORTH);
+
+        long[][] cpuData = facade.getCollectorData(collectorName);
+        XYSeries series = new XYSeries("gc-runs");
+        for (long[] data : cpuData) {
+            series.add(data[0], data[1]);
+        }
+        XYSeriesCollection dataset = new XYSeriesCollection();
+        dataset.addSeries(series);
+        JFreeChart chart = ChartFactory.createTimeSeriesChart(
+                null,
+                _("VM_GC_COLLECTOR_CHART_REAL_TIME_LABEL"),
+                _("VM_GC_COLLECTOR_CHART_GC_TIME_LABEL"),
+                dataset,
+                false, false, false);
+
+        ChartPanel chartPanel = new ChartPanel(chart);
+        // make this chart non-interactive
+        chartPanel.setDisplayToolTips(true);
+        chartPanel.setDoubleBuffered(true);
+        chartPanel.setMouseZoomable(false);
+        chartPanel.setPopupMenu(null);
+
+        detailsPanel.add(chartPanel, BorderLayout.CENTER);
+
+        return detailsPanel;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/client/ui/WrapLayout.java	Mon Jan 09 18:27:54 2012 -0500
@@ -0,0 +1,193 @@
+/*
+ * Taken from http://tips4java.wordpress.com/2008/11/06/wrap-layout/
+ *
+ * The about page (http://tips4java.wordpress.com/about/) says this:
+ * "You are free to use and/or modify any or all code posted on the Java Tips
+ * Weblog without restriction. A credit in the code comments would be nice,
+ * but not in any way mandatory."
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import java.awt.*;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+
+/**
+ *  FlowLayout subclass that fully supports wrapping of components.
+ */
+public class WrapLayout extends FlowLayout
+{
+	private Dimension preferredLayoutSize;
+
+	/**
+	* Constructs a new <code>WrapLayout</code> with a left
+	* alignment and a default 5-unit horizontal and vertical gap.
+	*/
+	public WrapLayout()
+	{
+		super();
+	}
+
+	/**
+	* Constructs a new <code>FlowLayout</code> with the specified
+	* alignment and a default 5-unit horizontal and vertical gap.
+	* The value of the alignment argument must be one of
+	* <code>WrapLayout</code>, <code>WrapLayout</code>,
+	* or <code>WrapLayout</code>.
+	* @param align the alignment value
+	*/
+	public WrapLayout(int align)
+	{
+		super(align);
+	}
+
+	/**
+	* Creates a new flow layout manager with the indicated alignment
+	* and the indicated horizontal and vertical gaps.
+	* <p>
+	* The value of the alignment argument must be one of
+	* <code>WrapLayout</code>, <code>WrapLayout</code>,
+	* or <code>WrapLayout</code>.
+	* @param align the alignment value
+	* @param hgap the horizontal gap between components
+	* @param vgap the vertical gap between components
+	*/
+	public WrapLayout(int align, int hgap, int vgap)
+	{
+		super(align, hgap, vgap);
+	}
+
+	/**
+	* Returns the preferred dimensions for this layout given the
+	* <i>visible</i> components in the specified target container.
+	* @param target the component which needs to be laid out
+	* @return the preferred dimensions to lay out the
+	* subcomponents of the specified container
+	*/
+	@Override
+	public Dimension preferredLayoutSize(Container target)
+	{
+		return layoutSize(target, true);
+	}
+
+	/**
+	* Returns the minimum dimensions needed to layout the <i>visible</i>
+	* components contained in the specified target container.
+	* @param target the component which needs to be laid out
+	* @return the minimum dimensions to lay out the
+	* subcomponents of the specified container
+	*/
+	@Override
+	public Dimension minimumLayoutSize(Container target)
+	{
+		Dimension minimum = layoutSize(target, false);
+		minimum.width -= (getHgap() + 1);
+		return minimum;
+	}
+
+	/**
+	* Returns the minimum or preferred dimension needed to layout the target
+	* container.
+	*
+	* @param target target to get layout size for
+	* @param preferred should preferred size be calculated
+	* @return the dimension to layout the target container
+	*/
+	private Dimension layoutSize(Container target, boolean preferred)
+	{
+	synchronized (target.getTreeLock())
+	{
+		//  Each row must fit with the width allocated to the containter.
+		//  When the container width = 0, the preferred width of the container
+		//  has not yet been calculated so lets ask for the maximum.
+
+		int targetWidth = target.getSize().width;
+
+		if (targetWidth == 0)
+			targetWidth = Integer.MAX_VALUE;
+
+		int hgap = getHgap();
+		int vgap = getVgap();
+		Insets insets = target.getInsets();
+		int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
+		int maxWidth = targetWidth - horizontalInsetsAndGap;
+
+		//  Fit components into the allowed width
+
+		Dimension dim = new Dimension(0, 0);
+		int rowWidth = 0;
+		int rowHeight = 0;
+
+		int nmembers = target.getComponentCount();
+
+		for (int i = 0; i < nmembers; i++)
+		{
+			Component m = target.getComponent(i);
+
+			if (m.isVisible())
+			{
+				Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
+
+				//  Can't add the component to current row. Start a new row.
+
+				if (rowWidth + d.width > maxWidth)
+				{
+					addRow(dim, rowWidth, rowHeight);
+					rowWidth = 0;
+					rowHeight = 0;
+				}
+
+				//  Add a horizontal gap for all components after the first
+
+				if (rowWidth != 0)
+				{
+					rowWidth += hgap;
+				}
+
+				rowWidth += d.width;
+				rowHeight = Math.max(rowHeight, d.height);
+			}
+		}
+
+		addRow(dim, rowWidth, rowHeight);
+
+		dim.width += horizontalInsetsAndGap;
+		dim.height += insets.top + insets.bottom + vgap * 2;
+
+		//	When using a scroll pane or the DecoratedLookAndFeel we need to
+		//  make sure the preferred size is less than the size of the
+		//  target containter so shrinking the container size works
+		//  correctly. Removing the horizontal gap is an easy way to do this.
+
+		Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
+
+		if (scrollPane != null)
+		{
+			dim.width -= (hgap + 1);
+		}
+
+		return dim;
+	}
+	}
+
+	/*
+	 *  A new row has been completed. Use the dimensions of this row
+	 *  to update the preferred size for the container.
+	 *
+	 *  @param dim update the width and height when appropriate
+	 *  @param rowWidth the width of the row to add
+	 *  @param rowHeight the height of the row to add
+	 */
+	private void addRow(Dimension dim, int rowWidth, int rowHeight)
+	{
+		dim.width = Math.max(dim.width, rowWidth);
+
+		if (dim.height > 0)
+		{
+			dim.height += getVgap();
+		}
+
+		dim.height += rowHeight;
+	}
+}