@skydive-project/skydive-ui
Version:
Skydive WebUI v2
1,084 lines (973 loc) • 32.9 kB
text/typescript
/*
* Copyright (C) 2020 Sylvain Afchain
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Node, Link, NodeAttrs, LinkAttrs } from './Topology'
import Tools from './Tools'
const SHOW_DEBUG = false
const WEIGHT_K8S_FEDERATION = 3000
const WEIGHT_K8S_CLUSTER = 3010
const WEIGHT_K8S_NODE = 3020
const WEIGHT_K8S_NAMESPACE = 3030
const WEIGHT_K8S_POD = 3040
const WEIGHT_K8S_CONTAINER = 3050
const WEIGHT_K8S_SERVICE = 3060
const WEIGHT_K8S_OTHER = 3200
const WEIGHT_PHY_FABRIC = 5010
const WEIGHT_PHY_HOST = 5020
const WEIGHT_PHY_BRIDGES = 5030
const WEIGHT_PHY_NET = 5050
const WEIGHT_PHY_PORTS = 5060
const WEIGHT_VIRT_NAMESPACE = 7010
const WEIGHT_VIRT_VMS = 7020
const WEIGHT_VIRT_CONTAINERS = 7030
const WEIGHT_VIRT_BRIDGES = 7040
const WEIGHT_VIRT_NET = 7050
const WEIGHT_VIRT_PORTS = 7060
const WEIGHT_NONE = 20000
export interface Filter {
id: string
label: string
category: string
tag?: string
callback: () => void
}
export interface MenuItem {
class: string
text: string
disabled: boolean
callback: () => void
}
export interface GraphField {
type: string,
data: any
}
export interface NodeDataField {
field: string
title?: string
expanded: boolean
icon: string
iconClass?: string
sortKeys?: (data: any) => Array<string>
filterKeys?: (data: any) => Array<string>
normalizer?: (data: any) => any
graph?: (data: any) => GraphField
}
export interface LinkDataField {
field: string
title: string
expanded: boolean
icon: string
}
export interface Config {
subTitle?(subTitle: string): string
filters?(): Promise<Array<Filter>>
defaultFilter?(): Filter
nodeAttrs?(attrs: NodeAttrs | null, node: Node): NodeAttrs
nodeSortFnc?(a: Node, b: Node): number
nodeClicked?(node: Node): void
nodeDblClicked?(node: Node): void
nodeMenu?(items: Array<MenuItem>, node: Node): Array<MenuItem>
nodeTags?(tags: Array<string>, node: Node): Array<string>
defaultNodeTag?(): string
nodeTabTitle?(node: Node): string
groupSize?(): number
groupType?(node: Node): string | undefined
groupName?(node: Node): string | undefined
weightTitles?(): Map<number, string>
suggestions?(): Array<string>
nodeDataFields?(dataFields: Array<NodeDataField>): Array<NodeDataField>
linkAttrs?(attrs: LinkAttrs | null, link: Link): LinkAttrs
linkTabTitle?(link: Link): string
linkDataFields?(dataFields: Array<LinkDataField>): Array<LinkDataField>
defaultLinkTagMode?(): number
}
class ConfigWithID {
id: string
config: Config
constructor(id: string, config: Config) {
this.id = id
this.config = config
}
}
export default class ConfigReducer {
default: DefaultConfig
configs: Array<ConfigWithID>
constructor() {
this.default = new DefaultConfig()
this.configs = new Array<ConfigWithID>()
}
append(id: string, config: Config) {
this.configs.push(new ConfigWithID(id, config))
}
appendURL(id: string, url: string): Promise<Config | undefined> {
var promise = new Promise<Config>((resolve, reject) => {
if (!url) {
resolve()
return
}
fetch(url).then(resp => {
resp.text().then(data => {
try {
var config = eval(data)
this.append(id, config)
resolve(config)
} catch (e) {
reject(e)
}
})
}).catch((reason) => {
throw Error(reason)
})
})
return promise
}
subTitle(): string {
var subTitle = this.default.subTitle()
for (let c of this.configs) {
if (c.config.subTitle) {
subTitle = c.config.subTitle(subTitle)
}
}
return subTitle
}
filters(): Promise<Array<Filter>> {
var promise = new Promise<Array<Filter>>(resolve => {
this.default.filters().then(filters => {
var all = new Array<Promise<Array<Filter>>>()
for (let c of this.configs) {
if (c.config.filters) {
all.push(c.config.filters())
}
}
if (all.length > 0) {
Promise.all(all).then(values => {
for (let vfs of values) {
for (let filter of vfs) {
if (!filters.some(f => filter.id === f.id)) {
filters.push(filter)
}
}
}
resolve(filters)
})
} else {
resolve(filters)
}
})
})
return promise
}
defaultFilter(): Filter {
var defaultFilter = this.default.defaultFilter()
for (let c of this.configs) {
if (c.config.defaultFilter) {
defaultFilter = c.config.defaultFilter()
}
}
return defaultFilter
}
nodeAttrs(node: Node): NodeAttrs {
var attrs = this.default.nodeAttrs(node)
for (let c of this.configs) {
if (c.config.nodeAttrs) {
attrs = c.config.nodeAttrs(attrs, node)
}
}
return attrs
}
nodeSortFnc(a: Node, b: Node): number {
var fnc = this.default.nodeSortFnc
for (let c of this.configs) {
if (c.config.nodeSortFnc) {
fnc = c.config.nodeSortFnc
}
}
return fnc(a, b)
}
nodeClicked(node: Node): void {
var fnc = this.default.nodeClicked
for (let c of this.configs) {
if (c.config.nodeClicked) {
fnc = c.config.nodeClicked
}
}
return fnc(node)
}
nodeDblClicked(node: Node): void {
var fnc = this.default.nodeDblClicked
for (let c of this.configs) {
if (c.config.nodeDblClicked) {
fnc = c.config.nodeDblClicked
}
}
return fnc(node)
}
nodeMenu(node: Node): Array<MenuItem> {
var items = this.default.nodeMenu(node)
for (let c of this.configs) {
if (c.config.nodeMenu) {
items = c.config.nodeMenu(items, node)
}
}
return items
}
nodeTags(node: Node): Array<string> {
var tags = this.default.nodeTags(node)
for (let c of this.configs) {
if (c.config.nodeTags) {
tags = c.config.nodeTags([], node)
}
}
return tags
}
defaultNodeTag(): string {
var defaultNodeTag = this.default.defaultNodeTag()
for (let c of this.configs) {
if (c.config.defaultNodeTag) {
defaultNodeTag = c.config.defaultNodeTag()
}
}
return defaultNodeTag
}
nodeTabTitle(node: Node): string {
var nodeTabTitle = this.default.nodeTabTitle(node)
for (let c of this.configs) {
if (c.config.nodeTabTitle) {
nodeTabTitle = c.config.nodeTabTitle(node)
}
}
return nodeTabTitle
}
groupSize(): number {
var size = this.default.groupSize()
for (let c of this.configs) {
if (c.config.groupSize) {
size = c.config.groupSize()
}
}
return size
}
groupType(node: Node): string | undefined {
var groupType = this.default.groupType(node)
for (let c of this.configs) {
if (c.config.groupType) {
groupType = c.config.groupType(node)
}
}
return groupType
}
groupName(node: Node): string | undefined {
var groupName = this.default.groupName(node)
for (let c of this.configs) {
if (c.config.groupName) {
groupName = c.config.groupName(node)
}
}
return groupName
}
weightTitles(): Map<number, string> {
var titles = this.default.weightTitles()
for (let c of this.configs) {
if (c.config.weightTitles) {
titles = c.config.weightTitles()
}
}
return titles
}
suggestions(): Array<string> {
var result = this.default.suggestions()
for (let c of this.configs) {
if (c.config.suggestions) {
result = c.config.suggestions()
}
}
return result
}
nodeDataFields(): Array<NodeDataField> {
var fields = this.default.nodeDataFields()
for (let c of this.configs) {
if (c.config.nodeDataFields) {
fields = c.config.nodeDataFields(fields)
}
}
return fields
}
linkAttrs(link: Link): LinkAttrs {
var attrs = this.default.linkAttrs(link)
for (let c of this.configs) {
if (c.config.linkAttrs) {
attrs = c.config.linkAttrs(attrs, link)
}
}
return attrs
}
linkTabTitle(link: Link): string {
var title = this.default.linkTabTitle(link)
for (let c of this.configs) {
if (c.config.linkTabTitle) {
title = c.config.linkTabTitle(link)
}
}
return title
}
linkDataFields(): Array<LinkDataField> {
var fields = this.default.linkDataFields()
for (let c of this.configs) {
if (c.config.linkDataFields) {
fields = c.config.linkDataFields(fields)
}
}
return fields
}
defaultLinkTagMode(): number {
var size = this.default.defaultLinkTagMode()
for (let c of this.configs) {
if (c.config.defaultLinkTagMode) {
size = c.config.defaultLinkTagMode()
}
}
return size
}
}
class DefaultConfig {
subTitle(): string {
return ""
}
filters(): Promise<Array<Filter>> {
var promise = new Promise<Array<Filter>>(resolve => {
const nf = (name: string, type: string, tag: string, limit: number) => {
return {
id: name,
label: name,
category: type,
tag: tag,
callback: () => {
var gremlin = "G.V().Has(" +
"'Name','" + name + "'," +
"'Type','" + type + "').descendants(10).as('k8s').Out().In().Has('Type', 'netns').Descendants(10).as('infra').select('k8s', 'infra').SubGraph()"
window.App.setGremlinFilter(gremlin)
}
}
}
var filters = new Array<Filter>()
// TODO replace by only one query once merged:
// https://github.com/skydive-project/skydive/pull/2338
var api = new window.API.TopologyApi(window.App.apiConf)
api.searchTopology({ GremlinQuery: `G.V().Has("Type", "host").Values("Name")` }).then(result => {
for (let name of result) {
filters.push(nf(name, "host", "infrastucture", 1))
}
api.searchTopology({ GremlinQuery: `G.V().Has("Type", "namespace").Values("Name")` }).then(result => {
if (result) {
for (let name of result) {
filters.push(nf(name, "namespace", "kubernetes", 10))
}
resolve(filters)
}
})
})
})
return promise
}
defaultFilter(): Filter {
return {
id: "default",
label: "Default",
category: "default",
tag: "infrastructure",
callback: () => {
var gremlin = ""
window.App.setGremlinFilter(gremlin)
}
}
}
private newAttrs(node: Node): NodeAttrs {
var name = node.data.Name
if (name.length > 24) {
name = node.data.Name.substring(0, 24) + "."
}
var attrs = {
classes: [node.data.Type],
name: name,
icon: "\uf192",
href: '',
iconClass: '',
weight: 0,
badges: []
}
return attrs
}
nodeAttrs(node: Node): NodeAttrs {
switch (node.data.Manager) {
case "k8s":
return this.nodeAttrsK8s(node)
default:
return this.nodeAttrsInfra(node)
}
}
private nodeAttrsK8s(node: Node): NodeAttrs {
var attrs = this.newAttrs(node)
switch (node.data.Type) {
case "cluster":
attrs.href = "assets/icons/cluster.png"
attrs.weight = WEIGHT_K8S_CLUSTER
break
/*
case "configmap":
attrs.href = "assets/icons/configmap.png"
attrs.weight = WEIGHT_K8S_POD
break
case "cronjob":
attrs.href = "assets/icons/cronjob.png"
attrs.weight = WEIGHT_K8S_POD
break
case "daemonset":
attrs.href = "assets/icons/daemonset.png"
attrs.weight = WEIGHT_K8S_POD
break
case "deployment":
attrs.href = "assets/icons/deployment.png"
attrs.weight = WEIGHT_K8S_POD
break
case "endpoints":
attrs.href = "assets/icons/endpoints.png"
attrs.weight = WEIGHT_K8S_POD
break
case "ingress":
attrs.href = "assets/icons/ingress.png"
attrs.weight = WEIGHT_K8S_POD
break
case "job":
attrs.href = "assets/icons/job.png"
attrs.weight = WEIGHT_K8S_POD
break
case "persistentvolume":
attrs.href = "assets/icons/persistentvolume.png"
attrs.weight = WEIGHT_K8S_POD
break
case "persistentvolumeclaim":
attrs.href = "assets/icons/persistentvolumeclaim.png"
attrs.weight = WEIGHT_K8S_POD
break
case "networkpolicy":
attrs.href = "assets/icons/networkpolicy.png"
attrs.weight = WEIGHT_K8S_POD
break
case "replicaset":
attrs.href = "assets/icons/replicaset.png"
attrs.weight = WEIGHT_K8S_POD
break
case "replicationcontroller":
attrs.href = "assets/icons/replicationcontroller.png"
attrs.weight = WEIGHT_K8S_POD
break
case "secret":
attrs.href = "assets/icons/secret.png"
attrs.weight = WEIGHT_K8S_POD
break
case "statefulset":
attrs.href = "assets/icons/statefulset.png"
attrs.weight = WEIGHT_K8S_POD
break
case "storageclass":
attrs.href = "assets/icons/storageclass.png"
attrs.weight = WEIGHT_K8S_NODE
break
*/
case "node":
attrs.icon = "\uf109"
attrs.weight = WEIGHT_K8S_NODE
break
case "namespace":
attrs.icon = "\uf24d"
attrs.weight = WEIGHT_K8S_NAMESPACE
break
case "pod":
attrs.href = "assets/icons/pod.png"
attrs.weight = WEIGHT_K8S_POD
break
case "container":
attrs.href = "assets/icons/container.png"
attrs.weight = WEIGHT_K8S_CONTAINER
break
case "service":
attrs.href = "assets/icons/service.png"
attrs.weight = WEIGHT_K8S_SERVICE
break
default:
attrs.href = "assets/icons/k8s.png"
attrs.weight = WEIGHT_K8S_OTHER
}
if (SHOW_DEBUG) {
attrs.name = attrs.weight.toString() + "|" + attrs.name
}
return attrs
}
private nodeAttrsInfra(node: Node): NodeAttrs {
var attrs = this.newAttrs(node)
switch (node.data.Type) {
case "host":
attrs.icon = "\uf109"
attrs.weight = WEIGHT_PHY_HOST
break
case "switch":
case "bridge":
attrs.icon = "\uf6ff"
attrs.weight = WEIGHT_PHY_BRIDGES
break
case "patch":
case "port":
case "switchport":
attrs.icon = "\uf0e8"
attrs.weight = WEIGHT_PHY_PORTS
break
case "erspan":
attrs.icon = "\uf1e0"
attrs.weight = WEIGHT_PHY_PORTS
break
case "device":
case "internal":
case "interface":
case "tun":
case "tap":
attrs.icon = "\uf796"
attrs.weight = WEIGHT_PHY_NET
break
case "geneve":
case "vxlan":
case "gre":
case "gretap":
attrs.icon = "\uf55b"
attrs.weight = WEIGHT_VIRT_NET
break
case "veth":
attrs.icon = "\uf4d7"
attrs.weight = WEIGHT_VIRT_NET
break
case "ovsport":
attrs.icon = "\uf0e8"
attrs.weight = WEIGHT_VIRT_PORTS
break
case "ovsbridge":
case "openvswitch":
attrs.icon = "\uf6ff"
attrs.weight = WEIGHT_VIRT_BRIDGES
break
case "netns":
attrs.icon = "\uf24d"
attrs.weight = WEIGHT_VIRT_NAMESPACE
break
case "libvirt":
attrs.icon = "\uf109"
attrs.weight = WEIGHT_VIRT_VMS
break
case "container":
attrs.icon = "\uf49e"
attrs.weight = WEIGHT_VIRT_CONTAINERS
break
default:
attrs.icon = "\uf796"
attrs.weight = WEIGHT_NONE
}
if (node.data.IPV4 && node.data.IPV4.length) {
attrs.weight = WEIGHT_PHY_NET
}
if (node.data.Probe === "fabric") {
attrs.weight = WEIGHT_PHY_FABRIC
}
if (node.data.OfPort) {
attrs.weight = WEIGHT_VIRT_PORTS
}
var virt = ["tap", "veth", "tun", "openvswitch"]
if (node.data.Driver && virt.indexOf(node.data.Driver) > 0) {
attrs.weight = WEIGHT_VIRT_NET
}
if (node.data.Manager === "docker") {
attrs.badges = [{ text: "\uf395", iconClass: 'font-brands', fill: '#3888ae', stroke: '#fff' }]
} else if (node.data.Manager === "runc") {
attrs.badges = [{ text: "\uf7bc", iconClass: 'font-brands', fill: '#000', stroke: '#f44336' }]
}
if (node.data.Captures) {
attrs.badges = [{ text: "\uf03d" }]
}
if (SHOW_DEBUG) {
attrs.name = attrs.weight.toString() + "|" + attrs.name
}
return attrs
}
nodeSortFnc(a: Node, b: Node): number {
return a.data.Name.localeCompare(b.data.Name)
}
nodeClicked(node: Node): void {
window.App.tc.selectNode(node.id)
}
nodeDblClicked(node: Node): void {
window.App.tc.expand(node)
}
nodeMenu(node: Node): Array<MenuItem> {
return [
{
class: "", text: "Capture", disabled: false, callback: () => {
var api = new window.API.CapturesApi(window.App.apiConf)
api.createCapture({ GremlinQuery: `G.V('${node.id}')` }).then(result => {
console.log(result)
})
}
},
{ class: "", text: "Capture all", disabled: true, callback: () => { console.log("Capture all") } },
{ class: "", text: "Injection", disabled: false, callback: () => { console.log("Injection") } },
//{ class: "", text: "Flows", disabled: false, callback: () => { console.log("Flows") } },
//{ class: "", text: "Filter NS(demo)", disabled: false, callback: () => { window.App.loadExtraConfig("/assets/nsconfig.js") } }
]
}
nodeTags(data: any): Array<string> {
if (data.Manager && data.Manager === "k8s") {
switch (data.Type) {
case "namespace":
case "pod":
case "container":
return ["kubernetes", "compute", "network"]
default:
return ["kubernetes"]
}
} else {
switch (data.Type) {
case "container":
return ["infrastructure", "compute", "network"]
case "netns":
case "veth":
return ["infrastructure", "network"]
default:
return ["infrastructure"]
}
}
}
defaultNodeTag() {
return "infrastructure"
}
nodeTabTitle(node: Node): string {
return node.data.Name.substring(0, 8)
}
groupSize(): number {
return 3
}
groupType(node: Node): string | undefined {
var nodeType = node.data.Type
if (!nodeType) {
return
}
switch (nodeType) {
case "configmap":
case "cronjob":
case "daemonset":
case "deployment":
case "endpoints":
case "ingress":
case "job":
case "persistentvolume":
case "persistentvolumeclaim":
case "pod":
case "networkpolicy":
case "replicaset":
case "replicationcontroller":
case "secret":
case "service":
case "statefulset":
return "app"
default:
return nodeType
}
}
groupName(node: Node): string | undefined {
if (node.data.K8s) {
var labels = node.data.K8s.Labels
if (!labels) {
return name + "(s)"
}
var app = labels["k8s-app"] || labels["app"]
if (!app) {
return "default"
}
return app
}
var nodeType = this.groupType(node)
if (!nodeType) {
return
}
return nodeType + "(s)"
}
weightTitles(): Map<number, string> {
var wt = new Map<number, string>()
wt.set(WEIGHT_K8S_FEDERATION, "k8s-Federations")
wt.set(WEIGHT_K8S_CLUSTER, "k8s-clusters")
wt.set(WEIGHT_K8S_NODE, "k8s-nodes")
wt.set(WEIGHT_K8S_NAMESPACE, "k8s-namespaces")
wt.set(WEIGHT_K8S_POD, "k8s-pods")
wt.set(WEIGHT_K8S_CONTAINER, "k8s-containers")
wt.set(WEIGHT_K8S_SERVICE, "k8s-services")
wt.set(WEIGHT_K8S_OTHER, "k8s-more")
wt.set(WEIGHT_VIRT_VMS, "virt-VMs")
wt.set(WEIGHT_VIRT_CONTAINERS, "virt-containers")
wt.set(WEIGHT_VIRT_BRIDGES, "virt-bridges")
wt.set(WEIGHT_VIRT_NAMESPACE, "virt-namespaces")
wt.set(WEIGHT_VIRT_NET, "virt-net")
wt.set(WEIGHT_VIRT_PORTS, "virt-ports")
wt.set(WEIGHT_PHY_FABRIC, "phy-fabric")
wt.set(WEIGHT_PHY_HOST, "phy-hosts")
wt.set(WEIGHT_PHY_BRIDGES, "phy-bridges")
wt.set(WEIGHT_PHY_NET, "phy-net")
wt.set(WEIGHT_PHY_PORTS, "phy-ports")
wt.set(WEIGHT_NONE, "Not classified")
if (SHOW_DEBUG) {
for (let [key, value] of wt) {
wt.set(key, key.toString() + "|" + value)
}
}
return wt
}
suggestions(): Array<string> {
return [
"data.IPV4",
"data.MAC",
"data.Name"
]
}
nodeDataFields(): Array<NodeDataField> {
return [
{
field: "",
title: "General",
expanded: true,
icon: "\uf05a",
sortKeys: (data: any): Array<string> => {
return ['Name', 'Type', 'MAC', 'Driver', 'State']
},
filterKeys: (data: any): Array<string> => {
switch (data.Type) {
case "host":
return ['Name']
default:
return ['Name', 'Type', 'MAC', 'Driver', 'State']
}
}
},
{
field: "Sockets",
expanded: false,
icon: "\uf1e6"
},
{
field: "Captures",
expanded: false,
icon: "\uf51f",
normalizer: (data: any): any => {
for (let capture of data) {
capture.ID = capture.ID.split('-')[0]
}
return data
}
},
{
field: "Injections",
expanded: false,
icon: "\uf48e"
},
{
field: "Docker",
expanded: false,
icon: "\uf395",
iconClass: "font-brands"
},
{
field: "Runc",
expanded: false,
icon: "\uf7bc",
iconClass: "font-brands"
},
{
field: "IPV4",
expanded: true,
icon: "\uf1fa"
},
{
field: "IPV6",
expanded: true,
icon: "\uf1fa"
},
{
field: "LastUpdateMetric",
title: "Last metrics",
expanded: false,
icon: "\uf201",
normalizer: (data: any): any => {
return {
RxPackets: data.RxPackets ? data.RxPackets.toLocaleString() : 0,
RxBytes: data.RxBytes ? Tools.prettyBytes(data.RxBytes) : 0,
TxPackets: data.TxPackets ? data.TxPackets.toLocaleString() : 0,
TxBytes: data.TxPackets ? Tools.prettyBytes(data.TxBytes) : 0,
Start: data.Start ? new Date(data.Start).toLocaleString() : 0,
Last: data.Last ? new Date(data.Last).toLocaleString() : 0
}
},
graph: (data: any): any => {
return {
type: "LineChart",
data: [
[
{ type: "datetime", label: "time" },
"RxBytes",
"TxBytes"
],
[new Date(data.Last || 0), data.RxBytes || 0, data.TxBytes || 0]
]
}
}
},
{
field: "Metric",
title: "Total metrics",
expanded: false,
icon: "\uf201",
normalizer: (data: any): any => {
return {
RxPackets: data.RxPackets ? data.RxPackets.toLocaleString() : 0,
RxBytes: data.RxBytes ? Tools.prettyBytes(data.RxBytes) : 0,
TxPackets: data.TxPackets ? data.TxPackets.toLocaleString() : 0,
TxBytes: data.TxPackets ? Tools.prettyBytes(data.TxBytes) : 0,
Last: data.Last ? new Date(data.Last).toLocaleString() : 0
}
}
},
{
field: "Features",
expanded: false,
icon: "\uf022"
},
{
field: "FDB",
expanded: false,
icon: "\uf0ce"
},
{
field: "Neighbors",
expanded: false,
icon: "\uf0ce"
},
{
field: "RoutingTables",
title: "Routing tables",
expanded: false,
icon: "\uf0ce",
normalizer: (data: any): any => {
var rows = new Array<any>()
for (let table of data) {
if (!table.Routes) {
continue
}
for (let route of table.Routes) {
if (!route.NextHops) {
continue
}
for (let nh of route.NextHops) {
rows.push({
ID: table.ID,
Src: table.Src,
Protocol: route["Protocol"],
Prefix: route["Prefix"],
Priority: nh["Priority"],
IP: nh["IP"],
IfIndex: nh["IfIndex"]
})
}
}
}
return rows
}
}
]
}
linkAttrs(link: Link): LinkAttrs {
var metric = link.source.data.LastUpdateMetric
var bandwidth = 0
if (metric) {
bandwidth = (metric.RxBytes + metric.TxBytes) * 8
bandwidth /= (metric.Last - metric.Start) / 1000
}
var attrs = {
classes: [link.data.RelationType],
icon: "\uf362",
directed: false,
href: '',
iconClass: '',
label: bandwidth ? Tools.prettyBandwidth(bandwidth) : ""
}
if (bandwidth > 0) {
attrs.classes.push('traffic')
}
if (link.data.RelationType === "layer2") {
attrs.classes.push("traffic")
}
if (link.data.Directed) {
attrs.directed = true
}
return attrs
}
linkTabTitle(link: Link): string {
var src = link.source.data.Name
var dst = link.target.data.Name
if (src && dst) {
return src.substring(0, 8) + " / " + dst.substring(0, 8)
}
return link.id.split("-")[0]
}
linkDataFields(): Array<LinkDataField> {
return [
{
field: "",
title: "General",
expanded: true,
icon: "\uf05a",
},
{
field: "NSM",
title: "Network Service Mesh",
expanded: true,
icon: "\uf542",
},
{
field: "NSM.Source",
title: "Source",
expanded: false,
icon: "\uf018",
},
{
field: "NSM.Via",
title: "Via",
expanded: false,
icon: "\uf018",
},
{
field: "NSM.Destination",
title: "Destination",
expanded: false,
icon: "\uf018",
}
]
}
defaultLinkTagMode(): number {
return 2
}
}