diff --git a/controller/controller.go b/controller/controller.go
index 5bab7529..2c4912b3 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -2,6 +2,7 @@ package controller
import (
"github.com/go-openapi/loads"
+ influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations"
@@ -13,6 +14,7 @@ import (
var cfg *Config
var str *store.Store
var mtr *metricsAgent
+var idb influxdb2.Client
const version = "v0.2.0"
@@ -47,6 +49,10 @@ func Run(inCfg *Config) error {
return errors.Wrap(err, "error opening store")
}
+ if cfg.Influx != nil {
+ idb = influxdb2.NewClient(cfg.Influx.Url, cfg.Influx.Token)
+ }
+
if cfg.Metrics != nil {
mtr = newMetricsAgent()
go mtr.run()
diff --git a/controller/metrics.go b/controller/metrics.go
index c40495e9..daa5b066 100644
--- a/controller/metrics.go
+++ b/controller/metrics.go
@@ -21,7 +21,6 @@ import (
)
type metricsAgent struct {
- influx influxdb2.Client
writeApi api.WriteAPIBlocking
metricsQueue chan *model.Metrics
envCache map[string]*envCacheEntry
@@ -43,9 +42,8 @@ func newMetricsAgent() *metricsAgent {
shutdown: make(chan struct{}),
joined: make(chan struct{}),
}
- if cfg.Influx != nil {
- ma.influx = influxdb2.NewClient(cfg.Influx.Url, cfg.Influx.Token)
- ma.writeApi = ma.influx.WriteAPIBlocking(cfg.Influx.Org, cfg.Influx.Bucket)
+ if idb != nil {
+ ma.writeApi = idb.WriteAPIBlocking(cfg.Influx.Org, cfg.Influx.Bucket)
}
return ma
}
diff --git a/controller/overview.go b/controller/overview.go
index d2454142..533d24d9 100644
--- a/controller/overview.go
+++ b/controller/overview.go
@@ -1,7 +1,10 @@
package controller
import (
+ "context"
+ "fmt"
"github.com/go-openapi/runtime/middleware"
+ "github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/metadata"
"github.com/sirupsen/logrus"
@@ -26,31 +29,90 @@ func overviewHandler(_ metadata.OverviewParams, principal *rest_model_zrok.Princ
logrus.Errorf("error finding services for environment '%v': %v", env.ZId, err)
return metadata.NewOverviewInternalServerError()
}
- if env.Active {
- es := &rest_model_zrok.EnvironmentServices{
- Environment: &rest_model_zrok.Environment{
- Address: env.Address,
- CreatedAt: env.CreatedAt.String(),
- Description: env.Description,
- Host: env.Host,
- UpdatedAt: env.UpdatedAt.String(),
- ZID: env.ZId,
- },
- }
- for _, svc := range svcs {
- if svc.Active {
- es.Services = append(es.Services, &rest_model_zrok.Service{
- CreatedAt: svc.CreatedAt.String(),
- Frontend: svc.Frontend,
- Backend: svc.Backend,
- UpdatedAt: svc.UpdatedAt.String(),
- ZID: svc.ZId,
- Name: svc.Name,
- })
- }
- }
- out = append(out, es)
+ es := &rest_model_zrok.EnvironmentServices{
+ Environment: &rest_model_zrok.Environment{
+ Address: env.Address,
+ CreatedAt: env.CreatedAt.String(),
+ Description: env.Description,
+ Host: env.Host,
+ UpdatedAt: env.UpdatedAt.String(),
+ ZID: env.ZId,
+ },
}
+ sparkData, err := sparkDataForServices(svcs)
+ if err != nil {
+ logrus.Errorf("error querying spark data for services: %v", err)
+ return metadata.NewOverviewInternalServerError()
+ }
+ for _, svc := range svcs {
+ es.Services = append(es.Services, &rest_model_zrok.Service{
+ CreatedAt: svc.CreatedAt.String(),
+ Frontend: svc.Frontend,
+ Backend: svc.Backend,
+ UpdatedAt: svc.UpdatedAt.String(),
+ ZID: svc.ZId,
+ Name: svc.Name,
+ Metrics: sparkData[svc.Name],
+ })
+ }
+ out = append(out, es)
}
return metadata.NewOverviewOK().WithPayload(out)
}
+
+func sparkDataForServices(svcs []*store.Service) (map[string][]int64, error) {
+ out := make(map[string][]int64)
+
+ if len(svcs) > 0 {
+ qapi := idb.QueryAPI(cfg.Influx.Org)
+
+ result, err := qapi.Query(context.Background(), sparkFluxQuery(svcs))
+ if err != nil {
+ return nil, err
+ }
+
+ for result.Next() {
+ combinedRate := int64(0)
+ readRate := result.Record().ValueByKey("_value_t1")
+ if readRate != nil {
+ combinedRate += int64(readRate.(float64))
+ }
+ writeRate := result.Record().ValueByKey("_value_t2")
+ if writeRate != nil {
+ combinedRate += int64(writeRate.(float64))
+ }
+ svcName := result.Record().ValueByKey("service_t1").(string)
+ svcMetrics := out[svcName]
+ svcMetrics = append(svcMetrics, combinedRate)
+ out[svcName] = svcMetrics
+ }
+ }
+ return out, nil
+}
+
+func sparkFluxQuery(svcs []*store.Service) string {
+ svcFilter := "|> filter(fn: (r) =>"
+ for i, svc := range svcs {
+ if i > 0 {
+ svcFilter += " or"
+ }
+ svcFilter += fmt.Sprintf(" r[\"service\"] == \"%v\"", svc.Name)
+ }
+ svcFilter += ")"
+ query := "read = from(bucket: \"zrok\")" +
+ "|> range(start: -5m)" +
+ "|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")" +
+ "|> filter(fn: (r) => r[\"_field\"] == \"bytesRead\")" +
+ "|> filter(fn: (r) => r[\"namespace\"] == \"frontend\")" +
+ svcFilter +
+ "|> aggregateWindow(every: 5s, fn: mean, createEmpty: true)\n\n" +
+ "written = from(bucket: \"zrok\")" +
+ "|> range(start: -5m)" +
+ "|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")" +
+ "|> filter(fn: (r) => r[\"_field\"] == \"bytesWritten\")" +
+ "|> filter(fn: (r) => r[\"namespace\"] == \"frontend\")" +
+ svcFilter +
+ "|> aggregateWindow(every: 5s, fn: mean, createEmpty: true)\n\n" +
+ "join(tables: {t1: read, t2: written}, on: [\"_time\"])"
+ return query
+}
diff --git a/controller/store/environment.go b/controller/store/environment.go
index 2f3b75f7..216f8dc0 100644
--- a/controller/store/environment.go
+++ b/controller/store/environment.go
@@ -12,11 +12,10 @@ type Environment struct {
Host string
Address string
ZId string
- Active bool
}
func (self *Store) CreateEnvironment(accountId int, i *Environment, tx *sqlx.Tx) (int, error) {
- stmt, err := tx.Prepare("insert into environments (account_id, description, host, address, z_id, active) values (?, ?, ?, ?, ?, true)")
+ stmt, err := tx.Prepare("insert into environments (account_id, description, host, address, z_id) values (?, ?, ?, ?, ?)")
if err != nil {
return 0, errors.Wrap(err, "error preparing environments insert statement")
}
diff --git a/controller/store/service.go b/controller/store/service.go
index 137181eb..fa3e6909 100644
--- a/controller/store/service.go
+++ b/controller/store/service.go
@@ -12,11 +12,10 @@ type Service struct {
Name string
Frontend string
Backend string
- Active bool
}
func (self *Store) CreateService(envId int, svc *Service, tx *sqlx.Tx) (int, error) {
- stmt, err := tx.Prepare("insert into services (environment_id, z_id, name, frontend, backend, active) values (?, ?, ?, ?, ?, true)")
+ stmt, err := tx.Prepare("insert into services (environment_id, z_id, name, frontend, backend) values (?, ?, ?, ?, ?)")
if err != nil {
return 0, errors.Wrap(err, "error preparing services insert statement")
}
@@ -72,12 +71,12 @@ func (self *Store) FindServicesForEnvironment(envId int, tx *sqlx.Tx) ([]*Servic
}
func (self *Store) UpdateService(svc *Service, tx *sqlx.Tx) error {
- sql := "update services set z_id = ?, name = ?, frontend = ?, backend = ?, active = ?, updated_at = strftime('%Y-%m-%d %H:%M:%f', 'now') where id = ?"
+ sql := "update services set z_id = ?, name = ?, frontend = ?, backend = ?, updated_at = strftime('%Y-%m-%d %H:%M:%f', 'now') where id = ?"
stmt, err := tx.Prepare(sql)
if err != nil {
return errors.Wrap(err, "error preparing services update statement")
}
- _, err = stmt.Exec(svc.ZId, svc.Name, svc.Frontend, svc.Backend, svc.Active, svc.Id)
+ _, err = stmt.Exec(svc.ZId, svc.Name, svc.Frontend, svc.Backend, svc.Id)
if err != nil {
return errors.Wrap(err, "error executing services update statement")
}
diff --git a/controller/store/sql/000_base.sql b/controller/store/sql/000_base.sql
index 3640a59d..fb331149 100644
--- a/controller/store/sql/000_base.sql
+++ b/controller/store/sql/000_base.sql
@@ -38,7 +38,6 @@ create table environments (
host string,
address string,
z_id string not null unique,
- active boolean not null,
created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
@@ -55,7 +54,6 @@ create table services (
name string not null unique,
frontend string,
backend string,
- active boolean not null,
created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
diff --git a/controller/untunnel.go b/controller/untunnel.go
index a0756059..13d260cb 100644
--- a/controller/untunnel.go
+++ b/controller/untunnel.go
@@ -101,8 +101,7 @@ func (self *untunnelHandler) Handle(params tunnel.UntunnelParams, principal *res
logrus.Infof("deallocated service '%v'", svcName)
- ssvc.Active = false
- if err := str.UpdateService(ssvc, tx); err != nil {
+ if err := str.DeleteService(ssvc.Id, tx); err != nil {
logrus.Errorf("error deactivating service '%v': %v", svcZId, err)
return tunnel.NewUntunnelInternalServerError()
}
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 6fa742a7..bdccface 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -21,6 +21,7 @@
"react-flow-renderer": "^10.3.12",
"react-router-dom": "^6.4.0",
"react-scripts": "5.0.1",
+ "react-sparklines": "^1.7.0",
"styled-components": "^5.3.5"
}
},
@@ -14401,6 +14402,18 @@
}
}
},
+ "node_modules/react-sparklines": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/react-sparklines/-/react-sparklines-1.7.0.tgz",
+ "integrity": "sha512-bJFt9K4c5Z0k44G8KtxIhbG+iyxrKjBZhdW6afP+R7EnIq+iKjbWbEFISrf3WKNFsda+C46XAfnX0StS5fbDcg==",
+ "dependencies": {
+ "prop-types": "^15.5.10"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -27208,6 +27221,14 @@
"workbox-webpack-plugin": "^6.4.1"
}
},
+ "react-sparklines": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/react-sparklines/-/react-sparklines-1.7.0.tgz",
+ "integrity": "sha512-bJFt9K4c5Z0k44G8KtxIhbG+iyxrKjBZhdW6afP+R7EnIq+iKjbWbEFISrf3WKNFsda+C46XAfnX0StS5fbDcg==",
+ "requires": {
+ "prop-types": "^15.5.10"
+ }
+ },
"react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
diff --git a/ui/package.json b/ui/package.json
index eede04ce..283bafc7 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -16,6 +16,7 @@
"react-flow-renderer": "^10.3.12",
"react-router-dom": "^6.4.0",
"react-scripts": "5.0.1",
+ "react-sparklines": "^1.7.0",
"styled-components": "^5.3.5"
},
"scripts": {
diff --git a/ui/src/Environments.js b/ui/src/Environments.js
index 2251b37f..bc5013ff 100644
--- a/ui/src/Environments.js
+++ b/ui/src/Environments.js
@@ -30,17 +30,8 @@ const Environments = (props) => {
},
]
- const conditionalRowStyles = [
- {
- when: row => !row.environment.active,
- style: {
- display: 'none'
- }
- }
- ]
-
const servicesComponent = ({ data }) =>