New sample showing usage of spring-flo to visualize Spring Integration apps
This commit is contained in:
202
samples/spring-flo-si/LICENSE.txt
Normal file
202
samples/spring-flo-si/LICENSE.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
42
samples/spring-flo-si/README.adoc
Normal file
42
samples/spring-flo-si/README.adoc
Normal file
@@ -0,0 +1,42 @@
|
||||
# A sample app using spring-flo to visualize Spring Integration applications
|
||||
|
||||
This sample uses flo as a live viewer for Spring Integration applications.
|
||||
|
||||
This https://spring.io/blog/2016/04/26/spring-integration-4-3-m2-is-available[blog] discusses
|
||||
how to activate the new endpoint in a Spring Integration application. When
|
||||
the endpoint is active in your SI application, just enter that into the spring
|
||||
flo viewer. You should then see something like this:
|
||||
|
||||
image::imgs/basicGraph.png[width="800"]
|
||||
|
||||
# Running the sample
|
||||
|
||||
A basic Spring Boot app is used to serve the sample. Launch it with:
|
||||
|
||||
mvn spring-boot:run
|
||||
|
||||
then open `http://localhost:8082`. In the `Spring Integration Graph Endpoint`
|
||||
field enter the url for the spring integration data, for example: `http://localhost:8080/integration`
|
||||
and the graph should load.
|
||||
|
||||
|
||||
# Using the application
|
||||
|
||||
Once the graph is loaded you can drag nodes around to adjust the layout. (Press the `Read-Only`
|
||||
button to prevent moving nodes around). Hovering over a node will show a tool tip with more
|
||||
information for that element. If you hover over a channel you will see many stats about
|
||||
traffic flowing over that channel:
|
||||
|
||||
image::imgs/tooltip.png[width="500"]
|
||||
|
||||
It is possible to select one of those stats of interest and have it shown directly on the graph.
|
||||
Simply select what you are interested in and enter the name of that stat in the `Link label path`
|
||||
field at the top. The values for that stat will then be shown on the links between graph
|
||||
elements:
|
||||
|
||||
image::imgs/numbersGraph.png[width="800"]
|
||||
|
||||
If you enter a Refresh rate (minimal allowed is 250ms) then that stat will actually
|
||||
update on the graph at that rate with a small animation indicating where on the graph changes
|
||||
in value are occurring.
|
||||
|
||||
BIN
samples/spring-flo-si/imgs/basicGraph.png
Normal file
BIN
samples/spring-flo-si/imgs/basicGraph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
BIN
samples/spring-flo-si/imgs/numbersGraph.png
Normal file
BIN
samples/spring-flo-si/imgs/numbersGraph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
BIN
samples/spring-flo-si/imgs/tooltip.png
Normal file
BIN
samples/spring-flo-si/imgs/tooltip.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
115
samples/spring-flo-si/pom.xml
Executable file
115
samples/spring-flo-si/pom.xml
Executable file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-flo-sample-si</artifactId>
|
||||
<version>0.0.1.BUILD-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>spring-flo-sample</name>
|
||||
<description>Spring Flo Sample</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.3.2.RELEASE</version>
|
||||
<relativePath /> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<!-- TODO maybe adjust flos dependencies so this configuration isn't so complicated -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>codemirror</artifactId>
|
||||
<version>5.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>angular</artifactId>
|
||||
<version>1.3.8</version> <!-- flo wants 1.3.5 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>jshint</artifactId>
|
||||
<version>2.8.0</version> <!-- flo wants 2.6.3 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>requirejs</artifactId>
|
||||
<version>2.1.18</version> <!-- flo wants 2.1.15 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>jquery</artifactId>
|
||||
<version>2.2.0</version> <!-- jointjs wants 2.0.3 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>lodash</artifactId>
|
||||
<version>3.10.1</version> <!-- jointjs -> graphlib -> wants 3.10.1-amd -->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>webjars-locator</artifactId>
|
||||
<version>0.31</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>requirejs-domready</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>requirejs-text</artifactId>
|
||||
<version>2.0.15</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>jointjs</artifactId>
|
||||
<version>0.9.7</version> <!-- flo wants 0.9.6 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>json5</artifactId>
|
||||
<version>0.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>spring-flo</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.webjars.bower</groupId>
|
||||
<artifactId>joint</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<start-class>org.springframework.flo.Application</start-class>
|
||||
<java.version>1.7</java.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
46
samples/spring-flo-si/src/main/java/org/springframework/flo/Application.java
Executable file
46
samples/spring-flo-si/src/main/java/org/springframework/flo/Application.java
Executable file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.flo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
// @Bean
|
||||
// public WebMvcConfigurer corsConfigurer() {
|
||||
// return new WebMvcConfigurerAdapter() {
|
||||
// @Override
|
||||
// public void addCorsMappings(CorsRegistry registry) {
|
||||
// registry.addMapping("/").allowedOrigins("http://localhost:9000");
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
server.port=8082
|
||||
189
samples/spring-flo-si/src/main/resources/static/css/flosi.css
Normal file
189
samples/spring-flo-si/src/main/resources/static/css/flosi.css
Normal file
@@ -0,0 +1,189 @@
|
||||
|
||||
.header {
|
||||
font-weight: 400;
|
||||
font-family: "Roboto",sans-serif;
|
||||
font-size: 36px;
|
||||
color: #ffffff;
|
||||
padding: 2px;
|
||||
background-color: #283E49;
|
||||
border: none;
|
||||
/* border-top: 4px solid #6db33f; */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.input-label-div {
|
||||
font-family: "Roboto",sans-serif;
|
||||
font-size: 18px;
|
||||
color: #ffffff;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#labelpath {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#refreshrate {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.inputfield {
|
||||
font-family: "Roboto",sans-serif;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#endpoint {
|
||||
font-family: "Roboto",sans-serif;
|
||||
font-size: 18px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#endpoint-button {
|
||||
font-family: "Roboto",sans-serif;
|
||||
font-size: 18px;
|
||||
height:80px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #283E49;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.header-small {
|
||||
font: 300 24px "Helvetica Neue";
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.border-selected {
|
||||
stroke: #34302d;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.controls {
|
||||
border-radius: 2px;
|
||||
border: solid;
|
||||
border-color: #283E49;
|
||||
padding: 5px;
|
||||
margin-top: 3px;
|
||||
background-color: #283E49;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.button {
|
||||
color: #ffffff;
|
||||
background-image: none;
|
||||
border-radius: 2px;
|
||||
background-color: #00B0A7;
|
||||
font-size: 18px;
|
||||
line-height: 14px;
|
||||
font-family: "Roboto",sans-serif;
|
||||
border: 2px solid #00B0A7;
|
||||
padding: 5px 20px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.button span {
|
||||
background-color: #34302d;
|
||||
background-image: none;
|
||||
border-radius: 2px;
|
||||
color: #f1f1f1;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
font-family: Montserrat,sans-serif;
|
||||
border: 2px solid #6db33f;
|
||||
padding: 5px 20px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.button input {
|
||||
background-color: #34302d;
|
||||
background-image: none;
|
||||
color: #f1f1f1;
|
||||
font-size: 14px;
|
||||
font-family: Montserrat,sans-serif;
|
||||
text-shadow: none;
|
||||
border: 0px;
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
button.off {
|
||||
background-color: #00B0A7;
|
||||
color: #283E49;
|
||||
}
|
||||
|
||||
.flow-definition-container {
|
||||
display: none;
|
||||
border: 1px solid;
|
||||
border-color: #283E49;
|
||||
border-radius: 2px;
|
||||
margin-top: 3px;
|
||||
background-color: #ffffff;
|
||||
font-family: monospace;
|
||||
z-index: 2;
|
||||
width:100%;
|
||||
height:100px;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea:input {
|
||||
outline: none;
|
||||
border: 1px solid #6db33f;
|
||||
}
|
||||
|
||||
textarea:input:focus {
|
||||
outline: none;
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
.canvas {
|
||||
border-color: #283E49;
|
||||
}
|
||||
.flow-definition {
|
||||
border: 5px;
|
||||
height:100%;
|
||||
width:100%;
|
||||
font-size: 16px;
|
||||
resize: none;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* The text label on the nodes */
|
||||
|
||||
.label {
|
||||
font-family: 'Ubuntu Mono';
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
/* The class for the 'icon/unicode_char' on the nodes */
|
||||
.label2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.box {
|
||||
stroke: #283e49;
|
||||
}
|
||||
|
||||
|
||||
.node-tooltip-option-name {
|
||||
font-family: 'Ubuntu Mono', monospace;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="collapse-handle" viewBox="0 0 24 24">
|
||||
<line x1="4" y1="4" x2="20" y2="20" stroke="black" stroke-width="8" stroke-linecap="round"/>
|
||||
<line x1="20" y1="4" x2="4" y2="20" stroke="black" stroke-width="8" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 296 B |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="27.963px" height="27.963px" viewBox="0 0 27.963 27.963" style="enable-background:new 0 0 27.963 27.963;"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<circle cx="13.9815" cy="13.9815" r="12.9815" stroke="black" stroke-width="1" fill="red" />
|
||||
<polygon style="fill:white;" points="15.578,17.158 16.19,4.579 11.803,4.579 12.413,17.158 "/>
|
||||
<path style="fill:white;" d="M13.997,18.546c-1.471,0-2.5,1.029-2.5,2.526c0,1.443,0.999,2.528,2.444,2.528h0.056
|
||||
c1.499,0,2.469-1.085,2.469-2.528C16.44,19.575,15.467,18.546,13.997,18.546z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 791 B |
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="1000px" height="1000px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M139.2,264.6c-47.5,71.7-72.7,155.2-72.7,241.5c0,59,11.6,116.3,34.4,170.3c22,52.1,53.6,98.9,93.7,139
|
||||
c40.1,40.1,86.9,71.7,139,93.7c54,22.8,111.3,34.4,170.3,34.4s116.3-11.6,170.3-34.4c52.1-22,98.9-53.6,139-93.7
|
||||
c40.1-40.1,71.7-86.9,93.7-139c22.8-54,34.4-111.3,34.4-170.3c0-50.4-8.5-99.8-25.3-146.9c-16.2-45.5-39.8-87.8-70.1-125.7
|
||||
c-30-37.5-65.6-69.7-106-95.6c-41.1-26.4-86-45.6-133.4-57l-39.8,165.3c57.3,13.8,109.3,46.9,146.3,93.4
|
||||
c38.1,47.8,58.3,105.4,58.3,166.7c0,71.4-27.8,138.6-78.3,189.1c-50.5,50.5-117.7,78.3-189.1,78.3s-138.6-27.8-189.1-78.3
|
||||
c-50.5-50.5-78.3-117.7-78.3-189.1c0-52.8,15.3-103.8,44.3-147.6c6.5-9.9,13.7-19.2,21.4-28.1L399.1,456L508,70.1l-406.4,0.1
|
||||
l95.6,124C175.7,215.5,156.2,239,139.2,264.6z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
41
samples/spring-flo-si/src/main/resources/static/index.html
Executable file
41
samples/spring-flo-si/src/main/resources/static/index.html
Executable file
@@ -0,0 +1,41 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<title>Spring Integration Viewer</title>
|
||||
<link href="http://fonts.googleapis.com/css?family=Open+Sans|Montserrat:400,700" rel="stylesheet" type="text/css" />
|
||||
<link href="http://fonts.googleapis.com/css?family=Ubuntu+Mono|Montserrat:400,700" rel="stylesheet" type="text/css" />
|
||||
<link href="http://fonts.googleapis.com/css?family=Roboto|Montserrat:400,700" rel="stylesheet" type="text/css" />
|
||||
<link href="/webjars/spring-flo/dist/spring-flo.css" rel="stylesheet" />
|
||||
<link href="css/flosi.css" rel="stylesheet" />
|
||||
|
||||
<script data-main="js/main" src="/webjars/requirejs/require.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header" class="header">
|
||||
Spring Integration Viewer
|
||||
</div>
|
||||
<div class="input-label-div" ng-controller="SiController">
|
||||
Spring Integration Graph endpoint:
|
||||
<input type="text" class="inputfield" name="endpoint" id="endpoint" ng-model="endpoint" ng-enter="load(endpoint)">
|
||||
<input type="submit" class="button" value="Load" ng-click="load(endpoint)">
|
||||
Link label path:
|
||||
<input type="text" class="inputfield" name="labelpath" id="labelpath" ng-model="labelpath" ng-enter="updateLabelPath(labelpath)">
|
||||
Refresh rate (ms):
|
||||
<input type="text" class="inputfield" name="refreshrate" id="refreshrate" ng-model="refreshrate" ng-enter="updateRefreshRate(refreshrate)">
|
||||
</div>
|
||||
<div id="flo-container">
|
||||
<flo-editor ng-cloak metamodel-service-name="SampleMetamodelService" render-service-name="SampleRenderService" editor-service-name="SampleEditorService" palette-size="170"
|
||||
min-zoom="10" max-zoom="300" zoom-step="10" ng-init="canvasControls={zoom:true};flo.noPalette=true;">
|
||||
<div id="controls" class="controls">
|
||||
<button class="button" id="performLayout" ng-click="flo.performLayout(); flo.fitToPage();">Reset Layout</button>
|
||||
<button class="button" id="readOnly" ng-click="flo.readOnlyCanvas(!flo.readOnlyCanvas())" ng-class="{off:!flo.readOnlyCanvas()}">Read-Only</button>
|
||||
</div>
|
||||
<div class="flow-definition-container">
|
||||
<textarea ng-if="editor" dsl-editor="true" id="flow-definition" class="flow-definition"></textarea>
|
||||
<textarea ng-if="!editor" id="flow-definition" class="flow-definition" placeholder="Enter stream definition..."
|
||||
ng-model="definition.text" ng-keyup="flo.scheduleUpdateGraphRepresentation(); $event.stopPropagation();" ng-blur="flo.enableSyncing(true)" ng-focus="flo.enableSyncing(false)"></textarea>
|
||||
</div>
|
||||
</flo-editor>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var joint = require('joint');
|
||||
|
||||
return ['$log', function($log) {
|
||||
|
||||
function createHandles(flo, createHandle, element) {
|
||||
var bbox = element.getBBox();
|
||||
var pt = bbox.origin().offset(bbox.width + 3, bbox.height + 3);
|
||||
createHandle(element, 'remove', flo.deleteSelectedNode, pt);
|
||||
}
|
||||
|
||||
function validatePort(/*flo, cellView, magnet*/) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateLink(flo, cellViewS, magnetS, cellViewT, magnetT/*, end, linkView*/) {
|
||||
// Prevent linking from input ports.
|
||||
if (magnetS && magnetS.getAttribute('type') === 'input') {
|
||||
return false;
|
||||
}
|
||||
// Prevent linking from output ports to input ports within one element.
|
||||
if (cellViewS === cellViewT) {
|
||||
return false;
|
||||
}
|
||||
// Prevent linking to input ports.
|
||||
if (magnetT && magnetT.getAttribute('type') === 'output') {
|
||||
return false;
|
||||
}
|
||||
return cellViewS.model && cellViewT.model && !(cellViewS.model instanceof joint.shapes.flo.ErrorDecoration) && !(cellViewT.model instanceof joint.shapes.flo.ErrorDecoration);
|
||||
}
|
||||
|
||||
function preDelete(flo, cell) {
|
||||
repairDamage(flo, cell);
|
||||
}
|
||||
|
||||
function handleNodeDropping(flo, dragDescriptor) {
|
||||
// this is a viewer not an editor, so do not adjust the graph structure
|
||||
}
|
||||
|
||||
function calculateDragDescriptor(flo, draggedView, targetUnderMouse, point, context) {
|
||||
// check if it's a tap being dragged
|
||||
var source = draggedView.model;
|
||||
if ((targetUnderMouse instanceof joint.dia.Element) && source.attr('metadata/name') === 'tap') { // jshint ignore:line
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: draggedView.model,
|
||||
},
|
||||
target: {
|
||||
cell: targetUnderMouse,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Find closest port
|
||||
var range = 30;
|
||||
var graph = flo.getGraph();
|
||||
var paper = flo.getPaper();
|
||||
var closestData;
|
||||
var minDistance = Number.MAX_VALUE;
|
||||
var maxIcomingLinks = draggedView.model.attr('metadata/constraints/maxIncomingLinksNumber');
|
||||
var maxOutgoingLinks = draggedView.model.attr('metadata/constraints/maxOutgoingLinksNumber');
|
||||
var hasIncomingPort = typeof maxIcomingLinks !== 'number' || maxIcomingLinks > 0;
|
||||
var hasOutgoingPort = typeof maxOutgoingLinks !== 'number' || maxOutgoingLinks > 0;
|
||||
if (!hasIncomingPort && !hasOutgoingPort) {
|
||||
return;
|
||||
}
|
||||
var elements = graph.findModelsInArea(joint.g.rect(point.x - range, point.y - range, 2 * range, 2 * range)); // jshint ignore:line
|
||||
if (Array.isArray(elements)) {
|
||||
elements.forEach(function(model) {
|
||||
var view = paper.findViewByModel(model);
|
||||
if (view && view !== draggedView && model instanceof joint.dia.Element) { // jshint ignore:line
|
||||
var targetMaxIcomingLinks = view.model.attr('metadata/constraints/maxIncomingLinksNumber');
|
||||
var targetMaxOutgoingLinks = view.model.attr('metadata/constraints/maxOutgoingLinksNumber');
|
||||
var targetHasIncomingPort = typeof targetMaxIcomingLinks !== 'number' || targetMaxIcomingLinks > 0;
|
||||
var targetHasOutgoingPort = typeof targetMaxOutgoingLinks !== 'number' || targetMaxOutgoingLinks > 0;
|
||||
if (view.model.attr('metadata/constraints/xorSourceSink')) {
|
||||
if (targetHasIncomingPort) {
|
||||
targetHasIncomingPort = targetHasIncomingPort && graph.getConnectedLinks(view.model, { outbound: true }).length === 0;
|
||||
}
|
||||
if (targetHasOutgoingPort) {
|
||||
targetHasOutgoingPort = targetHasOutgoingPort && graph.getConnectedLinks(view.model, { inbound: true }).length === 0;
|
||||
}
|
||||
}
|
||||
if (draggedView.model.attr('metadata/constraints/xorSourceSink')) {
|
||||
if (hasIncomingPort) {
|
||||
targetHasOutgoingPort = targetHasOutgoingPort && graph.getConnectedLinks(view.model, { outbound: true }).length === 0;
|
||||
}
|
||||
if (hasOutgoingPort) {
|
||||
targetHasIncomingPort = targetHasIncomingPort && graph.getConnectedLinks(view.model, { inbound: true }).length === 0;
|
||||
}
|
||||
}
|
||||
view.$('[magnet]').each(function(index, magnet) {
|
||||
var type = magnet.getAttribute('type');
|
||||
if ((type === 'input' && targetHasIncomingPort && hasOutgoingPort) || (type === 'output' && targetHasOutgoingPort && hasIncomingPort)) {
|
||||
var bbox = joint.V(magnet).bbox(false, paper.viewport); // jshint ignore:line
|
||||
var distance = point.distance({
|
||||
x: bbox.x + bbox.width / 2,
|
||||
y: bbox.y + bbox.height / 2
|
||||
});
|
||||
if (distance < range && distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestData = {
|
||||
context: context,
|
||||
source: {
|
||||
cell: draggedView.model,
|
||||
selector: type === 'output' ? '.input-port' : '.output-port'
|
||||
},
|
||||
target: {
|
||||
cell: model,
|
||||
selector: '.' + type+'-port'
|
||||
},
|
||||
range: minDistance
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (closestData) {
|
||||
return closestData;
|
||||
}
|
||||
|
||||
// Check if drop on a link is allowed
|
||||
if (targetUnderMouse instanceof joint.dia.Link && !(source.attr('metadata/constraints/xorSourceSink') || source.attr('metadata/constraints/maxOutgoingLinksNumber') === 0 || source.attr('metadata/constraints/maxIncomingLinksNumber') === 0) && graph.getConnectedLinks(source).length === 0) { // jshint ignore:line
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: source
|
||||
},
|
||||
target: {
|
||||
cell: targetUnderMouse
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
context: context,
|
||||
source: {
|
||||
cell: source
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function validateNode(flo, element) {
|
||||
var errors = [];
|
||||
var graph = flo.getGraph();
|
||||
var constraints = element.attr('metadata/constraints');
|
||||
if (constraints) {
|
||||
var incoming = graph.getConnectedLinks(element, {inbound: true});
|
||||
var outgoing = graph.getConnectedLinks(element, {outbound: true});
|
||||
if (typeof constraints.maxIncomingLinksNumber === 'number' || typeof constraints.minIncomingLinksNumber === 'number') {
|
||||
if (typeof constraints.maxIncomingLinksNumber === 'number' && constraints.maxIncomingLinksNumber < incoming.length) {
|
||||
if (constraints.maxIncomingLinksNumber === 0) {
|
||||
errors.push({
|
||||
message: 'Sources must appear at the start of a stream',
|
||||
range: element.attr('range')
|
||||
});
|
||||
} else {
|
||||
errors.push({
|
||||
message: 'Max allowed number of incoming links is ' + constraints.maxIncomingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.minIncomingLinksNumber === 'number' && constraints.minIncomingLinksNumber > incoming.length) {
|
||||
errors.push({
|
||||
message: 'Min allowed number of incoming links is ' + constraints.minIncomingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.maxOutgoingLinksNumber === 'number' || typeof constraints.minOutgoingLinksNumber === 'number') {
|
||||
if (typeof constraints.maxOutgoingLinksNumber === 'number' && constraints.maxOutgoingLinksNumber < outgoing.length) {
|
||||
if (constraints.maxOutgoingLinksNumber === 0) {
|
||||
errors.push({
|
||||
message: 'Sinks must appear at the end of a stream',
|
||||
range: element.attr('range')
|
||||
});
|
||||
} else {
|
||||
errors.push({
|
||||
message: 'Max allowed number of outgoing links is ' + constraints.maxOutgoingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof constraints.minOutgoingLinksNumber === 'number' && constraints.minOutgoingLinksNumber > outgoing.length) {
|
||||
errors.push({
|
||||
message: 'Min allowed number of outgoing links is ' + constraints.minOutgoingLinksNumber,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (constraints.xorSourceSink && incoming.length && outgoing.length) {
|
||||
errors.push({
|
||||
message: 'Node can either have incoming or outgoing links, but not both',
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!element.attr('metadata') || element.attr('metadata/unresolved')) {
|
||||
var msg = 'Unknown element \'' + element.attr('metadata/name') + '\'';
|
||||
if (element.attr('metadata/group')) {
|
||||
msg += ' from group \'' + element.attr('metadata/group') + '\'.';
|
||||
}
|
||||
errors.push({
|
||||
message: msg,
|
||||
range: element.attr('range')
|
||||
});
|
||||
}
|
||||
|
||||
// If possible, verify the properties specified match those allowed on this type of element
|
||||
// propertiesRanges are the ranges for each property included the entire '--name=value'.
|
||||
// The format of a range is {'start':{'ch':NNNN,'line':NNNN},'end':{'ch':NNNN,'line':NNNN}}
|
||||
var propertiesRanges = element.attr('propertiesranges');
|
||||
if (propertiesRanges) {
|
||||
var moduleSchema = element.attr('metadata');
|
||||
// Grab the list of supported properties for this module type
|
||||
moduleSchema.get('properties').then(function(moduleSchemaProperties) {
|
||||
if (!moduleSchemaProperties) {
|
||||
moduleSchemaProperties = {};
|
||||
}
|
||||
// Example moduleSchemaProperties:
|
||||
// {"host":{"name":"host","type":"String","description":"the hostname of the mail server","defaultValue":"localhost","hidden":false},
|
||||
// "password":{"name":"password","type":"String","description":"the password to use to connect to the mail server ","defaultValue":null,"hidden":false}
|
||||
var specifiedProperties = element.attr('props');
|
||||
Object.keys(specifiedProperties).forEach(function(propertyName) {
|
||||
if (!moduleSchemaProperties[propertyName]) {
|
||||
// The schema does not mention that property
|
||||
var propertyRange = propertiesRanges[propertyName];
|
||||
if (propertyRange) {
|
||||
errors.push({
|
||||
message: 'unrecognized option \''+propertyName+'\' for module \''+element.attr('metadata/name')+'\'',
|
||||
range: propertyRange
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
function moveNodeOnNode(flo, node, pivotNode, side, shouldRepairDamage) {
|
||||
side = side || 'left';
|
||||
if (canSwap(flo, node, pivotNode, side)) {
|
||||
var link;
|
||||
var i;
|
||||
if (side === 'left') {
|
||||
var sources = [];
|
||||
if (shouldRepairDamage) {
|
||||
/*
|
||||
* Commented out because it doesn't prevent cycles.
|
||||
*/
|
||||
// if (graph.getConnectedLinks(pivotNode, {inbound: true}).length > 0 || graph.getConnectedLinks(node, {outbound: true}).length > 0) {
|
||||
repairDamage(flo, node);
|
||||
// }
|
||||
}
|
||||
var pivotTargetLinks = flo.getGraph().getConnectedLinks(pivotNode, {inbound: true});
|
||||
for (i = 0; i < pivotTargetLinks.length; i++) {
|
||||
link = pivotTargetLinks[i];
|
||||
sources.push(link.get('source').id);
|
||||
link.remove();
|
||||
}
|
||||
for (i = 0; i < sources.length; i++) {
|
||||
flo.createLink({
|
||||
'id': sources[i],
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': pivotNode.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
} else if (side === 'right') {
|
||||
var targets = [];
|
||||
if (shouldRepairDamage) {
|
||||
/*
|
||||
* Commented out because it doesn't prevent cycles.
|
||||
*/
|
||||
// if (graph.getConnectedLinks(pivotNode, {outbound: true}).length > 0 || graph.getConnectedLinks(node, {inbound: true}).length > 0) {
|
||||
repairDamage(flo, node);
|
||||
// }
|
||||
}
|
||||
var pivotSourceLinks = flo.getGraph().getConnectedLinks(pivotNode, {outbound: true});
|
||||
for (i = 0; i < pivotSourceLinks.length; i++) {
|
||||
link = pivotSourceLinks[i];
|
||||
targets.push(link.get('target').id);
|
||||
link.remove();
|
||||
}
|
||||
for (i = 0; i < targets.length; i++) {
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': targets[i],
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
flo.createLink({
|
||||
'id': pivotNode.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveNodeOnLink(flo, node, link, shouldRepairDamage) {
|
||||
var source = link.get('source').id;
|
||||
var target = link.get('target').id;
|
||||
|
||||
if (shouldRepairDamage) {
|
||||
repairDamage(flo, node);
|
||||
}
|
||||
link.remove();
|
||||
|
||||
if (source) {
|
||||
flo.createLink({
|
||||
'id': source,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': node.id,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
if (target) {
|
||||
flo.createLink({
|
||||
'id': node.id,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': target,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function repairDamage(flo, node) {
|
||||
/*
|
||||
* remove incoming, outgoing links and cache their sources and targets not equal to current node
|
||||
*/
|
||||
var sources = [];
|
||||
var targets = [];
|
||||
var i = 0;
|
||||
var links = flo.getGraph().getConnectedLinks(node);
|
||||
for (i = 0; i < links.length; i++) {
|
||||
var targetId = links[i].get('target').id;
|
||||
var sourceId = links[i].get('source').id;
|
||||
if (targetId === node.id) {
|
||||
links[i].remove();
|
||||
sources.push(sourceId);
|
||||
} else if (sourceId === node.id) {
|
||||
links[i].remove();
|
||||
targets.push(targetId);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* best attempt to connect source and targets bypassing the node
|
||||
*/
|
||||
if (sources.length === 1) {
|
||||
var source = sources[0];
|
||||
for (i = 0; i < targets.length; i++) {
|
||||
flo.createLink({
|
||||
'id': source,
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': targets[i],
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
} else if (targets.length === 1) {
|
||||
var target = targets[0];
|
||||
for (i = 0; i < sources.length; i++) {
|
||||
flo.createLink({
|
||||
'id': sources[i],
|
||||
'selector': '.output-port'
|
||||
}, {
|
||||
'id': target,
|
||||
'selector': '.input-port'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if node being dropped and drop target node next to each other such that they won't be swapped by the drop
|
||||
*/
|
||||
function canSwap(flo, dropee, target, side) {
|
||||
var i, targetId, sourceId, noSwap = (dropee.id === target.id);
|
||||
if (dropee === target) {
|
||||
$log.debug('What!??? Dragged == Dropped!!! id = ' + target);
|
||||
}
|
||||
var links = flo.getGraph().getConnectedLinks(dropee);
|
||||
for (i = 0; i < links.length && !noSwap; i++) {
|
||||
targetId = links[i].get('target').id;
|
||||
sourceId = links[i].get('source').id;
|
||||
noSwap = (side === 'left' && targetId === target.id && sourceId === dropee.id) || (side === 'right' && targetId === dropee.id && sourceId === target.id);
|
||||
}
|
||||
return !noSwap;
|
||||
}
|
||||
|
||||
return {
|
||||
'createHandles': createHandles,
|
||||
'validatePort': validatePort,
|
||||
'validateLink': validateLink,
|
||||
'calculateDragDescriptor': calculateDragDescriptor,
|
||||
'handleNodeDropping': handleNodeDropping,
|
||||
'validateNode': validateNode,
|
||||
'preDelete': preDelete,
|
||||
'interactive': {
|
||||
'vertexAdd': false
|
||||
},
|
||||
'allowLinkVertexEdit': false
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
104
samples/spring-flo-si/src/main/resources/static/js/flosi-app.js
Normal file
104
samples/spring-flo-si/src/main/resources/static/js/flosi-app.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
var angular = require('angular');
|
||||
require('flo');
|
||||
var app = angular.module('floSiApp', [ 'spring.flo' ]);
|
||||
app.factory('SampleMetamodelService', require('metamodel-service'));
|
||||
app.factory('SampleRenderService', require('render-service'));
|
||||
app.factory('SampleEditorService', require('editor-service'));
|
||||
|
||||
app.controller('SiController', ['$scope', '$http', 'SampleMetamodelService', function($scope, $http, metamodelService) {
|
||||
$scope.endpoint = 'http://localhost:8080/integration';
|
||||
$scope.labelpath = "stats.sendcount";
|
||||
$scope.refreshrate=0;
|
||||
|
||||
var refreshTimer;
|
||||
|
||||
$scope.load = function(endpoint) {
|
||||
console.log("Loading from endpoint: '"+endpoint+"'");
|
||||
$scope.endpoint = endpoint;
|
||||
// Load the graph from the endpoint
|
||||
$http.get(endpoint, { }).success(function(json) {
|
||||
// console.log("JSON is "+json);
|
||||
// console.log("it is "+$('#endpoint').val());
|
||||
// $('#flow-definition').val('foo');
|
||||
$scope.definition.text = JSON.stringify(json);
|
||||
$scope.flo.updateGraphRepresentation();
|
||||
}).error(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateRefreshRate = function(newRefreshRate) {
|
||||
console.log("Update refresh rate: '"+newRefreshRate+"'");
|
||||
$scope.refreshrate=newRefreshRate;
|
||||
if (refreshTimer) {
|
||||
clearTimeout(refreshTimer);
|
||||
}
|
||||
if (newRefreshRate >0) {
|
||||
if (newRefreshRate < 250) {
|
||||
$scope.refreshrate = 250;
|
||||
}
|
||||
var refresher = function() {
|
||||
refresh();
|
||||
refreshTimer = setTimeout(function() { refresher() }, $scope.refreshrate);
|
||||
}
|
||||
refreshTimer = setTimeout(refresher, $scope.refreshrate);
|
||||
} else {
|
||||
$scope.refreshrate=0;
|
||||
}
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
$http.get($scope.endpoint, { }).success(function(json) {
|
||||
metamodelService.updateGraphLabels($scope.flo, JSON.stringify(json), $scope.labelpath);
|
||||
}).error(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.updateLabelPath = function(newLabelPath) {
|
||||
console.log("Update label path: '"+newLabelPath+"'");
|
||||
$scope.labelpath = newLabelPath;
|
||||
// Update the graph from the endpoint
|
||||
$http.get($scope.endpoint, { }).success(function(json) {
|
||||
metamodelService.updateGraphLabels($scope.flo, JSON.stringify(json), newLabelPath);
|
||||
}).error(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
}]).directive('ngEnter', function() {
|
||||
return function(scope, element, attrs) {
|
||||
element.bind("keydown keypress", function(event) {
|
||||
if(event.which === 13) {
|
||||
scope.$apply(function(){
|
||||
scope.$eval(attrs.ngEnter);
|
||||
});
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
return app;
|
||||
});
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a graph to a text representation.
|
||||
*
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
// Graph
|
||||
var g;
|
||||
|
||||
// Number of Links left to visit
|
||||
var numberOfLinksToVisit;
|
||||
|
||||
// Number of nodes left to visit
|
||||
var numberOfNodesToVisit;
|
||||
|
||||
// Map of links left to visit indexed by id
|
||||
var linksToVisit;
|
||||
|
||||
// Map of nodes left to visit indexed by id
|
||||
var nodesToVisit;
|
||||
|
||||
// Map of nodes incoming non-visited links degrees index by node id
|
||||
var nodesInDegrees;
|
||||
|
||||
// Priority:
|
||||
// 1. find links whose source has no other links pointing at it
|
||||
// 2. find links whose source has already been processed (not currently needed in sample DSL since
|
||||
// can't create graphs like that due to metamodel constraints)
|
||||
// 3. find remaining links
|
||||
function nextLink() {
|
||||
var indegree = Number.MAX_INT;
|
||||
var currentBest;
|
||||
for (var id in linksToVisit) {
|
||||
var link = g.getCell(id);
|
||||
var source = g.getCell(link.get('source').id);
|
||||
var currentInDegree = nodesInDegrees[source.get('id')];
|
||||
if (currentInDegree === 0) {
|
||||
return visit(link);
|
||||
} else if (indegree > currentInDegree) {
|
||||
indegree = currentInDegree;
|
||||
currentBest = link;
|
||||
}
|
||||
}
|
||||
if (currentBest) {
|
||||
return visit(currentBest);
|
||||
}
|
||||
}
|
||||
|
||||
function visit(e) {
|
||||
if (e.isLink()) {
|
||||
delete linksToVisit[e.get('id')];
|
||||
nodesInDegrees[e.get('target').id]--;
|
||||
numberOfLinksToVisit--;
|
||||
} else {
|
||||
delete nodesToVisit[e.get('id')];
|
||||
numberOfNodesToVisit--;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
function init(graph) {
|
||||
numberOfLinksToVisit = 0;
|
||||
numberOfNodesToVisit = 0;
|
||||
linksToVisit = {};
|
||||
nodesToVisit = {};
|
||||
nodesInDegrees = {};
|
||||
g = graph;
|
||||
g.getElements().forEach(function(element) {
|
||||
if (element.attr('metadata/name')) { // is it a node?
|
||||
nodesToVisit[element.get('id')] = element;
|
||||
var indegree = 0;
|
||||
g.getConnectedLinks(element, {inbound: true}).forEach(function(link) {
|
||||
if (link.get('source') && link.get('source').id && g.getCell(link.get('source').id) &&
|
||||
g.getCell(link.get('source').id).attr('metadata/name')) {
|
||||
linksToVisit[link.get('id')] = link;
|
||||
numberOfLinksToVisit++;
|
||||
indegree++;
|
||||
}
|
||||
});
|
||||
nodesInDegrees[element.get('id')] = indegree;
|
||||
numberOfNodesToVisit++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts at a link and proceeds down a chain. Converts each node to
|
||||
* text and then joins them with a ' > '.
|
||||
*/
|
||||
function chainToText(link) {
|
||||
var text = '';
|
||||
var source = g.getCell(link.get('source').id);
|
||||
text += nodeToText(source, true);
|
||||
while (link) {
|
||||
var target = g.getCell(link.get('target').id);
|
||||
text += ' > ';
|
||||
text += nodeToText(target, false);
|
||||
|
||||
// Find next not visited link to follow
|
||||
link = null;
|
||||
var outgoingLinks = g.getConnectedLinks(target, {outbound: true});
|
||||
for (var i = 0; i < outgoingLinks.length && !link; i++) {
|
||||
if (linksToVisit[outgoingLinks[i].get('id')]) {
|
||||
source = target;
|
||||
link = visit(outgoingLinks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Very basic format. From a node to the text:
|
||||
* "name --key=value --key=value"
|
||||
*/
|
||||
function nodeToText(element) {
|
||||
var text = '';
|
||||
var props = element.attr('props');
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
text += element.attr('metadata/name');
|
||||
if (props) {
|
||||
Object.keys(props).forEach(function(propertyName) {
|
||||
text += ' --' + propertyName + '=' + props[propertyName];
|
||||
});
|
||||
}
|
||||
visit(element);
|
||||
return text;
|
||||
}
|
||||
|
||||
function appendChainText(text, chainText) {
|
||||
if (chainText) {
|
||||
if (text) {
|
||||
text += '\n';
|
||||
}
|
||||
text += chainText;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// Translate a graph into a basic string
|
||||
return function(g) {
|
||||
var text = '';
|
||||
var chainText;
|
||||
var id;
|
||||
init(g);
|
||||
while (numberOfLinksToVisit) {
|
||||
chainText = chainToText(nextLink());
|
||||
text = appendChainText(text, chainText);
|
||||
}
|
||||
// Visit all disconnected nodes
|
||||
for (id in nodesToVisit) {
|
||||
chainText = nodeToText(nodesToVisit[id], true);
|
||||
text = appendChainText(text, chainText);
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
});
|
||||
83
samples/spring-flo-si/src/main/resources/static/js/main.js
Executable file
83
samples/spring-flo-si/src/main/resources/static/js/main.js
Executable file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
requirejs.config({
|
||||
baseUrl:'js',
|
||||
paths: {
|
||||
joint: '/webjars/jointjs/dist/joint',
|
||||
backbone: '/webjars/backbone/backbone',
|
||||
domReady: '/webjars/requirejs-domready/domReady',
|
||||
angular: '/webjars/angular/angular',
|
||||
jquery: '/webjars/jquery/dist/jquery',
|
||||
bootstrap:'/webjars/bootstrap/bootstrap',
|
||||
lodash: '/webjars/lodash/lodash', // lodash.compat
|
||||
dagre: '/webjars/dagre/dist/dagre.core',
|
||||
graphlib: '/webjars/graphlib/graphlib.core',
|
||||
text : '/webjars/requirejs-text/text',
|
||||
flo : '/webjars/spring-flo/dist/spring-flo',
|
||||
json5 : '/webjars/json5/json5'
|
||||
},
|
||||
map: {
|
||||
'*': {
|
||||
// Backbone requires underscore. This forces requireJS to load lodash instead:
|
||||
'underscore': 'lodash'
|
||||
}
|
||||
},
|
||||
packages: [
|
||||
{
|
||||
name: 'codemirror',
|
||||
location: '../lib/codemirror',
|
||||
main: 'lib/codemirror'
|
||||
}
|
||||
],
|
||||
shim: {
|
||||
angular: {
|
||||
deps: ['bootstrap'],
|
||||
exports: 'angular'
|
||||
},
|
||||
bootstrap: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
graphlib: {
|
||||
deps: ['underscore']
|
||||
},
|
||||
dagre: {
|
||||
deps: ['graphlib', 'underscore']
|
||||
},
|
||||
joint: {
|
||||
deps: ['jquery', 'underscore', 'backbone'],
|
||||
},
|
||||
underscore: {
|
||||
exports: '_'
|
||||
},
|
||||
'flo': {
|
||||
deps: ['angular', 'jquery', 'joint', 'underscore']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
define(['require','angular'], function (require, angular) {
|
||||
'use strict';
|
||||
require(['domReady!', 'flosi-app'],
|
||||
function (document) {
|
||||
angular.bootstrap(document, ['floSiApp']);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
[
|
||||
{
|
||||
'name':'general', 'group':'general', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'service-activator', 'group':'messaging-endpoints', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'message-handler', 'group':'messaging-endpoints', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'gateway', 'group':'messaging-endpoints', 'description':'Gateway',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'splitter', 'group':'routing', 'description':'Splitter',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'router', 'group':'routing', 'description':'Router',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'transformer', 'group':'transformation', 'description':'Transformer',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'bridge', 'group':'routing', 'description':'Bridge',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'channel', 'group':'connectors', 'description':'Channel',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'publish-subscribe-channel', 'group':'connectors', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'test-producer', 'group':'producers', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'BareHandler', 'group':'producers', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
},
|
||||
{
|
||||
'name':'filter', 'group':'routing', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'aggregator', 'group':'routing', 'description':'Produce an output message after correlating some set of incoming messages',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'logging-channel-adapter', 'group':'connectors', 'description':'?',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'chain', 'group':'messaging-endpoints', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'inbound-channel-adapter', 'group':'messaging-endpoints', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
,
|
||||
{
|
||||
'name':'outbound-channel-adapter', 'group':'messaging-endpoints', 'description':'',
|
||||
'constraints':{ 'maxIncomingLinksNumber':10, 'maxOutgoingLinksNumber':10 }
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
require('json5');
|
||||
|
||||
var convertGraphToText = require('graph-to-text');
|
||||
var convertTextToGraph = require('text-to-graph');
|
||||
var updateGraph = require('update-graph');
|
||||
|
||||
return ['$http', '$q', '$timeout', '$log', 'MetamodelUtils', function($http, $q, $timeout, $log, metamodelUtils) {
|
||||
|
||||
var metamodel;
|
||||
|
||||
// Internally stored metamodel load promise
|
||||
var request;
|
||||
|
||||
var statsProperties = [
|
||||
{'name':'name','default':'?', 'description':'name'},
|
||||
{'name':'id','default':'?', 'description':'node id'},
|
||||
{'name':'componentType','default':'','description':'Detailed component type'},
|
||||
{'name':'stats.loggingEnabled', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.statsEnabled', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.countsEnabled', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.count', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.min', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.max', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.mean', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.standardDeviation', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendRate.countLong', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.count', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.min', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.max', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.mean', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.standardDeviation', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.errorRate.countLong', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendCount', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendErrorCount', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.timeSinceLastSend', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.meanSendRate', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.meanErrorRate', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.meanErrorRatio', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.meanSendDuration', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.minSendDuration', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.maxSendDuration', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.standardDeviationSendDuration', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.count', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.min', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.max', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.mean', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.standardDeviation', 'default': '?', 'description':'?' },
|
||||
{'name':'stats.sendDuration.countLong', 'default': '?', 'description':'?' }];
|
||||
|
||||
/**
|
||||
* Helper that goes from basic JSON to a lazy getter structure. Useful when the
|
||||
* metamodel is 'cheap' to build. If it is costly to discover the actual properties
|
||||
* the getter may be more complex (e.g. make a REST request).
|
||||
*/
|
||||
function createMetadata(entry) {
|
||||
var props = {};
|
||||
if (!entry.properties) {
|
||||
// use the default stats properties
|
||||
entry.properties = JSON.parse(JSON.stringify(statsProperties));
|
||||
}
|
||||
if (Array.isArray(entry.properties)) {
|
||||
entry.properties.forEach(function(property) {
|
||||
if (!property.id) {
|
||||
property.id = property.name;
|
||||
}
|
||||
props[property.id] = property;
|
||||
});
|
||||
}
|
||||
entry.properties = props;
|
||||
return {
|
||||
name: entry.name,
|
||||
group: entry.group,
|
||||
icon: entry.icon,
|
||||
constraints: entry.constraints,
|
||||
description: entry.description,
|
||||
metadata: entry.metadata,
|
||||
properties: entry.properties,
|
||||
get: function(property) {
|
||||
var deferred = $q.defer();
|
||||
if (entry.hasOwnProperty(property)) {
|
||||
deferred.resolve(entry[property]);
|
||||
} else {
|
||||
deferred.reject();
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function load() {
|
||||
// COULDDO: to cache the result here, check result before doing this processing
|
||||
// and simply return it if it is set. If doing that may want to override refresh
|
||||
// in this service
|
||||
var metamodelData = JSON5.parse(require('text!metamodel-sample.json'));
|
||||
var deferred = $q.defer();
|
||||
var newData = {};
|
||||
metamodelData.forEach(function(data) {
|
||||
var metadata = createMetadata(data);
|
||||
if (!newData[metadata.group]) {
|
||||
newData[metadata.group] = {};
|
||||
}
|
||||
newData[metadata.group][metadata.name] = metadata;
|
||||
});
|
||||
metamodel = newData;
|
||||
deferred.resolve(metamodel);
|
||||
request = deferred.promise;
|
||||
return request;
|
||||
}
|
||||
|
||||
function graphToText(flo, definition) {
|
||||
definition.text = convertGraphToText(flo.getGraph());
|
||||
}
|
||||
|
||||
function updateGraphLabels(flo, text, labelpath) {
|
||||
updateGraph(text, flo.getGraph(), labelpath);
|
||||
}
|
||||
|
||||
function textToGraph(flo, definition) {
|
||||
// TODO perhaps push these flo operations into the 'caller' to make this simpler
|
||||
flo.getGraph().clear();
|
||||
load().then(function(metamodel) {
|
||||
convertTextToGraph(definition.text, flo, metamodel, metamodelUtils);
|
||||
updateGraph(definition.text,flo.getGraph(),'stats.sendcount');
|
||||
flo.performLayout();
|
||||
flo.fitToPage();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
'load': load,
|
||||
'textToGraph': textToGraph,
|
||||
'updateGraphLabels': updateGraphLabels,
|
||||
'graphToText': graphToText
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -0,0 +1,746 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var joint = require('joint');
|
||||
|
||||
var dagre = require('dagre');
|
||||
|
||||
var HANDLE_ICON_MAP = {
|
||||
'remove': 'icons/delete.svg',
|
||||
};
|
||||
|
||||
var DECORATION_ICON_MAP = {
|
||||
'error': 'icons/error.svg'
|
||||
};
|
||||
|
||||
var IMAGE_W = 120,
|
||||
IMAGE_H = 40;
|
||||
|
||||
var HORIZONTAL_PADDING = 10;
|
||||
|
||||
joint.shapes.si = {};
|
||||
|
||||
joint.routers.floforintegration = (function() {
|
||||
|
||||
// expands a box by specific value
|
||||
function expand(bbox, val) {
|
||||
return joint.g.rect(bbox).moveAndExpand({ x: -val, y: -val, width: 2 * val, height: 2 * val });
|
||||
}
|
||||
|
||||
function routeAround(exp, ref, anchor, opt) {
|
||||
|
||||
var anchorSide = exp.sideNearestToPoint(anchor);
|
||||
var expAnchor = exp.pointNearestToPoint(anchor);
|
||||
var line = joint.g.line(ref, anchor);
|
||||
var center = exp.center();
|
||||
var intersection;
|
||||
var pts = [];
|
||||
|
||||
if (anchorSide !== 'top') {
|
||||
intersection = line.intersection(joint.g.line(exp.origin(), exp.topRight()));
|
||||
if (intersection) {
|
||||
if (anchorSide === 'bottom') {
|
||||
if (intersection.x - exp.x + expAnchor.x - exp.x <= exp.width) {
|
||||
pts = [exp.origin(), exp.bottomLeft()];
|
||||
} else {
|
||||
pts = [exp.topRight(), exp.corner()];
|
||||
}
|
||||
} else {
|
||||
if (anchorSide === 'left') {
|
||||
pts = [exp.origin()];
|
||||
} else {
|
||||
pts = [exp.topRight()];
|
||||
}
|
||||
}
|
||||
if (opt.includeExtraPoints) {
|
||||
pts.push(expAnchor);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
}
|
||||
|
||||
if (anchorSide !== 'bottom') {
|
||||
intersection = line.intersection(joint.g.line(exp.corner(), exp.bottomLeft()));
|
||||
if (intersection) {
|
||||
if (anchorSide === 'top') {
|
||||
if (intersection.x - exp.x + expAnchor.x - exp.x <= exp.width) {
|
||||
pts = [exp.bottomLeft(), exp.origin()];
|
||||
} else {
|
||||
pts = [exp.corner(), exp.topRight()];
|
||||
}
|
||||
} else {
|
||||
if (anchorSide === 'left') {
|
||||
pts = [exp.bottomLeft()];
|
||||
} else {
|
||||
pts = [exp.corner()];
|
||||
}
|
||||
}
|
||||
if (opt.includeExtraPoints) {
|
||||
pts.push(expAnchor);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
}
|
||||
|
||||
if (anchorSide !== 'left') {
|
||||
intersection = line.intersection(joint.g.line(exp.origin(), exp.bottomLeft()));
|
||||
if (intersection) {
|
||||
if (anchorSide === 'right') {
|
||||
if (intersection.y - exp.y + expAnchor.y - exp.y <= exp.height) {
|
||||
pts = [exp.origin(), exp.topRight()];
|
||||
} else {
|
||||
pts = [exp.bottomLeft(), exp.corner()];
|
||||
}
|
||||
} else {
|
||||
if (anchorSide === 'top') {
|
||||
pts = [exp.origin()];
|
||||
} else {
|
||||
pts = [exp.bottomLeft()];
|
||||
}
|
||||
}
|
||||
if (opt.includeExtraPoints) {
|
||||
pts.push(expAnchor);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
}
|
||||
|
||||
if (anchorSide !== 'right') {
|
||||
intersection = line.intersection(joint.g.line(exp.topRight(), exp.corner()));
|
||||
if (intersection) {
|
||||
if (anchorSide === 'left') {
|
||||
if (intersection.y - exp.y + expAnchor.y - exp.y <= exp.height) {
|
||||
pts = [exp.topRight(), exp.origin()];
|
||||
} else {
|
||||
pts = [exp.corner(), exp.bottomLeft()];
|
||||
}
|
||||
} else {
|
||||
if (anchorSide === 'top') {
|
||||
pts = [exp.topRight()];
|
||||
} else {
|
||||
pts = [exp.corner()];
|
||||
}
|
||||
}
|
||||
if (opt.includeExtraPoints) {
|
||||
pts.push(expAnchor);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
||||
|
||||
function findRoute(vx, opt, linkView) {
|
||||
|
||||
var vertices = opt.metro ? joint.routers.metro(vx, opt, linkView) : vx;
|
||||
var sourceRoute = [], targetRoute = [];
|
||||
|
||||
var paper = linkView.paper;
|
||||
var reference = vertices.length ? vertices[0] : joint.g.rect(linkView.targetBBox).center();
|
||||
var sourceAnchorPt = paper.options.linkConnectionPoint(linkView, linkView.sourceView, linkView.sourceMagnet, reference);
|
||||
var targetAnchorPt = paper.options.linkConnectionPoint(linkView, linkView.targetView, linkView.targetMagnet, sourceAnchorPt);
|
||||
var padding = opt.elementPadding || 20;
|
||||
|
||||
if (linkView.sourceView) {
|
||||
var expSource = expand(linkView.sourceView.model.getBBox(), padding);
|
||||
while (vertices.length && expSource.containsPoint(vertices[0])) {
|
||||
vertices.splice(0, 1);
|
||||
}
|
||||
var sourceRef = vertices.length ? joint.g.point(vertices[0]) : targetAnchorPt;
|
||||
sourceRoute = routeAround(expSource, sourceRef, sourceAnchorPt, opt).reverse();
|
||||
}
|
||||
if (linkView.targetView) {
|
||||
var expTarget = expand(linkView.targetView.model.getBBox(), padding);
|
||||
while (vertices.length && expTarget.containsPoint(vertices[vertices.length - 1])) {
|
||||
vertices.splice(vertices.length - 1, 1);
|
||||
}
|
||||
var targetRef = vertices.length ? joint.g.point(vertices[vertices.length - 1]) : sourceAnchorPt;
|
||||
|
||||
targetRoute = routeAround(expTarget, targetRef, targetAnchorPt, opt);
|
||||
}
|
||||
|
||||
return sourceRoute.concat(vertices).concat(targetRoute);
|
||||
};
|
||||
|
||||
return findRoute;
|
||||
|
||||
})();
|
||||
|
||||
joint.shapes.si.Channel = joint.shapes.basic.Generic.extend({
|
||||
|
||||
markup:
|
||||
'<g class="shape">'+
|
||||
'<rect class="border"/>' +
|
||||
'<path class="the_shape" d="M 0 10 H 100 A 8 10 0 0 1 100 30 H 0"/>'+
|
||||
'<ellipse class="the_shape" cx="0" cy="20" rx="8" ry="10"/>'+
|
||||
'<text class="label"/>'+
|
||||
'<text class="label2"/>'+
|
||||
'</g>' +
|
||||
// '<text class="stream-label"/>'+
|
||||
'<rect class="input-port" />'+
|
||||
'<rect class="output-port"/>'+
|
||||
'<circle class="tap-port"/>',
|
||||
|
||||
defaults: joint.util.deepSupplement({
|
||||
type: 'channel',//joint.shapes.flo.NODE_TYPE,
|
||||
position: {x: 0, y: 0},
|
||||
size: { width: 100, height: 40 },
|
||||
attrs: {
|
||||
'.': {
|
||||
magnet: false,
|
||||
},
|
||||
'.the_shape': {
|
||||
'stroke':'#000000'
|
||||
},
|
||||
// rounded edges around image
|
||||
// '.border': {
|
||||
// width: IMAGE_W,
|
||||
// height: IMAGE_H,
|
||||
// rx: 2,
|
||||
// ry: 2,
|
||||
// 'fill-opacity':0, // see through
|
||||
// stroke: '#eeeeee',
|
||||
// 'stroke-width': 0,
|
||||
// },
|
||||
// '.box': {
|
||||
// width: IMAGE_W,
|
||||
// height: IMAGE_H,
|
||||
// rx: 2,
|
||||
// ry: 2,
|
||||
// //'fill-opacity':0, // see through
|
||||
// stroke: '#6db33f',
|
||||
// fill: '#eeeeee',
|
||||
// 'stroke-width': 2,
|
||||
// },
|
||||
'.input-port': {
|
||||
type: 'input',
|
||||
port: 'input',
|
||||
r:4,
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + -2 + ',' + ((20/2)-2+10) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
'.output-port': {
|
||||
type: 'output',
|
||||
port: 'output',
|
||||
r:4,
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + (100+8-2) + ',' + ((20/2)-2+10) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
// '.tap-port': {
|
||||
// type: 'output',
|
||||
// port: 'tap',
|
||||
// r: 4,
|
||||
// magnet: true,
|
||||
// fill: '#eeeeee',
|
||||
// 'ref-x': 0.5,
|
||||
// 'ref-y': 0.99999999,
|
||||
// ref: '.border',
|
||||
// stroke: '#34302D'
|
||||
// },
|
||||
'.label': {
|
||||
'ref-x': 0.5, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.525,
|
||||
'y-alignment': 'middle',
|
||||
'x-alignment' : 'middle',
|
||||
ref: '.the_shape', // jointjs specific: element for ref-x, ref-y
|
||||
fill: 'black',
|
||||
'stroke': 'black',
|
||||
'font-size': '12px',
|
||||
'font-family': 'Ubuntu Mono',
|
||||
'color': 'black'
|
||||
},
|
||||
'.label2': {
|
||||
'y-alignment': 'middle',
|
||||
'ref-x': HORIZONTAL_PADDING+2, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.55, // jointjs specific: relative position to ref'd element
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
fill: 'black',
|
||||
'font-size': 20
|
||||
},
|
||||
// '.stream-label': {
|
||||
// 'x-alignment': 'middle',
|
||||
// 'y-alignment': -0.999999,
|
||||
// 'ref-x': 0.5, // jointjs specific: relative position to ref'd element
|
||||
// 'ref-y': 0, // jointjs specific: relative position to ref'd element
|
||||
// ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
// fill: '#AAAAAA',
|
||||
// 'font-size': 15
|
||||
// },
|
||||
// '.shape': {
|
||||
// }
|
||||
}
|
||||
}, joint.shapes.basic.Generic.prototype.defaults)
|
||||
});
|
||||
|
||||
|
||||
joint.shapes.si.Node = joint.shapes.basic.Generic.extend({
|
||||
markup:
|
||||
'<g class="shape"><image class="image" /></g>'+
|
||||
'<rect class="border-white"/>' +
|
||||
'<rect class="border"/>' +
|
||||
'<rect class="box"/>'+
|
||||
'<text class="label"/>'+
|
||||
'<text class="label2"></text>'+
|
||||
'<rect class="input-port" />'+
|
||||
'<rect class="error-port" />'+
|
||||
'<rect class="output-port"/>'+
|
||||
'<rect class="output-port-cover"/>',
|
||||
|
||||
defaults: joint.util.deepSupplement({
|
||||
|
||||
type: 'node',//joint.shapes.flo.NODE_TYPE,
|
||||
position: {x: 0, y: 0},
|
||||
size: { width: IMAGE_W, height: IMAGE_H },
|
||||
attrs: {
|
||||
'.': { magnet: false },
|
||||
// rounded edges around image
|
||||
'.border': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
'fill-opacity':0, // see through
|
||||
stroke: '#eeeeee',
|
||||
'stroke-width': 0
|
||||
},
|
||||
|
||||
'.box': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
//'fill-opacity':0, // see through
|
||||
stroke: '#6db33f',
|
||||
fill: '#eeeeee',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.input-port': {
|
||||
type: 'input',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + -2 + ',' + ((IMAGE_H/2)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.output-port': {
|
||||
type: 'output',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + (IMAGE_W-2) + ',' + ((IMAGE_H/2)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.error-port': {
|
||||
type: 'output',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#ff0000',
|
||||
transform: 'translate(' + (IMAGE_W/2-2) + ',' + ((IMAGE_H)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.label': {
|
||||
'text-anchor': 'middle',
|
||||
'ref-x': 0.5, // jointjs specific: relative position to ref'd element
|
||||
// 'ref-y': -12, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.3,
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
fill: 'black',
|
||||
'font-size': 14
|
||||
},
|
||||
'.label2': {
|
||||
'text': '\u21d2',
|
||||
'text-anchor': 'middle',
|
||||
'ref-x': 0.15, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.15, // jointjs specific: relative position to ref'd element
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
transform: 'translate(' + (IMAGE_W/2) + ',' + (IMAGE_H/2) + ')',
|
||||
fill: 'black',
|
||||
'font-size': 24
|
||||
},
|
||||
'.shape': {
|
||||
},
|
||||
'.image': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H
|
||||
}
|
||||
}
|
||||
}, joint.shapes.basic.Generic.prototype.defaults)
|
||||
});
|
||||
|
||||
joint.shapes.si.ServiceActivator = joint.shapes.basic.Generic.extend({
|
||||
markup:
|
||||
'<g class="shape"><image class="image" /></g>'+
|
||||
'<rect class="border-white"/>' +
|
||||
'<rect class="border"/>' +
|
||||
'<rect class="box"/>'+
|
||||
'<text class="label"/>'+
|
||||
'<text class="label2"></text>'+
|
||||
'<rect class="input-port" />'+
|
||||
'<rect class="error-port" />'+
|
||||
'<rect class="output-port"/>'+
|
||||
'<rect class="output-port-cover"/>'+
|
||||
'<g transform="scale(0.4)">'+
|
||||
'<path class="blockSolid" d="M 43 20 l 6 6 l 6 -6 l -6 -6 l -6 6"/>'+
|
||||
'<path class="blockEmpty" d="M 86 20 l 6 6 l 6 -6 l -6 -6 l -6 6"/>'+
|
||||
'<path class="arrowGray" d="M 0 20 h 33 l 0 -5 l 7 5 l -7 5 l 0 -5"/>'+
|
||||
'<path class="arrowBlack" d="M 52 20 h 26 l 0 -5 l 7 5 l -7 5 l 0 -5"/>'+
|
||||
'</g>'
|
||||
,
|
||||
|
||||
|
||||
defaults: joint.util.deepSupplement({
|
||||
|
||||
type: 'node',//joint.shapes.flo.NODE_TYPE,
|
||||
position: {x: 0, y: 0},
|
||||
size: { width: IMAGE_W, height: IMAGE_H },
|
||||
attrs: {
|
||||
'.': { magnet: false },
|
||||
// rounded edges around image
|
||||
'.border': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
'fill-opacity':0, // see through
|
||||
stroke: '#eeeeee',
|
||||
'stroke-width': 0
|
||||
},
|
||||
'.arrowGray': {
|
||||
'stroke':'#aaaaaa',
|
||||
'stroke-width':'2',
|
||||
'fill': '#aaaaaa'
|
||||
},
|
||||
'.arrowBlack': {
|
||||
'stroke':'black',
|
||||
'stroke-width':'2',
|
||||
'fill': 'black'
|
||||
},
|
||||
'.blockSolid': {
|
||||
'stroke':'#000000',
|
||||
'stroke-width':'2',
|
||||
'fill': 'black'
|
||||
},
|
||||
'.blockEmpty': {
|
||||
'stroke':'#000000',
|
||||
'stroke-width':'2',
|
||||
'fill': 'white'
|
||||
},
|
||||
'.box': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H,
|
||||
rx: 3,
|
||||
ry: 3,
|
||||
//'fill-opacity':0, // see through
|
||||
stroke: '#6db33f',
|
||||
fill: '#eeeeee',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.input-port': {
|
||||
type: 'input',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + -2 + ',' + ((IMAGE_H/2)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.output-port': {
|
||||
type: 'output',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#eeeeee',
|
||||
transform: 'translate(' + (IMAGE_W-2) + ',' + ((IMAGE_H/2)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.error-port': {
|
||||
type: 'output',
|
||||
height: 4, width: 4,
|
||||
magnet: true,
|
||||
fill: '#ff0000',
|
||||
transform: 'translate(' + (IMAGE_W/2-2) + ',' + ((IMAGE_H)-2) + ')',
|
||||
stroke: '#34302d',
|
||||
'stroke-width': 1
|
||||
},
|
||||
'.label': {
|
||||
'text-anchor': 'middle',
|
||||
'ref-x': 0.5, // jointjs specific: relative position to ref'd element
|
||||
// 'ref-y': -12, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.3,
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
fill: 'black',
|
||||
'font-size': 14
|
||||
},
|
||||
'.label2': {
|
||||
'text': '\u21d2',
|
||||
'text-anchor': 'middle',
|
||||
'ref-x': 0.15, // jointjs specific: relative position to ref'd element
|
||||
'ref-y': 0.15, // jointjs specific: relative position to ref'd element
|
||||
ref: '.border', // jointjs specific: element for ref-x, ref-y
|
||||
transform: 'translate(' + (IMAGE_W/2) + ',' + (IMAGE_H/2) + ')',
|
||||
fill: 'black',
|
||||
'font-size': 24
|
||||
},
|
||||
'.shape': {
|
||||
},
|
||||
'.image': {
|
||||
width: IMAGE_W,
|
||||
height: IMAGE_H
|
||||
}
|
||||
}
|
||||
}, joint.shapes.basic.Generic.prototype.defaults)
|
||||
});
|
||||
|
||||
|
||||
return ['$log', function($log) {
|
||||
|
||||
function createHandle(kind) {
|
||||
return new joint.shapes.flo.ErrorDecoration({
|
||||
size: {width: 10, height: 10},
|
||||
attrs: {
|
||||
'image': {
|
||||
'xlink:href': HANDLE_ICON_MAP[kind]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createDecoration(kind) {
|
||||
return new joint.shapes.flo.ErrorDecoration({
|
||||
size: {width: 16, height: 16},
|
||||
attrs: {
|
||||
'image': {
|
||||
'xlink:href': DECORATION_ICON_MAP[kind]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createNode(metadata, props) {
|
||||
if (metadata.name === 'channel' || metadata.name === 'publish-subscribe-channel') {
|
||||
return new joint.shapes.si.Channel();
|
||||
} else if (metadata.name === 'service-activator') {
|
||||
return new joint.shapes.si.ServiceActivator();
|
||||
} else {
|
||||
return new joint.shapes.si.Node();
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNewNode(node, context) {
|
||||
var metadata = node.attr('metadata');
|
||||
if (metadata) {
|
||||
node.attr('.label/text', node.attr('metadata/name'));
|
||||
if (node.attr('metadata/constraints/maxIncomingLinksNumber') === 0) {
|
||||
node.attr('.input-port/display','none');
|
||||
}
|
||||
if (node.attr('metadata/constraints/maxOutgoingLinksNumber') === 0) {
|
||||
node.attr('.output-port/display','none');
|
||||
}
|
||||
|
||||
var type = node.attr('metadata/name');
|
||||
if (type === 'tap') {
|
||||
if (!node.attr('props/channel')) {
|
||||
node.attr('props/channel', 'tap:stream:STREAM');
|
||||
}
|
||||
refreshVisuals(node, 'props/channel', context.paper);
|
||||
} else if (type === 'named-channel') {
|
||||
// Default channel for named channel is 'queue:default'
|
||||
if (!node.attr('props/channel')) {
|
||||
node.attr('props/channel', 'queue:default');
|
||||
}
|
||||
refreshVisuals(node, 'props/channel', context.paper);
|
||||
}
|
||||
}
|
||||
node.attr('.label2/text','');
|
||||
|
||||
}
|
||||
|
||||
function validateNode(flo, node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
function fitLabel(paper, node, labelPath) {
|
||||
var label = node.attr(labelPath);
|
||||
if (label && label.length<9) {
|
||||
return;
|
||||
}
|
||||
var view = paper.findViewByModel(node);
|
||||
if (view && label) {
|
||||
var textView = view.findBySelector(labelPath.substr(0, labelPath.indexOf('/')))[0];
|
||||
var offset = 0;
|
||||
if (node.attr('.label2/text')) {
|
||||
var label2View = view.findBySelector('.label2')[0];
|
||||
if (label2View) {
|
||||
var box = joint.V(label2View).bbox(false, paper.viewport);
|
||||
offset = HORIZONTAL_PADDING + box.width;
|
||||
}
|
||||
}
|
||||
var width = joint.V(textView).bbox(false, paper.viewport).width;
|
||||
var threshold = IMAGE_W - HORIZONTAL_PADDING - HORIZONTAL_PADDING - offset;
|
||||
if (offset) {
|
||||
node.attr('.label1/ref-x', Math.max((offset + HORIZONTAL_PADDING + width / 2) / IMAGE_W, 0.5), { silent: true });
|
||||
}
|
||||
// Trim package prefix
|
||||
|
||||
if (!label.endsWith('?')) {
|
||||
// console.log("modifying label "+label);
|
||||
// Sample name: com.foo.method(a.b.c.Order)
|
||||
var openParen = label.indexOf('(');
|
||||
if (openParen !== -1) {
|
||||
label = label.substring(0,openParen);
|
||||
}
|
||||
width = joint.V(textView).bbox(false, paper.viewport).width;
|
||||
if (width > threshold) {
|
||||
var lastDot = label.lastIndexOf('.');
|
||||
if (lastDot !== -1) {
|
||||
label = label.substring(lastDot+1);
|
||||
}
|
||||
console.log('driving label change');
|
||||
node.attr(labelPath, label, { silent: true });
|
||||
view.update();
|
||||
width = joint.V(textView).bbox(false, paper.viewport).width;
|
||||
for (var i = 1; i < label.length && width > threshold; i++) {
|
||||
node.attr(labelPath, label.substr(0, label.length - i) + '\u2026', { silent: true });
|
||||
view.update();
|
||||
width = joint.V(textView).bbox(false, paper.viewport).width;
|
||||
if (offset) {
|
||||
node.attr('.label1/ref-x', Math.max((offset + HORIZONTAL_PADDING + width / 2) / IMAGE_W, 0.5), { silent: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// view.update();
|
||||
}
|
||||
}
|
||||
|
||||
function createLink() {
|
||||
var link = new joint.shapes.flo.Link(joint.util.deepSupplement({
|
||||
router: { name: 'floforintegration', args: {elementPadding: 20/*, metro: true*/} },
|
||||
connector: { name: 'smooth' },
|
||||
attrs: {
|
||||
'.': {
|
||||
//filter: { name: 'dropShadow', args: { dx: 1, dy: 1, blur: 2 } }
|
||||
},
|
||||
'.connection': { 'stroke-width': 3, 'stroke': 'black', 'stroke-linecap': 'round' },
|
||||
'.marker-arrowheads': { display: 'none' },
|
||||
'.tool-options': { display: 'none' },
|
||||
'stroke':'red' // TODO necessary?
|
||||
},
|
||||
}, joint.shapes.flo.Link.prototype.defaults));
|
||||
return link;
|
||||
}
|
||||
|
||||
function isSemanticProperty(propertyPath) {
|
||||
return propertyPath === '.label/text';
|
||||
}
|
||||
|
||||
function refreshVisuals(element, changedPropertyPath, paper) {
|
||||
fitLabel(paper, element, '.label/text');
|
||||
}
|
||||
|
||||
function layout(paper) {
|
||||
var graph = paper.model;
|
||||
var i;
|
||||
var g = new dagre.graphlib.Graph();
|
||||
g.setGraph({});
|
||||
g.setDefaultEdgeLabel(function() {return{};});
|
||||
|
||||
var nodes = graph.getElements();
|
||||
for (i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
if (node.get('type') === joint.shapes.flo.NODE_TYPE) {
|
||||
g.setNode(node.id, node.get('size'));
|
||||
}
|
||||
}
|
||||
|
||||
var links = graph.getLinks();
|
||||
for (i = 0; i < links.length; i++) {
|
||||
var link = links[i];
|
||||
if (link.get('type') === joint.shapes.flo.LINK_TYPE) {
|
||||
var options = {
|
||||
minlen: 1.5
|
||||
};
|
||||
// if (link.get('labels') && link.get('labels').length > 0) {
|
||||
// options.minlen = 1 + link.get('labels').length * 0.5;
|
||||
// }
|
||||
g.setEdge(link.get('source').id, link.get('target').id, options);
|
||||
link.set('vertices', []);
|
||||
}
|
||||
}
|
||||
|
||||
g.graph().rankdir = 'LR';
|
||||
dagre.layout(g);
|
||||
g.nodes().forEach(function(v) {
|
||||
var node = graph.getCell(v);
|
||||
if (node) {
|
||||
var bbox = node.getBBox();
|
||||
node.translate(g.node(v).x - bbox.x, g.node(v).y - bbox.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getLinkAnchorPoint(linkView, view, magnet, reference) {
|
||||
if (magnet) {
|
||||
var cssClass = magnet.getAttribute('class');
|
||||
var bbox = joint.V(magnet).bbox(false, linkView.paper.viewport);
|
||||
var rect = joint.g.rect(bbox);
|
||||
if (cssClass.indexOf('input-port') !== -1) {
|
||||
return joint.g.point(rect.x, rect.y + rect.height / 2);
|
||||
} else if (cssClass.indexOf('error-port') !== -1) {
|
||||
return joint.g.point(rect.x + rect.width / 2, rect.y + rect.height);
|
||||
} else {
|
||||
return joint.g.point(rect.x + rect.width, rect.y + rect.height / 2);
|
||||
}
|
||||
} else {
|
||||
$log.debug('No magnet!');
|
||||
return reference;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'createHandle': createHandle,
|
||||
'createDecoration': createDecoration,
|
||||
'createNode': createNode,
|
||||
'createLink': createLink,
|
||||
'initializeNewNode': initializeNewNode,
|
||||
'isSemanticProperty': isSemanticProperty,
|
||||
'refreshVisuals': refreshVisuals,
|
||||
'layout': layout,
|
||||
'getLinkAnchorPoint': getLinkAnchorPoint
|
||||
};
|
||||
|
||||
}];
|
||||
|
||||
});
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a text representation to a graph.
|
||||
*
|
||||
* @author Alex Boyko
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
var joint = require('joint');
|
||||
|
||||
function collapseOneLevel(prefix, obj, collector) {
|
||||
var type = typeof obj;
|
||||
if (obj == null) {
|
||||
collector[prefix] = null;
|
||||
return;
|
||||
}
|
||||
if (type === 'object') {
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
collapseOneLevel(prefix.length==0?key:prefix+'.'+key,obj[key],collector);
|
||||
});
|
||||
} else if (type === 'array') {
|
||||
for (var i=0;i<obj.length;i++) {
|
||||
collapseOneLevel(prefix.length==0?key:prefix+'.'+i,obj[i],collector);
|
||||
}
|
||||
} else {
|
||||
collector[prefix] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
function collapse(obj, prefix) {
|
||||
if (!prefix) {
|
||||
prefix = '';
|
||||
}
|
||||
var retval = {};
|
||||
collapseOneLevel(prefix,obj,retval);
|
||||
console.log("collapsed = "+JSON.stringify(retval));
|
||||
return retval;
|
||||
}
|
||||
|
||||
var MAGNITUDE_NUMBERS = [ 1000000000, 1000000, 1000];
|
||||
var MAGNITUDE_LITERALS = ['B', 'M', 'K'];
|
||||
|
||||
var rateLabel = function() {
|
||||
var postFix, division, index = -1, fixed = 3;
|
||||
do {
|
||||
division = this.rate / MAGNITUDE_NUMBERS[++index];
|
||||
} while (!Math.floor(division) && index < MAGNITUDE_NUMBERS.length);
|
||||
if (index === MAGNITUDE_NUMBERS.length) {
|
||||
postFix = '';
|
||||
division = this.rate;
|
||||
} else {
|
||||
postFix = MAGNITUDE_LITERALS[index];
|
||||
}
|
||||
for (var decimal = 1; decimal <= 100 && Math.floor(division / decimal); decimal*=10) {
|
||||
fixed--;
|
||||
}
|
||||
return division.toFixed(fixed) + postFix;
|
||||
};
|
||||
|
||||
function animate(link,p) {
|
||||
// console.log("moving label on "+link.id+" to "+p);
|
||||
if (!link.label(1)) {
|
||||
console.log("No label1 on this link??");
|
||||
} else {
|
||||
link.label(1,{position: p})
|
||||
p+=0.025
|
||||
if (p>0.975) p = 0;
|
||||
setTimeout(function() {animate(link,p)},25);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return function(input, flo, metamodel, metamodelUtils) {
|
||||
// input is a string like this (3 nodes: foo, goo and hoo): foo --a=b --c=d > goo --d=e --f=g>hoo
|
||||
var trimmed = input.trim();
|
||||
if (trimmed.length===0) {
|
||||
return;
|
||||
}
|
||||
var getMetadata = function(type) {
|
||||
var group = metamodelUtils.matchGroup(metamodel, type, 1, 1);
|
||||
var md = metamodelUtils.getMetadata(metamodel, type, group);
|
||||
if (!md || md.unresolved) {
|
||||
var secondAttempt;
|
||||
// Examples: mail:outbound-channel-adapter or file:inbound-channel-adapter
|
||||
if (type.indexOf("inbound-channel-adapter")!=-1) {
|
||||
type = "inbound-channel-adapter";
|
||||
group = metamodelUtils.matchGroup(metamodel, type, 1, 1);
|
||||
secondAttempt = metamodelUtils.getMetadata(metamodel, type, group);
|
||||
if (secondAttempt && !secondAttempt.unresolved) {
|
||||
md = secondAttempt;
|
||||
}
|
||||
} else if (type.indexOf("outbound-channel-adapter")!=-1) {
|
||||
type = "outbound-channel-adapter";
|
||||
group = metamodelUtils.matchGroup(metamodel, type, 1, 1);
|
||||
secondAttempt = metamodelUtils.getMetadata(metamodel, type, group);
|
||||
if (secondAttempt && !secondAttempt.unresolved) {
|
||||
md = secondAttempt;
|
||||
}
|
||||
} else {
|
||||
// use the general one - this will ensure validation is OK and tooltips work but
|
||||
// we aren't really sure what type it is.
|
||||
type = 'general';
|
||||
group = metamodelUtils.matchGroup(metamodel, type, 1, 1);
|
||||
secondAttempt = metamodelUtils.getMetadata(metamodel, type, group);
|
||||
if (secondAttempt && !secondAttempt.unresolved) {
|
||||
md = secondAttempt;
|
||||
}
|
||||
}
|
||||
}
|
||||
return md;
|
||||
}
|
||||
var integrationGraph = JSON.parse(input);
|
||||
var nodes = integrationGraph.nodes;
|
||||
var nodesMap = {};
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
var node = nodes[i];
|
||||
var stats = node.stats;
|
||||
var props = collapse(node.stats,'stats');
|
||||
var props2 = collapse(node.properties,'properties');
|
||||
for (var attrname in props2) { props[attrname] = props2[attrname]; }
|
||||
props.name = node.name;
|
||||
props.id = node.nodeId;
|
||||
var newNode = flo.createNode(getMetadata(node.componentType), props);
|
||||
var nodeName = node.name;
|
||||
var metadataName = newNode.attr('metadata').name;
|
||||
if (metadataName === 'splitter' && nodeName.endsWith('.splitter')) {
|
||||
nodeName = nodeName.substring(0,nodeName.length-'.splitter'.length);
|
||||
} else if (metadataName === 'aggregator' && nodeName.endsWith('.aggregator')) {
|
||||
nodeName = nodeName.substring(0,nodeName.length-'.aggregator'.length);
|
||||
} else if (metadataName === 'service-activator' && nodeName.endsWith('serviceActivator')) {
|
||||
nodeName = nodeName.substring(0,nodeName.length-'.serviceActivator'.length);
|
||||
}
|
||||
if (node.name.indexOf('ConsumerEndpointFactoryBean')!==-1) {
|
||||
if (metadataName === 'router' && props['properties.expression']) {
|
||||
nodeName = props['properties.expression']+'?';
|
||||
} else if (metadataName != 'general') {
|
||||
nodeName = metadataName;
|
||||
}
|
||||
}
|
||||
newNode.attr('props/componentType',node.componentType);
|
||||
// if (nodeName != node.componentType) {
|
||||
// // Don't lose the componentType. For example the nodeName might end up as mailOut but
|
||||
// // componentType is mail:outbound-channel-adapter
|
||||
// }
|
||||
newNode.attr('.label/text',nodeName);
|
||||
nodesMap[node.nodeId] = newNode;
|
||||
}
|
||||
var links = integrationGraph.links;
|
||||
for (var i=0;i<links.length;i++) {
|
||||
var link = links[i];
|
||||
var isErrorLink = false;
|
||||
var fromPort = '.output-port';
|
||||
var toName = nodesMap[link.to].attr('.label/text');
|
||||
var fromName = nodesMap[link.from].attr('.label/text');
|
||||
if (link.type == 'error') {
|
||||
fromPort = '.error-port';
|
||||
isErrorLink=true;
|
||||
}
|
||||
var jointLink = flo.createLink({'id': nodesMap[link.from].id,'selector': fromPort},
|
||||
{'id': nodesMap[link.to].id, 'selector': '.input-port'});
|
||||
if (isErrorLink) {
|
||||
jointLink.attr('.connection/stroke','red');
|
||||
} else {
|
||||
if (nodes[link.from-1].stats && nodes[link.from-1].stats.hasOwnProperty('sendCount')) {
|
||||
// jointLink.label(0, {
|
||||
// position: 15,
|
||||
// type: 'outgoing-rate',
|
||||
// // rate: sourceRates.outgoingRate,
|
||||
// attrs: {
|
||||
// text: {
|
||||
// transform: 'translate(0, -8)',
|
||||
// //text: '{{rateLabel()}}',
|
||||
// text: nodes[link.from-1].stats.sendCount,
|
||||
// 'fill': 'black',
|
||||
// 'stroke': 'none',
|
||||
// 'font-size': '12'
|
||||
// },
|
||||
// rect: {
|
||||
// display: 'none'
|
||||
//// transform: 'translate(0, -5)',
|
||||
//// stroke: 'black',
|
||||
//// rx:1,ry:1,
|
||||
//// 'border-width': '2px',
|
||||
//// 'stroke-width': 1,
|
||||
//// fill: '#00B0A7'
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// jointLink.label(1, {
|
||||
// position: 0.5,
|
||||
// type: 'message',
|
||||
// // rate: sourceRates.outgoingRate,
|
||||
// attrs: {
|
||||
// text: {
|
||||
// transform: 'translate(0, 0)',
|
||||
// //text: '{{rateLabel()}}',
|
||||
// text: ' ',
|
||||
// 'fill': 'black',
|
||||
// 'stroke': 'black',
|
||||
// 'font-size': '2'
|
||||
// },
|
||||
// rect: {
|
||||
// transform: 'translate(0, 0)',
|
||||
// stroke: '#ffffff',
|
||||
// rx:1,ry:1,
|
||||
// 'border-width': '3px',
|
||||
// 'stroke-width': 2,
|
||||
// fill: '#ffffff'
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// console.log("Label 1 on link id "+jointLink.id+" = "+jointLink.label(1));
|
||||
// setTimeout(function() {animate(this,0.0)}.bind(jointLink),1000);
|
||||
// jointLink.transition('labels/1/position',1,{valueFunction: joint.util.interpolate.unit, timingFunction: joint.util.timing.bounce});
|
||||
}
|
||||
}
|
||||
}
|
||||
// var lines = trimmed.split('\n');
|
||||
// for (var l=0;l<lines.length;l++) {
|
||||
// var line = lines[l];
|
||||
// var elements = line.split('>');
|
||||
// var lastNode = null;
|
||||
// for (var e=0;e<elements.length;e++) {
|
||||
// var element = elements[e].trim();
|
||||
// // Has properties?
|
||||
// var startOfProps = element.indexOf(' ');
|
||||
// var name = element;
|
||||
// var properties = {};
|
||||
// if (startOfProps !== -1) {
|
||||
// name = element.substring(0,startOfProps);
|
||||
// var propValues = element.substring(startOfProps+1).trim().split(' ');
|
||||
// for (var p=0;p<propValues.length;p++) {
|
||||
// var propValue = propValues[p].trim();
|
||||
// if (propValue.length===0) {
|
||||
// // allows for multiple spaces between options
|
||||
// continue;
|
||||
// }
|
||||
// var equalsIndex = propValue.indexOf('=');
|
||||
// // The 2 skips the '--'
|
||||
// var key = propValue.substring(2,equalsIndex);
|
||||
// var value = propValue.substring(equalsIndex+1);
|
||||
// properties[key] = value;
|
||||
// }
|
||||
// }
|
||||
// var group = metamodelUtils.matchGroup(metamodel, name, 1, 1);
|
||||
// var newNode = flo.createNode(metamodelUtils.getMetadata(metamodel,name,group),properties);
|
||||
// newNode.attr('.label/text',name);
|
||||
// if (lastNode) {
|
||||
// flo.createLink({'id': lastNode.id,'selector': '.output-port'},
|
||||
// {'id': newNode.id,'selector': '.input-port'});
|
||||
// }
|
||||
// lastNode = newNode;
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update the labels on links on the graph based on a fresh copy of the graph data.
|
||||
* This assumes no nodes have changed, no links have changed - purely the counter
|
||||
* stats on those elements.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
var joint = require('joint');
|
||||
|
||||
function collapseOneLevel(prefix, obj, collector) {
|
||||
var type = typeof obj;
|
||||
if (obj == null) {
|
||||
collector[prefix] = null;
|
||||
return;
|
||||
}
|
||||
if (type === 'object') {
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
collapseOneLevel(prefix.length==0?key:prefix+'.'+key,obj[key],collector);
|
||||
});
|
||||
} else if (type === 'array') {
|
||||
for (var i=0;i<obj.length;i++) {
|
||||
collapseOneLevel(prefix.length==0?key:prefix+'.'+i,obj[i],collector);
|
||||
}
|
||||
} else {
|
||||
collector[prefix] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
function collapse(obj, prefix) {
|
||||
if (!prefix) {
|
||||
prefix = '';
|
||||
}
|
||||
var retval = {};
|
||||
collapseOneLevel(prefix,obj,retval);
|
||||
// console.log("collapsed = "+JSON.stringify(retval));
|
||||
return retval;
|
||||
}
|
||||
|
||||
var MAGNITUDE_NUMBERS = [ 1000000000, 1000000, 1000];
|
||||
var MAGNITUDE_LITERALS = ['B', 'M', 'K'];
|
||||
|
||||
var rateLabel = function() {
|
||||
var postFix, division, index = -1, fixed = 3;
|
||||
do {
|
||||
division = this.rate / MAGNITUDE_NUMBERS[++index];
|
||||
} while (!Math.floor(division) && index < MAGNITUDE_NUMBERS.length);
|
||||
if (index === MAGNITUDE_NUMBERS.length) {
|
||||
postFix = '';
|
||||
division = this.rate;
|
||||
} else {
|
||||
postFix = MAGNITUDE_LITERALS[index];
|
||||
}
|
||||
for (var decimal = 1; decimal <= 100 && Math.floor(division / decimal); decimal*=10) {
|
||||
fixed--;
|
||||
}
|
||||
return division.toFixed(fixed) + postFix;
|
||||
};
|
||||
|
||||
function animate(link,p) {
|
||||
// console.log("moving label on "+link.id+" to "+p);
|
||||
if (!link.label(1)) {
|
||||
console.log("No label1 on this link??");
|
||||
} else {
|
||||
var label = link.label(1);
|
||||
if (label.timer) {
|
||||
clearTimeout(label.timer);
|
||||
}
|
||||
link.label(1,{position: p})
|
||||
p+=0.06
|
||||
if (p<1) {
|
||||
label.timer = setTimeout(function() {animate(link,p)},15);
|
||||
} else {
|
||||
link.label(1, {
|
||||
position: 0.0,
|
||||
type: 'blip',
|
||||
// rate: sourceRates.outgoingRate,
|
||||
attrs: {
|
||||
text: {
|
||||
transform: 'translate(0, 0)',
|
||||
//text: '{{rateLabel()}}',
|
||||
text: '',
|
||||
'fill': 'black',
|
||||
'stroke': 'black',
|
||||
'font-size': '4'
|
||||
},
|
||||
rect: {
|
||||
transform: 'translate(0, 0)',
|
||||
stroke: '#00ffff',
|
||||
rx:1,ry:1,
|
||||
'border-width': '3px',
|
||||
'stroke-width': 4,
|
||||
fill: '#00ffff'
|
||||
}
|
||||
}
|
||||
});
|
||||
// link.label(1,{text:{text:''}});
|
||||
// label.attr('rect/display','none');
|
||||
// label.attr('text/display','none');
|
||||
// label.attrs.text.display = 'none';
|
||||
// label.attrs.rect.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return function(input, graph, labelpath) { //flo, metamodel, metamodelUtils) {
|
||||
// input is a string like this (3 nodes: foo, goo and hoo): foo --a=b --c=d > goo --d=e --f=g>hoo
|
||||
var trimmed = input.trim();
|
||||
if (trimmed.length===0) {
|
||||
return;
|
||||
}
|
||||
var integrationGraph = JSON.parse(input);
|
||||
var nodes = integrationGraph.nodes;
|
||||
var graphNodes = graph.getElements();
|
||||
var map = {};
|
||||
var linksToVisit = graph.getLinks();
|
||||
graphNodes.forEach(function(element) {
|
||||
if (element.attr('metadata/name')) { // is it a node?
|
||||
// if (!element.get('source')) {
|
||||
map[element.attr('props/id')] = element;
|
||||
} else {
|
||||
linksToVisit.push(element);
|
||||
}
|
||||
});
|
||||
function toLabel(text) {
|
||||
var string = text.toString();
|
||||
if (string.length>5) {
|
||||
string = string.substring(0,5);
|
||||
if (string.endsWith('.')) {
|
||||
string = string.substring(0,4);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
// Go through nodes, updating properties
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
var inputNode = nodes[i];
|
||||
var props = collapse(inputNode.stats,'stats');
|
||||
var props2 = collapse(inputNode.properties,'properties');
|
||||
for (var attrname in props2) { props[attrname] = props2[attrname]; }
|
||||
|
||||
map[inputNode.nodeId].attr('props',props);
|
||||
}
|
||||
for (var i=0;i<linksToVisit.length;i++) {
|
||||
var link = linksToVisit[i];
|
||||
var sourceId = link.get('source').id;
|
||||
var sourceElement = graph.getCell(sourceId);
|
||||
var rate;
|
||||
var props = sourceElement.attr('props');
|
||||
Object.keys(props).forEach(function(key) {
|
||||
if (key.toLowerCase() === labelpath.toLowerCase()) {
|
||||
rate = props[key];
|
||||
}
|
||||
});
|
||||
var existingLabel = link.label(0);
|
||||
var existingValue;
|
||||
var animateLink = false;
|
||||
if (existingLabel) {
|
||||
existingValue = existingLabel.attrs.text.text;
|
||||
if (existingValue !== toLabel(rate)) {
|
||||
animateLink = true;
|
||||
}
|
||||
}
|
||||
if (rate) {
|
||||
link.label(0, {
|
||||
position: 15,
|
||||
type: 'outgoing-rate',
|
||||
attrs: {
|
||||
text: { transform: 'translate(0, -8)', text: toLabel(rate), 'fill': 'black', 'stroke': 'none', 'font-size': '12' },
|
||||
rect: {
|
||||
display: 'none'
|
||||
// transform: 'translate(0, -5)',
|
||||
// stroke: 'black',
|
||||
// rx:1,ry:1,
|
||||
// 'border-width': '2px',
|
||||
// 'stroke-width': 1,
|
||||
// fill: '#00B0A7'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (animateLink) {
|
||||
link.label(1, {
|
||||
position: 0.0,
|
||||
type: 'blip',
|
||||
// rate: sourceRates.outgoingRate,
|
||||
attrs: {
|
||||
text: {
|
||||
transform: 'translate(0, 0)',
|
||||
//text: '{{rateLabel()}}',
|
||||
text: ' ',
|
||||
'fill': '#00ffff',
|
||||
'stroke': 'black',
|
||||
'font-size': '10'
|
||||
},
|
||||
rect: {
|
||||
transform: 'translate(0, 0)',
|
||||
// stroke: '#00B0A7',
|
||||
'stroke': 'black',
|
||||
rx:2,ry:2,
|
||||
'border-width': 2,
|
||||
'stroke-width': 3,
|
||||
// fill: '#00B0A7'
|
||||
'fill': 'black'
|
||||
}
|
||||
}
|
||||
});
|
||||
link.label(1).timer = setTimeout(function() {animate(this,0.0)}.bind(link),0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user