├── .gitignore ├── main.go ├── model ├── camel.go ├── config.go ├── graphite.go ├── metric.go ├── route.go ├── service.go └── utils.go ├── public ├── css │ ├── AdminLTE.min.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ ├── flow.css │ ├── font-awesome.min.css │ ├── ionicons.min.css │ ├── skin-blue.min.css │ └── vis.min.css ├── favicon.ico ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── img │ ├── camel-graph.png │ ├── grafana_icon.svg │ └── graphite-icon-57x57.png ├── index.html ├── info.json ├── js │ ├── app.js │ ├── controllers │ │ ├── environment.js │ │ ├── environments.js │ │ └── optionsDialog.js │ ├── providers │ │ └── local-timeout.js │ ├── services │ │ └── utils.js │ └── vendor │ │ ├── angular-relative-date.min.js │ │ ├── angular-relative-date.min.js.map │ │ ├── angular-route.js │ │ ├── angular-sanitize.js │ │ ├── angular-ui-router.js │ │ ├── angular.js │ │ ├── jquery-1.11.1.min.js │ │ ├── moment.min.js │ │ ├── ui-bootstrap-tpls-0.14.3.min.js │ │ ├── vis.min.js │ │ └── xml2json.min.js └── views │ ├── environment.html │ ├── environments.html │ └── options.html ├── readme.md └── services.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "net/http/pprof" 5 | "net/http" 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "flag" 10 | "github.com/avvero/camel-graph/model" 11 | ) 12 | 13 | var ( 14 | httpPort = flag.String("httpPort", "8080", "http server port") 15 | serviceUpdateIntervalSeconds = flag.Int("serviceUpdateIntervalSeconds", 60, "update interval for infos") 16 | routeUpdateIntervalSeconds = flag.Int("routeUpdateIntervalSeconds", 60, "update interval for infos") 17 | graphiteUrl = flag.String("graphiteUrl", "", "host and port to send plaint text metrics to graphite") 18 | graphiteRepeatSendOnFail = flag.Bool("graphiteRepeatSendOnFail", false, "repeat send metrcis to graphite on fail") 19 | ) 20 | 21 | func main() { 22 | flag.Parse() 23 | 24 | config, err := model.ReadConfig("services.json") 25 | config.ServiceUpdateIntervalSeconds = *serviceUpdateIntervalSeconds 26 | config.RouteUpdateIntervalSeconds = *routeUpdateIntervalSeconds 27 | if err != nil { 28 | panic(fmt.Sprintf("Error during configuration %v", err)) 29 | } 30 | 31 | var metricConsumer model.MetricConsumer 32 | if *graphiteUrl != "" { 33 | metricConsumer = model.NewGraphite(*graphiteUrl, *graphiteRepeatSendOnFail) 34 | log.Println("Metrics will be passed to graphite: " + *graphiteUrl) 35 | } else { 36 | metricConsumer = &model.MetricConsumerStub{} 37 | } 38 | 39 | instance, err := model.NewInstance(config, &metricConsumer) 40 | if err != nil { 41 | panic(fmt.Sprintf("Error during configuration %v", err)) 42 | } 43 | 44 | // proxy stuff 45 | http.Handle("/", http.FileServer(http.Dir("public"))) 46 | http.HandleFunc("/data", func(w http.ResponseWriter, r *http.Request) { 47 | var js []byte 48 | var err error 49 | envName := r.URL.Query().Get("env") 50 | var environmentToReturn *model.Environment 51 | if envName != "" { 52 | for _, environment := range instance.Environments { 53 | if environment.Name == envName { 54 | environmentToReturn = environment 55 | break 56 | } 57 | } 58 | } 59 | // marshal 60 | if envName != "" && environmentToReturn != nil { 61 | js, err = json.Marshal(environmentToReturn) 62 | } else { 63 | js, err = json.Marshal(instance) 64 | } 65 | if err != nil { 66 | http.Error(w, err.Error(), http.StatusInternalServerError) 67 | return 68 | } 69 | w.Header().Set("Content-Type", "application/json") 70 | w.Write(js) 71 | }) 72 | 73 | log.Println("Http server started on port " + *httpPort) 74 | http.ListenAndServe(":" + *httpPort, nil) 75 | } 76 | -------------------------------------------------------------------------------- /model/camel.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type ReadRouteResponse struct { 4 | Value map[string]ReadRouteEntry `json:"value,omitempty"` 5 | Status int `json:"status,omitempty"` 6 | } 7 | 8 | type ReadRouteEntry struct { 9 | EndpointUri string 10 | CamelManagementName string 11 | Uptime string 12 | CamelId string 13 | RouteId string 14 | State string 15 | ExchangesTotal int 16 | ExchangesCompleted int 17 | ExchangesFailed int 18 | ExchangesInflight int 19 | MaxProcessingTime int 20 | MinProcessingTime int 21 | LastProcessingTime int 22 | MeanProcessingTime int 23 | TotalProcessingTime int 24 | FailuresHandled int 25 | Redeliveries int 26 | StartTimestamp string 27 | } 28 | 29 | type ReadResponse struct { 30 | Value string `json:"value,omitempty"` 31 | Error string `json:"error,omitempty"` 32 | Status int `json:"status,omitempty"` 33 | } 34 | 35 | type ReadRoutesEndpointsEntry struct { 36 | Routes *map[string]*ReadRouteEndpointsEntry `json:"routes,omitempty"` 37 | } 38 | 39 | type ReadRouteEndpointsEntry struct { 40 | Inputs []*RouteEndpointEntry `json:"inputs,omitempty"` 41 | Outputs []*RouteEndpointEntry `json:"outputs,omitempty"` 42 | } 43 | 44 | type RouteEndpointEntry struct { 45 | Uri string `json:"uri,omitempty"` 46 | } 47 | -------------------------------------------------------------------------------- /model/config.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "os" 5 | "encoding/json" 6 | "time" 7 | ) 8 | 9 | type InstanceConfig struct { 10 | Created time.Time 11 | Environments []*EnvironmentConfig 12 | ServiceUpdateIntervalSeconds int 13 | RouteUpdateIntervalSeconds int 14 | } 15 | 16 | type EnvironmentConfig struct { 17 | Name string 18 | Services []*ServiceConfig 19 | } 20 | 21 | type ServiceConfig struct { 22 | Name string 23 | Url string 24 | Color string 25 | Authorization *Authorization 26 | } 27 | 28 | type Authorization struct { 29 | Login string 30 | Pass string 31 | } 32 | 33 | // Read config 34 | func ReadConfig(fileName string) (*InstanceConfig, error) { 35 | rootConfig := InstanceConfig{} 36 | file, err := os.Open(fileName) 37 | if err != nil { 38 | return nil, err 39 | } 40 | decoder := json.NewDecoder(file) 41 | err = decoder.Decode(&rootConfig) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return &rootConfig, nil 46 | } 47 | -------------------------------------------------------------------------------- /model/graphite.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "log" 5 | "fmt" 6 | "net" 7 | "time" 8 | "github.com/fatih/pool" 9 | ) 10 | 11 | type Graphite struct { 12 | pool *pool.Pool 13 | metrics chan *Metric 14 | repeatSendOnFail bool 15 | } 16 | 17 | const ( 18 | StopCharacter = "\r\n\r\n" 19 | SleepDuration = 5 20 | ) 21 | 22 | func NewGraphite(url string, repeatSendOnFail bool) *Graphite { 23 | //pool 24 | factory := func() (net.Conn, error) { return net.Dial("tcp", url) } 25 | pool, err := pool.NewChannelPool(5, 30, factory) 26 | if err != nil { 27 | panic(fmt.Sprintf("Error during creating new pool for graphite %v", err)) 28 | } 29 | 30 | graphite := &Graphite{pool: &pool, metrics: make(chan *Metric, 1000), repeatSendOnFail: repeatSendOnFail} 31 | go func() { 32 | for { 33 | select { 34 | case metric := <-graphite.metrics: 35 | times := 1 36 | for err := graphite.send(metric); err != nil && times < 4; { 37 | sleepTime := SleepDuration * time.Duration(times) 38 | log.Println(fmt.Sprintf("Will sleep for %v (%v attempt of 3)", sleepTime, times)) 39 | time.Sleep(sleepTime * time.Second) 40 | times++ 41 | } 42 | } 43 | } 44 | }() 45 | return graphite 46 | } 47 | func (graphite *Graphite) send(metric *Metric) error { 48 | message := fmt.Sprintf("%s %v %v", metric.name, metric.value, metric.time.Unix()) 49 | 50 | conn, err := (*graphite.pool).Get() 51 | if err != nil { 52 | if graphite.repeatSendOnFail { 53 | log.Println(fmt.Sprintf("Could not connected to graphite")) 54 | return err 55 | } else { 56 | log.Println(fmt.Sprintf("Could not connected to graphite, metrics will be lost")) 57 | return nil 58 | } 59 | } 60 | defer conn.Close() 61 | 62 | _, err = conn.Write([]byte(message + StopCharacter)) 63 | if err != nil { 64 | log.Println(fmt.Sprintf("Something wrong with the connection it will be closed")) 65 | if pc, ok := conn.(*pool.PoolConn); ok { 66 | pc.MarkUnusable() 67 | pc.Close() 68 | } 69 | } 70 | //log.Println(fmt.Sprintf("Do send metrics: %s", message)) 71 | //log.Println(fmt.Sprintf("available connections in the pool: %s", (*graphite.pool).Len())) 72 | return nil 73 | } 74 | 75 | func (graphite *Graphite) consumeMetric(metric *Metric) { 76 | graphite.metrics <- metric 77 | } 78 | -------------------------------------------------------------------------------- /model/metric.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | "log" 6 | "fmt" 7 | ) 8 | 9 | type Metric struct { 10 | name string 11 | time time.Time 12 | value interface{} 13 | } 14 | 15 | type MetricConsumer interface { 16 | consumeMetric(metric *Metric) 17 | } 18 | 19 | type MetricConsumerStub struct { 20 | } 21 | 22 | func (it *MetricConsumerStub) consumeMetric(metric *Metric) { 23 | message := fmt.Sprintf("%s %v %v", metric.name, metric.value, metric.time.Unix()) 24 | if false { 25 | log.Println(fmt.Sprintf("log metrics: %s", message)) 26 | } 27 | } 28 | 29 | func NewMetric(metricName string, value interface{}, t time.Time) *Metric { 30 | return &Metric{name: metricName, value: value, time: t} 31 | } -------------------------------------------------------------------------------- /model/route.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Route struct { 6 | Context string `json:"context,omitempty"` 7 | Name string `json:"name,omitempty"` 8 | Error string `json:"error,omitempty"` 9 | LastUpdated JsonTime `json:"lastUpdated"` 10 | State string `json:"state,omitempty"` 11 | Uptime string `json:"uptime,omitempty"` 12 | Schema string `json:"schema,omitempty"` 13 | EndpointUri string `json:"endpointUri,omitempty"` 14 | Endpoints *Endpoints `json:"endpoints,omitempty"` 15 | // metrics 16 | ExchangesTotal int `json:"exchangesTotal,omitempty"` 17 | ExchangesCompleted int `json:"exchangesCompleted,omitempty"` 18 | ExchangesFailed int `json:"exchangesFailed,omitempty"` 19 | ExchangesInflight int `json:"exchangesInflight,omitempty"` 20 | MaxProcessingTime int `json:"maxProcessingTime,omitempty"` 21 | MinProcessingTime int `json:"minProcessingTime,omitempty"` 22 | LastProcessingTime int `json:"lastProcessingTime,omitempty"` 23 | MeanProcessingTime int `json:"meanProcessingTime,omitempty"` 24 | TotalProcessingTime int `json:"totalProcessingTime,omitempty"` 25 | FailuresHandled int `json:"failuresHandled,omitempty"` 26 | Redeliveries int `json:"redeliveries,omitempty"` 27 | StartTimestamp string`json:"startTimestamp,omitempty"` 28 | // meta 29 | 30 | // DONE, FAILED 31 | UpdatingState string `json:"updatingState,omitempty"` 32 | metrics chan *Metric 33 | service *Service 34 | upd chan time.Time 35 | } 36 | 37 | type Context struct { 38 | Name string `json:"name,omitempty"` 39 | Routes []*Route `json:"routes,omitempty"` 40 | } 41 | 42 | type Endpoints struct { 43 | Inputs []string `json:"inputs,omitempty"` 44 | Outputs []string `json:"outputs,omitempty"` 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /model/service.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | "sync" 6 | "errors" 7 | "encoding/json" 8 | "log" 9 | "fmt" 10 | "strings" 11 | "github.com/antchfx/xquery/xml" 12 | ) 13 | 14 | const ( 15 | UPDATE_STATE_IN_PROCESS = "in_process" 16 | UPDATE_STATE_DONE = "done" 17 | UPDATE_STATE_FAILED = "failed" 18 | 19 | NONE = "None" 20 | ) 21 | 22 | const ( 23 | GetRouteSchemaPath = "%s/jolokia/exec/org.apache.camel:context=%s,type=routes,name=\"%s\"/dumpRouteAsXml(boolean)/true" 24 | GetRouteEndpointsPath = "%s/jolokia/exec/org.apache.camel:context=%s,type=routes,name=\"%s\"/createRouteStaticEndpointJson(boolean)/true" 25 | GetRoutesPath = "/jolokia/read/org.apache.camel:type=routes,*" 26 | ) 27 | 28 | type Instance struct { 29 | Environments []*Environment `json:"environments,omitempty"` 30 | } 31 | 32 | type Environment struct { 33 | Name string `json:"name,omitempty"` 34 | ServiceMap map[string]*Service `json:"serviceMap,omitempty"` 35 | } 36 | 37 | type JsonTime time.Time 38 | 39 | func (t JsonTime)MarshalJSON() ([]byte, error) { 40 | //do your serializing here 41 | stamp := fmt.Sprintf("\"%s\"", time.Time(t).Format(time.RFC3339)) 42 | return []byte(stamp), nil 43 | } 44 | 45 | type Service struct { 46 | Name string `json:"name,omitempty"` 47 | Url string `json:"url,omitempty"` 48 | RouteMap map[string]*Route `json:"routeMap,omitempty"` 49 | LastUpdated JsonTime `json:"lastUpdated,"` 50 | Error string `json:"error,omitempty"` 51 | Color string `json:"color,omitempty"` 52 | // IN_PROCESS, DONE, FAILED 53 | UpdatingState string `json:"updatingState,omitempty"` 54 | 55 | updateMutex sync.Mutex 56 | metricConsumer *MetricConsumer 57 | config *ServiceConfig 58 | environment *Environment 59 | upd chan time.Time 60 | } 61 | 62 | func NewInstance(config *InstanceConfig, metricConsumer *MetricConsumer) (*Instance, error) { 63 | instance := &Instance{Environments: make([]*Environment, len(config.Environments))} 64 | for i, environmentConfig := range config.Environments { 65 | environment, err := NewEnvironment(config, environmentConfig, metricConsumer) 66 | if err != nil { 67 | return nil, err 68 | } 69 | instance.Environments[i] = environment 70 | } 71 | return instance, nil 72 | } 73 | 74 | func NewEnvironment(instanceConfig *InstanceConfig, envConfig *EnvironmentConfig, metricConsumer *MetricConsumer) (*Environment, error) { 75 | if envConfig.Name == "" { 76 | return nil, errors.New("environment name must not be empty") 77 | } 78 | environment := &Environment{ 79 | Name: envConfig.Name, 80 | ServiceMap: make(map[string]*Service)} 81 | for _, serviceConfig := range envConfig.Services { 82 | service, err := NewService(instanceConfig, serviceConfig, environment, metricConsumer) 83 | if err != nil { 84 | return nil, err 85 | } 86 | environment.ServiceMap[service.Name] = service 87 | } 88 | return environment, nil 89 | } 90 | 91 | //Creates new service from config 92 | func NewService(instanceConfig *InstanceConfig, config *ServiceConfig, environment *Environment, metricConsumer *MetricConsumer) (*Service, error) { 93 | if config.Name == "" { 94 | return nil, errors.New("service name must not be empty") 95 | } 96 | if config.Url == "" { 97 | return nil, errors.New("service url must not be empty") 98 | } 99 | service := &Service{ 100 | config: config, 101 | Name: config.Name, 102 | Url: config.Url, 103 | Color: config.Color, 104 | upd: make(chan time.Time, 100), 105 | RouteMap: make(map[string]*Route), 106 | UpdatingState: UPDATE_STATE_IN_PROCESS, 107 | environment: environment, 108 | metricConsumer: metricConsumer} 109 | go service.doUpdate(instanceConfig.ServiceUpdateIntervalSeconds, instanceConfig.RouteUpdateIntervalSeconds) 110 | return service, nil 111 | } 112 | 113 | 114 | func (service *Service) doUpdate(serviceUpdateIntervalSeconds int, routeUpdateIntervalSeconds int) { 115 | ticker := time.NewTicker(time.Duration(serviceUpdateIntervalSeconds) * time.Second) 116 | service.upd <- time.Now() 117 | 118 | for { 119 | select { 120 | case <-ticker.C: 121 | if service.UpdatingState == UPDATE_STATE_IN_PROCESS { 122 | //log.Printf("info: %s:%s is still has been updating", service.environment.Name, service.Name) 123 | } else { 124 | service.upd <- time.Now() 125 | } 126 | case t := <-service.upd: 127 | service.UpdatingState = UPDATE_STATE_IN_PROCESS 128 | err := service.update(t, routeUpdateIntervalSeconds) 129 | if err != nil { 130 | service.UpdatingState = UPDATE_STATE_FAILED 131 | service.Error = fmt.Sprintf("%s", err) 132 | } else { 133 | service.Error = "" 134 | service.UpdatingState = UPDATE_STATE_DONE 135 | service.LastUpdated = JsonTime(t) 136 | } 137 | } 138 | } 139 | } 140 | 141 | func (service *Service) update(t time.Time, routeUpdateIntervalSeconds int) error { 142 | url := service.config.Url + GetRoutesPath 143 | body, err := callEndpoint(url, service.config.Authorization) 144 | if err != nil { 145 | log.Printf("error: %s:%s error during getting routes from %s: %s", service.environment.Name, service.Name, 146 | service.config.Url, err) 147 | return err 148 | } else { 149 | for _, r := range service.RouteMap { 150 | r.State = NONE 151 | } 152 | // Merge from object 153 | response := &ReadRouteResponse{} 154 | json.Unmarshal(body, response) 155 | //log.Printf("info: %s:%s read route response %v", service.environment.Name, service.Name, response) 156 | for _, v := range response.Value { 157 | properContext := strings.Replace(v.CamelManagementName, " ", "_", -1) 158 | properRouteId := strings.Replace(v.RouteId, " ", "_", -1) 159 | routeName := fmt.Sprintf("%s.%s", properContext, properRouteId) 160 | route, exists := service.RouteMap[routeName] 161 | if !exists { 162 | route = &Route{ 163 | Context: v.CamelManagementName, 164 | Name: v.RouteId, 165 | EndpointUri: v.EndpointUri, 166 | Endpoints: &Endpoints{ 167 | Inputs: make([]string, 0), 168 | Outputs: make([]string, 0), 169 | }, 170 | service: service, 171 | UpdatingState: UPDATE_STATE_IN_PROCESS, 172 | 173 | upd: make(chan time.Time, 100), 174 | metrics: make(chan *Metric, 1000)} 175 | service.RouteMap[routeName] = route 176 | // Add first input 177 | route.Endpoints.Inputs = append(route.Endpoints.Inputs, cleanEndpoint(route, v.EndpointUri)) 178 | go route.doUpdate(routeUpdateIntervalSeconds) 179 | go route.sendMetrics() 180 | } 181 | route.State = v.State 182 | route.Uptime = v.Uptime 183 | // Metrics 184 | route.ExchangesTotal = v.ExchangesTotal 185 | route.ExchangesCompleted = v.ExchangesCompleted 186 | route.ExchangesFailed = v.ExchangesFailed 187 | route.ExchangesInflight = v.ExchangesInflight 188 | route.MaxProcessingTime = v.MaxProcessingTime 189 | route.MinProcessingTime = v.MinProcessingTime 190 | route.LastProcessingTime = v.LastProcessingTime 191 | route.MeanProcessingTime = v.MeanProcessingTime 192 | route.TotalProcessingTime = v.TotalProcessingTime 193 | route.FailuresHandled = v.FailuresHandled 194 | route.Redeliveries = v.Redeliveries 195 | 196 | route.collectMetrics(&v, t) 197 | } 198 | // stop update 199 | return nil 200 | } 201 | } 202 | 203 | func (route *Route) doUpdate(routeUpdateIntervalSeconds int) { 204 | ticker := time.NewTicker(time.Duration(routeUpdateIntervalSeconds) * time.Second) 205 | route.upd <- time.Now() 206 | 207 | for { 208 | select { 209 | case <-ticker.C: 210 | if route.State == NONE { 211 | log.Printf("info: %s:%s:%s route is outed of service", route.service.environment.Name, 212 | route.service.Name, route.Name) 213 | return 214 | } 215 | if route.UpdatingState == UPDATE_STATE_IN_PROCESS { 216 | //log.Printf("info: %s:%s:%s route is still has been updating", route.service.environment.Name, 217 | // route.service.Name, route.Name) 218 | } else { 219 | //log.Printf("info: %s:%s:%s route is updating", route.service.environment.Name, 220 | // route.service.Name, route.Name) 221 | route.upd <- time.Now() 222 | } 223 | case t := <-route.upd: 224 | route.UpdatingState = UPDATE_STATE_IN_PROCESS 225 | err:= route.update() 226 | if err != nil { 227 | route.UpdatingState = UPDATE_STATE_FAILED 228 | route.Error = fmt.Sprintf("%s", err) 229 | if route.State == NONE { 230 | //close(route.upd) 231 | //close(route.metrics) 232 | } 233 | } else { 234 | route.Error = "" 235 | route.UpdatingState = UPDATE_STATE_DONE 236 | route.LastUpdated = JsonTime(t) 237 | } 238 | } 239 | } 240 | } 241 | 242 | func (route *Route) update() error { 243 | // Endpoints 244 | url := fmt.Sprintf(GetRouteEndpointsPath, route.service.config.Url, route.Context, route.Name) 245 | 246 | //log.Printf("indo: %s:%s:%s getting route endoints from %s", 247 | // route.service.environment.Name, route.service.Name, route.Name, url) 248 | 249 | body, err := callEndpoint(url, route.service.config.Authorization) 250 | if err != nil { 251 | log.Printf("error: %s:%s:%s error during getting route endoints from %s: %s", 252 | route.service.environment.Name, route.service.Name, route.Name, route.service.config.Url, err) 253 | return err 254 | } else { 255 | response := &ReadResponse{} 256 | json.Unmarshal(body, response) 257 | if response.Status != 200 { 258 | log.Printf("error: %s:%s:%s error during getting route endoints from %s: %s", 259 | route.service.environment.Name, route.service.Name, route.Name, route.service.config.Url, response.Error) 260 | return errors.New(response.Error) 261 | } else { 262 | endpointsEntry := &ReadRoutesEndpointsEntry{} 263 | json.Unmarshal([]byte(response.Value), endpointsEntry) 264 | if endpointsEntry != nil && *endpointsEntry.Routes != nil{ 265 | for _, v := range *endpointsEntry.Routes { 266 | if v.Outputs == nil { 267 | continue 268 | } 269 | for _, o := range v.Outputs { 270 | 271 | // skip configured and not filled endpoints 272 | if len(o.Uri) == 0 && strings.Contains(o.Uri, "{{") { 273 | continue 274 | } 275 | candidate := cleanEndpoint(route, o.Uri) 276 | if !contains(route.Endpoints.Outputs, candidate) { 277 | route.Endpoints.Outputs = append(route.Endpoints.Outputs, candidate) 278 | } 279 | } 280 | } 281 | } 282 | } 283 | } 284 | // Schema 285 | url = fmt.Sprintf(GetRouteSchemaPath, route.service.config.Url, route.Context, route.Name) 286 | //log.Printf("info: %s:%s:%s getting schema %s", route.service.environment.Name, route.service.Name, route.Name, url) 287 | body, err = callEndpoint(url, route.service.config.Authorization) 288 | if err != nil { 289 | log.Printf("error: %s:%s:%s error during getting schema from %s: %s", route.service.environment.Name, 290 | route.service.Name, route.Name, route.service.config.Url, err) 291 | return err 292 | } else { 293 | response := &ReadResponse{} 294 | json.Unmarshal(body, response) 295 | if response != nil && len(response.Value) > 0 { 296 | route.Schema = response.Value 297 | xmlNode, _ := xmlquery.Parse(strings.NewReader(route.Schema)) 298 | // to endpoints 299 | toEndpoints := xmlquery.Find(xmlNode, "//to") 300 | for _, toEndpoint := range toEndpoints { 301 | uri := toEndpoint.SelectAttr("uri") 302 | if len(uri) > 0 { 303 | candidate := cleanEndpoint(route, uri) 304 | if !contains(route.Endpoints.Outputs, candidate) { 305 | route.Endpoints.Outputs = append(route.Endpoints.Outputs, candidate) 306 | } 307 | } 308 | } 309 | // from endpoints 310 | fromEndpoints := xmlquery.Find(xmlNode, "//from") 311 | for _, fromEndpoint := range fromEndpoints { 312 | uri := fromEndpoint.SelectAttr("uri") 313 | if len(uri) > 0 { 314 | candidate := cleanEndpoint(route, uri) 315 | if !contains(route.Endpoints.Inputs, candidate) { 316 | route.Endpoints.Inputs = append(route.Endpoints.Inputs, candidate) 317 | } 318 | } 319 | } 320 | } 321 | return nil 322 | } 323 | } 324 | 325 | func contains(list []string, entry string) bool { 326 | if list == nil || len(list) == 0 { 327 | return false 328 | } 329 | for _, v := range list { 330 | if v == entry { 331 | return true 332 | } 333 | } 334 | return false 335 | } 336 | 337 | func cleanEndpoints(route *Route, v []*RouteEndpointEntry) (inputs []string) { 338 | inputs = make([]string, 0) 339 | for _, i := range v { 340 | url := i.Uri 341 | inputs = append(inputs, cleanEndpoint(route, url)) 342 | } 343 | return 344 | } 345 | 346 | func cleanEndpoint(route *Route, endpoint string) (result string) { 347 | endpoint = strings.Replace(endpoint, "%7B", "{", -1) 348 | endpoint = strings.Replace(endpoint, "%7D", "}", -1) 349 | endpoint = strings.Replace(endpoint, "://", ":", -1) 350 | endpoint = strings.Replace(endpoint, "activemq:", "jms:", -1) 351 | //take left part before ? 352 | endpoint = strings.Split(endpoint, "?")[0] 353 | if strings.Contains(endpoint, "VirtualTopic") { 354 | topicName := strings.Split(endpoint, "VirtualTopic")[1] 355 | endpoint = "VirtualTopic" + topicName 356 | } 357 | if strings.HasPrefix(endpoint, "direct") { 358 | endpoint = route.service.Name + ":" + endpoint 359 | } 360 | if strings.HasPrefix(endpoint, "timer") { 361 | endpoint = route.service.Name + ":" + endpoint 362 | } 363 | return endpoint 364 | } 365 | 366 | func (route *Route) collectMetrics(e *ReadRouteEntry, t time.Time) { 367 | properContext := strings.Replace(e.CamelManagementName, " ", "_", -1) 368 | properRouteId := strings.Replace(e.RouteId, " ", "_", -1) 369 | routeName := fmt.Sprintf("%s_%s", properContext, properRouteId) 370 | properRouteName := strings.Replace(routeName, ".", "_", -1) 371 | 372 | serviceName := fmt.Sprintf("camel-graph.%s.%s.%s", route.service.environment.Name, route.service.Name, properRouteName) 373 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "exchanges_total"), e.ExchangesTotal, t) 374 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "exchanges_completed"), e.ExchangesCompleted, t) 375 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "exchanges_failed"), e.ExchangesFailed, t) 376 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "exchanges_inflight"), e.ExchangesInflight, t) 377 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "max_processing_time"), e.MaxProcessingTime, t) 378 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "min_processing_time"), e.MinProcessingTime, t) 379 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "last_processing_time"), e.LastProcessingTime, t) 380 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "mean_processing_time"), e.MeanProcessingTime, t) 381 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "total_processing_time"), e.TotalProcessingTime, t) 382 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "failures_handled"), e.FailuresHandled, t) 383 | route.metrics <- NewMetric(fmt.Sprintf("%s.%s", serviceName, "redeliveries"), e.Redeliveries, t) 384 | } 385 | 386 | func (route *Route) sendMetrics() { 387 | for { 388 | select { 389 | case metric := <-route.metrics: 390 | (*route.service.metricConsumer).consumeMetric(metric) 391 | } 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /model/utils.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "net/http" 5 | "io/ioutil" 6 | "time" 7 | "errors" 8 | ) 9 | 10 | func callEndpoint(url string, auth *Authorization) ([]byte, error) { 11 | client := &http.Client{ 12 | Timeout: time.Duration(60 * time.Second), 13 | } 14 | req, err := http.NewRequest("GET", url, nil) 15 | if auth != nil { 16 | req.SetBasicAuth(auth.Login, auth.Pass) 17 | } 18 | resp, err := client.Do(req) 19 | if err != nil { 20 | return nil, err 21 | } 22 | if resp.StatusCode != 200 { 23 | return nil, errors.New("Status " + resp.Status) 24 | } 25 | defer resp.Body.Close() 26 | //return json.NewDecoder(resp.Body).Decode(target) 27 | return ioutil.ReadAll(resp.Body) 28 | } 29 | -------------------------------------------------------------------------------- /public/css/flow.css: -------------------------------------------------------------------------------- 1 | .entry-log.level-ERROR, 2 | .entry-log .log-level.level-ERROR { 3 | color: #AC2925; 4 | } 5 | 6 | .entry-log .log-level.level-ERROR { 7 | font-weight: bold; 8 | } 9 | 10 | .entry-log .log-level.level-WARN { 11 | color: darkorange; 12 | font-weight: bold; 13 | } 14 | 15 | .entry-log .log-level.level-INFO { 16 | color: blue; 17 | font-weight: bold; 18 | } 19 | 20 | .entry-log .log-level.level-DEBUG { 21 | color: green; 22 | font-weight: bold; 23 | font-weight: bold; 24 | } 25 | 26 | .entry-log { 27 | /*display: none;*/ 28 | word-wrap: break-word; 29 | } 30 | 31 | .entry-log.selected { 32 | /*display: none;*/ 33 | background-color: #FFE485; 34 | } 35 | 36 | .show-level-ERROR .entry-log.level-ERROR { 37 | display: block 38 | } 39 | 40 | .show-level-WARN .entry-log.level-WARN { 41 | display: block 42 | } 43 | 44 | .show-level-INFO .entry-log.level-INFO { 45 | display: block 46 | } 47 | 48 | .show-level-DEBUG .entry-log.level-DEBUG { 49 | display: block 50 | } 51 | 52 | .show-level-TRACE .entry-log.level-TRACE { 53 | display: block 54 | } 55 | 56 | .entry-log span.log-level { 57 | width: 50px; 58 | display: inline-block; 59 | text-align: right; 60 | } 61 | 62 | .entry-log span.log-thread { 63 | /*min-width: 150px;*/ 64 | display: inline-block; 65 | /*float: right;*/ 66 | } 67 | 68 | .entry-log span.log-mdc { 69 | /*float: right;*/ 70 | /*padding-right: 5px;*/ 71 | } 72 | 73 | .entry-log div.log-msg-multiline { 74 | margin-left: 50px; 75 | padding-left: 5px; 76 | } 77 | 78 | .entry-log .log-msg-error-stack { 79 | margin-left: 50px; 80 | padding-left: 5px; 81 | } 82 | 83 | .flow { 84 | padding-left: 3px; 85 | padding-right: 33px; 86 | overflow: auto; 87 | position: fixed !important; 88 | position: absolute; 89 | top: 5px; 90 | right: 0; 91 | bottom: 0px; 92 | left: 155px; 93 | } 94 | 95 | .log-list { 96 | font-size: 12px; 97 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 98 | word-wrap: break-word; 99 | } 100 | 101 | .searchable .flow { 102 | top: 54px; 103 | } 104 | 105 | .log-msg-error-stack.callout.callout-danger { 106 | background-color: white !important; 107 | color: #AC2925 !important; 108 | } 109 | 110 | @-webkit-keyframes pulse { 111 | 0% { 112 | -webkit-transform: scale(0); 113 | opacity: 0; 114 | } 115 | 8% { 116 | -webkit-transform: scale(0); 117 | opacity: 0; 118 | } 119 | 15% { 120 | -webkit-transform: scale(0.1); 121 | opacity: 1; 122 | } 123 | 30% { 124 | -webkit-transform: scale(0.5); 125 | opacity: 1; 126 | } 127 | 100% { 128 | opacity: 0; 129 | -webkit-transform: scale(1); 130 | } 131 | } 132 | 133 | @-moz-keyframes pulse { 134 | 0% { 135 | -moz-transform: scale(0); 136 | opacity: 0; 137 | } 138 | 8% { 139 | -moz-transform: scale(0); 140 | opacity: 0; 141 | } 142 | 15% { 143 | -moz-transform: scale(0.1); 144 | opacity: 1; 145 | } 146 | 30% { 147 | -moz-transform: scale(0.5); 148 | opacity: 1; 149 | } 150 | 100% { 151 | opacity: 0; 152 | -moz-transform: scale(1); 153 | } 154 | } 155 | 156 | .pulse_holder { 157 | display: block; 158 | z-index: 1030; 159 | position: relative; 160 | left: -32px; 161 | top: -20px; 162 | width: 16px; 163 | } 164 | 165 | .pulse_holder .pulse_marker { 166 | width: 16px; 167 | height: 16px; 168 | background: #969099; 169 | border-radius: 28px; 170 | } 171 | 172 | .pulse_holder.connected .pulse_marker { 173 | background: rgb(60, 141, 188); 174 | } 175 | 176 | .pulse_holder .pulse_rays { 177 | margin: 0 auto; 178 | border-radius: 100px; 179 | position: relative; 180 | right: 24px; 181 | top: -24px; 182 | z-index: 10; 183 | background-color: transparent; 184 | opacity: 0.1; 185 | width: 64px; 186 | height: 64px; 187 | border: 2px solid rgb(128, 123, 122); 188 | -webkit-border-radius: 100px; 189 | -moz-border-radius: 100px; 190 | -o-border-radius: 100px; 191 | -ms-border-radius: 100px; 192 | border-radius: 100px; 193 | /* Giving Animation Function */ 194 | -webkit-animation: pulse 2s linear infinite; 195 | -moz-animation: pulse 2s linear infinite; 196 | border-image: initial; 197 | } 198 | 199 | .pulse_holder.connected .pulse_rays { 200 | border: 2px solid rgb(60, 141, 188); 201 | } 202 | 203 | .babies_container { 204 | width: auto; 205 | margin-right: auto; 206 | margin-left: auto; 207 | max-width: 520px; 208 | padding: 0 15px; 209 | } 210 | 211 | .babies_container.full_width { 212 | max-width: inherit; 213 | } 214 | 215 | .babies_container .endpoints pre { 216 | border-radius: 0px; 217 | } 218 | 219 | .pointer { 220 | cursor: pointer; 221 | } 222 | 223 | .scroll_to_top, 224 | .scroll_to_bottom { 225 | float: right; 226 | display: block; 227 | z-index: 1031; 228 | position: fixed; 229 | right: 5px; 230 | font-size: 24px; 231 | cursor: pointer; 232 | } 233 | 234 | .scroll_to_top { 235 | top: 5px; 236 | } 237 | 238 | .scroll_to_bottom { 239 | bottom: 0px; 240 | } 241 | 242 | .scroll_to_top .fa, 243 | .scroll_to_bottom .fa { 244 | font-size: 30px; 245 | } 246 | 247 | .help { 248 | z-index: 1035; 249 | position: fixed; 250 | display: none; 251 | font-size: 14px; 252 | } 253 | 254 | .help_back, .help_side-menu { 255 | z-index: 809; 256 | position: fixed; 257 | width: 100%; 258 | height: 100%; 259 | background-color: black; 260 | opacity: 0.3; 261 | top: 0px; 262 | } 263 | 264 | .help_side-menu { 265 | width: 150px; 266 | z-index: 1031; 267 | } 268 | 269 | .help.controls { 270 | left: 177px; 271 | margin-top: -34px; 272 | } 273 | 274 | .help.markers { 275 | left: 177px; 276 | margin-top: 8px; 277 | } 278 | 279 | .help.levels { 280 | left: 177px; 281 | margin-top: 24px; 282 | } 283 | 284 | .help.description { 285 | width: 250px; 286 | margin: auto; 287 | margin-top: 100px; 288 | position: relative; 289 | } 290 | 291 | .help.server_pulse { 292 | /* width: 250px; */ 293 | top: 3px; 294 | left: 177px; 295 | position: fixed; 296 | } 297 | 298 | .show-help .help.markers, 299 | .show-help .help.server_pulse, 300 | .show-help .help.description, 301 | .show-help .help.controls, 302 | .show-help .help.levels { 303 | display: block; 304 | } 305 | 306 | .settings { 307 | height: 200px 308 | } 309 | 310 | .setting { 311 | padding-bottom: 5px; 312 | } 313 | 314 | .flow.with-settings { 315 | bottom: 250px; 316 | } 317 | 318 | .help.with-settings { 319 | bottom: 200px; 320 | } 321 | 322 | .container.with-settings .scroll_to_top { 323 | bottom: 263px; 324 | } 325 | 326 | .log-search-field { 327 | width: 400px; 328 | } 329 | 330 | /****** 331 | * LTE Template 332 | */ 333 | 334 | .content-wrapper { 335 | background-color: white; 336 | } 337 | 338 | .skin-blue .sidebar-form input[type="text"].filled, .skin-blue .sidebar-form input[type="text"].filled + .input-group-btn .btn { 339 | background-color: #fff; 340 | color: #666; 341 | } 342 | 343 | .github_link { 344 | bottom: 12px; 345 | right: 10px; 346 | position: fixed; 347 | z-index: 1030; 348 | } 349 | 350 | .sidebar-menu > li.github-menu-link { 351 | bottom: 0px; 352 | position: absolute; 353 | width: 150px; 354 | } 355 | 356 | .main-sidebar { 357 | width: 150px; 358 | } 359 | 360 | .main-header > .navbar { 361 | margin-left: 150px; 362 | margin-right: 36px; 363 | } 364 | 365 | .main-header .logo { 366 | width: 150px; 367 | } 368 | 369 | .content-wrapper { 370 | margin-left: 150px; 371 | } 372 | 373 | .skin-blue .sidebar-menu > li.active-red > a { 374 | color: #fff; 375 | background: #1e282c; 376 | border-left-color: #dd4b39; 377 | } 378 | 379 | .skin-blue .sidebar-menu > li.active-orange > a { 380 | color: #fff; 381 | background: #1e282c; 382 | border-left-color: #f39c12; 383 | } 384 | 385 | .skin-blue .sidebar-menu > li.deactivated > a { 386 | text-decoration: line-through; 387 | } 388 | 389 | .log-list .callout { 390 | border-radius: 3px; 391 | margin: 0; 392 | padding: 0; 393 | margin-left: 50px; 394 | padding-left: 5px; 395 | } 396 | 397 | .log-list .callout.callout-info { 398 | background-color: white !important; 399 | color: black !important; 400 | } 401 | 402 | .main-header { 403 | display: none; 404 | } 405 | 406 | .searchable .main-header { 407 | display: block; 408 | } 409 | 410 | .chart-box { 411 | margin-left: 150px; 412 | bottom: 0px; 413 | position: fixed; 414 | right: 22px; 415 | } 416 | 417 | .debug-box { 418 | margin-left: 150px; 419 | bottom: 100px; 420 | position: fixed; 421 | right: 22px; 422 | background-color: #001a35; 423 | color: white; 424 | z-index: 2000; 425 | } 426 | 427 | .main-sidebar .logo { 428 | color: #fff; 429 | border-bottom: 0 solid transparent; 430 | width: 150px; 431 | -webkit-transition: width .3s ease-in-out; 432 | -o-transition: width .3s ease-in-out; 433 | transition: width .3s ease-in-out; 434 | display: block; 435 | height: 50px; 436 | font-size: 20px; 437 | line-height: 50px; 438 | text-align: center; 439 | width: 150px; 440 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 441 | padding: 0 15px; 442 | font-weight: 300; 443 | overflow: hidden; 444 | } 445 | 446 | .main-sidebar, .left-side { 447 | padding-top: 0px; 448 | } 449 | 450 | .tooltip { 451 | position: fixed; 452 | } 453 | 454 | .search-result { 455 | position: fixed; 456 | width: inherit; 457 | padding-right: 118px; 458 | padding-left: 10px; 459 | padding-bottom: 10px; 460 | margin-bottom: 10px; 461 | } 462 | 463 | .search-result .log-list { 464 | max-height: 305px; 465 | overflow: auto; 466 | } 467 | 468 | .search-result .callout { 469 | padding: 3px; 470 | } 471 | 472 | .search-result .log-list { 473 | font-size: 10px; 474 | } 475 | 476 | .callout-navy { 477 | background-color: #FFFFFF; 478 | color: black; 479 | border-color: #1A2226; 480 | border: 1px solid #374850; 481 | border-left: 5px solid; 482 | } 483 | 484 | #flow-slider-container { 485 | position: fixed; 486 | right: 8px; 487 | height: 100%; 488 | z-index: 99; 489 | } 490 | 491 | .with-slider { 492 | overflow: hidden; 493 | } 494 | 495 | #flow-slider { 496 | height: 100%; 497 | padding-top: 46px; 498 | padding-bottom: 46px; 499 | } 500 | 501 | .log-level-buttons .btn { 502 | font-size: 21px; 503 | padding: 5px 2px; 504 | } 505 | 506 | .vertical { 507 | /*writing-mode:tb-rl;*/ 508 | -webkit-transform: rotate(90deg); 509 | -moz-transform: rotate(90deg); 510 | -o-transform: rotate(90deg); 511 | -ms-transform: rotate(90deg); 512 | transform: rotate(90deg); 513 | white-space: nowrap; 514 | display: block; 515 | bottom: 0; 516 | /* width:20px; */ 517 | /* height:20px; */ 518 | } 519 | 520 | .btn-group.select-buttons .btn { 521 | font-size: 14px; 522 | padding: 5px 11px; 523 | } 524 | 525 | .select-next-prev-block { 526 | position: fixed; 527 | right: 31px; 528 | top: 4px; 529 | } 530 | 531 | .select-next-prev-block .fa { 532 | color: #ff851b; 533 | font-size: 19px; 534 | } 535 | 536 | .select-next-prev-block .fa:hover { 537 | color: #3C8DBC; 538 | } 539 | 540 | .searchable .select-next-prev-block { 541 | top: 54px; 542 | } 543 | 544 | .fade_in { 545 | animation: fadein 1s; 546 | -moz-animation: fadein 1s; /* Firefox */ 547 | -webkit-animation: fadein 1s; /* Safari and Chrome */ 548 | -o-animation: fadein 1s; /* Opera */ 549 | } 550 | 551 | .fade_out { 552 | animation: fadeout 2s; 553 | -moz-animation: fadeout 2s; /* Firefox */ 554 | -webkit-animation: fadeout 2s; /* Safari and Chrome */ 555 | -o-animation: fadeout 2s; /* Opera */ 556 | } 557 | 558 | @keyframes fadein { 559 | from { 560 | opacity: 0; 561 | } 562 | to { 563 | opacity: 1; 564 | } 565 | } 566 | 567 | @-moz-keyframes fadein { /* Firefox */ 568 | from { 569 | opacity: 0; 570 | } 571 | to { 572 | opacity: 1; 573 | } 574 | } 575 | 576 | @-webkit-keyframes fadein { /* Safari and Chrome */ 577 | from { 578 | opacity: 0; 579 | } 580 | to { 581 | opacity: 1; 582 | } 583 | } 584 | 585 | @-o-keyframes fadein { /* Opera */ 586 | from { 587 | opacity: 0; 588 | } 589 | to { 590 | opacity: 1; 591 | } 592 | } 593 | 594 | .babys_list { 595 | left: 10px; 596 | } 597 | 598 | code.version { 599 | float: right; 600 | font-size: small; 601 | position: relative; 602 | top: -37px; 603 | } 604 | 605 | .babies { 606 | padding-left: 17px; 607 | padding-right: 17px; 608 | } 609 | 610 | .babies .components { 611 | padding-left: 5px; 612 | font-size: small; 613 | font-weight: 500; 614 | color: #666666; 615 | } 616 | 617 | .components code.version { 618 | float: none; 619 | font-size: small; 620 | position: inherit; 621 | padding: 2px 4px; 622 | font-size: 90%; 623 | border-radius: 4px; 624 | } 625 | 626 | code.tag { 627 | font-size: small; 628 | color: #4d6bc7; 629 | } 630 | 631 | code.tag.url { 632 | position: absolute; 633 | margin-top: 26px; 634 | color: #bbbbbb; 635 | background-color: inherit; 636 | } 637 | 638 | code.place { 639 | color: #40c73c; 640 | } 641 | 642 | .babies_container .place { 643 | float: right; 644 | } 645 | 646 | @keyframes shake { 647 | 0% { 648 | transform: translateX(0); 649 | } 650 | 12.5% { 651 | transform: translateX(-8px) rotateY(-5deg) 652 | } 653 | 37.5% { 654 | transform: translateX(7px) rotateY(4deg) 655 | } 656 | 62.5% { 657 | transform: translateX(-5px) rotateY(-2deg) 658 | } 659 | 87.5% { 660 | transform: translateX(4px) rotateY(1deg) 661 | } 662 | 100% { 663 | transform: translateX(0) 664 | } 665 | } 666 | 667 | .shake { 668 | animation: shake 1500ms ease-in-out; 669 | } 670 | 671 | .last_updated { 672 | position: relative; 673 | float: right; 674 | font-size: small; 675 | color: #bbbbbb; 676 | top: -10px; 677 | } 678 | 679 | .connection_error { 680 | color: #c7254e; 681 | font-size: initial; 682 | float: left; 683 | position: relative; 684 | top: -14px; 685 | } 686 | 687 | .crop { 688 | overflow: hidden; 689 | white-space: nowrap; 690 | text-overflow: ellipsis; 691 | } 692 | 693 | .tag.crop { 694 | width: 500px; 695 | } 696 | 697 | .app_name.crop { 698 | width: 460px; 699 | } 700 | 701 | .nav > li.link > a { 702 | position: relative; 703 | display: block; 704 | padding: 3px; 705 | } 706 | 707 | .nav > li.link > a > img { 708 | max-width: 42px; 709 | } 710 | 711 | #camel-map { 712 | width: 100%; 713 | margin: 0; 714 | padding: 0; 715 | height: 100%; 716 | } 717 | 718 | .camel-map-page-header { 719 | position: absolute; 720 | width: 100%; 721 | padding: 0px 40px 0px 0; 722 | margin: 0; 723 | } 724 | 725 | .vis-network { 726 | border: 1px dashed #eeeeee; 727 | } 728 | 729 | html, body { 730 | height: 100%; 731 | margin: 0px; 732 | padding: 0px; 733 | } 734 | 735 | .full_height { 736 | height: 100%; 737 | } 738 | 739 | .camel-graph-form { 740 | padding-top: 78px; 741 | padding-bottom: 50px; 742 | } 743 | 744 | .camel-graph-tab { 745 | padding-top: 5px; 746 | display: none; 747 | padding-left: 69px; 748 | padding-right: 15px; 749 | } 750 | 751 | .camel-graph-tab.visible { 752 | display: block; 753 | } 754 | 755 | .camel-graph-tab-data { 756 | overflow: auto; 757 | } 758 | 759 | .endpoint-list { 760 | overflow: auto; 761 | height: 100%; 762 | bottom: 0; 763 | } 764 | 765 | .endpoint-list ol, ul { 766 | padding-left: 0px; 767 | } 768 | 769 | .endpoint-list-entry { 770 | cursor: pointer; 771 | padding-left: 5px; 772 | } 773 | 774 | .endpoint-list-entry:hover { 775 | background-color: #c4e3f3; 776 | } 777 | 778 | .endpoint-list-entry.selected { 779 | background-color: #c9dec9; 780 | border-left: 3px solid #9bb19a; 781 | margin-left: 0px; 782 | padding-left: 2px; 783 | } 784 | 785 | .endpoint-list-header { 786 | color: #9bb19a; 787 | } 788 | 789 | .humpway-graph-legend { 790 | position: fixed; 791 | padding-top: 20px; 792 | padding-left: 20px; 793 | z-index: 100; 794 | } 795 | 796 | .humpway-graph-legend ul { 797 | margin: 0px; 798 | padding: 0px; 799 | list-style-type: none; 800 | } 801 | 802 | .humpway-graph-legend ul li { 803 | margin: 0px; 804 | padding: 0px; 805 | padding-bottom: 5px; 806 | } 807 | 808 | .humpway-graph-legend .circle { 809 | border-radius: 50%; 810 | width: 20px; 811 | height: 20px; 812 | display: inline-block; 813 | background: green; 814 | } 815 | 816 | .humpway-graph-legend ul li.service.failed .circle{ 817 | background-color: #f0f0f0 !important; 818 | color: #b4b4b4 !important; 819 | } 820 | 821 | .humpway-graph-legend ul li.service.in_process .circle{ 822 | -webkit-animation: blinking 1s infinite; /* Safari 4+ */ 823 | -moz-animation: blinking 1s infinite; /* Fx 5+ */ 824 | -o-animation: blinking 1s infinite; /* Opera 12+ */ 825 | animation: blinking 1s infinite; /* IE 10+, Fx 29+ */ 826 | } 827 | 828 | @-webkit-keyframes blinking { 829 | 0%, 49% { 830 | /*background-color: rgb(117, 209, 63);*/ 831 | /*border: 3px solid #e50000;*/ 832 | } 833 | 50%, 100% { 834 | background-color: #acacac; 835 | /*border: 3px solid rgb(117, 209, 63);*/ 836 | } 837 | } 838 | 839 | .service-name { 840 | display: inline-block; 841 | padding-top: 0px; 842 | padding-left: 39px; 843 | } 844 | 845 | .camel-logo { 846 | padding-left: 110px; 847 | } 848 | 849 | .page-header.camel-map-page-header .camel-logo { 850 | padding-left: 0px; 851 | padding-top: 14px; 852 | position: absolute; 853 | } 854 | 855 | .page-header.camel-map-page-header .camel-logo img { 856 | max-height: 51px; 857 | } 858 | 859 | .page-header.camel-map-page-header h1 { 860 | margin-left: 57px; 861 | } 862 | 863 | .modal-body.scrollable { 864 | max-height: calc(100vh - 238px); 865 | overflow-y: auto; 866 | } 867 | 868 | .btn-flat { 869 | color: #333; 870 | background-color: #fff; 871 | border-color: #ccc; 872 | } 873 | 874 | .tabs-left { 875 | margin-top: 3rem; 876 | } 877 | 878 | .nav-tabs { 879 | float: left; 880 | border-bottom: 0; 881 | } 882 | 883 | .list-group { 884 | width: 100%; 885 | } 886 | 887 | .list-group .list-group-item { 888 | height: 42px; 889 | } 890 | 891 | .list-group .list-group-item h4, .list-group .list-group-item span { 892 | line-height: 11px; 893 | } 894 | 895 | .list-group-item.active, .list-group-item.active:focus, .list-group-item.active:hover { 896 | z-index: 2; 897 | color: #555; 898 | background-color: #e7e7e7; 899 | border: 1px solid #ddd; 900 | } -------------------------------------------------------------------------------- /public/css/skin-blue.min.css: -------------------------------------------------------------------------------- 1 | .skin-blue .main-header .navbar { 2 | background-color: white; 3 | } 4 | 5 | .skin-blue .main-header .navbar .nav > li > a { 6 | color: #fff 7 | } 8 | 9 | .skin-blue .main-header .navbar .nav > li > a:hover, .skin-blue .main-header .navbar .nav > li > a:active, .skin-blue .main-header .navbar .nav > li > a:focus, .skin-blue .main-header .navbar .nav .open > a, .skin-blue .main-header .navbar .nav .open > a:hover, .skin-blue .main-header .navbar .nav .open > a:focus, .skin-blue .main-header .navbar .nav > .active > a { 10 | background: rgba(0, 0, 0, 0.1); 11 | color: #f6f6f6 12 | } 13 | 14 | .skin-blue .main-header .navbar .sidebar-toggle { 15 | color: #fff 16 | } 17 | 18 | .skin-blue .main-header .navbar .sidebar-toggle:hover { 19 | color: #f6f6f6; 20 | background: rgba(0, 0, 0, 0.1) 21 | } 22 | 23 | .skin-blue .main-header .navbar .sidebar-toggle { 24 | color: #fff 25 | } 26 | 27 | .skin-blue .main-header .navbar .sidebar-toggle:hover { 28 | background-color: #367fa9 29 | } 30 | 31 | @media (max-width: 767px) { 32 | .skin-blue .main-header .navbar .dropdown-menu li.divider { 33 | background-color: rgba(255, 255, 255, 0.1) 34 | } 35 | 36 | .skin-blue .main-header .navbar .dropdown-menu li a { 37 | color: #fff 38 | } 39 | 40 | .skin-blue .main-header .navbar .dropdown-menu li a:hover { 41 | background: #367fa9 42 | } 43 | } 44 | 45 | .skin-blue .main-header .logo { 46 | /*background-color: #367fa9;*/ 47 | color: #fff; 48 | border-bottom: 0 solid transparent 49 | } 50 | 51 | .skin-blue .main-header .logo:hover { 52 | background-color: #357ca5 53 | } 54 | 55 | .skin-blue .main-header li.user-header { 56 | background-color: #3c8dbc 57 | } 58 | 59 | .skin-blue .content-header { 60 | background: transparent 61 | } 62 | 63 | .skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side { 64 | background-color: #222d32 65 | } 66 | 67 | .skin-blue .user-panel > .info, .skin-blue .user-panel > .info > a { 68 | color: #fff 69 | } 70 | 71 | .skin-blue .sidebar-menu > li.header { 72 | color: #4b646f; 73 | background: #1a2226 74 | } 75 | 76 | .skin-blue .sidebar-menu > li > a { 77 | border-left: 3px solid transparent 78 | } 79 | 80 | .skin-blue .sidebar-menu > li:hover > a, .skin-blue .sidebar-menu > li.active > a { 81 | color: #fff; 82 | background: #1e282c; 83 | border-left-color: #3c8dbc 84 | } 85 | 86 | .skin-blue .sidebar-menu > li > .treeview-menu { 87 | margin: 0 1px; 88 | background: #2c3b41 89 | } 90 | 91 | .skin-blue .sidebar a { 92 | color: #b8c7ce 93 | } 94 | 95 | .skin-blue .sidebar a:hover { 96 | text-decoration: none 97 | } 98 | 99 | .skin-blue .treeview-menu > li > a { 100 | color: #8aa4af 101 | } 102 | 103 | .skin-blue .treeview-menu > li.active > a, .skin-blue .treeview-menu > li > a:hover { 104 | color: #fff 105 | } 106 | 107 | .skin-blue .sidebar-form { 108 | border-radius: 3px; 109 | border: 1px solid #374850; 110 | margin: 10px 10px 111 | } 112 | 113 | .skin-blue .sidebar-form input[type="text"], .skin-blue .sidebar-form .btn { 114 | box-shadow: none; 115 | background-color: #374850; 116 | border: 1px solid transparent; 117 | height: 35px; 118 | -webkit-transition: all .3s ease-in-out; 119 | -o-transition: all .3s ease-in-out; 120 | transition: all .3s ease-in-out 121 | } 122 | 123 | .skin-blue .sidebar-form input[type="text"] { 124 | color: #666; 125 | border-top-left-radius: 2px; 126 | border-top-right-radius: 0; 127 | border-bottom-right-radius: 0; 128 | border-bottom-left-radius: 2px 129 | } 130 | 131 | .skin-blue .sidebar-form input[type="text"]:focus, .skin-blue .sidebar-form input[type="text"]:focus + .input-group-btn .btn { 132 | background-color: #fff; 133 | color: #666 134 | } 135 | 136 | .skin-blue .sidebar-form input[type="text"]:focus + .input-group-btn .btn { 137 | border-left-color: #fff 138 | } 139 | 140 | .skin-blue .sidebar-form .btn { 141 | color: #999; 142 | border-top-left-radius: 0; 143 | border-top-right-radius: 2px; 144 | border-bottom-right-radius: 2px; 145 | border-bottom-left-radius: 0 146 | } 147 | 148 | .skin-blue.layout-top-nav .main-header > .logo { 149 | background-color: #3c8dbc; 150 | color: #fff; 151 | border-bottom: 0 solid transparent 152 | } 153 | 154 | .skin-blue.layout-top-nav .main-header > .logo:hover { 155 | background-color: #3b8ab8 156 | } -------------------------------------------------------------------------------- /public/css/vis.min.css: -------------------------------------------------------------------------------- 1 | .vis .overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis [class*=span]{min-height:0;width:auto}div.vis-configuration{position:relative;display:block;float:left;font-size:12px}div.vis-configuration-wrapper{display:block;width:700px}div.vis-configuration-wrapper::after{clear:both;content:"";display:block}div.vis-configuration.vis-config-option-container{display:block;width:495px;background-color:#fff;border:2px solid #f7f8fa;border-radius:4px;margin-top:20px;left:10px;padding-left:5px}div.vis-configuration.vis-config-button{display:block;width:495px;height:25px;vertical-align:middle;line-height:25px;background-color:#f7f8fa;border:2px solid #ceced0;border-radius:4px;margin-top:20px;left:10px;padding-left:5px;cursor:pointer;margin-bottom:30px}div.vis-configuration.vis-config-button.hover{background-color:#4588e6;border:2px solid #214373;color:#fff}div.vis-configuration.vis-config-item{display:block;float:left;width:495px;height:25px;vertical-align:middle;line-height:25px}div.vis-configuration.vis-config-item.vis-config-s2{left:10px;background-color:#f7f8fa;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s3{left:20px;background-color:#e4e9f0;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s4{left:30px;background-color:#cfd8e6;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-header{font-size:18px;font-weight:700}div.vis-configuration.vis-config-label{width:120px;height:25px;line-height:25px}div.vis-configuration.vis-config-label.vis-config-s3{width:110px}div.vis-configuration.vis-config-label.vis-config-s4{width:100px}div.vis-configuration.vis-config-colorBlock{top:1px;width:30px;height:19px;border:1px solid #444;border-radius:2px;padding:0;margin:0;cursor:pointer}input.vis-configuration.vis-config-checkbox{left:-5px}input.vis-configuration.vis-config-rangeinput{position:relative;top:-5px;width:60px;padding:1px;margin:0;pointer-events:none}input.vis-configuration.vis-config-range{-webkit-appearance:none;border:0 solid #fff;background-color:rgba(0,0,0,0);width:300px;height:20px}input.vis-configuration.vis-config-range::-webkit-slider-runnable-track{width:300px;height:5px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-webkit-slider-thumb{-webkit-appearance:none;border:1px solid #14334b;height:17px;width:17px;border-radius:50%;background:#3876c2;background:-moz-linear-gradient(top,#3876c2 0,#385380 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3876c2),color-stop(100%,#385380));background:-webkit-linear-gradient(top,#3876c2 0,#385380 100%);background:-o-linear-gradient(top,#3876c2 0,#385380 100%);background:-ms-linear-gradient(top,#3876c2 0,#385380 100%);background:linear-gradient(to bottom,#3876c2 0,#385380 100%);box-shadow:#111927 0 0 1px 0;margin-top:-7px}input.vis-configuration.vis-config-range:focus{outline:0}input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track{background:#9d9d9d;background:-moz-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9d9d9d),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-o-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:linear-gradient(to bottom,#9d9d9d 0,#c8c8c8 99%)}input.vis-configuration.vis-config-range::-moz-range-track{width:300px;height:10px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}input.vis-configuration.vis-config-range::-ms-track{width:300px;height:5px;background:0 0;border-color:transparent;border-width:6px 0;color:transparent}input.vis-configuration.vis-config-range::-ms-fill-lower{background:#777;border-radius:10px}input.vis-configuration.vis-config-range::-ms-fill-upper{background:#ddd;border-radius:10px}input.vis-configuration.vis-config-range::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:focus::-ms-fill-lower{background:#888}input.vis-configuration.vis-config-range:focus::-ms-fill-upper{background:#ccc}.vis-configuration-popup{position:absolute;background:rgba(57,76,89,.85);border:2px solid #f2faff;line-height:30px;height:30px;width:150px;text-align:center;color:#fff;font-size:14px;border-radius:4px;-webkit-transition:opacity .3s ease-in-out;-moz-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.vis-configuration-popup:after,.vis-configuration-popup:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.vis-configuration-popup:after{border-color:rgba(136,183,213,0);border-left-color:rgba(57,76,89,.85);border-width:8px;margin-top:-8px}.vis-configuration-popup:before{border-color:rgba(194,225,245,0);border-left-color:#f2faff;border-width:12px;margin-top:-12px}div.vis-tooltip{position:absolute;visibility:hidden;padding:5px;white-space:nowrap;font-family:verdana;font-size:14px;color:#000;background-color:#f5f4ed;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #808074;box-shadow:3px 3px 10px rgba(0,0,0,.2);pointer-events:none;z-index:5}div.vis-color-picker{position:absolute;top:0;left:30px;margin-top:-140px;margin-left:30px;width:310px;height:444px;z-index:1;padding:10px;border-radius:15px;background-color:#fff;display:none;box-shadow:rgba(0,0,0,.5) 0 0 10px 0}div.vis-color-picker div.vis-arrow{position:absolute;top:147px;left:5px}div.vis-color-picker div.vis-arrow::after,div.vis-color-picker div.vis-arrow::before{right:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.vis-color-picker div.vis-arrow:after{border-color:rgba(255,255,255,0);border-right-color:#fff;border-width:30px;margin-top:-30px}div.vis-color-picker div.vis-color{position:absolute;width:289px;height:289px;cursor:pointer}div.vis-color-picker div.vis-brightness{position:absolute;top:313px}div.vis-color-picker div.vis-opacity{position:absolute;top:350px}div.vis-color-picker div.vis-selector{position:absolute;top:137px;left:137px;width:15px;height:15px;border-radius:15px;border:1px solid #fff;background:#4c4c4c;background:-moz-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#4c4c4c),color-stop(12%,#595959),color-stop(25%,#666),color-stop(39%,#474747),color-stop(50%,#2c2c2c),color-stop(51%,#000),color-stop(60%,#111),color-stop(76%,#2b2b2b),color-stop(91%,#1c1c1c),color-stop(100%,#131313));background:-webkit-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-o-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-ms-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:linear-gradient(to bottom,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%)}div.vis-color-picker div.vis-new-color{position:absolute;width:140px;height:20px;border:1px solid rgba(0,0,0,.1);border-radius:5px;top:380px;left:159px;text-align:right;padding-right:2px;font-size:10px;color:rgba(0,0,0,.4);vertical-align:middle;line-height:20px}div.vis-color-picker div.vis-initial-color{position:absolute;width:140px;height:20px;border:1px solid rgba(0,0,0,.1);border-radius:5px;top:380px;left:10px;text-align:left;padding-left:2px;font-size:10px;color:rgba(0,0,0,.4);vertical-align:middle;line-height:20px}div.vis-color-picker div.vis-label{position:absolute;width:300px;left:10px}div.vis-color-picker div.vis-label.vis-brightness{top:300px}div.vis-color-picker div.vis-label.vis-opacity{top:338px}div.vis-color-picker div.vis-button{position:absolute;width:68px;height:25px;border-radius:10px;vertical-align:middle;text-align:center;line-height:25px;top:410px;border:2px solid #d9d9d9;background-color:#f7f7f7;cursor:pointer}div.vis-color-picker div.vis-button.vis-cancel{left:5px}div.vis-color-picker div.vis-button.vis-load{left:82px}div.vis-color-picker div.vis-button.vis-apply{left:159px}div.vis-color-picker div.vis-button.vis-save{left:236px}div.vis-color-picker input.vis-range{width:290px;height:20px}div.vis-network div.vis-manipulation{box-sizing:content-box;border-width:0;border-bottom:1px;border-style:solid;border-color:#d6d9d8;background:#fff;background:-moz-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(48%,#fcfcfc),color-stop(50%,#fafafa),color-stop(100%,#fcfcfc));background:-webkit-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-o-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-ms-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:linear-gradient(to bottom,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);padding-top:4px;position:absolute;left:0;top:0;width:100%;height:28px}div.vis-network div.vis-edit-mode{position:absolute;left:0;top:5px;height:30px}div.vis-network div.vis-close{position:absolute;right:0;top:0;width:30px;height:30px;background-position:20px 3px;background-repeat:no-repeat;background-image:url(img/network/cross.png);cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-close:hover{opacity:.6}div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{float:left;font-family:verdana;font-size:12px;-moz-border-radius:15px;border-radius:15px;display:inline-block;background-position:0 0;background-repeat:no-repeat;height:24px;margin-left:10px;cursor:pointer;padding:0 8px 0 8px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-manipulation div.vis-button:hover{box-shadow:1px 1px 8px rgba(0,0,0,.2)}div.vis-network div.vis-manipulation div.vis-button:active{box-shadow:1px 1px 8px rgba(0,0,0,.5)}div.vis-network div.vis-manipulation div.vis-button.vis-back{background-image:url(img/network/backIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-none:hover{box-shadow:1px 1px 8px transparent;cursor:default}div.vis-network div.vis-manipulation div.vis-button.vis-none:active{box-shadow:1px 1px 8px transparent}div.vis-network div.vis-manipulation div.vis-button.vis-none{padding:0}div.vis-network div.vis-manipulation div.notification{margin:2px;font-weight:700}div.vis-network div.vis-manipulation div.vis-button.vis-add{background-image:url(img/network/addNodeIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit,div.vis-network div.vis-manipulation div.vis-button.vis-edit{background-image:url(img/network/editIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit.vis-edit-mode{background-color:#fcfcfc;border:1px solid #ccc}div.vis-network div.vis-manipulation div.vis-button.vis-connect{background-image:url(img/network/connectIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-delete{background-image:url(img/network/deleteIcon.png)}div.vis-network div.vis-edit-mode div.vis-label,div.vis-network div.vis-manipulation div.vis-label{margin:0 0 0 23px;line-height:25px}div.vis-network div.vis-manipulation div.vis-separator-line{float:left;display:inline-block;width:1px;height:21px;background-color:#bdbdbd;margin:0 7px 0 15px}div.vis-network div.vis-navigation div.vis-button{width:34px;height:34px;-moz-border-radius:17px;border-radius:17px;position:absolute;display:inline-block;background-position:2px 2px;background-repeat:no-repeat;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-navigation div.vis-button:hover{box-shadow:0 0 3px 3px rgba(56,207,21,.3)}div.vis-network div.vis-navigation div.vis-button:active{box-shadow:0 0 1px 3px rgba(56,207,21,.95)}div.vis-network div.vis-navigation div.vis-button.vis-up{background-image:url(img/network/upArrow.png);bottom:50px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-down{background-image:url(img/network/downArrow.png);bottom:10px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-left{background-image:url(img/network/leftArrow.png);bottom:10px;left:15px}div.vis-network div.vis-navigation div.vis-button.vis-right{background-image:url(img/network/rightArrow.png);bottom:10px;left:95px}div.vis-network div.vis-navigation div.vis-button.vis-zoomIn{background-image:url(img/network/plus.png);bottom:10px;right:15px}div.vis-network div.vis-navigation div.vis-button.vis-zoomOut{background-image:url(img/network/minus.png);bottom:10px;right:55px}div.vis-network div.vis-navigation div.vis-button.vis-zoomExtends{background-image:url(img/network/zoomExtends.png);bottom:50px;right:15px}.vis-current-time{background-color:#ff7f6e;width:2px;z-index:1;pointer-events:none}.vis-rolling-mode-btn{height:40px;width:40px;position:absolute;top:7px;right:20px;border-radius:50%;font-size:28px;cursor:pointer;opacity:.8;color:#fff;font-weight:700;text-align:center;background:#3876c2}.vis-rolling-mode-btn:before{content:"\26F6"}.vis-rolling-mode-btn:hover{opacity:1}.vis-custom-time{background-color:#6e94ff;width:2px;cursor:move;z-index:1}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-horizontal{position:absolute;width:100%;height:0;border-bottom:1px solid}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-minor{border-color:#e5e5e5}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-major{border-color:#bfbfbf}.vis-data-axis .vis-y-axis.vis-major{width:100%;position:absolute;color:#4d4d4d;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-major.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-minor{position:absolute;width:100%;color:#bebebe;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-minor.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title{position:absolute;color:#4d4d4d;white-space:nowrap;bottom:20px;text-align:center}.vis-data-axis .vis-y-axis.vis-title.vis-measure{padding:0;margin:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title.vis-left{bottom:0;-webkit-transform-origin:left top;-moz-transform-origin:left top;-ms-transform-origin:left top;-o-transform-origin:left top;transform-origin:left bottom;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.vis-data-axis .vis-y-axis.vis-title.vis-right{bottom:0;-webkit-transform-origin:right bottom;-moz-transform-origin:right bottom;-ms-transform-origin:right bottom;-o-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.vis-legend{background-color:rgba(247,252,255,.65);padding:5px;border:1px solid #b3b3b3;box-shadow:2px 2px 10px rgba(154,154,154,.55)}.vis-legend-text{white-space:nowrap;display:inline-block}.vis-item{position:absolute;color:#1a1a1a;border-color:#97b0f8;border-width:1px;background-color:#d5ddf6;display:inline-block;z-index:1}.vis-item.vis-selected{border-color:#ffc200;background-color:#fff785;z-index:2}.vis-editable.vis-selected{cursor:move}.vis-item.vis-point.vis-selected{background-color:#fff785}.vis-item.vis-box{text-align:center;border-style:solid;border-radius:2px}.vis-item.vis-point{background:0 0}.vis-item.vis-dot{position:absolute;padding:0;border-width:4px;border-style:solid;border-radius:4px}.vis-item.vis-range{border-style:solid;border-radius:2px;box-sizing:border-box}.vis-item.vis-background{border:none;background-color:rgba(213,221,246,.4);box-sizing:border-box;padding:0;margin:0}.vis-item .vis-item-overflow{position:relative;width:100%;height:100%;padding:0;margin:0;overflow:hidden}.vis-item-visible-frame{white-space:nowrap}.vis-item.vis-range .vis-item-content{position:relative;display:inline-block}.vis-item.vis-background .vis-item-content{position:absolute;display:inline-block}.vis-item.vis-line{padding:0;position:absolute;width:0;border-left-width:1px;border-left-style:solid}.vis-item .vis-item-content{white-space:nowrap;box-sizing:border-box;padding:5px}.vis-item .vis-onUpdateTime-tooltip{position:absolute;background:#4f81bd;color:#fff;width:200px;text-align:center;white-space:nowrap;padding:5px;border-radius:1px;transition:.4s;-o-transition:.4s;-moz-transition:.4s;-webkit-transition:.4s}.vis-item .vis-delete,.vis-item .vis-delete-rtl{position:absolute;top:0;width:24px;height:24px;box-sizing:border-box;padding:0 5px;cursor:pointer;-webkit-transition:background .2s linear;-moz-transition:background .2s linear;-ms-transition:background .2s linear;-o-transition:background .2s linear;transition:background .2s linear}.vis-item .vis-delete{right:-24px}.vis-item .vis-delete-rtl{left:-24px}.vis-item .vis-delete-rtl:after,.vis-item .vis-delete:after{content:"\00D7";color:red;font-family:arial,sans-serif;font-size:22px;font-weight:700;-webkit-transition:color .2s linear;-moz-transition:color .2s linear;-ms-transition:color .2s linear;-o-transition:color .2s linear;transition:color .2s linear}.vis-item .vis-delete-rtl:hover,.vis-item .vis-delete:hover{background:red}.vis-item .vis-delete-rtl:hover:after,.vis-item .vis-delete:hover:after{color:#fff}.vis-item .vis-drag-center{position:absolute;width:100%;height:100%;top:0;left:0;cursor:move}.vis-item.vis-range .vis-drag-left{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;left:-4px;cursor:w-resize}.vis-item.vis-range .vis-drag-right{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;right:-4px;cursor:e-resize}.vis-range.vis-item.vis-readonly .vis-drag-left,.vis-range.vis-item.vis-readonly .vis-drag-right{cursor:auto}.vis-itemset{position:relative;padding:0;margin:0;box-sizing:border-box}.vis-itemset .vis-background,.vis-itemset .vis-foreground{position:absolute;width:100%;height:100%;overflow:visible}.vis-axis{position:absolute;width:100%;height:0;left:0;z-index:1}.vis-foreground .vis-group{position:relative;box-sizing:border-box;border-bottom:1px solid #bfbfbf}.vis-foreground .vis-group:last-child{border-bottom:none}.vis-nesting-group{cursor:pointer}.vis-nested-group{background:#f5f5f5}.vis-label.vis-nesting-group.expanded:before{content:"\25BC"}.vis-label.vis-nesting-group.collapsed-rtl:before{content:"\25C0"}.vis-label.vis-nesting-group.collapsed:before{content:"\25B6"}.vis-overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-labelset{position:relative;overflow:hidden;box-sizing:border-box}.vis-labelset .vis-label{position:relative;left:0;top:0;width:100%;color:#4d4d4d;box-sizing:border-box}.vis-labelset .vis-label{border-bottom:1px solid #bfbfbf}.vis-labelset .vis-label.draggable{cursor:pointer}.vis-labelset .vis-label:last-child{border-bottom:none}.vis-labelset .vis-label .vis-inner{display:inline-block;padding:5px}.vis-labelset .vis-label .vis-inner.vis-hidden{padding:0}.vis-panel{position:absolute;padding:0;margin:0;box-sizing:border-box}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right,.vis-panel.vis-top{border:1px #bfbfbf}.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right{border-top-style:solid;border-bottom-style:solid;overflow:hidden}.vis-left.vis-panel.vis-vertical-scroll,.vis-right.vis-panel.vis-vertical-scroll{height:100%;overflow-x:hidden;overflow-y:scroll}.vis-left.vis-panel.vis-vertical-scroll{direction:rtl}.vis-left.vis-panel.vis-vertical-scroll .vis-content{direction:ltr}.vis-right.vis-panel.vis-vertical-scroll{direction:ltr}.vis-right.vis-panel.vis-vertical-scroll .vis-content{direction:rtl}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-top{border-left-style:solid;border-right-style:solid}.vis-background{overflow:hidden}.vis-panel>.vis-content{position:relative}.vis-panel .vis-shadow{position:absolute;width:100%;height:1px;box-shadow:0 0 10px rgba(0,0,0,.8)}.vis-panel .vis-shadow.vis-top{top:-1px;left:0}.vis-panel .vis-shadow.vis-bottom{bottom:-1px;left:0}.vis-graph-group0{fill:#4f81bd;fill-opacity:0;stroke-width:2px;stroke:#4f81bd}.vis-graph-group1{fill:#f79646;fill-opacity:0;stroke-width:2px;stroke:#f79646}.vis-graph-group2{fill:#8c51cf;fill-opacity:0;stroke-width:2px;stroke:#8c51cf}.vis-graph-group3{fill:#75c841;fill-opacity:0;stroke-width:2px;stroke:#75c841}.vis-graph-group4{fill:#ff0100;fill-opacity:0;stroke-width:2px;stroke:#ff0100}.vis-graph-group5{fill:#37d8e6;fill-opacity:0;stroke-width:2px;stroke:#37d8e6}.vis-graph-group6{fill:#042662;fill-opacity:0;stroke-width:2px;stroke:#042662}.vis-graph-group7{fill:#00ff26;fill-opacity:0;stroke-width:2px;stroke:#00ff26}.vis-graph-group8{fill:#f0f;fill-opacity:0;stroke-width:2px;stroke:#f0f}.vis-graph-group9{fill:#8f3938;fill-opacity:0;stroke-width:2px;stroke:#8f3938}.vis-timeline .vis-fill{fill-opacity:.1;stroke:none}.vis-timeline .vis-bar{fill-opacity:.5;stroke-width:1px}.vis-timeline .vis-point{stroke-width:2px;fill-opacity:1}.vis-timeline .vis-legend-background{stroke-width:1px;fill-opacity:.9;fill:#fff;stroke:#c2c2c2}.vis-timeline .vis-outline{stroke-width:1px;fill-opacity:1;fill:#fff;stroke:#e5e5e5}.vis-timeline .vis-icon-fill{fill-opacity:.3;stroke:none}.vis-time-axis{position:relative;overflow:hidden}.vis-time-axis.vis-foreground{top:0;left:0;width:100%}.vis-time-axis.vis-background{position:absolute;top:0;left:0;width:100%;height:100%}.vis-time-axis .vis-text{position:absolute;color:#4d4d4d;padding:3px;overflow:hidden;box-sizing:border-box;white-space:nowrap}.vis-time-axis .vis-text.vis-measure{position:absolute;padding-left:0;padding-right:0;margin-left:0;margin-right:0;visibility:hidden}.vis-time-axis .vis-grid.vis-vertical{position:absolute;border-left:1px solid}.vis-time-axis .vis-grid.vis-vertical-rtl{position:absolute;border-right:1px solid}.vis-time-axis .vis-grid.vis-minor{border-color:#e5e5e5}.vis-time-axis .vis-grid.vis-major{border-color:#bfbfbf}.vis-timeline{position:relative;border:1px solid #bfbfbf;overflow:hidden;padding:0;margin:0;box-sizing:border-box} -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avvero/camel-graph/f6bf8c9262c3111ae242cd8a75241c8d8f65049a/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avvero/camel-graph/f6bf8c9262c3111ae242cd8a75241c8d8f65049a/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avvero/camel-graph/f6bf8c9262c3111ae242cd8a75241c8d8f65049a/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avvero/camel-graph/f6bf8c9262c3111ae242cd8a75241c8d8f65049a/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avvero/camel-graph/f6bf8c9262c3111ae242cd8a75241c8d8f65049a/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avvero/camel-graph/f6bf8c9262c3111ae242cd8a75241c8d8f65049a/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/img/camel-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avvero/camel-graph/f6bf8c9262c3111ae242cd8a75241c8d8f65049a/public/img/camel-graph.png -------------------------------------------------------------------------------- /public/img/grafana_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /public/img/graphite-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avvero/camel-graph/f6bf8c9262c3111ae242cd8a75241c8d8f65049a/public/img/graphite-icon-57x57.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Camel-graph 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | -------------------------------------------------------------------------------- /public/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "camel-graph", 3 | "version": "1.0" 4 | } 5 | -------------------------------------------------------------------------------- /public/js/app.js: -------------------------------------------------------------------------------- 1 | angular.module("flow", [ 2 | 'ngRoute', 3 | 'ui.router', 4 | 'ngSanitize', 5 | 'ui.bootstrap', 6 | 'relativeDate' 7 | ]) 8 | angular.module("flow").constant('constants', { 9 | version: "1.0.2" 10 | }) 11 | // configure our routes 12 | angular.module("flow").config(function ($routeProvider, $stateProvider, $urlRouterProvider) { 13 | 14 | $urlRouterProvider.otherwise("/") 15 | 16 | $stateProvider 17 | .state('index', { 18 | url: "/", 19 | views: { 20 | "single": { 21 | templateUrl: 'views/environments.html', 22 | controller: placesController, 23 | resolve: placesController.resolve 24 | } 25 | } 26 | }) 27 | .state('component', { 28 | url: "/listen/:component", 29 | views: { 30 | "single": { 31 | templateUrl: 'views/environment.html', 32 | controller: placeController, 33 | resolve: placesController.resolve 34 | } 35 | } 36 | }) 37 | }) 38 | angular.module("flow").run(function ($rootScope) { 39 | 40 | }) 41 | 42 | angular.module("flow").controller('mainController', function ($scope) { 43 | 44 | }) 45 | 46 | angular.module("flow").filter('toArray', function() { return function(obj) { 47 | if (!(obj instanceof Object)) return obj; 48 | var arr = []; 49 | for (var key in obj) { 50 | arr.push({ key: key, value: obj[key] }); 51 | } 52 | return arr; 53 | }}); -------------------------------------------------------------------------------- /public/js/controllers/environment.js: -------------------------------------------------------------------------------- 1 | function placeController($scope, data, $timeout, $http, $stateParams, $location, $anchorScroll, $uibModal, utils) { 2 | $scope.$stateParams = $stateParams 3 | $scope.showSearchBox = true 4 | $scope.selected = null 5 | $scope.connection = {} 6 | $scope.shaked = null 7 | $scope.$on('$destroy', function () { 8 | $scope.isDestroed = true 9 | }); 10 | 11 | $scope.dataUpdateIntervalSeconds = 60 12 | 13 | $scope.merge = function (oldData, newData) { 14 | var result = {} 15 | result.name = newData.name 16 | result.lastUpdated = newData.lastUpdated 17 | result.serviceMap = {} 18 | 19 | //merging 20 | for (var serviceName in newData.serviceMap) { 21 | if (newData.serviceMap.hasOwnProperty(serviceName)) { 22 | if (!result.serviceMap[serviceName]) { 23 | result.serviceMap[serviceName] = {} 24 | } 25 | if (oldData.serviceMap && oldData.serviceMap.hasOwnProperty(serviceName)) { 26 | result.serviceMap[serviceName].error = newData.serviceMap[serviceName].error 27 | result.serviceMap[serviceName].lastUpdated = newData.serviceMap[serviceName].lastUpdated 28 | // merge 29 | } else { 30 | result.serviceMap[serviceName] = newData.serviceMap[serviceName] 31 | } 32 | } 33 | } 34 | 35 | // flatting services 36 | result.services = [] 37 | for (var serviceName in result.serviceMap) { 38 | if (result.serviceMap.hasOwnProperty(serviceName)) { 39 | result.services.push(result.serviceMap[serviceName]) 40 | } 41 | } 42 | // flatting routes 43 | for (var i = 0; i < result.services.length; i++) { 44 | var service = result.services[i] 45 | service.routes = [] 46 | for (var routeEntry in service.routeMap) { 47 | if (service.routeMap.hasOwnProperty(routeEntry)) { 48 | service.routes.push(service.routeMap[routeEntry]) 49 | } 50 | } 51 | } 52 | $scope.connection.lastUpdated = result.lastUpdated 53 | return result 54 | } 55 | 56 | $scope.data = $scope.merge({}, data) 57 | utils.processSchema($scope.data) 58 | $scope.graph = utils.buildGraphFromSchema($scope.data) 59 | 60 | $scope.listen = function (delay) { 61 | $timeout(function () { 62 | $http({ 63 | method: 'GET', 64 | url: '/data?env=' + $stateParams.component + "&t=" + new Date().getTime(), 65 | headers: {'Content-Type': 'application/json;charset=UTF-8'} 66 | }) 67 | .success(function (newData) { 68 | console.info("Has come new data: " + newData) 69 | $scope.connection.error = null 70 | var data = $scope.merge({}, newData) 71 | utils.processSchema(data) 72 | utils.updateGraph($scope.graph, data) 73 | }) 74 | .error(function (error, error2, error3) { 75 | $scope.connection.error = "Connection with server is lost" 76 | }); 77 | if (!!$scope.isDestroed) return 78 | $scope.listen($scope.dataUpdateIntervalSeconds * 1000) 79 | }, delay, true); 80 | } 81 | $scope.listen($scope.dataUpdateIntervalSeconds * 1000) 82 | 83 | $scope.drawGraph = function (nodes, edges) { 84 | console.info("Start draw") 85 | var data = { 86 | nodes: nodes, 87 | edges: edges 88 | }; 89 | var options = { 90 | nodes: { 91 | // shadow: true, 92 | shape: 'dot', 93 | size: 12, 94 | font: { 95 | size: 20 96 | } 97 | }, 98 | edges: { 99 | // shadow: true, 100 | smooth: true, 101 | arrows: {to: true}, 102 | scaling: { 103 | customScalingFunction: function (min,max,total,value) { 104 | return value/total; 105 | }, 106 | min:1, 107 | max: 10 108 | }, 109 | arrowStrikethrough: false, 110 | font: {size: 8, color: 'grey', face: 'arial'} 111 | } 112 | }; 113 | var container = document.getElementById('camel-map'); 114 | var graphNetwork = new vis.Network(container, data, options); 115 | graphNetwork.on("click", function (params) { 116 | if (params.edges.length > 0 || params.nodes.length > 0 ) { 117 | var data = { 118 | nodes: nodes.get(params.nodes), 119 | edges: edges.get(params.edges) 120 | } 121 | //prepare 122 | if (data.nodes.length > 0) { 123 | data.endpoint = data.nodes[0].label 124 | } else { 125 | data.route = data.edges[0].route 126 | } 127 | $scope.$apply(function () { 128 | $scope.selectGraphElement(data) 129 | }); 130 | } 131 | }); 132 | 133 | if (false) { 134 | var updateFrameVar = setInterval(function() { updateFrameTimer(); }, 60); 135 | function updateFrameTimer() { 136 | graphNetwork.redraw(); 137 | currentRadius += 0.05; 138 | } 139 | var currentRadius = 0; 140 | graphNetwork.on("beforeDrawing", function(ctx) { 141 | var inode; 142 | var nodePosition = graphNetwork.getPositions(); 143 | var arrayLength = nodes.length; 144 | for (inode = 0; inode < arrayLength; inode++) { 145 | var node = nodes.get(inode) 146 | if (node && node.strike) { 147 | ctx.strokeStyle = node.color; 148 | ctx.fillStyle = '#fffbfb'; 149 | 150 | var radius = Math.abs(50 * Math.sin(currentRadius + inode / 50.0)); 151 | ctx.circle(nodePosition[node.id].x, nodePosition[node.id].y, radius); 152 | ctx.fill(); 153 | ctx.stroke(); 154 | } 155 | } 156 | }); 157 | } 158 | console.info("Finish draw") 159 | return graphNetwork 160 | } 161 | $scope.graphNetwork = $scope.drawGraph($scope.graph.nodesDataSet, $scope.graph.edgesDataSet) 162 | 163 | // ----- 164 | // ----- VIEW 165 | // ----- 166 | $scope.goToPlaces = function () { 167 | $location.path('#') 168 | } 169 | 170 | //Select who must be selected 171 | if ($location.search().select) { 172 | if (!$scope.data) return 173 | if (!$scope.data.environments) return 174 | 175 | 176 | for (var i = 0; i < $scope.info.app.components.length; i++) { 177 | var url = window.decodeURIComponent($location.search().select) 178 | if ($scope.info.app.components[i].url == url) { 179 | // $scope.selected = $scope.babies[i] 180 | $anchorScroll(); 181 | $scope.shaked = $scope.info.app.components[i] 182 | $location.hash('component_' + url); 183 | break 184 | } 185 | } 186 | } 187 | 188 | $scope.selectedTab = 'graph' 189 | $scope.setTab = function (v) { 190 | $scope.selectedTab = v 191 | } 192 | $scope.showHideSearchBox = function() { 193 | $scope.showSearchBox = !$scope.showSearchBox 194 | if ($scope.showSearchBox) { 195 | $scope.setTab('graph') 196 | } 197 | } 198 | 199 | //Selected graph endpoint 200 | $scope.endpointSearchValue = '' 201 | $scope.selectedGraphEndpoint = null 202 | $scope.selectGraphEndpoint = function (entry) { 203 | $scope.selectedGraphEndpoint = $scope.selectedGraphEndpoint == entry ? null : entry 204 | } 205 | $scope.$watch('selectedGraphEndpoint', function (newValue, oldValue) { 206 | var options = { 207 | // scale: scale, 208 | // offset: {x:offsetx,y:offsety}, 209 | animation: { 210 | duration: 1000, 211 | easingFunction: 'easeOutQuart' 212 | } 213 | }; 214 | 215 | if (newValue) { 216 | var id = $scope.graph.endpoints[newValue] 217 | $scope.graph.nodesDataSet.update([{id: id, size: 20}]); 218 | $scope.graphNetwork.focus(id, options); 219 | } else { 220 | // fly away 221 | // $scope.graphNetwork.fit({animation: options}); 222 | } 223 | if (oldValue) { 224 | var id = $scope.graph.endpoints[oldValue] 225 | $scope.graph.nodesDataSet.update([{id: id, size: 10}]); 226 | } 227 | }); 228 | 229 | //Selected graph element 230 | $scope.selectedGraphElement = null 231 | $scope.selectGraphElement = function (entry) { 232 | console.info(entry) 233 | if (entry.endpoint) { 234 | $scope.selectGraphEndpoint(entry.endpoint) 235 | } else { 236 | $scope.selectGraphEndpoint(null) 237 | } 238 | $scope.selectedGraphElement = $scope.selectedGraphElement == entry ? null : entry 239 | $scope.showOptionsDialog(entry) 240 | } 241 | 242 | $scope.showOptionsDialog = function (data) { 243 | var options = {} 244 | var modalInstance = $uibModal.open({ 245 | templateUrl: 'views/options.html', 246 | controller: optionsDialogController, 247 | size: "lg", 248 | resolve: { 249 | data: function ($q, $http) { 250 | var deferred = $q.defer(); 251 | deferred.resolve(data) 252 | return deferred.promise; 253 | } 254 | } 255 | }); 256 | modalInstance.result.then(function (params) {}, function () {}); 257 | } 258 | } -------------------------------------------------------------------------------- /public/js/controllers/environments.js: -------------------------------------------------------------------------------- 1 | function placesController($scope, data, $timeout, $http) { 2 | $scope.data = data 3 | } 4 | 5 | placesController.resolve = { 6 | data: function ($q, $http, $stateParams) { 7 | var deferred = $q.defer(); 8 | 9 | $http({ 10 | method: 'GET', 11 | url: '/data?env=' + $stateParams.component + "&t=" + new Date().getTime(), 12 | headers: {'Content-Type': 'application/json;charset=UTF-8'} 13 | }) 14 | .success(function (data) { 15 | deferred.resolve(data) 16 | }) 17 | .error(function (data) { 18 | deferred.reject("error value"); 19 | }); 20 | 21 | return deferred.promise; 22 | } 23 | } -------------------------------------------------------------------------------- /public/js/controllers/optionsDialog.js: -------------------------------------------------------------------------------- 1 | function optionsDialogController(data, $scope, $uibModalInstance) { 2 | $scope.data = data 3 | $scope.ok = function () { 4 | $uibModalInstance.close(data); 5 | } 6 | } -------------------------------------------------------------------------------- /public/js/providers/local-timeout.js: -------------------------------------------------------------------------------- 1 | angular.module('flow').provider('localTimeout', function LocalTimeoutProvider() { 2 | 3 | this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', 4 | function($rootScope, $browser, $q, $exceptionHandler) { 5 | var deferreds = {}; 6 | 7 | function isDefined(value){return typeof value !== 'undefined';} 8 | 9 | /** 10 | * @ngdoc function 11 | * @name ng.$timeout 12 | * @requires $browser 13 | * 14 | * @description 15 | * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 16 | * block and delegates any exceptions to 17 | * {@link ng.$exceptionHandler $exceptionHandler} service. 18 | * 19 | * The return value of registering a timeout function is a promise, which will be resolved when 20 | * the timeout is reached and the timeout function is executed. 21 | * 22 | * To cancel a timeout request, call `$timeout.cancel(promise)`. 23 | * 24 | * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 25 | * synchronously flush the queue of deferred functions. 26 | * 27 | * @param {function()} fn A function, whose execution should be delayed. 28 | * @param {number=} [delay=0] Delay in milliseconds. 29 | * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 30 | * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block. 31 | * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 32 | * promise will be resolved with is the return value of the `fn` function. 33 | * 34 | */ 35 | function timeout(fn, $scope, delay, invokeApply) { 36 | var deferred = $q.defer(), 37 | promise = deferred.promise, 38 | skipApply = (isDefined(invokeApply) && !invokeApply), 39 | timeoutId; 40 | 41 | timeoutId = $browser.defer(function() { 42 | try { 43 | deferred.resolve(fn()); 44 | } catch(e) { 45 | deferred.reject(e); 46 | $exceptionHandler(e); 47 | } 48 | finally { 49 | delete deferreds[promise.$$timeoutId]; 50 | } 51 | 52 | if (!skipApply) $scope.$digest(); 53 | }, delay); 54 | 55 | promise.$$timeoutId = timeoutId; 56 | deferreds[timeoutId] = deferred; 57 | 58 | return promise; 59 | } 60 | 61 | 62 | /** 63 | * @ngdoc function 64 | * @name ng.$timeout#cancel 65 | * @methodOf ng.$timeout 66 | * 67 | * @description 68 | * Cancels a task associated with the `promise`. As a result of this, the promise will be 69 | * resolved with a rejection. 70 | * 71 | * @param {Promise=} promise Promise returned by the `$timeout` function. 72 | * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 73 | * canceled. 74 | */ 75 | timeout.cancel = function(promise) { 76 | if (promise && promise.$$timeoutId in deferreds) { 77 | deferreds[promise.$$timeoutId].reject('canceled'); 78 | delete deferreds[promise.$$timeoutId]; 79 | return $browser.defer.cancel(promise.$$timeoutId); 80 | } 81 | return false; 82 | }; 83 | 84 | return timeout; 85 | }]; 86 | }) -------------------------------------------------------------------------------- /public/js/services/utils.js: -------------------------------------------------------------------------------- 1 | angular.module('flow').service('utils', function () { 2 | var getRandomColor = function () { 3 | var letters = '0123456789ABCDEF'; 4 | var color = '#'; 5 | for (var i = 0; i < 6; i++) { 6 | color += letters[Math.floor(Math.random() * 16)]; 7 | } 8 | return color; 9 | } 10 | return { 11 | getRandomColor: getRandomColor, 12 | processSchema: function (data) { 13 | // Set color 14 | for (var i = 0; i < data.services.length; i++) { 15 | var service = data.services[i] 16 | service.color = service.color || this.getRandomColor() 17 | for (var j = 0; j < service.routes.length; j++) { 18 | service.routes[j].color = this.getRandomColor() 19 | } 20 | } 21 | }, 22 | buildGraphFromSchema: function (data) { 23 | var graph = { 24 | edgeMap: {}, 25 | endpoints: {}, 26 | endpointsKeys: [], 27 | nodesDataSet: null, 28 | edgesDataSet: null, 29 | 30 | edgeExists: function (from, to) { 31 | var k = from + "_" + to 32 | return !!this.edgeMap[k] 33 | }, 34 | addEndpoint: function (endpoint, service) { 35 | var e = this.endpoints[endpoint] 36 | if (typeof e == "undefined") { 37 | e = this.nodesDataSet.length 38 | this.endpoints[endpoint] = e 39 | // console.info(service.name + ": add endpoint " + endpoint) 40 | this.addNode(e, endpoint, service) 41 | } else { 42 | this.updateNode(e, endpoint, service) 43 | } 44 | }, 45 | addNode: function (id, endpoint, service) { 46 | this.nodesDataSet.add({ 47 | id: id, 48 | label: endpoint, 49 | color: service.color 50 | }) 51 | }, 52 | updateNode: function (id, endpoint, service) { 53 | // do nothing 54 | }, 55 | getRouteTitle: function (route) { 56 | var title = '' + route.name + '' 57 | + '
State: ' + (route.state || 'none') 58 | + '
Uptime: ' + (route.uptime || '-') 59 | + '
LastUpdated: ' + (route.lastUpdated ? moment(route.lastUpdated).fromNow(): "-") 60 | + '
----' 61 | + '
exchangesTotal: ' + (route.exchangesTotal || 0) 62 | + '
exchangesCompleted: ' + (route.exchangesCompleted || 0) 63 | + '
exchangesFailed: ' + (route.exchangesFailed || 0) 64 | + '
exchangesInflight: ' + (route.exchangesInflight || 0) 65 | + '
maxProcessingTime: ' + (route.maxProcessingTime || 0) 66 | + '
minProcessingTime: ' + (route.minProcessingTime || 0) 67 | + '
lastProcessingTime: ' + (route.lastProcessingTime || 0) 68 | + '
meanProcessingTime: ' + (route.meanProcessingTime || 0) 69 | + '
totalProcessingTime: ' + (route.totalProcessingTime || 0) 70 | + '
failuresHandled: ' + (route.failuresHandled || 0) 71 | + '
redeliveries: ' + (route.redeliveries || 0) 72 | + '
startTimestamp: ' + (route.startTimestamp || '-') 73 | return title 74 | }, 75 | addEdge: function (route, from, to, service) { 76 | var id = from + "_" + to 77 | // console.info(service.name + ": add edge " + id + ": " + route.name) 78 | var routeRepresentation = this.getRouteRepresentation(route, service) 79 | this.edgesDataSet.add({ 80 | id: id, 81 | from: from, 82 | to: to, 83 | route: route, 84 | color: routeRepresentation.color, 85 | title: this.getRouteTitle(route), 86 | value: routeRepresentation.weight, 87 | dashes: routeRepresentation.dashes, 88 | label: route.exchangesTotal ? '' + route.exchangesTotal : null, 89 | font: {align: 'top'} 90 | }) 91 | this.edgeMap[id] = 1 92 | }, 93 | updateEdge: function (route, from, to, service) { 94 | var id = from + "_" + to 95 | var existedEdge = this.edgesDataSet.get(id) 96 | // console.info(service.name + ": update edge " + id + ": " + route.name) 97 | if (typeof existedEdge != "undefined" && typeof route != "undefined") { 98 | this.nodesDataSet.update({ 99 | id: to, 100 | strike: false 101 | }) 102 | if (this.isNeedUpdate(existedEdge, route)) { 103 | // console.info(route.name + " " + existedEdge.route.exchangesTotal + ' -> ' + route.exchangesTotal) 104 | var routeRepresentation = this.getRouteRepresentation(route, service) 105 | this.edgesDataSet.update([{ 106 | id: id, 107 | from: from, 108 | to: to, 109 | route: route, 110 | color: routeRepresentation.color, 111 | title: this.getRouteTitle(route), 112 | value: routeRepresentation.weight, 113 | dashes: routeRepresentation.dashes, 114 | label: route.exchangesTotal ? '' + route.exchangesTotal : null, 115 | font: {align: 'top'} 116 | }]); 117 | this.nodesDataSet.update({ 118 | id: to, 119 | strike: true 120 | }) 121 | } 122 | } 123 | }, 124 | getRouteRepresentation: function (route, service) { 125 | var color = service.color 126 | var dashes = false 127 | var weight = 1 //route.exchangesTotal, 128 | if (route.state == 'None') { 129 | color = "#b2b2b2" 130 | dashes = true 131 | } else if (route.state != 'Started') { 132 | color = "#ff251e" 133 | dashes = true 134 | } 135 | return { 136 | color: {color: color}, 137 | dashes: dashes, 138 | weight: weight 139 | } 140 | }, 141 | isNeedUpdate: function (edge, route) { 142 | if (typeof route.exchangesTotal == "undefined" 143 | || typeof edge.route == "undefined" 144 | || typeof edge.route.exchangesTotal == "undefined") { 145 | return false 146 | } 147 | return route.exchangesTotal != edge.route.exchangesTotal 148 | || route.state != edge.route.state 149 | || route.uptime != edge.route.uptime 150 | }, 151 | build: function (data) { 152 | this.nodesDataSet = new vis.DataSet([]) 153 | this.edgesDataSet = new vis.DataSet([]) 154 | 155 | // Created elements from endpoints 156 | for (var i = 0; i < data.services.length; i++) { 157 | var service = data.services[i] 158 | for (var j = 0; j < service.routes.length; j++) { 159 | var route = service.routes[j] 160 | if (route.endpoints && route.endpoints.inputs) { 161 | for (var n = 0; n < route.endpoints.inputs.length; n++) { 162 | var endpoint = route.endpoints.inputs[n] 163 | this.addEndpoint(endpoint, service) 164 | } 165 | } 166 | if (route.endpoints && route.endpoints.outputs) { 167 | for (var n = 0; n < route.endpoints.outputs.length; n++) { 168 | var endpoint = route.endpoints.outputs[n] 169 | //TODO duplicates 170 | if (endpoint.indexOf("{{") !== -1) { 171 | continue 172 | } 173 | this.addEndpoint(endpoint, service) 174 | } 175 | } 176 | } 177 | } 178 | // Create edges 179 | for (var i = 0; i < data.services.length; i++) { 180 | var service = data.services[i] 181 | for (var j = 0; j < service.routes.length; j++) { 182 | var route = service.routes[j] 183 | if (route.endpoints && route.endpoints.inputs) { 184 | for (var n = 0; n < route.endpoints.inputs.length; n++) { 185 | var input = route.endpoints.inputs[n] 186 | if (route.endpoints.outputs) { 187 | for (var m = 0; m < route.endpoints.outputs.length; m++) { 188 | var output = route.endpoints.outputs[m] 189 | var from = this.endpoints[input] 190 | var to = this.endpoints[output] 191 | // console.info(service.name + ": add " + input + ' (' + from + ')' + 192 | // " to " + output + ' (' + to + ')' + ' route ' + route.name + ' exists ' + this.edgeExists(from, to)) 193 | // console.info("pre done " + (from && to && !this.edgeExists(from, to))) 194 | // console.info("pre done " + (from && to && !this.edgeMap[from + '_' + to])) 195 | if (typeof from != "undefined" 196 | && typeof to != "undefined" 197 | && !this.edgeExists(from, to)) { 198 | // console.info("done") 199 | this.addEdge(route, from, to, service) 200 | } 201 | } 202 | } 203 | } 204 | } 205 | } 206 | } 207 | //TODO 208 | for (var key in this.endpoints) { 209 | this.endpointsKeys.push(key); 210 | } 211 | } 212 | } 213 | graph.build(data) 214 | return graph 215 | }, 216 | updateGraph: function (graph, data) { 217 | // nodes 218 | for (var i = 0; i < data.services.length; i++) { 219 | var service = data.services[i] 220 | for (var j = 0; j < service.routes.length; j++) { 221 | var route = service.routes[j] 222 | if (route.endpoints && route.endpoints.inputs) { 223 | for (var n = 0; n < route.endpoints.inputs.length; n++) { 224 | var endpoint = route.endpoints.inputs[n] 225 | var id = graph.endpoints[endpoint] 226 | if (typeof id == "undefined") { 227 | graph.addEndpoint(endpoint, service) 228 | } 229 | } 230 | } 231 | if (route.endpoints && route.endpoints.outputs) { 232 | for (var n = 0; n < route.endpoints.outputs.length; n++) { 233 | var endpoint = route.endpoints.outputs[n] 234 | //TODO duplicates 235 | if (endpoint.indexOf("{{") !== -1) { 236 | continue 237 | } 238 | var id = graph.endpoints[endpoint] 239 | if (typeof id == "undefined") { 240 | graph.addEndpoint(endpoint, service) 241 | } 242 | } 243 | } 244 | } 245 | } 246 | // edges 247 | for (var i = 0; i < data.services.length; i++) { 248 | var service = data.services[i] 249 | for (var j = 0; j < service.routes.length; j++) { 250 | var route = service.routes[j] 251 | if (route.endpoints && route.endpoints.inputs) { 252 | for (var n = 0; n < route.endpoints.inputs.length; n++) { 253 | var input = route.endpoints.inputs[n] 254 | if (route.endpoints.outputs) { 255 | for (var m = 0; m < route.endpoints.outputs.length; m++) { 256 | var output = route.endpoints.outputs[m] 257 | var from = graph.endpoints[input] 258 | var to = graph.endpoints[output] 259 | //TODO duplicates 260 | if (typeof from != "undefined" 261 | && typeof to != "undefined") { 262 | if (graph.edgeExists(from, to)) { 263 | graph.updateEdge(route, from, to, service) 264 | } else { 265 | graph.addEdge(route, from, to, service) 266 | } 267 | } 268 | } 269 | } 270 | } 271 | } 272 | } 273 | } 274 | } 275 | } 276 | }) -------------------------------------------------------------------------------- /public/js/vendor/angular-relative-date.min.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var o;o="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,o.relativeDate=e()}}(function(){return function e(o,r,t){function a(u,f){if(!r[u]){if(!o[u]){var s="function"==typeof require&&require;if(!f&&s)return s(u,!0);if(n)return n(u,!0);var i=new Error("Cannot find module '"+u+"'");throw i.code="MODULE_NOT_FOUND",i}var _=r[u]={exports:{}};o[u][0].call(_.exports,function(e){var r=o[u][1][e];return a(r?r:e)},_,_.exports,e,o,r,t)}return r[u].exports}for(var n="function"==typeof require&&require,u=0;us&&i>a&&(e=new Date(e.getFullYear(),e.getMonth(),e.getDate(),0,0,0),a=n(r,e));var l=function(o,a){var n;return n="just_now"===o?o:r>=e?o+"_ago":o+"_from_now",t.instant(n,{time:a})};switch(!1){case!(30>a):return l("just_now");case!(u>a):return l("seconds",a);case!(2*u>a):return l("a_minute");case!(f>a):return l("minutes",Math.floor(a/u));case 1!==Math.floor(a/f):return l("an_hour");case!(s>a):return l("hours",Math.floor(a/f));case!(2*s>a):return l("a_day");case!(i>a):return l("days",Math.floor(a/s));case 1!==Math.floor(a/i):return l("a_week");case!(_>a):return l("weeks",Math.floor(a/i));case 1!==Math.floor(a/_):return l("a_month");case!(m>a):return l("months",Math.floor(a/_));case 1!==Math.floor(a/m):return l("a_year");default:return l("over_a_year")}}}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=t;var a=function(e,o){return e.has("$translate")?e.get("$translate"):{instant:function(e,r){return o[e].replace("{{time}}",r.time)}}},n=function(e,o){return Math.round(Math.abs(e-o)/1e3)}},{}],2:[function(e,o,r){"use strict";function t(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(r,"__esModule",{value:!0});var a=e("./translations"),n=t(a),u=e("./filter"),f=t(u),s=angular.module("relativeDate",[]);s.value("now",null),s.value("relativeDateTranslations",n["default"]),s.filter("relativeDate",["$injector","now","relativeDateTranslations",f["default"]]),r["default"]=s},{"./filter":1,"./translations":3}],3:[function(e,o,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r["default"]={just_now:"just now",seconds_ago:"{{time}} seconds ago",a_minute_ago:"a minute ago",minutes_ago:"{{time}} minutes ago",an_hour_ago:"an hour ago",hours_ago:"{{time}} hours ago",a_day_ago:"yesterday",days_ago:"{{time}} days ago",a_week_ago:"a week ago",weeks_ago:"{{time}} weeks ago",a_month_ago:"a month ago",months_ago:"{{time}} months ago",a_year_ago:"a year ago",years_ago:"{{time}} years ago",over_a_year_ago:"over a year ago",seconds_from_now:"{{time}} seconds from now",a_minute_from_now:"a minute from now",minutes_from_now:"{{time}} minutes from now",an_hour_from_now:"an hour from now",hours_from_now:"{{time}} hours from now",a_day_from_now:"tomorrow",days_from_now:"{{time}} days from now",a_week_from_now:"a week from now",weeks_from_now:"{{time}} weeks from now",a_month_from_now:"a month from now",months_from_now:"{{time}} months from now",a_year_from_now:"a year from now",years_from_now:"{{time}} years from now",over_a_year_from_now:"over a year from now"}},{}]},{},[2])(2)}); 2 | //# sourceMappingURL=angular-relative-date.min.js.map 3 | -------------------------------------------------------------------------------- /public/js/vendor/angular-relative-date.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["angular-relative-date.min.js"],"names":["f","exports","module","define","amd","g","window","global","self","this","relativeDate","e","t","n","r","s","o","u","a","require","i","Error","code","l","call","length","1","_dereq_","relativeDateFilter","$injector","_now","relativeDateTranslations","$translate","getTranslate","date","now","Date","delta","minute","hour","day","week","month","year","calculateDelta","getFullYear","getMonth","getDate","translate","translatePhrase","timeValue","translateKey","instant","time","Math","floor","Object","defineProperty","value","injector","translations","has","get","id","params","replace","round","abs","2","_interopRequireDefault","obj","__esModule","default","_translations","_translations2","_filter","_filter2","angular","filter","./filter","./translations","3","just_now","seconds_ago","a_minute_ago","minutes_ago","an_hour_ago","hours_ago","a_day_ago","days_ago","a_week_ago","weeks_ago","a_month_ago","months_ago","a_year_ago","years_ago","over_a_year_ago","seconds_from_now","a_minute_from_now","minutes_from_now","an_hour_from_now","hours_from_now","a_day_from_now","days_from_now","a_week_from_now","weeks_from_now","a_month_from_now","months_from_now","a_year_from_now","years_from_now","over_a_year_from_now"],"mappings":"CAAA,SAAUA,GAAG,GAAoB,gBAAVC,UAAoC,mBAATC,QAAsBA,OAAOD,QAAQD,QAAS,IAAmB,kBAATG,SAAqBA,OAAOC,IAAKD,UAAUH,OAAO,CAAC,GAAIK,EAAkCA,GAAb,mBAATC,QAAwBA,OAA+B,mBAATC,QAAwBA,OAA6B,mBAAPC,MAAsBA,KAAYC,KAAKJ,EAAEK,aAAeV,MAAO,WAAqC,MAAO,SAAUW,GAAEC,EAAEC,EAAEC,GAAG,QAASC,GAAEC,EAAEC,GAAG,IAAIJ,EAAEG,GAAG,CAAC,IAAIJ,EAAEI,GAAG,CAAC,GAAIE,GAAkB,kBAATC,UAAqBA,OAAQ,KAAIF,GAAGC,EAAE,MAAOA,GAAEF,GAAE,EAAI,IAAGI,EAAE,MAAOA,GAAEJ,GAAE,EAAI,IAAIhB,GAAE,GAAIqB,OAAM,uBAAuBL,EAAE,IAAK,MAAMhB,GAAEsB,KAAK,mBAAmBtB,EAAE,GAAIuB,GAAEV,EAAEG,IAAIf,WAAYW,GAAEI,GAAG,GAAGQ,KAAKD,EAAEtB,QAAQ,SAASU,GAAG,GAAIE,GAAED,EAAEI,GAAG,GAAGL,EAAG,OAAOI,GAAEF,EAAEA,EAAEF,IAAIY,EAAEA,EAAEtB,QAAQU,EAAEC,EAAEC,EAAEC,GAAG,MAAOD,GAAEG,GAAGf,QAAkD,IAAI,GAA1CmB,GAAkB,kBAATD,UAAqBA,QAAgBH,EAAE,EAAEA,EAAEF,EAAEW,OAAOT,IAAID,EAAED,EAAEE,GAAI,OAAOD,KAAKW,GAAG,SAASC,EAAQzB,EAAOD,GAC30B,YAsBA,SAAS2B,GAAmBC,EAAWC,EAAMC,GAC3C,GAAIC,GAAaC,EAAaJ,EAAWE,EAEzC,OAAO,UAAUG,GACf,GAAIC,GAAML,GAAQ,GAAIM,KAEhBF,aAAgBE,QACpBF,EAAO,GAAIE,MAAKF,GAGlB,IAAIG,GAAQ,KAERC,EAAS,GACTC,EAAgB,GAATD,EACPE,EAAa,GAAPD,EACNE,EAAa,EAAND,EACPE,EAAc,GAANF,EACRG,EAAa,IAANH,CAEXH,GAAQO,EAAeT,EAAKD,GAExBG,EAAQG,GAAeC,EAARJ,IACjBH,EAAO,GAAIE,MAAKF,EAAKW,cAAeX,EAAKY,WAAYZ,EAAKa,UAAW,EAAG,EAAG,GAC3EV,EAAQO,EAAeT,EAAKD,GAG9B,IAAIc,GAAY,SAAmBC,EAAiBC,GAClD,GAAIC,EAUJ,OAPEA,GADsB,aAApBF,EACaA,EACNd,GAAOD,EACDe,EAAkB,OAElBA,EAAkB,YAG5BjB,EAAWoB,QAAQD,GACxBE,KAAMH,IAIV,SAAQ,GACN,MAAe,GAARb,GACL,MAAOW,GAAU,WAEnB,OAAeV,EAARD,GACL,MAAOW,GAAU,UAAWX,EAE9B,OAAe,EAAIC,EAAZD,GACL,MAAOW,GAAU,WAEnB,OAAeT,EAARF,GACL,MAAOW,GAAU,UAAWM,KAAKC,MAAMlB,EAAQC,GAEjD,KAAkC,KAA7BgB,KAAKC,MAAMlB,EAAQE,GACtB,MAAOS,GAAU,UAEnB,OAAeR,EAARH,GACL,MAAOW,GAAU,QAASM,KAAKC,MAAMlB,EAAQE,GAE/C,OAAqB,EAANC,EAARH,GACL,MAAOW,GAAU,QAEnB,OAAeP,EAARJ,GACL,MAAOW,GAAU,OAAQM,KAAKC,MAAMlB,EAAQG,GAE9C,KAAkC,KAA7Bc,KAAKC,MAAMlB,EAAQI,GACtB,MAAOO,GAAU,SAEnB,OAAeN,EAARL,GACL,MAAOW,GAAU,QAASM,KAAKC,MAAMlB,EAAQI,GAE/C,KAAmC,KAA9Ba,KAAKC,MAAMlB,EAAQK,GACtB,MAAOM,GAAU,UAEnB,OAAeL,EAARN,GACL,MAAOW,GAAU,SAAUM,KAAKC,MAAMlB,EAAQK,GAEhD,KAAkC,KAA7BY,KAAKC,MAAMlB,EAAQM,GACtB,MAAOK,GAAU,SAEnB,SACE,MAAOA,GAAU,iBAvGzBQ,OAAOC,eAAexD,EAAS,cAC7ByD,OAAO,IAETzD,EAAAA,WAAkB2B,CAClB,IAAIK,GAAe,SAAsB0B,EAAUC,GACjD,MAAID,GAASE,IAAI,cACRF,EAASG,IAAI,eAGlBV,QAAS,SAAiBW,EAAIC,GAC5B,MAAOJ,GAAaG,GAAIE,QAAQ,WAAYD,EAAOX,SAMvDT,EAAiB,SAAwBT,EAAKD,GAChD,MAAOoB,MAAKY,MAAMZ,KAAKa,IAAIhC,EAAMD,GAAQ,WA2FrCkC,GAAG,SAASzC,EAAQzB,EAAOD,GACjC,YAcA,SAASoE,GAAuBC,GAAO,MAAOA,IAAOA,EAAIC,WAAaD,GAAQE,UAASF,GAZvFd,OAAOC,eAAexD,EAAS,cAC7ByD,OAAO,GAGT,IAAIe,GAAgB9C,EAAQ,kBAExB+C,EAAiBL,EAAuBI,GAExCE,EAAUhD,EAAQ,YAElBiD,EAAWP,EAAuBM,GAMlCjE,EAAemE,QAAQ3E,OAAO,kBAElCQ,GAAagD,MAAM,MAAO,MAC1BhD,EAAagD,MAAM,2BAA4BgB,EAAAA,YAE/ChE,EAAaoE,OAAO,gBAAiB,YAAa,MAAO,2BAA4BF,EAAAA,aAErF3E,EAAAA,WAAkBS,IAEfqE,WAAW,EAAEC,iBAAiB,IAAIC,GAAG,SAAStD,EAAQzB,EAAOD,GAChE,YAEAuD,QAAOC,eAAexD,EAAS,cAC7ByD,OAAO,IAETzD,EAAAA,YACEiF,SAAU,WACVC,YAAa,uBACbC,aAAc,eACdC,YAAa,uBACbC,YAAa,cACbC,UAAW,qBACXC,UAAW,YACXC,SAAU,oBACVC,WAAY,aACZC,UAAW,qBACXC,YAAa,cACbC,WAAY,sBACZC,WAAY,aACZC,UAAW,qBACXC,gBAAiB,kBACjBC,iBAAkB,4BAClBC,kBAAmB,oBACnBC,iBAAkB,4BAClBC,iBAAkB,mBAClBC,eAAgB,0BAChBC,eAAgB,WAChBC,cAAe,yBACfC,gBAAiB,kBACjBC,eAAgB,0BAChBC,iBAAkB,mBAClBC,gBAAiB,2BACjBC,gBAAiB,kBACjBC,eAAgB,0BAChBC,qBAAsB,kCAGb,IAAI","file":"angular-relative-date.min.js","sourcesContent":["(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define([],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.relativeDate = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o day && delta < week) {\n date = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);\n delta = calculateDelta(now, date);\n }\n\n var translate = function translate(translatePhrase, timeValue) {\n var translateKey;\n\n if (translatePhrase === 'just_now') {\n translateKey = translatePhrase;\n } else if (now >= date) {\n translateKey = translatePhrase + '_ago';\n } else {\n translateKey = translatePhrase + '_from_now';\n }\n\n return $translate.instant(translateKey, {\n time: timeValue\n });\n };\n\n switch (false) {\n case !(delta < 30):\n return translate('just_now');\n\n case !(delta < minute):\n return translate('seconds', delta);\n\n case !(delta < 2 * minute):\n return translate('a_minute');\n\n case !(delta < hour):\n return translate('minutes', Math.floor(delta / minute));\n\n case Math.floor(delta / hour) !== 1:\n return translate('an_hour');\n\n case !(delta < day):\n return translate('hours', Math.floor(delta / hour));\n\n case !(delta < day * 2):\n return translate('a_day');\n\n case !(delta < week):\n return translate('days', Math.floor(delta / day));\n\n case Math.floor(delta / week) !== 1:\n return translate('a_week');\n\n case !(delta < month):\n return translate('weeks', Math.floor(delta / week));\n\n case Math.floor(delta / month) !== 1:\n return translate('a_month');\n\n case !(delta < year):\n return translate('months', Math.floor(delta / month));\n\n case Math.floor(delta / year) !== 1:\n return translate('a_year');\n\n default:\n return translate('over_a_year');\n }\n };\n}\n\n},{}],2:[function(_dereq_,module,exports){\n'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _translations = _dereq_('./translations');\n\nvar _translations2 = _interopRequireDefault(_translations);\n\nvar _filter = _dereq_('./filter');\n\nvar _filter2 = _interopRequireDefault(_filter);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\n/* global angular */\n\nvar relativeDate = angular.module('relativeDate', []);\n\nrelativeDate.value('now', null);\nrelativeDate.value('relativeDateTranslations', _translations2.default);\n\nrelativeDate.filter('relativeDate', ['$injector', 'now', 'relativeDateTranslations', _filter2.default]);\n\nexports.default = relativeDate;\n\n},{\"./filter\":1,\"./translations\":3}],3:[function(_dereq_,module,exports){\n'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = {\n just_now: 'just now',\n seconds_ago: '{{time}} seconds ago',\n a_minute_ago: 'a minute ago',\n minutes_ago: '{{time}} minutes ago',\n an_hour_ago: 'an hour ago',\n hours_ago: '{{time}} hours ago',\n a_day_ago: 'yesterday',\n days_ago: '{{time}} days ago',\n a_week_ago: 'a week ago',\n weeks_ago: '{{time}} weeks ago',\n a_month_ago: 'a month ago',\n months_ago: '{{time}} months ago',\n a_year_ago: 'a year ago',\n years_ago: '{{time}} years ago',\n over_a_year_ago: 'over a year ago',\n seconds_from_now: '{{time}} seconds from now',\n a_minute_from_now: 'a minute from now',\n minutes_from_now: '{{time}} minutes from now',\n an_hour_from_now: 'an hour from now',\n hours_from_now: '{{time}} hours from now',\n a_day_from_now: 'tomorrow',\n days_from_now: '{{time}} days from now',\n a_week_from_now: 'a week from now',\n weeks_from_now: '{{time}} weeks from now',\n a_month_from_now: 'a month from now',\n months_from_now: '{{time}} months from now',\n a_year_from_now: 'a year from now',\n years_from_now: '{{time}} years from now',\n over_a_year_from_now: 'over a year from now'\n};\n\n},{}]},{},[2])(2)\n});\n\n"],"sourceRoot":"/source/"} -------------------------------------------------------------------------------- /public/js/vendor/angular-sanitize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.3.16 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 9 | * Any commits to this file should be reviewed with security in mind. * 10 | * Changes to this file can potentially create security vulnerabilities. * 11 | * An approval from 2 Core members with history of modifying * 12 | * this file is required. * 13 | * * 14 | * Does the change somehow allow for arbitrary javascript to be executed? * 15 | * Or allows for someone to change the prototype of built-in objects? * 16 | * Or gives undesired access to variables likes document or window? * 17 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 18 | 19 | var $sanitizeMinErr = angular.$$minErr('$sanitize'); 20 | 21 | /** 22 | * @ngdoc module 23 | * @name ngSanitize 24 | * @description 25 | * 26 | * # ngSanitize 27 | * 28 | * The `ngSanitize` module provides functionality to sanitize HTML. 29 | * 30 | * 31 | *
32 | * 33 | * See {@link ngSanitize.$sanitize `$sanitize`} for usage. 34 | */ 35 | 36 | /* 37 | * HTML Parser By Misko Hevery (misko@hevery.com) 38 | * based on: HTML Parser By John Resig (ejohn.org) 39 | * Original code by Erik Arvidsson, Mozilla Public License 40 | * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js 41 | * 42 | * // Use like so: 43 | * htmlParser(htmlString, { 44 | * start: function(tag, attrs, unary) {}, 45 | * end: function(tag) {}, 46 | * chars: function(text) {}, 47 | * comment: function(text) {} 48 | * }); 49 | * 50 | */ 51 | 52 | 53 | /** 54 | * @ngdoc service 55 | * @name $sanitize 56 | * @kind function 57 | * 58 | * @description 59 | * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are 60 | * then serialized back to properly escaped html string. This means that no unsafe input can make 61 | * it into the returned string, however, since our parser is more strict than a typical browser 62 | * parser, it's possible that some obscure input, which would be recognized as valid HTML by a 63 | * browser, won't make it through the sanitizer. The input may also contain SVG markup. 64 | * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and 65 | * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. 66 | * 67 | * @param {string} html HTML input. 68 | * @returns {string} Sanitized HTML. 69 | * 70 | * @example 71 | 72 | 73 | 85 |
86 | Snippet: 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
DirectiveHowSourceRendered
ng-bind-htmlAutomatically uses $sanitize
<div ng-bind-html="snippet">
</div>
ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value 104 |
<div ng-bind-html="deliberatelyTrustDangerousSnippet()">
105 | </div>
106 |
ng-bindAutomatically escapes
<div ng-bind="snippet">
</div>
116 |
117 |
118 | 119 | it('should sanitize the html snippet by default', function() { 120 | expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). 121 | toBe('

an html\nclick here\nsnippet

'); 122 | }); 123 | 124 | it('should inline raw snippet if bound to a trusted value', function() { 125 | expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). 126 | toBe("

an html\n" + 127 | "click here\n" + 128 | "snippet

"); 129 | }); 130 | 131 | it('should escape snippet without any filter', function() { 132 | expect(element(by.css('#bind-default div')).getInnerHtml()). 133 | toBe("<p style=\"color:blue\">an html\n" + 134 | "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 135 | "snippet</p>"); 136 | }); 137 | 138 | it('should update', function() { 139 | element(by.model('snippet')).clear(); 140 | element(by.model('snippet')).sendKeys('new text'); 141 | expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). 142 | toBe('new text'); 143 | expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( 144 | 'new text'); 145 | expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( 146 | "new <b onclick=\"alert(1)\">text</b>"); 147 | }); 148 |
149 |
150 | */ 151 | function $SanitizeProvider() { 152 | this.$get = ['$$sanitizeUri', function($$sanitizeUri) { 153 | return function(html) { 154 | var buf = []; 155 | htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { 156 | return !/^unsafe/.test($$sanitizeUri(uri, isImage)); 157 | })); 158 | return buf.join(''); 159 | }; 160 | }]; 161 | } 162 | 163 | function sanitizeText(chars) { 164 | var buf = []; 165 | var writer = htmlSanitizeWriter(buf, angular.noop); 166 | writer.chars(chars); 167 | return buf.join(''); 168 | } 169 | 170 | 171 | // Regular Expressions for parsing tags and attributes 172 | var START_TAG_REGEXP = 173 | /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/, 174 | END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/, 175 | ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, 176 | BEGIN_TAG_REGEXP = /^/g, 179 | DOCTYPE_REGEXP = /]*?)>/i, 180 | CDATA_REGEXP = //g, 181 | SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 182 | // Match everything outside of normal chars and " (quote character) 183 | NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; 184 | 185 | 186 | // Good source of info about elements and attributes 187 | // http://dev.w3.org/html5/spec/Overview.html#semantics 188 | // http://simon.html5.org/html-elements 189 | 190 | // Safe Void Elements - HTML5 191 | // http://dev.w3.org/html5/spec/Overview.html#void-elements 192 | var voidElements = makeMap("area,br,col,hr,img,wbr"); 193 | 194 | // Elements that you can, intentionally, leave open (and which close themselves) 195 | // http://dev.w3.org/html5/spec/Overview.html#optional-tags 196 | var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), 197 | optionalEndTagInlineElements = makeMap("rp,rt"), 198 | optionalEndTagElements = angular.extend({}, 199 | optionalEndTagInlineElements, 200 | optionalEndTagBlockElements); 201 | 202 | // Safe Block Elements - HTML5 203 | var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + 204 | "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + 205 | "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); 206 | 207 | // Inline Elements - HTML5 208 | var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + 209 | "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + 210 | "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); 211 | 212 | // SVG Elements 213 | // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements 214 | var svgElements = makeMap("animate,animateColor,animateMotion,animateTransform,circle,defs," + 215 | "desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient," + 216 | "line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,set," + 217 | "stop,svg,switch,text,title,tspan,use"); 218 | 219 | // Special Elements (can contain anything) 220 | var specialElements = makeMap("script,style"); 221 | 222 | var validElements = angular.extend({}, 223 | voidElements, 224 | blockElements, 225 | inlineElements, 226 | optionalEndTagElements, 227 | svgElements); 228 | 229 | //Attributes that have href and hence need to be sanitized 230 | var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href"); 231 | 232 | var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + 233 | 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + 234 | 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + 235 | 'scope,scrolling,shape,size,span,start,summary,target,title,type,' + 236 | 'valign,value,vspace,width'); 237 | 238 | // SVG attributes (without "id" and "name" attributes) 239 | // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes 240 | var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + 241 | 'attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,' + 242 | 'color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,' + 243 | 'font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,' + 244 | 'gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,' + 245 | 'keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,' + 246 | 'markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,' + 247 | 'overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,' + 248 | 'repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,' + 249 | 'stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,' + 250 | 'stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,' + 251 | 'stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,' + 252 | 'underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,' + 253 | 'viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,' + 254 | 'xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,' + 255 | 'zoomAndPan'); 256 | 257 | var validAttrs = angular.extend({}, 258 | uriAttrs, 259 | svgAttrs, 260 | htmlAttrs); 261 | 262 | function makeMap(str) { 263 | var obj = {}, items = str.split(','), i; 264 | for (i = 0; i < items.length; i++) obj[items[i]] = true; 265 | return obj; 266 | } 267 | 268 | 269 | /** 270 | * @example 271 | * htmlParser(htmlString, { 272 | * start: function(tag, attrs, unary) {}, 273 | * end: function(tag) {}, 274 | * chars: function(text) {}, 275 | * comment: function(text) {} 276 | * }); 277 | * 278 | * @param {string} html string 279 | * @param {object} handler 280 | */ 281 | function htmlParser(html, handler) { 282 | if (typeof html !== 'string') { 283 | if (html === null || typeof html === 'undefined') { 284 | html = ''; 285 | } else { 286 | html = '' + html; 287 | } 288 | } 289 | var index, chars, match, stack = [], last = html, text; 290 | stack.last = function() { return stack[stack.length - 1]; }; 291 | 292 | while (html) { 293 | text = ''; 294 | chars = true; 295 | 296 | // Make sure we're not in a script or style element 297 | if (!stack.last() || !specialElements[stack.last()]) { 298 | 299 | // Comment 300 | if (html.indexOf("", index) === index) { 305 | if (handler.comment) handler.comment(html.substring(4, index)); 306 | html = html.substring(index + 3); 307 | chars = false; 308 | } 309 | // DOCTYPE 310 | } else if (DOCTYPE_REGEXP.test(html)) { 311 | match = html.match(DOCTYPE_REGEXP); 312 | 313 | if (match) { 314 | html = html.replace(match[0], ''); 315 | chars = false; 316 | } 317 | // end tag 318 | } else if (BEGING_END_TAGE_REGEXP.test(html)) { 319 | match = html.match(END_TAG_REGEXP); 320 | 321 | if (match) { 322 | html = html.substring(match[0].length); 323 | match[0].replace(END_TAG_REGEXP, parseEndTag); 324 | chars = false; 325 | } 326 | 327 | // start tag 328 | } else if (BEGIN_TAG_REGEXP.test(html)) { 329 | match = html.match(START_TAG_REGEXP); 330 | 331 | if (match) { 332 | // We only have a valid start-tag if there is a '>'. 333 | if (match[4]) { 334 | html = html.substring(match[0].length); 335 | match[0].replace(START_TAG_REGEXP, parseStartTag); 336 | } 337 | chars = false; 338 | } else { 339 | // no ending tag found --- this piece should be encoded as an entity. 340 | text += '<'; 341 | html = html.substring(1); 342 | } 343 | } 344 | 345 | if (chars) { 346 | index = html.indexOf("<"); 347 | 348 | text += index < 0 ? html : html.substring(0, index); 349 | html = index < 0 ? "" : html.substring(index); 350 | 351 | if (handler.chars) handler.chars(decodeEntities(text)); 352 | } 353 | 354 | } else { 355 | // IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w]. 356 | html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), 357 | function(all, text) { 358 | text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); 359 | 360 | if (handler.chars) handler.chars(decodeEntities(text)); 361 | 362 | return ""; 363 | }); 364 | 365 | parseEndTag("", stack.last()); 366 | } 367 | 368 | if (html == last) { 369 | throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + 370 | "of html: {0}", html); 371 | } 372 | last = html; 373 | } 374 | 375 | // Clean up any remaining tags 376 | parseEndTag(); 377 | 378 | function parseStartTag(tag, tagName, rest, unary) { 379 | tagName = angular.lowercase(tagName); 380 | if (blockElements[tagName]) { 381 | while (stack.last() && inlineElements[stack.last()]) { 382 | parseEndTag("", stack.last()); 383 | } 384 | } 385 | 386 | if (optionalEndTagElements[tagName] && stack.last() == tagName) { 387 | parseEndTag("", tagName); 388 | } 389 | 390 | unary = voidElements[tagName] || !!unary; 391 | 392 | if (!unary) 393 | stack.push(tagName); 394 | 395 | var attrs = {}; 396 | 397 | rest.replace(ATTR_REGEXP, 398 | function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { 399 | var value = doubleQuotedValue 400 | || singleQuotedValue 401 | || unquotedValue 402 | || ''; 403 | 404 | attrs[name] = decodeEntities(value); 405 | }); 406 | if (handler.start) handler.start(tagName, attrs, unary); 407 | } 408 | 409 | function parseEndTag(tag, tagName) { 410 | var pos = 0, i; 411 | tagName = angular.lowercase(tagName); 412 | if (tagName) 413 | // Find the closest opened tag of the same type 414 | for (pos = stack.length - 1; pos >= 0; pos--) 415 | if (stack[pos] == tagName) 416 | break; 417 | 418 | if (pos >= 0) { 419 | // Close all the open elements, up the stack 420 | for (i = stack.length - 1; i >= pos; i--) 421 | if (handler.end) handler.end(stack[i]); 422 | 423 | // Remove the open elements from the stack 424 | stack.length = pos; 425 | } 426 | } 427 | } 428 | 429 | var hiddenPre=document.createElement("pre"); 430 | /** 431 | * decodes all entities into regular string 432 | * @param value 433 | * @returns {string} A string with decoded entities. 434 | */ 435 | function decodeEntities(value) { 436 | if (!value) { return ''; } 437 | 438 | hiddenPre.innerHTML = value.replace(//g, '>'); 464 | } 465 | 466 | /** 467 | * create an HTML/XML writer which writes to buffer 468 | * @param {Array} buf use buf.jain('') to get out sanitized html string 469 | * @returns {object} in the form of { 470 | * start: function(tag, attrs, unary) {}, 471 | * end: function(tag) {}, 472 | * chars: function(text) {}, 473 | * comment: function(text) {} 474 | * } 475 | */ 476 | function htmlSanitizeWriter(buf, uriValidator) { 477 | var ignore = false; 478 | var out = angular.bind(buf, buf.push); 479 | return { 480 | start: function(tag, attrs, unary) { 481 | tag = angular.lowercase(tag); 482 | if (!ignore && specialElements[tag]) { 483 | ignore = tag; 484 | } 485 | if (!ignore && validElements[tag] === true) { 486 | out('<'); 487 | out(tag); 488 | angular.forEach(attrs, function(value, key) { 489 | var lkey=angular.lowercase(key); 490 | var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); 491 | if (validAttrs[lkey] === true && 492 | (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { 493 | out(' '); 494 | out(key); 495 | out('="'); 496 | out(encodeEntities(value)); 497 | out('"'); 498 | } 499 | }); 500 | out(unary ? '/>' : '>'); 501 | } 502 | }, 503 | end: function(tag) { 504 | tag = angular.lowercase(tag); 505 | if (!ignore && validElements[tag] === true) { 506 | out(''); 509 | } 510 | if (tag == ignore) { 511 | ignore = false; 512 | } 513 | }, 514 | chars: function(chars) { 515 | if (!ignore) { 516 | out(encodeEntities(chars)); 517 | } 518 | } 519 | }; 520 | } 521 | 522 | 523 | // define ngSanitize module and register $sanitize service 524 | angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); 525 | 526 | /* global sanitizeText: false */ 527 | 528 | /** 529 | * @ngdoc filter 530 | * @name linky 531 | * @kind function 532 | * 533 | * @description 534 | * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and 535 | * plain email address links. 536 | * 537 | * Requires the {@link ngSanitize `ngSanitize`} module to be installed. 538 | * 539 | * @param {string} text Input text. 540 | * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. 541 | * @returns {string} Html-linkified text. 542 | * 543 | * @usage 544 | 545 | * 546 | * @example 547 | 548 | 549 | 561 |
562 | Snippet: 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 574 | 577 | 578 | 579 | 580 | 583 | 586 | 587 | 588 | 589 | 590 | 591 | 592 |
FilterSourceRendered
linky filter 572 |
<div ng-bind-html="snippet | linky">
</div>
573 |
575 |
576 |
linky target 581 |
<div ng-bind-html="snippetWithTarget | linky:'_blank'">
</div>
582 |
584 |
585 |
no filter
<div ng-bind="snippet">
</div>
593 | 594 | 595 | it('should linkify the snippet with urls', function() { 596 | expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 597 | toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + 598 | 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); 599 | expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); 600 | }); 601 | 602 | it('should not linkify snippet without the linky filter', function() { 603 | expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). 604 | toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + 605 | 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); 606 | expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); 607 | }); 608 | 609 | it('should update', function() { 610 | element(by.model('snippet')).clear(); 611 | element(by.model('snippet')).sendKeys('new http://link.'); 612 | expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 613 | toBe('new http://link.'); 614 | expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); 615 | expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) 616 | .toBe('new http://link.'); 617 | }); 618 | 619 | it('should work with the target property', function() { 620 | expect(element(by.id('linky-target')). 621 | element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). 622 | toBe('http://angularjs.org/'); 623 | expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); 624 | }); 625 | 626 | 627 | */ 628 | angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { 629 | var LINKY_URL_REGEXP = 630 | /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/, 631 | MAILTO_REGEXP = /^mailto:/; 632 | 633 | return function(text, target) { 634 | if (!text) return text; 635 | var match; 636 | var raw = text; 637 | var html = []; 638 | var url; 639 | var i; 640 | while ((match = raw.match(LINKY_URL_REGEXP))) { 641 | // We can not end in these as they are sometimes found at the end of the sentence 642 | url = match[0]; 643 | // if we did not match ftp/http/www/mailto then assume mailto 644 | if (!match[2] && !match[4]) { 645 | url = (match[3] ? 'http://' : 'mailto:') + url; 646 | } 647 | i = match.index; 648 | addText(raw.substr(0, i)); 649 | addLink(url, match[0].replace(MAILTO_REGEXP, '')); 650 | raw = raw.substring(i + match[0].length); 651 | } 652 | addText(raw); 653 | return $sanitize(html.join('')); 654 | 655 | function addText(text) { 656 | if (!text) { 657 | return; 658 | } 659 | html.push(sanitizeText(text)); 660 | } 661 | 662 | function addLink(url, text) { 663 | html.push(''); 672 | addText(text); 673 | html.push(''); 674 | } 675 | }; 676 | }]); 677 | 678 | 679 | })(window, window.angular); 680 | -------------------------------------------------------------------------------- /public/js/vendor/moment.min.js: -------------------------------------------------------------------------------- 1 | //! moment.js 2 | //! version : 2.4.0 3 | //! authors : Tim Wood, Iskren Chernev, Moment.js contributors 4 | //! license : MIT 5 | //! momentjs.com 6 | (function(a){function b(a,b){return function(c){return i(a.call(this,c),b)}}function c(a,b){return function(c){return this.lang().ordinal(a.call(this,c),b)}}function d(){}function e(a){u(a),g(this,a)}function f(a){var b=o(a),c=b.year||0,d=b.month||0,e=b.week||0,f=b.day||0,g=b.hour||0,h=b.minute||0,i=b.second||0,j=b.millisecond||0;this._input=a,this._milliseconds=+j+1e3*i+6e4*h+36e5*g,this._days=+f+7*e,this._months=+d+12*c,this._data={},this._bubble()}function g(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return b.hasOwnProperty("toString")&&(a.toString=b.toString),b.hasOwnProperty("valueOf")&&(a.valueOf=b.valueOf),a}function h(a){return 0>a?Math.ceil(a):Math.floor(a)}function i(a,b){for(var c=a+"";c.lengthd;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function n(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=Kb[a]||Lb[b]||b}return a}function o(a){var b,c,d={};for(c in a)a.hasOwnProperty(c)&&(b=n(c),b&&(d[b]=a[c]));return d}function p(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}bb[b]=function(e,f){var g,h,i=bb.fn._lang[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=bb().utc().set(d,a);return i.call(bb.fn._lang,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function r(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function s(a){return t(a)?366:365}function t(a){return 0===a%4&&0!==a%100||0===a%400}function u(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[gb]<0||a._a[gb]>11?gb:a._a[hb]<1||a._a[hb]>r(a._a[fb],a._a[gb])?hb:a._a[ib]<0||a._a[ib]>23?ib:a._a[jb]<0||a._a[jb]>59?jb:a._a[kb]<0||a._a[kb]>59?kb:a._a[lb]<0||a._a[lb]>999?lb:-1,a._pf._overflowDayOfYear&&(fb>b||b>hb)&&(b=hb),a._pf.overflow=b)}function v(a){a._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function w(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length)),a._isValid}function x(a){return a?a.toLowerCase().replace("_","-"):a}function y(a,b){return b.abbr=a,mb[a]||(mb[a]=new d),mb[a].set(b),mb[a]}function z(a){delete mb[a]}function A(a){var b,c,d,e,f=0,g=function(a){if(!mb[a]&&nb)try{require("./lang/"+a)}catch(b){}return mb[a]};if(!a)return bb.fn._lang;if(!k(a)){if(c=g(a))return c;a=[a]}for(;f0;){if(c=g(e.slice(0,b).join("-")))return c;if(d&&d.length>=b&&m(e,d,!0)>=b-1)break;b--}f++}return bb.fn._lang}function B(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function C(a){var b,c,d=a.match(rb);for(b=0,c=d.length;c>b;b++)d[b]=Pb[d[b]]?Pb[d[b]]:B(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function D(a,b){return a.isValid()?(b=E(b,a.lang()),Mb[b]||(Mb[b]=C(b)),Mb[b](a)):a.lang().invalidDate()}function E(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(sb.lastIndex=0;d>=0&&sb.test(a);)a=a.replace(sb,c),sb.lastIndex=0,d-=1;return a}function F(a,b){var c;switch(a){case"DDDD":return vb;case"YYYY":case"GGGG":case"gggg":return wb;case"YYYYY":case"GGGGG":case"ggggg":return xb;case"S":case"SS":case"SSS":case"DDD":return ub;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return zb;case"a":case"A":return A(b._l)._meridiemParse;case"X":return Cb;case"Z":case"ZZ":return Ab;case"T":return Bb;case"SSSS":return yb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"ww":case"W":case"WW":case"e":case"E":return tb;default:return c=new RegExp(N(M(a.replace("\\","")),"i"))}}function G(a){var b=(Ab.exec(a)||[])[0],c=(b+"").match(Hb)||["-",0,0],d=+(60*c[1])+q(c[2]);return"+"===c[0]?-d:d}function H(a,b,c){var d,e=c._a;switch(a){case"M":case"MM":null!=b&&(e[gb]=q(b)-1);break;case"MMM":case"MMMM":d=A(c._l).monthsParse(b),null!=d?e[gb]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[hb]=q(b));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=q(b));break;case"YY":e[fb]=q(b)+(q(b)>68?1900:2e3);break;case"YYYY":case"YYYYY":e[fb]=q(b);break;case"a":case"A":c._isPm=A(c._l).isPM(b);break;case"H":case"HH":case"h":case"hh":e[ib]=q(b);break;case"m":case"mm":e[jb]=q(b);break;case"s":case"ss":e[kb]=q(b);break;case"S":case"SS":case"SSS":case"SSSS":e[lb]=q(1e3*("0."+b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=G(b);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":a=a.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=b)}}function I(a){var b,c,d,e,f,g,h,i,j,k,l=[];if(!a._d){for(d=K(a),a._w&&null==a._a[hb]&&null==a._a[gb]&&(f=function(b){return b?b.length<3?parseInt(b,10)>68?"19"+b:"20"+b:b:null==a._a[fb]?bb().weekYear():a._a[fb]},g=a._w,null!=g.GG||null!=g.W||null!=g.E?h=X(f(g.GG),g.W||1,g.E,4,1):(i=A(a._l),j=null!=g.d?T(g.d,i):null!=g.e?parseInt(g.e,10)+i._week.dow:0,k=parseInt(g.w,10)||1,null!=g.d&&js(e)&&(a._pf._overflowDayOfYear=!0),c=S(e,0,a._dayOfYear),a._a[gb]=c.getUTCMonth(),a._a[hb]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=l[b]=d[b];for(;7>b;b++)a._a[b]=l[b]=null==a._a[b]?2===b?1:0:a._a[b];l[ib]+=q((a._tzm||0)/60),l[jb]+=q((a._tzm||0)%60),a._d=(a._useUTC?S:R).apply(null,l)}}function J(a){var b;a._d||(b=o(a._i),a._a=[b.year,b.month,b.day,b.hour,b.minute,b.second,b.millisecond],I(a))}function K(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function L(a){a._a=[],a._pf.empty=!0;var b,c,d,e,f,g=A(a._l),h=""+a._i,i=h.length,j=0;for(d=E(a._f,g).match(rb)||[],b=0;b0&&a._pf.unusedInput.push(f),h=h.slice(h.indexOf(c)+c.length),j+=c.length),Pb[e]?(c?a._pf.empty=!1:a._pf.unusedTokens.push(e),H(e,c,a)):a._strict&&!c&&a._pf.unusedTokens.push(e);a._pf.charsLeftOver=i-j,h.length>0&&a._pf.unusedInput.push(h),a._isPm&&a._a[ib]<12&&(a._a[ib]+=12),a._isPm===!1&&12===a._a[ib]&&(a._a[ib]=0),I(a),u(a)}function M(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function N(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function O(a){var b,c,d,e,f;if(0===a._f.length)return a._pf.invalidFormat=!0,a._d=new Date(0/0),void 0;for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function P(a){var b,c=a._i,d=Db.exec(c);if(d){for(a._pf.iso=!0,b=4;b>0;b--)if(d[b]){a._f=Fb[b-1]+(d[6]||" ");break}for(b=0;4>b;b++)if(Gb[b][1].exec(c)){a._f+=Gb[b][0];break}Ab.exec(c)&&(a._f+="Z"),L(a)}else a._d=new Date(c)}function Q(b){var c=b._i,d=ob.exec(c);c===a?b._d=new Date:d?b._d=new Date(+d[1]):"string"==typeof c?P(b):k(c)?(b._a=c.slice(0),I(b)):l(c)?b._d=new Date(+c):"object"==typeof c?J(b):b._d=new Date(c)}function R(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function S(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function T(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function U(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function V(a,b,c){var d=eb(Math.abs(a)/1e3),e=eb(d/60),f=eb(e/60),g=eb(f/24),h=eb(g/365),i=45>d&&["s",d]||1===e&&["m"]||45>e&&["mm",e]||1===f&&["h"]||22>f&&["hh",f]||1===g&&["d"]||25>=g&&["dd",g]||45>=g&&["M"]||345>g&&["MM",eb(g/30)]||1===h&&["y"]||["yy",h];return i[2]=b,i[3]=a>0,i[4]=c,U.apply({},i)}function W(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=bb(a).add("d",f),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function X(a,b,c,d,e){var f,g,h=new Date(Date.UTC(a,0)).getUTCDay();return c=null!=c?c:e,f=e-h+(h>d?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:s(a-1)+g}}function Y(a){var b=a._i,c=a._f;return"undefined"==typeof a._pf&&v(a),null===b?bb.invalid({nullInput:!0}):("string"==typeof b&&(a._i=b=A().preparse(b)),bb.isMoment(b)?(a=g({},b),a._d=new Date(+b._d)):c?k(c)?O(a):L(a):Q(a),new e(a))}function Z(a,b){bb.fn[a]=bb.fn[a+"s"]=function(a){var c=this._isUTC?"UTC":"";return null!=a?(this._d["set"+c+b](a),bb.updateOffset(this),this):this._d["get"+c+b]()}}function $(a){bb.duration.fn[a]=function(){return this._data[a]}}function _(a,b){bb.duration.fn["as"+a]=function(){return+this/b}}function ab(a){var b=!1,c=bb;"undefined"==typeof ender&&(this.moment=a?function(){return!b&&console&&console.warn&&(b=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),c.apply(null,arguments)}:bb)}for(var bb,cb,db="2.4.0",eb=Math.round,fb=0,gb=1,hb=2,ib=3,jb=4,kb=5,lb=6,mb={},nb="undefined"!=typeof module&&module.exports,ob=/^\/?Date\((\-?\d+)/i,pb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,qb=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,rb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,sb=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,tb=/\d\d?/,ub=/\d{1,3}/,vb=/\d{3}/,wb=/\d{1,4}/,xb=/[+\-]?\d{1,6}/,yb=/\d+/,zb=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Ab=/Z|[\+\-]\d\d:?\d\d/i,Bb=/T/i,Cb=/[\+\-]?\d+(\.\d{1,3})?/,Db=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d:?\d\d|Z)?)?$/,Eb="YYYY-MM-DDTHH:mm:ssZ",Fb=["YYYY-MM-DD","GGGG-[W]WW","GGGG-[W]WW-E","YYYY-DDD"],Gb=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Hb=/([\+\-]|\d\d)/gi,Ib="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Jb={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Kb={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Lb={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Mb={},Nb="DDD w W M D d".split(" "),Ob="M D H h m s w W".split(" "),Pb={M:function(){return this.month()+1},MMM:function(a){return this.lang().monthsShort(this,a)},MMMM:function(a){return this.lang().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.lang().weekdaysMin(this,a)},ddd:function(a){return this.lang().weekdaysShort(this,a)},dddd:function(a){return this.lang().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return i(this.year()%100,2)},YYYY:function(){return i(this.year(),4)},YYYYY:function(){return i(this.year(),5)},gg:function(){return i(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return i(this.weekYear(),5)},GG:function(){return i(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return i(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return q(this.milliseconds()/100)},SS:function(){return i(q(this.milliseconds()/10),2)},SSS:function(){return i(this.milliseconds(),3)},SSSS:function(){return i(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+":"+i(q(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(10*a/6),4)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()}},Qb=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Nb.length;)cb=Nb.pop(),Pb[cb+"o"]=c(Pb[cb],cb);for(;Ob.length;)cb=Ob.pop(),Pb[cb+cb]=b(Pb[cb],2);for(Pb.DDDD=b(Pb.DDD,3),g(d.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a){var b,c,d;for(this._monthsParse||(this._monthsParse=[]),b=0;12>b;b++)if(this._monthsParse[b]||(c=bb.utc([2e3,b]),d="^"+this.months(c,"")+"|^"+this.monthsShort(c,""),this._monthsParse[b]=new RegExp(d.replace(".",""),"i")),this._monthsParse[b].test(a))return b},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=bb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b){var c=this._calendar[a];return"function"==typeof c?c.apply(b):c},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",preparse:function(a){return a},postformat:function(a){return a},week:function(a){return W(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),bb=function(b,c,d,e){return"boolean"==typeof d&&(e=d,d=a),Y({_i:b,_f:c,_l:d,_strict:e,_isUTC:!1})},bb.utc=function(b,c,d,e){var f;return"boolean"==typeof d&&(e=d,d=a),f=Y({_useUTC:!0,_isUTC:!0,_l:d,_i:b,_f:c,_strict:e}).utc()},bb.unix=function(a){return bb(1e3*a)},bb.duration=function(a,b){var c,d,e,g=bb.isDuration(a),h="number"==typeof a,i=g?a._input:h?{}:a,j=null;return h?b?i[b]=a:i.milliseconds=a:(j=pb.exec(a))?(c="-"===j[1]?-1:1,i={y:0,d:q(j[hb])*c,h:q(j[ib])*c,m:q(j[jb])*c,s:q(j[kb])*c,ms:q(j[lb])*c}):(j=qb.exec(a))&&(c="-"===j[1]?-1:1,e=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*c},i={y:e(j[2]),M:e(j[3]),d:e(j[4]),h:e(j[5]),m:e(j[6]),s:e(j[7]),w:e(j[8])}),d=new f(i),g&&a.hasOwnProperty("_lang")&&(d._lang=a._lang),d},bb.version=db,bb.defaultFormat=Eb,bb.updateOffset=function(){},bb.lang=function(a,b){var c;return a?(b?y(x(a),b):null===b?(z(a),a="en"):mb[a]||A(a),c=bb.duration.fn._lang=bb.fn._lang=A(a),c._abbr):bb.fn._lang._abbr},bb.langData=function(a){return a&&a._lang&&a._lang._abbr&&(a=a._lang._abbr),A(a)},bb.isMoment=function(a){return a instanceof e},bb.isDuration=function(a){return a instanceof f},cb=Qb.length-1;cb>=0;--cb)p(Qb[cb]);for(bb.normalizeUnits=function(a){return n(a)},bb.invalid=function(a){var b=bb.utc(0/0);return null!=a?g(b._pf,a):b._pf.userInvalidated=!0,b},bb.parseZone=function(a){return bb(a).parseZone()},g(bb.fn=e.prototype,{clone:function(){return bb(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){return D(bb(this).utc(),"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds()]},isValid:function(){return w(this)},isDSTShifted:function(){return this._a?this.isValid()&&m(this._a,(this._isUTC?bb.utc(this._a):bb(this._a)).toArray())>0:!1},parsingFlags:function(){return g({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(a){var b=D(this,a||bb.defaultFormat);return this.lang().postformat(b)},add:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,1),this},subtract:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,-1),this},diff:function(a,b,c){var d,e,f=this._isUTC?bb(a).zone(this._offset||0):bb(a).local(),g=6e4*(this.zone()-f.zone());return b=n(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+f.daysInMonth()),e=12*(this.year()-f.year())+(this.month()-f.month()),e+=(this-bb(this).startOf("month")-(f-bb(f).startOf("month")))/d,e-=6e4*(this.zone()-bb(this).startOf("month").zone()-(f.zone()-bb(f).startOf("month").zone()))/d,"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:h(e)},from:function(a,b){return bb.duration(this.diff(a)).lang(this.lang()._abbr).humanize(!b)},fromNow:function(a){return this.from(bb(),a)},calendar:function(){var a=this.diff(bb().zone(this.zone()).startOf("day"),"days",!0),b=-6>a?"sameElse":-1>a?"lastWeek":0>a?"lastDay":1>a?"sameDay":2>a?"nextDay":7>a?"nextWeek":"sameElse";return this.format(this.lang().calendar(b,this))},isLeapYear:function(){return t(this.year())},isDST:function(){return this.zone()+bb(a).startOf(b)},isBefore:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)<+bb(a).startOf(b)},isSame:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)===+bb(a).startOf(b)},min:function(a){return a=bb.apply(null,arguments),this>a?this:a},max:function(a){return a=bb.apply(null,arguments),a>this?this:a},zone:function(a){var b=this._offset||0;return null==a?this._isUTC?b:this._d.getTimezoneOffset():("string"==typeof a&&(a=G(a)),Math.abs(a)<16&&(a=60*a),this._offset=a,this._isUTC=!0,b!==a&&j(this,bb.duration(b-a,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?bb(a).zone():0,0===(this.zone()-a)%60},daysInMonth:function(){return r(this.year(),this.month())},dayOfYear:function(a){var b=eb((bb(this).startOf("day")-bb(this).startOf("year"))/864e5)+1;return null==a?b:this.add("d",a-b)},weekYear:function(a){var b=W(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==a?b:this.add("y",a-b)},isoWeekYear:function(a){var b=W(this,1,4).year;return null==a?b:this.add("y",a-b)},week:function(a){var b=this.lang().week(this);return null==a?b:this.add("d",7*(a-b))},isoWeek:function(a){var b=W(this,1,4).week;return null==a?b:this.add("d",7*(a-b))},weekday:function(a){var b=(this.day()+7-this.lang()._week.dow)%7;return null==a?b:this.add("d",a-b)},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},get:function(a){return a=n(a),this[a]()},set:function(a,b){return a=n(a),"function"==typeof this[a]&&this[a](b),this},lang:function(b){return b===a?this._lang:(this._lang=A(b),this)}}),cb=0;cb/g,">").replace(/"/g,""").replace(/'/g,"'");}else{return B;}}function k(B){return B.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&");}function w(C,F,D,E){var B=0;for(;B0){if(w(z.arrayAccessFormPaths,D,B,C)){D[B]=[D[B]];}}}function a(G){var E=G.split(/[-T:+Z]/g);var F=new Date(E[0],E[1]-1,E[2]);var D=E[5].split(".");F.setHours(E[3],E[4],D[0]);if(D.length>1){F.setMilliseconds(D[1]);}if(E[6]&&E[7]){var C=E[6]*60+Number(E[7]);var B=/\d\d-\d\d:\d\d$/.test(G)?"-":"+";C=0+(B=="-"?-1*C:C);F.setMinutes(F.getMinutes()-C-F.getTimezoneOffset());}else{if(G.indexOf("Z",G.length-1)!==-1){F=new Date(Date.UTC(F.getFullYear(),F.getMonth(),F.getDate(),F.getHours(),F.getMinutes(),F.getSeconds(),F.getMilliseconds()));}}return F;}function q(D,B,C){if(z.datetimeAccessFormPaths.length>0){var E=C.split(".#")[0];if(w(z.datetimeAccessFormPaths,D,B,E)){return a(D);}else{return D;}}else{return D;}}function b(E,C,B,D){if(C==h.ELEMENT_NODE&&z.xmlElementsFilter.length>0){return w(z.xmlElementsFilter,E,B,D);}else{return true;}}function A(D,J){if(D.nodeType==h.DOCUMENT_NODE){var K=new Object;var B=D.childNodes;for(var L=0;L1&&K.__text!=null&&z.skipEmptyTextNodesForObj){if((z.stripWhitespaces&&K.__text=="")||(K.__text.trim()=="")){delete K.__text;}}}}}delete K.__cnt;if(z.enableToStringFunc&&(K.__text!=null||K.__cdata!=null)){K.toString=function(){return(this.__text!=null?this.__text:"")+(this.__cdata!=null?this.__cdata:"");};}return K;}else{if(D.nodeType==h.TEXT_NODE||D.nodeType==h.CDATA_SECTION_NODE){return D.nodeValue;}}}}function o(I,F,H,C){var E="<"+((I!=null&&I.__prefix!=null)?(I.__prefix+":"):"")+F;if(H!=null){for(var G=0;G";}function v(C,B){return C.indexOf(B,C.length-B.length)!==-1;}function y(C,B){if((z.arrayAccessForm=="property"&&v(B.toString(),("_asArray")))||B.toString().indexOf(z.attributePrefix)==0||B.toString().indexOf("__")==0||(C[B] instanceof Function)){return true;}else{return false;}}function m(D){var C=0;if(D instanceof Object){for(var B in D){if(y(D,B)){continue;}C++;}}return C;}function l(D,B,C){return z.jsonPropertiesFilter.length==0||C==""||w(z.jsonPropertiesFilter,D,B,C);}function c(D){var C=[];if(D instanceof Object){for(var B in D){if(B.toString().indexOf("__")==-1&&B.toString().indexOf(z.attributePrefix)==0){C.push(B);}}}return C;}function g(C){var B="";if(C.__cdata!=null){B+="";}if(C.__text!=null){if(z.escapeMode){B+=s(C.__text);}else{B+=C.__text;}}return B;}function d(C){var B="";if(C instanceof Object){B+=g(C);}else{if(C!=null){if(z.escapeMode){B+=s(C);}else{B+=C;}}}return B;}function p(C,B){if(C===""){return B;}else{return C+"."+B;}}function f(D,G,F,E){var B="";if(D.length==0){B+=o(D,G,F,true);}else{for(var C=0;C0){for(var E in I){if(y(I,E)||(H!=""&&!l(I,E,p(H,E)))){continue;}var D=I[E];var G=c(D);if(D==null||D==undefined){B+=o(D,E,G,true);}else{if(D instanceof Object){if(D instanceof Array){B+=f(D,E,G,H);}else{if(D instanceof Date){B+=o(D,E,G,false);B+=D.toISOString();B+=j(D,E);}else{var C=m(D);if(C>0||D.__text!=null||D.__cdata!=null){B+=o(D,E,G,false);B+=e(D,p(H,E));B+=j(D,E);}else{B+=o(D,E,G,true);}}}}else{B+=o(D,E,G,false);B+=d(D);B+=j(D,E);}}}}B+=d(I);return B;}this.parseXmlString=function(D){var F=window.ActiveXObject||"ActiveXObject" in window;if(D===undefined){return null;}var E;if(window.DOMParser){var G=new window.DOMParser();var B=null;if(!F){try{B=G.parseFromString("INVALID","text/xml").getElementsByTagName("parsererror")[0].namespaceURI;}catch(C){B=null;}}try{E=G.parseFromString(D,"text/xml");if(B!=null&&E.getElementsByTagNameNS(B,"parsererror").length>0){E=null;}}catch(C){E=null;}}else{if(D.indexOf("")+2);}E=new ActiveXObject("Microsoft.XMLDOM");E.async="false";E.loadXML(D);}return E;};this.asArray=function(B){if(B===undefined||B==null){return[];}else{if(B instanceof Array){return B;}else{return[B];}}};this.toXmlDateTime=function(B){if(B instanceof Date){return B.toISOString();}else{if(typeof(B)==="number"){return new Date(B).toISOString();}else{return null;}}};this.asDateTime=function(B){if(typeof(B)=="string"){return a(B);}else{return B;}};this.xml2json=function(B){return A(B);};this.xml_str2json=function(B){var C=this.parseXmlString(B);if(C!=null){return this.xml2json(C);}else{return null;}};this.json2xml_str=function(B){return e(B,"");};this.json2xml=function(C){var B=this.json2xml_str(C);return this.parseXmlString(B);};this.getVersion=function(){return t;};};})); -------------------------------------------------------------------------------- /public/views/environment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 11 |
12 | 23 |
24 |
25 |
26 | 28 | 29 | 32 | 33 |
34 |
35 |
    36 |
  • ... 38 |
  • 39 |
  • 41 | {{key}} 42 |
  • 43 |
44 |
45 |
46 |
47 |
48 |
    49 |
  • Circle stands for endpoint
  • 50 |
  • Arrow stands for route
  • 51 |
  • Services:
  • 52 |
  • 53 |
    54 |
    {{service.name}}
    55 |
    56 |
  • 57 |
  • ---
  • 58 |
  • Nodes: {{graph.nodesDataSet.length}}
  • 59 |
  • Edges: {{graph.edgesDataSet.length}}
  • 60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | {{service.name}} 69 |
70 | {{route.name}} 71 | 72 | 73 |
{{route.schema}}
74 | 75 | 76 | 77 | 78 |
79 |
80 |
81 |
82 |
83 |
84 | 85 | -------------------------------------------------------------------------------- /public/views/environments.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 10 |
11 |

Choose environment:

12 |

13 | #{{environment.name}} 14 |

15 | 16 |
17 |
18 |

19 | No environments :( 20 |

21 |
22 |
23 | -------------------------------------------------------------------------------- /public/views/options.html: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | **Problem**: Managing hundreds of complex bus routes manually has become impractical\ 3 | **Solution**: Automatic route graph generation\ 4 | **Status**: Finished, article is published 5 | 6 | ---- 7 | 8 | ## Camel-graph 9 | ![camel-graph](https://user-images.githubusercontent.com/884337/50089472-812aca00-0238-11e9-8988-eb70b665ef81.png) 10 | 11 | Camel-graph is the viewer for routes in servicemix and camel applications. [Article with intention and result explanation (Russian)](https://habr.com/ru/post/435594/) 12 | ## Requirenment 13 | Viewer work over JMS with help of jolokia, so jolokia is required to be in your application or in servicemix. 14 | ## Example 15 | ![main-sm](https://user-images.githubusercontent.com/884337/50090052-137f9d80-023a-11e9-8bd3-24df76b7e32f.png) 16 | ## Configuration - services.json 17 | ```json 18 | { 19 | "environments": [ 20 | { 21 | "name": "dev", 22 | "services": [ 23 | { 24 | "name": "smx", 25 | "url": "http://localhost:8181", 26 | "color": "#62aa34", 27 | "authorization": { 28 | "login": "smx", 29 | "pass": "smx" 30 | } 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | 37 | ``` 38 | ## Launch 39 | ``` 40 | go build 41 | ./camel-graph -httpPort=8080 42 | ``` 43 | -------------------------------------------------------------------------------- /services.json: -------------------------------------------------------------------------------- 1 | { 2 | "environments": [ 3 | { 4 | "name": "dev", 5 | "services": [ 6 | { 7 | "name": "smx", 8 | "url": "http://localhost:8181", 9 | "color": "#62aa34", 10 | "authorization": { 11 | "login": "smx", 12 | "pass": "smx" 13 | } 14 | }, 15 | { 16 | "name": "spring_service", 17 | "url": "http://localhost:10002", 18 | "color": "#272dc5" 19 | } 20 | ] 21 | } 22 | ] 23 | } --------------------------------------------------------------------------------