Mizu analyze improvements (#90)

This commit is contained in:
gadotroee
2021-07-01 16:18:02 +03:00
committed by GitHub
parent b762e3c194
commit 3662fbcdf6
15 changed files with 722 additions and 171 deletions

View File

@@ -9,6 +9,7 @@ import (
"mizuserver/pkg/controllers"
"mizuserver/pkg/models"
"mizuserver/pkg/routes"
"mizuserver/pkg/up9"
)
var browserClientSocketUUIDs = make([]string, 0)
@@ -18,6 +19,9 @@ type RoutesEventHandlers struct {
SocketHarOutChannel chan<- *tap.OutputChannelItem
}
func init() {
go up9.UpdateAnalyzeStatus(broadcastToBrowserClients)
}
func (h *RoutesEventHandlers) WebSocketConnect(ep *ikisocket.EventPayload) {
if ep.Kws.GetAttribute("is_tapper") == true {
@@ -84,7 +88,6 @@ func (h *RoutesEventHandlers) WebSocketMessage(ep *ikisocket.EventPayload) {
}
}
func removeSocketUUIDFromBrowserSlice(uuidToRemove string) {
newUUIDSlice := make([]string, 0, len(browserClientSocketUUIDs))
for _, uuid := range browserClientSocketUUIDs {

View File

@@ -1,41 +1,18 @@
package controllers
import (
"bytes"
"compress/zlib"
"encoding/json"
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/google/martian/har"
"io/ioutil"
"log"
"mizuserver/pkg/database"
"mizuserver/pkg/models"
"mizuserver/pkg/up9"
"mizuserver/pkg/utils"
"mizuserver/pkg/validation"
"net/http"
"net/url"
"time"
)
const (
OrderDesc = "desc"
OrderAsc = "asc"
LT = "lt"
GT = "gt"
)
var (
operatorToSymbolMapping = map[string]string{
LT: "<",
GT: ">",
}
operatorToOrderMapping = map[string]string{
LT: OrderDesc,
GT: OrderAsc,
}
)
func GetEntries(c *fiber.Ctx) error {
entriesFilter := &models.EntriesFilter{}
@@ -47,8 +24,8 @@ func GetEntries(c *fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).JSON(err)
}
order := operatorToOrderMapping[entriesFilter.Operator]
operatorSymbol := operatorToSymbolMapping[entriesFilter.Operator]
order := database.OperatorToOrderMapping[entriesFilter.Operator]
operatorSymbol := database.OperatorToSymbolMapping[entriesFilter.Operator]
var entries []models.MizuEntry
database.GetEntriesTable().
Order(fmt.Sprintf("timestamp %s", order)).
@@ -57,7 +34,7 @@ func GetEntries(c *fiber.Ctx) error {
Limit(entriesFilter.Limit).
Find(&entries)
if len(entries) > 0 && order == OrderDesc {
if len(entries) > 0 && order == database.OrderDesc {
// the entries always order from oldest to newest so we should revers
utils.ReverseSlice(entries)
}
@@ -73,7 +50,7 @@ func GetEntries(c *fiber.Ctx) error {
func GetHARs(c *fiber.Ctx) error {
entriesFilter := &models.HarFetchRequestBody{}
order := OrderDesc
order := database.OrderDesc
if err := c.QueryParser(entriesFilter); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(err)
}
@@ -144,67 +121,20 @@ func GetHARs(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).SendStream(buffer)
}
func uploadEntriesImpl(token string, model string, envPrefix string) {
sleepTime := time.Second * 10
var timestampFrom int64 = 0
for {
timestampTo := time.Now().UnixNano() / int64(time.Millisecond)
fmt.Printf("Getting entries from %v, to %v\n", timestampFrom, timestampTo)
entriesArray := getEntriesFromDb(timestampFrom, timestampTo)
if len(entriesArray) > 0 {
fmt.Printf("About to upload %v entries\n", len(entriesArray))
body, jMarshalErr := json.Marshal(entriesArray)
if jMarshalErr != nil {
log.Fatal(jMarshalErr)
}
var in bytes.Buffer
w := zlib.NewWriter(&in)
_, _ = w.Write(body)
_ = w.Close()
reqBody := ioutil.NopCloser(bytes.NewReader(in.Bytes()))
postUrl, _ := url.Parse(fmt.Sprintf("https://traffic.%s/dumpTrafficBulk/%s", envPrefix, model))
fmt.Println(postUrl)
req := &http.Request{
Method: http.MethodPost,
URL: postUrl,
Header: map[string][]string{
"Content-Encoding": {"deflate"},
"Content-Type": {"application/octet-stream"},
"Guest-Auth": {token},
},
Body: reqBody,
}
_, postErr := http.DefaultClient.Do(req)
if postErr != nil {
log.Fatal(postErr)
}
fmt.Printf("Finish uploading %v entries to %s\n", len(entriesArray), postUrl)
} else {
fmt.Println("Nothing to upload")
}
fmt.Printf("Sleeping for %v...\n", sleepTime)
time.Sleep(sleepTime)
timestampFrom = timestampTo
}
}
func UploadEntries(c *fiber.Ctx) error {
entriesFilter := &models.UploadEntriesRequestBody{}
if err := c.QueryParser(entriesFilter); err != nil {
uploadRequestBody := &models.UploadEntriesRequestBody{}
if err := c.QueryParser(uploadRequestBody); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(err)
}
if err := validation.Validate(entriesFilter); err != nil {
if err := validation.Validate(uploadRequestBody); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(err)
}
go uploadEntriesImpl(entriesFilter.Token, entriesFilter.Model, entriesFilter.Dest)
if up9.GetAnalyzeInfo().IsAnalyzing {
return c.Status(fiber.StatusBadRequest).SendString("Cannot analyze, mizu is already analyzing")
}
token, _ := up9.CreateAnonymousToken(uploadRequestBody.Dest)
go up9.UploadEntriesImpl(token.Token, token.Model, uploadRequestBody.Dest)
return c.Status(fiber.StatusOK).SendString("OK")
}
@@ -231,32 +161,10 @@ func GetFullEntries(c *fiber.Ctx) error {
timestampTo = entriesFilter.To
}
entriesArray := getEntriesFromDb(timestampFrom, timestampTo)
entriesArray := database.GetEntriesFromDb(timestampFrom, timestampTo)
return c.Status(fiber.StatusOK).JSON(entriesArray)
}
func getEntriesFromDb(timestampFrom int64, timestampTo int64) []har.Entry {
order := OrderDesc
var entries []models.MizuEntry
database.GetEntriesTable().
Where(fmt.Sprintf("timestamp BETWEEN %v AND %v", timestampFrom, timestampTo)).
Order(fmt.Sprintf("timestamp %s", order)).
Find(&entries)
if len(entries) > 0 {
// the entries always order from oldest to newest so we should revers
utils.ReverseSlice(entries)
}
entriesArray := make([]har.Entry, 0)
for _, entryData := range entries {
var harEntry har.Entry
_ = json.Unmarshal([]byte(entryData.Entry), &harEntry)
entriesArray = append(entriesArray, harEntry)
}
return entriesArray
}
func GetEntry(c *fiber.Ctx) error {
var entryData models.EntryData
database.GetEntriesTable().

View File

@@ -3,6 +3,7 @@ package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/up9inc/mizu/shared"
"mizuserver/pkg/up9"
)
var TapStatus shared.TapStatus
@@ -10,3 +11,7 @@ var TapStatus shared.TapStatus
func GetTappingStatus(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(TapStatus)
}
func AnalyzeInformation(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(up9.GetAnalyzeInfo())
}

View File

@@ -1,9 +1,13 @@
package database
import (
"encoding/json"
"fmt"
"github.com/google/martian/har"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"mizuserver/pkg/models"
"mizuserver/pkg/utils"
)
const (
@@ -14,6 +18,24 @@ var (
DB = initDataBase(DBPath)
)
const (
OrderDesc = "desc"
OrderAsc = "asc"
LT = "lt"
GT = "gt"
)
var (
OperatorToSymbolMapping = map[string]string{
LT: "<",
GT: ">",
}
OperatorToOrderMapping = map[string]string{
LT: OrderDesc,
GT: OrderAsc,
}
)
func GetEntriesTable() *gorm.DB {
return DB.Table("mizu_entries")
}
@@ -23,3 +45,26 @@ func initDataBase(databasePath string) *gorm.DB {
_ = temp.AutoMigrate(&models.MizuEntry{}) // this will ensure table is created
return temp
}
func GetEntriesFromDb(timestampFrom int64, timestampTo int64) []har.Entry {
order := OrderDesc
var entries []models.MizuEntry
GetEntriesTable().
Where(fmt.Sprintf("timestamp BETWEEN %v AND %v", timestampFrom, timestampTo)).
Order(fmt.Sprintf("timestamp %s", order)).
Find(&entries)
if len(entries) > 0 {
// the entries always order from oldest to newest so we should revers
utils.ReverseSlice(entries)
}
entriesArray := make([]har.Entry, 0)
for _, entryData := range entries {
var harEntry har.Entry
_ = json.Unmarshal([]byte(entryData.Entry), &harEntry)
entriesArray = append(entriesArray, harEntry)
}
return entriesArray
}

View File

@@ -50,8 +50,6 @@ type EntriesFilter struct {
}
type UploadEntriesRequestBody struct {
Token string `query:"token"`
Model string `query:"model"`
Dest string `query:"dest"`
}

View File

@@ -20,4 +20,5 @@ func EntriesRoutes(fiberApp *fiber.App) {
routeGroup.Get("/generalStats", controllers.GetGeneralStats) // get general stats about entries in DB
routeGroup.Get("/tapStatus", controllers.GetTappingStatus) // get tapping status
routeGroup.Get("/analyzeStatus", controllers.AnalyzeInformation)
}

178
api/pkg/up9/main.go Normal file
View File

@@ -0,0 +1,178 @@
package up9
import (
"bytes"
"compress/zlib"
"encoding/json"
"fmt"
"github.com/up9inc/mizu/shared"
"io/ioutil"
"log"
"mizuserver/pkg/database"
"net/http"
"net/url"
"time"
)
const (
AnalyzeCheckSleepTime = 5 * time.Second
)
type GuestToken struct {
Token string `json:"token"`
Model string `json:"model"`
}
type ModelStatus struct {
LastMajorGeneration float64 `json:"lastMajorGeneration"`
}
func getGuestToken(url string, target *GuestToken) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(target)
}
func CreateAnonymousToken(envPrefix string) (*GuestToken, error) {
tokenUrl := fmt.Sprintf("https://trcc.%v/anonymous/token", envPrefix)
token := &GuestToken{}
if err := getGuestToken(tokenUrl, token); err != nil {
fmt.Println(err)
return nil, err
}
return token, nil
}
func GetRemoteUrl(analyzeDestination string, analyzeToken string) string {
return fmt.Sprintf("https://%s/share/%s", analyzeDestination, analyzeToken)
}
func CheckIfModelReady(analyzeDestination string, analyzeModel string, analyzeToken string) bool {
statusUrl, _ := url.Parse(fmt.Sprintf("https://trcc.%s/models/%s/status", analyzeDestination, analyzeModel))
req := &http.Request{
Method: http.MethodGet,
URL: statusUrl,
Header: map[string][]string{
"Content-Type": {"application/json"},
"Guest-Auth": {analyzeToken},
},
}
statusResp, err := http.DefaultClient.Do(req)
if err != nil {
return false
}
defer statusResp.Body.Close()
target := &ModelStatus{}
_ = json.NewDecoder(statusResp.Body).Decode(&target)
return target.LastMajorGeneration > 0
}
func GetTrafficDumpUrl(analyzeDestination string, analyzeModel string) *url.URL {
postUrl, _ := url.Parse(fmt.Sprintf("https://traffic.%s/dumpTrafficBulk/%s", analyzeDestination, analyzeModel))
return postUrl
}
type AnalyzeInformation struct {
IsAnalyzing bool
AnalyzedModel string
AnalyzeToken string
AnalyzeDestination string
}
func (info *AnalyzeInformation) Reset() {
info.IsAnalyzing = false
info.AnalyzedModel = ""
info.AnalyzeToken = ""
info.AnalyzeDestination = ""
}
var analyzeInformation = &AnalyzeInformation{}
func GetAnalyzeInfo() *shared.AnalyzeStatus {
return &shared.AnalyzeStatus{
IsAnalyzing: analyzeInformation.IsAnalyzing,
RemoteUrl: GetRemoteUrl(analyzeInformation.AnalyzeDestination, analyzeInformation.AnalyzeToken),
IsRemoteReady: CheckIfModelReady(analyzeInformation.AnalyzeDestination, analyzeInformation.AnalyzedModel, analyzeInformation.AnalyzeToken),
}
}
func UploadEntriesImpl(token string, model string, envPrefix string) {
analyzeInformation.IsAnalyzing = true
analyzeInformation.AnalyzedModel = model
analyzeInformation.AnalyzeToken = token
analyzeInformation.AnalyzeDestination = envPrefix
sleepTime := time.Second * 10
var timestampFrom int64 = 0
for {
timestampTo := time.Now().UnixNano() / int64(time.Millisecond)
fmt.Printf("Getting entries from %v, to %v\n", timestampFrom, timestampTo)
entriesArray := database.GetEntriesFromDb(timestampFrom, timestampTo)
if len(entriesArray) > 0 {
fmt.Printf("About to upload %v entries\n", len(entriesArray))
body, jMarshalErr := json.Marshal(entriesArray)
if jMarshalErr != nil {
analyzeInformation.Reset()
fmt.Println("Stopping analyzing")
log.Fatal(jMarshalErr)
}
var in bytes.Buffer
w := zlib.NewWriter(&in)
_, _ = w.Write(body)
_ = w.Close()
reqBody := ioutil.NopCloser(bytes.NewReader(in.Bytes()))
req := &http.Request{
Method: http.MethodPost,
URL: GetTrafficDumpUrl(envPrefix, model),
Header: map[string][]string{
"Content-Encoding": {"deflate"},
"Content-Type": {"application/octet-stream"},
"Guest-Auth": {token},
},
Body: reqBody,
}
if _, postErr := http.DefaultClient.Do(req); postErr != nil {
analyzeInformation.Reset()
log.Println("Stopping analyzing")
log.Fatal(postErr)
}
fmt.Printf("Finish uploading %v entries to %s\n", len(entriesArray), GetTrafficDumpUrl(envPrefix, model))
} else {
fmt.Println("Nothing to upload")
}
fmt.Printf("Sleeping for %v...\n", sleepTime)
time.Sleep(sleepTime)
timestampFrom = timestampTo
}
}
func UpdateAnalyzeStatus(callback func(data []byte)) {
for {
if !analyzeInformation.IsAnalyzing {
time.Sleep(AnalyzeCheckSleepTime)
continue
}
analyzeStatus := GetAnalyzeInfo()
socketMessage := shared.CreateWebSocketMessageTypeAnalyzeStatus(*analyzeStatus)
jsonMessage, _ := json.Marshal(socketMessage)
callback(jsonMessage)
time.Sleep(AnalyzeCheckSleepTime)
}
}

View File

@@ -63,7 +63,7 @@ func init() {
tapCmd.Flags().Uint16VarP(&mizuTapOptions.GuiPort, "gui-port", "p", 8899, "Provide a custom port for the web interface webserver")
tapCmd.Flags().StringVarP(&mizuTapOptions.Namespace, "namespace", "n", "", "Namespace selector")
tapCmd.Flags().BoolVar(&mizuTapOptions.Analyze, "analyze", false, "Uploads traffic to UP9 cloud for further analysis")
tapCmd.Flags().BoolVar(&mizuTapOptions.Analyze, "analyze", false, "Uploads traffic to UP9 cloud for further analysis (Beta)")
tapCmd.Flags().StringVar(&mizuTapOptions.AnalyzeDestination, "dest", "up9.app", "Destination environment")
tapCmd.Flags().BoolVarP(&mizuTapOptions.AllNamespaces, "all-namespaces", "A", false, "Tap all namespaces")
tapCmd.Flags().StringVarP(&mizuTapOptions.KubeConfigPath, "kube-config", "k", "", "Path to kube-config file")

View File

@@ -2,7 +2,6 @@ package cmd
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
@@ -81,30 +80,6 @@ func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) {
waitForFinish(ctx, cancel)
}
type GuestToken struct {
Token string `json:"token"`
Model string `json:"model"`
}
func getGuestToken(url string, target *GuestToken) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(target)
}
func CreateAnonymousToken(envPrefix string) (*GuestToken, error) {
tokenUrl := fmt.Sprintf("https://trcc.%v/anonymous/token", envPrefix)
token := &GuestToken{}
if err := getGuestToken(tokenUrl, token); err != nil {
fmt.Println(err)
return nil, err
}
return token, nil
}
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, tappingOptions *MizuTapOptions, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error {
if err := createMizuAggregator(ctx, kubernetesProvider, tappingOptions, mizuApiFilteringOptions); err != nil {
@@ -225,10 +200,10 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
for {
select {
case newTarget := <-added:
fmt.Printf("+%s\n", newTarget.Name)
fmt.Printf(mizu.Green, fmt.Sprintf("+%s\n", newTarget.Name))
case removedTarget := <-removed:
fmt.Printf("-%s\n", removedTarget.Name)
fmt.Printf(mizu.Red, fmt.Sprintf("-%s\n", removedTarget.Name))
restartTappersDebouncer.SetOn()
case modifiedTarget := <-modified:
@@ -274,14 +249,13 @@ func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
cancel()
} else {
fmt.Printf("Web interface is now available at http://localhost:%d\n", tappingOptions.GuiPort)
time.Sleep(time.Second * 5) // Waiting to be sure port forwarding finished
if tappingOptions.Analyze {
token, _ := CreateAnonymousToken(tappingOptions.AnalyzeDestination)
if _, err := http.Get(fmt.Sprintf("http://localhost:%d/api/uploadEntries?token=%s&model=%s&dest=%s", tappingOptions.GuiPort, token.Token, token.Model, tappingOptions.AnalyzeDestination)); err != nil {
if _, err := http.Get(fmt.Sprintf("http://localhost:%d/api/uploadEntries?dest=%s", tappingOptions.GuiPort, tappingOptions.AnalyzeDestination)); err != nil {
fmt.Println(err)
} else {
fmt.Println("Staring to upload and analyze the data, it may take a few minutes")
fmt.Println("https://" + tappingOptions.AnalyzeDestination + "/share/" + token.Token)
fmt.Printf(mizu.Yellow, "Traffic is uploading to UP9 cloud for further analsys")
fmt.Println()
}
}
}

View File

@@ -15,3 +15,14 @@ const (
TapperPodName = "mizu-tapper"
K8sAllNamespaces = ""
)
const (
Black = "\033[1;30m%s\033[0m"
Red = "\033[1;31m%s\033[0m"
Green = "\033[1;32m%s\033[0m"
Yellow = "\033[1;33m%s\033[0m"
Purple = "\033[1;34m%s\033[0m"
Magenta = "\033[1;35m%s\033[0m"
Teal = "\033[1;36m%s\033[0m"
White = "\033[1;37m%s\033[0m"
)

View File

@@ -1,16 +1,29 @@
package shared
type WebSocketMessageType string
const (
WebSocketMessageTypeEntry WebSocketMessageType = "entry"
WebSocketMessageTypeTappedEntry WebSocketMessageType = "tappedEntry"
WebSocketMessageTypeUpdateStatus WebSocketMessageType = "status"
WebSocketMessageTypeAnalyzeStatus WebSocketMessageType = "analyzeStatus"
)
type WebSocketMessageMetadata struct {
MessageType WebSocketMessageType `json:"messageType,omitempty"`
}
type WebSocketAnalyzeStatusMessage struct {
*WebSocketMessageMetadata
AnalyzeStatus AnalyzeStatus `json:"analyzeStatus"`
}
type AnalyzeStatus struct {
IsAnalyzing bool `json:"isAnalyzing"`
RemoteUrl string `json:"remoteUrl"`
IsRemoteReady bool `json:"isRemoteReady"`
}
type WebSocketStatusMessage struct {
*WebSocketMessageMetadata
TappingStatus TapStatus `json:"tappingStatus"`
@@ -34,6 +47,15 @@ func CreateWebSocketStatusMessage(tappingStatus TapStatus) WebSocketStatusMessag
}
}
func CreateWebSocketMessageTypeAnalyzeStatus(analyzeStatus AnalyzeStatus) WebSocketAnalyzeStatusMessage {
return WebSocketAnalyzeStatusMessage{
WebSocketMessageMetadata: &WebSocketMessageMetadata{
MessageType: WebSocketMessageTypeAnalyzeStatus,
},
AnalyzeStatus: analyzeStatus,
}
}
type TrafficFilteringOptions struct {
PlainTextMaskingRegexes []*SerializableRegexp
}

View File

@@ -10,6 +10,8 @@
display: flex
align-items: center
padding-left: 24px
padding-right: 24px
justify-content: space-between
.title
font-size: 45px

View File

@@ -1,17 +1,42 @@
import React from 'react';
import {HarPage} from "./components/HarPage";
import React, {useState} from 'react';
import './App.sass';
import logo from './components/assets/Mizu.svg';
import {Button, ThemeProvider} from "@material-ui/core";
import Theme from './style/mui.theme';
import {HarPage} from "./components/HarPage";
const App = () => {
const [analyzeStatus, setAnalyzeStatus] = useState(null);
return (
<ThemeProvider theme={Theme}>
<div className="mizuApp">
<div className="header">
<div style={{display: "flex", alignItems: "center"}}>
<div className="title"><img src={logo} alt="logo"/></div>
<div className="description">Traffic viewer for Kubernetes</div>
</div>
<HarPage/>
<div>
{analyzeStatus?.isAnalyzing &&
<div title={!analyzeStatus?.isRemoteReady ? "Analysis is not ready yet" : "Go To see further analysis"}>
<Button
variant="contained"
color="primary"
disabled={!analyzeStatus?.isRemoteReady}
onClick={() => {
window.open(analyzeStatus?.remoteUrl)
}}>
Analysis Results
</Button>
</div>
}
</div>
</div>
<HarPage setAnalyzeStatus={setAnalyzeStatus}/>
</div>
</ThemeProvider>
);
}

View File

@@ -35,7 +35,11 @@ enum ConnectionStatus {
Paused
}
export const HarPage: React.FC = () => {
interface HarPageProps {
setAnalyzeStatus: (status: any) => void;
}
export const HarPage: React.FC<HarPageProps> = ({setAnalyzeStatus}) => {
const classes = useLayoutStyles();
@@ -83,6 +87,9 @@ export const HarPage: React.FC = () => {
case "status":
setTappingStatus(message.tappingStatus);
break
case "analyzeStatus":
setAnalyzeStatus(message.analyzeStatus);
break
default:
console.error(`unsupported websocket message type, Got: ${message.messageType}`)
}
@@ -94,6 +101,10 @@ export const HarPage: React.FC = () => {
fetch(`http://localhost:8899/api/tapStatus`)
.then(response => response.json())
.then(data => setTappingStatus(data));
fetch(`http://localhost:8899/api/analyzeStatus`)
.then(response => response.json())
.then(data => setAnalyzeStatus(data));
}, []);
@@ -135,7 +146,8 @@ export const HarPage: React.FC = () => {
return (
<div className="HarPage">
<div className="harPageHeader">
<img style={{cursor: "pointer", marginRight: 15, height: 30}} alt="pause" src={connection === ConnectionStatus.Connected ? pauseIcon : playIcon} onClick={toggleConnection}/>
<img style={{cursor: "pointer", marginRight: 15, height: 30}} alt="pause"
src={connection === ConnectionStatus.Connected ? pauseIcon : playIcon} onClick={toggleConnection}/>
<div className="connectionText">
{getConnectionTitle()}
<div className={"indicatorContainer " + getConnectionStatusClass(true)}>
@@ -169,7 +181,8 @@ export const HarPage: React.FC = () => {
</div>
</div>
<div className={classes.details}>
{selectedHarEntry && <HAREntryDetailed harEntry={selectedHarEntry} classes={{root: classes.harViewer}}/>}
{selectedHarEntry &&
<HAREntryDetailed harEntry={selectedHarEntry} classes={{root: classes.harViewer}}/>}
</div>
</div>}
{tappingStatus?.pods != null && <StatusBar tappingStatus={tappingStatus}/>}

366
ui/src/style/mui.theme.ts Normal file
View File

@@ -0,0 +1,366 @@
import createMuiTheme, { Theme, ThemeOptions } from "@material-ui/core/styles/createMuiTheme";
import { Palette } from "@material-ui/core/styles/createPalette";
interface IColor {
main: string;
light: string;
dark: string;
contrastText: string;
}
interface IPalette extends Palette {
primaryBackground: IColor
secondaryBackground: IColor
}
export interface ITheme extends Theme {
palette: IPalette;
}
interface IThemeOptions extends ThemeOptions {
palette: IPalette;
overrides: any;
}
const theme = createMuiTheme({
"palette": {
"common": {
"black": "#000",
"white": "#fff"
},
"type": "dark",
"primary": {
"main": "#627ef7",
"light": "#a0b2ff",
"dark": "#2b3560",
"contrastText": "#090b14"
},
"primaryBackground": {
"main": "#171c30",
"light": "#252c47",
"dark": "#090b14",
"contrastText": "#fff"
},
"secondary": {
"main": "rgba(255, 255, 255, 0.75)",
"light": "#fff",
"dark": "rgba(255, 255, 255, 0.5)",
"contrastText": "#000"
},
"secondaryBackground": {
"main": "rgba(255, 255, 255, 0.12)",
"light": "rgba(255, 255, 255, 0.25)",
"dark": "rgba(255, 255, 255, 0.06)",
"contrastText": "#fff"
},
"error": {
"main": "#cb5411",
"light": "#ff3a30",
"dark": "rgba(255, 58, 48, 0.35)",
"contrastText": "#fff"
},
"warning": {
"light": "#ffb74d",
"main": "#ff9800",
"dark": "#f57c00",
"contrastText": "#344073"
},
"info": {
"light": "#64b5f6",
"main": "#2196f3",
"dark": "#1976d2",
"contrastText": "#fff"
},
"success": {
"light": "#3eb545",
"main": "rgba(62, 181, 69, 0.5)",
"dark": "rgba(62, 181, 69, 0.35)",
"contrastText": "#fff"
},
"grey": {
"50": "#fafafa",
"100": "#f5f5f5",
"200": "#eeeeee",
"300": "#e0e0e0",
"400": "#bdbdbd",
"500": "#9e9e9e",
"600": "#757575",
"700": "#616161",
"800": "#424242",
"900": "#212121",
"A100": "#d5d5d5",
"A200": "#aaaaaa",
"A400": "#303030",
"A700": "#616161"
},
"contrastThreshold": 3,
"tonalOffset": 0.2,
"text": {
"primary": "#fff",
"secondary": "rgba(255, 255, 255, 0.85)",
"disabled": "rgba(255, 255, 255, 0.75)",
"hint": "rgba(255, 255, 255, 0.65)",
},
"divider": "rgba(255, 255, 255, 0.12)",
"background": {
"paper": "#344073",
"default": "#252c47",
},
"action": {
"active": "#fff",
"hover": "rgba(255, 255, 255, 0.08)",
"hoverOpacity": 0.08,
"selected": "rgba(255, 255, 255, 0.16)",
"selectedOpacity": 0.16,
"disabled": "rgba(255, 255, 255, 0.3)",
"disabledBackground": "rgba(255, 255, 255, 0.12)",
"disabledOpacity": 0.38,
"focus": "rgba(255, 255, 255, 0.12)",
"focusOpacity": 0.12,
"activatedOpacity": 0.24
}
},
"props": {},
"shadows": [
"none",
"0px 2px 1px -1px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 1px 3px 0px rgba(0,0,0,0.12)",
"0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)",
"0px 3px 3px -2px rgba(0,0,0,0.2),0px 3px 4px 0px rgba(0,0,0,0.14),0px 1px 8px 0px rgba(0,0,0,0.12)",
"0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)",
"0px 3px 5px -1px rgba(0,0,0,0.2),0px 5px 8px 0px rgba(0,0,0,0.14),0px 1px 14px 0px rgba(0,0,0,0.12)",
"0px 3px 5px -1px rgba(0,0,0,0.2),0px 6px 10px 0px rgba(0,0,0,0.14),0px 1px 18px 0px rgba(0,0,0,0.12)",
"0px 4px 5px -2px rgba(0,0,0,0.2),0px 7px 10px 1px rgba(0,0,0,0.14),0px 2px 16px 1px rgba(0,0,0,0.12)",
"0px 5px 5px -3px rgba(0,0,0,0.2),0px 8px 10px 1px rgba(0,0,0,0.14),0px 3px 14px 2px rgba(0,0,0,0.12)",
"0px 5px 6px -3px rgba(0,0,0,0.2),0px 9px 12px 1px rgba(0,0,0,0.14),0px 3px 16px 2px rgba(0,0,0,0.12)",
"0px 6px 6px -3px rgba(0,0,0,0.2),0px 10px 14px 1px rgba(0,0,0,0.14),0px 4px 18px 3px rgba(0,0,0,0.12)",
"0px 6px 7px -4px rgba(0,0,0,0.2),0px 11px 15px 1px rgba(0,0,0,0.14),0px 4px 20px 3px rgba(0,0,0,0.12)",
"0px 7px 8px -4px rgba(0,0,0,0.2),0px 12px 17px 2px rgba(0,0,0,0.14),0px 5px 22px 4px rgba(0,0,0,0.12)",
"0px 7px 8px -4px rgba(0,0,0,0.2),0px 13px 19px 2px rgba(0,0,0,0.14),0px 5px 24px 4px rgba(0,0,0,0.12)",
"0px 7px 9px -4px rgba(0,0,0,0.2),0px 14px 21px 2px rgba(0,0,0,0.14),0px 5px 26px 4px rgba(0,0,0,0.12)",
"0px 8px 9px -5px rgba(0,0,0,0.2),0px 15px 22px 2px rgba(0,0,0,0.14),0px 6px 28px 5px rgba(0,0,0,0.12)",
"0px 8px 10px -5px rgba(0,0,0,0.2),0px 16px 24px 2px rgba(0,0,0,0.14),0px 6px 30px 5px rgba(0,0,0,0.12)",
"0px 8px 11px -5px rgba(0,0,0,0.2),0px 17px 26px 2px rgba(0,0,0,0.14),0px 6px 32px 5px rgba(0,0,0,0.12)",
"0px 9px 11px -5px rgba(0,0,0,0.2),0px 18px 28px 2px rgba(0,0,0,0.14),0px 7px 34px 6px rgba(0,0,0,0.12)",
"0px 9px 12px -6px rgba(0,0,0,0.2),0px 19px 29px 2px rgba(0,0,0,0.14),0px 7px 36px 6px rgba(0,0,0,0.12)",
"0px 10px 13px -6px rgba(0,0,0,0.2),0px 20px 31px 3px rgba(0,0,0,0.14),0px 8px 38px 7px rgba(0,0,0,0.12)",
"0px 10px 13px -6px rgba(0,0,0,0.2),0px 21px 33px 3px rgba(0,0,0,0.14),0px 8px 40px 7px rgba(0,0,0,0.12)",
"0px 10px 14px -6px rgba(0,0,0,0.2),0px 22px 35px 3px rgba(0,0,0,0.14),0px 8px 42px 7px rgba(0,0,0,0.12)",
"0px 11px 14px -7px rgba(0,0,0,0.2),0px 23px 36px 3px rgba(0,0,0,0.14),0px 9px 44px 8px rgba(0,0,0,0.12)",
"0px 11px 15px -7px rgba(0,0,0,0.2),0px 24px 38px 3px rgba(0,0,0,0.14),0px 9px 46px 8px rgba(0,0,0,0.12)"
],
"typography": {
"htmlFontSize": 16,
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontSize": 14,
"fontWeightLight": 300,
"fontWeightRegular": 400,
"fontWeightMedium": 500,
"fontWeightBold": 600,
"h1": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 300,
"fontSize": "6rem",
"lineHeight": 1.167,
"letterSpacing": "-0.01562em"
},
"h2": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 300,
"fontSize": "3.75rem",
"lineHeight": 1.2,
"letterSpacing": "-0.00833em"
},
"h3": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 400,
"fontSize": "3rem",
"lineHeight": 1.167,
"letterSpacing": "0em"
},
"h4": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 400,
"fontSize": "2.125rem",
"lineHeight": 1.235,
"letterSpacing": "0.00735em"
},
"h5": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 400,
"fontSize": "1.2rem",
"lineHeight": 1.334,
"letterSpacing": "0.02em"
},
"h6": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 400,
"fontSize": "1rem",
"lineHeight": 1.6,
"letterSpacing": "0.0075em",
"textTransform": "uppercase"
},
"subtitle1": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 400,
"fontSize": "1rem",
"lineHeight": 1.75,
"letterSpacing": "0.00938em"
},
"subtitle2": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 500,
"fontSize": "0.875rem",
"lineHeight": 1.57,
"letterSpacing": "0.00714em"
},
"body1": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 400,
"fontSize": "1rem",
"lineHeight": 1.5,
"letterSpacing": "0.00938em"
},
"body2": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 400,
"fontSize": "0.875rem",
"lineHeight": 1.43,
"letterSpacing": "0.01071em"
},
"button": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 500,
"fontSize": "0.875rem",
"lineHeight": 1.75,
"letterSpacing": "0.02857em",
"textTransform": "uppercase"
},
"caption": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 400,
"fontSize": "0.8rem",
"lineHeight": 1.66,
"letterSpacing": "0.03333em"
},
"overline": {
"fontFamily": "'Source Sans Pro', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif",
"fontWeight": 400,
"fontSize": "0.75rem",
"lineHeight": 2.66,
"letterSpacing": "0.08333em",
"textTransform": "uppercase"
}
},
"shape": {
"borderRadius": 16
},
"transitions": {
"easing": {
"easeInOut": "cubic-bezier(0.4, 0, 0.2, 1)",
"easeOut": "cubic-bezier(0.0, 0, 0.2, 1)",
"easeIn": "cubic-bezier(0.4, 0, 1, 1)",
"sharp": "cubic-bezier(0.4, 0, 0.6, 1)"
},
"duration": {
"shortest": 150,
"shorter": 200,
"short": 250,
"standard": 300,
"complex": 375,
"enteringScreen": 225,
"leavingScreen": 195
}
},
"zIndex": {
"mobileStepper": 1000,
"speedDial": 1050,
"appBar": 1100,
"drawer": 1200,
"modal": 1300,
"snackbar": 1400,
"tooltip": 1500
}
} as IThemeOptions) as ITheme;
theme.overrides = {
...theme.overrides,
MuiTooltip: {
tooltip: {
backgroundColor: "#2a335a",
fontSize: "14px",
borderRadius: "8px",
}
},
"MuiButton": {
label: {
fontWeight: "bold",
},
"contained": {
color: '#F7F9FC',
padding: "7px 18px",
},
"containedPrimary": {
color: '#F7F9FC',
"&:disabled": {
backgroundColor: "rgba(0,0,0,0.15)",
color: "rgba(255, 255, 255, 0.8)"
}
},
"outlinedPrimary": {
padding: "8px 18px",
"&:hover": {
backgroundColor: "rgba(255, 255, 255, 0.06)"
},
"&:disabled": {
backgroundColor: "rgba(255, 255, 255, 0.06)",
color: "rgba(255, 255, 255, 0.25)"
},
}
},
"MuiSwitch": {
root: {
padding: 11,
width: 44,
height: 29,
},
switchBase: {
'&$checked': {
transform: "translateX(10px)",
"& $thumb": {
backgroundColor: theme.palette.common.white,
},
"&.MuiSwitch-colorPrimary + $track": {
backgroundColor: theme.palette.secondaryBackground.dark,
border: "3px solid " + theme.palette.primary.light,
opacity: 1,
},
}
},
thumb: {
width: 8,
height: 8,
border: "3px solid " + theme.palette.primary.main,
backgroundColor: theme.palette.primary.dark,
boxShadow: 'none',
},
track: {
width: 14,
height: 4,
backgroundColor: theme.palette.secondaryBackground.dark,
border: "3px solid " + theme.palette.primary.light,
opacity: 1,
}
}
};
export default theme;