Browse Source

daemon demo wip

Martin Kunz 4 years ago
parent
commit
fba1b256b8

+ 19 - 0
.idea/$PRODUCT_WORKSPACE_FILE$

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="masterDetails">
+    <states>
+      <state key="ProjectJDKs.UI">
+        <settings>
+          <last-edited>1.8</last-edited>
+          <splitter-proportions>
+            <option name="proportions">
+              <list>
+                <option value="0.2" />
+              </list>
+            </option>
+          </splitter-proportions>
+        </settings>
+      </state>
+    </states>
+  </component>
+</project>

+ 16 - 0
.idea/compiler.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <annotationProcessing>
+      <profile name="Maven default annotation processors profile" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <module name="uraxis" />
+      </profile>
+    </annotationProcessing>
+    <bytecodeTargetLevel>
+      <module name="uraxis" target="1.7" />
+    </bytecodeTargetLevel>
+  </component>
+</project>

+ 6 - 0
.idea/encodings.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$" charset="UTF-8" />
+  </component>
+</project>

+ 16 - 0
.idea/misc.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="FrameworkDetectionExcludesConfiguration">
+    <file type="Osmorc" url="file://$PROJECT_DIR$" />
+  </component>
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_12" project-jdk-name="12" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/classes" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/uraxis.iml" filepath="$PROJECT_DIR$/uraxis.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 6 - 5
pom.xml

@@ -28,7 +28,7 @@
 		<!--********************************************************************-->
 
 		<!-- Host, username and password of the robot to be used when running "mvn install -Premote" -->
-		<urcap.install.host>localhost</urcap.install.host>
+		<urcap.install.host>localhost:2222</urcap.install.host>
 		<urcap.install.username>root</urcap.install.username>
 		<urcap.install.password>easybot</urcap.install.password>
 
@@ -39,6 +39,7 @@
 		<ursimvm.install.host></ursimvm.install.host>
 		<ursimvm.install.username>ur</ursimvm.install.username>
 		<ursimvm.install.password>easybot</ursimvm.install.password>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 	</properties>
 
 	<build>
@@ -48,8 +49,8 @@
 				<artifactId>maven-compiler-plugin</artifactId>
 				<version>3.6.0</version>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+					<source>1.7</source>
+					<target>1.7</target>
 				</configuration>
 			</plugin>
 			<plugin>
@@ -114,7 +115,7 @@
 			<plugin>
 				<groupId>org.codehaus.mojo</groupId>
 				<artifactId>exec-maven-plugin</artifactId>
-				<version>1.1</version>
+				<version>1.6.0</version>
 				<executions>
 					<!-- generate URCap package after compiling -->
 					<execution>
@@ -124,7 +125,7 @@
 							<goal>exec</goal>
 						</goals>
 						<configuration>
-							<executable>cp</executable>
+							<executable>copy</executable>
 								<commandlineArgs>target/${project.build.finalName}.jar target/${project.build.finalName}.urcap</commandlineArgs>
 							<workingDirectory>.</workingDirectory>
 						</configuration>

+ 10 - 1
src/main/java/uraxis/impl/Activator.java

@@ -2,6 +2,9 @@ package uraxis.impl;
 
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
+import com.ur.urcap.api.contribution.InstallationNodeService;
+import com.ur.urcap.api.contribution.ProgramNodeService;
+import com.ur.urcap.api.contribution.DaemonService;
 
 /**
  * Hello world activator for the OSGi bundle URCAPS contribution
@@ -9,8 +12,14 @@ import org.osgi.framework.BundleContext;
  */
 public class Activator implements BundleActivator {
 	@Override
-	public void start(BundleContext bundleContext) throws Exception {
+	public void start(BundleContext context) throws Exception {
 		System.out.println("Activator says Hello World!");
+		MyDaemonDaemonService daemonService = new MyDaemonDaemonService();
+		MyDaemonInstallationNodeService installationNodeService = new MyDaemonInstallationNodeService(daemonService);
+
+		context.registerService(InstallationNodeService.class, installationNodeService, null);
+		context.registerService(ProgramNodeService.class, new MyDaemonProgramNodeService(), null);
+		context.registerService(DaemonService.class, daemonService, null);
 	}
 
 	@Override

+ 40 - 0
src/main/java/uraxis/impl/MyDaemonDaemonService.java

@@ -0,0 +1,40 @@
+package uraxis.impl;
+
+import com.ur.urcap.api.contribution.DaemonContribution;
+import com.ur.urcap.api.contribution.DaemonService;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+
+public class MyDaemonDaemonService implements DaemonService {
+
+	private DaemonContribution daemonContribution;
+
+	public MyDaemonDaemonService() {
+	}
+
+	@Override
+	public void init(DaemonContribution daemonContribution) {
+		this.daemonContribution = daemonContribution;
+		try {
+			daemonContribution.installResource(new URL("file:com/ur/urcap/examples/mydaemon/impl/daemon/"));
+		} catch (MalformedURLException e) {	}
+	}
+
+	@Override
+	public URL getExecutable() {
+		try {
+			// Two equivalent example daemons are available:
+			return new URL("file:com/ur/urcap/examples/mydaemon/impl/daemon/hello-world.py"); // Python executable
+			// return new URL("file:com/ur/urcap/examples/mydaemon/impl/daemon/HelloWorld"); // C++ executable
+		} catch (MalformedURLException e) {
+			return null;
+		}
+	}
+
+	public DaemonContribution getDaemon() {
+		return daemonContribution;
+	}
+
+}

+ 215 - 0
src/main/java/uraxis/impl/MyDaemonInstallationNodeContribution.java

@@ -0,0 +1,215 @@
+package uraxis.impl;
+
+import com.ur.urcap.api.contribution.DaemonContribution;
+import com.ur.urcap.api.contribution.InstallationNodeContribution;
+import com.ur.urcap.api.domain.data.DataModel;
+import com.ur.urcap.api.domain.script.ScriptWriter;
+import com.ur.urcap.api.ui.annotation.Input;
+import com.ur.urcap.api.ui.annotation.Label;
+import com.ur.urcap.api.ui.component.InputButton;
+import com.ur.urcap.api.ui.component.InputEvent;
+import com.ur.urcap.api.ui.component.InputTextField;
+import com.ur.urcap.api.ui.component.LabelComponent;
+
+import java.awt.EventQueue;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class MyDaemonInstallationNodeContribution implements InstallationNodeContribution {
+	private static final String POPUPTITLE_KEY = "popuptitle";
+
+	private static final String XMLRPC_VARIABLE = "my_daemon";
+	private static final String ENABLED_KEY = "enabled";
+	private static final String DEFAULT_VALUE = "HelloWorld";
+
+	private DataModel model;
+	private final MyDaemonDaemonService daemonService;
+	// private XmlRpcMyDaemonInterface xmlRpcDaemonInterface;
+	private MyDaemonInterface daemonInterface;
+
+	private Timer uiTimer;
+
+	public MyDaemonInstallationNodeContribution(MyDaemonDaemonService daemonService, DataModel model) {
+		this.daemonService = daemonService;
+		this.model = model;
+		daemonInterface = new MyDaemonInterface("127.0.0.1", 40404);
+		applyDesiredDaemonStatus();
+	}
+
+	@Input(id = POPUPTITLE_KEY)
+	private InputTextField popupTitleField;
+
+	@Input(id = "btnEnableDaemon")
+	private InputButton enableDaemonButton;
+
+	@Input(id = "btnDisableDaemon")
+	private InputButton disableDaemonButton;
+
+	@Label(id = "lblDaemonStatus")
+	private LabelComponent daemonStatusLabel;
+
+	@Input(id = POPUPTITLE_KEY)
+	public void onMessageChange(InputEvent event) {
+		if (event.getEventType() == InputEvent.EventType.ON_CHANGE) {
+			setPopupTitle(popupTitleField.getText());
+		}
+	}
+
+	@Input(id = "btnEnableDaemon")
+	public void onStartClick(InputEvent event) {
+		if (event.getEventType() == InputEvent.EventType.ON_CHANGE) {
+			setDaemonEnabled(true);
+			applyDesiredDaemonStatus();
+		}
+	}
+
+	@Input(id = "btnDisableDaemon")
+	public void onStopClick(InputEvent event) {
+		if (event.getEventType() == InputEvent.EventType.ON_CHANGE) {
+			setDaemonEnabled(false);
+			applyDesiredDaemonStatus();
+		}
+	}
+
+	@Override
+	public void openView() {
+		enableDaemonButton.setText("Start Daemon");
+		disableDaemonButton.setText("Stop daemon");
+		popupTitleField.setText(getPopupTitle());
+
+		//UI updates from non-GUI threads must use EventQueue.invokeLater (or SwingUtilities.invokeLater)
+		uiTimer = new Timer(true);
+		uiTimer.schedule(new TimerTask() {
+			@Override
+			public void run() {
+				EventQueue.invokeLater(new Runnable() {
+					@Override
+					public void run() {
+						updateUI();
+					}
+				});
+			}
+		}, 0, 1000);
+	}
+
+	private void updateUI() {
+		DaemonContribution.State state = getDaemonState();
+
+		if (state == DaemonContribution.State.RUNNING) {
+			enableDaemonButton.setEnabled(false);
+			disableDaemonButton.setEnabled(true);
+		} else {
+			enableDaemonButton.setEnabled(true);
+			disableDaemonButton.setEnabled(false);
+		}
+
+		String text = "";
+		switch (state) {
+		case RUNNING:
+			text = "My Daemon runs";
+			break;
+		case STOPPED:
+			text = "My Daemon stopped";
+			break;
+		case ERROR:
+			text = "My Daemon failed";
+			break;
+		}
+		daemonStatusLabel.setText(text);
+	}
+
+	@Override
+	public void closeView() {
+		if (uiTimer != null) {
+			uiTimer.cancel();
+		}
+	}
+
+	public boolean isDefined() {
+		return !getPopupTitle().isEmpty() && getDaemonState() == DaemonContribution.State.RUNNING;
+	}
+
+	@Override
+	public void generateScript(ScriptWriter writer) {
+		writer.globalVariable(XMLRPC_VARIABLE, "rpc_factory(\"xmlrpc\", \"http://127.0.0.1:40404/RPC2\")");
+		// Apply the settings to the daemon on program start in the Installation pre-amble
+		writer.appendLine(XMLRPC_VARIABLE + ".set_title(\"" + getPopupTitle() + "\")");
+	}
+
+	public String getPopupTitle() {
+		if (!model.isSet(POPUPTITLE_KEY)) {
+			resetToDefaultValue();
+		}
+		return model.get(POPUPTITLE_KEY, DEFAULT_VALUE);
+	}
+
+	private void setPopupTitle(String title) {
+		if ("".equals(title)) {
+			resetToDefaultValue();
+		} else {
+			model.set(POPUPTITLE_KEY, title);
+			// Apply the new setting to the daemon for real-time preview purposes
+			// Note this might influence a running program, since the actual state is stored in the daemon.
+			setDaemonTitle(title);
+		}
+	}
+
+	private void resetToDefaultValue() {
+		popupTitleField.setText(DEFAULT_VALUE);
+		model.set(POPUPTITLE_KEY, DEFAULT_VALUE);
+		setDaemonTitle(DEFAULT_VALUE);
+	}
+
+	private void setDaemonTitle(String title) {
+		try {
+			daemonInterface.setTitle(title);
+		} catch(Exception e){
+			System.err.println("Could not set the title in the daemon process.");
+		}
+	}
+
+	private void applyDesiredDaemonStatus() {
+		new Thread(new Runnable() {
+			@Override
+			public void run() {
+				if (isDaemonEnabled()) {
+					// Download the daemon settings to the daemon process on initial start for real-time preview purposes
+					try {
+						awaitDaemonRunning(5000);
+						daemonInterface.setTitle(getPopupTitle());
+					} catch (Exception e) {
+						System.err.println("Could not set the title in the daemon process.");
+					}
+				} else {
+					daemonService.getDaemon().stop();
+				}
+			}
+		}).start();
+	}
+
+	private void awaitDaemonRunning(long timeOutMilliSeconds) throws InterruptedException {
+		daemonService.getDaemon().start();
+		long endTime = System.nanoTime() + timeOutMilliSeconds * 1000L * 1000L;
+		while(System.nanoTime() < endTime && (daemonService.getDaemon().getState() != DaemonContribution.State.RUNNING || !daemonInterface.isReachable())) {
+			Thread.sleep(100);
+		}
+	}
+
+	private DaemonContribution.State getDaemonState(){
+		return daemonService.getDaemon().getState();
+	}
+
+	private Boolean isDaemonEnabled() {
+		return model.get(ENABLED_KEY, true); //This daemon is enabled by default
+	}
+
+	private void setDaemonEnabled(Boolean enable) {
+		model.set(ENABLED_KEY, enable);
+	}
+
+	public String getXMLRPCVariable(){
+		return XMLRPC_VARIABLE;
+	}
+
+	public MyDaemonInterface getDaemonInterface() {return daemonInterface; }
+}

+ 34 - 0
src/main/java/uraxis/impl/MyDaemonInstallationNodeService.java

@@ -0,0 +1,34 @@
+package uraxis.impl;
+
+import com.ur.urcap.api.contribution.InstallationNodeContribution;
+import com.ur.urcap.api.contribution.InstallationNodeService;
+import com.ur.urcap.api.domain.URCapAPI;
+
+import java.io.InputStream;
+
+import com.ur.urcap.api.domain.data.DataModel;
+
+public class MyDaemonInstallationNodeService implements InstallationNodeService {
+
+	private final MyDaemonDaemonService daemonService;
+
+	public MyDaemonInstallationNodeService(MyDaemonDaemonService daemonService) {
+		this.daemonService = daemonService;
+	}
+
+	@Override
+	public InstallationNodeContribution createInstallationNode(URCapAPI api, DataModel model) {
+		return new MyDaemonInstallationNodeContribution(daemonService, model);
+	}
+
+	@Override
+	public String getTitle() {
+		return "My Daemon";
+	}
+
+	@Override
+	public InputStream getHTML() {
+		InputStream is = this.getClass().getResourceAsStream("/com/ur/urcap/examples/mydaemon/impl/installation.html");
+		return is;
+	}
+}

+ 61 - 0
src/main/java/uraxis/impl/MyDaemonInterface.java

@@ -0,0 +1,61 @@
+package uraxis.impl;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+
+public class MyDaemonInterface {
+
+	public MyDaemonInterface(String host, int port) {
+//		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
+//		config.setEnabledForExtensions(true);
+//		try {
+//			config.setServerURL(new URL("http://" + host + ":" + port + "/RPC2"));
+//		} catch (MalformedURLException e) {
+//			e.printStackTrace();
+//		}
+//		config.setConnectionTimeout(1000); //1s
+//		client = new XmlRpcClient();
+//		client.setConfig(config);
+	}
+
+	public boolean isReachable() {
+//		try {
+//			client.execute("get_title", new ArrayList<String>());
+//			return true;
+//		} catch (XmlRpcException e) {
+//			return false;
+//		}
+		return true;
+	}
+
+	public void setTitle(String title) {
+	}
+
+	public String getTitle() {
+		return "";
+	}
+
+	public String getMessage(String name) {
+		return "";
+	}
+
+//	public String getTitle() throws XmlRpcException, UnknownResponseException {
+//		Object result = client.execute("get_title", new ArrayList<String>());
+//		return processString(result);
+//	}
+//
+//	public String setTitle(String title) throws XmlRpcException, UnknownResponseException {
+//		ArrayList<String> args = new ArrayList<String>();
+//		args.add(title);
+//		Object result = client.execute("set_title", args);
+//		return processString(result);
+//	}
+//
+//	public String getMessage(String name) throws XmlRpcException, UnknownResponseException {
+//		ArrayList<String> args = new ArrayList<String>();
+//		args.add(name);
+//		Object result = client.execute("get_message", args);
+//		return processString(result);
+//	}
+}

+ 122 - 0
src/main/java/uraxis/impl/MyDaemonProgramNodeContribution.java

@@ -0,0 +1,122 @@
+package uraxis.impl;
+
+import com.ur.urcap.api.contribution.ProgramNodeContribution;
+import com.ur.urcap.api.domain.URCapAPI;
+import com.ur.urcap.api.domain.data.DataModel;
+import com.ur.urcap.api.domain.script.ScriptWriter;
+import com.ur.urcap.api.ui.annotation.Input;
+import com.ur.urcap.api.ui.annotation.Label;
+import com.ur.urcap.api.ui.component.InputEvent;
+import com.ur.urcap.api.ui.component.InputTextField;
+import com.ur.urcap.api.ui.component.LabelComponent;
+
+import java.awt.*;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class MyDaemonProgramNodeContribution implements ProgramNodeContribution {
+	private static final String NAME = "name";
+
+	private final DataModel model;
+	private final URCapAPI api;
+	private Timer uiTimer;
+
+	public MyDaemonProgramNodeContribution(URCapAPI api, DataModel model) {
+		this.api = api;
+		this.model = model;
+	}
+
+	@Input(id = "yourname")
+	private InputTextField nameTextField;
+
+	@Label(id = "titlePreviewLabel")
+	private LabelComponent titlePreviewLabel;
+
+	@Label(id = "messagePreviewLabel")
+	private LabelComponent messagePreviewLabel;
+
+	@Input(id = "yourname")
+	public void onInput(InputEvent event) {
+		if (event.getEventType() == InputEvent.EventType.ON_CHANGE) {
+			setName(nameTextField.getText());
+			updatePreview();
+		}
+	}
+
+	@Override
+	public void openView() {
+		nameTextField.setText(getName());
+
+		//UI updates from non-GUI threads must use EventQueue.invokeLater (or SwingUtilities.invokeLater)
+		uiTimer = new Timer(true);
+		uiTimer.schedule(new TimerTask() {
+			@Override
+			public void run() {
+				EventQueue.invokeLater(new Runnable() {
+					@Override
+					public void run() {
+						updatePreview();
+					}
+				});
+			}
+		}, 0, 1000);
+	}
+
+	@Override
+	public void closeView() {
+		uiTimer.cancel();
+	}
+
+	@Override
+	public String getTitle() {
+		return "My Daemon: " + (model.isSet(NAME) ? getName() : "");
+	}
+
+	@Override
+	public boolean isDefined() {
+		return getInstallation().isDefined() && !getName().isEmpty();
+	}
+
+	@Override
+	public void generateScript(ScriptWriter writer) {
+		// Interact with the daemon process through XML-RPC calls
+		// Note, alternatively plain sockets can be used.
+		writer.assign("mydaemon_message", getInstallation().getXMLRPCVariable() + ".get_message(\"" + getName() + "\")");
+		writer.assign("mydaemon_title", getInstallation().getXMLRPCVariable() + ".get_title()");
+		writer.appendLine("popup(mydaemon_message, mydaemon_title, False, False, blocking=True)");
+		writer.writeChildren();
+	}
+
+	private void updatePreview() {
+		String title = "";
+		String message = "";
+		try {
+			// Provide a real-time preview of the daemon state
+			title = getInstallation().getDaemonInterface().getTitle();
+			message = getInstallation().getDaemonInterface().getMessage(getName());
+		} catch (Exception e) {
+			System.err.println("Could not retrieve essential data from the daemon process for the preview.");
+			title = message = "<Daemon disconnected>";
+		}
+
+		titlePreviewLabel.setText(title);
+		messagePreviewLabel.setText(message);
+	}
+
+	private String getName() {
+		return model.get(NAME, "");
+	}
+
+	private void setName(String name) {
+		if ("".equals(name)){
+			model.remove(NAME);
+		}else{
+			model.set(NAME, name);
+		}
+	}
+
+	private MyDaemonInstallationNodeContribution getInstallation(){
+		return api.getInstallationNode(MyDaemonInstallationNodeContribution.class);
+	}
+
+}

+ 45 - 0
src/main/java/uraxis/impl/MyDaemonProgramNodeService.java

@@ -0,0 +1,45 @@
+package uraxis.impl;
+
+import com.ur.urcap.api.contribution.ProgramNodeContribution;
+import com.ur.urcap.api.contribution.ProgramNodeService;
+import com.ur.urcap.api.domain.URCapAPI;
+import com.ur.urcap.api.domain.data.DataModel;
+
+import java.io.InputStream;
+
+public class MyDaemonProgramNodeService implements ProgramNodeService {
+
+	public MyDaemonProgramNodeService() {
+	}
+
+	@Override
+	public String getId() {
+		return "MyDaemonNode";
+	}
+
+	@Override
+	public String getTitle() {
+		return "My Daemon";
+	}
+
+	@Override
+	public InputStream getHTML() {
+		InputStream is = this.getClass().getResourceAsStream("/com/ur/urcap/examples/mydaemon/impl/programnode.html");
+		return is;
+	}
+
+	@Override
+	public boolean isDeprecated() {
+		return false;
+	}
+
+	@Override
+	public boolean isChildrenAllowed() {
+		return true;
+	}
+
+	@Override
+	public ProgramNodeContribution createNode(URCapAPI api, DataModel model) {
+		return new MyDaemonProgramNodeContribution(api, model);
+	}
+}

+ 4 - 0
src/main/java/uraxis/impl/UnknownResponseException.java

@@ -0,0 +1,4 @@
+package uraxis.impl;
+
+public class UnknownResponseException extends Exception {
+}

+ 38 - 0
src/main/resources/com/ur/urcap/examples/mydaemon/impl/daemon/hello-world.py

@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+import time
+import sys
+
+import xmlrpclib
+from SimpleXMLRPCServer import SimpleXMLRPCServer
+
+title = ""
+
+def set_title(new_title):
+	global title
+	title = new_title
+	return title
+
+def get_title():
+	tmp = ""
+	if str(title):
+		tmp = title
+	else:
+		tmp = "No title set"
+	return tmp + " (Python)"
+
+def get_message(name):
+	if str(name):
+		return "Hello " + str(name) + ", welcome to PolyScope!"
+	else:
+		return "No name set"
+
+sys.stdout.write("MyDaemon daemon started")
+sys.stderr.write("MyDaemon daemon started")
+
+server = SimpleXMLRPCServer(("127.0.0.1", 40404))
+server.register_function(set_title, "set_title")
+server.register_function(get_title, "get_title")
+server.register_function(get_message, "get_message")
+server.serve_forever()
+

+ 32 - 0
src/main/resources/com/ur/urcap/examples/mydaemon/impl/installation.html

@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>My Daemon</title>
+		<style>
+			label {
+				display: inline-block;
+				width: 100px;
+				height: 28px;
+			}
+
+			input {
+				display: inline-block;
+				width: 200px;
+				height: 28px;
+			}
+		</style>
+	</head>
+	<body>
+		<h1>My Daemon</h1>
+		<form>
+			<p>The popup title below is shared between all MyDaemon program nodes through the driver.<br />Cannot be empty.</p>
+			<br />
+			<label>Popup title:</label><input id="popuptitle" type="text"/>
+			<div class="spacer">&nbsp;</div>		
+			<input id="btnEnableDaemon" type="button" style="font-size: 18px;" />
+			<input id="btnDisableDaemon" type="button" style="font-size: 18px;" />
+			<div>&nbsp;</div>
+			<label id="lblDaemonStatus" style="width: 200px;">Status of daemon</label>
+		</form>
+	</body>
+</html>

+ 40 - 0
src/main/resources/com/ur/urcap/examples/mydaemon/impl/programnode.html

@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>MyDaemon</title>
+		<style>
+			input {
+				display: inline-block;
+				width: 200px;
+				height: 28px;
+			}
+
+			label {
+				display: inline-block;
+				width: 150px;
+				height: 28px;
+			}
+
+			#preview {
+				display: block;
+				padding: 35px 20px 20px 0px;
+			}
+
+			#header {
+				padding: 0px 0px 20px 0px;
+			}
+		</style>
+	</head>
+	<body>
+		<form>
+			<p>This program node will open a popup on execution.</p><br \>
+			<label>Enter your name:</label> <input id="yourname" type="text"/>
+			<br/>
+			<div id="preview">
+				<h3 id="header">Preview</h3><br \>
+				Title: <label id="titlePreviewLabel" style="width: 400px;"/> <br \>
+				Message: <label id="messagePreviewLabel" style="width: 400px;"/>
+			</div>
+		</form>
+	</body>
+</html>

File diff suppressed because it is too large
+ 37 - 0
uraxis.iml