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