summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pom.xml82
-rw-r--r--src/main/java/de/bwlehrpool/bwlp_guac/AvailableClient.java14
-rw-r--r--src/main/java/de/bwlehrpool/bwlp_guac/BwlpAuthenticationProvider.java92
-rw-r--r--src/main/java/de/bwlehrpool/bwlp_guac/BwlpUserContext.java11
-rw-r--r--src/main/java/de/bwlehrpool/bwlp_guac/ConnectionManager.java116
-rw-r--r--src/main/java/de/bwlehrpool/bwlp_guac/JsonLocation.java34
-rw-r--r--src/main/java/de/bwlehrpool/bwlp_guac/LocationField.java26
-rw-r--r--src/main/resources/bwlpModule.js5
-rw-r--r--src/main/resources/config/locationConfig.js11
-rw-r--r--src/main/resources/controllers/locationFieldController.js23
-rw-r--r--src/main/resources/guac-manifest.json14
-rw-r--r--src/main/resources/styles/locationField.css47
-rw-r--r--src/main/resources/templates/locationField.html18
-rw-r--r--src/main/resources/translations/en.json5
14 files changed, 463 insertions, 35 deletions
diff --git a/pom.xml b/pom.xml
index f4c361f..559638e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,6 +1,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.bwlehrpool</groupId>
@@ -26,6 +26,77 @@
<target>1.7</target>
</configuration>
</plugin>
+
+ <!-- Pre-cache Angular templates with maven-angular-plugin -->
+ <plugin>
+ <groupId>com.keithbranton.mojo</groupId>
+ <artifactId>angular-maven-plugin</artifactId>
+ <version>0.3.2</version>
+ <executions>
+ <execution>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>html2js</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <sourceDir>${basedir}/src/main/resources</sourceDir>
+ <include>**/*.html</include>
+ <target>${basedir}/src/main/resources/generated/templates-main/templates.js</target>
+ <prefix>app/ext/bwlp</prefix>
+ </configuration>
+ </plugin>
+
+ <!-- JS/CSS Minification Plugin -->
+ <plugin>
+ <groupId>com.samaxes.maven</groupId>
+ <artifactId>minify-maven-plugin</artifactId>
+ <version>1.7.5</version>
+ <executions>
+ <execution>
+ <id>default-cli</id>
+ <configuration>
+ <charset>UTF-8</charset>
+
+ <webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
+ <webappTargetDir>${project.build.directory}/classes</webappTargetDir>
+
+ <cssSourceDir>/</cssSourceDir>
+ <cssTargetDir>/</cssTargetDir>
+ <cssFinalFile>bwlp.css</cssFinalFile>
+
+ <cssSourceIncludes>
+ <cssSourceInclude>**/*.css</cssSourceInclude>
+ </cssSourceIncludes>
+
+ <jsSourceDir>/</jsSourceDir>
+ <jsTargetDir>/</jsTargetDir>
+ <jsFinalFile>bwlp.js</jsFinalFile>
+
+ <jsSourceIncludes>
+ <jsSourceInclude>**/*.js</jsSourceInclude>
+ </jsSourceIncludes>
+
+ <!-- Do not minify and include tests -->
+ <jsSourceExcludes>
+ <jsSourceExclude>**/*.test.js</jsSourceExclude>
+ </jsSourceExcludes>
+ <jsEngine>CLOSURE</jsEngine>
+
+ <!-- Disable warnings for JSDoc annotations -->
+ <closureWarningLevels>
+ <misplacedTypeAnnotation>OFF</misplacedTypeAnnotation>
+ <nonStandardJsDocs>OFF</nonStandardJsDocs>
+ </closureWarningLevels>
+
+ </configuration>
+ <goals>
+ <goal>minify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
@@ -41,6 +112,11 @@
<artifactId>guacamole-ext</artifactId>
<version>1.1.0</version>
</dependency>
-
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.5</version>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/src/main/java/de/bwlehrpool/bwlp_guac/AvailableClient.java b/src/main/java/de/bwlehrpool/bwlp_guac/AvailableClient.java
index c886397..e9144f8 100644
--- a/src/main/java/de/bwlehrpool/bwlp_guac/AvailableClient.java
+++ b/src/main/java/de/bwlehrpool/bwlp_guac/AvailableClient.java
@@ -1,6 +1,7 @@
package de.bwlehrpool.bwlp_guac;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
@@ -15,10 +16,14 @@ public class AvailableClient implements Cloneable {
private static final AtomicLong CON_ID = new AtomicLong();
+ public ArrayList<JsonLocation> locationList = new ArrayList<JsonLocation>();
+
private final String clientip;
private String password;
+ private int locationid;
+
private State state;
private String inUseBy;
@@ -35,6 +40,7 @@ public class AvailableClient implements Cloneable {
public AvailableClient(JsonClient source) {
this.clientip = source.clientip;
+ this.locationid = source.locationid;
update(source);
}
@@ -110,6 +116,14 @@ public class AvailableClient implements Cloneable {
return clientip + "/" + state + "/" + inUseBy;
}
+ public State getState() {
+ return state;
+ }
+
+ public int getLocationid() {
+ return locationid;
+ }
+
public GuacamoleConfiguration toGuacConfig() {
GuacamoleConfiguration cfg = new GuacamoleConfiguration();
cfg.setProtocol("vnc");
diff --git a/src/main/java/de/bwlehrpool/bwlp_guac/BwlpAuthenticationProvider.java b/src/main/java/de/bwlehrpool/bwlp_guac/BwlpAuthenticationProvider.java
index 4ec4f48..b902621 100644
--- a/src/main/java/de/bwlehrpool/bwlp_guac/BwlpAuthenticationProvider.java
+++ b/src/main/java/de/bwlehrpool/bwlp_guac/BwlpAuthenticationProvider.java
@@ -1,16 +1,25 @@
package de.bwlehrpool.bwlp_guac;
-import java.util.Collections;
-import java.util.Map;
-import java.util.WeakHashMap;
+import java.io.IOException;
+import java.util.*;
import org.apache.guacamole.GuacamoleException;
-import org.apache.guacamole.net.auth.AuthenticatedUser;
-import org.apache.guacamole.net.auth.AuthenticationProvider;
-import org.apache.guacamole.net.auth.Credentials;
-import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.form.TextField;
+import org.apache.guacamole.net.auth.*;
+import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
+import org.codehaus.jackson.JsonGenerationException;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.servlet.http.HttpServletRequest;
+import javax.xml.soap.Text;
+
+import org.apache.guacamole.form.Field;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
public class BwlpAuthenticationProvider implements AuthenticationProvider {
@@ -44,7 +53,7 @@ public class BwlpAuthenticationProvider implements AuthenticationProvider {
}
public UserContext updateUserContext(UserContext context, AuthenticatedUser authenticatedUser,
- Credentials credentials) throws GuacamoleException {
+ Credentials credentials) throws GuacamoleException {
LOGGER.warn("Ignoring updateUserContext called with " + context.toString());
return null;
}
@@ -52,13 +61,19 @@ public class BwlpAuthenticationProvider implements AuthenticationProvider {
public UserContext decorate(UserContext context, AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
String username = authenticatedUser.getCredentials().getUsername();
+
LOGGER.warn("decorate called for " + username);
BwlpUserContext user = oldMappings.get(username);
+
if (user != null)
return user;
+
+ int locationid = requestLocation(credentials);
+
LOGGER.warn("Doing the decoration");
- user = new BwlpUserContext(authenticatedUser, context);
+ user = new BwlpUserContext(authenticatedUser, context, locationid);
oldMappings.put(username, user);
+
return user;
}
@@ -67,17 +82,74 @@ public class BwlpAuthenticationProvider implements AuthenticationProvider {
public UserContext redecorate(UserContext decorated, UserContext context, AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException {
+
String username = authenticatedUser.getCredentials().getUsername();
+
LOGGER.warn("REdecorate called for " + username);
BwlpUserContext user = oldMappings.get(username);
+
if (user != null && user.hasValidConnection())
return user;
+
+ int locationid = requestLocation(credentials);
+
LOGGER.warn("Doing the REdecoration");
- user = new BwlpUserContext(authenticatedUser, context);
+ user = new BwlpUserContext(authenticatedUser, context, locationid);
oldMappings.put(username, user);
+
return user;
}
+
+ private int requestLocation(Credentials credentials) throws GuacamoleException {
+ // Request the user to select a location
+ ConnectionManager.updateList();
+ HttpServletRequest request = credentials.getRequest();
+ String locationJson = request.getParameter("location");
+
+ if (locationJson == null) {
+ throw new GuacamoleInsufficientCredentialsException(
+ "Select Location", new CredentialsInfo(
+ Collections.<Field>singletonList(new LocationField())
+ ));
+ }
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ String message = "Select a Location";
+
+ int selectedId = 0;
+ boolean tryAgain = false;
+ String password = "";
+ String correctPassword = null;
+ try {
+ JsonNode location = mapper.readTree(locationJson);
+ selectedId = Integer.parseInt(location.get("id").asText());
+ if (selectedId != 0) {
+ password = location.get("password").asText();
+ correctPassword = ConnectionManager.getLocationPool().get(selectedId).password;
+ }
+ } catch (Exception e) {
+ LOGGER.info("Error reading location");
+ LOGGER.info(e.toString());
+ tryAgain = true;
+ }
+
+ if (selectedId != 0 && correctPassword != null && !password.equals(correctPassword)) {
+ tryAgain = true;
+ message = "Wrong password!";
+ }
+
+ if (tryAgain) {
+ throw new GuacamoleCredentialsException(
+ message, new CredentialsInfo(
+ Collections.<Field>singletonList(new LocationField())
+ ));
+ }
+
+ return selectedId;
+ }
+
public void shutdown() {
}
diff --git a/src/main/java/de/bwlehrpool/bwlp_guac/BwlpUserContext.java b/src/main/java/de/bwlehrpool/bwlp_guac/BwlpUserContext.java
index 9a6e5d7..05fa78f 100644
--- a/src/main/java/de/bwlehrpool/bwlp_guac/BwlpUserContext.java
+++ b/src/main/java/de/bwlehrpool/bwlp_guac/BwlpUserContext.java
@@ -8,6 +8,8 @@ import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.net.auth.simple.SimpleConnection;
import org.apache.guacamole.net.auth.simple.SimpleDirectory;
@@ -29,6 +31,7 @@ public class BwlpUserContext extends AbstractUserContext {
private final AuthenticatedUser authUser;
private final UserContext originalContext;
+ private final Integer locationid;
/**
* The Directory with access to all connections within the root group associated
@@ -36,15 +39,17 @@ public class BwlpUserContext extends AbstractUserContext {
*/
private Directory<Connection> connectionDirectory;
- public BwlpUserContext(AuthenticatedUser authenticatedUser, UserContext context) {
+ public BwlpUserContext(AuthenticatedUser authenticatedUser, UserContext context, int locationid)
+ throws GuacamoleCredentialsException {
authUser = authenticatedUser;
originalContext = context;
+ this.locationid = locationid;
// OK
addConn();
}
- private void addConn() {
- WrappedConnection connection = ConnectionManager.getForUser(authUser.getCredentials().getUsername());
+ private void addConn() throws GuacamoleCredentialsException {
+ WrappedConnection connection = ConnectionManager.getForUser(authUser.getCredentials().getUsername(), locationid);
if (connection != null) {
connectionDirectory = new SimpleDirectory<Connection>(connection);
} else {
diff --git a/src/main/java/de/bwlehrpool/bwlp_guac/ConnectionManager.java b/src/main/java/de/bwlehrpool/bwlp_guac/ConnectionManager.java
index f1ad057..eacb347 100644
--- a/src/main/java/de/bwlehrpool/bwlp_guac/ConnectionManager.java
+++ b/src/main/java/de/bwlehrpool/bwlp_guac/ConnectionManager.java
@@ -10,8 +10,7 @@ import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
+import java.util.*;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
@@ -21,6 +20,10 @@ import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
+import org.apache.guacamole.form.Field;
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
+import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,6 +34,7 @@ import org.slf4j.LoggerFactory;
*/
public class ConnectionManager {
+
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class);
// TODO: Config
@@ -38,11 +42,16 @@ public class ConnectionManager {
private static final LinkedHashMap<String, AvailableClient> clientPool = new LinkedHashMap<String, AvailableClient>();
+ private static final LinkedHashMap<Integer, JsonLocation> locationPool = new LinkedHashMap<Integer, JsonLocation>();
+
+ public static LinkedHashMap<Integer, JsonLocation> getLocationPool() { return locationPool; }
+
/**
* Pass plain user name, get existing connection (if any), or a fresh one
* @param user (LDAP) user name
*/
- public static WrappedConnection getForUser(String user) {
+ public static WrappedConnection getForUser(String user, int locationid)
+ throws GuacamoleCredentialsException {
if (SOURCE_URL == null) {
LOGGER.warn("Don't have a source URL for client machines!");
return null;
@@ -51,17 +60,35 @@ public class ConnectionManager {
updateList();
// Find existing/free client
AvailableClient freeClient;
- for (;;) {
+ for (; ; ) {
freeClient = null;
synchronized (clientPool) {
- for (AvailableClient ac : clientPool.values()) {
+ Collection<AvailableClient> clients;
+ if (locationid == 0) {
+ clients = new HashSet<AvailableClient>();
+ for (JsonLocation loc : locationPool.values()) {
+ if (!loc.hasPassword()) clients.addAll(loc.clientList);
+ }
+ } else {
+ JsonLocation location = locationPool.get(locationid);
+ if (location == null) {
+ // Request the user to select a location
+ throw new GuacamoleInsufficientCredentialsException(
+ "Select Location", new CredentialsInfo(
+ Collections.<Field>singletonList(new LocationField())
+ ));
+ }
+ clients = location.clientList;
+ }
+
+ for (AvailableClient ac : clients) {
if (ac.isInUseBy(user)) {
freeClient = ac;
break;
}
}
if (freeClient == null) {
- for (AvailableClient ac : clientPool.values()) {
+ for (AvailableClient ac : clients) {
if (ac.claim(user)) {
freeClient = ac;
break;
@@ -69,8 +96,13 @@ public class ConnectionManager {
}
}
}
- if (freeClient == null)
- return null; // TODO: No more clients available -- how to handle?
+ if (freeClient == null) {
+ // Request the user to select another location
+ throw new GuacamoleCredentialsException(
+ "No free client. Select another location.", new CredentialsInfo(
+ Collections.<Field>singletonList(new LocationField())
+ ));
+ }
// Found free or existing client, check if connection is (still) possible
if (freeClient.checkConnection(0)) {
LOGGER.info("Establishing mapping for user " + user + " to " + freeClient);
@@ -79,6 +111,8 @@ public class ConnectionManager {
// Connection check failed - release and loop again
freeClient.releaseConnection(user);
}
+ } catch (GuacamoleCredentialsException e) {
+ throw e;
} catch (Exception e) {
LOGGER.warn("KACKE AM DAMPFEN", e);
return null;
@@ -91,7 +125,7 @@ public class ConnectionManager {
* Fetch fresh client list from satellite server.
* Cache for 15 seconds.
*/
- private static synchronized void updateList() {
+ public static synchronized void updateList() {
long now = System.currentTimeMillis();
if (now < lastUpdate) {
lastUpdate = now;
@@ -139,23 +173,68 @@ public class ConnectionManager {
LOGGER.warn("Not updating local list");
return;
}
- list = root.clients;
- if (list == null) {
- LOGGER.info("Client list null");
+
+
+ if (root.locations == null) {
+ LOGGER.info("Location list null");
}
- if (root.locations != null) {
- for (JsonLocation l : root.locations) {
- LOGGER.info("Location " + l.name + " with pw " + l.password);
+ synchronized (locationPool) {
+ for (JsonLocation lnew : root.locations) {
+ JsonLocation existing = locationPool.get(lnew.id);
+ boolean redoClientMapping = false;
+ if (existing == null) {
+ locationPool.put(lnew.id, lnew);
+ existing = lnew;
+ redoClientMapping = true;
+ } else {
+ if (existing.locationids != lnew.locationids) redoClientMapping = true;
+ existing.locationids = lnew.locationids;
+ existing.name = lnew.name;
+ existing.password = lnew.password;
+ }
+ if (redoClientMapping) {
+ existing.clientList = new ArrayList<AvailableClient>();
+ for (AvailableClient client : clientPool.values()) {
+ int locationid = client.getLocationid();
+ for (int id : existing.locationids) {
+ if (id == locationid) {
+ existing.clientList.add(client);
+ break;
+ }
+ }
+ }
+ }
+ existing.checked = true;
+ LOGGER.info("Location " + lnew.name + " with pw " + lnew.password);
}
+ for (JsonLocation loc : locationPool.values()) {
+ if (loc.checked) loc.checked = false;
+ else locationPool.remove(loc.id);
+ }
+ }
+
+ if (root.clients == null) {
+ LOGGER.info("Client list null");
}
synchronized (clientPool) {
- for (JsonClient cnew : list) {
+ for (JsonClient cnew : root.clients) {
if (cnew.password == null || cnew.clientip == null)
continue; // Invalid
AvailableClient existing = clientPool.get(cnew.clientip);
if (existing == null) {
// New client
- clientPool.put(cnew.clientip, new AvailableClient(cnew));
+ AvailableClient newClient = new AvailableClient(cnew);
+ clientPool.put(cnew.clientip, newClient);
+
+ for (JsonLocation loc : locationPool.values()) {
+ for (int id : loc.locationids) {
+ if (id == cnew.locationid) {
+ loc.clientList.add(newClient);
+ newClient.locationList.add(loc);
+ break;
+ }
+ }
+ }
LOGGER.info("New client " + cnew.clientip);
} else {
existing.update(cnew);
@@ -166,6 +245,9 @@ public class ConnectionManager {
AvailableClient c = it.next();
if (c.isTimeout(NOW)) {
LOGGER.info("Removing client " + c + " from list");
+ for (JsonLocation loc : c.locationList) {
+ loc.clientList.remove(c);
+ }
it.remove();
}
diff --git a/src/main/java/de/bwlehrpool/bwlp_guac/JsonLocation.java b/src/main/java/de/bwlehrpool/bwlp_guac/JsonLocation.java
index 6bf9d1c..e6fc1b6 100644
--- a/src/main/java/de/bwlehrpool/bwlp_guac/JsonLocation.java
+++ b/src/main/java/de/bwlehrpool/bwlp_guac/JsonLocation.java
@@ -1,5 +1,10 @@
package de.bwlehrpool.bwlp_guac;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import java.util.ArrayList;
+
public class JsonLocation {
public int id;
@@ -7,7 +12,34 @@ public class JsonLocation {
public int[] locationids;
public String name;
-
+
public String password;
+
+ @JsonProperty("password")
+ public Boolean hasPassword() { return password != null; }
+
+ @JsonProperty("freeCount")
+ public int getFreeCount() {
+ int count = 0;
+ for (AvailableClient client : clientList) {
+ if (client.getState() == JsonClient.State.IDLE) count++;
+ }
+ return count;
+ }
+
+ @JsonProperty("offlineCount")
+ public int getOfflineCount() {
+ int count = 0;
+ for (AvailableClient client : clientList) {
+ if (client.getState() == JsonClient.State.OFFLINE || client.getState() == JsonClient.State.STANDBY) count++;
+ }
+ return count;
+ }
+
+ @JsonIgnore
+ public ArrayList<AvailableClient> clientList = new ArrayList<AvailableClient>();
+
+ @JsonIgnore
+ public boolean checked = false;
}
diff --git a/src/main/java/de/bwlehrpool/bwlp_guac/LocationField.java b/src/main/java/de/bwlehrpool/bwlp_guac/LocationField.java
new file mode 100644
index 0000000..5e0266d
--- /dev/null
+++ b/src/main/java/de/bwlehrpool/bwlp_guac/LocationField.java
@@ -0,0 +1,26 @@
+package de.bwlehrpool.bwlp_guac;
+
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.form.Field;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * Field which lets the user select a location.
+ */
+public class LocationField extends Field {
+
+ public LocationField() {
+ super("location", "LOCATION");
+ }
+
+ @JsonProperty("locations")
+ public Collection<JsonLocation> getLocations() throws GuacamoleException {
+ return ConnectionManager.getLocationPool().values();
+ }
+} \ No newline at end of file
diff --git a/src/main/resources/bwlpModule.js b/src/main/resources/bwlpModule.js
new file mode 100644
index 0000000..c74580a
--- /dev/null
+++ b/src/main/resources/bwlpModule.js
@@ -0,0 +1,5 @@
+angular.module('location', [
+ 'form'
+]);
+
+angular.module('index').requires.push('location'); \ No newline at end of file
diff --git a/src/main/resources/config/locationConfig.js b/src/main/resources/config/locationConfig.js
new file mode 100644
index 0000000..6074752
--- /dev/null
+++ b/src/main/resources/config/locationConfig.js
@@ -0,0 +1,11 @@
+angular.module('location').config(['formServiceProvider',
+ function locationConfig(formServiceProvider) {
+
+ // Define field for the TOTP code provided by the user
+ formServiceProvider.registerFieldType('LOCATION', {
+ module : 'location',
+ controller : 'locationFieldController',
+ templateUrl : 'app/ext/bwlp/templates/locationField.html'
+ });
+
+ }]); \ No newline at end of file
diff --git a/src/main/resources/controllers/locationFieldController.js b/src/main/resources/controllers/locationFieldController.js
new file mode 100644
index 0000000..8117ed7
--- /dev/null
+++ b/src/main/resources/controllers/locationFieldController.js
@@ -0,0 +1,23 @@
+angular.module('location').controller('locationFieldController', ['$scope', '$window',
+ function locationFieldController($scope, $window) {
+
+ $scope.data = { id: 0, password: '' }
+
+ $scope.$watch('data', function(newValue) {
+ $scope.model = JSON.stringify(newValue);
+ }, true);
+
+ $scope.selectLocation = function selectLocation($event, id) {
+ if (angular.element($event.target).hasClass('bwlp-password-input')) return;
+ $scope.data.password = '';
+ if ($scope.data.id === id) {
+ $scope.data.id = 0;
+ angular.element('.selected-location').removeClass('selected-location');
+ return;
+ }
+ $scope.data.id = id;
+ angular.element('.selected-location').removeClass('selected-location');
+ angular.element($event.currentTarget).addClass('selected-location');
+ };
+
+}]); \ No newline at end of file
diff --git a/src/main/resources/guac-manifest.json b/src/main/resources/guac-manifest.json
index ef3c431..e507183 100644
--- a/src/main/resources/guac-manifest.json
+++ b/src/main/resources/guac-manifest.json
@@ -3,5 +3,17 @@
"name" : "bwLehrpool virtual pool",
"namespace" : "de.bwlehrpool",
"authProviders": ["de.bwlehrpool.bwlp_guac.BwlpAuthenticationProvider"],
- "html" : [ "disclaimer.html" ]
+ "html" : [ "disclaimer.html" ],
+ "translations" : [
+ "translations/en.json"
+ ],
+ "js" : [
+ "bwlp.min.js"
+ ],
+ "css" : [
+ "bwlp.min.css"
+ ],
+ "resources" : {
+ "templates/locationField.html" : "text/html"
+ }
}
diff --git a/src/main/resources/styles/locationField.css b/src/main/resources/styles/locationField.css
new file mode 100644
index 0000000..cec1466
--- /dev/null
+++ b/src/main/resources/styles/locationField.css
@@ -0,0 +1,47 @@
+.bwlp-location-container {
+ border: 2px solid #3d3d3d;
+ width: 100%;
+ border-spacing: 0;
+}
+
+.bwlp-location {
+ background-color: white;
+ cursor: pointer;
+}
+
+.bwlp-location > td {
+ padding: 14px;
+}
+
+.bwlp-location .bwlp-password {
+ padding: 4px 8px !important;
+ margin: -10px 0 !important;
+}
+
+.bwlp-location:hover {
+ filter: brightness(0.9);
+}
+
+.bwlp-location.selected-location {
+ background-color: #c8ffc8;
+}
+
+.bwlp-location-status {
+ font-size: 16px;
+ text-align: center;
+}
+
+.bwlp-password {
+ text-align: right;
+}
+
+.bwlp-password > span {
+ font-size: 16px;
+ opacity: 0.5;
+}
+
+.bwlp-password-input {
+ margin: 0 !important;
+ padding: 5px 8px !important;
+ background-color: white !important;
+} \ No newline at end of file
diff --git a/src/main/resources/templates/locationField.html b/src/main/resources/templates/locationField.html
new file mode 100644
index 0000000..f76259e
--- /dev/null
+++ b/src/main/resources/templates/locationField.html
@@ -0,0 +1,18 @@
+<table class="bwlp-location-container">
+ <tr ng-repeat="location in field.locations" class="bwlp-location"
+ ng-click="selectLocation($event, location.id)">
+ <td>
+ {{ location.name }}
+ </td>
+ <td class="bwlp-location-status">
+ {{ location.freeCount }} available ({{ location.offlineCount }} offline)
+ </td>
+ <td style="width: 200px">
+ <div ng-if="location.password" class="bwlp-password">
+ <span ng-if="data.id !== location.id">Password protected</span>
+ <input ng-if="data.id === location.id" ng-model="data.password"
+ type="password" placeholder="Password" class="bwlp-password-input" required>
+ </div>
+ </td>
+ </tr>
+</table>
diff --git a/src/main/resources/translations/en.json b/src/main/resources/translations/en.json
new file mode 100644
index 0000000..9ad430b
--- /dev/null
+++ b/src/main/resources/translations/en.json
@@ -0,0 +1,5 @@
+{
+ "LOGIN" : {
+ "FIELD_HEADER_LOCATION" : ""
+ }
+} \ No newline at end of file