Add sample parksmap app and grid template.
This commit is contained in:
		@ -76,6 +76,36 @@ EOF
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
#+RESULTS:
 | 
			
		||||
: subscription.operators.coreos.com/openshift-pipelines-operator configured
 | 
			
		||||
: subscription.operators.coreos.com/openshift-pipelines-operator created
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
* Step 2 - Create sample selenium pipeline
 | 
			
		||||
 | 
			
		||||
Once OpenShift Pipelines is installed we can create an example pipeline to demonstrate the flow in the diagram above.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#+begin_src bash :results output
 | 
			
		||||
# Create namespace for application test environment
 | 
			
		||||
oc new-project parksmap-tst
 | 
			
		||||
 | 
			
		||||
# Create pipeline
 | 
			
		||||
cat << EOF | oc apply --filename -
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EOF
 | 
			
		||||
#+end_src
 | 
			
		||||
 | 
			
		||||
#+RESULTS:
 | 
			
		||||
#+begin_example
 | 
			
		||||
Now using project "parksmap-tst" on server "https://api.cluster-272j9.dynamic.redhatworkshops.io:6443".
 | 
			
		||||
 | 
			
		||||
You can add applications to this project with the 'new-app' command. For example, try:
 | 
			
		||||
 | 
			
		||||
    oc new-app rails-postgresql-example
 | 
			
		||||
 | 
			
		||||
to build a new example application in Ruby. Or use kubectl to deploy a simple Kubernetes application:
 | 
			
		||||
 | 
			
		||||
    kubectl create deployment hello-node --image=k8s.gcr.io/e2e-test-images/agnhost:2.33 -- /agnhost serve-hostname
 | 
			
		||||
 | 
			
		||||
#+end_example
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										101
									
								
								2024-03-14-scalable-selenium-pipelines/parksmap/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								2024-03-14-scalable-selenium-pipelines/parksmap/pom.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
			
		||||
<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/maven-v4_0_0.xsd">
 | 
			
		||||
  <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
  <parent>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-parent</artifactId>
 | 
			
		||||
    <version>1.5.19.RELEASE</version>
 | 
			
		||||
  </parent>
 | 
			
		||||
 | 
			
		||||
  <groupId>com.openshift.evg.roadshow</groupId>
 | 
			
		||||
  <artifactId>parksmap-web</artifactId>
 | 
			
		||||
  <version>1.0.0-SNAPSHOT</version>
 | 
			
		||||
  <packaging>jar</packaging>
 | 
			
		||||
 | 
			
		||||
  <properties>
 | 
			
		||||
    <netflix.feign.version>9.3.1</netflix.feign.version>
 | 
			
		||||
    <springboot.version>1.5.19.RELEASE</springboot.version>
 | 
			
		||||
  </properties>
 | 
			
		||||
 | 
			
		||||
  <dependencies>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>org.springframework.boot</groupId>
 | 
			
		||||
      <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>org.springframework.boot</groupId>
 | 
			
		||||
      <artifactId>spring-boot-starter-websocket</artifactId>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>org.springframework</groupId>
 | 
			
		||||
      <artifactId>spring-messaging</artifactId>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
      <artifactId>spring-cloud-context</artifactId>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>io.fabric8</groupId>
 | 
			
		||||
      <artifactId>openshift-client</artifactId>
 | 
			
		||||
      <version>4.2.0</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>io.github.openfeign</groupId>
 | 
			
		||||
      <artifactId>feign-core</artifactId>
 | 
			
		||||
      <version>${netflix.feign.version}</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>io.github.openfeign</groupId>
 | 
			
		||||
      <artifactId>feign-jackson</artifactId>
 | 
			
		||||
      <version>${netflix.feign.version}</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>io.github.openfeign</groupId>
 | 
			
		||||
      <artifactId>feign-jaxrs</artifactId>
 | 
			
		||||
      <version>${netflix.feign.version}</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>io.github.openfeign</groupId>
 | 
			
		||||
      <artifactId>feign-gson</artifactId>
 | 
			
		||||
      <version>${netflix.feign.version}</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>io.github.openfeign</groupId>
 | 
			
		||||
      <artifactId>feign-slf4j</artifactId>
 | 
			
		||||
      <version>${netflix.feign.version}</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>org.slf4j</groupId>
 | 
			
		||||
      <artifactId>slf4j-api</artifactId>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>javax.ws.rs</groupId>
 | 
			
		||||
      <artifactId>javax.ws.rs-api</artifactId>
 | 
			
		||||
      <version>2.0.1</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
  </dependencies>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  <dependencyManagement>
 | 
			
		||||
    <dependencies>
 | 
			
		||||
      <dependency>
 | 
			
		||||
        <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
        <artifactId>spring-cloud-dependencies</artifactId>
 | 
			
		||||
        <version>Brixton.SR4</version>
 | 
			
		||||
        <type>pom</type>
 | 
			
		||||
        <scope>import</scope>
 | 
			
		||||
      </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
  </dependencyManagement>
 | 
			
		||||
 | 
			
		||||
  <build>
 | 
			
		||||
    <finalName>${project.artifactId}</finalName>
 | 
			
		||||
    <plugins>
 | 
			
		||||
      <plugin>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
      </plugin>
 | 
			
		||||
    </plugins>
 | 
			
		||||
  </build>
 | 
			
		||||
</project>
 | 
			
		||||
@ -0,0 +1,18 @@
 | 
			
		||||
package com.openshift.evg.roadshow;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.SpringApplication;
 | 
			
		||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
 | 
			
		||||
import org.springframework.context.annotation.ComponentScan;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Created by jmorales on 24/08/16.
 | 
			
		||||
 */
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@ComponentScan(basePackages = "com.openshift.evg.roadshow.rest")
 | 
			
		||||
public class ParksMapApplication {
 | 
			
		||||
 | 
			
		||||
  public static void main(String[] args) {
 | 
			
		||||
    SpringApplication.run(ParksMapApplication.class, args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,146 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.helpers.EndpointRegistrar;
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.helpers.EndpointWatcher;
 | 
			
		||||
 | 
			
		||||
import io.fabric8.kubernetes.api.model.HasMetadata;
 | 
			
		||||
import io.fabric8.kubernetes.client.KubernetesClientException;
 | 
			
		||||
import io.fabric8.kubernetes.client.Watch;
 | 
			
		||||
import io.fabric8.kubernetes.client.Watcher;
 | 
			
		||||
import io.fabric8.openshift.client.DefaultOpenShiftClient;
 | 
			
		||||
import io.fabric8.openshift.client.OpenShiftClient;
 | 
			
		||||
 | 
			
		||||
public abstract class AbstractResourceWatcher<T extends HasMetadata> implements Watcher<T> {
 | 
			
		||||
  private static final Logger logger = LoggerFactory.getLogger(AbstractResourceWatcher.class);
 | 
			
		||||
 | 
			
		||||
  private String currentNamespace = null;
 | 
			
		||||
 | 
			
		||||
  private Watch watch;
 | 
			
		||||
 | 
			
		||||
  private OpenShiftClient client = new DefaultOpenShiftClient();;
 | 
			
		||||
 | 
			
		||||
  private EndpointRegistrar endpointRegistrar;
 | 
			
		||||
 | 
			
		||||
  private Map<String, EndpointWatcher> endpointsWatchers = new HashMap<String, EndpointWatcher>();
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void eventReceived(Action action, T t) {
 | 
			
		||||
    logger.info("Action: {}, Resource: {}", action, t);
 | 
			
		||||
 | 
			
		||||
    String resourceName = t.getMetadata().getName();
 | 
			
		||||
    // }
 | 
			
		||||
    if (action == Action.ADDED) {
 | 
			
		||||
      logger.info("Resource {} added", resourceName);
 | 
			
		||||
      EndpointWatcher epW = endpointsWatchers.get(resourceName);
 | 
			
		||||
      if (epW == null) {
 | 
			
		||||
        epW = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
 | 
			
		||||
        endpointsWatchers.put(resourceName, epW);
 | 
			
		||||
      }
 | 
			
		||||
    } else if (action == Action.DELETED) {
 | 
			
		||||
      logger.info("Resource {} deleted", resourceName);
 | 
			
		||||
      EndpointWatcher epW = endpointsWatchers.get(resourceName);
 | 
			
		||||
      if (epW != null) {
 | 
			
		||||
        epW.close();
 | 
			
		||||
        endpointRegistrar.unregister(resourceName);
 | 
			
		||||
        endpointsWatchers.remove(resourceName);
 | 
			
		||||
      }
 | 
			
		||||
    } else if (action == Action.MODIFIED) {
 | 
			
		||||
      // TODO: Modification of a resource is cumbersome. Look into how to
 | 
			
		||||
      // best implement this
 | 
			
		||||
      EndpointWatcher epW = endpointsWatchers.get(resourceName);
 | 
			
		||||
      endpointsWatchers.remove(resourceName);
 | 
			
		||||
      endpointRegistrar.unregister(resourceName);
 | 
			
		||||
      epW = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
 | 
			
		||||
      endpointsWatchers.put(resourceName, epW);
 | 
			
		||||
 | 
			
		||||
    } else if (action == Action.ERROR) {
 | 
			
		||||
      logger.error("Resource ERRORED");
 | 
			
		||||
      EndpointWatcher epW = endpointsWatchers.get(resourceName);
 | 
			
		||||
      endpointsWatchers.remove(resourceName);
 | 
			
		||||
      epW = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
 | 
			
		||||
      endpointsWatchers.put(resourceName, epW);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * This will get notified when Kubernetes client is closed
 | 
			
		||||
   *
 | 
			
		||||
   * @param e
 | 
			
		||||
   */
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onClose(KubernetesClientException e) {
 | 
			
		||||
    if (e != null) {
 | 
			
		||||
      // This is when the client is closed
 | 
			
		||||
      logger.error("[ERROR] There was an error in the client {}", e.getMessage());
 | 
			
		||||
      init(endpointRegistrar);
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.info("Closing this Watcher");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void cleanUp() {
 | 
			
		||||
    if (watch != null)
 | 
			
		||||
      watch.close();
 | 
			
		||||
    // If this watch has been closed, we create complete set of new watches
 | 
			
		||||
    for (EndpointWatcher epWatcher : endpointsWatchers.values()) {
 | 
			
		||||
      epWatcher.close();
 | 
			
		||||
    }
 | 
			
		||||
    endpointsWatchers.clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void init(EndpointRegistrar endpointRegistrar) {
 | 
			
		||||
    cleanUp();
 | 
			
		||||
 | 
			
		||||
    // BackendsController
 | 
			
		||||
    if (this.endpointRegistrar == null) {
 | 
			
		||||
      this.endpointRegistrar = endpointRegistrar;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (currentNamespace == null) {
 | 
			
		||||
      currentNamespace = client.getNamespace();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    logger.info("[INFO] {} is watching for resources started in namespace {} ", this.getClass().getName(),
 | 
			
		||||
        currentNamespace);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      List<T> resources = listWatchedResources();
 | 
			
		||||
      for (T resource : resources) {
 | 
			
		||||
        String resourceName = resource.getMetadata().getName();
 | 
			
		||||
        EndpointWatcher endpointWatcher = endpointsWatchers.get(resourceName);
 | 
			
		||||
        if (endpointWatcher == null) {
 | 
			
		||||
          endpointWatcher = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
 | 
			
		||||
          endpointsWatchers.put(resourceName, endpointWatcher);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      watch = doInit();
 | 
			
		||||
    } catch (KubernetesClientException e) {
 | 
			
		||||
      // If there is no proper permission, don't fail misserably
 | 
			
		||||
      logger.error(
 | 
			
		||||
          "Error initialiting application. Probably you need the appropriate permissions to view this namespace {}. {}",
 | 
			
		||||
          currentNamespace, e.getMessage());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected OpenShiftClient getOpenShiftClient() {
 | 
			
		||||
    return client;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected String getNamespace() {
 | 
			
		||||
    return currentNamespace;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected abstract List<T> listWatchedResources();
 | 
			
		||||
 | 
			
		||||
  protected abstract Watch doInit();
 | 
			
		||||
 | 
			
		||||
  protected abstract String getUrl(String resourceName);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,124 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.PostConstruct;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMethod;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestParam;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.ApiGatewayController;
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.DataGatewayController;
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.helpers.EndpointRegistrar;
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Backend controller. Every time a backend appears/dissapears in OpenShift
 | 
			
		||||
 * it will send a notification to the web to show/hide the appropriate layer/map
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Created by jmorales on 24/08/16.
 | 
			
		||||
 */
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/ws/backends")
 | 
			
		||||
public class BackendsController implements EndpointRegistrar {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(BackendsController.class);
 | 
			
		||||
 | 
			
		||||
    @Value("${test}")
 | 
			
		||||
    private Boolean test;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private SimpMessagingTemplate messagingTemplate;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ApiGatewayController apiGateway;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private DataGatewayController dataGateway;
 | 
			
		||||
    
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ServiceWatcher serviceWatcher;
 | 
			
		||||
    
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RouteWatcher routeWatcher;
 | 
			
		||||
    
 | 
			
		||||
    private Map<String, Backend> registeredBackends = new HashMap<String, Backend>();
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * This method is used to start monitoring for services
 | 
			
		||||
     */
 | 
			
		||||
    @RequestMapping(method = RequestMethod.GET, value = "/init")
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    public void init() {
 | 
			
		||||
    	routeWatcher.init(this);
 | 
			
		||||
    	serviceWatcher.init(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param
 | 
			
		||||
     * @return
 | 
			
		||||
     */
 | 
			
		||||
    @RequestMapping(method = RequestMethod.GET, value = "/register", produces = "application/json")
 | 
			
		||||
    public List<Backend> register(@RequestParam(value = "endpoint") String endpoint) {
 | 
			
		||||
        logger.info("Backends.register endpoint at ({})", endpoint);
 | 
			
		||||
 | 
			
		||||
        Backend newBackend = null;
 | 
			
		||||
        
 | 
			
		||||
        String endpointUrl = routeWatcher.getUrl(endpoint); // try to find a route for endpoint
 | 
			
		||||
        if (endpointUrl == null || endpointUrl.trim().equals("")) { 
 | 
			
		||||
        	endpointUrl = serviceWatcher.getUrl(endpoint); // otherwise, find a service for endpoint
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Query for backend data.
 | 
			
		||||
        if (endpoint != null) {
 | 
			
		||||
        	if ((newBackend = apiGateway.getFromRemote(endpointUrl)) != null) {
 | 
			
		||||
                // TODO: BackendId should not be fetched from remote. For now I replace the remote one with the local one.
 | 
			
		||||
                newBackend.setId(endpoint);
 | 
			
		||||
                // Register the new backend
 | 
			
		||||
                apiGateway.add(endpoint, endpointUrl);
 | 
			
		||||
                dataGateway.add(endpoint, endpointUrl);
 | 
			
		||||
                registeredBackends.put(endpoint, newBackend);
 | 
			
		||||
 | 
			
		||||
                logger.info("Backend from server: ({}) ", newBackend);
 | 
			
		||||
                // Notify web
 | 
			
		||||
                messagingTemplate.convertAndSend("/topic/add", newBackend);
 | 
			
		||||
            } else {
 | 
			
		||||
                logger.info("Backend with provided id ({}) already registered", endpoint);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return new ArrayList<Backend>(registeredBackends.values());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @RequestMapping(method = RequestMethod.GET, value = "/unregister", produces = "application/json")
 | 
			
		||||
    public List<Backend> unregister(@RequestParam(value = "endpointName") String endpointName) {
 | 
			
		||||
        logger.info("Backends.unregister service at ({})", endpointName);
 | 
			
		||||
 | 
			
		||||
        Backend backend = null;
 | 
			
		||||
        if ((backend = registeredBackends.get(endpointName)) != null) {
 | 
			
		||||
            messagingTemplate.convertAndSend("/topic/remove", backend); // Notify web
 | 
			
		||||
 | 
			
		||||
            registeredBackends.remove(endpointName);
 | 
			
		||||
            apiGateway.remove(endpointName);
 | 
			
		||||
            dataGateway.remove(endpointName);
 | 
			
		||||
        } else {
 | 
			
		||||
            logger.info("No backend at ({})", endpointName);
 | 
			
		||||
        }
 | 
			
		||||
        return new ArrayList<Backend>(registeredBackends.values());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @RequestMapping(method = RequestMethod.GET, value = "/list", produces = "application/json")
 | 
			
		||||
    public List<Backend> getAll() {
 | 
			
		||||
        logger.info("Backends: getAll");
 | 
			
		||||
        return new ArrayList<Backend>(registeredBackends.values());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest;
 | 
			
		||||
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMethod;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Healthz endpoint for liveness and readiness of the application
 | 
			
		||||
 *
 | 
			
		||||
 * Created by jmorales on 11/08/16.
 | 
			
		||||
 */
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/ws/healthz")
 | 
			
		||||
public class Healthz {
 | 
			
		||||
 | 
			
		||||
  @RequestMapping(method = RequestMethod.GET, value = "/")
 | 
			
		||||
  public String healthz() {
 | 
			
		||||
    return "OK";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,53 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
import io.fabric8.kubernetes.client.Watch;
 | 
			
		||||
import io.fabric8.openshift.api.model.Route;
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
public class RouteWatcher extends AbstractResourceWatcher<Route> {
 | 
			
		||||
  private static final Logger logger = LoggerFactory.getLogger(ServiceWatcher.class);
 | 
			
		||||
 | 
			
		||||
  private static final String PARKSMAP_BACKEND_LABEL = "type=parksmap-backend";
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected List<Route> listWatchedResources() {
 | 
			
		||||
    return getOpenShiftClient().routes().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).list()
 | 
			
		||||
        .getItems();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected Watch doInit() {
 | 
			
		||||
    return getOpenShiftClient().routes().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).watch(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected String getUrl(String routeName) {
 | 
			
		||||
    List<Route> routes = getOpenShiftClient().routes().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL)
 | 
			
		||||
        .withField("metadata.name", routeName).list().getItems();
 | 
			
		||||
    if (routes.isEmpty()) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Route route = routes.get(0);
 | 
			
		||||
    String routeUrl = "";
 | 
			
		||||
    try {
 | 
			
		||||
      String protocol = "http://";
 | 
			
		||||
      if((route.getSpec().getTls()!=null)&&(route.getSpec().getTls().getTermination()!=null)){
 | 
			
		||||
        protocol = "https://";
 | 
			
		||||
      }
 | 
			
		||||
      routeUrl = protocol + route.getSpec().getHost();
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      logger.error("Route {} does not have a port assigned", routeName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    logger.info("[INFO] Computed route URL: {}", routeUrl);
 | 
			
		||||
 | 
			
		||||
    return routeUrl;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,51 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
import io.fabric8.kubernetes.api.model.Service;
 | 
			
		||||
import io.fabric8.kubernetes.client.Watch;
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
public class ServiceWatcher extends AbstractResourceWatcher<Service> {
 | 
			
		||||
  private static final Logger logger = LoggerFactory.getLogger(ServiceWatcher.class);
 | 
			
		||||
 | 
			
		||||
  private static final String PARKSMAP_BACKEND_LABEL = "type=parksmap-backend";
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected List<Service> listWatchedResources() {
 | 
			
		||||
    return getOpenShiftClient().services().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).list()
 | 
			
		||||
        .getItems();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected Watch doInit() {
 | 
			
		||||
    return getOpenShiftClient().services().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).watch(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected String getUrl(String serviceName) {
 | 
			
		||||
    List<Service> services = getOpenShiftClient().services().inNamespace(getNamespace())
 | 
			
		||||
        .withLabel(PARKSMAP_BACKEND_LABEL).withField("metadata.name", serviceName).list().getItems();
 | 
			
		||||
    if (services.isEmpty()) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Service service = services.get(0);
 | 
			
		||||
    String serviceURL = "";
 | 
			
		||||
    int port = 8080;
 | 
			
		||||
    try {
 | 
			
		||||
      port = service.getSpec().getPorts().get(0).getPort();
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      logger.error("Service {} does not have a port assigned", serviceName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    serviceURL = "http://" + serviceName + ":" + port;
 | 
			
		||||
 | 
			
		||||
    logger.info("[INFO] Computed service URL: {}", serviceURL);
 | 
			
		||||
    return serviceURL;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,31 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest;
 | 
			
		||||
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
 | 
			
		||||
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
 | 
			
		||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
 | 
			
		||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class provides support for websockets communication with the web Web
 | 
			
		||||
 * will use t2 topics: /topic/add Notification that there is a new backend
 | 
			
		||||
 * /topic/remove Notification of removal of backend
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Created by jmorales on 26/08/16.
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
@EnableWebSocketMessageBroker
 | 
			
		||||
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void configureMessageBroker(MessageBrokerRegistry config) {
 | 
			
		||||
    config.enableSimpleBroker("/topic");
 | 
			
		||||
    config.setApplicationDestinationPrefixes("/app");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void registerStompEndpoints(StompEndpointRegistry registry) {
 | 
			
		||||
    registry.addEndpoint("/socks-backends").withSockJS();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,101 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway;
 | 
			
		||||
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.api.BackendServiceRemote;
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.helpers.CustomErrorDecoder;
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
 | 
			
		||||
 | 
			
		||||
import feign.Feign;
 | 
			
		||||
import feign.Retryer;
 | 
			
		||||
import feign.jackson.JacksonDecoder;
 | 
			
		||||
import feign.jackson.JacksonEncoder;
 | 
			
		||||
import feign.jaxrs.JAXRSContract;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.stereotype.Controller;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import static java.util.concurrent.TimeUnit.SECONDS;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * API Gateway. It will dispatch connections to the appropriate backend
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Created by jmorales on 24/08/16.
 | 
			
		||||
 */
 | 
			
		||||
@Controller
 | 
			
		||||
public class ApiGatewayController {
 | 
			
		||||
 | 
			
		||||
  private static final Logger logger = LoggerFactory.getLogger(ApiGatewayController.class);
 | 
			
		||||
 | 
			
		||||
  private Map<String, BackendServiceRemote> remoteServices = new HashMap<String, BackendServiceRemote>();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   *
 | 
			
		||||
   * @param backendId
 | 
			
		||||
   * @param url
 | 
			
		||||
   */
 | 
			
		||||
  public final void add(String backendId, String url) {
 | 
			
		||||
    if (remoteServices.get(backendId) == null) {
 | 
			
		||||
      remoteServices.put(backendId, Feign.builder().client(CustomFeignClient.getClient()).contract(new JAXRSContract()).encoder(new JacksonEncoder())
 | 
			
		||||
          .decoder(new JacksonDecoder()).target(BackendServiceRemote.class, url));
 | 
			
		||||
      logger.info("Backend ({}) added to the API Gateway", backendId);
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.error("This backend ({}) did already exist in the API Gateway", backendId);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   *
 | 
			
		||||
   * @param backendId
 | 
			
		||||
   */
 | 
			
		||||
  public final void remove(String backendId) {
 | 
			
		||||
    if (remoteServices.get(backendId) != null) {
 | 
			
		||||
      remoteServices.remove(backendId);
 | 
			
		||||
      logger.info("Backend ({}) removed from the API Gateway", backendId);
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.error("This backend ({}) did NOT exist in the API Gateway", backendId);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   *
 | 
			
		||||
   * @param backendId
 | 
			
		||||
   * @return
 | 
			
		||||
   */
 | 
			
		||||
  public Backend getFromLocal(String backendId) {
 | 
			
		||||
    BackendServiceRemote backend = null;
 | 
			
		||||
    if ((backend = remoteServices.get(backendId)) != null) {
 | 
			
		||||
      logger.info("Calling remote service {}", backendId);
 | 
			
		||||
      try {
 | 
			
		||||
        return backend.get();
 | 
			
		||||
      } catch (Exception e) {
 | 
			
		||||
        logger.error("Error connecting to backend server {}", e.getMessage());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   *
 | 
			
		||||
   * @param remoteURL
 | 
			
		||||
   * @return
 | 
			
		||||
   */
 | 
			
		||||
  public Backend getFromRemote(String remoteURL) {
 | 
			
		||||
    logger.info("Calling remote service at {}", remoteURL);
 | 
			
		||||
    try {
 | 
			
		||||
      return Feign.builder().client(CustomFeignClient.getClient()).contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder())
 | 
			
		||||
          .retryer(new Retryer.Default(200, SECONDS.toMillis(1), 5)).errorDecoder(new CustomErrorDecoder())
 | 
			
		||||
          .target(BackendServiceRemote.class, remoteURL).get();
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      logger.error("Error connecting to backend server {}", e.getMessage());
 | 
			
		||||
      logger.error("Error message",e);
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,64 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway;
 | 
			
		||||
 | 
			
		||||
import javax.net.ssl.HostnameVerifier;
 | 
			
		||||
import javax.net.ssl.SSLContext;
 | 
			
		||||
import javax.net.ssl.SSLSocketFactory;
 | 
			
		||||
import javax.net.ssl.X509TrustManager;
 | 
			
		||||
import java.security.cert.X509Certificate;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.security.SecureRandom;
 | 
			
		||||
import java.security.cert.CertificateException;
 | 
			
		||||
 | 
			
		||||
import feign.Client;
 | 
			
		||||
 | 
			
		||||
class CustomFeignClient {
 | 
			
		||||
  private static final Logger logger = LoggerFactory.getLogger(CustomFeignClient.class);
 | 
			
		||||
    /**
 | 
			
		||||
   * This method should not be used in production!! It is only in a poof of
 | 
			
		||||
   * concept!
 | 
			
		||||
   * 
 | 
			
		||||
   * @return
 | 
			
		||||
   */
 | 
			
		||||
  private static SSLSocketFactory getSSLSocketFactory() {
 | 
			
		||||
    try {
 | 
			
		||||
      SSLContext context = SSLContext.getInstance("TLS");
 | 
			
		||||
      context.init(null, new X509TrustManager[] { new X509TrustManager() {
 | 
			
		||||
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
 | 
			
		||||
          logger.info("checkClientTrusted");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
 | 
			
		||||
          logger.info("checkClientTrusted");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public X509Certificate[] getAcceptedIssuers() {
 | 
			
		||||
          logger.info("checkClientTrusted");
 | 
			
		||||
          return new X509Certificate[0];
 | 
			
		||||
        }
 | 
			
		||||
      } }, new SecureRandom());
 | 
			
		||||
      logger.warn("Ignoring certification errors! Don't use in production!");
 | 
			
		||||
      return context.getSocketFactory();
 | 
			
		||||
    } catch (Exception exception) {
 | 
			
		||||
      throw new RuntimeException(exception);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static Client getClient() {
 | 
			
		||||
    return new Client.Default(getSSLSocketFactory(), getHostNameVerifier());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static HostnameVerifier getHostNameVerifier() {
 | 
			
		||||
    HostnameVerifier hostnameVerifier= new HostnameVerifier(){
 | 
			
		||||
 | 
			
		||||
      public boolean verify(String hostname,
 | 
			
		||||
              javax.net.ssl.SSLSession sslSession) {
 | 
			
		||||
                logger.warn("Ignoring hostname verification errors! Don't use in production!");
 | 
			
		||||
          return true;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
    return hostnameVerifier;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,86 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway;
 | 
			
		||||
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.api.DataServiceRemote;
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.model.DataPoint;
 | 
			
		||||
import feign.Feign;
 | 
			
		||||
import feign.jackson.JacksonDecoder;
 | 
			
		||||
import feign.jackson.JacksonEncoder;
 | 
			
		||||
import feign.jaxrs.JAXRSContract;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * API Gateway. It will dispatch connections to the appropriate backend
 | 
			
		||||
 *
 | 
			
		||||
 * Created by jmorales on 24/08/16.
 | 
			
		||||
 */
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/ws/data")
 | 
			
		||||
public class DataGatewayController {
 | 
			
		||||
 | 
			
		||||
  private static final Logger logger = LoggerFactory.getLogger(DataGatewayController.class);
 | 
			
		||||
 | 
			
		||||
  private Map<String, DataServiceRemote> remoteServices = new HashMap<String, DataServiceRemote>();
 | 
			
		||||
 | 
			
		||||
  public DataGatewayController() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   * @param backendId
 | 
			
		||||
   * @param url
 | 
			
		||||
   */
 | 
			
		||||
  public final void add(String backendId, String url) {
 | 
			
		||||
    if (remoteServices.get(backendId) == null) {
 | 
			
		||||
      remoteServices.put(backendId, Feign.builder().client(CustomFeignClient.getClient()).contract(new JAXRSContract()).encoder(new JacksonEncoder())
 | 
			
		||||
          .decoder(new JacksonDecoder()).target(DataServiceRemote.class, url));
 | 
			
		||||
      logger.info("Backend ({}) added to the Data Gateway", backendId);
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.error("This backend ({}) did already exist in the Data Gateway", backendId);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   * @param backendId
 | 
			
		||||
   */
 | 
			
		||||
  public final void remove(String backendId) {
 | 
			
		||||
    if (remoteServices.get(backendId) != null) {
 | 
			
		||||
      remoteServices.remove(backendId);
 | 
			
		||||
      logger.info("Backend ({}) removed from the Data Gateway", backendId);
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.error("This backend ({}) did NOT exist in the Data Gateway", backendId);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @RequestMapping(method = RequestMethod.GET, value = "/all", produces = "application/json")
 | 
			
		||||
  public List<DataPoint> getAll(@RequestParam(value = "service") String serviceURL) {
 | 
			
		||||
    DataServiceRemote remote = remoteServices.get(serviceURL);
 | 
			
		||||
    if (remote != null) {
 | 
			
		||||
      logger.info("[WEB-CALL] Calling remote service for {}", serviceURL);
 | 
			
		||||
      return remote.getAll();
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.error("[WEB-CALL] No remote service for {}", serviceURL);
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @RequestMapping(method = RequestMethod.GET, value = "/within", produces = "application/json")
 | 
			
		||||
  public List<DataPoint> getWithin(@RequestParam(value = "service") String serviceURL, @RequestParam("lat1") float lat1,
 | 
			
		||||
      @RequestParam("lon1") float lon1, @RequestParam("lat2") float lat2, @RequestParam("lon2") float lon2) {
 | 
			
		||||
    DataServiceRemote remote = remoteServices.get(serviceURL);
 | 
			
		||||
    if (remote != null) {
 | 
			
		||||
      logger.info("[WEB-CALL] Calling remote service for {}", serviceURL);
 | 
			
		||||
      return remote.findWithin(lat1, lon1, lat2, lon2);
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.error("[WEB-CALL] No remote service for {}", serviceURL);
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway.api;
 | 
			
		||||
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
 | 
			
		||||
 | 
			
		||||
import javax.ws.rs.GET;
 | 
			
		||||
import javax.ws.rs.Path;
 | 
			
		||||
import javax.ws.rs.Produces;
 | 
			
		||||
import javax.ws.rs.core.MediaType;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Contract to use in the backends to provide backend information
 | 
			
		||||
 *
 | 
			
		||||
 * Created by jmorales on 26/09/16.
 | 
			
		||||
 */
 | 
			
		||||
@Path("/ws/info")
 | 
			
		||||
public interface BackendServiceRemote {
 | 
			
		||||
  @GET
 | 
			
		||||
  @Path("/")
 | 
			
		||||
  @Produces(MediaType.APPLICATION_JSON)
 | 
			
		||||
  public Backend get();
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway.api;
 | 
			
		||||
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.model.DataPoint;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMethod;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestParam;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
 | 
			
		||||
import javax.ws.rs.GET;
 | 
			
		||||
import javax.ws.rs.Path;
 | 
			
		||||
import javax.ws.rs.Produces;
 | 
			
		||||
import javax.ws.rs.QueryParam;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Created by jmorales on 28/09/16.
 | 
			
		||||
 */
 | 
			
		||||
@RequestMapping("/ws/data")
 | 
			
		||||
@Path("/ws/data")
 | 
			
		||||
@RestController
 | 
			
		||||
public interface DataServiceRemote {
 | 
			
		||||
  @RequestMapping(method = RequestMethod.GET, value = "/", produces = "application/json")
 | 
			
		||||
  @GET()
 | 
			
		||||
  @Path("/all")
 | 
			
		||||
  @Produces("application/json")
 | 
			
		||||
  public List<DataPoint> getAll();
 | 
			
		||||
 | 
			
		||||
  @RequestMapping(method = RequestMethod.GET, value = "/within", produces = "application/json")
 | 
			
		||||
  @GET()
 | 
			
		||||
  @Path("/within")
 | 
			
		||||
  @Produces("application/json")
 | 
			
		||||
  public List<DataPoint> findWithin(@RequestParam("lat1") @QueryParam("lat1") float lat1,
 | 
			
		||||
      @RequestParam("lon1") @QueryParam("lon1") float lon1, @RequestParam("lat2") @QueryParam("lat2") float lat2,
 | 
			
		||||
      @RequestParam("lon2") @QueryParam("lon2") float lon2);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway.helpers;
 | 
			
		||||
 | 
			
		||||
import feign.Response;
 | 
			
		||||
import feign.RetryableException;
 | 
			
		||||
import feign.codec.ErrorDecoder;
 | 
			
		||||
 | 
			
		||||
import static feign.FeignException.errorStatus;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Created by jmorales on 04/10/16.
 | 
			
		||||
 */
 | 
			
		||||
public class CustomErrorDecoder implements ErrorDecoder {
 | 
			
		||||
    @Override
 | 
			
		||||
    public Exception decode(String s, Response response) {
 | 
			
		||||
        if (response.status() == 503)
 | 
			
		||||
            return new RetryableException("Error 503 from server. Let's retry", null);
 | 
			
		||||
        else
 | 
			
		||||
            return errorStatus(s, response);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway.helpers;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
 | 
			
		||||
 | 
			
		||||
public interface EndpointRegistrar {
 | 
			
		||||
  List<Backend> register(String endpointName);
 | 
			
		||||
 | 
			
		||||
  List<Backend> unregister(String endpointName);
 | 
			
		||||
 | 
			
		||||
  void init();
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,104 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway.helpers;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import io.fabric8.kubernetes.api.model.Endpoints;
 | 
			
		||||
import io.fabric8.kubernetes.client.KubernetesClient;
 | 
			
		||||
import io.fabric8.kubernetes.client.KubernetesClientException;
 | 
			
		||||
import io.fabric8.kubernetes.client.Watch;
 | 
			
		||||
import io.fabric8.kubernetes.client.Watcher;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * Created by jmorales on 04/10/16.
 | 
			
		||||
 */
 | 
			
		||||
public class EndpointWatcher implements Watcher<Endpoints> {
 | 
			
		||||
 | 
			
		||||
  private static final Logger logger = LoggerFactory.getLogger(EndpointWatcher.class);
 | 
			
		||||
 | 
			
		||||
  private Watch watch;
 | 
			
		||||
 | 
			
		||||
  private String namespace;
 | 
			
		||||
 | 
			
		||||
  private String endpointName;
 | 
			
		||||
 | 
			
		||||
  private KubernetesClient client;
 | 
			
		||||
 | 
			
		||||
  private AtomicInteger endpointsAvailable = new AtomicInteger(0);
 | 
			
		||||
 | 
			
		||||
  private EndpointRegistrar callback;
 | 
			
		||||
 | 
			
		||||
  public EndpointWatcher(EndpointRegistrar callback, KubernetesClient client, String namespace, String endpointName) {
 | 
			
		||||
    this.client = client;
 | 
			
		||||
    this.namespace = namespace;
 | 
			
		||||
    this.endpointName = endpointName;
 | 
			
		||||
    this.callback = callback;
 | 
			
		||||
    logger.info("EndpointWatcher created for: endpoints/{} -n {}", endpointName, namespace);
 | 
			
		||||
 | 
			
		||||
    if (hasEndpoints()) {
 | 
			
		||||
      callback.register(endpointName);
 | 
			
		||||
    } else {
 | 
			
		||||
      callback.unregister(endpointName);
 | 
			
		||||
    }
 | 
			
		||||
    // Create the watch
 | 
			
		||||
    watch = client.endpoints().inNamespace(namespace).withName(endpointName).watch(this);
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private boolean hasEndpoints() {
 | 
			
		||||
    return hasEndpoints(client.endpoints().inNamespace(namespace).withName(endpointName).get());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private boolean hasEndpoints(Endpoints endpoints) {
 | 
			
		||||
    int size = getEndpointsAddressSize(endpoints);
 | 
			
		||||
    if (size > 0) {
 | 
			
		||||
      endpointsAvailable.set(size);
 | 
			
		||||
      return size > 0;
 | 
			
		||||
    } else
 | 
			
		||||
      return false;
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private int getEndpointsAddressSize(Endpoints endpoints) {
 | 
			
		||||
    if (endpoints.getSubsets().size() > 0)
 | 
			
		||||
      return endpoints.getSubsets().get(0).getAddresses().size();
 | 
			
		||||
    else
 | 
			
		||||
      return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void eventReceived(Action action, Endpoints endpoints) {
 | 
			
		||||
    int current = getEndpointsAddressSize(endpoints);
 | 
			
		||||
    int previous = endpointsAvailable.getAndSet(current);
 | 
			
		||||
    if (previous != current) {
 | 
			
		||||
      logger.info("Endpoints changed, from {} to {}", previous, current);
 | 
			
		||||
      if (previous == 0) {
 | 
			
		||||
        if (current > 0) {
 | 
			
		||||
          logger.info("There are endpoints for {} available. Registering", endpointName);
 | 
			
		||||
          callback.register(endpointName);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (current == 0) {
 | 
			
		||||
        if (previous > 0) {
 | 
			
		||||
          logger.info("There's no endpoints for {}. Unregistering", endpointName);
 | 
			
		||||
          callback.unregister(endpointName);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.info("Endpoints changes ignored");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onClose(KubernetesClientException e) {
 | 
			
		||||
    callback.init();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void close() {
 | 
			
		||||
    if (watch != null)
 | 
			
		||||
      watch.close();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,121 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway.model;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This represents a backend. Once a backend is registered, a call to the
 | 
			
		||||
 * backend to get this information about it will be issued.
 | 
			
		||||
 *
 | 
			
		||||
 * Created by jmorales on 24/08/16.
 | 
			
		||||
 */
 | 
			
		||||
public class Backend {
 | 
			
		||||
 | 
			
		||||
    public static final String BACKEND_TYPE_MARKER  = "marker";
 | 
			
		||||
    public static final String BACKEND_TYPE_CLUSTER = "cluster";
 | 
			
		||||
    public static final String BACKEND_TYPE_TEMP    = "temp";
 | 
			
		||||
    public static final String BACKEND_TYPE_HEATMAP = "heatmap";
 | 
			
		||||
 | 
			
		||||
    public static final String BACKEND_SCOPE_ALL   = "all";
 | 
			
		||||
    public static final String BACKEND_SCOPE_WITHIN = "within";
 | 
			
		||||
 | 
			
		||||
    private String id;
 | 
			
		||||
    private String displayName;
 | 
			
		||||
    private Coordinates center = new Coordinates("0", "0");
 | 
			
		||||
    private int zoom = 1;
 | 
			
		||||
    private int maxZoom = 1;
 | 
			
		||||
    private String type = BACKEND_TYPE_CLUSTER;
 | 
			
		||||
    private boolean visible = true;
 | 
			
		||||
    private String scope = BACKEND_SCOPE_ALL;
 | 
			
		||||
 | 
			
		||||
    public Backend() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Backend(String id, String displayName, String service) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
        this.displayName = displayName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Backend(String id, String displayName, Coordinates center, int zoom) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
        this.displayName = displayName;
 | 
			
		||||
        this.center = center;
 | 
			
		||||
        this.zoom = zoom;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getId() {
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setId(String id) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getDisplayName() {
 | 
			
		||||
        return displayName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setDisplayName(String displayName) {
 | 
			
		||||
        this.displayName = displayName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Coordinates getCenter() {
 | 
			
		||||
        return center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setCenter(Coordinates center) {
 | 
			
		||||
        this.center = center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getZoom() {
 | 
			
		||||
        return zoom;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setZoom(int zoom) {
 | 
			
		||||
        this.zoom = zoom;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getMaxZoom() {
 | 
			
		||||
        return maxZoom;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setMaxZoom(int maxzoom) {
 | 
			
		||||
        this.maxZoom = maxzoom;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getType() {
 | 
			
		||||
        return type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setType(String type) {
 | 
			
		||||
        this.type = type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isVisible() {
 | 
			
		||||
        return visible;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setVisible(boolean visible) {
 | 
			
		||||
        this.visible = visible;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getScope() {
 | 
			
		||||
        return scope;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setScope(String scope) {
 | 
			
		||||
        this.scope = scope;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "Backend{" +
 | 
			
		||||
                "id='" + id + '\'' +
 | 
			
		||||
                ", displayName='" + displayName + '\'' +
 | 
			
		||||
                ", center='" + center + '\'' +
 | 
			
		||||
                ", zoom='" + zoom + '\'' +
 | 
			
		||||
                ", type='" + type + '\'' +
 | 
			
		||||
                ", scope='" + scope + '\'' +
 | 
			
		||||
                ", visible='" + visible + '\'' +
 | 
			
		||||
                ", maxZoom='" + maxZoom + '\'' +
 | 
			
		||||
                '}';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,52 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway.model;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TODO: Remove???
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Created by jmorales on 18/08/16.
 | 
			
		||||
 */
 | 
			
		||||
public class Coordinates {
 | 
			
		||||
    private String latitude;
 | 
			
		||||
    private String longitude;
 | 
			
		||||
 | 
			
		||||
    public Coordinates() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Coordinates(String lat, String lng) {
 | 
			
		||||
        this.latitude = lat;
 | 
			
		||||
        this.longitude = lng;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Coordinates(List<?> position) {
 | 
			
		||||
        if (position.size() > 0)
 | 
			
		||||
            this.latitude = position.get(0).toString();
 | 
			
		||||
        if (position.size() > 1)
 | 
			
		||||
            this.longitude = position.get(1).toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getLatitude() {
 | 
			
		||||
        return latitude;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setLatitude(String lat) {
 | 
			
		||||
        this.latitude = lat;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getLongitude() {
 | 
			
		||||
        return longitude;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setLongitude(String lng) {
 | 
			
		||||
        this.longitude = lng;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "Coordinates{" +
 | 
			
		||||
                "lat='" + latitude + '\'' +
 | 
			
		||||
                ", lng='" + longitude + '\'' +
 | 
			
		||||
                '}';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,72 @@
 | 
			
		||||
package com.openshift.evg.roadshow.rest.gateway.model;
 | 
			
		||||
 | 
			
		||||
public class DataPoint {
 | 
			
		||||
 | 
			
		||||
    private String id;
 | 
			
		||||
    private String name;
 | 
			
		||||
 | 
			
		||||
    private Coordinates position;
 | 
			
		||||
 | 
			
		||||
    private String longitude;
 | 
			
		||||
    private String latitude;
 | 
			
		||||
 | 
			
		||||
    private String info;
 | 
			
		||||
 | 
			
		||||
    public DataPoint() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public DataPoint(String id, String name) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
        this.name = name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Object getName() {
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setName(String name) {
 | 
			
		||||
        this.name = name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Coordinates getPosition() {
 | 
			
		||||
        return position;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPosition(Coordinates position) {
 | 
			
		||||
        this.position = position;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Object getLongitude() {
 | 
			
		||||
        return longitude;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setLongitude(String longitude) {
 | 
			
		||||
        this.longitude = longitude;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Object getLatitude() {
 | 
			
		||||
        return latitude;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setLatitude(String latitude) {
 | 
			
		||||
        this.latitude = latitude;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getInfo() {
 | 
			
		||||
        return info;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setInfo(String info) {
 | 
			
		||||
        this.info = info;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "NationalPark{" +
 | 
			
		||||
                "id=" + id +
 | 
			
		||||
                ", name=" + name +
 | 
			
		||||
                ", coordinates=" + position +
 | 
			
		||||
                ", info=" + info +
 | 
			
		||||
                '}';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,2 @@
 | 
			
		||||
spring.application.name=parksmap
 | 
			
		||||
test=true
 | 
			
		||||
@ -0,0 +1,2 @@
 | 
			
		||||
spring.application.name=parksmap
 | 
			
		||||
test=false
 | 
			
		||||
@ -0,0 +1 @@
 | 
			
		||||
1
 | 
			
		||||
@ -0,0 +1,464 @@
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
	<title>Map Visualizer on OpenShift 4</title>
 | 
			
		||||
	<link rel="stylesheet" href="leaflet/leaflet.css"/>
 | 
			
		||||
	<link rel="stylesheet" href="parksmap.css"/>
 | 
			
		||||
	<link href="//fonts.googleapis.com/css?family=Oswald" rel="stylesheet" type="text/css">
 | 
			
		||||
	<link rel="stylesheet" href="leaflet/markercluster/MarkerCluster.css"/>
 | 
			
		||||
	<link rel="stylesheet" href="leaflet/markercluster/MarkerCluster.Default.css"/>
 | 
			
		||||
	<link rel="stylesheet" href="leaflet/font-awesome.min.css">
 | 
			
		||||
	<link rel="stylesheet" href="leaflet/bootstrap.min.css">
 | 
			
		||||
	<link rel="stylesheet" href="leaflet/beautify-marker/leaflet-beautify-marker-icon.css">
 | 
			
		||||
	<link rel="stylesheet" href="leaflet/messagebox/leaflet-messagebox.css">
 | 
			
		||||
 | 
			
		||||
	<script src="leaflet/leaflet.js"></script>
 | 
			
		||||
	<script src="leaflet/markercluster/leaflet.markercluster.js"></script>
 | 
			
		||||
 | 
			
		||||
	<script src="leaflet/heatmap/webgl-heatmap.js"></script>
 | 
			
		||||
	<script src="leaflet/heatmap/webgl-heatmap-leaflet.js"></script>
 | 
			
		||||
	<script src="leaflet/beautify-marker/leaflet-beautify-marker-icon.js"></script>
 | 
			
		||||
	<script src="leaflet/beautify-marker/leaflet-beautify-marker.js"></script>
 | 
			
		||||
	<script src="leaflet/messagebox/leaflet-messagebox.js"></script>
 | 
			
		||||
	<script src="leaflet/spin.js"></script>
 | 
			
		||||
	<script src="jquery-3.1.0.min.js"></script>
 | 
			
		||||
	<script src="jquery.spin.js"></script>
 | 
			
		||||
	<!-- For websockets -->
 | 
			
		||||
    <script src="sockjs-1.1.0.js"></script>
 | 
			
		||||
    <script src="stomp.js"></script>
 | 
			
		||||
 | 
			
		||||
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
 | 
			
		||||
 | 
			
		||||
	<style type="text/css">
 | 
			
		||||
		body {
 | 
			
		||||
			padding: 0;
 | 
			
		||||
			margin: 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		html, body, #map {
 | 
			
		||||
			height: 100%;
 | 
			
		||||
			font-family: 'Oswald';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.leaflet-container .leaflet-control-zoom {
 | 
			
		||||
			margin-left: 13px;
 | 
			
		||||
			margin-top: 70px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		#map {
 | 
			
		||||
			z-index: 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		#title {
 | 
			
		||||
			z-index: 2;
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			left: 10px;
 | 
			
		||||
		}
 | 
			
		||||
	</style>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
<h1 id="title" class="title">Map Visualizer on OpenShift 4</h1>
 | 
			
		||||
<div id="map"></div>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
 backend{
 | 
			
		||||
    id: "name";
 | 
			
		||||
    displayName: "displayName";
 | 
			
		||||
    center: ["",""],
 | 
			
		||||
    zoom: 1,
 | 
			
		||||
    visible: true,
 | 
			
		||||
    scope: [all,within],
 | 
			
		||||
    type: [marker,cluster,temp]
 | 
			
		||||
    maxzoom: 5,
 | 
			
		||||
    layer: L.markerClusterGroup()
 | 
			
		||||
 };
 | 
			
		||||
 */
 | 
			
		||||
    /************************
 | 
			
		||||
     * BEGIN OF WEBSOCKETS COMM
 | 
			
		||||
     ************************/
 | 
			
		||||
    var stompClient = null;
 | 
			
		||||
 | 
			
		||||
 		const isGeolocationAvailable = () => {
 | 
			
		||||
			if (location.hostname === "localhost" && "geolocation" in navigator) return true;
 | 
			
		||||
			if (location.protocol.substr(0, 5) === "https" && "geolocation" in navigator) return true;
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 		const getGeolocation = () => {
 | 
			
		||||
			let p = new Promise((resolve, reject) => {
 | 
			
		||||
				navigator.geolocation.getCurrentPosition(pos => {
 | 
			
		||||
					console.log(`User location is ${pos.coords.latitude},${pos.coords.longitude}`);
 | 
			
		||||
					resolve(pos.coords);
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
			return p;
 | 
			
		||||
		 };
 | 
			
		||||
 | 
			
		||||
    function connect() {
 | 
			
		||||
        var socket = new SockJS('/socks-backends');
 | 
			
		||||
        stompClient = Stomp.over(socket);
 | 
			
		||||
        stompClient.connect({}, function(frame) {
 | 
			
		||||
            // console.log('Connected: ' + frame);
 | 
			
		||||
            stompClient.subscribe('/topic/add', function(message){
 | 
			
		||||
                var backend = getBackend(JSON.parse(message.body));
 | 
			
		||||
				addBackend(backend);
 | 
			
		||||
            });
 | 
			
		||||
            stompClient.subscribe('/topic/remove', function(message){
 | 
			
		||||
                var backend = getBackend(JSON.parse(message.body));
 | 
			
		||||
                removeBackend(backend.id);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    // TODO: Reconnect automatically. When redeploying application, need to reconnect, otherwise page needs to be reloaded
 | 
			
		||||
    // Look for solution like: https://github.com/jmesnil/stomp-websocket/issues/81#issuecomment-246854734
 | 
			
		||||
 | 
			
		||||
    // Backend types: {popup_marker,marker,heatmap,temp}
 | 
			
		||||
    var getBackend = function(backendFromServer){
 | 
			
		||||
		var backend;
 | 
			
		||||
		if (backendFromServer.type=='temp'){
 | 
			
		||||
			backend = {
 | 
			
		||||
				id: backendFromServer.id,
 | 
			
		||||
				displayName: backendFromServer.displayName,
 | 
			
		||||
				center: backendFromServer.center,
 | 
			
		||||
				zoom: backendFromServer.zoom,
 | 
			
		||||
				type: backendFromServer.type,
 | 
			
		||||
				visible: backendFromServer.visible,
 | 
			
		||||
				scope: backendFromServer.scope,
 | 
			
		||||
				maxzoom: backendFromServer.maxZoom,
 | 
			
		||||
				layer: new L.layerGroup()
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
		else if (backendFromServer.type=='marker'){
 | 
			
		||||
			backend = {
 | 
			
		||||
				id: backendFromServer.id,
 | 
			
		||||
				displayName: backendFromServer.displayName,
 | 
			
		||||
				center: backendFromServer.center,
 | 
			
		||||
				zoom: backendFromServer.zoom,
 | 
			
		||||
				type: backendFromServer.type,
 | 
			
		||||
				visible: backendFromServer.visible,
 | 
			
		||||
				scope: backendFromServer.scope,
 | 
			
		||||
				maxzoom: backendFromServer.maxZoom,
 | 
			
		||||
				layer: new L.layerGroup()
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
		else if (backendFromServer.type=='cluster'){
 | 
			
		||||
			// TODO: Get scope, type and visible from the backend
 | 
			
		||||
        	backend = {
 | 
			
		||||
            	id: backendFromServer.id,
 | 
			
		||||
            	displayName: backendFromServer.displayName,
 | 
			
		||||
            	center: backendFromServer.center,
 | 
			
		||||
            	zoom: backendFromServer.zoom,
 | 
			
		||||
				type: backendFromServer.type,
 | 
			
		||||
				visible: backendFromServer.visible,
 | 
			
		||||
				scope: backendFromServer.scope,
 | 
			
		||||
				maxzoom: backendFromServer.maxZoom,
 | 
			
		||||
            	layer: new L.markerClusterGroup()
 | 
			
		||||
        	};
 | 
			
		||||
		}
 | 
			
		||||
		else if (backendFromServer.type=='heatmap'){
 | 
			
		||||
			// TODO: Get scope, type and visible from the backend
 | 
			
		||||
			backend = {
 | 
			
		||||
				id: backendFromServer.id,
 | 
			
		||||
				displayName: backendFromServer.displayName,
 | 
			
		||||
				center: backendFromServer.center,
 | 
			
		||||
				zoom: backendFromServer.zoom,
 | 
			
		||||
				type: backendFromServer.type,
 | 
			
		||||
				visible: backendFromServer.visible,
 | 
			
		||||
				scope: backendFromServer.scope,
 | 
			
		||||
				maxzoom: backendFromServer.maxZoom,
 | 
			
		||||
				layer: L.webGLHeatmap({size: 30000, autoresize: true, alphaRange: 1, opacity: 1}) // opacity close to 0, less clear
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
        return backend;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function disconnect() {
 | 
			
		||||
        if (stompClient != null) {
 | 
			
		||||
            stompClient.disconnect();
 | 
			
		||||
        }
 | 
			
		||||
        setConnected(false);
 | 
			
		||||
        console.log("Disconnected");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /************************
 | 
			
		||||
     * END OF WEBSOCKETS COMM
 | 
			
		||||
     ************************/
 | 
			
		||||
 | 
			
		||||
	var backends = new Map();
 | 
			
		||||
 | 
			
		||||
	var mbAttr = 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
 | 
			
		||||
					'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
 | 
			
		||||
					'Imagery © <a href="http://mapbox.com">Mapbox</a>';
 | 
			
		||||
	var mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}';
 | 
			
		||||
	var mapToken = 'pk.eyJ1IjoiZ3JhaGFtZHVtcGxldG9uIiwiYSI6ImNpemR6cnFhbTF0YWszMnA5eTJ0dXY1ZW8ifQ.-91_VlsyyskWAWF54GYFMg';
 | 
			
		||||
 | 
			
		||||
	var streets = L.tileLayer(mbUrl, {
 | 
			
		||||
		id: 'mapbox/streets-v11',
 | 
			
		||||
		attribution: mbAttr,
 | 
			
		||||
		tileSize: 512,
 | 
			
		||||
		maxZoom: 18,
 | 
			
		||||
		zoomOffset: -1,
 | 
			
		||||
		accessToken: mapToken
 | 
			
		||||
	});
 | 
			
		||||
	var grayscale = L.tileLayer(mbUrl, {
 | 
			
		||||
		id: 'mapbox/light-v10',
 | 
			
		||||
		attribution: mbAttr,
 | 
			
		||||
		tileSize: 512,
 | 
			
		||||
		maxZoom: 18,
 | 
			
		||||
		zoomOffset: -1,
 | 
			
		||||
		accessToken: mapToken
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	let coords = [27.60567, -18.19336];
 | 
			
		||||
 | 
			
		||||
	var map = L.map('map', {
 | 
			
		||||
		center: coords,
 | 
			
		||||
		zoom: 3,
 | 
			
		||||
		layers: [grayscale]
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if (isGeolocationAvailable()) {
 | 
			
		||||
		getGeolocation().then(position => {
 | 
			
		||||
			map.setView([position.latitude, position.longitude], 3);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	var baseLayers = {
 | 
			
		||||
		"Grayscale": grayscale,
 | 
			
		||||
		"Streets": streets
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	var overlays = {};
 | 
			
		||||
 | 
			
		||||
	var controls = L.control.layers(baseLayers, overlays, {collapsed:false} );
 | 
			
		||||
	controls.addTo(map);
 | 
			
		||||
 | 
			
		||||
	// Add messageBox
 | 
			
		||||
	var options = { timeout: 5000, position: 'bottomleft' }
 | 
			
		||||
	var msgBox = L.control.messagebox(options).addTo(map);
 | 
			
		||||
 | 
			
		||||
	async function addBackend(backend){
 | 
			
		||||
		console.log("Adding layer for backend: " + backend.id);
 | 
			
		||||
 | 
			
		||||
        // Check that the backend (by name did not exist, in which case, we'll remove the old one)
 | 
			
		||||
        if (backends.has(backend.id)){
 | 
			
		||||
            console.log("Removing old backend: " + backend.id);
 | 
			
		||||
            removeBackend(backend.id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        backends.set(backend.id, backend);
 | 
			
		||||
 | 
			
		||||
		if (backend.scope=='all') {
 | 
			
		||||
			queryAll(backend);
 | 
			
		||||
		}else if (backend.scope=='within'){
 | 
			
		||||
			queryWithin(backend);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Add layer to the map
 | 
			
		||||
		map.addLayer(backend.layer);
 | 
			
		||||
		controls.addOverlay(backend.layer, backend.displayName);
 | 
			
		||||
 | 
			
		||||
		// Focus map on the backend center and zoom
 | 
			
		||||
		
 | 
			
		||||
		// if geolocation is available, center on current position.
 | 
			
		||||
		let latitude = backend.center.latitude;
 | 
			
		||||
		let longitude = backend.center.longitude;
 | 
			
		||||
 | 
			
		||||
		if (isGeolocationAvailable()) {
 | 
			
		||||
			let position = await getGeolocation();
 | 
			
		||||
			latitude = position.latitude;
 | 
			
		||||
			longitude = position.longitude;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		map.setView([latitude, longitude], backend.zoom);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function queryAll(backend){
 | 
			
		||||
		// Expected dataPoint { name, latitude, longitude, details }
 | 
			
		||||
		$('#map').spin();
 | 
			
		||||
		$.get("/ws/data/all?service="+backend.id, function(data){
 | 
			
		||||
			console.log("Displaying " + data.length + " points from backend " + backend.id);
 | 
			
		||||
			var dataPoints = [];
 | 
			
		||||
			for (var i = 0; i < data.length; i++){
 | 
			
		||||
				dataPoint = data[i];
 | 
			
		||||
				if (backend.type=='cluster') {
 | 
			
		||||
					showCluster(backend,dataPoint);
 | 
			
		||||
				}else if (backend.type=='heatmap') {
 | 
			
		||||
					showHeatmap(backend,dataPoint);
 | 
			
		||||
				}else if (backend.type=='marker') {
 | 
			
		||||
					showMarker(backend,dataPoint);
 | 
			
		||||
				}else if (backend.type=='temp') {
 | 
			
		||||
					showTemp(backend,dataPoint);
 | 
			
		||||
				}else{
 | 
			
		||||
					console.log("Backend type " + backend.type + " not supported")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			console.log("Done");
 | 
			
		||||
			$('#map').spin(false);
 | 
			
		||||
		}, "json");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function queryWithin(backend) {
 | 
			
		||||
		if (backend.visible == true && backend.scope == 'within'){
 | 
			
		||||
			if (backend.maxzoom<=map.getZoom()) {
 | 
			
		||||
				console.log("Querying data for backend: " + backend.id);
 | 
			
		||||
 | 
			
		||||
				var bounds = map.getBounds();
 | 
			
		||||
				var url = "/ws/data/within?service=" + backend.id + "&lat1=" + bounds.getNorthWest().lat + "&lon1=" + bounds.getNorthWest().lng + "&lat2=" + bounds.getSouthEast().lat + "&lon2=" + bounds.getSouthEast().lng;
 | 
			
		||||
 | 
			
		||||
				// Expected dataPoint { name, latitude, longitude, details }
 | 
			
		||||
				$('#map').spin();
 | 
			
		||||
				$.get(url, function (data) {
 | 
			
		||||
					// Clear previous points
 | 
			
		||||
					backend.layer.clearLayers();
 | 
			
		||||
 | 
			
		||||
					console.log("Displaying " + data.length + " points from backend " + backend.id);
 | 
			
		||||
					var dataPoints = [];
 | 
			
		||||
					for (var i = 0; i < data.length; i++) {
 | 
			
		||||
						dataPoint = data[i];
 | 
			
		||||
						if (backend.type == 'cluster') {
 | 
			
		||||
							showCluster(backend, dataPoint);
 | 
			
		||||
						} else if (backend.type == 'heatmap') {
 | 
			
		||||
							showHeatmap(backend, dataPoint);
 | 
			
		||||
						} else if (backend.type == 'marker') {
 | 
			
		||||
							showMarker(backend, dataPoint);
 | 
			
		||||
						} else if (backend.type == 'temp') {
 | 
			
		||||
							showTemp(backend, dataPoint);
 | 
			
		||||
						} else {
 | 
			
		||||
							console.log("Backend type " + backend.type + " not supported")
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					console.log("Done");
 | 
			
		||||
					$('#map').spin(false);
 | 
			
		||||
				}, "json");
 | 
			
		||||
			}else{
 | 
			
		||||
				msgBox.show('No data querying for ' + backend.displayName + ' at this level of zoom');
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function queryWithinAll(){
 | 
			
		||||
		backends.forEach(queryWithin);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function removeBackend(backendId){
 | 
			
		||||
	    if (backends.has(backendId)) {
 | 
			
		||||
            var backend = backends.get(backendId);
 | 
			
		||||
            controls.removeLayer(backend.layer);
 | 
			
		||||
            map.removeLayer(backend.layer);
 | 
			
		||||
            backends.delete(backend.id);
 | 
			
		||||
        } else{
 | 
			
		||||
            console.log("Trying to remove a non existant backend: " + backendId);
 | 
			
		||||
        }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: Change initial load to only queryWithins
 | 
			
		||||
	function initialLoad(){
 | 
			
		||||
		backends.clear();
 | 
			
		||||
		$.get("/ws/backends/list", function(data){
 | 
			
		||||
			for (var i = 0; i < data.length; i++){
 | 
			
		||||
				var backendFromServer = data[i];
 | 
			
		||||
				var backend = getBackend(backendFromServer);
 | 
			
		||||
                addBackend(backend);
 | 
			
		||||
			}
 | 
			
		||||
		}, "json");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function showCluster(backend, dataPoint){
 | 
			
		||||
		var popupInformation = "<b>" + dataPoint.name + "</b></br>";
 | 
			
		||||
		// TODO: Work additionalInfo
 | 
			
		||||
		var marker = L.marker([dataPoint.latitude, dataPoint.longitude]).bindPopup(popupInformation);
 | 
			
		||||
		marker.addTo(backend.layer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function showMarker(backend, dataPoint){
 | 
			
		||||
		var popupInformation = "<b>" + dataPoint.name + "</b></br>";
 | 
			
		||||
		// TODO: Work additionalInfo
 | 
			
		||||
		var marker = L.marker([dataPoint.latitude, dataPoint.longitude]).bindPopup(popupInformation);
 | 
			
		||||
		marker.addTo(backend.layer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function showHeatmap(backend, dataPoint){
 | 
			
		||||
		if (isNaN(dataPoint.latitude) || isNaN(dataPoint.longitude)){
 | 
			
		||||
			console.log("Invalid Data Point: " + dataPoint);
 | 
			
		||||
		}else{
 | 
			
		||||
			try {
 | 
			
		||||
				var temp = dataPoint.info.match(/.*Avg:(.*)$/);
 | 
			
		||||
				if (isNaN(temp[1])){
 | 
			
		||||
					console.log("No temp in this datapoint: " + dataPoint);
 | 
			
		||||
				}else {
 | 
			
		||||
					if (dataPoint.latitude!=null && dataPoint.longitude!=null)
 | 
			
		||||
						backend.layer.addDataPoint(dataPoint.latitude, dataPoint.longitude, Math.round(temp[1])+50);
 | 
			
		||||
				}
 | 
			
		||||
			}catch(err){
 | 
			
		||||
				console.log("Error with this dataPoint [" + dataPoint + "]. Error="+ err.message);
 | 
			
		||||
			}
 | 
			
		||||
			backend.layer.draw();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function showTemp(backend, dataPoint) {
 | 
			
		||||
		try{
 | 
			
		||||
			var temp = dataPoint.info.match(/.*Avg:(.*)$/);
 | 
			
		||||
			if (isNaN(temp[1])){
 | 
			
		||||
				console.log("No temp in this datapoint: " + dataPoint);
 | 
			
		||||
			}else {
 | 
			
		||||
				if (dataPoint.latitude!=null && dataPoint.longitude!=null) {
 | 
			
		||||
					var iconColor;
 | 
			
		||||
					if (temp[1] < 0) iconColor = 'royalblue';
 | 
			
		||||
					if (temp[1] >= 0) iconColor = 'skyblue';
 | 
			
		||||
					if (temp[1] >= 10) iconColor = 'yellow';
 | 
			
		||||
					if (temp[1] >= 20) iconColor = 'orange';
 | 
			
		||||
					if (temp[1] >= 30) iconColor = 'orangered';
 | 
			
		||||
					if (temp[1] >= 40) iconColor = 'red';
 | 
			
		||||
					var iconSize = (map.getZoom() - 5 ) * 3;
 | 
			
		||||
					var options = { iconShape: 'circle-dot', borderWidth: iconSize, borderColor: iconColor };
 | 
			
		||||
					var marker = L.marker([dataPoint.latitude, dataPoint.longitude], { icon: L.BeautifyIcon.icon(options) })
 | 
			
		||||
							 .bindPopup("<b>" + temp[0] + "</b>");
 | 
			
		||||
					marker.addTo(backend.layer);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}catch(err){
 | 
			
		||||
			console.log("Error with this dataPoint [" + dataPoint + "]. Error="+ err.message);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Set a timeout to load/unload backends
 | 
			
		||||
	setTimeout(function(){
 | 
			
		||||
		// Get notified of registrations/unregistrations
 | 
			
		||||
 | 
			
		||||
	}, 5000);
 | 
			
		||||
 | 
			
		||||
	map.whenReady(initialLoad).whenReady(connect);
 | 
			
		||||
	map.on('moveend', queryWithinAll);
 | 
			
		||||
	map.on('moveend', function() {
 | 
			
		||||
		console.log("map was panned!");
 | 
			
		||||
		console.log("zoom: " + map.getZoom());    // prints out zoom level
 | 
			
		||||
		console.log("center: " + map.getCenter());    // prints out map center
 | 
			
		||||
	});
 | 
			
		||||
	map.on('overlayadd', onOverlayAdd).on('overlayremove', onOverlayRemove);
 | 
			
		||||
 | 
			
		||||
	function onOverlayAdd(e){
 | 
			
		||||
		backends.forEach(function (backend,backendId,thisMap) {
 | 
			
		||||
			if (backend.displayName==e.name){
 | 
			
		||||
				// Execute the actions
 | 
			
		||||
				backend.visible = true;
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function onOverlayRemove(e){
 | 
			
		||||
		backends.forEach(function (backend,backendId,thisMap) {
 | 
			
		||||
			if (backend.displayName==e.name){
 | 
			
		||||
				// Execute the actions
 | 
			
		||||
				backend.visible = false;
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										8755
									
								
								2024-03-14-scalable-selenium-pipelines/parksmap/src/main/resources/static/jquery-2.0.0.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8755
									
								
								2024-03-14-scalable-selenium-pipelines/parksmap/src/main/resources/static/jquery-2.0.0.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -0,0 +1,79 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) 2011-2014 Felix Gnass
 | 
			
		||||
 * Licensed under the MIT license
 | 
			
		||||
 * http://spin.js.org/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Basic Usage:
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
$('#el').spin() // Creates a default Spinner using the text color of #el.
 | 
			
		||||
$('#el').spin({ ... }) // Creates a Spinner using the provided options.
 | 
			
		||||
 | 
			
		||||
$('#el').spin(false) // Stops and removes the spinner.
 | 
			
		||||
 | 
			
		||||
Using Presets:
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
$('#el').spin('small') // Creates a 'small' Spinner using the text color of #el.
 | 
			
		||||
$('#el').spin('large', '#fff') // Creates a 'large' white Spinner.
 | 
			
		||||
 | 
			
		||||
Adding a custom preset:
 | 
			
		||||
=======================
 | 
			
		||||
 | 
			
		||||
$.fn.spin.presets.flower = {
 | 
			
		||||
  lines:   9
 | 
			
		||||
, length: 10
 | 
			
		||||
, width:  20
 | 
			
		||||
, radius:  0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$('#el').spin('flower', 'red')
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
;(function(factory) {
 | 
			
		||||
 | 
			
		||||
  if (typeof exports == 'object') {
 | 
			
		||||
    // CommonJS
 | 
			
		||||
    factory(require('jquery'), require('spin.js'))
 | 
			
		||||
  } else if (typeof define == 'function' && define.amd) {
 | 
			
		||||
    // AMD, register as anonymous module
 | 
			
		||||
    define(['jquery', 'spin'], factory)
 | 
			
		||||
  } else {
 | 
			
		||||
    // Browser globals
 | 
			
		||||
    if (!window.Spinner) throw new Error('Spin.js not present')
 | 
			
		||||
    factory(window.jQuery, window.Spinner)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}(function($, Spinner) {
 | 
			
		||||
 | 
			
		||||
  $.fn.spin = function(opts, color) {
 | 
			
		||||
 | 
			
		||||
    return this.each(function() {
 | 
			
		||||
      var $this = $(this)
 | 
			
		||||
        , data = $this.data()
 | 
			
		||||
 | 
			
		||||
      if (data.spinner) {
 | 
			
		||||
        data.spinner.stop()
 | 
			
		||||
        delete data.spinner
 | 
			
		||||
      }
 | 
			
		||||
      if (opts !== false) {
 | 
			
		||||
        opts = $.extend(
 | 
			
		||||
          { color: color || $this.css('color') }
 | 
			
		||||
        , $.fn.spin.presets[opts] || opts
 | 
			
		||||
        )
 | 
			
		||||
        data.spinner = new Spinner(opts).spin(this)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $.fn.spin.presets = {
 | 
			
		||||
    tiny:  { lines:  8, length: 2, width: 2, radius: 3 }
 | 
			
		||||
  , small: { lines:  8, length: 4, width: 3, radius: 5 }
 | 
			
		||||
  , large: { lines: 10, length: 8, width: 4, radius: 8 }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}));
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
.leaflet-marker-photo { 
 | 
			
		||||
	border: 2px solid #fff; 
 | 
			
		||||
	box-shadow: 3px 3px 10px #888; 
 | 
			
		||||
}	
 | 
			
		||||
 | 
			
		||||
.leaflet-marker-photo div { 
 | 
			
		||||
    width: 100%;  
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background-size: cover;    
 | 
			
		||||
    background-position: center center;
 | 
			
		||||
    background-repeat: no-repeat;
 | 
			
		||||
}	
 | 
			
		||||
 | 
			
		||||
.leaflet-marker-photo b {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: -7px;
 | 
			
		||||
	right: -11px;
 | 
			
		||||
	color: #555;
 | 
			
		||||
	background-color: #fff;
 | 
			
		||||
	border-radius: 8px;
 | 
			
		||||
	height: 12px;
 | 
			
		||||
	min-width: 12px;
 | 
			
		||||
	line-height: 12px;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	padding: 3px;
 | 
			
		||||
	box-shadow: 0 3px 14px rgba(0,0,0,0.4);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,83 @@
 | 
			
		||||
L.Photo = L.FeatureGroup.extend({
 | 
			
		||||
	options: {
 | 
			
		||||
		icon: {						
 | 
			
		||||
			iconSize: [40, 40]
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	initialize: function (photos, options) {
 | 
			
		||||
		L.setOptions(this, options);
 | 
			
		||||
		L.FeatureGroup.prototype.initialize.call(this, photos);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	addLayers: function (photos) {
 | 
			
		||||
		if (photos) {
 | 
			
		||||
			for (var i = 0, len = photos.length; i < len; i++) {
 | 
			
		||||
				this.addLayer(photos[i]);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return this;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	addLayer: function (photo) {	
 | 
			
		||||
		L.FeatureGroup.prototype.addLayer.call(this, this.createMarker(photo));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	createMarker: function (photo) {
 | 
			
		||||
		var marker = L.marker(photo, {
 | 
			
		||||
			icon: L.divIcon(L.extend({
 | 
			
		||||
				html: '<div style="background-image: url(' + photo.thumbnail + ');"></div>',
 | 
			
		||||
				className: 'leaflet-marker-photo'
 | 
			
		||||
			}, photo, this.options.icon)),
 | 
			
		||||
			title: photo.caption || ''
 | 
			
		||||
		});		
 | 
			
		||||
		marker.photo = photo;
 | 
			
		||||
		return marker;
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
L.photo = function (photos, options) {
 | 
			
		||||
	return new L.Photo(photos, options);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
if (L.MarkerClusterGroup) {
 | 
			
		||||
 | 
			
		||||
	L.Photo.Cluster = L.MarkerClusterGroup.extend({
 | 
			
		||||
		options: {
 | 
			
		||||
			featureGroup: L.photo,		
 | 
			
		||||
			maxClusterRadius: 100,		
 | 
			
		||||
			showCoverageOnHover: false,
 | 
			
		||||
			iconCreateFunction: function(cluster) {
 | 
			
		||||
				return new L.DivIcon(L.extend({
 | 
			
		||||
					className: 'leaflet-marker-photo', 
 | 
			
		||||
					html: '<div style="background-image: url(' + cluster.getAllChildMarkers()[0].photo.thumbnail + ');"></div><b>' + cluster.getChildCount() + '</b>'
 | 
			
		||||
				}, this.icon));
 | 
			
		||||
		   	},	
 | 
			
		||||
			icon: {						
 | 
			
		||||
				iconSize: [40, 40]
 | 
			
		||||
			}		   		
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		initialize: function (options) {	
 | 
			
		||||
			options = L.Util.setOptions(this, options);
 | 
			
		||||
			L.MarkerClusterGroup.prototype.initialize.call(this);
 | 
			
		||||
			this._photos = options.featureGroup(null, options);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		add: function (photos) {
 | 
			
		||||
			this.addLayer(this._photos.addLayers(photos));
 | 
			
		||||
			return this;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		clear: function () {
 | 
			
		||||
			this._photos.clearLayers();
 | 
			
		||||
			this.clearLayers();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	L.photo.cluster = function (options) {
 | 
			
		||||
		return new L.Photo.Cluster(options);	
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
/*
 | 
			
		||||
  Leaflet.BeautifyIcon, a plugin that adds colorful iconic markers for Leaflet by giving full control of style to end user, It has also ability to adjust font awesome
 | 
			
		||||
  and glyphicon icons
 | 
			
		||||
  (c) 2016-2017, Muhammad Arslan Sajid
 | 
			
		||||
  http://leafletjs.com
 | 
			
		||||
*/
 | 
			
		||||
.beautify-marker {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    font-weight: 700;
 | 
			
		||||
    font-family: monospace;
 | 
			
		||||
    position:absolute;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .beautify-marker.circle {
 | 
			
		||||
        border-radius: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .beautify-marker.circle-dot, .beautify-marker.doughnut {
 | 
			
		||||
        border-radius: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .beautify-marker.marker {
 | 
			
		||||
        border-top-left-radius: 50%;
 | 
			
		||||
        border-top-right-radius: 50% 100%;
 | 
			
		||||
        border-bottom-left-radius: 100% 50%;
 | 
			
		||||
        border-bottom-right-radius: 0%;
 | 
			
		||||
    }
 | 
			
		||||
@ -0,0 +1,198 @@
 | 
			
		||||
/*
 | 
			
		||||
  Leaflet.BeautifyIcon, a plugin that adds colorful iconic markers for Leaflet by giving full control of style to end user, It has also ability to adjust font awesome
 | 
			
		||||
  and glyphicon icons
 | 
			
		||||
  (c) 2016-2017, Muhammad Arslan Sajid
 | 
			
		||||
  http://leafletjs.com
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/*global L of leaflet*/
 | 
			
		||||
 | 
			
		||||
(function (window, document, undefined) {
 | 
			
		||||
 | 
			
		||||
    'use strict';
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    * Leaflet.BeautifyIcon assumes that you have already included the Leaflet library.
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    * Default settings for various style markers
 | 
			
		||||
    */
 | 
			
		||||
    var defaults = {
 | 
			
		||||
 | 
			
		||||
        iconColor: '#1EB300',
 | 
			
		||||
 | 
			
		||||
        iconAnchor: {
 | 
			
		||||
            'marker': [14, 36]
 | 
			
		||||
            , 'circle': [11, 10]
 | 
			
		||||
            , 'circle-dot': [5, 5]
 | 
			
		||||
            , 'rectangle-dot': [5, 6]
 | 
			
		||||
            , 'doughnut': [8, 8]
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        popupAnchor: {
 | 
			
		||||
            'marker': [0, -25]
 | 
			
		||||
            , 'circle': [-3, -76]
 | 
			
		||||
            , 'circle-dot': [0, -2]
 | 
			
		||||
            , 'rectangle-dot': [0, -2]
 | 
			
		||||
            , 'doughnut': [0, -2]
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        innerIconAnchor: {
 | 
			
		||||
            'marker': [-2, 5]
 | 
			
		||||
            , 'circle': [0, 2]
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        iconSize: {
 | 
			
		||||
            'marker': [28, 28]
 | 
			
		||||
            , 'circle': [22, 22]
 | 
			
		||||
            , 'circle-dot': [2, 2]
 | 
			
		||||
            , 'rectangle-dot': [2, 2]
 | 
			
		||||
            , 'doughnut': [15, 15]
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    L.BeautifyIcon = {
 | 
			
		||||
 | 
			
		||||
        Icon: L.Icon.extend({
 | 
			
		||||
 | 
			
		||||
            options: {
 | 
			
		||||
                icon: 'leaf'
 | 
			
		||||
               , iconSize: defaults.iconSize.circle
 | 
			
		||||
               , iconAnchor: defaults.iconAnchor.circle
 | 
			
		||||
               , iconShape: 'circle'
 | 
			
		||||
               , iconStyle: ''
 | 
			
		||||
               , innerIconAnchor: [0, 3] // circle with fa or glyphicon or marker with text
 | 
			
		||||
               , innerIconStyle: ''
 | 
			
		||||
               , isAlphaNumericIcon: false
 | 
			
		||||
               , text: 1
 | 
			
		||||
               , borderColor: defaults.iconColor
 | 
			
		||||
               , borderWidth: 2
 | 
			
		||||
               , borderStyle: 'solid'
 | 
			
		||||
               , backgroundColor: 'white'
 | 
			
		||||
               , textColor: defaults.iconColor
 | 
			
		||||
               , customClasses: ''
 | 
			
		||||
               , spin: false
 | 
			
		||||
               , prefix: 'fa'
 | 
			
		||||
               , html: ''
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            initialize: function (options) {
 | 
			
		||||
 | 
			
		||||
                this.applyDefaults(options);
 | 
			
		||||
                this.options = L.Util.setOptions(this, options);
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            applyDefaults: function (options) {
 | 
			
		||||
 | 
			
		||||
                if (options) {
 | 
			
		||||
                    if (!options.iconSize && options.iconShape) {
 | 
			
		||||
                        options.iconSize = defaults.iconSize[options.iconShape];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!options.iconAnchor && options.iconShape) {
 | 
			
		||||
                        options.iconAnchor = defaults.iconAnchor[options.iconShape];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!options.popupAnchor && options.iconShape) {
 | 
			
		||||
                        options.popupAnchor = defaults.popupAnchor[options.iconShape];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!options.innerIconAnchor) {
 | 
			
		||||
                        // if icon is type of circle or marker
 | 
			
		||||
                        if (options.iconShape === 'circle' || options.iconShape === 'marker') {
 | 
			
		||||
                            if (options.iconShape === 'circle' && options.isAlphaNumericIcon) { // if circle with text
 | 
			
		||||
                                options.innerIconAnchor = [0, -1];
 | 
			
		||||
                            }
 | 
			
		||||
                            else if (options.iconShape === 'marker' && !options.isAlphaNumericIcon) {// marker with icon
 | 
			
		||||
                                options.innerIconAnchor = defaults.innerIconAnchor[options.iconShape];
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            createIcon: function () {
 | 
			
		||||
 | 
			
		||||
                var iconDiv = document.createElement('div')
 | 
			
		||||
                    , options = this.options;
 | 
			
		||||
 | 
			
		||||
                iconDiv.innerHTML = !options.html ? this.createIconInnerHtml() : options.html;
 | 
			
		||||
                this._setIconStyles(iconDiv);
 | 
			
		||||
 | 
			
		||||
                return iconDiv;
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            createIconInnerHtml: function () {
 | 
			
		||||
 | 
			
		||||
                var options = this.options;
 | 
			
		||||
 | 
			
		||||
                if (options.iconShape === 'circle-dot' || options.iconShape === 'rectangle-dot' || options.iconShape === 'doughnut') {
 | 
			
		||||
                    return '';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var innerIconStyle = this.getInnerIconStyle(options);
 | 
			
		||||
                if (options.isAlphaNumericIcon) {
 | 
			
		||||
                    return '<div style="' + innerIconStyle + '">' + options.text + '</div>';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var spinClass = '';
 | 
			
		||||
                if (options.spin) {
 | 
			
		||||
                    spinClass = ' fa-spin';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return '<i class="' + options.prefix + ' ' + options.prefix + '-' + options.icon + spinClass + '" style="' + innerIconStyle + '"></i>';
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            getInnerIconStyle: function (options) {
 | 
			
		||||
 | 
			
		||||
                var innerAnchor = L.point(options.innerIconAnchor)
 | 
			
		||||
                return 'color:' + options.textColor + ';margin-top:' + innerAnchor.y + 'px; margin-left:' + innerAnchor.x + 'px;' + options.innerIconStyle;
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            _setIconStyles: function (iconDiv) {
 | 
			
		||||
 | 
			
		||||
                var options = this.options
 | 
			
		||||
                    , size = L.point(options.iconSize)
 | 
			
		||||
                    , anchor = L.point(options.iconAnchor);
 | 
			
		||||
 | 
			
		||||
                iconDiv.className = 'beautify-marker ';
 | 
			
		||||
 | 
			
		||||
                if (options.iconShape) {
 | 
			
		||||
                    iconDiv.className += options.iconShape;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (options.customClasses) {
 | 
			
		||||
                    iconDiv.className += ' ' + options.customClasses;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                iconDiv.style.backgroundColor = options.backgroundColor;
 | 
			
		||||
                iconDiv.style.color = options.textColor;
 | 
			
		||||
                iconDiv.style.borderColor = options.borderColor;
 | 
			
		||||
                iconDiv.style.borderWidth = options.borderWidth + 'px';
 | 
			
		||||
                iconDiv.style.borderStyle = options.borderStyle;
 | 
			
		||||
 | 
			
		||||
                if (size) {
 | 
			
		||||
                    iconDiv.style.width = size.x + 'px';
 | 
			
		||||
                    iconDiv.style.height = size.y + 'px';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (anchor) {
 | 
			
		||||
                    iconDiv.style.marginLeft = (-anchor.x) + 'px';
 | 
			
		||||
                    iconDiv.style.marginTop = (-anchor.y) + 'px';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (options.iconStyle) {
 | 
			
		||||
                    var cStyle = iconDiv.getAttribute('style');
 | 
			
		||||
                    cStyle += options.iconStyle;
 | 
			
		||||
                    iconDiv.setAttribute('style', cStyle);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    L.BeautifyIcon.icon = function (options) {
 | 
			
		||||
        return new L.BeautifyIcon.Icon(options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}(this, document));
 | 
			
		||||
@ -0,0 +1,58 @@
 | 
			
		||||
/*
 | 
			
		||||
  Leaflet.BeautifyMarker, a plugin that adds colorful iconic markers for Leaflet by giving full control of style to end user, It has also ability to adjust font awesome
 | 
			
		||||
  and glyphicon icons
 | 
			
		||||
  (c) 2016-2017, Muhammad Arslan Sajid
 | 
			
		||||
  http://leafletjs.com
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Leaflet.BeautifyIcon assumes that you have already included the Leaflet library.
 | 
			
		||||
*/
 | 
			
		||||
L.BeautifyMarker = L.Marker.extend({
 | 
			
		||||
 | 
			
		||||
    registerMapZoomendEvent: false,
 | 
			
		||||
 | 
			
		||||
    options: {
 | 
			
		||||
        icon: L.BeautifyIcon.icon()
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onAdd: function (map) {
 | 
			
		||||
        L.Marker.prototype.onAdd.call(this, map);
 | 
			
		||||
 | 
			
		||||
        if (this.options && this.options.icon && this.options.icon.options && this.options.icon.options.iconShape === 'marker') {
 | 
			
		||||
            var icon = this._icon;
 | 
			
		||||
            this._updateMarkerShapeIcon(icon);
 | 
			
		||||
            if (!this.registerMapZoomendEvent) {
 | 
			
		||||
                L.DomEvent.addListener(map, 'zoomend', this._onMapZoomChange, this);
 | 
			
		||||
                L.DomEvent.addListener(this, 'drag', this._onMarkerDrag, this);
 | 
			
		||||
                this.registerMapZoomendEvent = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    _onMarkerDrag:function(){
 | 
			
		||||
        
 | 
			
		||||
        this._updateMarkerShapeIcon(this._icon);
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    _onMapZoomChange: function () {
 | 
			
		||||
 | 
			
		||||
        var needToRotateIcons = document.getElementsByClassName('marker');
 | 
			
		||||
        for (var i = 0, length = needToRotateIcons.length; i < length; i++) {
 | 
			
		||||
            var icon = needToRotateIcons[i];
 | 
			
		||||
            this._updateMarkerShapeIcon(icon);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _updateMarkerShapeIcon: function (icon) {
 | 
			
		||||
 | 
			
		||||
        var transform = icon.style.transform;
 | 
			
		||||
        icon.style.transform = transform + ' rotate(45deg)';
 | 
			
		||||
        icon.firstChild.style.transform = 'rotate(315deg)';
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
L.BeautifyMarker.marker = function (latlng, options) {
 | 
			
		||||
 | 
			
		||||
    return new L.BeautifyMarker(latlng, options);
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -0,0 +1,175 @@
 | 
			
		||||
/*
 | 
			
		||||
 * MIT Copyright 2016 Ursudio <info@ursudio.com>
 | 
			
		||||
 * http://www.ursudio.com/
 | 
			
		||||
 * Please attribute Ursudio in any production associated with this JavaScript plugin.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
L.WebGLHeatMap = L.Renderer.extend({
 | 
			
		||||
 | 
			
		||||
    // tested on Leaflet 1.0.1
 | 
			
		||||
    version : '0.2.0', 
 | 
			
		||||
 | 
			
		||||
    options: {
 | 
			
		||||
        // @option size: Number
 | 
			
		||||
        // corresponds with units below
 | 
			
		||||
        size: 30000,
 | 
			
		||||
        // @option units: String
 | 
			
		||||
        // 'm' for meters or 'px' for pixels
 | 
			
		||||
        units : 'm', 
 | 
			
		||||
        opacity: 1,
 | 
			
		||||
		gradientTexture: false,
 | 
			
		||||
		alphaRange: 1,
 | 
			
		||||
        // @option padding: 
 | 
			
		||||
        // don't add padding (0 helps with zoomanim)
 | 
			
		||||
        padding: 0
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _initContainer : function () {
 | 
			
		||||
        var container = this._container = document.createElement('canvas'),
 | 
			
		||||
            options = this.options;
 | 
			
		||||
 | 
			
		||||
        container.id = 'webgl-leaflet-' + L.Util.stamp(this);
 | 
			
		||||
        container.style.opacity = options.opacity;
 | 
			
		||||
        container.style.position = 'absolute';
 | 
			
		||||
            
 | 
			
		||||
        this.gl = window.createWebGLHeatmap({ 
 | 
			
		||||
            canvas: container, 
 | 
			
		||||
            gradientTexture: options.gradientTexture, 
 | 
			
		||||
            alphaRange: [0, options.alphaRange]
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this._container = container;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onAdd: function () {
 | 
			
		||||
        L.Renderer.prototype.onAdd.call(this);
 | 
			
		||||
		this.resize();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // events
 | 
			
		||||
 | 
			
		||||
    getEvents : function () {
 | 
			
		||||
        var events = L.Renderer.prototype.getEvents.call(this);
 | 
			
		||||
 | 
			
		||||
        L.Util.extend(events, {
 | 
			
		||||
            resize : this.resize,
 | 
			
		||||
            move : L.Util.throttle(this._update, 49, this)
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return events;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    resize: function () {
 | 
			
		||||
		var canvas = this._container,
 | 
			
		||||
			size = this._map.getSize();
 | 
			
		||||
		
 | 
			
		||||
		canvas.width = size.x;
 | 
			
		||||
		canvas.height = size.y;
 | 
			
		||||
 | 
			
		||||
		this.gl.adjustSize();
 | 
			
		||||
		this.draw();
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    reposition: function () {
 | 
			
		||||
        // canvas moves opposite to map pane's position
 | 
			
		||||
    	var pos = this._map
 | 
			
		||||
            ._getMapPanePos()
 | 
			
		||||
            .multiplyBy(-1);
 | 
			
		||||
 | 
			
		||||
		L.DomUtil.setPosition(this._container, pos);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _update : function () {
 | 
			
		||||
        L.Renderer.prototype._update.call(this);
 | 
			
		||||
        this.draw();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // draw function
 | 
			
		||||
    	
 | 
			
		||||
    draw : function () {
 | 
			
		||||
		var map = this._map,
 | 
			
		||||
			heatmap = this.gl,
 | 
			
		||||
			dataLen = this.data.length,
 | 
			
		||||
            floor = Math.floor,
 | 
			
		||||
            scaleFn = this['_scale' + this.options.units];
 | 
			
		||||
 | 
			
		||||
		if (!map) return;
 | 
			
		||||
 | 
			
		||||
        heatmap.clear();
 | 
			
		||||
        this.reposition();
 | 
			
		||||
 | 
			
		||||
		if (dataLen) {
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < dataLen; i++) {
 | 
			
		||||
				var dataVal = this.data[i],
 | 
			
		||||
					latlng = L.latLng( dataVal ),
 | 
			
		||||
					point = map.latLngToContainerPoint( latlng );
 | 
			
		||||
                
 | 
			
		||||
                heatmap.addPoint(
 | 
			
		||||
                    floor(point.x),
 | 
			
		||||
                    floor(point.y),
 | 
			
		||||
                    scaleFn(this, latlng),
 | 
			
		||||
					dataVal[2]
 | 
			
		||||
				);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            heatmap.update();
 | 
			
		||||
 | 
			
		||||
            if (this._multiply) {
 | 
			
		||||
            	heatmap.multiply( this._multiply );
 | 
			
		||||
            	heatmap.update();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        heatmap.display();
 | 
			
		||||
    },
 | 
			
		||||
	
 | 
			
		||||
    // scale methods 
 | 
			
		||||
 | 
			
		||||
    _scalem : function (ref, latlng) {
 | 
			
		||||
    	// necessary to maintain accurately sized circles
 | 
			
		||||
    	// to change scale to miles (for example), you will need to convert 40075017 (equatorial circumference of the Earth in metres) to miles
 | 
			
		||||
        var map = ref._map;
 | 
			
		||||
        var lngRadius = (ref.options.size / 40075017) *
 | 
			
		||||
            360 / Math.cos((Math.PI / 180) * latlng.lat),
 | 
			
		||||
            latlng2 = new L.LatLng(latlng.lat, latlng.lng - lngRadius),
 | 
			
		||||
            point = map.latLngToLayerPoint(latlng),
 | 
			
		||||
            point2 = map.latLngToLayerPoint(latlng2);
 | 
			
		||||
 | 
			
		||||
            return Math.max(Math.round(point.x - point2.x), 1);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _scalepx: function (ref, latlng) {
 | 
			
		||||
        return ref.options.size;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // data handling methods
 | 
			
		||||
 | 
			
		||||
    data : [],
 | 
			
		||||
	
 | 
			
		||||
    addDataPoint: function (lat, lon, value) {
 | 
			
		||||
        this.data.push( [ lat, lon, value / 100 ] );
 | 
			
		||||
    },
 | 
			
		||||
	
 | 
			
		||||
    setData: function (dataset) {
 | 
			
		||||
		// format: [[lat, lon, intensity],...]
 | 
			
		||||
		this.data = dataset;
 | 
			
		||||
		this._multiply = null;
 | 
			
		||||
		this.draw();
 | 
			
		||||
    },
 | 
			
		||||
	
 | 
			
		||||
    clear: function () {
 | 
			
		||||
		this.setData([]);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // affects original points
 | 
			
		||||
    multiply: function (n) {
 | 
			
		||||
    	this._multiply = n;
 | 
			
		||||
    	this.draw();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
L.webGLHeatmap = function ( options ) {
 | 
			
		||||
    return new L.WebGLHeatMap( options );
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1 @@
 | 
			
		||||
L.WebGLHeatMap=L.Renderer.extend({version:"0.2.0",options:{size:3e4,units:"m",opacity:1,gradientTexture:!1,alphaRange:1,padding:0},_initContainer:function(){var t=this._container=document.createElement("canvas"),i=this.options;t.id="webgl-leaflet-"+L.Util.stamp(this),t.style.opacity=i.opacity,t.style.position="absolute",this.gl=window.createWebGLHeatmap({canvas:t,gradientTexture:i.gradientTexture,alphaRange:[0,i.alphaRange]}),this._container=t},onAdd:function(){L.Renderer.prototype.onAdd.call(this),this.resize()},getEvents:function(){var t=L.Renderer.prototype.getEvents.call(this);return L.Util.extend(t,{resize:this.resize,move:L.Util.throttle(this._update,49,this)}),t},resize:function(){var t=this._container,i=this._map.getSize();t.width=i.x,t.height=i.y,this.gl.adjustSize(),this.draw()},reposition:function(){var t=this._map._getMapPanePos().multiplyBy(-1);L.DomUtil.setPosition(this._container,t)},_update:function(){L.Renderer.prototype._update.call(this),this.draw()},draw:function(){var t=this._map,i=this.gl,e=this.data.length,a=Math.floor,n=this["_scale"+this.options.units];if(t){if(i.clear(),this.reposition(),e){for(var s=0;s<e;s++){var o=this.data[s],r=L.latLng(o),l=t.latLngToContainerPoint(r);i.addPoint(a(l.x),a(l.y),n(r),o[2])}i.update(),this._multiply&&(i.multiply(this._multiply),i.update())}i.display()}},_scalem:function(t){var i=this._map,e=this.options.size/40075017*360/Math.cos(L.LatLng.DEG_TO_RAD*t.lat),a=new L.LatLng(t.lat,t.lng-e),n=i.latLngToLayerPoint(t),s=i.latLngToLayerPoint(a);return Math.max(Math.round(n.x-s.x),1)},_scalepx:function(t){return options.size},data:[],addDataPoint:function(t,i,e){this.data.push([t,i,e/100])},setData:function(t){this.data=t,this._multiply=null,this.draw()},clear:function(){this.setData([])},multiply:function(t){this._multiply=t,this.draw()}}),L.webGLHeatmap=function(t){return new L.WebGLHeatMap(t)};
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.2 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 696 B  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.5 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.4 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 618 B  | 
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -0,0 +1,623 @@
 | 
			
		||||
/* required styles */
 | 
			
		||||
 | 
			
		||||
.leaflet-pane,
 | 
			
		||||
.leaflet-tile,
 | 
			
		||||
.leaflet-marker-icon,
 | 
			
		||||
.leaflet-marker-shadow,
 | 
			
		||||
.leaflet-tile-container,
 | 
			
		||||
.leaflet-map-pane svg,
 | 
			
		||||
.leaflet-map-pane canvas,
 | 
			
		||||
.leaflet-zoom-box,
 | 
			
		||||
.leaflet-image-layer,
 | 
			
		||||
.leaflet-layer {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-container {
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-tile,
 | 
			
		||||
.leaflet-marker-icon,
 | 
			
		||||
.leaflet-marker-shadow {
 | 
			
		||||
	-webkit-user-select: none;
 | 
			
		||||
	   -moz-user-select: none;
 | 
			
		||||
	        user-select: none;
 | 
			
		||||
	  -webkit-user-drag: none;
 | 
			
		||||
	}
 | 
			
		||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
 | 
			
		||||
.leaflet-safari .leaflet-tile {
 | 
			
		||||
	image-rendering: -webkit-optimize-contrast;
 | 
			
		||||
	}
 | 
			
		||||
/* hack that prevents hw layers "stretching" when loading new tiles */
 | 
			
		||||
.leaflet-safari .leaflet-tile-container {
 | 
			
		||||
	width: 1600px;
 | 
			
		||||
	height: 1600px;
 | 
			
		||||
	-webkit-transform-origin: 0 0;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-marker-icon,
 | 
			
		||||
.leaflet-marker-shadow {
 | 
			
		||||
	display: block;
 | 
			
		||||
	}
 | 
			
		||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
 | 
			
		||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
 | 
			
		||||
.leaflet-container .leaflet-overlay-pane svg,
 | 
			
		||||
.leaflet-container .leaflet-marker-pane img,
 | 
			
		||||
.leaflet-container .leaflet-tile-pane img,
 | 
			
		||||
.leaflet-container img.leaflet-image-layer {
 | 
			
		||||
	max-width: none !important;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-container.leaflet-touch-zoom {
 | 
			
		||||
	-ms-touch-action: pan-x pan-y;
 | 
			
		||||
	touch-action: pan-x pan-y;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-container.leaflet-touch-drag {
 | 
			
		||||
	-ms-touch-action: pinch-zoom;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-drag {
 | 
			
		||||
	-ms-touch-action: none;
 | 
			
		||||
	touch-action: none;
 | 
			
		||||
}
 | 
			
		||||
.leaflet-tile {
 | 
			
		||||
	filter: inherit;
 | 
			
		||||
	visibility: hidden;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-tile-loaded {
 | 
			
		||||
	visibility: inherit;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-zoom-box {
 | 
			
		||||
	width: 0;
 | 
			
		||||
	height: 0;
 | 
			
		||||
	-moz-box-sizing: border-box;
 | 
			
		||||
	     box-sizing: border-box;
 | 
			
		||||
	z-index: 800;
 | 
			
		||||
	}
 | 
			
		||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
 | 
			
		||||
.leaflet-overlay-pane svg {
 | 
			
		||||
	-moz-user-select: none;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-pane         { z-index: 400; }
 | 
			
		||||
 | 
			
		||||
.leaflet-tile-pane    { z-index: 200; }
 | 
			
		||||
.leaflet-overlay-pane { z-index: 400; }
 | 
			
		||||
.leaflet-shadow-pane  { z-index: 500; }
 | 
			
		||||
.leaflet-marker-pane  { z-index: 600; }
 | 
			
		||||
.leaflet-tooltip-pane   { z-index: 650; }
 | 
			
		||||
.leaflet-popup-pane   { z-index: 700; }
 | 
			
		||||
 | 
			
		||||
.leaflet-map-pane canvas { z-index: 100; }
 | 
			
		||||
.leaflet-map-pane svg    { z-index: 200; }
 | 
			
		||||
 | 
			
		||||
.leaflet-vml-shape {
 | 
			
		||||
	width: 1px;
 | 
			
		||||
	height: 1px;
 | 
			
		||||
	}
 | 
			
		||||
.lvml {
 | 
			
		||||
	behavior: url(#default#VML);
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* control positioning */
 | 
			
		||||
 | 
			
		||||
.leaflet-control {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	z-index: 800;
 | 
			
		||||
	pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
 | 
			
		||||
	pointer-events: auto;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-top,
 | 
			
		||||
.leaflet-bottom {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	z-index: 1000;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-top {
 | 
			
		||||
	top: 0;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-right {
 | 
			
		||||
	right: 0;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-bottom {
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-left {
 | 
			
		||||
	left: 0;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control {
 | 
			
		||||
	float: left;
 | 
			
		||||
	clear: both;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-right .leaflet-control {
 | 
			
		||||
	float: right;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-top .leaflet-control {
 | 
			
		||||
	margin-top: 10px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-bottom .leaflet-control {
 | 
			
		||||
	margin-bottom: 10px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-left .leaflet-control {
 | 
			
		||||
	margin-left: 10px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-right .leaflet-control {
 | 
			
		||||
	margin-right: 10px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* zoom and fade animations */
 | 
			
		||||
 | 
			
		||||
.leaflet-fade-anim .leaflet-tile {
 | 
			
		||||
	will-change: opacity;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-fade-anim .leaflet-popup {
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	-webkit-transition: opacity 0.2s linear;
 | 
			
		||||
	   -moz-transition: opacity 0.2s linear;
 | 
			
		||||
	     -o-transition: opacity 0.2s linear;
 | 
			
		||||
	        transition: opacity 0.2s linear;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-zoom-animated {
 | 
			
		||||
	-webkit-transform-origin: 0 0;
 | 
			
		||||
	    -ms-transform-origin: 0 0;
 | 
			
		||||
	        transform-origin: 0 0;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-zoom-anim .leaflet-zoom-animated {
 | 
			
		||||
	will-change: transform;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-zoom-anim .leaflet-zoom-animated {
 | 
			
		||||
	-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
 | 
			
		||||
	   -moz-transition:    -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
 | 
			
		||||
	     -o-transition:      -o-transform 0.25s cubic-bezier(0,0,0.25,1);
 | 
			
		||||
	        transition:         transform 0.25s cubic-bezier(0,0,0.25,1);
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-zoom-anim .leaflet-tile,
 | 
			
		||||
.leaflet-pan-anim .leaflet-tile {
 | 
			
		||||
	-webkit-transition: none;
 | 
			
		||||
	   -moz-transition: none;
 | 
			
		||||
	     -o-transition: none;
 | 
			
		||||
	        transition: none;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-zoom-anim .leaflet-zoom-hide {
 | 
			
		||||
	visibility: hidden;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* cursors */
 | 
			
		||||
 | 
			
		||||
.leaflet-interactive {
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-grab {
 | 
			
		||||
	cursor: -webkit-grab;
 | 
			
		||||
	cursor:    -moz-grab;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-crosshair,
 | 
			
		||||
.leaflet-crosshair .leaflet-interactive {
 | 
			
		||||
	cursor: crosshair;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-popup-pane,
 | 
			
		||||
.leaflet-control {
 | 
			
		||||
	cursor: auto;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-dragging .leaflet-grab,
 | 
			
		||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
 | 
			
		||||
.leaflet-dragging .leaflet-marker-draggable {
 | 
			
		||||
	cursor: move;
 | 
			
		||||
	cursor: -webkit-grabbing;
 | 
			
		||||
	cursor:    -moz-grabbing;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
/* marker & overlays interactivity */
 | 
			
		||||
.leaflet-marker-icon,
 | 
			
		||||
.leaflet-marker-shadow,
 | 
			
		||||
.leaflet-image-layer,
 | 
			
		||||
.leaflet-pane > svg path,
 | 
			
		||||
.leaflet-tile-container {
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-marker-icon.leaflet-interactive,
 | 
			
		||||
.leaflet-image-layer.leaflet-interactive,
 | 
			
		||||
.leaflet-pane > svg path.leaflet-interactive {
 | 
			
		||||
	pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
 | 
			
		||||
	pointer-events: auto;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
/* visual tweaks */
 | 
			
		||||
 | 
			
		||||
.leaflet-container {
 | 
			
		||||
	background: #ddd;
 | 
			
		||||
	outline: 0;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-container a {
 | 
			
		||||
	color: #0078A8;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-container a.leaflet-active {
 | 
			
		||||
	outline: 2px solid orange;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-zoom-box {
 | 
			
		||||
	border: 2px dotted #38f;
 | 
			
		||||
	background: rgba(255,255,255,0.5);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* general typography */
 | 
			
		||||
.leaflet-container {
 | 
			
		||||
	font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* general toolbar styles */
 | 
			
		||||
 | 
			
		||||
.leaflet-bar {
 | 
			
		||||
	box-shadow: 0 1px 5px rgba(0,0,0,0.65);
 | 
			
		||||
	border-radius: 4px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-bar a,
 | 
			
		||||
.leaflet-bar a:hover {
 | 
			
		||||
	background-color: #fff;
 | 
			
		||||
	border-bottom: 1px solid #ccc;
 | 
			
		||||
	width: 26px;
 | 
			
		||||
	height: 26px;
 | 
			
		||||
	line-height: 26px;
 | 
			
		||||
	display: block;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	text-decoration: none;
 | 
			
		||||
	color: black;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-bar a,
 | 
			
		||||
.leaflet-control-layers-toggle {
 | 
			
		||||
	background-position: 50% 50%;
 | 
			
		||||
	background-repeat: no-repeat;
 | 
			
		||||
	display: block;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-bar a:hover {
 | 
			
		||||
	background-color: #f4f4f4;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-bar a:first-child {
 | 
			
		||||
	border-top-left-radius: 4px;
 | 
			
		||||
	border-top-right-radius: 4px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-bar a:last-child {
 | 
			
		||||
	border-bottom-left-radius: 4px;
 | 
			
		||||
	border-bottom-right-radius: 4px;
 | 
			
		||||
	border-bottom: none;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-bar a.leaflet-disabled {
 | 
			
		||||
	cursor: default;
 | 
			
		||||
	background-color: #f4f4f4;
 | 
			
		||||
	color: #bbb;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-touch .leaflet-bar a {
 | 
			
		||||
	width: 30px;
 | 
			
		||||
	height: 30px;
 | 
			
		||||
	line-height: 30px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* zoom control */
 | 
			
		||||
 | 
			
		||||
.leaflet-control-zoom-in,
 | 
			
		||||
.leaflet-control-zoom-out {
 | 
			
		||||
	font: bold 18px 'Lucida Console', Monaco, monospace;
 | 
			
		||||
	text-indent: 1px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-zoom-out {
 | 
			
		||||
	font-size: 20px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-touch .leaflet-control-zoom-in {
 | 
			
		||||
	font-size: 22px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-touch .leaflet-control-zoom-out {
 | 
			
		||||
	font-size: 24px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* layers control */
 | 
			
		||||
 | 
			
		||||
.leaflet-control-layers {
 | 
			
		||||
	box-shadow: 0 1px 5px rgba(0,0,0,0.4);
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	border-radius: 5px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-layers-toggle {
 | 
			
		||||
	background-image: url(images/layers.png);
 | 
			
		||||
	width: 36px;
 | 
			
		||||
	height: 36px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-retina .leaflet-control-layers-toggle {
 | 
			
		||||
	background-image: url(images/layers-2x.png);
 | 
			
		||||
	background-size: 26px 26px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-touch .leaflet-control-layers-toggle {
 | 
			
		||||
	width: 44px;
 | 
			
		||||
	height: 44px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-layers .leaflet-control-layers-list,
 | 
			
		||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
 | 
			
		||||
	display: none;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
 | 
			
		||||
	display: block;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-layers-expanded {
 | 
			
		||||
	padding: 6px 10px 6px 6px;
 | 
			
		||||
	color: #333;
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-layers-scrollbar {
 | 
			
		||||
	overflow-y: scroll;
 | 
			
		||||
	padding-right: 5px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-layers-selector {
 | 
			
		||||
	margin-top: 2px;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	top: 1px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-layers label {
 | 
			
		||||
	display: block;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-layers-separator {
 | 
			
		||||
	height: 0;
 | 
			
		||||
	border-top: 1px solid #ddd;
 | 
			
		||||
	margin: 5px -10px 5px -6px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
/* Default icon URLs */
 | 
			
		||||
.leaflet-default-icon-path {
 | 
			
		||||
	background-image: url(images/marker-icon.png);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* attribution and scale controls */
 | 
			
		||||
 | 
			
		||||
.leaflet-container .leaflet-control-attribution {
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	background: rgba(255, 255, 255, 0.7);
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-attribution,
 | 
			
		||||
.leaflet-control-scale-line {
 | 
			
		||||
	padding: 0 5px;
 | 
			
		||||
	color: #333;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-attribution a {
 | 
			
		||||
	text-decoration: none;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-attribution a:hover {
 | 
			
		||||
	text-decoration: underline;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-container .leaflet-control-attribution,
 | 
			
		||||
.leaflet-container .leaflet-control-scale {
 | 
			
		||||
	font-size: 11px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-left .leaflet-control-scale {
 | 
			
		||||
	margin-left: 5px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-bottom .leaflet-control-scale {
 | 
			
		||||
	margin-bottom: 5px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-scale-line {
 | 
			
		||||
	border: 2px solid #777;
 | 
			
		||||
	border-top: none;
 | 
			
		||||
	line-height: 1.1;
 | 
			
		||||
	padding: 2px 5px 1px;
 | 
			
		||||
	font-size: 11px;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	-moz-box-sizing: border-box;
 | 
			
		||||
	     box-sizing: border-box;
 | 
			
		||||
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	background: rgba(255, 255, 255, 0.5);
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-scale-line:not(:first-child) {
 | 
			
		||||
	border-top: 2px solid #777;
 | 
			
		||||
	border-bottom: none;
 | 
			
		||||
	margin-top: -2px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
 | 
			
		||||
	border-bottom: 2px solid #777;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-touch .leaflet-control-attribution,
 | 
			
		||||
.leaflet-touch .leaflet-control-layers,
 | 
			
		||||
.leaflet-touch .leaflet-bar {
 | 
			
		||||
	box-shadow: none;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-touch .leaflet-control-layers,
 | 
			
		||||
.leaflet-touch .leaflet-bar {
 | 
			
		||||
	border: 2px solid rgba(0,0,0,0.2);
 | 
			
		||||
	background-clip: padding-box;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* popup */
 | 
			
		||||
 | 
			
		||||
.leaflet-popup {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-popup-content-wrapper {
 | 
			
		||||
	padding: 1px;
 | 
			
		||||
	text-align: left;
 | 
			
		||||
	border-radius: 12px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-popup-content {
 | 
			
		||||
	margin: 13px 19px;
 | 
			
		||||
	line-height: 1.4;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-popup-content p {
 | 
			
		||||
	margin: 18px 0;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-popup-tip-container {
 | 
			
		||||
	width: 40px;
 | 
			
		||||
	height: 20px;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	left: 50%;
 | 
			
		||||
	margin-left: -20px;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-popup-tip {
 | 
			
		||||
	width: 17px;
 | 
			
		||||
	height: 17px;
 | 
			
		||||
	padding: 1px;
 | 
			
		||||
 | 
			
		||||
	margin: -10px auto 0;
 | 
			
		||||
 | 
			
		||||
	-webkit-transform: rotate(45deg);
 | 
			
		||||
	   -moz-transform: rotate(45deg);
 | 
			
		||||
	    -ms-transform: rotate(45deg);
 | 
			
		||||
	     -o-transform: rotate(45deg);
 | 
			
		||||
	        transform: rotate(45deg);
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-popup-content-wrapper,
 | 
			
		||||
.leaflet-popup-tip {
 | 
			
		||||
	background: white;
 | 
			
		||||
	color: #333;
 | 
			
		||||
	box-shadow: 0 3px 14px rgba(0,0,0,0.4);
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-container a.leaflet-popup-close-button {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	right: 0;
 | 
			
		||||
	padding: 4px 4px 0 0;
 | 
			
		||||
	border: none;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	width: 18px;
 | 
			
		||||
	height: 14px;
 | 
			
		||||
	font: 16px/14px Tahoma, Verdana, sans-serif;
 | 
			
		||||
	color: #c3c3c3;
 | 
			
		||||
	text-decoration: none;
 | 
			
		||||
	font-weight: bold;
 | 
			
		||||
	background: transparent;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-container a.leaflet-popup-close-button:hover {
 | 
			
		||||
	color: #999;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-popup-scrolled {
 | 
			
		||||
	overflow: auto;
 | 
			
		||||
	border-bottom: 1px solid #ddd;
 | 
			
		||||
	border-top: 1px solid #ddd;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-oldie .leaflet-popup-content-wrapper {
 | 
			
		||||
	zoom: 1;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-oldie .leaflet-popup-tip {
 | 
			
		||||
	width: 24px;
 | 
			
		||||
	margin: 0 auto;
 | 
			
		||||
 | 
			
		||||
	-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
 | 
			
		||||
	filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-oldie .leaflet-popup-tip-container {
 | 
			
		||||
	margin-top: -1px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-oldie .leaflet-control-zoom,
 | 
			
		||||
.leaflet-oldie .leaflet-control-layers,
 | 
			
		||||
.leaflet-oldie .leaflet-popup-content-wrapper,
 | 
			
		||||
.leaflet-oldie .leaflet-popup-tip {
 | 
			
		||||
	border: 1px solid #999;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* div icon */
 | 
			
		||||
 | 
			
		||||
.leaflet-div-icon {
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	border: 1px solid #666;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Tooltip */
 | 
			
		||||
/* Base styles for the element that has a tooltip */
 | 
			
		||||
.leaflet-tooltip {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	padding: 6px;
 | 
			
		||||
	background-color: #fff;
 | 
			
		||||
	border: 1px solid #fff;
 | 
			
		||||
	border-radius: 3px;
 | 
			
		||||
	color: #222;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
	-webkit-user-select: none;
 | 
			
		||||
	-moz-user-select: none;
 | 
			
		||||
	-ms-user-select: none;
 | 
			
		||||
	user-select: none;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
	box-shadow: 0 1px 3px rgba(0,0,0,0.4);
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-tooltip.leaflet-clickable {
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	pointer-events: auto;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-tooltip-top:before,
 | 
			
		||||
.leaflet-tooltip-bottom:before,
 | 
			
		||||
.leaflet-tooltip-left:before,
 | 
			
		||||
.leaflet-tooltip-right:before {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
	border: 6px solid transparent;
 | 
			
		||||
	background: transparent;
 | 
			
		||||
	content: "";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
/* Directions */
 | 
			
		||||
 | 
			
		||||
.leaflet-tooltip-bottom {
 | 
			
		||||
	margin-top: 6px;
 | 
			
		||||
}
 | 
			
		||||
.leaflet-tooltip-top {
 | 
			
		||||
	margin-top: -6px;
 | 
			
		||||
}
 | 
			
		||||
.leaflet-tooltip-bottom:before,
 | 
			
		||||
.leaflet-tooltip-top:before {
 | 
			
		||||
	left: 50%;
 | 
			
		||||
	margin-left: -6px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-tooltip-top:before {
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
	margin-bottom: -12px;
 | 
			
		||||
	border-top-color: #fff;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-tooltip-bottom:before {
 | 
			
		||||
	top: 0;
 | 
			
		||||
	margin-top: -12px;
 | 
			
		||||
	margin-left: -6px;
 | 
			
		||||
	border-bottom-color: #fff;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-tooltip-left {
 | 
			
		||||
	margin-left: -6px;
 | 
			
		||||
}
 | 
			
		||||
.leaflet-tooltip-right {
 | 
			
		||||
	margin-left: 6px;
 | 
			
		||||
}
 | 
			
		||||
.leaflet-tooltip-left:before,
 | 
			
		||||
.leaflet-tooltip-right:before {
 | 
			
		||||
	top: 50%;
 | 
			
		||||
	margin-top: -6px;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-tooltip-left:before {
 | 
			
		||||
	right: 0;
 | 
			
		||||
	margin-right: -12px;
 | 
			
		||||
	border-left-color: #fff;
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-tooltip-right:before {
 | 
			
		||||
	left: 0;
 | 
			
		||||
	margin-left: -12px;
 | 
			
		||||
	border-right-color: #fff;
 | 
			
		||||
	}
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
.marker-cluster-small {
 | 
			
		||||
	background-color: rgba(181, 226, 140, 0.6);
 | 
			
		||||
	}
 | 
			
		||||
.marker-cluster-small div {
 | 
			
		||||
	background-color: rgba(110, 204, 57, 0.6);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.marker-cluster-medium {
 | 
			
		||||
	background-color: rgba(241, 211, 87, 0.6);
 | 
			
		||||
	}
 | 
			
		||||
.marker-cluster-medium div {
 | 
			
		||||
	background-color: rgba(240, 194, 12, 0.6);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.marker-cluster-large {
 | 
			
		||||
	background-color: rgba(253, 156, 115, 0.6);
 | 
			
		||||
	}
 | 
			
		||||
.marker-cluster-large div {
 | 
			
		||||
	background-color: rgba(241, 128, 23, 0.6);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* IE 6-8 fallback colors */
 | 
			
		||||
.leaflet-oldie .marker-cluster-small {
 | 
			
		||||
	background-color: rgb(181, 226, 140);
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-oldie .marker-cluster-small div {
 | 
			
		||||
	background-color: rgb(110, 204, 57);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-oldie .marker-cluster-medium {
 | 
			
		||||
	background-color: rgb(241, 211, 87);
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-oldie .marker-cluster-medium div {
 | 
			
		||||
	background-color: rgb(240, 194, 12);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
.leaflet-oldie .marker-cluster-large {
 | 
			
		||||
	background-color: rgb(253, 156, 115);
 | 
			
		||||
	}
 | 
			
		||||
.leaflet-oldie .marker-cluster-large div {
 | 
			
		||||
	background-color: rgb(241, 128, 23);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.marker-cluster {
 | 
			
		||||
	background-clip: padding-box;
 | 
			
		||||
	border-radius: 20px;
 | 
			
		||||
	}
 | 
			
		||||
.marker-cluster div {
 | 
			
		||||
	width: 30px;
 | 
			
		||||
	height: 30px;
 | 
			
		||||
	margin-left: 5px;
 | 
			
		||||
	margin-top: 5px;
 | 
			
		||||
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	border-radius: 15px;
 | 
			
		||||
	font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
 | 
			
		||||
	}
 | 
			
		||||
.marker-cluster span {
 | 
			
		||||
	line-height: 30px;
 | 
			
		||||
	}
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
 | 
			
		||||
	-webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
 | 
			
		||||
	-moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
 | 
			
		||||
	-o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
 | 
			
		||||
	transition: transform 0.3s ease-out, opacity 0.3s ease-in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.leaflet-cluster-spider-leg {
 | 
			
		||||
	/* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
 | 
			
		||||
	-webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
 | 
			
		||||
	-moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
 | 
			
		||||
	-o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
 | 
			
		||||
	transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
.leaflet-control-messagebox {
 | 
			
		||||
	display: none; /* Initially hidden */
 | 
			
		||||
	border: 2px solid red;
 | 
			
		||||
	background-color: white;
 | 
			
		||||
	padding: 3px 10px;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,42 @@
 | 
			
		||||
L.Control.Messagebox = L.Control.extend({
 | 
			
		||||
    options: {
 | 
			
		||||
        position: 'topright',
 | 
			
		||||
        timeout: 3000
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onAdd: function (map) {
 | 
			
		||||
        this._container = L.DomUtil.create('div', 'leaflet-control-messagebox');
 | 
			
		||||
        //L.DomEvent.disableClickPropagation(this._container);
 | 
			
		||||
        return this._container;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    show: function (message, timeout) {
 | 
			
		||||
        var elem = this._container;
 | 
			
		||||
        elem.innerHTML = message;
 | 
			
		||||
        elem.style.display = 'block';
 | 
			
		||||
 | 
			
		||||
        timeout = timeout || this.options.timeout;
 | 
			
		||||
 | 
			
		||||
        if (typeof this.timeoutID == 'number') {
 | 
			
		||||
            clearTimeout(this.timeoutID);
 | 
			
		||||
        }
 | 
			
		||||
        this.timeoutID = setTimeout(function () {
 | 
			
		||||
            elem.style.display = 'none';
 | 
			
		||||
        }, timeout);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
L.Map.mergeOptions({
 | 
			
		||||
    messagebox: false
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
L.Map.addInitHook(function () {
 | 
			
		||||
    if (this.options.messagebox) {
 | 
			
		||||
        this.messagebox = new L.Control.Messagebox();
 | 
			
		||||
        this.addControl(this.messagebox);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
L.control.messagebox = function (options) {
 | 
			
		||||
    return new L.Control.Messagebox(options);
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,373 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) 2011-2014 Felix Gnass
 | 
			
		||||
 * Licensed under the MIT license
 | 
			
		||||
 * http://spin.js.org/
 | 
			
		||||
 *
 | 
			
		||||
 * Example:
 | 
			
		||||
    var opts = {
 | 
			
		||||
      lines: 12,            // The number of lines to draw
 | 
			
		||||
      length: 7,            // The length of each line
 | 
			
		||||
      width: 5,             // The line thickness
 | 
			
		||||
      radius: 10,           // The radius of the inner circle
 | 
			
		||||
      scale: 1.0,           // Scales overall size of the spinner
 | 
			
		||||
      corners: 1,           // Roundness (0..1)
 | 
			
		||||
      color: '#000',        // #rgb or #rrggbb
 | 
			
		||||
      opacity: 1/4,         // Opacity of the lines
 | 
			
		||||
      rotate: 0,            // Rotation offset
 | 
			
		||||
      direction: 1,         // 1: clockwise, -1: counterclockwise
 | 
			
		||||
      speed: 1,             // Rounds per second
 | 
			
		||||
      trail: 100,           // Afterglow percentage
 | 
			
		||||
      fps: 20,              // Frames per second when using setTimeout()
 | 
			
		||||
      zIndex: 2e9,          // Use a high z-index by default
 | 
			
		||||
      className: 'spinner', // CSS class to assign to the element
 | 
			
		||||
      top: '50%',           // center vertically
 | 
			
		||||
      left: '50%',          // center horizontally
 | 
			
		||||
      shadow: false,        // Whether to render a shadow
 | 
			
		||||
      hwaccel: false,       // Whether to use hardware acceleration (might be buggy)
 | 
			
		||||
      position: 'absolute'  // Element positioning
 | 
			
		||||
    };
 | 
			
		||||
    var target = document.getElementById('foo');
 | 
			
		||||
    var spinner = new Spinner(opts).spin(target);
 | 
			
		||||
 */
 | 
			
		||||
;(function(root, factory) {
 | 
			
		||||
  if (typeof module == 'object' && module.exports) module.exports = factory(); // CommonJS
 | 
			
		||||
  else if (typeof define == 'function' && define.amd) define(factory); // AMD module
 | 
			
		||||
  else root.Spinner = factory(); // Browser global
 | 
			
		||||
}
 | 
			
		||||
(this, function() {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  var prefixes = ['webkit', 'Moz', 'ms', 'O']; // Vendor prefixes
 | 
			
		||||
  var animations = {}; // Animation rules keyed by their name
 | 
			
		||||
  var useCssAnimations; // Whether to use CSS animations or setTimeout
 | 
			
		||||
  var sheet; // A stylesheet to hold the @keyframe or VML rules
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Utility function to create elements. If no tag name is given,
 | 
			
		||||
   * a DIV is created. Optionally properties can be passed.
 | 
			
		||||
   */
 | 
			
		||||
  function createEl(tag, prop) {
 | 
			
		||||
    var el = document.createElement(tag || 'div');
 | 
			
		||||
    var n;
 | 
			
		||||
 | 
			
		||||
    for (n in prop) el[n] = prop[n];
 | 
			
		||||
    return el;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Appends children and returns the parent.
 | 
			
		||||
   */
 | 
			
		||||
  function ins(parent /* child1, child2, ...*/) {
 | 
			
		||||
    for (var i = 1, n = arguments.length; i < n; i++) {
 | 
			
		||||
      parent.appendChild(arguments[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return parent;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates an opacity keyframe animation rule and returns its name.
 | 
			
		||||
   * Since most mobile Webkits have timing issues with animation-delay,
 | 
			
		||||
   * we create separate rules for each line/segment.
 | 
			
		||||
   */
 | 
			
		||||
  function addAnimation(alpha, trail, i, lines) {
 | 
			
		||||
    var name = ['opacity', trail, ~~(alpha * 100), i, lines].join('-');
 | 
			
		||||
    var start = 0.01 + i/lines * 100;
 | 
			
		||||
    var z = Math.max(1 - (1-alpha) / trail * (100-start), alpha);
 | 
			
		||||
    var prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase();
 | 
			
		||||
    var pre = prefix && '-' + prefix + '-' || '';
 | 
			
		||||
 | 
			
		||||
    if (!animations[name]) {
 | 
			
		||||
      sheet.insertRule(
 | 
			
		||||
        '@' + pre + 'keyframes ' + name + '{' +
 | 
			
		||||
        '0%{opacity:' + z + '}' +
 | 
			
		||||
        start + '%{opacity:' + alpha + '}' +
 | 
			
		||||
        (start+0.01) + '%{opacity:1}' +
 | 
			
		||||
        (start+trail) % 100 + '%{opacity:' + alpha + '}' +
 | 
			
		||||
        '100%{opacity:' + z + '}' +
 | 
			
		||||
        '}', sheet.cssRules.length);
 | 
			
		||||
 | 
			
		||||
      animations[name] = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return name;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tries various vendor prefixes and returns the first supported property.
 | 
			
		||||
   */
 | 
			
		||||
  function vendor(el, prop) {
 | 
			
		||||
    var s = el.style;
 | 
			
		||||
    var pp;
 | 
			
		||||
    var i;
 | 
			
		||||
 | 
			
		||||
    prop = prop.charAt(0).toUpperCase() + prop.slice(1);
 | 
			
		||||
    if (s[prop] !== undefined) return prop;
 | 
			
		||||
    for (i = 0; i < prefixes.length; i++) {
 | 
			
		||||
      pp = prefixes[i]+prop;
 | 
			
		||||
      if (s[pp] !== undefined) return pp;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets multiple style properties at once.
 | 
			
		||||
   */
 | 
			
		||||
  function css(el, prop) {
 | 
			
		||||
    for (var n in prop) {
 | 
			
		||||
      el.style[vendor(el, n) || n] = prop[n];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return el;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Fills in default values.
 | 
			
		||||
   */
 | 
			
		||||
  function merge(obj) {
 | 
			
		||||
    for (var i = 1; i < arguments.length; i++) {
 | 
			
		||||
      var def = arguments[i];
 | 
			
		||||
      for (var n in def) {
 | 
			
		||||
        if (obj[n] === undefined) obj[n] = def[n];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return obj;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the line color from the given string or array.
 | 
			
		||||
   */
 | 
			
		||||
  function getColor(color, idx) {
 | 
			
		||||
    return typeof color == 'string' ? color : color[idx % color.length];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Built-in defaults
 | 
			
		||||
 | 
			
		||||
  var defaults = {
 | 
			
		||||
    lines: 12,            // The number of lines to draw
 | 
			
		||||
    length: 7,            // The length of each line
 | 
			
		||||
    width: 5,             // The line thickness
 | 
			
		||||
    radius: 10,           // The radius of the inner circle
 | 
			
		||||
    scale: 1.0,           // Scales overall size of the spinner
 | 
			
		||||
    corners: 1,           // Roundness (0..1)
 | 
			
		||||
    color: '#000',        // #rgb or #rrggbb
 | 
			
		||||
    opacity: 1/4,         // Opacity of the lines
 | 
			
		||||
    rotate: 0,            // Rotation offset
 | 
			
		||||
    direction: 1,         // 1: clockwise, -1: counterclockwise
 | 
			
		||||
    speed: 1,             // Rounds per second
 | 
			
		||||
    trail: 100,           // Afterglow percentage
 | 
			
		||||
    fps: 20,              // Frames per second when using setTimeout()
 | 
			
		||||
    zIndex: 2e9,          // Use a high z-index by default
 | 
			
		||||
    className: 'spinner', // CSS class to assign to the element
 | 
			
		||||
    top: '50%',           // center vertically
 | 
			
		||||
    left: '50%',          // center horizontally
 | 
			
		||||
    shadow: false,        // Whether to render a shadow
 | 
			
		||||
    hwaccel: false,       // Whether to use hardware acceleration
 | 
			
		||||
    position: 'absolute'  // Element positioning
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /** The constructor */
 | 
			
		||||
  function Spinner(o) {
 | 
			
		||||
    this.opts = merge(o || {}, Spinner.defaults, defaults);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Global defaults that override the built-ins:
 | 
			
		||||
  Spinner.defaults = {};
 | 
			
		||||
 | 
			
		||||
  merge(Spinner.prototype, {
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds the spinner to the given target element. If this instance is already
 | 
			
		||||
     * spinning, it is automatically removed from its previous target b calling
 | 
			
		||||
     * stop() internally.
 | 
			
		||||
     */
 | 
			
		||||
    spin: function(target) {
 | 
			
		||||
      this.stop();
 | 
			
		||||
 | 
			
		||||
      var self = this;
 | 
			
		||||
      var o = self.opts;
 | 
			
		||||
      var el = self.el = createEl(null, {className: o.className});
 | 
			
		||||
 | 
			
		||||
      css(el, {
 | 
			
		||||
        position: o.position,
 | 
			
		||||
        width: 0,
 | 
			
		||||
        zIndex: o.zIndex,
 | 
			
		||||
        left: o.left,
 | 
			
		||||
        top: o.top
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (target) {
 | 
			
		||||
        target.insertBefore(el, target.firstChild || null);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      el.setAttribute('role', 'progressbar');
 | 
			
		||||
      self.lines(el, self.opts);
 | 
			
		||||
 | 
			
		||||
      if (!useCssAnimations) {
 | 
			
		||||
        // No CSS animation support, use setTimeout() instead
 | 
			
		||||
        var i = 0;
 | 
			
		||||
        var start = (o.lines - 1) * (1 - o.direction) / 2;
 | 
			
		||||
        var alpha;
 | 
			
		||||
        var fps = o.fps;
 | 
			
		||||
        var f = fps / o.speed;
 | 
			
		||||
        var ostep = (1 - o.opacity) / (f * o.trail / 100);
 | 
			
		||||
        var astep = f / o.lines;
 | 
			
		||||
 | 
			
		||||
        (function anim() {
 | 
			
		||||
          i++;
 | 
			
		||||
          for (var j = 0; j < o.lines; j++) {
 | 
			
		||||
            alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity);
 | 
			
		||||
 | 
			
		||||
            self.opacity(el, j * o.direction + start, alpha, o);
 | 
			
		||||
          }
 | 
			
		||||
          self.timeout = self.el && setTimeout(anim, ~~(1000 / fps));
 | 
			
		||||
        })();
 | 
			
		||||
      }
 | 
			
		||||
      return self;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stops and removes the Spinner.
 | 
			
		||||
     */
 | 
			
		||||
    stop: function() {
 | 
			
		||||
      var el = this.el;
 | 
			
		||||
      if (el) {
 | 
			
		||||
        clearTimeout(this.timeout);
 | 
			
		||||
        if (el.parentNode) el.parentNode.removeChild(el);
 | 
			
		||||
        this.el = undefined;
 | 
			
		||||
      }
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Internal method that draws the individual lines. Will be overwritten
 | 
			
		||||
     * in VML fallback mode below.
 | 
			
		||||
     */
 | 
			
		||||
    lines: function(el, o) {
 | 
			
		||||
      var i = 0;
 | 
			
		||||
      var start = (o.lines - 1) * (1 - o.direction) / 2;
 | 
			
		||||
      var seg;
 | 
			
		||||
 | 
			
		||||
      function fill(color, shadow) {
 | 
			
		||||
        return css(createEl(), {
 | 
			
		||||
          position: 'absolute',
 | 
			
		||||
          width: o.scale * (o.length + o.width) + 'px',
 | 
			
		||||
          height: o.scale * o.width + 'px',
 | 
			
		||||
          background: color,
 | 
			
		||||
          boxShadow: shadow,
 | 
			
		||||
          transformOrigin: 'left',
 | 
			
		||||
          transform: 'rotate(' + ~~(360/o.lines*i + o.rotate) + 'deg) translate(' + o.scale*o.radius + 'px' + ',0)',
 | 
			
		||||
          borderRadius: (o.corners * o.scale * o.width >> 1) + 'px'
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (; i < o.lines; i++) {
 | 
			
		||||
        seg = css(createEl(), {
 | 
			
		||||
          position: 'absolute',
 | 
			
		||||
          top: 1 + ~(o.scale * o.width / 2) + 'px',
 | 
			
		||||
          transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
 | 
			
		||||
          opacity: o.opacity,
 | 
			
		||||
          animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1 / o.speed + 's linear infinite'
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (o.shadow) ins(seg, css(fill('#000', '0 0 4px #000'), {top: '2px'}));
 | 
			
		||||
        ins(el, ins(seg, fill(getColor(o.color, i), '0 0 1px rgba(0,0,0,.1)')));
 | 
			
		||||
      }
 | 
			
		||||
      return el;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Internal method that adjusts the opacity of a single line.
 | 
			
		||||
     * Will be overwritten in VML fallback mode below.
 | 
			
		||||
     */
 | 
			
		||||
    opacity: function(el, i, val) {
 | 
			
		||||
      if (i < el.childNodes.length) el.childNodes[i].style.opacity = val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  function initVML() {
 | 
			
		||||
 | 
			
		||||
    /* Utility function to create a VML tag */
 | 
			
		||||
    function vml(tag, attr) {
 | 
			
		||||
      return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // No CSS transforms but VML support, add a CSS rule for VML elements:
 | 
			
		||||
    sheet.addRule('.spin-vml', 'behavior:url(#default#VML)');
 | 
			
		||||
 | 
			
		||||
    Spinner.prototype.lines = function(el, o) {
 | 
			
		||||
      var r = o.scale * (o.length + o.width);
 | 
			
		||||
      var s = o.scale * 2 * r;
 | 
			
		||||
 | 
			
		||||
      function grp() {
 | 
			
		||||
        return css(
 | 
			
		||||
          vml('group', {
 | 
			
		||||
            coordsize: s + ' ' + s,
 | 
			
		||||
            coordorigin: -r + ' ' + -r
 | 
			
		||||
          }),
 | 
			
		||||
          { width: s, height: s }
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      var margin = -(o.width + o.length) * o.scale * 2 + 'px';
 | 
			
		||||
      var g = css(grp(), {position: 'absolute', top: margin, left: margin});
 | 
			
		||||
      var i;
 | 
			
		||||
 | 
			
		||||
      function seg(i, dx, filter) {
 | 
			
		||||
        ins(
 | 
			
		||||
          g,
 | 
			
		||||
          ins(
 | 
			
		||||
            css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
 | 
			
		||||
            ins(
 | 
			
		||||
              css(
 | 
			
		||||
                vml('roundrect', {arcsize: o.corners}),
 | 
			
		||||
                {
 | 
			
		||||
                  width: r,
 | 
			
		||||
                  height: o.scale * o.width,
 | 
			
		||||
                  left: o.scale * o.radius,
 | 
			
		||||
                  top: -o.scale * o.width >> 1,
 | 
			
		||||
                  filter: filter
 | 
			
		||||
                }
 | 
			
		||||
              ),
 | 
			
		||||
              vml('fill', {color: getColor(o.color, i), opacity: o.opacity}),
 | 
			
		||||
              vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
 | 
			
		||||
            )
 | 
			
		||||
          )
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (o.shadow)
 | 
			
		||||
        for (i = 1; i <= o.lines; i++) {
 | 
			
		||||
          seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      for (i = 1; i <= o.lines; i++) seg(i);
 | 
			
		||||
      return ins(el, g);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Spinner.prototype.opacity = function(el, i, val, o) {
 | 
			
		||||
      var c = el.firstChild;
 | 
			
		||||
      o = o.shadow && o.lines || 0;
 | 
			
		||||
      if (c && i + o < c.childNodes.length) {
 | 
			
		||||
        c = c.childNodes[i + o]; c = c && c.firstChild; c = c && c.firstChild;
 | 
			
		||||
        if (c) c.opacity = val;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (typeof document !== 'undefined') {
 | 
			
		||||
    sheet = (function() {
 | 
			
		||||
      var el = createEl('style', {type : 'text/css'});
 | 
			
		||||
      ins(document.getElementsByTagName('head')[0], el);
 | 
			
		||||
      return el.sheet || el.styleSheet;
 | 
			
		||||
    }());
 | 
			
		||||
 | 
			
		||||
    var probe = css(createEl('group'), {behavior: 'url(#default#VML)'});
 | 
			
		||||
 | 
			
		||||
    if (!vendor(probe, 'transform') && probe.adj) initVML();
 | 
			
		||||
    else useCssAnimations = vendor(probe, 'animation');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return Spinner;
 | 
			
		||||
 | 
			
		||||
}));
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
.div-icon-less0 {
 | 
			
		||||
    background: royalblue;
 | 
			
		||||
    border: 1px solid royalblue;
 | 
			
		||||
}
 | 
			
		||||
.div-icon-0-10 {
 | 
			
		||||
    background: skyblue;
 | 
			
		||||
    border: 1px solid skyblue;
 | 
			
		||||
}
 | 
			
		||||
.div-icon-10-20 {
 | 
			
		||||
    background: yellow;
 | 
			
		||||
    border: 1px solid yellow;
 | 
			
		||||
}
 | 
			
		||||
.div-icon-20-30 {
 | 
			
		||||
    background: orange;
 | 
			
		||||
    border: 1px solid orange;
 | 
			
		||||
}
 | 
			
		||||
.div-icon-30-40 {
 | 
			
		||||
    background: orangered;
 | 
			
		||||
    border: 1px solid orangered;
 | 
			
		||||
}
 | 
			
		||||
.div-icon-more40 {
 | 
			
		||||
    background: red;
 | 
			
		||||
    border: 1px solid red;
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -0,0 +1,501 @@
 | 
			
		||||
// Generated by CoffeeScript 1.7.1
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   Stomp Over WebSocket http://www.jmesnil.net/stomp-websocket/doc/ | Apache License V2.0
 | 
			
		||||
 | 
			
		||||
   Copyright (C) 2010-2013 [Jeff Mesnil](http://jmesnil.net/)
 | 
			
		||||
   Copyright (C) 2012 [FuseSource, Inc.](http://fusesource.com)
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
(function() {
 | 
			
		||||
  var Byte, Client, Frame, Stomp,
 | 
			
		||||
    __hasProp = {}.hasOwnProperty,
 | 
			
		||||
    __slice = [].slice;
 | 
			
		||||
 | 
			
		||||
  Byte = {
 | 
			
		||||
    LF: '\x0A',
 | 
			
		||||
    NULL: '\x00'
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  Frame = (function() {
 | 
			
		||||
    var unmarshallSingle;
 | 
			
		||||
 | 
			
		||||
    function Frame(command, headers, body) {
 | 
			
		||||
      this.command = command;
 | 
			
		||||
      this.headers = headers != null ? headers : {};
 | 
			
		||||
      this.body = body != null ? body : '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Frame.prototype.toString = function() {
 | 
			
		||||
      var lines, name, skipContentLength, value, _ref;
 | 
			
		||||
      lines = [this.command];
 | 
			
		||||
      skipContentLength = this.headers['content-length'] === false ? true : false;
 | 
			
		||||
      if (skipContentLength) {
 | 
			
		||||
        delete this.headers['content-length'];
 | 
			
		||||
      }
 | 
			
		||||
      _ref = this.headers;
 | 
			
		||||
      for (name in _ref) {
 | 
			
		||||
        if (!__hasProp.call(_ref, name)) continue;
 | 
			
		||||
        value = _ref[name];
 | 
			
		||||
        lines.push("" + name + ":" + value);
 | 
			
		||||
      }
 | 
			
		||||
      if (this.body && !skipContentLength) {
 | 
			
		||||
        lines.push("content-length:" + (Frame.sizeOfUTF8(this.body)));
 | 
			
		||||
      }
 | 
			
		||||
      lines.push(Byte.LF + this.body);
 | 
			
		||||
      return lines.join(Byte.LF);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Frame.sizeOfUTF8 = function(s) {
 | 
			
		||||
      if (s) {
 | 
			
		||||
        return encodeURI(s).match(/%..|./g).length;
 | 
			
		||||
      } else {
 | 
			
		||||
        return 0;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    unmarshallSingle = function(data) {
 | 
			
		||||
      var body, chr, command, divider, headerLines, headers, i, idx, len, line, start, trim, _i, _j, _len, _ref, _ref1;
 | 
			
		||||
      divider = data.search(RegExp("" + Byte.LF + Byte.LF));
 | 
			
		||||
      headerLines = data.substring(0, divider).split(Byte.LF);
 | 
			
		||||
      command = headerLines.shift();
 | 
			
		||||
      headers = {};
 | 
			
		||||
      trim = function(str) {
 | 
			
		||||
        return str.replace(/^\s+|\s+$/g, '');
 | 
			
		||||
      };
 | 
			
		||||
      _ref = headerLines.reverse();
 | 
			
		||||
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
 | 
			
		||||
        line = _ref[_i];
 | 
			
		||||
        idx = line.indexOf(':');
 | 
			
		||||
        headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1));
 | 
			
		||||
      }
 | 
			
		||||
      body = '';
 | 
			
		||||
      start = divider + 2;
 | 
			
		||||
      if (headers['content-length']) {
 | 
			
		||||
        len = parseInt(headers['content-length']);
 | 
			
		||||
        body = ('' + data).substring(start, start + len);
 | 
			
		||||
      } else {
 | 
			
		||||
        chr = null;
 | 
			
		||||
        for (i = _j = start, _ref1 = data.length; start <= _ref1 ? _j < _ref1 : _j > _ref1; i = start <= _ref1 ? ++_j : --_j) {
 | 
			
		||||
          chr = data.charAt(i);
 | 
			
		||||
          if (chr === Byte.NULL) {
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          body += chr;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return new Frame(command, headers, body);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Frame.unmarshall = function(datas) {
 | 
			
		||||
      var frame, frames, last_frame, r;
 | 
			
		||||
      frames = datas.split(RegExp("" + Byte.NULL + Byte.LF + "*"));
 | 
			
		||||
      r = {
 | 
			
		||||
        frames: [],
 | 
			
		||||
        partial: ''
 | 
			
		||||
      };
 | 
			
		||||
      r.frames = (function() {
 | 
			
		||||
        var _i, _len, _ref, _results;
 | 
			
		||||
        _ref = frames.slice(0, -1);
 | 
			
		||||
        _results = [];
 | 
			
		||||
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
 | 
			
		||||
          frame = _ref[_i];
 | 
			
		||||
          _results.push(unmarshallSingle(frame));
 | 
			
		||||
        }
 | 
			
		||||
        return _results;
 | 
			
		||||
      })();
 | 
			
		||||
      last_frame = frames.slice(-1)[0];
 | 
			
		||||
      if (last_frame === Byte.LF || (last_frame.search(RegExp("" + Byte.NULL + Byte.LF + "*$"))) !== -1) {
 | 
			
		||||
        r.frames.push(unmarshallSingle(last_frame));
 | 
			
		||||
      } else {
 | 
			
		||||
        r.partial = last_frame;
 | 
			
		||||
      }
 | 
			
		||||
      return r;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Frame.marshall = function(command, headers, body) {
 | 
			
		||||
      var frame;
 | 
			
		||||
      frame = new Frame(command, headers, body);
 | 
			
		||||
      return frame.toString() + Byte.NULL;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return Frame;
 | 
			
		||||
 | 
			
		||||
  })();
 | 
			
		||||
 | 
			
		||||
  Client = (function() {
 | 
			
		||||
    var now;
 | 
			
		||||
 | 
			
		||||
    function Client(ws) {
 | 
			
		||||
      this.ws = ws;
 | 
			
		||||
      this.ws.binaryType = "arraybuffer";
 | 
			
		||||
      this.counter = 0;
 | 
			
		||||
      this.connected = false;
 | 
			
		||||
      this.heartbeat = {
 | 
			
		||||
        outgoing: 10000,
 | 
			
		||||
        incoming: 10000
 | 
			
		||||
      };
 | 
			
		||||
      this.maxWebSocketFrameSize = 16 * 1024;
 | 
			
		||||
      this.subscriptions = {};
 | 
			
		||||
      this.partialData = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Client.prototype.debug = function(message) {
 | 
			
		||||
      var _ref;
 | 
			
		||||
      return typeof window !== "undefined" && window !== null ? (_ref = window.console) != null ? _ref.log(message) : void 0 : void 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    now = function() {
 | 
			
		||||
      if (Date.now) {
 | 
			
		||||
        return Date.now();
 | 
			
		||||
      } else {
 | 
			
		||||
        return new Date().valueOf;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype._transmit = function(command, headers, body) {
 | 
			
		||||
      var out;
 | 
			
		||||
      out = Frame.marshall(command, headers, body);
 | 
			
		||||
      if (typeof this.debug === "function") {
 | 
			
		||||
        this.debug(">>> " + out);
 | 
			
		||||
      }
 | 
			
		||||
      while (true) {
 | 
			
		||||
        if (out.length > this.maxWebSocketFrameSize) {
 | 
			
		||||
          this.ws.send(out.substring(0, this.maxWebSocketFrameSize));
 | 
			
		||||
          out = out.substring(this.maxWebSocketFrameSize);
 | 
			
		||||
          if (typeof this.debug === "function") {
 | 
			
		||||
            this.debug("remaining = " + out.length);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          return this.ws.send(out);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype._setupHeartbeat = function(headers) {
 | 
			
		||||
      var serverIncoming, serverOutgoing, ttl, v, _ref, _ref1;
 | 
			
		||||
      if ((_ref = headers.version) !== Stomp.VERSIONS.V1_1 && _ref !== Stomp.VERSIONS.V1_2) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      _ref1 = (function() {
 | 
			
		||||
        var _i, _len, _ref1, _results;
 | 
			
		||||
        _ref1 = headers['heart-beat'].split(",");
 | 
			
		||||
        _results = [];
 | 
			
		||||
        for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
 | 
			
		||||
          v = _ref1[_i];
 | 
			
		||||
          _results.push(parseInt(v));
 | 
			
		||||
        }
 | 
			
		||||
        return _results;
 | 
			
		||||
      })(), serverOutgoing = _ref1[0], serverIncoming = _ref1[1];
 | 
			
		||||
      if (!(this.heartbeat.outgoing === 0 || serverIncoming === 0)) {
 | 
			
		||||
        ttl = Math.max(this.heartbeat.outgoing, serverIncoming);
 | 
			
		||||
        if (typeof this.debug === "function") {
 | 
			
		||||
          this.debug("send PING every " + ttl + "ms");
 | 
			
		||||
        }
 | 
			
		||||
        this.pinger = Stomp.setInterval(ttl, (function(_this) {
 | 
			
		||||
          return function() {
 | 
			
		||||
            _this.ws.send(Byte.LF);
 | 
			
		||||
            return typeof _this.debug === "function" ? _this.debug(">>> PING") : void 0;
 | 
			
		||||
          };
 | 
			
		||||
        })(this));
 | 
			
		||||
      }
 | 
			
		||||
      if (!(this.heartbeat.incoming === 0 || serverOutgoing === 0)) {
 | 
			
		||||
        ttl = Math.max(this.heartbeat.incoming, serverOutgoing);
 | 
			
		||||
        if (typeof this.debug === "function") {
 | 
			
		||||
          this.debug("check PONG every " + ttl + "ms");
 | 
			
		||||
        }
 | 
			
		||||
        return this.ponger = Stomp.setInterval(ttl, (function(_this) {
 | 
			
		||||
          return function() {
 | 
			
		||||
            var delta;
 | 
			
		||||
            delta = now() - _this.serverActivity;
 | 
			
		||||
            if (delta > ttl * 2) {
 | 
			
		||||
              if (typeof _this.debug === "function") {
 | 
			
		||||
                _this.debug("did not receive server activity for the last " + delta + "ms");
 | 
			
		||||
              }
 | 
			
		||||
              return _this.ws.close();
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
        })(this));
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype._parseConnect = function() {
 | 
			
		||||
      var args, connectCallback, errorCallback, headers;
 | 
			
		||||
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
 | 
			
		||||
      headers = {};
 | 
			
		||||
      switch (args.length) {
 | 
			
		||||
        case 2:
 | 
			
		||||
          headers = args[0], connectCallback = args[1];
 | 
			
		||||
          break;
 | 
			
		||||
        case 3:
 | 
			
		||||
          if (args[1] instanceof Function) {
 | 
			
		||||
            headers = args[0], connectCallback = args[1], errorCallback = args[2];
 | 
			
		||||
          } else {
 | 
			
		||||
            headers.login = args[0], headers.passcode = args[1], connectCallback = args[2];
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case 4:
 | 
			
		||||
          headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3];
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3], headers.host = args[4];
 | 
			
		||||
      }
 | 
			
		||||
      return [headers, connectCallback, errorCallback];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype.connect = function() {
 | 
			
		||||
      var args, errorCallback, headers, out;
 | 
			
		||||
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
 | 
			
		||||
      out = this._parseConnect.apply(this, args);
 | 
			
		||||
      headers = out[0], this.connectCallback = out[1], errorCallback = out[2];
 | 
			
		||||
      if (typeof this.debug === "function") {
 | 
			
		||||
        this.debug("Opening Web Socket...");
 | 
			
		||||
      }
 | 
			
		||||
      this.ws.onmessage = (function(_this) {
 | 
			
		||||
        return function(evt) {
 | 
			
		||||
          var arr, c, client, data, frame, messageID, onreceive, subscription, unmarshalledData, _i, _len, _ref, _results;
 | 
			
		||||
          data = typeof ArrayBuffer !== 'undefined' && evt.data instanceof ArrayBuffer ? (arr = new Uint8Array(evt.data), typeof _this.debug === "function" ? _this.debug("--- got data length: " + arr.length) : void 0, ((function() {
 | 
			
		||||
            var _i, _len, _results;
 | 
			
		||||
            _results = [];
 | 
			
		||||
            for (_i = 0, _len = arr.length; _i < _len; _i++) {
 | 
			
		||||
              c = arr[_i];
 | 
			
		||||
              _results.push(String.fromCharCode(c));
 | 
			
		||||
            }
 | 
			
		||||
            return _results;
 | 
			
		||||
          })()).join('')) : evt.data;
 | 
			
		||||
          _this.serverActivity = now();
 | 
			
		||||
          if (data === Byte.LF) {
 | 
			
		||||
            if (typeof _this.debug === "function") {
 | 
			
		||||
              _this.debug("<<< PONG");
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          if (typeof _this.debug === "function") {
 | 
			
		||||
            _this.debug("<<< " + data);
 | 
			
		||||
          }
 | 
			
		||||
          unmarshalledData = Frame.unmarshall(_this.partialData + data);
 | 
			
		||||
          _this.partialData = unmarshalledData.partial;
 | 
			
		||||
          _ref = unmarshalledData.frames;
 | 
			
		||||
          _results = [];
 | 
			
		||||
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
 | 
			
		||||
            frame = _ref[_i];
 | 
			
		||||
            switch (frame.command) {
 | 
			
		||||
              case "CONNECTED":
 | 
			
		||||
                if (typeof _this.debug === "function") {
 | 
			
		||||
                  _this.debug("connected to server " + frame.headers.server);
 | 
			
		||||
                }
 | 
			
		||||
                _this.connected = true;
 | 
			
		||||
                _this._setupHeartbeat(frame.headers);
 | 
			
		||||
                _results.push(typeof _this.connectCallback === "function" ? _this.connectCallback(frame) : void 0);
 | 
			
		||||
                break;
 | 
			
		||||
              case "MESSAGE":
 | 
			
		||||
                subscription = frame.headers.subscription;
 | 
			
		||||
                onreceive = _this.subscriptions[subscription] || _this.onreceive;
 | 
			
		||||
                if (onreceive) {
 | 
			
		||||
                  client = _this;
 | 
			
		||||
                  messageID = frame.headers["message-id"];
 | 
			
		||||
                  frame.ack = function(headers) {
 | 
			
		||||
                    if (headers == null) {
 | 
			
		||||
                      headers = {};
 | 
			
		||||
                    }
 | 
			
		||||
                    return client.ack(messageID, subscription, headers);
 | 
			
		||||
                  };
 | 
			
		||||
                  frame.nack = function(headers) {
 | 
			
		||||
                    if (headers == null) {
 | 
			
		||||
                      headers = {};
 | 
			
		||||
                    }
 | 
			
		||||
                    return client.nack(messageID, subscription, headers);
 | 
			
		||||
                  };
 | 
			
		||||
                  _results.push(onreceive(frame));
 | 
			
		||||
                } else {
 | 
			
		||||
                  _results.push(typeof _this.debug === "function" ? _this.debug("Unhandled received MESSAGE: " + frame) : void 0);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
              case "RECEIPT":
 | 
			
		||||
                _results.push(typeof _this.onreceipt === "function" ? _this.onreceipt(frame) : void 0);
 | 
			
		||||
                break;
 | 
			
		||||
              case "ERROR":
 | 
			
		||||
                _results.push(typeof errorCallback === "function" ? errorCallback(frame) : void 0);
 | 
			
		||||
                break;
 | 
			
		||||
              default:
 | 
			
		||||
                _results.push(typeof _this.debug === "function" ? _this.debug("Unhandled frame: " + frame) : void 0);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          return _results;
 | 
			
		||||
        };
 | 
			
		||||
      })(this);
 | 
			
		||||
      this.ws.onclose = (function(_this) {
 | 
			
		||||
        return function() {
 | 
			
		||||
          var msg;
 | 
			
		||||
          msg = "Whoops! Lost connection to " + _this.ws.url;
 | 
			
		||||
          if (typeof _this.debug === "function") {
 | 
			
		||||
            _this.debug(msg);
 | 
			
		||||
          }
 | 
			
		||||
          _this._cleanUp();
 | 
			
		||||
          return typeof errorCallback === "function" ? errorCallback(msg) : void 0;
 | 
			
		||||
        };
 | 
			
		||||
      })(this);
 | 
			
		||||
      return this.ws.onopen = (function(_this) {
 | 
			
		||||
        return function() {
 | 
			
		||||
          if (typeof _this.debug === "function") {
 | 
			
		||||
            _this.debug('Web Socket Opened...');
 | 
			
		||||
          }
 | 
			
		||||
          headers["accept-version"] = Stomp.VERSIONS.supportedVersions();
 | 
			
		||||
          headers["heart-beat"] = [_this.heartbeat.outgoing, _this.heartbeat.incoming].join(',');
 | 
			
		||||
          return _this._transmit("CONNECT", headers);
 | 
			
		||||
        };
 | 
			
		||||
      })(this);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype.disconnect = function(disconnectCallback, headers) {
 | 
			
		||||
      if (headers == null) {
 | 
			
		||||
        headers = {};
 | 
			
		||||
      }
 | 
			
		||||
      this._transmit("DISCONNECT", headers);
 | 
			
		||||
      this.ws.onclose = null;
 | 
			
		||||
      this.ws.close();
 | 
			
		||||
      this._cleanUp();
 | 
			
		||||
      return typeof disconnectCallback === "function" ? disconnectCallback() : void 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype._cleanUp = function() {
 | 
			
		||||
      this.connected = false;
 | 
			
		||||
      if (this.pinger) {
 | 
			
		||||
        Stomp.clearInterval(this.pinger);
 | 
			
		||||
      }
 | 
			
		||||
      if (this.ponger) {
 | 
			
		||||
        return Stomp.clearInterval(this.ponger);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype.send = function(destination, headers, body) {
 | 
			
		||||
      if (headers == null) {
 | 
			
		||||
        headers = {};
 | 
			
		||||
      }
 | 
			
		||||
      if (body == null) {
 | 
			
		||||
        body = '';
 | 
			
		||||
      }
 | 
			
		||||
      headers.destination = destination;
 | 
			
		||||
      return this._transmit("SEND", headers, body);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype.subscribe = function(destination, callback, headers) {
 | 
			
		||||
      var client;
 | 
			
		||||
      if (headers == null) {
 | 
			
		||||
        headers = {};
 | 
			
		||||
      }
 | 
			
		||||
      if (!headers.id) {
 | 
			
		||||
        headers.id = "sub-" + this.counter++;
 | 
			
		||||
      }
 | 
			
		||||
      headers.destination = destination;
 | 
			
		||||
      this.subscriptions[headers.id] = callback;
 | 
			
		||||
      this._transmit("SUBSCRIBE", headers);
 | 
			
		||||
      client = this;
 | 
			
		||||
      return {
 | 
			
		||||
        id: headers.id,
 | 
			
		||||
        unsubscribe: function() {
 | 
			
		||||
          return client.unsubscribe(headers.id);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype.unsubscribe = function(id) {
 | 
			
		||||
      delete this.subscriptions[id];
 | 
			
		||||
      return this._transmit("UNSUBSCRIBE", {
 | 
			
		||||
        id: id
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype.begin = function(transaction) {
 | 
			
		||||
      var client, txid;
 | 
			
		||||
      txid = transaction || "tx-" + this.counter++;
 | 
			
		||||
      this._transmit("BEGIN", {
 | 
			
		||||
        transaction: txid
 | 
			
		||||
      });
 | 
			
		||||
      client = this;
 | 
			
		||||
      return {
 | 
			
		||||
        id: txid,
 | 
			
		||||
        commit: function() {
 | 
			
		||||
          return client.commit(txid);
 | 
			
		||||
        },
 | 
			
		||||
        abort: function() {
 | 
			
		||||
          return client.abort(txid);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype.commit = function(transaction) {
 | 
			
		||||
      return this._transmit("COMMIT", {
 | 
			
		||||
        transaction: transaction
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype.abort = function(transaction) {
 | 
			
		||||
      return this._transmit("ABORT", {
 | 
			
		||||
        transaction: transaction
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype.ack = function(messageID, subscription, headers) {
 | 
			
		||||
      if (headers == null) {
 | 
			
		||||
        headers = {};
 | 
			
		||||
      }
 | 
			
		||||
      headers["message-id"] = messageID;
 | 
			
		||||
      headers.subscription = subscription;
 | 
			
		||||
      return this._transmit("ACK", headers);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Client.prototype.nack = function(messageID, subscription, headers) {
 | 
			
		||||
      if (headers == null) {
 | 
			
		||||
        headers = {};
 | 
			
		||||
      }
 | 
			
		||||
      headers["message-id"] = messageID;
 | 
			
		||||
      headers.subscription = subscription;
 | 
			
		||||
      return this._transmit("NACK", headers);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return Client;
 | 
			
		||||
 | 
			
		||||
  })();
 | 
			
		||||
 | 
			
		||||
  Stomp = {
 | 
			
		||||
    VERSIONS: {
 | 
			
		||||
      V1_0: '1.0',
 | 
			
		||||
      V1_1: '1.1',
 | 
			
		||||
      V1_2: '1.2',
 | 
			
		||||
      supportedVersions: function() {
 | 
			
		||||
        return '1.1,1.0';
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    client: function(url, protocols) {
 | 
			
		||||
      var klass, ws;
 | 
			
		||||
      if (protocols == null) {
 | 
			
		||||
        protocols = ['v10.stomp', 'v11.stomp'];
 | 
			
		||||
      }
 | 
			
		||||
      klass = Stomp.WebSocketClass || WebSocket;
 | 
			
		||||
      ws = new klass(url, protocols);
 | 
			
		||||
      return new Client(ws);
 | 
			
		||||
    },
 | 
			
		||||
    over: function(ws) {
 | 
			
		||||
      return new Client(ws);
 | 
			
		||||
    },
 | 
			
		||||
    Frame: Frame
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (typeof exports !== "undefined" && exports !== null) {
 | 
			
		||||
    exports.Stomp = Stomp;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (typeof window !== "undefined" && window !== null) {
 | 
			
		||||
    Stomp.setInterval = function(interval, f) {
 | 
			
		||||
      return window.setInterval(f, interval);
 | 
			
		||||
    };
 | 
			
		||||
    Stomp.clearInterval = function(id) {
 | 
			
		||||
      return window.clearInterval(id);
 | 
			
		||||
    };
 | 
			
		||||
    window.Stomp = Stomp;
 | 
			
		||||
  } else if (!exports) {
 | 
			
		||||
    self.Stomp = Stomp;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}).call(this);
 | 
			
		||||
@ -0,0 +1,92 @@
 | 
			
		||||
---
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
kind: Template
 | 
			
		||||
metadata:
 | 
			
		||||
  name: selenium-hub-parksmap
 | 
			
		||||
  annotations:
 | 
			
		||||
    description: "A Selenium Grid"
 | 
			
		||||
    iconClass: "icon-selenium"
 | 
			
		||||
    tags: "selenium,hub"
 | 
			
		||||
objects:
 | 
			
		||||
  - apiVersion: v1
 | 
			
		||||
    kind: Service
 | 
			
		||||
    metadata:
 | 
			
		||||
      name: selenium-hub-parksmap
 | 
			
		||||
    spec:
 | 
			
		||||
      selector:
 | 
			
		||||
        type: selenium-hub
 | 
			
		||||
      type: ClusterIP
 | 
			
		||||
      ports:
 | 
			
		||||
        - name: web
 | 
			
		||||
          port: 4444
 | 
			
		||||
          targetPort: 4444
 | 
			
		||||
          protocol: TCP
 | 
			
		||||
        - name: node
 | 
			
		||||
          port: 5555
 | 
			
		||||
          targetPort: 5555
 | 
			
		||||
          protocol: TCP
 | 
			
		||||
        - name: publish
 | 
			
		||||
          port: 4442
 | 
			
		||||
          targetPort: 4442
 | 
			
		||||
          protocol: TCP
 | 
			
		||||
        - name: subscribe
 | 
			
		||||
          port: 4443
 | 
			
		||||
          targetPort: 4443
 | 
			
		||||
          protocol: TCP
 | 
			
		||||
  - apiVersion: v1
 | 
			
		||||
    kind: DeploymentConfig
 | 
			
		||||
    metadata:
 | 
			
		||||
      name: selenium-hub-parksmap
 | 
			
		||||
    spec:
 | 
			
		||||
      replicas: 1
 | 
			
		||||
      selector:
 | 
			
		||||
        type: selenium-hub
 | 
			
		||||
      template:
 | 
			
		||||
        metadata:
 | 
			
		||||
          labels:
 | 
			
		||||
            type: selenium-hub
 | 
			
		||||
          name: selenium-hub-parksmap
 | 
			
		||||
        spec:
 | 
			
		||||
          containers:
 | 
			
		||||
          - env:
 | 
			
		||||
            - name: JAVA_OPTS
 | 
			
		||||
              value: "-Xmx512m"
 | 
			
		||||
            - name: POOL_MAX
 | 
			
		||||
              value: "30000"
 | 
			
		||||
            - name: GRID_NEW_SESSION_WAIT_TIMEOUT
 | 
			
		||||
              value: "30"
 | 
			
		||||
            - name: GRID_JETTY_MAX_THREADS
 | 
			
		||||
              value: "100"
 | 
			
		||||
            - name: GRID_NODE_POLLING
 | 
			
		||||
              value: "5"
 | 
			
		||||
            - name: GRID_CLEAN_UP_CYCLE
 | 
			
		||||
              value: "300"
 | 
			
		||||
            - name: GRID_TIMEOUT
 | 
			
		||||
              value: "300"
 | 
			
		||||
            - name: GRID_BROWSER_TIMEOUT
 | 
			
		||||
              value: "300"
 | 
			
		||||
            - name: GRID_MAX_SESSION
 | 
			
		||||
              value: "3"
 | 
			
		||||
            - name: GRID_UNREGISTER_IF_STILL_DOWN_AFTER
 | 
			
		||||
              value: "5"
 | 
			
		||||
            image: docker.io/selenium/hub:4.18.1
 | 
			
		||||
            name: selenium-hub-parksmap
 | 
			
		||||
            ports:
 | 
			
		||||
            - containerPort: 4444
 | 
			
		||||
              protocol: TCP
 | 
			
		||||
  - apiVersion: v1
 | 
			
		||||
    kind: Route
 | 
			
		||||
    metadata:
 | 
			
		||||
      name: selenium-hub-parksmap
 | 
			
		||||
    spec:
 | 
			
		||||
      host: selenium-hub-parksmap.apps.cluster-272j9.dynamic.redhatworkshops.io
 | 
			
		||||
      port:
 | 
			
		||||
        targetPort: 4444
 | 
			
		||||
      tls:
 | 
			
		||||
        termination: edge
 | 
			
		||||
      to:
 | 
			
		||||
        kind: Service
 | 
			
		||||
        name: selenium-hub-parksmap
 | 
			
		||||
        weight: 100
 | 
			
		||||
  labels:
 | 
			
		||||
    selenium-hub: master
 | 
			
		||||
		Reference in New Issue
	
	Block a user