diff --git a/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar b/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar index b16752b1b39943010cae9f23e9153028adba72d8..3e41a675a82fae11b5bf9229cb881a9529b97c50 100644 Binary files a/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar and b/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar differ diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentAnalysisCockpitProjectBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentAnalysisCockpitProjectBean.java index 73c716650995f16e06979e7a5be4104d9ca12dc3..6026779558d1c4bd3936fb3be0b26eabf1b704c1 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentAnalysisCockpitProjectBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentAnalysisCockpitProjectBean.java @@ -24,6 +24,8 @@ import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import kieker.analysis.model.analysisMetaModel.MIProject; +import kieker.analysis.model.analysisMetaModel.MIView; +import kieker.webgui.common.ACManager; import org.primefaces.model.DashboardColumn; import org.primefaces.model.DashboardModel; @@ -60,6 +62,7 @@ public class CurrentAnalysisCockpitProjectBean { * This is the actual model instance. It is the in-memory-model of the current (session) user. */ private MIProject project; + private MIView activeView; /** * Creates a new instance of this class. @@ -137,6 +140,14 @@ public class CurrentAnalysisCockpitProjectBean { } } + public String updateDisplay(final String displayName) { + try { + return ACManager.getInstance().getDisplay(this.projectName, this.activeView.getName(), displayName).toString(); + } catch (final NullPointerException ex) { + return "N/A"; + } + } + /** * This method clears the bean. In other words: The stored project is set to null and it will return the page of the project overview for navigation purposes. * @@ -150,4 +161,12 @@ public class CurrentAnalysisCockpitProjectBean { return CurrentAnalysisCockpitProjectBean.PAGE_PROJECT_OVERVIEW; } + + public MIView getActiveView() { + return this.activeView; + } + + public void setActiveView(final MIView activeView) { + this.activeView = activeView; + } } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentAnalysisControllerProjectBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentAnalysisControllerProjectBean.java index 67a3aaae27c7c5a17e0ab836758e3f2ab314571a..2e403f75e0fe1b5c0cb2b0b22f9c92c653262ae7 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentAnalysisControllerProjectBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentAnalysisControllerProjectBean.java @@ -142,6 +142,18 @@ public class CurrentAnalysisControllerProjectBean { } } + public void cleanAnalysis() { + try { + ACManager.getInstance().cleanAnalysisController(this.projectName); + } catch (final AnalysisNotInstantiatedException e) { + CurrentAnalysisControllerProjectBean.showMessage(FacesMessage.SEVERITY_WARN, "The analysis has not been instantiated yet."); + } catch (final AnalysisNotRunningException e) { + CurrentAnalysisControllerProjectBean.showMessage(FacesMessage.SEVERITY_WARN, "The analysis has not been started yet."); + } catch (final InterruptedException e) { + CurrentAnalysisControllerProjectBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occurred while cleaning the analysis."); + } + } + /** * Checks whether the analysis is currently running. * diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/ACManager.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/ACManager.java index c620180009e22e712cc5b8b6a24f97e0fc3b0de1..15fb3c3570f20d19fc48e97ebaa8a2b277366c49 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/ACManager.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/ACManager.java @@ -21,15 +21,26 @@ package kieker.webgui.common; import java.io.IOException; +import java.lang.Thread.State; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import kieker.analysis.AnalysisController; +import kieker.analysis.AnalysisController.AnalysisControllerWithMapping; import kieker.analysis.AnalysisController.STATE; import kieker.analysis.display.AbstractDisplay; +import kieker.analysis.display.PlainText; +import kieker.analysis.display.annotation.Display; import kieker.analysis.exception.AnalysisConfigurationException; +import kieker.analysis.model.analysisMetaModel.MIDisplay; +import kieker.analysis.model.analysisMetaModel.MIPlugin; import kieker.analysis.model.analysisMetaModel.MIProject; +import kieker.analysis.model.analysisMetaModel.MIView; +import kieker.analysis.plugin.AbstractPlugin; import kieker.webgui.common.exception.AnalysisNotInstantiatedException; import kieker.webgui.common.exception.AnalysisNotRunningException; import kieker.webgui.common.exception.ProjectAlreadyStartedException; @@ -42,6 +53,7 @@ import kieker.webgui.common.exception.ProjectStillRunningException; * @author Nils Christian Ehmke * @version 1.0 */ +// TODO How to differ between views and displays with same names? public final class ACManager { /** * This is the maximal time the application will wait for an analysis thread to be terminated. @@ -55,7 +67,7 @@ public final class ACManager { * This list contains the current analysis controllers and their corresponding threads. Not every project does have a controller, but every project can have * maximal one. */ - private final ConcurrentHashMap<String, Pair<AnalysisController, Thread>> analysisController = new ConcurrentHashMap<String, Pair<AnalysisController, Thread>>(); + private final ConcurrentHashMap<String, Triple<AnalysisController, Thread, UpdateDisplaysThread>> analysisController = new ConcurrentHashMap<String, Triple<AnalysisController, Thread, UpdateDisplaysThread>>(); /** * Creates a new instance of this class. @@ -84,16 +96,17 @@ public final class ACManager { * If the current thread has somehow been interrupted while waiting for the existing analysis thread. */ public void stopAnalysisController(final String project) throws AnalysisNotRunningException, InterruptedException { - final Pair<AnalysisController, Thread> currController = this.analysisController.get(project); - synchronized (currController) { - // Is there a thread available? - if ((currController.getFst() == null) || (currController.getSnd() == null)) { - throw new AnalysisNotRunningException(); + final Triple<AnalysisController, Thread, UpdateDisplaysThread> currController = this.analysisController.get(project); + try { + synchronized (currController) { + // Try to stop the analysis + currController.getFst().terminate(); + currController.getThd().terminate(); + currController.getSnd().join(ACManager.MAX_THREAD_WAIT_TIME_MS); + currController.getThd().join(ACManager.MAX_THREAD_WAIT_TIME_MS); } - - // Try to stop the analysis - currController.getFst().terminate(); - currController.getSnd().join(ACManager.MAX_THREAD_WAIT_TIME_MS); + } catch (final NullPointerException ex) { + throw new AnalysisNotRunningException(); } } @@ -108,33 +121,35 @@ public final class ACManager { * If the analysis has not yet been initialized. */ public void startAnalysisController(final String project) throws ProjectAlreadyStartedException, AnalysisNotInstantiatedException { - final Pair<AnalysisController, Thread> currController = this.analysisController.get(project); - synchronized (currController) { - // Is there an analysis after all? - if (currController.getFst() == null) { - throw new AnalysisNotInstantiatedException(); + final Triple<AnalysisController, Thread, UpdateDisplaysThread> currController = this.analysisController.get(project); + // Is there an analysis after all? + try { + synchronized (currController) { + // Check whether this analysis has already been started - or is already dead. + if (currController.getSnd().getState() != State.NEW) { + throw new ProjectAlreadyStartedException(); + } + // Otherwise start the analysis + currController.getSnd().start(); + currController.getThd().start(); } - // Check whether this analysis has already been started. - if (currController.getSnd() != null) { - throw new ProjectAlreadyStartedException(); - } else { - // Otherwise start a thread and run the analysis in it. - currController.setSnd(new Thread() { - @Override - public void run() { - try { - currController.getFst().run(); - } catch (final IllegalStateException ex) { - ex.printStackTrace(); - } catch (final AnalysisConfigurationException ex) { - ex.printStackTrace(); - } - } - }); + } catch (final NullPointerException ex) { + throw new AnalysisNotInstantiatedException(); + } + } - currController.getSnd().start(); + public void cleanAnalysisController(final String project) throws AnalysisNotInstantiatedException, AnalysisNotRunningException, InterruptedException { + final Triple<AnalysisController, Thread, UpdateDisplaysThread> currController = this.analysisController.get(project); + // Is there an analysis after all? + try { + synchronized (currController) { + this.stopAnalysisController(project); + this.analysisController.remove(project); } + } catch (final NullPointerException ex) { + throw new AnalysisNotInstantiatedException(); } + } /** @@ -153,29 +168,36 @@ public final class ACManager { */ public void instantiateAnalysisController(final String project) throws NullPointerException, AnalysisConfigurationException, IOException, ProjectStillRunningException { - // Add the potential new project atomically - final MIProject modelProject = FSManager.getInstance().openProject(project); - final ClassLoader classLoader = FSManager.getInstance().getClassLoader(project); - final Pair<AnalysisController, Thread> newController = new Pair<AnalysisController, Thread>(new AnalysisController(modelProject, classLoader), null); - final Pair<AnalysisController, Thread> currController = this.analysisController.putIfAbsent(project, newController); + final Triple<AnalysisController, Thread, UpdateDisplaysThread> newController = new Triple<AnalysisController, Thread, UpdateDisplaysThread>(); + synchronized (newController) { + // Add the potential new project atomically + final Triple<AnalysisController, Thread, UpdateDisplaysThread> currController = this.analysisController.putIfAbsent(project, newController); + // We can just add a new controller if there wasn't an old one. + if (currController != null) { + throw new ProjectStillRunningException("The project with the name '" + project + "' is still running."); + } + final MIProject modelProject = FSManager.getInstance().openProject(project); + final ClassLoader classLoader = FSManager.getInstance().getClassLoader(project); + final AnalysisControllerWithMapping controller = AnalysisController.createAnalysisController(modelProject, classLoader); - if (currController != null) { - // There is currently an analysis controller available. Decide what to do. - synchronized (currController) { - switch (currController.getFst().getState()) { - case FAILED: - case TERMINATED: - case READY: - // No problem in instantiating a new controller - this.analysisController.put(project, newController); - break; - case RUNNING: - throw new ProjectStillRunningException("The project with the name '" + project + "' is still running."); - default: - // No code necessary - break; + // Create the necessary threads for the analysis + final Thread runningThread = new Thread() { + @Override + public void run() { + try { + controller.getController().run(); + } catch (final IllegalStateException ex) { + ex.printStackTrace(); + } catch (final AnalysisConfigurationException ex) { + ex.printStackTrace(); + } } - } + }; + final UpdateDisplaysThread displayThread = new UpdateDisplaysThread(controller.getPluginMap(), modelProject); + // Put everything into our container + newController.setFst(controller.getController()); + newController.setSnd(runningThread); + newController.setThd(displayThread); } } @@ -199,6 +221,11 @@ public final class ACManager { return controllerStateString; } + public AbstractDisplay getDisplay(final String project, final String viewName, final String displayName) { + // TODO Catch exceptions + return this.analysisController.get(project).getThd().getDisplay(viewName, displayName); + } + /** * This method can be used to deliver the state of the analysis controller of the given project as a human readable string. * @@ -211,7 +238,7 @@ public final class ACManager { if (project == null) { return null; } - final Pair<AnalysisController, Thread> controller = this.analysisController.get(project); + final Triple<AnalysisController, Thread, UpdateDisplaysThread> controller = this.analysisController.get(project); final STATE controllerState; if (controller == null) { @@ -249,30 +276,72 @@ public final class ACManager { * @author Nils Christian Ehmke * @version 1.0 */ - @SuppressWarnings("unused") - private static class DisplayUpdateThread extends Thread { + private static class UpdateDisplaysThread extends Thread { /** * This is the time the thread waits between the updates. */ private static final long SLEEP_TIME_MS = 2 * 1000; + private final Map<MIPlugin, AbstractPlugin> myPluginMap; + private final MIProject myProject; + private final Map<String, Map<String, AbstractDisplay>> displayObjects = new ConcurrentHashMap<String, Map<String, AbstractDisplay>>(); + private volatile boolean terminated = false; - /** - * Default constructor. - */ - public DisplayUpdateThread() { - // No code necessary + public UpdateDisplaysThread(final Map<MIPlugin, AbstractPlugin> pluginMap, final MIProject modelProject) { + this.myPluginMap = pluginMap; + this.myProject = modelProject; + + // Initialize the hashmap and the necessary objects + for (final MIView view : this.myProject.getViews()) { + final Map<String, AbstractDisplay> viewMap = new ConcurrentHashMap<String, AbstractDisplay>(); + this.displayObjects.put(view.getName(), viewMap); + for (final MIDisplay display : view.getDisplays()) { + // TODO Use correct display object + viewMap.put(display.getName(), new PlainText()); + } + } + } + + public void terminate() { + this.terminated = true; + } + + public AbstractDisplay getDisplay(final String viewName, final String displayName) { + return this.displayObjects.get(viewName).get(displayName); } @Override public void run() { // Run until we have been interrupted - while (!Thread.interrupted()) { - // TODO Implement + while (!this.terminated) { + for (final MIView view : this.myProject.getViews()) { + final Map<String, AbstractDisplay> viewMap = this.displayObjects.get(view.getName()); + for (final MIDisplay display : view.getDisplays()) { + final AbstractDisplay displayObject = viewMap.get(display.getName()); + final AbstractPlugin pluginObject = this.myPluginMap.get(display.getParent()); + // Update the display object + final Method[] methods = pluginObject.getClass().getMethods(); + for (final Method method : methods) { + final Display displayAnnot = method.getAnnotation(Display.class); + if ((displayAnnot != null) && displayAnnot.name().equals(display.getName())) { + // We found the correct method + try { + method.invoke(pluginObject, displayObject); + } catch (final IllegalAccessException e) { + e.printStackTrace(); + } catch (final IllegalArgumentException e) { + e.printStackTrace(); + } catch (final InvocationTargetException e) { + e.printStackTrace(); + } + } + } + } + } // Wait a little bit. try { - Thread.sleep(DisplayUpdateThread.SLEEP_TIME_MS); + Thread.sleep(UpdateDisplaysThread.SLEEP_TIME_MS); } catch (final InterruptedException ex) { // We have been interrupted. Exit the thread return; diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java index 248f2f72829c496c016147aa2fb221087ebfe056..38e2a57f39ac96a8790af06adbd71896260f2869 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java @@ -79,10 +79,6 @@ public final class FSManager { * This is the name of the root-directory. */ private static final String ROOT_DIRECTORY = "data"; - /** - * This is the name of the directory for the views. - */ - private static final String VIEW_DIRECTORY = "view"; /** * This is the buffer (in bytes) used to copy files. */ @@ -146,7 +142,6 @@ public final class FSManager { final File projectDir = new File(FSManager.ROOT_DIRECTORY + File.separator + project); final File projectFile = new File(FSManager.ROOT_DIRECTORY + File.separator + project + File.separator + project + "." + FSManager.KAX_EXTENSION); final File libDir = new File(FSManager.ROOT_DIRECTORY + File.separator + project + File.separator + FSManager.LIB_DIRECTORY); - final File viewDir = new File(FSManager.ROOT_DIRECTORY + File.separator + project + File.separator + FSManager.VIEW_DIRECTORY); // We need an "empty" project in order to save it. @@ -162,17 +157,15 @@ public final class FSManager { // Create the directories final boolean projDirCreated = projectDir.mkdir(); final boolean libDirCreated = libDir.mkdir(); - final boolean viewDirCreated = viewDir.mkdir(); // The following part is only necessary to calm FindBugs... @SuppressWarnings("unused") - final boolean createResults = projDirCreated && libDirCreated && viewDirCreated; + final boolean createResults = projDirCreated && libDirCreated; // Now the empty project file try { AnalysisController.saveToFile(projectFile, emptyProject); } catch (final IOException ex) { // NOPMD (Rethrow to clean the directories) // Something went wrong. Remove the directories and files! - final boolean viewDirDeleted = viewDir.delete(); final boolean libDirDeleted = libDir.delete(); // Keep in mind that the potential remains of the file have to be deleted before the directory. final boolean projectFileDeleted = projectFile.delete(); @@ -180,7 +173,7 @@ public final class FSManager { // The following part is only necessary to calm FindBugs... @SuppressWarnings("unused") - final boolean deleteResults = viewDirDeleted && libDirDeleted && projectFileDeleted && projectDeleted; + final boolean deleteResults = libDirDeleted && projectFileDeleted && projectDeleted; // Rethrow the exception in order to inform the caller of this method throw ex; } @@ -348,27 +341,6 @@ public final class FSManager { } } - /** - * Delivers a list with all available views of the given project. - * - * @param project - * The name of the project. - * @return A list with the names of the available views. - */ - public List<String> getAllViews(final String project) { - final List<String> result = new ArrayList<String>(); - - // Get all files within the view-dir - final File[] files = new File(FSManager.ROOT_DIRECTORY + File.separator + project + File.separator + FSManager.VIEW_DIRECTORY).listFiles(); - for (final File file : files) { - if (file.isFile()) { - result.add(file.getName()); - } - } - - return result; - } - /** * Checks whether a project with the name exists on the file system. * diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/Triple.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/Triple.java new file mode 100644 index 0000000000000000000000000000000000000000..0ee851c073d5aa80bbbc4a5eac7f7bec9f2708ca --- /dev/null +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/Triple.java @@ -0,0 +1,110 @@ +package kieker.webgui.common; + +/** + * This is a simple helper class which can store three values. + * + * @author Nils Christian Ehmke + * @version 1.0 + * + * @param <F> + * The type of the first element. + * @param <S> + * The type of the second element. + * @param <T> + * The type of the third element. + */ +public class Triple<F, S, T> { + /** + * This is the first element. + */ + private F fst; + /** + * This is the second element. + */ + private S snd; + /** + * This is the third element + */ + private T thd; + + /** + * Creates a new instance of this class with null values stored for the elements. + */ + public Triple() { + // No code necessary + } + + /** + * Creates a new instance of this class using the given values. + * + * @param fst + * The first element to be stored in this object. + * @param snd + * The second element to be stored in this object. + * @param thd + * The third element to be stored in this object. + */ + public Triple(final F fst, final S snd, final T thd) { + this.fst = fst; + this.snd = snd; + this.thd = thd; + } + + /** + * Delivers the first element. + * + * @return The first element. + */ + public F getFst() { + return this.fst; + } + + /** + * Sets the first element to a new value. + * + * @param fst + * The new first element. + */ + public void setFst(final F fst) { + this.fst = fst; + } + + /** + * Delivers the second element. + * + * @return The second element. + */ + public S getSnd() { + return this.snd; + } + + /** + * Sets the second element to a new value. + * + * @param snd + * The new second element. + */ + public void setSnd(final S snd) { + this.snd = snd; + } + + /** + * Delivers the third element. + * + * @return The third element. + */ + public T getThd() { + return this.thd; + } + + /** + * Sets the third element to a new value. + * + * @param thd + * The new third element. + */ + public void setThd(final T thd) { + this.thd = thd; + } + +} diff --git a/Kieker.WebGUI/src/main/webapp/AnalysisCockpit.xhtml b/Kieker.WebGUI/src/main/webapp/AnalysisCockpit.xhtml index 0819d08d575b55e746f5a9b622b126a929928e79..5f703d19e7754ab5001b24e957a2cbd3df299b2a 100644 --- a/Kieker.WebGUI/src/main/webapp/AnalysisCockpit.xhtml +++ b/Kieker.WebGUI/src/main/webapp/AnalysisCockpit.xhtml @@ -36,7 +36,7 @@ <p:menuitem value="Controller"/> <p:menuitem value="Cockpit" disabled="true"/> </p:submenu> - + <p:submenu label="Help"> <p:menuitem value="User Guide" ajax="true" disabled="true"/> <p:separator/> @@ -51,24 +51,24 @@ <p:layoutUnit position="center" id="centerLayout"> - <p:dashboard id="board" model="#{currentAnalysisCockpitProjectBean.model}"> - <p:panel id="panel1" header="N/A" toggleable="true"> - <h:outputText value="N/A" /> - </p:panel> - <p:panel id="panel2" header="N/A" toggleable="true"> - <h:outputText value="N/A" /> - </p:panel> - </p:dashboard> + <h:form id="centerForm"> + <ui:repeat value="#{currentAnalysisCockpitProjectBean.activeView.displays}" var="display"> + <p:panel header="#{display.name}"> + <h:outputText value="#{currentAnalysisCockpitProjectBean.updateDisplay(display.name)}"/> + </p:panel> + </ui:repeat> + </h:form> </p:layoutUnit> <p:layoutUnit position="west" size="300" header="Views" resizable="true" collapsible="true"> <h:form id="viewsForm"> + <p:poll interval="1" update=":centerForm"/> <p:accordionPanel multiple="true" activeIndex="" value="#{currentAnalysisCockpitProjectBean.project.views}" var="currView"> <p:tab title="#{currView.name}"> <h:outputText value="#{currView.description}" rendered="#{not empty currView.description}"/> <h:outputText value="No description available." rendered="#{empty currView.description}"/> - <hr/> - Click <h:commandLink value="here"/> to activate this view. + <hr/> + Click <h:commandLink value="here" action="#{currentAnalysisCockpitProjectBean.setActiveView(currView)}"/> to activate this view. </p:tab> </p:accordionPanel> </h:form> diff --git a/Kieker.WebGUI/src/main/webapp/AnalysisController.xhtml b/Kieker.WebGUI/src/main/webapp/AnalysisController.xhtml index 4ee2df749671e1629340e1a8a91946995be322d3..5635915ff107b4cb3ca05a8ad8522a5ad9592ef7 100644 --- a/Kieker.WebGUI/src/main/webapp/AnalysisController.xhtml +++ b/Kieker.WebGUI/src/main/webapp/AnalysisController.xhtml @@ -36,7 +36,7 @@ <p:menuitem value="Controller" disabled="true"/> <p:menuitem value="Cockpit"/> </p:submenu> - + <p:submenu label="Help"> <p:menuitem value="User Guide" ajax="true" disabled="true"/> <p:separator/> @@ -56,8 +56,10 @@ <p:layoutUnit position="south" header="Control" resizable="true" collapsible="true"> <h:form id="controllerForm"> <p:commandButton value="Instantiate Analysis Controller" action="#{currentAnalysisControllerProjectBean.instantiateAnalysis()}" update=":messages" disabled="#{empty currentAnalysisControllerProjectBean.projectName}"/> + <p:commandButton value="Clean Analysis" action="#{currentAnalysisControllerProjectBean.cleanAnalysis()}" update=":messages" disabled="#{empty currentAnalysisControllerProjectBean.projectName}"/> <p:commandButton value="Start Analysis" action="#{currentAnalysisControllerProjectBean.startAnalysis()}" update=":messages" disabled="#{empty currentAnalysisControllerProjectBean.projectName}"/> <p:commandButton value="Stop Analysis" action="#{currentAnalysisControllerProjectBean.stopAnalysis()}" update=":messages" disabled="#{empty currentAnalysisControllerProjectBean.projectName}"/> + <p:poll interval="1" update=":ledsForm"/> </h:form> <hr/> <h:form id="ledsForm"> @@ -83,8 +85,8 @@ <p:tooltip for="iconLEDGreen_2" value="Indicates that the AnalysisController has been started and is running."/> <p:tooltip for="iconLEDRed2_2" value="Indicates that the AnalysisController has been terminated or has failed."/> </div> - <p:poll interval="1" update="ledsForm"/> </h:form> + </p:layoutUnit> </p:layout>