зеркало из https://github.com/Azure/ARO-RP.git
Created Cluster Health Dashboard in Admin Portal (#2674)
* Cluster Health DashBoard * built portal and remove some linting issues * made the time in UTC and fixed a bug * fixed few things and changed the code as per review * rebased * made few changes according to the review. * renamed a helper component, rebuilt the portal * minor changes * Did few nit changes and some code improvements * removed the mod issue * vendor issue * fixed lint issue * remove lint error try1 * changed the code according to the review received. * reverted the clusterdetaillist switch case * small nit
This commit is contained in:
Родитель
c05a425e10
Коммит
89d335a041
2
go.sum
2
go.sum
|
@ -960,6 +960,7 @@ github.com/jongio/azidext/go/azidext v0.4.0 h1:TOYyVFMeWGgXNhURSgrEtUCu7JAAKgsy+
|
|||
github.com/jongio/azidext/go/azidext v0.4.0/go.mod h1:VrlpGde5B+pPbTUxnThE5UIQQkcebdr3jrC2MmlMVSI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
|
@ -1151,6 +1152,7 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
|
|||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
|
||||
|
|
|
@ -4,7 +4,5 @@
|
|||
"index.html": "/index.html",
|
||||
"main.61fbb3a0.js.map": "/static/js/main.61fbb3a0.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/main.61fbb3a0.js"
|
||||
]
|
||||
}
|
||||
"entrypoints": ["static/js/main.61fbb3a0.js"]
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>ARO Portal</title><script defer="defer" src="/static/js/main.61fbb3a0.js"></script></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>ARO Portal</title><script defer="defer" src="/static/js/main.61fbb3a0.js"></script></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -11,6 +11,10 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/Azure/ARO-RP/pkg/portal/cluster"
|
||||
"github.com/Azure/ARO-RP/pkg/portal/prometheus"
|
||||
)
|
||||
|
||||
type AdminOpenShiftCluster struct {
|
||||
|
@ -190,3 +194,60 @@ func (p *portal) machineSets(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
|
||||
func (p *portal) statistics(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
duration := r.URL.Query().Get("duration")
|
||||
parsedDuration, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
p.badRequest(w, err)
|
||||
return
|
||||
}
|
||||
endTimeString := r.URL.Query().Get("endtime")
|
||||
endTime, err := time.Parse(time.RFC3339, endTimeString)
|
||||
if err != nil {
|
||||
p.badRequest(w, err)
|
||||
return
|
||||
}
|
||||
apiVars := mux.Vars(r)
|
||||
statisticsType := apiVars["statisticsType"]
|
||||
subscriptionID := apiVars["subscription"]
|
||||
resourceGroup := apiVars["resourceGroup"]
|
||||
clusterName := apiVars["clusterName"]
|
||||
resourceID := p.getResourceID(subscriptionID, resourceGroup, clusterName)
|
||||
promQuery, err := cluster.GetPromQuery(statisticsType)
|
||||
if err != nil {
|
||||
p.badRequest(w, err)
|
||||
return
|
||||
}
|
||||
prom := prometheus.New(p.log, p.dbOpenShiftClusters, p.dialer)
|
||||
httpClient, err := prom.Cli(ctx, resourceID)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
fetcher, err := p.makeFetcher(ctx, r)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
promHost, promScheme := prom.GetPrometheusHostAndScheme()
|
||||
prometheusURL := promScheme + "://" + promHost
|
||||
APIStatistics, err := fetcher.Statistics(ctx, httpClient, promQuery, parsedDuration, endTime, prometheusURL)
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
b, err := json.MarshalIndent(APIStatistics, "", " ")
|
||||
if err != nil {
|
||||
p.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
p.log.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ package cluster
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
configclient "github.com/openshift/client-go/config/clientset/versioned"
|
||||
machineclient "github.com/openshift/client-go/machine/clientset/versioned"
|
||||
|
@ -23,6 +25,7 @@ type FetchClient interface {
|
|||
ClusterOperators(context.Context) (*ClusterOperatorsInformation, error)
|
||||
Machines(context.Context) (*MachineListInformation, error)
|
||||
MachineSets(context.Context) (*MachineSetListInformation, error)
|
||||
Statistics(context.Context, *http.Client, string, time.Duration, time.Time, string) ([]Metrics, error)
|
||||
}
|
||||
|
||||
// client is an implementation of FetchClient. It currently contains a "fetcher"
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
package cluster
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
prometheusAPI "github.com/prometheus/client_golang/api"
|
||||
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// MetricValue contains the actual data of the metrics at certain timestamp, and a slice of this is used in the `Metrics` struct to combine all the metrics in one object.
|
||||
type MetricValue struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
// This is a structure which actually carries, a particular type of metrics
|
||||
type Metrics struct {
|
||||
Name string `json:"metricname"`
|
||||
Value []MetricValue `json:"metricvalue"`
|
||||
}
|
||||
|
||||
func (c *client) Statistics(ctx context.Context, httpClient *http.Client, promQuery string, duration time.Duration, endTime time.Time, prometheusURL string) ([]Metrics, error) {
|
||||
return c.fetcher.statistics(ctx, httpClient, promQuery, duration, endTime, prometheusURL)
|
||||
}
|
||||
|
||||
func (f *realFetcher) statistics(ctx context.Context, httpClient *http.Client, promQuery string, duration time.Duration, endTime time.Time, prometheusURL string) ([]Metrics, error) {
|
||||
promConfig := prometheusAPI.Config{
|
||||
Address: prometheusURL,
|
||||
RoundTripper: httpClient.Transport,
|
||||
}
|
||||
|
||||
client, err := prometheusAPI.NewClient(promConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v1api := v1.NewAPI(client)
|
||||
startTime := endTime.Add(-1 * duration)
|
||||
value, warning, err := v1api.QueryRange(ctx, promQuery, v1.Range{
|
||||
Start: startTime,
|
||||
End: endTime,
|
||||
Step: time.Minute * 2,
|
||||
})
|
||||
if len(warning) > 0 {
|
||||
f.log.Warn(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valueMatrix := value.(model.Matrix)
|
||||
return convertToTypeMetrics(valueMatrix), nil
|
||||
}
|
||||
|
||||
func convertToTypeMetrics(v model.Matrix) []Metrics {
|
||||
metrics := make([]Metrics, 0)
|
||||
for _, i := range v {
|
||||
metric := Metrics{}
|
||||
metric.Name = i.Metric.String()
|
||||
metricValues := make([]MetricValue, 0)
|
||||
for _, j := range i.Values {
|
||||
metricValues = append(metricValues, MetricValue{
|
||||
Timestamp: j.Timestamp.Time().Local().UTC(),
|
||||
Value: float64(j.Value),
|
||||
})
|
||||
}
|
||||
metric.Value = metricValues
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
||||
func GetPromQuery(statisticsType string) (string, error) {
|
||||
promQueries := map[string]string{
|
||||
"kubeapicodes": "sum(rate(apiserver_request_total{job=\"apiserver\",code=~\"[45]..\"}[10m])) by (code, verb)",
|
||||
"kubeapicpu": "rate(process_cpu_seconds_total{job=\"apiserver\"}[5m])",
|
||||
"kubeapimemory": "process_resident_memory_bytes{job=\"apiserver\"}",
|
||||
// kube-controller-manager
|
||||
"kubecontrollermanagercodes": "sum(rate(rest_client_requests_total{job=\"kube-controller-manager\"}[5m])) by (code)",
|
||||
"kubecontrollermanagercpu": "rate(process_cpu_seconds_total{job=\"kube-controller-manager\"}[5m])",
|
||||
"kubecontrollermanagermemory": "process_resident_memory_bytes{job=\"kube-controller-manager\"}",
|
||||
// DNS
|
||||
"dnsresponsecodes": "sum(rate(coredns_dns_responses_total[5m])) by (rcode)",
|
||||
"dnserrorrate": "sum(rate(coredns_dns_responses_total{rcode=~\"SERVFAIL|NXDOMAIN\"}[5m])) by (pod) / sum(rate(coredns_dns_responses_total{rcode=~\"NOERROR\"}[5m])) by (pod)",
|
||||
"dnshealthcheck": "histogram_quantile(0.99, sum(rate(coredns_health_request_duration_seconds_bucket[5m])) by (le))",
|
||||
"dnsforwardedtraffic": "histogram_quantile(0.95, sum(rate(coredns_forward_request_duration_seconds_bucket[5m])) by (le))",
|
||||
"dnsalltraffic": "histogram_quantile(0.95, sum(rate(coredns_dns_request_duration_seconds_bucket[5m])) by (le))",
|
||||
// Ingress
|
||||
"ingresscontrollercondition": "sum(ingress_controller_conditions) by (condition)",
|
||||
}
|
||||
promQuery, ok := promQueries[statisticsType]
|
||||
if !ok {
|
||||
return "", errors.New("invalid statistic type '" + statisticsType + "'")
|
||||
}
|
||||
return promQuery, nil
|
||||
}
|
|
@ -5,10 +5,8 @@ package portal
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
@ -37,12 +35,10 @@ func (p *portal) clusterInfo(w http.ResponseWriter, r *http.Request) {
|
|||
ctx := r.Context()
|
||||
|
||||
apiVars := mux.Vars(r)
|
||||
|
||||
subscription := apiVars["subscription"]
|
||||
resourceGroup := apiVars["resourceGroup"]
|
||||
clusterName := apiVars["clusterName"]
|
||||
|
||||
resourceId := strings.ToLower(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.RedHatOpenShift/openShiftClusters/%s", subscription, resourceGroup, clusterName))
|
||||
resourceId := p.getResourceID(subscription, resourceGroup, clusterName)
|
||||
|
||||
doc, err := p.dbOpenShiftClusters.Get(ctx, resourceId)
|
||||
if err != nil {
|
||||
|
|
|
@ -305,6 +305,7 @@ func (p *portal) aadAuthenticatedRoutes(r *mux.Router, prom *prometheus.Promethe
|
|||
r.Path("/api/{subscription}/{resourceGroup}/{clusterName}/nodes").HandlerFunc(p.nodes)
|
||||
r.Path("/api/{subscription}/{resourceGroup}/{clusterName}/machines").HandlerFunc(p.machines)
|
||||
r.Path("/api/{subscription}/{resourceGroup}/{clusterName}/machine-sets").HandlerFunc(p.machineSets)
|
||||
r.Path("/api/{subscription}/{resourceGroup}/{clusterName}/statistics/{statisticsType}").HandlerFunc(p.statistics)
|
||||
r.Path("/api/{subscription}/{resourceGroup}/{clusterName}").HandlerFunc(p.clusterInfo)
|
||||
|
||||
// prometheus
|
||||
|
@ -360,16 +361,10 @@ func (p *portal) indexV2(w http.ResponseWriter, r *http.Request) {
|
|||
// makeFetcher creates a cluster.FetchClient suitable for use by the Portal REST API
|
||||
func (p *portal) makeFetcher(ctx context.Context, r *http.Request) (cluster.FetchClient, error) {
|
||||
apiVars := mux.Vars(r)
|
||||
|
||||
subscription := apiVars["subscription"]
|
||||
subscriptionID := apiVars["subscription"]
|
||||
resourceGroup := apiVars["resourceGroup"]
|
||||
clusterName := apiVars["clusterName"]
|
||||
|
||||
resourceID :=
|
||||
strings.ToLower(
|
||||
fmt.Sprintf(
|
||||
"/subscriptions/%s/resourceGroups/%s/providers/Microsoft.RedHatOpenShift/openShiftClusters/%s",
|
||||
subscription, resourceGroup, clusterName))
|
||||
resourceID := p.getResourceID(subscriptionID, resourceGroup, clusterName)
|
||||
if !validate.RxClusterID.MatchString(resourceID) {
|
||||
return nil, fmt.Errorf("invalid resource ID")
|
||||
}
|
||||
|
@ -406,7 +401,19 @@ func (p *portal) serve(path string) func(w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
}
|
||||
|
||||
func (p *portal) getResourceID(subscriptionID, resourceGroup, clusterName string) string {
|
||||
return strings.ToLower(
|
||||
fmt.Sprintf(
|
||||
"/subscriptions/%s/resourceGroups/%s/providers/Microsoft.RedHatOpenShift/openShiftClusters/%s",
|
||||
subscriptionID, resourceGroup, clusterName))
|
||||
}
|
||||
|
||||
func (p *portal) internalServerError(w http.ResponseWriter, err error) {
|
||||
p.log.Warn(err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func (p *portal) badRequest(w http.ResponseWriter, err error) {
|
||||
p.log.Debug(err)
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ func (p *Prometheus) Director(r *http.Request) {
|
|||
cli := p.clientCache.Get(resourceID)
|
||||
if cli == nil {
|
||||
var err error
|
||||
cli, err = p.cli(ctx, resourceID)
|
||||
cli, err = p.Cli(ctx, resourceID)
|
||||
if err != nil {
|
||||
p.error(r, http.StatusInternalServerError, err)
|
||||
return
|
||||
|
@ -56,8 +56,7 @@ func (p *Prometheus) Director(r *http.Request) {
|
|||
}
|
||||
|
||||
r.RequestURI = ""
|
||||
r.URL.Scheme = "http"
|
||||
r.URL.Host = "prometheus-k8s-0:9090"
|
||||
r.URL.Host, r.URL.Scheme = p.GetPrometheusHostAndScheme()
|
||||
r.URL.Path = "/" + strings.Join(strings.Split(r.URL.Path, "/")[10:], "/")
|
||||
r.Header.Del("Cookie")
|
||||
r.Header.Del("Referer")
|
||||
|
@ -69,7 +68,11 @@ func (p *Prometheus) Director(r *http.Request) {
|
|||
*r = *r.WithContext(context.WithValue(ctx, contextKeyClient, cli))
|
||||
}
|
||||
|
||||
func (p *Prometheus) cli(ctx context.Context, resourceID string) (*http.Client, error) {
|
||||
func (p *Prometheus) GetPrometheusHostAndScheme() (string, string) {
|
||||
return "prometheus-k8s-0:9090", "http"
|
||||
}
|
||||
|
||||
func (p *Prometheus) Cli(ctx context.Context, resourceID string) (*http.Client, error) {
|
||||
openShiftDoc, err := p.dbOpenShiftClusters.Get(ctx, resourceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -6735,7 +6735,7 @@
|
|||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
|
@ -7309,8 +7309,7 @@
|
|||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"version": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"license": "Apache2",
|
||||
"dependencies": {
|
||||
"@fluentui/react": "^8.101.0",
|
||||
"@fluentui/react-charting": "^5.14.27",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/node": "^18.11.9",
|
||||
|
@ -2349,9 +2350,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@fluentui/react": {
|
||||
"version": "8.101.2",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.101.2.tgz",
|
||||
"integrity": "sha512-onnQYXia/UANmrlzR7gGVNQH6lBsNxAYHicbjdCTfJ4ZwZ7otQIy0Z2StY2qgwhhzRil+30xyQwsv3AlCRXLKw==",
|
||||
"version": "8.103.7",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.103.7.tgz",
|
||||
"integrity": "sha512-WXolw5VBRcwt4Ur1FJZ7QPffw6IQzt9Ba6lJrXts5PPrjTLE31F5o7R4s9xB/tX6QrWjSJ3XKWpRDqYXjtmD+g==",
|
||||
"dependencies": {
|
||||
"@fluentui/date-time-utilities": "^8.5.3",
|
||||
"@fluentui/font-icons-mdl2": "^8.5.4",
|
||||
|
@ -2375,6 +2376,44 @@
|
|||
"react-dom": ">=16.8.0 <19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fluentui/react-charting": {
|
||||
"version": "5.14.27",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react-charting/-/react-charting-5.14.27.tgz",
|
||||
"integrity": "sha512-Kp1yxyfYVBtFrEi325KHoYpzVL6DFUAY1AGhd2Cji0ZD9BH9HzpEDPZDfPtyV3s2IMShzvsagfo7JKfv9qL2Cw==",
|
||||
"dependencies": {
|
||||
"@fluentui/react-focus": "^8.8.10",
|
||||
"@fluentui/set-version": "^8.2.3",
|
||||
"@microsoft/load-themed-styles": "^1.10.26",
|
||||
"@types/d3-array": "1.2.1",
|
||||
"@types/d3-axis": "1.0.10",
|
||||
"@types/d3-format": "^1.3.1",
|
||||
"@types/d3-hierarchy": "2.0.0",
|
||||
"@types/d3-sankey": "^0.11.0",
|
||||
"@types/d3-scale": "^4.0.0",
|
||||
"@types/d3-selection": "1.4.1",
|
||||
"@types/d3-shape": "^1.2.3",
|
||||
"@types/d3-time": "^1.1.0",
|
||||
"@types/d3-time-format": "^2.1.0",
|
||||
"d3-array": "1.2.1",
|
||||
"d3-axis": "1.0.8",
|
||||
"d3-format": "^1.4.4",
|
||||
"d3-hierarchy": "2.0.0",
|
||||
"d3-sankey": "^0.12.3",
|
||||
"d3-scale": "^4.0.0",
|
||||
"d3-selection": "1.3.0",
|
||||
"d3-shape": "^1.2.0",
|
||||
"d3-time": "^1.1.0",
|
||||
"d3-time-format": "^2.1.3",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fluentui/react": "^8.103.7",
|
||||
"@types/react": ">=16.8.0 <19.0.0",
|
||||
"@types/react-dom": ">=16.8.0 <19.0.0",
|
||||
"react": ">=16.8.0 <19.0.0",
|
||||
"react-dom": ">=16.8.0 <19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fluentui/react-focus": {
|
||||
"version": "8.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.8.10.tgz",
|
||||
|
@ -4035,6 +4074,73 @@
|
|||
"integrity": "sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-1.2.1.tgz",
|
||||
"integrity": "sha512-YBaAfimGdWE4nDuoGVKsH89/dkz2hWZ0i8qC+xxqmqi+XJ/aXiRF0jPtzXmN7VdkpVjy1xuDmM5/m1FNuB6VWA=="
|
||||
},
|
||||
"node_modules/@types/d3-axis": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-1.0.10.tgz",
|
||||
"integrity": "sha512-5YF0wfdQMPKw01VAAupLIlg/T4pn5M3/vL9u0KZjiemnVnnKBEWE24na4X1iW+TfZiYJ8j+BgK2KFYnAAT54Ug==",
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-format": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz",
|
||||
"integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ=="
|
||||
},
|
||||
"node_modules/@types/d3-hierarchy": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz",
|
||||
"integrity": "sha512-YxdskUvwzqggpnSnDQj4KVkicgjpkgXn/g/9M9iGsiToLS3nG6Ytjo1FoYhYVAAElV/fJBGVL3cQ9Hb7tcv+lw=="
|
||||
},
|
||||
"node_modules/@types/d3-path": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz",
|
||||
"integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ=="
|
||||
},
|
||||
"node_modules/@types/d3-sankey": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-sankey/-/d3-sankey-0.11.2.tgz",
|
||||
"integrity": "sha512-U6SrTWUERSlOhnpSrgvMX64WblX1AxX6nEjI2t3mLK2USpQrnbwYYK+AS9SwiE7wgYmOsSSKoSdr8aoKBH0HgQ==",
|
||||
"dependencies": {
|
||||
"@types/d3-shape": "^1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==",
|
||||
"dependencies": {
|
||||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-selection": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.4.1.tgz",
|
||||
"integrity": "sha512-bv8IfFYo/xG6dxri9OwDnK3yCagYPeRIjTlrcdYJSx+FDWlCeBDepIHUpqROmhPtZ53jyna0aUajZRk0I3rXNA=="
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.8.tgz",
|
||||
"integrity": "sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==",
|
||||
"dependencies": {
|
||||
"@types/d3-path": "^1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-time": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.1.1.tgz",
|
||||
"integrity": "sha512-ULX7LoqXTCYtM+tLYOaeAJK7IwCT+4Gxlm2MaH0ErKLi07R5lh8NHCAyWcDkCCmx1AfRcBEV6H9QE9R25uP7jw=="
|
||||
},
|
||||
"node_modules/@types/d3-time-format": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.1.tgz",
|
||||
"integrity": "sha512-fck0Z9RGfIQn3GJIEKVrp15h9m6Vlg0d5XXeiE/6+CQiBmMDZxfR21XtjEPuDeg7gC3bBM0SdieA5XF3GW1wKA=="
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz",
|
||||
|
@ -6613,6 +6719,122 @@
|
|||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
|
||||
"integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz",
|
||||
"integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw=="
|
||||
},
|
||||
"node_modules/d3-axis": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.8.tgz",
|
||||
"integrity": "sha512-K0djTb26iQ6AsuD2d6Ka08wBHf4V30awIxV4XFuB/iLzYtTqqJlE/nIN0DBJJCX7lbOqbt2/oeX3r+sU5k2veg=="
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
|
||||
"integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ=="
|
||||
},
|
||||
"node_modules/d3-hierarchy": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz",
|
||||
"integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw=="
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-path": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
|
||||
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
|
||||
},
|
||||
"node_modules/d3-sankey": {
|
||||
"version": "0.12.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
|
||||
"integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
|
||||
"dependencies": {
|
||||
"d3-array": "1 - 2",
|
||||
"d3-shape": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"dependencies": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale/node_modules/d3-array": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz",
|
||||
"integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale/node_modules/d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"dependencies": {
|
||||
"d3-array": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-selection": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.0.tgz",
|
||||
"integrity": "sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA=="
|
||||
},
|
||||
"node_modules/d3-shape": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
|
||||
"integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
|
||||
"dependencies": {
|
||||
"d3-path": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
|
||||
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
|
||||
},
|
||||
"node_modules/d3-time-format": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz",
|
||||
"integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==",
|
||||
"dependencies": {
|
||||
"d3-time": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
|
@ -8859,20 +9081,6 @@
|
|||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
|
@ -9619,6 +9827,14 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz",
|
||||
|
@ -19679,7 +19895,7 @@
|
|||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"strip-json-comments": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -19759,9 +19975,9 @@
|
|||
}
|
||||
},
|
||||
"@fluentui/react": {
|
||||
"version": "8.101.2",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.101.2.tgz",
|
||||
"integrity": "sha512-onnQYXia/UANmrlzR7gGVNQH6lBsNxAYHicbjdCTfJ4ZwZ7otQIy0Z2StY2qgwhhzRil+30xyQwsv3AlCRXLKw==",
|
||||
"version": "8.103.7",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.103.7.tgz",
|
||||
"integrity": "sha512-WXolw5VBRcwt4Ur1FJZ7QPffw6IQzt9Ba6lJrXts5PPrjTLE31F5o7R4s9xB/tX6QrWjSJ3XKWpRDqYXjtmD+g==",
|
||||
"requires": {
|
||||
"@fluentui/date-time-utilities": "^8.5.3",
|
||||
"@fluentui/font-icons-mdl2": "^8.5.4",
|
||||
|
@ -19779,6 +19995,37 @@
|
|||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"@fluentui/react-charting": {
|
||||
"version": "5.14.27",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react-charting/-/react-charting-5.14.27.tgz",
|
||||
"integrity": "sha512-Kp1yxyfYVBtFrEi325KHoYpzVL6DFUAY1AGhd2Cji0ZD9BH9HzpEDPZDfPtyV3s2IMShzvsagfo7JKfv9qL2Cw==",
|
||||
"requires": {
|
||||
"@fluentui/react-focus": "^8.8.10",
|
||||
"@fluentui/set-version": "^8.2.3",
|
||||
"@microsoft/load-themed-styles": "^1.10.26",
|
||||
"@types/d3-array": "1.2.1",
|
||||
"@types/d3-axis": "1.0.10",
|
||||
"@types/d3-format": "^1.3.1",
|
||||
"@types/d3-hierarchy": "2.0.0",
|
||||
"@types/d3-sankey": "^0.11.0",
|
||||
"@types/d3-scale": "^4.0.0",
|
||||
"@types/d3-selection": "1.4.1",
|
||||
"@types/d3-shape": "^1.2.3",
|
||||
"@types/d3-time": "^1.1.0",
|
||||
"@types/d3-time-format": "^2.1.0",
|
||||
"d3-array": "1.2.1",
|
||||
"d3-axis": "1.0.8",
|
||||
"d3-format": "^1.4.4",
|
||||
"d3-hierarchy": "2.0.0",
|
||||
"d3-sankey": "^0.12.3",
|
||||
"d3-scale": "^4.0.0",
|
||||
"d3-selection": "1.3.0",
|
||||
"d3-shape": "^1.2.0",
|
||||
"d3-time": "^1.1.0",
|
||||
"d3-time-format": "^2.1.3",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"@fluentui/react-focus": {
|
||||
"version": "8.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.8.10.tgz",
|
||||
|
@ -19871,7 +20118,7 @@
|
|||
"requires": {
|
||||
"@humanwhocodes/object-schema": "^1.2.1",
|
||||
"debug": "^4.1.1",
|
||||
"minimatch": "^5.1.0"
|
||||
"minimatch": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"@humanwhocodes/module-importer": {
|
||||
|
@ -20569,7 +20816,7 @@
|
|||
"error-stack-parser": "^2.0.6",
|
||||
"find-up": "^5.0.0",
|
||||
"html-entities": "^2.1.0",
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^2.0.3",
|
||||
"schema-utils": "^3.0.0",
|
||||
"source-map": "^0.7.3"
|
||||
}
|
||||
|
@ -20981,6 +21228,73 @@
|
|||
"integrity": "sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/d3-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-1.2.1.tgz",
|
||||
"integrity": "sha512-YBaAfimGdWE4nDuoGVKsH89/dkz2hWZ0i8qC+xxqmqi+XJ/aXiRF0jPtzXmN7VdkpVjy1xuDmM5/m1FNuB6VWA=="
|
||||
},
|
||||
"@types/d3-axis": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-1.0.10.tgz",
|
||||
"integrity": "sha512-5YF0wfdQMPKw01VAAupLIlg/T4pn5M3/vL9u0KZjiemnVnnKBEWE24na4X1iW+TfZiYJ8j+BgK2KFYnAAT54Ug==",
|
||||
"requires": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"@types/d3-format": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz",
|
||||
"integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ=="
|
||||
},
|
||||
"@types/d3-hierarchy": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz",
|
||||
"integrity": "sha512-YxdskUvwzqggpnSnDQj4KVkicgjpkgXn/g/9M9iGsiToLS3nG6Ytjo1FoYhYVAAElV/fJBGVL3cQ9Hb7tcv+lw=="
|
||||
},
|
||||
"@types/d3-path": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz",
|
||||
"integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ=="
|
||||
},
|
||||
"@types/d3-sankey": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-sankey/-/d3-sankey-0.11.2.tgz",
|
||||
"integrity": "sha512-U6SrTWUERSlOhnpSrgvMX64WblX1AxX6nEjI2t3mLK2USpQrnbwYYK+AS9SwiE7wgYmOsSSKoSdr8aoKBH0HgQ==",
|
||||
"requires": {
|
||||
"@types/d3-shape": "^1"
|
||||
}
|
||||
},
|
||||
"@types/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==",
|
||||
"requires": {
|
||||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"@types/d3-selection": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.4.1.tgz",
|
||||
"integrity": "sha512-bv8IfFYo/xG6dxri9OwDnK3yCagYPeRIjTlrcdYJSx+FDWlCeBDepIHUpqROmhPtZ53jyna0aUajZRk0I3rXNA=="
|
||||
},
|
||||
"@types/d3-shape": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.8.tgz",
|
||||
"integrity": "sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==",
|
||||
"requires": {
|
||||
"@types/d3-path": "^1"
|
||||
}
|
||||
},
|
||||
"@types/d3-time": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.1.1.tgz",
|
||||
"integrity": "sha512-ULX7LoqXTCYtM+tLYOaeAJK7IwCT+4Gxlm2MaH0ErKLi07R5lh8NHCAyWcDkCCmx1AfRcBEV6H9QE9R25uP7jw=="
|
||||
},
|
||||
"@types/d3-time-format": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.1.tgz",
|
||||
"integrity": "sha512-fck0Z9RGfIQn3GJIEKVrp15h9m6Vlg0d5XXeiE/6+CQiBmMDZxfR21XtjEPuDeg7gC3bBM0SdieA5XF3GW1wKA=="
|
||||
},
|
||||
"@types/eslint": {
|
||||
"version": "8.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz",
|
||||
|
@ -21626,7 +21940,7 @@
|
|||
"integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^2.0.0",
|
||||
"regex-parser": "^2.2.11"
|
||||
}
|
||||
},
|
||||
|
@ -21938,7 +22252,7 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"find-cache-dir": "^3.3.1",
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^2.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"schema-utils": "^2.6.5"
|
||||
},
|
||||
|
@ -22942,6 +23256,109 @@
|
|||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
|
||||
"integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
|
||||
},
|
||||
"d3-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz",
|
||||
"integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw=="
|
||||
},
|
||||
"d3-axis": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.8.tgz",
|
||||
"integrity": "sha512-K0djTb26iQ6AsuD2d6Ka08wBHf4V30awIxV4XFuB/iLzYtTqqJlE/nIN0DBJJCX7lbOqbt2/oeX3r+sU5k2veg=="
|
||||
},
|
||||
"d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="
|
||||
},
|
||||
"d3-format": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
|
||||
"integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ=="
|
||||
},
|
||||
"d3-hierarchy": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz",
|
||||
"integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw=="
|
||||
},
|
||||
"d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"requires": {
|
||||
"d3-color": "1 - 3"
|
||||
}
|
||||
},
|
||||
"d3-path": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
|
||||
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
|
||||
},
|
||||
"d3-sankey": {
|
||||
"version": "0.12.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
|
||||
"integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
|
||||
"requires": {
|
||||
"d3-array": "1 - 2",
|
||||
"d3-shape": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"requires": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"dependencies": {
|
||||
"d3-array": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz",
|
||||
"integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==",
|
||||
"requires": {
|
||||
"internmap": "1 - 2"
|
||||
}
|
||||
},
|
||||
"d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"requires": {
|
||||
"d3-array": "2 - 3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"d3-selection": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.0.tgz",
|
||||
"integrity": "sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA=="
|
||||
},
|
||||
"d3-shape": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
|
||||
"integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
|
||||
"requires": {
|
||||
"d3-path": "1"
|
||||
}
|
||||
},
|
||||
"d3-time": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
|
||||
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
|
||||
},
|
||||
"d3-time-format": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz",
|
||||
"integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==",
|
||||
"requires": {
|
||||
"d3-time": "1"
|
||||
}
|
||||
},
|
||||
"damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
|
@ -23534,7 +23951,7 @@
|
|||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"levn": "^0.4.1",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.9.1",
|
||||
"regexpp": "^3.2.0",
|
||||
|
@ -23781,7 +24198,7 @@
|
|||
"has": "^1.0.3",
|
||||
"is-core-module": "^2.8.1",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"object.values": "^1.1.5",
|
||||
"resolve": "^1.22.0",
|
||||
"tsconfig-paths": "^3.14.1"
|
||||
|
@ -23839,7 +24256,7 @@
|
|||
"has": "^1.0.3",
|
||||
"jsx-ast-utils": "^3.3.2",
|
||||
"language-tags": "^1.0.5",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"semver": "^6.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -23867,7 +24284,7 @@
|
|||
"eslint-utils": "^3.0.0",
|
||||
"ignore": "^5.1.1",
|
||||
"is-core-module": "^2.11.0",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"resolve": "^1.22.1",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
|
@ -23903,7 +24320,7 @@
|
|||
"doctrine": "^2.1.0",
|
||||
"estraverse": "^5.3.0",
|
||||
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.2",
|
||||
"object.entries": "^1.1.5",
|
||||
"object.fromentries": "^2.0.5",
|
||||
"object.hasown": "^1.1.1",
|
||||
|
@ -24330,7 +24747,7 @@
|
|||
"integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^2.0.0",
|
||||
"schema-utils": "^3.0.0"
|
||||
}
|
||||
},
|
||||
|
@ -24340,7 +24757,7 @@
|
|||
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimatch": "^5.1.0"
|
||||
"minimatch": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"filesize": {
|
||||
|
@ -24454,7 +24871,7 @@
|
|||
"fs-extra": "^9.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"memfs": "^3.1.2",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.0.4",
|
||||
"schema-utils": "2.7.0",
|
||||
"semver": "^7.3.2",
|
||||
"tapable": "^1.0.0"
|
||||
|
@ -24613,13 +25030,6 @@
|
|||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
|
@ -24701,7 +25111,7 @@
|
|||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^5.1.0",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
|
@ -25165,6 +25575,11 @@
|
|||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz",
|
||||
|
@ -25521,7 +25936,7 @@
|
|||
"async": "^3.2.3",
|
||||
"chalk": "^4.0.2",
|
||||
"filelist": "^1.0.1",
|
||||
"minimatch": "^5.1.0"
|
||||
"minimatch": "^3.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
|
@ -29055,7 +29470,7 @@
|
|||
"gzip-size": "^6.0.0",
|
||||
"immer": "^9.0.7",
|
||||
"is-root": "^2.1.0",
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^3.2.0",
|
||||
"open": "^8.4.0",
|
||||
"pkg-up": "^3.1.0",
|
||||
"prompts": "^2.4.2",
|
||||
|
@ -29158,7 +29573,7 @@
|
|||
"requires": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"babel-jest": "^27.4.2",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-named-asset-import": "^0.3.8",
|
||||
|
@ -29252,7 +29667,7 @@
|
|||
"integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimatch": "^5.1.0"
|
||||
"minimatch": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"regenerate": {
|
||||
|
@ -29421,7 +29836,7 @@
|
|||
"requires": {
|
||||
"adjust-sourcemap-loader": "^4.0.0",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"loader-utils": "^2.0.4",
|
||||
"loader-utils": "^2.0.0",
|
||||
"postcss": "^7.0.35",
|
||||
"source-map": "0.6.1"
|
||||
},
|
||||
|
@ -30296,7 +30711,7 @@
|
|||
"requires": {
|
||||
"@istanbuljs/schema": "^0.1.2",
|
||||
"glob": "^7.1.4",
|
||||
"minimatch": "^5.1.0"
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"text-table": {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"proxy": "https://localhost:8444",
|
||||
"dependencies": {
|
||||
"@fluentui/react": "^8.101.0",
|
||||
"@fluentui/react-charting": "^5.14.27",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/node": "^18.11.9",
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
IPanelStyles,
|
||||
Panel,
|
||||
PanelType,
|
||||
} from "@fluentui/react/lib/Panel"
|
||||
import { IPanelStyles, Panel, PanelType } from "@fluentui/react/lib/Panel"
|
||||
import { useBoolean } from "@fluentui/react-hooks"
|
||||
import { useState, useEffect, useRef, MutableRefObject, ReactElement } from "react"
|
||||
import {
|
||||
|
@ -17,10 +13,10 @@ import {
|
|||
IIconStyles,
|
||||
} from "@fluentui/react"
|
||||
import { AxiosResponse } from "axios"
|
||||
import { FetchClusterInfo } from "./Request"
|
||||
import { fetchClusterInfo } from "./Request"
|
||||
import { ICluster, headerStyles } from "./App"
|
||||
import { Nav, INavLink, INavStyles } from "@fluentui/react/lib/Nav"
|
||||
import { ClusterDetailComponent } from "./ClusterDetailList"
|
||||
import { ClusterDetailComponent, MemoisedClusterDetailListComponent } from "./ClusterDetailList"
|
||||
import React from "react"
|
||||
|
||||
const navStyles: Partial<INavStyles> = {
|
||||
|
@ -37,15 +33,6 @@ const navStyles: Partial<INavStyles> = {
|
|||
},
|
||||
}
|
||||
|
||||
|
||||
// let customPanelStyle: Partial<IPanelStyles> = {
|
||||
// root: { top: "40px", left: "225px" },
|
||||
// content: { paddingLeft: 30, paddingRight: 5 },
|
||||
// navigation: {
|
||||
// justifyContent: "flex-start",
|
||||
// },
|
||||
// }
|
||||
|
||||
const headerStyle: Partial<IStackStyles> = {
|
||||
root: {
|
||||
alignSelf: "flex-start",
|
||||
|
@ -81,6 +68,10 @@ export const overviewKey = "overview"
|
|||
export const nodesKey = "nodes"
|
||||
export const machinesKey = "machines"
|
||||
export const machineSetsKey = "machinesets"
|
||||
export const apiStatisticsKey = "apistatistics"
|
||||
export const kcmStatisticsKey = "kcmstatistics"
|
||||
export const dnsStatisticsKey = "dnsstatistics"
|
||||
export const ingressStatisticsKey = "ingressstatistics"
|
||||
|
||||
const errorBarStyles: Partial<IMessageBarStyles> = { root: { marginBottom: 15 } }
|
||||
|
||||
|
@ -122,32 +113,56 @@ export function ClusterDetailPanel(props: {
|
|||
{
|
||||
links: [
|
||||
{
|
||||
name: 'Overview',
|
||||
name: "Overview",
|
||||
key: overviewKey,
|
||||
url: '#overview',
|
||||
icon: 'ThisPC',
|
||||
url: "#overview",
|
||||
icon: "ThisPC",
|
||||
},
|
||||
{
|
||||
name: 'Nodes',
|
||||
name: "Nodes",
|
||||
key: nodesKey,
|
||||
url: '#nodes',
|
||||
icon: 'BuildQueue',
|
||||
url: "#nodes",
|
||||
icon: "BuildQueue",
|
||||
},
|
||||
{
|
||||
name: 'Machines',
|
||||
name: "Machines",
|
||||
key: machinesKey,
|
||||
url: '#machines',
|
||||
icon: 'BuildQueue',
|
||||
url: "#machines",
|
||||
icon: "BuildQueue",
|
||||
},
|
||||
{
|
||||
name: 'MachineSets',
|
||||
name: "MachineSets",
|
||||
key: machineSetsKey,
|
||||
url: '#machinesets',
|
||||
icon: 'BuildQueue',
|
||||
url: "#machinesets",
|
||||
icon: "BuildQueue",
|
||||
},
|
||||
{
|
||||
name: "APIStatistics",
|
||||
key: apiStatisticsKey,
|
||||
url: "#apistatistics",
|
||||
icon: "BIDashboard",
|
||||
},
|
||||
{
|
||||
name: "KCMStatistics",
|
||||
key: kcmStatisticsKey,
|
||||
url: "#kcmstatistics",
|
||||
icon: "BIDashboard",
|
||||
},
|
||||
{
|
||||
name: "DNSStatistics",
|
||||
key: dnsStatisticsKey,
|
||||
url: "#dnsstatistics",
|
||||
icon: "BIDashboard",
|
||||
},
|
||||
{
|
||||
name: "IngressStatistics",
|
||||
key: ingressStatisticsKey,
|
||||
url: "#ingressstatistics",
|
||||
icon: "BIDashboard",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
// updateData - updates the state of the component
|
||||
// can be used if we want a refresh button.
|
||||
|
@ -187,7 +202,7 @@ export function ClusterDetailPanel(props: {
|
|||
if (fetching === "" && props.loaded === "DONE" && resourceID != "") {
|
||||
setFetching("FETCHING")
|
||||
setError(null)
|
||||
FetchClusterInfo(props.currentCluster).then(onData)
|
||||
fetchClusterInfo(props.currentCluster).then(onData)
|
||||
}
|
||||
}, [data, fetching, setFetching])
|
||||
|
||||
|
@ -217,15 +232,18 @@ export function ClusterDetailPanel(props: {
|
|||
}
|
||||
}
|
||||
|
||||
const [doubleChevronIconProp, setdoubleChevronIconProp] = useState({ iconName: "doublechevronleft"})
|
||||
function _onClickDoubleChevronIcon() {
|
||||
// const [doubleChevronIconProp, setdoubleChevronIconProp] = useState({ iconName: "doublechevronleft"})
|
||||
const doubleChevronIconProp = useRef({ iconName: "doublechevronleft" })
|
||||
const _onClickDoubleChevronIcon = () => {
|
||||
let customPanelStyleRootLeft
|
||||
if (doubleChevronIconProp.iconName == "doublechevronright") {
|
||||
if (doubleChevronIconProp.current.iconName == "doublechevronright") {
|
||||
customPanelStyleRootLeft = "225px"
|
||||
setdoubleChevronIconProp({ iconName: "doublechevronleft"})
|
||||
// setdoubleChevronIconProp({ iconName: "doublechevronleft"})
|
||||
doubleChevronIconProp.current = { iconName: "doublechevronleft" }
|
||||
} else {
|
||||
customPanelStyleRootLeft = "0px"
|
||||
setdoubleChevronIconProp({ iconName: "doublechevronright"})
|
||||
// setdoubleChevronIconProp({ iconName: "doublechevronright"})
|
||||
doubleChevronIconProp.current = { iconName: "doublechevronright" }
|
||||
}
|
||||
|
||||
setcustomPanelStyle({
|
||||
|
@ -237,16 +255,14 @@ export function ClusterDetailPanel(props: {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
const onRenderHeader = (
|
||||
): ReactElement => {
|
||||
const onRenderHeader = (): ReactElement => {
|
||||
return (
|
||||
<>
|
||||
<Stack styles={headerStyle} horizontal>
|
||||
<Stack.Item styles={doubleChevronIconStyle}>
|
||||
<IconButton
|
||||
onClick={_onClickDoubleChevronIcon}
|
||||
iconProps={doubleChevronIconProp}
|
||||
iconProps={doubleChevronIconProp.current}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
|
@ -287,7 +303,7 @@ export function ClusterDetailPanel(props: {
|
|||
</Stack.Item>
|
||||
<Separator vertical />
|
||||
<Stack.Item grow>
|
||||
<ClusterDetailComponent
|
||||
<MemoisedClusterDetailListComponent
|
||||
item={data}
|
||||
cluster={props.currentCluster}
|
||||
isDataLoaded={dataLoaded}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { Component } from "react"
|
||||
import { OverviewWrapper } from './ClusterDetailListComponents/OverviewWrapper';
|
||||
import { NodesWrapper } from './ClusterDetailListComponents/NodesWrapper';
|
||||
import { MachinesWrapper } from "./ClusterDetailListComponents/MachinesWrapper";
|
||||
import { MachineSetsWrapper } from "./ClusterDetailListComponents/MachineSetsWrapper";
|
||||
import React from "react"
|
||||
import { OverviewWrapper } from "./ClusterDetailListComponents/OverviewWrapper"
|
||||
import { NodesWrapper } from "./ClusterDetailListComponents/NodesWrapper"
|
||||
import { MachinesWrapper } from "./ClusterDetailListComponents/MachinesWrapper"
|
||||
import { MachineSetsWrapper } from "./ClusterDetailListComponents/MachineSetsWrapper"
|
||||
import { Statistics } from "./ClusterDetailListComponents/Statistics/Statistics"
|
||||
|
||||
import { ICluster } from "./App"
|
||||
|
||||
interface ClusterDetailComponentProps {
|
||||
|
@ -38,38 +41,95 @@ interface IClusterDetailComponentState {
|
|||
detailPanelSelected: string
|
||||
}
|
||||
|
||||
export class ClusterDetailComponent extends Component<ClusterDetailComponentProps, IClusterDetailComponentState> {
|
||||
|
||||
export class ClusterDetailComponent extends Component<
|
||||
ClusterDetailComponentProps,
|
||||
IClusterDetailComponentState
|
||||
> {
|
||||
constructor(props: ClusterDetailComponentProps | Readonly<ClusterDetailComponentProps>) {
|
||||
super(props);
|
||||
super(props)
|
||||
}
|
||||
|
||||
public render() {
|
||||
switch (this.props.detailPanelVisible.toLowerCase()) {
|
||||
case "overview":
|
||||
{
|
||||
case "overview": {
|
||||
return (
|
||||
<OverviewWrapper clusterName= {this.props.item.name} currentCluster={this.props.cluster!} detailPanelSelected={this.props.detailPanelVisible} loaded={this.props.isDataLoaded}/>
|
||||
<OverviewWrapper
|
||||
clusterName={this.props.item.name}
|
||||
currentCluster={this.props.cluster!}
|
||||
detailPanelSelected={this.props.detailPanelVisible}
|
||||
loaded={this.props.isDataLoaded}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "nodes":
|
||||
{
|
||||
return (
|
||||
<NodesWrapper currentCluster={this.props.cluster!} detailPanelSelected={this.props.detailPanelVisible} loaded={this.props.isDataLoaded}/>
|
||||
);
|
||||
}
|
||||
case "machines":
|
||||
{
|
||||
return (
|
||||
<MachinesWrapper currentCluster={this.props.cluster!} detailPanelSelected={this.props.detailPanelVisible} loaded={this.props.isDataLoaded}/>
|
||||
);
|
||||
}
|
||||
case "machinesets":
|
||||
{
|
||||
return (
|
||||
<MachineSetsWrapper currentCluster={this.props.cluster!} detailPanelSelected={this.props.detailPanelVisible} loaded={this.props.isDataLoaded}/>
|
||||
);
|
||||
}
|
||||
case "nodes": {
|
||||
return (
|
||||
<NodesWrapper
|
||||
currentCluster={this.props.cluster!}
|
||||
detailPanelSelected={this.props.detailPanelVisible}
|
||||
loaded={this.props.isDataLoaded}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "machines": {
|
||||
return (
|
||||
<MachinesWrapper
|
||||
currentCluster={this.props.cluster!}
|
||||
detailPanelSelected={this.props.detailPanelVisible}
|
||||
loaded={this.props.isDataLoaded}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "machinesets": {
|
||||
return (
|
||||
<MachineSetsWrapper
|
||||
currentCluster={this.props.cluster!}
|
||||
detailPanelSelected={this.props.detailPanelVisible}
|
||||
loaded={this.props.isDataLoaded}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "apistatistics": {
|
||||
return (
|
||||
<Statistics
|
||||
currentCluster={this.props.cluster!}
|
||||
detailPanelSelected={this.props.detailPanelVisible}
|
||||
loaded={this.props.isDataLoaded}
|
||||
statisticsType={"api"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "kcmstatistics": {
|
||||
return (
|
||||
<Statistics
|
||||
currentCluster={this.props.cluster!}
|
||||
detailPanelSelected={this.props.detailPanelVisible}
|
||||
loaded={this.props.isDataLoaded}
|
||||
statisticsType={"kcm"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "dnsstatistics": {
|
||||
return (
|
||||
<Statistics
|
||||
currentCluster={this.props.cluster!}
|
||||
detailPanelSelected={this.props.detailPanelVisible}
|
||||
loaded={this.props.isDataLoaded}
|
||||
statisticsType={"dns"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "ingressstatistics": {
|
||||
return (
|
||||
<Statistics
|
||||
currentCluster={this.props.cluster!}
|
||||
detailPanelSelected={this.props.detailPanelVisible}
|
||||
loaded={this.props.isDataLoaded}
|
||||
statisticsType={"ingress"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const MemoisedClusterDetailListComponent = React.memo(ClusterDetailComponent)
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { useState, useEffect, useRef } from "react"
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { FetchMachineSets } from '../Request';
|
||||
import { AxiosResponse } from "axios"
|
||||
import { fetchMachineSets } from "../Request"
|
||||
import { ICluster } from "../App"
|
||||
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from '@fluentui/react';
|
||||
import { machineSetsKey } from "../ClusterDetail";
|
||||
import { MachineSetsListComponent } from "./MachineSetsList";
|
||||
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from "@fluentui/react"
|
||||
import { machineSetsKey } from "../ClusterDetail"
|
||||
import { MachineSetsListComponent } from "./MachineSetsList"
|
||||
|
||||
export interface IMachineSet {
|
||||
name?: string,
|
||||
type?: string,
|
||||
createdAt?: string,
|
||||
desiredReplicas?: string,
|
||||
replicas?: string,
|
||||
errorReason?: string,
|
||||
name?: string
|
||||
type?: string
|
||||
createdAt?: string
|
||||
desiredReplicas?: string
|
||||
replicas?: string
|
||||
errorReason?: string
|
||||
errorMessage?: string
|
||||
publicLoadBalancerName?: string
|
||||
subnet?: string
|
||||
|
@ -22,9 +22,9 @@ export interface IMachineSet {
|
|||
}
|
||||
|
||||
export interface IOSDisk {
|
||||
diskSettings: string,
|
||||
diskSizeGB: string,
|
||||
managedDisk: IManagedDisk,
|
||||
diskSettings: string
|
||||
diskSizeGB: string
|
||||
managedDisk: IManagedDisk
|
||||
osType: string
|
||||
}
|
||||
|
||||
|
@ -51,8 +51,7 @@ export function MachineSetsWrapper(props: {
|
|||
isMultiline={false}
|
||||
onDismiss={() => setError(null)}
|
||||
dismissButtonAriaLabel="Close"
|
||||
styles={errorBarStyles}
|
||||
>
|
||||
styles={errorBarStyles}>
|
||||
{error?.statusText}
|
||||
</MessageBar>
|
||||
)
|
||||
|
@ -65,33 +64,37 @@ export function MachineSetsWrapper(props: {
|
|||
setData(newData)
|
||||
const machineSetList: IMachineSet[] = []
|
||||
if (state && state.current) {
|
||||
newData.machines.forEach((element: { name: string;
|
||||
type: string;
|
||||
createdat: string;
|
||||
desiredreplicas: number;
|
||||
replicas: number;
|
||||
errorreason: string;
|
||||
errormessage: string;
|
||||
publicloadbalancername: string;
|
||||
subnet: string;
|
||||
accountstoragetype: string;
|
||||
vNet: string;}) => {
|
||||
const machineSet: IMachineSet = {
|
||||
name: element.name,
|
||||
type: element.type,
|
||||
createdAt: element.createdat,
|
||||
desiredReplicas: element.desiredreplicas.toString(),
|
||||
replicas: element.replicas.toString(),
|
||||
errorReason: element.errorreason,
|
||||
errorMessage: element.errormessage,
|
||||
publicLoadBalancerName: element.publicloadbalancername,
|
||||
subnet: element.subnet,
|
||||
vNet: element.vNet,
|
||||
accountStorageType: element.accountstoragetype
|
||||
}
|
||||
newData.machines.forEach(
|
||||
(element: {
|
||||
name: string
|
||||
type: string
|
||||
createdat: string
|
||||
desiredreplicas: number
|
||||
replicas: number
|
||||
errorreason: string
|
||||
errormessage: string
|
||||
publicloadbalancername: string
|
||||
subnet: string
|
||||
accountstoragetype: string
|
||||
vNet: string
|
||||
}) => {
|
||||
const machineSet: IMachineSet = {
|
||||
name: element.name,
|
||||
type: element.type,
|
||||
createdAt: element.createdat,
|
||||
desiredReplicas: element.desiredreplicas.toString(),
|
||||
replicas: element.replicas.toString(),
|
||||
errorReason: element.errorreason,
|
||||
errorMessage: element.errormessage,
|
||||
publicLoadBalancerName: element.publicloadbalancername,
|
||||
subnet: element.subnet,
|
||||
vNet: element.vNet,
|
||||
accountStorageType: element.accountstoragetype,
|
||||
}
|
||||
|
||||
machineSetList.push(machineSet)
|
||||
});
|
||||
machineSetList.push(machineSet)
|
||||
}
|
||||
)
|
||||
state.current.setState({ machineSets: machineSetList })
|
||||
}
|
||||
}
|
||||
|
@ -106,12 +109,14 @@ export function MachineSetsWrapper(props: {
|
|||
setFetching(props.currentCluster.name)
|
||||
}
|
||||
|
||||
if (props.detailPanelSelected.toLowerCase() == machineSetsKey &&
|
||||
fetching === "" &&
|
||||
props.loaded &&
|
||||
props.currentCluster.name != "") {
|
||||
if (
|
||||
props.detailPanelSelected.toLowerCase() == machineSetsKey &&
|
||||
fetching === "" &&
|
||||
props.loaded &&
|
||||
props.currentCluster.name != ""
|
||||
) {
|
||||
setFetching("FETCHING")
|
||||
FetchMachineSets(props.currentCluster).then(onData)
|
||||
fetchMachineSets(props.currentCluster).then(onData)
|
||||
}
|
||||
}, [data, props.loaded, props.detailPanelSelected])
|
||||
|
||||
|
@ -119,8 +124,12 @@ export function MachineSetsWrapper(props: {
|
|||
<Stack>
|
||||
<Stack.Item grow>{error && errorBar()}</Stack.Item>
|
||||
<Stack>
|
||||
<MachineSetsListComponent machineSets={data!} ref={state} clusterName={props.currentCluster != null ? props.currentCluster.name : ""}/>
|
||||
<MachineSetsListComponent
|
||||
machineSets={data!}
|
||||
ref={state}
|
||||
clusterName={props.currentCluster != null ? props.currentCluster.name : ""}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { useState, useEffect, useRef } from "react"
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { FetchMachines } from '../Request';
|
||||
import { AxiosResponse } from "axios"
|
||||
import { fetchMachines } from "../Request"
|
||||
import { ICluster } from "../App"
|
||||
import { MachinesListComponent } from './MachinesList';
|
||||
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from '@fluentui/react';
|
||||
import { machinesKey } from "../ClusterDetail";
|
||||
import { MachinesListComponent } from "./MachinesList"
|
||||
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from "@fluentui/react"
|
||||
import { machinesKey } from "../ClusterDetail"
|
||||
|
||||
export interface IMachine {
|
||||
name?: string,
|
||||
createdTime: string,
|
||||
lastUpdated: string,
|
||||
errorReason: string,
|
||||
errorMessage: string,
|
||||
lastOperation: string,
|
||||
lastOperationDate: string,
|
||||
name?: string
|
||||
createdTime: string
|
||||
lastUpdated: string
|
||||
errorReason: string
|
||||
errorMessage: string
|
||||
lastOperation: string
|
||||
lastOperationDate: string
|
||||
status: string
|
||||
}
|
||||
|
||||
|
@ -36,8 +36,7 @@ export function MachinesWrapper(props: {
|
|||
isMultiline={false}
|
||||
onDismiss={() => setError(null)}
|
||||
dismissButtonAriaLabel="Close"
|
||||
styles={errorBarStyles}
|
||||
>
|
||||
styles={errorBarStyles}>
|
||||
{error?.statusText}
|
||||
</MessageBar>
|
||||
)
|
||||
|
@ -50,26 +49,30 @@ export function MachinesWrapper(props: {
|
|||
setData(newData)
|
||||
const machineList: IMachine[] = []
|
||||
if (state && state.current) {
|
||||
newData.machines.forEach((element: { name: string;
|
||||
createdTime: string;
|
||||
lastUpdated: string;
|
||||
errorReason: string;
|
||||
errorMessage: string;
|
||||
lastOperation: string;
|
||||
lastOperationDate: string;
|
||||
status: string; }) => {
|
||||
const machine: IMachine = {
|
||||
name: element.name,
|
||||
createdTime: element.createdTime,
|
||||
lastUpdated: element.lastUpdated,
|
||||
errorReason: element.errorReason,
|
||||
errorMessage: element.errorMessage,
|
||||
lastOperation: element.lastOperation,
|
||||
lastOperationDate: element.lastOperationDate,
|
||||
status: element.status,
|
||||
newData.machines.forEach(
|
||||
(element: {
|
||||
name: string
|
||||
createdTime: string
|
||||
lastUpdated: string
|
||||
errorReason: string
|
||||
errorMessage: string
|
||||
lastOperation: string
|
||||
lastOperationDate: string
|
||||
status: string
|
||||
}) => {
|
||||
const machine: IMachine = {
|
||||
name: element.name,
|
||||
createdTime: element.createdTime,
|
||||
lastUpdated: element.lastUpdated,
|
||||
errorReason: element.errorReason,
|
||||
errorMessage: element.errorMessage,
|
||||
lastOperation: element.lastOperation,
|
||||
lastOperationDate: element.lastOperationDate,
|
||||
status: element.status,
|
||||
}
|
||||
machineList.push(machine)
|
||||
}
|
||||
machineList.push(machine)
|
||||
});
|
||||
)
|
||||
state.current.setState({ machines: machineList })
|
||||
}
|
||||
}
|
||||
|
@ -84,12 +87,14 @@ export function MachinesWrapper(props: {
|
|||
setFetching(props.currentCluster.name)
|
||||
}
|
||||
|
||||
if (props.detailPanelSelected.toLowerCase() == machinesKey &&
|
||||
fetching === "" &&
|
||||
props.loaded &&
|
||||
props.currentCluster.name != "") {
|
||||
if (
|
||||
props.detailPanelSelected.toLowerCase() == machinesKey &&
|
||||
fetching === "" &&
|
||||
props.loaded &&
|
||||
props.currentCluster.name != ""
|
||||
) {
|
||||
setFetching("FETCHING")
|
||||
FetchMachines(props.currentCluster).then(onData)
|
||||
fetchMachines(props.currentCluster).then(onData)
|
||||
}
|
||||
}, [data, props.loaded, props.detailPanelSelected])
|
||||
|
||||
|
@ -97,8 +102,12 @@ export function MachinesWrapper(props: {
|
|||
<Stack>
|
||||
<Stack.Item grow>{error && errorBar()}</Stack.Item>
|
||||
<Stack>
|
||||
<MachinesListComponent machines={data!} ref={state} clusterName={props.currentCluster != null ? props.currentCluster.name : ""} />
|
||||
<MachinesListComponent
|
||||
machines={data!}
|
||||
ref={state}
|
||||
clusterName={props.currentCluster != null ? props.currentCluster.name : ""}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import { useState, useEffect, useRef } from "react"
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { FetchNodes } from '../Request';
|
||||
import { AxiosResponse } from "axios"
|
||||
import { fetchNodes } from "../Request"
|
||||
import { ICluster } from "../App"
|
||||
import { NodesListComponent } from './NodesList';
|
||||
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from '@fluentui/react';
|
||||
import { nodesKey } from "../ClusterDetail";
|
||||
import { NodesListComponent } from "./NodesList"
|
||||
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from "@fluentui/react"
|
||||
import { nodesKey } from "../ClusterDetail"
|
||||
|
||||
export interface ICondition {
|
||||
status: string,
|
||||
lastHeartbeatTime: string,
|
||||
lastTransitionTime: string,
|
||||
status: string
|
||||
lastHeartbeatTime: string
|
||||
lastTransitionTime: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface ITaint {
|
||||
key: string,
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface IVolume {
|
||||
Path: string,
|
||||
Path: string
|
||||
}
|
||||
|
||||
export interface IResourceUsage {
|
||||
|
@ -29,11 +29,11 @@ export interface IResourceUsage {
|
|||
}
|
||||
|
||||
export interface INode {
|
||||
name: string,
|
||||
createdTime: string,
|
||||
capacity: IResourceUsage,
|
||||
name: string
|
||||
createdTime: string
|
||||
capacity: IResourceUsage
|
||||
allocatable: IResourceUsage
|
||||
conditions?: ICondition[],
|
||||
conditions?: ICondition[]
|
||||
taints?: ITaint[]
|
||||
labels?: Map<string, string>
|
||||
annotations?: Map<string, string>
|
||||
|
@ -52,7 +52,7 @@ export function NodesWrapper(props: {
|
|||
const [data, setData] = useState<any>([])
|
||||
const [error, setError] = useState<AxiosResponse | null>(null)
|
||||
const state = useRef<NodesListComponent>(null)
|
||||
|
||||
|
||||
const [fetching, setFetching] = useState("")
|
||||
|
||||
const errorBarStyles: Partial<IMessageBarStyles> = { root: { marginBottom: 15 } }
|
||||
|
@ -64,8 +64,7 @@ export function NodesWrapper(props: {
|
|||
isMultiline={false}
|
||||
onDismiss={() => setError(null)}
|
||||
dismissButtonAriaLabel="Close"
|
||||
styles={errorBarStyles}
|
||||
>
|
||||
styles={errorBarStyles}>
|
||||
{error?.statusText}
|
||||
</MessageBar>
|
||||
)
|
||||
|
@ -78,43 +77,47 @@ export function NodesWrapper(props: {
|
|||
setData(newData)
|
||||
const nodeList: INode[] = []
|
||||
if (state && state.current) {
|
||||
newData.nodes.forEach((element: { name: any;
|
||||
createdTime: any;
|
||||
capacity: any;
|
||||
allocatable: any;
|
||||
taints: ITaint[],
|
||||
conditions: ICondition[],
|
||||
labels: Record<string, string>,
|
||||
annotations: Record<string, string>,
|
||||
volumes: IVolume[]}) => {
|
||||
const node: INode = {
|
||||
name: element.name,
|
||||
createdTime: element.createdTime,
|
||||
capacity: element.capacity,
|
||||
allocatable: element.allocatable,
|
||||
}
|
||||
node.taints = []
|
||||
element.taints.forEach((taint: ITaint) => {
|
||||
node.taints!.push(taint)
|
||||
});
|
||||
node.conditions = []
|
||||
element.conditions.forEach((condition: ICondition) => {
|
||||
node.conditions!.push(condition)
|
||||
});
|
||||
node.labels = new Map([])
|
||||
Object.entries(element.labels).forEach((label: [string, string]) => {
|
||||
newData.nodes.forEach(
|
||||
(element: {
|
||||
name: any
|
||||
createdTime: any
|
||||
capacity: any
|
||||
allocatable: any
|
||||
taints: ITaint[]
|
||||
conditions: ICondition[]
|
||||
labels: Record<string, string>
|
||||
annotations: Record<string, string>
|
||||
volumes: IVolume[]
|
||||
}) => {
|
||||
const node: INode = {
|
||||
name: element.name,
|
||||
createdTime: element.createdTime,
|
||||
capacity: element.capacity,
|
||||
allocatable: element.allocatable,
|
||||
}
|
||||
node.taints = []
|
||||
element.taints.forEach((taint: ITaint) => {
|
||||
node.taints!.push(taint)
|
||||
})
|
||||
node.conditions = []
|
||||
element.conditions.forEach((condition: ICondition) => {
|
||||
node.conditions!.push(condition)
|
||||
})
|
||||
node.labels = new Map([])
|
||||
Object.entries(element.labels).forEach((label: [string, string]) => {
|
||||
node.labels?.set(label[0], label[1])
|
||||
});
|
||||
node.volumes = []
|
||||
element.volumes.forEach((volume: IVolume) => {
|
||||
node.volumes!.push(volume)
|
||||
});
|
||||
node.annotations = new Map([])
|
||||
Object.entries(element.annotations).forEach((annotation: [string, string]) => {
|
||||
node.annotations?.set(annotation[0], annotation[1])
|
||||
});
|
||||
nodeList.push(node)
|
||||
});
|
||||
})
|
||||
node.volumes = []
|
||||
element.volumes.forEach((volume: IVolume) => {
|
||||
node.volumes!.push(volume)
|
||||
})
|
||||
node.annotations = new Map([])
|
||||
Object.entries(element.annotations).forEach((annotation: [string, string]) => {
|
||||
node.annotations?.set(annotation[0], annotation[1])
|
||||
})
|
||||
nodeList.push(node)
|
||||
}
|
||||
)
|
||||
state.current.setState({ nodes: nodeList })
|
||||
}
|
||||
}
|
||||
|
@ -129,21 +132,27 @@ export function NodesWrapper(props: {
|
|||
setFetching(props.currentCluster.name)
|
||||
}
|
||||
|
||||
if (props.detailPanelSelected.toLowerCase() == nodesKey &&
|
||||
fetching === "" &&
|
||||
props.loaded &&
|
||||
props.currentCluster.name != "") {
|
||||
if (
|
||||
props.detailPanelSelected.toLowerCase() == nodesKey &&
|
||||
fetching === "" &&
|
||||
props.loaded &&
|
||||
props.currentCluster.name != ""
|
||||
) {
|
||||
setFetching("FETCHING")
|
||||
FetchNodes(props.currentCluster).then(onData)
|
||||
fetchNodes(props.currentCluster).then(onData)
|
||||
}
|
||||
}, [data, props.loaded, props.detailPanelSelected])
|
||||
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item grow>{error && errorBar()}</Stack.Item>
|
||||
<Stack>
|
||||
<NodesListComponent nodes={data!} ref={state} clusterName={props.currentCluster != null ? props.currentCluster.name : ""} />
|
||||
<NodesListComponent
|
||||
nodes={data!}
|
||||
ref={state}
|
||||
clusterName={props.currentCluster != null ? props.currentCluster.name : ""}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { useState, useEffect, useRef } from "react"
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { FetchClusterInfo } from '../Request';
|
||||
import { AxiosResponse } from "axios"
|
||||
import { fetchClusterInfo } from "../Request"
|
||||
import { ICluster } from "../App"
|
||||
import { ClusterDetailComponent } from '../ClusterDetailList'
|
||||
import { OverviewComponent } from './Overview';
|
||||
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from '@fluentui/react';
|
||||
import { overviewKey } from "../ClusterDetail";
|
||||
import { ClusterDetailComponent } from "../ClusterDetailList"
|
||||
import { OverviewComponent } from "./Overview"
|
||||
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from "@fluentui/react"
|
||||
import { overviewKey } from "../ClusterDetail"
|
||||
|
||||
const errorBarStyles: Partial<IMessageBarStyles> = { root: { marginBottom: 15 } }
|
||||
|
||||
|
@ -27,8 +27,7 @@ export function OverviewWrapper(props: {
|
|||
isMultiline={false}
|
||||
onDismiss={() => setError(null)}
|
||||
dismissButtonAriaLabel="Close"
|
||||
styles={errorBarStyles}
|
||||
>
|
||||
styles={errorBarStyles}>
|
||||
{error?.statusText}
|
||||
</MessageBar>
|
||||
)
|
||||
|
@ -54,12 +53,14 @@ export function OverviewWrapper(props: {
|
|||
setFetching(props.currentCluster.name)
|
||||
}
|
||||
|
||||
if (props.detailPanelSelected.toLowerCase() == overviewKey &&
|
||||
fetching === "" &&
|
||||
props.loaded &&
|
||||
props.currentCluster.name != "") {
|
||||
if (
|
||||
props.detailPanelSelected.toLowerCase() == overviewKey &&
|
||||
fetching === "" &&
|
||||
props.loaded &&
|
||||
props.currentCluster.name != ""
|
||||
) {
|
||||
setFetching("FETCHING")
|
||||
FetchClusterInfo(props.currentCluster).then(onData)
|
||||
fetchClusterInfo(props.currentCluster).then(onData)
|
||||
}
|
||||
}, [data, props.loaded, props.clusterName])
|
||||
|
||||
|
@ -67,8 +68,11 @@ export function OverviewWrapper(props: {
|
|||
<Stack>
|
||||
<Stack.Item grow>{error && errorBar()}</Stack.Item>
|
||||
<Stack>
|
||||
<OverviewComponent item={data} clusterName={props.currentCluster != null ? props.currentCluster.name : ""}/>
|
||||
<OverviewComponent
|
||||
item={data}
|
||||
clusterName={props.currentCluster != null ? props.currentCluster.name : ""}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
import { TimePicker, IComboBox, DatePicker, IDatePickerStyles, Stack, Text, TooltipHost, IStackStyles, IconButton, mergeStyleSets } from "@fluentui/react"
|
||||
import { iconButtonStyles } from "./Statistics"
|
||||
|
||||
export function GraphOptionsComponent(props: {duration: string, setDuration: React.Dispatch<React.SetStateAction<string>> , endDate: Date, setEndDate: React.Dispatch<React.SetStateAction<Date>>}): any {
|
||||
const calculatorAddition = { iconName: "CalculatorAddition" }
|
||||
const calculatorSubtract = { iconName: "CalculatorSubtract" }
|
||||
const _increaseDuration = () => {
|
||||
let setToDuration = getIncreaseDurationMap().get(props.duration)
|
||||
if (setToDuration != undefined) {
|
||||
props.setDuration(setToDuration)
|
||||
}
|
||||
}
|
||||
const _decreaseDuration = () => {
|
||||
let setToDuration = getDecreaseDurationMap().get(props.duration)
|
||||
if (setToDuration != undefined) {
|
||||
props.setDuration(setToDuration)
|
||||
}
|
||||
}
|
||||
const durationStyle: Partial<IStackStyles> = {
|
||||
root: {
|
||||
alignSelf: "flex-start",
|
||||
border: "2px",
|
||||
marginLeft: "3px",
|
||||
marginRight: "3px",
|
||||
},
|
||||
}
|
||||
const dateTimePickerStyles = mergeStyleSets({
|
||||
iconContainer: {
|
||||
marginLeft: 5,
|
||||
},
|
||||
})
|
||||
const classNames = mergeStyleSets({
|
||||
iconContainer: {
|
||||
margin: "0px 0px",
|
||||
height: 25,
|
||||
width: "90px",
|
||||
},
|
||||
})
|
||||
|
||||
const datePickerStyles: Partial<IDatePickerStyles> = { root: { maxWidth: 145, marginTop: 5, } };
|
||||
const timePickerStyles: Partial<IDatePickerStyles> = { root: { maxWidth: 75, marginLeft: 5, marginRight: 5 } };
|
||||
const onTimeChange = (event: React.FormEvent<IComboBox>, date: Date) => {
|
||||
var localDate = new Date()
|
||||
localDate.setUTCFullYear(props.endDate.getFullYear(), props.endDate.getMonth(), props.endDate.getDate())
|
||||
localDate.setUTCHours(date.getHours());
|
||||
localDate.setUTCMinutes(date.getMinutes());
|
||||
props.setEndDate(localDate)
|
||||
};
|
||||
const onDateChange = (date: Date | null | undefined): void => {
|
||||
let localDate: Date = new Date();
|
||||
localDate.setUTCFullYear(date!.getFullYear(), date!.getMonth(), date!.getDate())
|
||||
localDate.setHours(props.endDate.getHours());
|
||||
localDate.setMinutes(props.endDate.getMinutes());
|
||||
props.setEndDate(localDate)
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Stack horizontal verticalAlign="center" className={classNames.iconContainer} /* style={{ boxShadow: theme.effects.elevation8 }}*/>
|
||||
<TooltipHost>
|
||||
<IconButton
|
||||
onClick={_decreaseDuration}
|
||||
iconProps={calculatorSubtract}
|
||||
styles={iconButtonStyles}
|
||||
/>
|
||||
</TooltipHost>
|
||||
<TooltipHost>
|
||||
<Text styles={durationStyle}>{props.duration}</Text>
|
||||
</TooltipHost>
|
||||
<TooltipHost >
|
||||
<IconButton
|
||||
onClick={_increaseDuration}
|
||||
iconProps={calculatorAddition}
|
||||
styles={iconButtonStyles}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
<Stack horizontal verticalAlign="center" className={dateTimePickerStyles.iconContainer} /* style={{ boxShadow: theme.effects.elevation8 }}*/>
|
||||
<DatePicker
|
||||
styles={datePickerStyles}
|
||||
placeholder="End Date"
|
||||
ariaLabel="End Date"
|
||||
onSelectDate={onDateChange}
|
||||
value={convertToUTC(props.endDate)}
|
||||
allowTextInput
|
||||
/>
|
||||
<TimePicker
|
||||
styles={timePickerStyles}
|
||||
allowFreeform
|
||||
placeholder={timeToString(convertToUTC(props.endDate))}
|
||||
autoComplete="on"
|
||||
onChange={onTimeChange}
|
||||
defaultValue={convertToUTC(props.endDate)}
|
||||
useComboBoxAsMenuWidth
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
function timeToString(date: Date): string {
|
||||
var str
|
||||
let hourString = date.getHours().toString()
|
||||
str = hourString + ":"
|
||||
if (hourString.length === 1) {
|
||||
str = "0" + hourString + ":"
|
||||
}
|
||||
let minuteString = date.getMinutes().toString()
|
||||
if (minuteString.length === 1) {
|
||||
str = str + "0"
|
||||
}
|
||||
str += minuteString
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
export const convertToUTC = (date: Date): Date => {
|
||||
let localDate = new Date()
|
||||
localDate.setFullYear(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
|
||||
localDate.setHours(date.getUTCHours())
|
||||
localDate.setMinutes(date.getUTCMinutes())
|
||||
|
||||
return localDate
|
||||
}
|
||||
|
||||
export const convertTimeToHours = (duration: string): string => {
|
||||
let durationMap = new Map<string, string>()
|
||||
durationMap.set("1d", "24h")
|
||||
durationMap.set("2d", "48h")
|
||||
durationMap.set("1w", "168h")
|
||||
durationMap.set("2w", "336h")
|
||||
durationMap.set("4w", "672h")
|
||||
durationMap.set("8w", "1344h")
|
||||
if (durationMap.has(duration)) {
|
||||
return durationMap.get(duration)!
|
||||
}
|
||||
return duration
|
||||
}
|
||||
|
||||
const getIncreaseDurationMap = (): Map<string, string> => {
|
||||
let increaseDurationMap = new Map<string, string>()
|
||||
increaseDurationMap.set("1m", "5m")
|
||||
increaseDurationMap.set("5m", "10m")
|
||||
increaseDurationMap.set("10m", "30m")
|
||||
increaseDurationMap.set("30m", "1h")
|
||||
increaseDurationMap.set("1h", "2h")
|
||||
increaseDurationMap.set("2h", "6h")
|
||||
increaseDurationMap.set("6h", "12h")
|
||||
increaseDurationMap.set("12h", "1d")
|
||||
increaseDurationMap.set("1d", "2d")
|
||||
increaseDurationMap.set("2d", "1w")
|
||||
increaseDurationMap.set("1w", "2w")
|
||||
increaseDurationMap.set("2w", "4w")
|
||||
increaseDurationMap.set("4w", "8w")
|
||||
|
||||
return increaseDurationMap
|
||||
}
|
||||
|
||||
const getDecreaseDurationMap = (): Map<string, string> => {
|
||||
let decreaseDurationMap = new Map<string, string>()
|
||||
decreaseDurationMap.set("8w", "4w")
|
||||
decreaseDurationMap.set("4w", "2w")
|
||||
decreaseDurationMap.set("2w", "1w")
|
||||
decreaseDurationMap.set("1w", "2d")
|
||||
decreaseDurationMap.set("2d", "1d")
|
||||
decreaseDurationMap.set("1d", "12h")
|
||||
decreaseDurationMap.set("12h", "6h")
|
||||
decreaseDurationMap.set("6h", "2h")
|
||||
decreaseDurationMap.set("2h", "1h")
|
||||
decreaseDurationMap.set("1h", "30m")
|
||||
decreaseDurationMap.set("30m", "10m")
|
||||
decreaseDurationMap.set("10m", "5m")
|
||||
decreaseDurationMap.set("5m", "1m")
|
||||
|
||||
return decreaseDurationMap
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
import { useState } from "react"
|
||||
import { ICluster } from "../../App"
|
||||
import {
|
||||
Modal,
|
||||
Stack,
|
||||
Text,
|
||||
IStackStyles,
|
||||
IconButton,
|
||||
mergeStyleSets,
|
||||
getTheme,
|
||||
Label,
|
||||
ThemeProvider,
|
||||
IStackTokens,
|
||||
PartialTheme,
|
||||
DefaultPalette,
|
||||
} from "@fluentui/react"
|
||||
import { useBoolean } from "@fluentui/react-hooks"
|
||||
import { StatisticsWrapper } from "./StatisticsWrapper"
|
||||
|
||||
import { GraphOptionsComponent } from "./GraphOptionsComponent"
|
||||
|
||||
export const iconButtonStyles = mergeStyleSets({
|
||||
icon: {
|
||||
color: "white",
|
||||
},
|
||||
root: {
|
||||
selectors: {
|
||||
":hover .ms-Button-icon": {
|
||||
color: DefaultPalette.accent,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const global = new Date()
|
||||
export function Statistics(props: {
|
||||
currentCluster: ICluster
|
||||
detailPanelSelected: string
|
||||
loaded: boolean
|
||||
statisticsType: string
|
||||
}) {
|
||||
const [globalDuration, setGlobalDuration] = useState<string>("1h")
|
||||
const [globalEndDate, setGlobalEndDate] = useState<Date>(global)
|
||||
|
||||
function GlobalGraphOptionsBar() {
|
||||
const stackStyles: IStackStyles = {
|
||||
root: [
|
||||
{
|
||||
width: "100%",
|
||||
padding: 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
const stackNavStyles: IStackStyles = {
|
||||
root: {
|
||||
padding: "0px 15px",
|
||||
height: 40,
|
||||
},
|
||||
}
|
||||
const containerStackTokens: IStackTokens = {}
|
||||
const appStackTokens: IStackTokens = { childrenGap: 10 }
|
||||
return (
|
||||
<Stack styles={stackStyles} tokens={containerStackTokens} horizontalAlign={"stretch"}>
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<Stack
|
||||
grow
|
||||
tokens={appStackTokens}
|
||||
horizontalAlign={"start"}
|
||||
verticalAlign={"center"}
|
||||
horizontal
|
||||
styles={stackNavStyles}>
|
||||
<Stack.Item grow>
|
||||
<Text>{"Global Graph Options"}</Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<GraphOptionsComponent
|
||||
duration={globalDuration}
|
||||
setDuration={setGlobalDuration}
|
||||
endDate={globalEndDate}
|
||||
setEndDate={setGlobalEndDate}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</ThemeProvider>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const darkTheme: PartialTheme = {
|
||||
semanticColors: {
|
||||
bodyBackground: DefaultPalette.accent,
|
||||
bodyText: DefaultPalette.white,
|
||||
},
|
||||
defaultFontStyle: {
|
||||
fontWeight: 500,
|
||||
},
|
||||
}
|
||||
const theme = getTheme()
|
||||
|
||||
function GraphWrapper(lprops: { heading: string; statisticsName: string }) {
|
||||
const [isModalOpen, { setTrue: showModal, setFalse: hideModal }] = useBoolean(false)
|
||||
const [duration, setDuration] = useState<string>(globalDuration)
|
||||
const [endDate, setEndDate] = useState<Date>(globalEndDate)
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
titleAriaId={lprops.heading}
|
||||
isOpen={isModalOpen}
|
||||
onDismiss={hideModal}
|
||||
isBlocking={false}>
|
||||
<Stack
|
||||
style={{ boxShadow: theme.effects.elevation8 }}
|
||||
styles={{ root: { margin: "2px" } }}>
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<Stack
|
||||
horizontalAlign="stretch"
|
||||
horizontal /*className={classNames.iconContainer} /*style={{ boxShadow: theme.effects.elevation64 }}*/
|
||||
>
|
||||
<Stack.Item grow={0.5}>
|
||||
<GraphOptionsComponent
|
||||
duration={duration}
|
||||
setDuration={setDuration}
|
||||
endDate={endDate}
|
||||
setEndDate={setEndDate}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" grow={1}>
|
||||
<Label> {lprops.heading} </Label>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Cancel" }}
|
||||
ariaLabel="Close popup modal"
|
||||
onClick={hideModal}
|
||||
styles={iconButtonStyles}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</ThemeProvider>
|
||||
<StatisticsWrapper
|
||||
currentCluster={props.currentCluster}
|
||||
detailPanelSelected={props.detailPanelSelected}
|
||||
loaded={props.loaded}
|
||||
statisticsName={lprops.statisticsName}
|
||||
duration={duration}
|
||||
endDate={endDate}
|
||||
graphHeight={500}
|
||||
graphWidth={1500}
|
||||
/>
|
||||
</Stack>
|
||||
</Modal>
|
||||
<Stack style={{ boxShadow: theme.effects.elevation8 }} styles={{ root: { margin: "2px" } }}>
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<Stack horizontalAlign="stretch" horizontal>
|
||||
<Stack.Item align="center">
|
||||
<IconButton
|
||||
onClick={showModal}
|
||||
iconProps={{ iconName: "FullScreen" }}
|
||||
styles={iconButtonStyles}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" grow={1}>
|
||||
<Text> {lprops.heading} </Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<GraphOptionsComponent
|
||||
duration={duration}
|
||||
setDuration={setDuration}
|
||||
endDate={endDate}
|
||||
setEndDate={setEndDate}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</ThemeProvider>
|
||||
<StatisticsWrapper
|
||||
currentCluster={props.currentCluster}
|
||||
detailPanelSelected={props.detailPanelSelected}
|
||||
loaded={props.loaded}
|
||||
statisticsName={lprops.statisticsName}
|
||||
duration={duration}
|
||||
endDate={endDate}
|
||||
graphHeight={200}
|
||||
graphWidth={740}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface Map {
|
||||
[key: string]: {
|
||||
stasticsName: string
|
||||
heading: string
|
||||
}[]
|
||||
}
|
||||
|
||||
let statisticsDataMap: Map = {
|
||||
api: [
|
||||
{ stasticsName: "kubeapicodes", heading: "KubeAPI Server response sizes by code and verb" },
|
||||
{ stasticsName: "kubeapicpu", heading: "KubeAPI CPU per instance" },
|
||||
{ stasticsName: "kubeapimemory", heading: "KubeAPI Memory per instance" },
|
||||
],
|
||||
kcm: [
|
||||
{
|
||||
stasticsName: "kubecontrollermanagercodes",
|
||||
heading: "Kube Controller Manager Server response sizes by code and verb",
|
||||
},
|
||||
{
|
||||
stasticsName: "kubecontrollermanagercpu",
|
||||
heading: "Kube Controller Manager CPU per instance",
|
||||
},
|
||||
{
|
||||
stasticsName: "kubecontrollermanagermemory",
|
||||
heading: "Kube Controller Manager Memory per instance",
|
||||
},
|
||||
],
|
||||
dns: [
|
||||
{
|
||||
stasticsName: "dnsresponsecodes",
|
||||
heading: "Response Codes",
|
||||
},
|
||||
{
|
||||
stasticsName: "dnsalltraffic",
|
||||
heading: "All Traffic",
|
||||
},
|
||||
{
|
||||
stasticsName: "dnserrorrate",
|
||||
heading: "Error Rate",
|
||||
},
|
||||
{
|
||||
stasticsName: "dnshealthcheck",
|
||||
heading: "Health Check",
|
||||
},
|
||||
{
|
||||
stasticsName: "dnsforwardedtraffic",
|
||||
heading: "Forwarded Traffic",
|
||||
},
|
||||
],
|
||||
ingress: [
|
||||
{
|
||||
stasticsName: "ingresscontrollercondition",
|
||||
heading: "Ingress Controller Condition",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const statisticsJSX = (sDataMap: Map): JSX.Element[] => {
|
||||
let sDataArray = sDataMap[props.statisticsType]
|
||||
let sDataArrayLength = sDataArray.length
|
||||
let stackItems: JSX.Element[] = []
|
||||
let stacks: JSX.Element[] = []
|
||||
sDataArray.map((sData, i) => {
|
||||
if (i % 2 != 0 || i === sDataArrayLength - 1) {
|
||||
stackItems.push(
|
||||
<Stack.Item>
|
||||
<GraphWrapper statisticsName={sData.stasticsName} heading={sData.heading} />
|
||||
</Stack.Item>
|
||||
)
|
||||
stacks.push(<Stack horizontal>{stackItems}</Stack>)
|
||||
stackItems = []
|
||||
return
|
||||
}
|
||||
stackItems.push(
|
||||
<Stack.Item>
|
||||
<GraphWrapper statisticsName={sData.stasticsName} heading={sData.heading} />
|
||||
</Stack.Item>
|
||||
)
|
||||
})
|
||||
return stacks
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<GlobalGraphOptionsBar />
|
||||
{statisticsJSX(statisticsDataMap)}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import { Stack, StackItem, IStackProps } from "@fluentui/react"
|
||||
import { Spinner, SpinnerSize } from "@fluentui/react/lib/Spinner"
|
||||
import {
|
||||
ILineChartPoints,
|
||||
ILegendsProps,
|
||||
IChartProps,
|
||||
LineChart,
|
||||
ILineChartDataPoint,
|
||||
ILineChartProps,
|
||||
} from "@fluentui/react-charting"
|
||||
import { DefaultPalette } from "@fluentui/react/lib/Styling"
|
||||
import { IMetrics } from "./StatisticsWrapper"
|
||||
import { convertToUTC } from "./GraphOptionsComponent"
|
||||
|
||||
export function StatisticsComponent(props: {
|
||||
metrics: IMetrics[]
|
||||
clusterName: any
|
||||
duration: string
|
||||
height: number
|
||||
width: number
|
||||
endDate: Date
|
||||
fetchStatus: string
|
||||
}) {
|
||||
const width = props.width
|
||||
const height = props.height
|
||||
const [points, setPoints] = useState<ILineChartPoints[]>([])
|
||||
const [data, setData] = useState<IChartProps>({})
|
||||
const [spinnerVisible, setSpinnerVisible] = useState<boolean>(true)
|
||||
const timeFormat = "%H:%M"
|
||||
|
||||
const colors: string[] = [
|
||||
DefaultPalette.blue,
|
||||
DefaultPalette.blueLight,
|
||||
DefaultPalette.blueDark,
|
||||
DefaultPalette.blueMid,
|
||||
DefaultPalette.black,
|
||||
DefaultPalette.red,
|
||||
DefaultPalette.redDark,
|
||||
DefaultPalette.yellow,
|
||||
DefaultPalette.yellowDark,
|
||||
DefaultPalette.yellowLight,
|
||||
DefaultPalette.green,
|
||||
DefaultPalette.greenLight,
|
||||
DefaultPalette.greenDark,
|
||||
DefaultPalette.purple,
|
||||
DefaultPalette.purpleLight,
|
||||
DefaultPalette.purpleDark,
|
||||
DefaultPalette.orange,
|
||||
DefaultPalette.orangeLight,
|
||||
DefaultPalette.orangeLighter,
|
||||
DefaultPalette.magenta,
|
||||
DefaultPalette.magentaDark,
|
||||
DefaultPalette.magentaLight,
|
||||
DefaultPalette.themePrimary,
|
||||
DefaultPalette.neutralPrimary,
|
||||
DefaultPalette.neutralDark,
|
||||
DefaultPalette.neutralSecondary,
|
||||
DefaultPalette.neutralTertiary,
|
||||
DefaultPalette.teal,
|
||||
DefaultPalette.tealDark,
|
||||
DefaultPalette.tealLight,
|
||||
DefaultPalette.accent,
|
||||
DefaultPalette.themeDarker,
|
||||
DefaultPalette.themeDarkAlt,
|
||||
DefaultPalette.themeDark,
|
||||
DefaultPalette.themeLight,
|
||||
DefaultPalette.themeLighter,
|
||||
DefaultPalette.themeLighterAlt,
|
||||
DefaultPalette.themePrimary,
|
||||
DefaultPalette.themeSecondary,
|
||||
DefaultPalette.themeTertiary,
|
||||
]
|
||||
|
||||
function StatisticsHelperComponent(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (props.fetchStatus === "error") {
|
||||
setSpinnerVisible(false)
|
||||
return
|
||||
}
|
||||
const newPoints: ILineChartPoints[] = []
|
||||
props.metrics.forEach((metric, i) => {
|
||||
var dataPoints: ILineChartDataPoint[] = []
|
||||
metric.MetricValue.forEach((metricValue) => {
|
||||
let timeStamp = new Date(metricValue.timestamp)
|
||||
let metricsTime = convertToUTC(timeStamp)
|
||||
var data: ILineChartDataPoint = {
|
||||
x: metricsTime,
|
||||
y: metricValue.value,
|
||||
}
|
||||
dataPoints.push(data)
|
||||
})
|
||||
var lineChartPoint: ILineChartPoints = {
|
||||
legend: metric.Name,
|
||||
data: dataPoints,
|
||||
color: colors[i],
|
||||
}
|
||||
newPoints.push(lineChartPoint)
|
||||
})
|
||||
setPoints(newPoints)
|
||||
}, [props.metrics, props.fetchStatus])
|
||||
|
||||
useEffect(() => {
|
||||
setData({
|
||||
chartTitle: "Line Chart",
|
||||
lineChartData: points,
|
||||
})
|
||||
props.fetchStatus === "success" ? setSpinnerVisible(false) : setSpinnerVisible(true)
|
||||
}, [points])
|
||||
|
||||
useEffect(() => {
|
||||
setSpinnerVisible(true)
|
||||
}, [props.duration, props.endDate])
|
||||
|
||||
const rootStyle = { width: `${width}px`, height: `${height}px` }
|
||||
const tokens = {
|
||||
sectionStack: {
|
||||
childrenGap: 10,
|
||||
},
|
||||
spinnerStack: {
|
||||
childrenGap: 20,
|
||||
},
|
||||
}
|
||||
const rowProps: IStackProps = { horizontal: false, verticalAlign: "center" }
|
||||
const legendProps: Partial<ILegendsProps> = {
|
||||
canSelectMultipleLegends: true,
|
||||
allowFocusOnLegends: true,
|
||||
}
|
||||
let lineChartProps: ILineChartProps = {
|
||||
data: data,
|
||||
strokeWidth: 2,
|
||||
tickFormat: timeFormat,
|
||||
height: height,
|
||||
width: width,
|
||||
legendProps: legendProps,
|
||||
}
|
||||
|
||||
const renderLineChart = (lineChartProps: ILineChartProps) => {
|
||||
return (
|
||||
<div style={rootStyle}>
|
||||
<LineChart {...lineChartProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<StackItem>
|
||||
{spinnerVisible ? (
|
||||
<Stack {...rowProps} tokens={tokens.spinnerStack}>
|
||||
<StackItem>
|
||||
<Spinner size={SpinnerSize.large} />
|
||||
</StackItem>
|
||||
{renderLineChart(lineChartProps)}
|
||||
</Stack>
|
||||
) : (
|
||||
renderLineChart(lineChartProps)
|
||||
)}
|
||||
</StackItem>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div>{StatisticsHelperComponent()}</div>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
import { useState, useEffect } from "react"
|
||||
import { AxiosResponse } from "axios"
|
||||
import { ICluster } from "../../App"
|
||||
import { StatisticsComponent } from "./StatisticsComponent"
|
||||
import { fetchStatistics } from "../../Request"
|
||||
import {
|
||||
apiStatisticsKey,
|
||||
dnsStatisticsKey,
|
||||
ingressStatisticsKey,
|
||||
kcmStatisticsKey,
|
||||
} from "../../ClusterDetail"
|
||||
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from "@fluentui/react"
|
||||
|
||||
export interface IMetricValue {
|
||||
timestamp: Date
|
||||
value: number
|
||||
}
|
||||
|
||||
export interface IMetrics {
|
||||
Name: string
|
||||
MetricValue: IMetricValue[]
|
||||
}
|
||||
|
||||
export function StatisticsWrapper(props: {
|
||||
currentCluster: ICluster
|
||||
detailPanelSelected: string
|
||||
loaded: boolean
|
||||
statisticsName: string
|
||||
duration: string
|
||||
endDate: Date
|
||||
graphHeight: number
|
||||
graphWidth: number
|
||||
}) {
|
||||
const [error, setError] = useState<AxiosResponse | null>(null)
|
||||
const [metrics, setMetrics] = useState<IMetrics[]>([])
|
||||
const [fetching, setFetching] = useState("")
|
||||
const [localDuration, setLocalDuration] = useState(props.duration)
|
||||
const [localEndDate, setLocalEndDate] = useState(props.endDate)
|
||||
const errorBarStyles: Partial<IMessageBarStyles> = { root: { marginBottom: 15 } }
|
||||
const statisticsKeys = [
|
||||
apiStatisticsKey,
|
||||
dnsStatisticsKey,
|
||||
ingressStatisticsKey,
|
||||
kcmStatisticsKey,
|
||||
]
|
||||
|
||||
const errorBar = (): any => {
|
||||
return (
|
||||
<MessageBar
|
||||
messageBarType={MessageBarType.error}
|
||||
isMultiline={false}
|
||||
onDismiss={() => setError(null)}
|
||||
dismissButtonAriaLabel="Close"
|
||||
styles={errorBarStyles}>
|
||||
{error?.statusText}
|
||||
</MessageBar>
|
||||
)
|
||||
}
|
||||
|
||||
// updateData - updates the state of the component
|
||||
// can be used if we want a refresh button.
|
||||
// api/clusterdetail returns a single item.
|
||||
const updateData = (newData: any) => {
|
||||
const metrics: IMetrics[] = []
|
||||
newData.forEach((element: { metricname: any; metricvalue: IMetricValue[] }) => {
|
||||
const metric: IMetrics = {
|
||||
Name: element.metricname,
|
||||
MetricValue: element.metricvalue,
|
||||
}
|
||||
metrics.push(metric)
|
||||
})
|
||||
setMetrics(metrics)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const onData = (result: AxiosResponse | null) => {
|
||||
if (result?.status === 200) {
|
||||
setFetching("success")
|
||||
updateData(result.data)
|
||||
setError(null)
|
||||
} else {
|
||||
setError(result)
|
||||
setFetching("error")
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
statisticsKeys.includes(props.detailPanelSelected.toLowerCase()) &&
|
||||
(fetching === "" || localDuration != props.duration || localEndDate != props.endDate) &&
|
||||
props.loaded &&
|
||||
props.currentCluster.name != ""
|
||||
) {
|
||||
setLocalDuration(props.duration)
|
||||
setLocalEndDate(props.endDate)
|
||||
setFetching("FETCHING")
|
||||
fetchStatistics(
|
||||
props.currentCluster,
|
||||
props.statisticsName,
|
||||
props.duration,
|
||||
props.endDate
|
||||
).then(onData)
|
||||
}
|
||||
}, [props.loaded, props.detailPanelSelected, props.duration, props.endDate])
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item grow>{error && errorBar()}</Stack.Item>
|
||||
<Stack>
|
||||
<StatisticsComponent
|
||||
metrics={metrics}
|
||||
fetchStatus={fetching}
|
||||
duration={props.duration}
|
||||
clusterName={props.currentCluster != null ? props.currentCluster.name : ""}
|
||||
height={props.graphHeight}
|
||||
width={props.graphWidth}
|
||||
endDate={props.endDate}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
|
@ -26,7 +26,7 @@ import {
|
|||
IDetailsListStyles,
|
||||
} from "@fluentui/react/lib/DetailsList"
|
||||
import { useBoolean } from "@fluentui/react-hooks"
|
||||
import { FetchClusters } from "./Request"
|
||||
import { fetchClusters } from "./Request"
|
||||
import { KubeconfigButton } from "./Kubeconfig"
|
||||
import { AxiosResponse } from "axios"
|
||||
import { ICluster, headerStyles } from "./App"
|
||||
|
@ -78,55 +78,53 @@ const separatorStyle = {
|
|||
|
||||
const popupStyles = mergeStyleSets({
|
||||
root: {
|
||||
background: 'rgba(0, 0, 0, 0.2)',
|
||||
bottom: '0',
|
||||
left: '0',
|
||||
position: 'fixed',
|
||||
right: '0',
|
||||
top: '0',
|
||||
background: "rgba(0, 0, 0, 0.2)",
|
||||
bottom: "0",
|
||||
left: "0",
|
||||
position: "fixed",
|
||||
right: "0",
|
||||
top: "0",
|
||||
},
|
||||
content: {
|
||||
background: 'white',
|
||||
left: '50%',
|
||||
maxWidth: '400px',
|
||||
padding: '0 2em 2em',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
background: "white",
|
||||
left: "50%",
|
||||
maxWidth: "400px",
|
||||
padding: "0 2em 2em",
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const PopupModal = (props: {title: string, text: string, hidePopup: any}) => {
|
||||
const PopupModal = (props: { title: string; text: string; hidePopup: any }) => {
|
||||
return (
|
||||
<>
|
||||
<Layer>
|
||||
<Popup
|
||||
className={popupStyles.root}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
onDismiss={props.hidePopup}
|
||||
enableAriaHiddenSiblings={true}
|
||||
>
|
||||
<FocusTrapZone>
|
||||
<div role="document" className={popupStyles.content}>
|
||||
<h2>{props.title}</h2>
|
||||
<p>
|
||||
{props.text}
|
||||
</p>
|
||||
<DefaultButton onClick={() => {
|
||||
<Layer>
|
||||
<Popup
|
||||
className={popupStyles.root}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
onDismiss={props.hidePopup}
|
||||
enableAriaHiddenSiblings={true}>
|
||||
<FocusTrapZone>
|
||||
<div role="document" className={popupStyles.content}>
|
||||
<h2>{props.title}</h2>
|
||||
<p>{props.text}</p>
|
||||
<DefaultButton
|
||||
onClick={() => {
|
||||
// this is to change the URL in the address bar
|
||||
window.history.replaceState({}, "", "/v2")
|
||||
props.hidePopup()
|
||||
}}>
|
||||
Close
|
||||
</DefaultButton>
|
||||
</div>
|
||||
</FocusTrapZone>
|
||||
</Popup>
|
||||
</Layer>
|
||||
Close
|
||||
</DefaultButton>
|
||||
</div>
|
||||
</FocusTrapZone>
|
||||
</Popup>
|
||||
</Layer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
interface IClusterListState {
|
||||
columns: IColumn[]
|
||||
|
@ -301,7 +299,9 @@ class ClusterListComponent extends Component<ClusterListComponentProps, ICluster
|
|||
<IconButton
|
||||
iconProps={{ iconName: "BIDashboard" }}
|
||||
aria-label="Prometheus"
|
||||
href={item.resourceId + (+item.version >= 4.11 ? `/prometheus` : `/prometheus/graph`)}
|
||||
href={
|
||||
item.resourceId + (+item.version >= 4.11 ? `/prometheus` : `/prometheus/graph`)
|
||||
}
|
||||
/>
|
||||
</TooltipHost>
|
||||
<TooltipHost content={`SSH`}>
|
||||
|
@ -346,7 +346,9 @@ class ClusterListComponent extends Component<ClusterListComponentProps, ICluster
|
|||
<div className={classNames.controlWrapper}>
|
||||
<TextField placeholder="Filter on resource ID" onChange={this._onChangeText} />
|
||||
</div>
|
||||
<Text id="ClusterCount" className={classNames.itemsCount}>Showing {items.length} items</Text>
|
||||
<Text id="ClusterCount" className={classNames.itemsCount}>
|
||||
Showing {items.length} items
|
||||
</Text>
|
||||
<DetailsList
|
||||
items={items}
|
||||
columns={columns}
|
||||
|
@ -372,7 +374,9 @@ class ClusterListComponent extends Component<ClusterListComponentProps, ICluster
|
|||
): void => {
|
||||
this.setState({
|
||||
items: text
|
||||
? this.props.items.filter((i) => i.resourceId.toLowerCase().indexOf(text.trim().toLowerCase()) != -1)
|
||||
? this.props.items.filter(
|
||||
(i) => i.resourceId.toLowerCase().indexOf(text.trim().toLowerCase()) != -1
|
||||
)
|
||||
: this.props.items,
|
||||
})
|
||||
}
|
||||
|
@ -425,7 +429,7 @@ export function ClusterList(props: {
|
|||
}) {
|
||||
const [data, setData] = useState<any>([])
|
||||
const [error, setError] = useState<AxiosResponse | null>(null)
|
||||
const [isPopupVisible, { setTrue: showPopup, setFalse: hidePopup }] = useBoolean(false);
|
||||
const [isPopupVisible, { setTrue: showPopup, setFalse: hidePopup }] = useBoolean(false)
|
||||
const state = useRef<ClusterListComponent>(null)
|
||||
const [fetching, setFetching] = useState("")
|
||||
|
||||
|
@ -463,13 +467,15 @@ export function ClusterList(props: {
|
|||
|
||||
if (fetching === "" && props.csrfTokenAvailable === "DONE") {
|
||||
setFetching("FETCHING")
|
||||
FetchClusters().then(onData)
|
||||
fetchClusters().then(onData)
|
||||
}
|
||||
|
||||
if (props.params) {
|
||||
const resourceID: string = props.params["resourceid"]
|
||||
const clusterList = data as ICluster[]
|
||||
const currentCluster = clusterList.find((item): item is ICluster => resourceID === item.resourceId)
|
||||
const currentCluster = clusterList.find(
|
||||
(item): item is ICluster => resourceID === item.resourceId
|
||||
)
|
||||
|
||||
if (fetching === "DONE" && !currentCluster) {
|
||||
showPopup()
|
||||
|
@ -478,7 +484,6 @@ export function ClusterList(props: {
|
|||
|
||||
props.setCurrentCluster(currentCluster)
|
||||
}
|
||||
|
||||
}, [data, fetching, setFetching, props.csrfTokenAvailable])
|
||||
|
||||
const _items: ICommandBarItemProps[] = [
|
||||
|
@ -504,10 +509,15 @@ export function ClusterList(props: {
|
|||
styles={controlStyles}
|
||||
/>
|
||||
<Separator styles={separatorStyle} />
|
||||
|
||||
|
||||
{error && errorBar()}
|
||||
|
||||
{isPopupVisible && PopupModal({title: "Resource Not Found", text: "No resource found due to Invalid/Non-existent resource ID in the URL.", hidePopup: hidePopup})}
|
||||
{isPopupVisible &&
|
||||
PopupModal({
|
||||
title: "Resource Not Found",
|
||||
text: "No resource found due to Invalid/Non-existent resource ID in the URL.",
|
||||
hidePopup: hidePopup,
|
||||
})}
|
||||
|
||||
<ClusterListComponent
|
||||
items={data}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import axios, { AxiosResponse } from "axios"
|
||||
import { ICluster } from "./App"
|
||||
import { convertTimeToHours } from "./ClusterDetailListComponents/Statistics/GraphOptionsComponent"
|
||||
|
||||
const OnError = (err: AxiosResponse): AxiosResponse | null => {
|
||||
if (err.status === 403) {
|
||||
|
@ -10,7 +11,7 @@ const OnError = (err: AxiosResponse): AxiosResponse | null => {
|
|||
}
|
||||
}
|
||||
|
||||
export const FetchClusters = async (): Promise<AxiosResponse | null> => {
|
||||
export const fetchClusters = async (): Promise<AxiosResponse | null> => {
|
||||
try {
|
||||
const result = await axios("/api/clusters")
|
||||
return result
|
||||
|
@ -20,7 +21,7 @@ export const FetchClusters = async (): Promise<AxiosResponse | null> => {
|
|||
}
|
||||
}
|
||||
|
||||
export const FetchClusterInfo = async (cluster: ICluster): Promise<AxiosResponse | null> => {
|
||||
export const fetchClusterInfo = async (cluster: ICluster): Promise<AxiosResponse | null> => {
|
||||
try {
|
||||
const result = await axios(
|
||||
"/api/" + cluster.subscription + "/" + cluster.resourceGroup + "/" + cluster.name
|
||||
|
@ -32,7 +33,7 @@ export const FetchClusterInfo = async (cluster: ICluster): Promise<AxiosResponse
|
|||
}
|
||||
}
|
||||
|
||||
export const FetchInfo = async (): Promise<AxiosResponse | null> => {
|
||||
export const fetchInfo = async (): Promise<AxiosResponse | null> => {
|
||||
try {
|
||||
const result = await axios("/api/info")
|
||||
return result
|
||||
|
@ -42,10 +43,11 @@ export const FetchInfo = async (): Promise<AxiosResponse | null> => {
|
|||
}
|
||||
}
|
||||
|
||||
export const FetchNodes = async (cluster: ICluster): Promise<AxiosResponse | null> => {
|
||||
export const fetchNodes = async (cluster: ICluster): Promise<AxiosResponse | null> => {
|
||||
try {
|
||||
const result = await axios(
|
||||
"/api/" + cluster.subscription + "/" + cluster.resourceGroup + "/" + cluster.name + "/nodes")
|
||||
"/api/" + cluster.subscription + "/" + cluster.resourceGroup + "/" + cluster.name + "/nodes"
|
||||
)
|
||||
return result
|
||||
} catch (e: any) {
|
||||
const err = e.response as AxiosResponse
|
||||
|
@ -53,10 +55,17 @@ export const FetchNodes = async (cluster: ICluster): Promise<AxiosResponse | nul
|
|||
}
|
||||
}
|
||||
|
||||
export const FetchMachines = async (cluster: ICluster): Promise<AxiosResponse | null> => {
|
||||
export const fetchMachines = async (cluster: ICluster): Promise<AxiosResponse | null> => {
|
||||
try {
|
||||
const result = await axios(
|
||||
"/api/" + cluster.subscription + "/" + cluster.resourceGroup + "/" + cluster.name + "/machines")
|
||||
"/api/" +
|
||||
cluster.subscription +
|
||||
"/" +
|
||||
cluster.resourceGroup +
|
||||
"/" +
|
||||
cluster.name +
|
||||
"/machines"
|
||||
)
|
||||
return result
|
||||
} catch (e: any) {
|
||||
const err = e.response as AxiosResponse
|
||||
|
@ -64,10 +73,17 @@ export const FetchMachines = async (cluster: ICluster): Promise<AxiosResponse |
|
|||
}
|
||||
}
|
||||
|
||||
export const FetchMachineSets = async (cluster: ICluster): Promise<AxiosResponse | null> => {
|
||||
export const fetchMachineSets = async (cluster: ICluster): Promise<AxiosResponse | null> => {
|
||||
try {
|
||||
const result = await axios(
|
||||
"/api/" + cluster.subscription + "/" + cluster.resourceGroup + "/" + cluster.name + "/machine-sets")
|
||||
"/api/" +
|
||||
cluster.subscription +
|
||||
"/" +
|
||||
cluster.resourceGroup +
|
||||
"/" +
|
||||
cluster.name +
|
||||
"/machine-sets"
|
||||
)
|
||||
return result
|
||||
} catch (e: any) {
|
||||
const err = e.response as AxiosResponse
|
||||
|
@ -75,7 +91,7 @@ export const FetchMachineSets = async (cluster: ICluster): Promise<AxiosResponse
|
|||
}
|
||||
}
|
||||
|
||||
export const FetchRegions = async (): Promise<AxiosResponse | null> => {
|
||||
export const fetchRegions = async (): Promise<AxiosResponse | null> => {
|
||||
try {
|
||||
const result = await axios("/api/regions")
|
||||
return result
|
||||
|
@ -87,7 +103,7 @@ export const FetchRegions = async (): Promise<AxiosResponse | null> => {
|
|||
|
||||
export const ProcessLogOut = async (): Promise<any> => {
|
||||
try {
|
||||
const result = await axios({method: "POST", url: "/api/logout"})
|
||||
const result = await axios({ method: "POST", url: "/api/logout" })
|
||||
return result
|
||||
} catch (e: any) {
|
||||
const err = e.response as AxiosResponse
|
||||
|
@ -114,3 +130,22 @@ export const RequestKubeconfig = async (
|
|||
return OnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchStatistics = async (
|
||||
cluster: ICluster,
|
||||
statisticsName: string,
|
||||
duration: string,
|
||||
endDate: Date
|
||||
): Promise<AxiosResponse | null> => {
|
||||
duration = convertTimeToHours(duration)
|
||||
let endDateJSON = endDate.toJSON()
|
||||
try {
|
||||
const result = await axios(
|
||||
`/api/${cluster.subscription}/${cluster.resourceGroup}/${cluster.name}/statistics/${statisticsName}?duration=${duration}&endtime=${endDateJSON}`
|
||||
)
|
||||
return result
|
||||
} catch (e: any) {
|
||||
const err = e.response as AxiosResponse
|
||||
return OnError(err)
|
||||
}
|
||||
}
|
||||
|
|
131
vendor/github.com/prometheus/client_golang/api/client.go
сгенерированный
поставляемый
Normal file
131
vendor/github.com/prometheus/client_golang/api/client.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,131 @@
|
|||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package api provides clients for the HTTP APIs.
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DefaultRoundTripper is used if no RoundTripper is set in Config.
|
||||
var DefaultRoundTripper http.RoundTripper = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// Config defines configuration parameters for a new client.
|
||||
type Config struct {
|
||||
// The address of the Prometheus to connect to.
|
||||
Address string
|
||||
|
||||
// RoundTripper is used by the Client to drive HTTP requests. If not
|
||||
// provided, DefaultRoundTripper will be used.
|
||||
RoundTripper http.RoundTripper
|
||||
}
|
||||
|
||||
func (cfg *Config) roundTripper() http.RoundTripper {
|
||||
if cfg.RoundTripper == nil {
|
||||
return DefaultRoundTripper
|
||||
}
|
||||
return cfg.RoundTripper
|
||||
}
|
||||
|
||||
// Client is the interface for an API client.
|
||||
type Client interface {
|
||||
URL(ep string, args map[string]string) *url.URL
|
||||
Do(context.Context, *http.Request) (*http.Response, []byte, error)
|
||||
}
|
||||
|
||||
// NewClient returns a new Client.
|
||||
//
|
||||
// It is safe to use the returned Client from multiple goroutines.
|
||||
func NewClient(cfg Config) (Client, error) {
|
||||
u, err := url.Parse(cfg.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Path = strings.TrimRight(u.Path, "/")
|
||||
|
||||
return &httpClient{
|
||||
endpoint: u,
|
||||
client: http.Client{Transport: cfg.roundTripper()},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type httpClient struct {
|
||||
endpoint *url.URL
|
||||
client http.Client
|
||||
}
|
||||
|
||||
func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
|
||||
p := path.Join(c.endpoint.Path, ep)
|
||||
|
||||
for arg, val := range args {
|
||||
arg = ":" + arg
|
||||
p = strings.Replace(p, arg, val, -1)
|
||||
}
|
||||
|
||||
u := *c.endpoint
|
||||
u.Path = p
|
||||
|
||||
return &u
|
||||
}
|
||||
|
||||
func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
if ctx != nil {
|
||||
req = req.WithContext(ctx)
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
defer func() {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var body []byte
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
body = buf.Bytes()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
<-done
|
||||
err = resp.Body.Close()
|
||||
if err == nil {
|
||||
err = ctx.Err()
|
||||
}
|
||||
case <-done:
|
||||
}
|
||||
|
||||
return resp, body, err
|
||||
}
|
1161
vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go
сгенерированный
поставляемый
Normal file
1161
vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go
сгенерированный
поставляемый
Normal file
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1058,6 +1058,8 @@ github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/mo
|
|||
github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1alpha1
|
||||
# github.com/prometheus/client_golang v1.12.1
|
||||
## explicit; go 1.13
|
||||
github.com/prometheus/client_golang/api
|
||||
github.com/prometheus/client_golang/api/prometheus/v1
|
||||
github.com/prometheus/client_golang/prometheus
|
||||
github.com/prometheus/client_golang/prometheus/collectors
|
||||
github.com/prometheus/client_golang/prometheus/internal
|
||||
|
|
Загрузка…
Ссылка в новой задаче