mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-09-09 22:41:05 +00:00
Mizu analyze improvements (#90)
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
|||||||
"mizuserver/pkg/controllers"
|
"mizuserver/pkg/controllers"
|
||||||
"mizuserver/pkg/models"
|
"mizuserver/pkg/models"
|
||||||
"mizuserver/pkg/routes"
|
"mizuserver/pkg/routes"
|
||||||
|
"mizuserver/pkg/up9"
|
||||||
)
|
)
|
||||||
|
|
||||||
var browserClientSocketUUIDs = make([]string, 0)
|
var browserClientSocketUUIDs = make([]string, 0)
|
||||||
@@ -18,6 +19,9 @@ type RoutesEventHandlers struct {
|
|||||||
SocketHarOutChannel chan<- *tap.OutputChannelItem
|
SocketHarOutChannel chan<- *tap.OutputChannelItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
go up9.UpdateAnalyzeStatus(broadcastToBrowserClients)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *RoutesEventHandlers) WebSocketConnect(ep *ikisocket.EventPayload) {
|
func (h *RoutesEventHandlers) WebSocketConnect(ep *ikisocket.EventPayload) {
|
||||||
if ep.Kws.GetAttribute("is_tapper") == true {
|
if ep.Kws.GetAttribute("is_tapper") == true {
|
||||||
@@ -84,7 +88,6 @@ func (h *RoutesEventHandlers) WebSocketMessage(ep *ikisocket.EventPayload) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func removeSocketUUIDFromBrowserSlice(uuidToRemove string) {
|
func removeSocketUUIDFromBrowserSlice(uuidToRemove string) {
|
||||||
newUUIDSlice := make([]string, 0, len(browserClientSocketUUIDs))
|
newUUIDSlice := make([]string, 0, len(browserClientSocketUUIDs))
|
||||||
for _, uuid := range browserClientSocketUUIDs {
|
for _, uuid := range browserClientSocketUUIDs {
|
||||||
|
@@ -1,41 +1,18 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"compress/zlib"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/google/martian/har"
|
"github.com/google/martian/har"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"mizuserver/pkg/database"
|
"mizuserver/pkg/database"
|
||||||
"mizuserver/pkg/models"
|
"mizuserver/pkg/models"
|
||||||
|
"mizuserver/pkg/up9"
|
||||||
"mizuserver/pkg/utils"
|
"mizuserver/pkg/utils"
|
||||||
"mizuserver/pkg/validation"
|
"mizuserver/pkg/validation"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
"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 {
|
func GetEntries(c *fiber.Ctx) error {
|
||||||
entriesFilter := &models.EntriesFilter{}
|
entriesFilter := &models.EntriesFilter{}
|
||||||
|
|
||||||
@@ -47,8 +24,8 @@ func GetEntries(c *fiber.Ctx) error {
|
|||||||
return c.Status(fiber.StatusBadRequest).JSON(err)
|
return c.Status(fiber.StatusBadRequest).JSON(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
order := operatorToOrderMapping[entriesFilter.Operator]
|
order := database.OperatorToOrderMapping[entriesFilter.Operator]
|
||||||
operatorSymbol := operatorToSymbolMapping[entriesFilter.Operator]
|
operatorSymbol := database.OperatorToSymbolMapping[entriesFilter.Operator]
|
||||||
var entries []models.MizuEntry
|
var entries []models.MizuEntry
|
||||||
database.GetEntriesTable().
|
database.GetEntriesTable().
|
||||||
Order(fmt.Sprintf("timestamp %s", order)).
|
Order(fmt.Sprintf("timestamp %s", order)).
|
||||||
@@ -57,7 +34,7 @@ func GetEntries(c *fiber.Ctx) error {
|
|||||||
Limit(entriesFilter.Limit).
|
Limit(entriesFilter.Limit).
|
||||||
Find(&entries)
|
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
|
// the entries always order from oldest to newest so we should revers
|
||||||
utils.ReverseSlice(entries)
|
utils.ReverseSlice(entries)
|
||||||
}
|
}
|
||||||
@@ -73,7 +50,7 @@ func GetEntries(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
func GetHARs(c *fiber.Ctx) error {
|
func GetHARs(c *fiber.Ctx) error {
|
||||||
entriesFilter := &models.HarFetchRequestBody{}
|
entriesFilter := &models.HarFetchRequestBody{}
|
||||||
order := OrderDesc
|
order := database.OrderDesc
|
||||||
if err := c.QueryParser(entriesFilter); err != nil {
|
if err := c.QueryParser(entriesFilter); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(err)
|
return c.Status(fiber.StatusBadRequest).JSON(err)
|
||||||
}
|
}
|
||||||
@@ -144,67 +121,20 @@ func GetHARs(c *fiber.Ctx) error {
|
|||||||
return c.Status(fiber.StatusOK).SendStream(buffer)
|
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 {
|
func UploadEntries(c *fiber.Ctx) error {
|
||||||
entriesFilter := &models.UploadEntriesRequestBody{}
|
uploadRequestBody := &models.UploadEntriesRequestBody{}
|
||||||
if err := c.QueryParser(entriesFilter); err != nil {
|
if err := c.QueryParser(uploadRequestBody); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(err)
|
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)
|
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")
|
return c.Status(fiber.StatusOK).SendString("OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,32 +161,10 @@ func GetFullEntries(c *fiber.Ctx) error {
|
|||||||
timestampTo = entriesFilter.To
|
timestampTo = entriesFilter.To
|
||||||
}
|
}
|
||||||
|
|
||||||
entriesArray := getEntriesFromDb(timestampFrom, timestampTo)
|
entriesArray := database.GetEntriesFromDb(timestampFrom, timestampTo)
|
||||||
return c.Status(fiber.StatusOK).JSON(entriesArray)
|
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 {
|
func GetEntry(c *fiber.Ctx) error {
|
||||||
var entryData models.EntryData
|
var entryData models.EntryData
|
||||||
database.GetEntriesTable().
|
database.GetEntriesTable().
|
||||||
|
@@ -3,6 +3,7 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/up9inc/mizu/shared"
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"mizuserver/pkg/up9"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TapStatus shared.TapStatus
|
var TapStatus shared.TapStatus
|
||||||
@@ -10,3 +11,7 @@ var TapStatus shared.TapStatus
|
|||||||
func GetTappingStatus(c *fiber.Ctx) error {
|
func GetTappingStatus(c *fiber.Ctx) error {
|
||||||
return c.Status(fiber.StatusOK).JSON(TapStatus)
|
return c.Status(fiber.StatusOK).JSON(TapStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AnalyzeInformation(c *fiber.Ctx) error {
|
||||||
|
return c.Status(fiber.StatusOK).JSON(up9.GetAnalyzeInfo())
|
||||||
|
}
|
||||||
|
@@ -1,9 +1,13 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/martian/har"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"mizuserver/pkg/models"
|
"mizuserver/pkg/models"
|
||||||
|
"mizuserver/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -14,6 +18,24 @@ var (
|
|||||||
DB = initDataBase(DBPath)
|
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 {
|
func GetEntriesTable() *gorm.DB {
|
||||||
return DB.Table("mizu_entries")
|
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
|
_ = temp.AutoMigrate(&models.MizuEntry{}) // this will ensure table is created
|
||||||
return temp
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -50,8 +50,6 @@ type EntriesFilter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UploadEntriesRequestBody struct {
|
type UploadEntriesRequestBody struct {
|
||||||
Token string `query:"token"`
|
|
||||||
Model string `query:"model"`
|
|
||||||
Dest string `query:"dest"`
|
Dest string `query:"dest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,4 +20,5 @@ func EntriesRoutes(fiberApp *fiber.App) {
|
|||||||
routeGroup.Get("/generalStats", controllers.GetGeneralStats) // get general stats about entries in DB
|
routeGroup.Get("/generalStats", controllers.GetGeneralStats) // get general stats about entries in DB
|
||||||
|
|
||||||
routeGroup.Get("/tapStatus", controllers.GetTappingStatus) // get tapping status
|
routeGroup.Get("/tapStatus", controllers.GetTappingStatus) // get tapping status
|
||||||
|
routeGroup.Get("/analyzeStatus", controllers.AnalyzeInformation)
|
||||||
}
|
}
|
||||||
|
178
api/pkg/up9/main.go
Normal file
178
api/pkg/up9/main.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@@ -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().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().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().StringVar(&mizuTapOptions.AnalyzeDestination, "dest", "up9.app", "Destination environment")
|
||||||
tapCmd.Flags().BoolVarP(&mizuTapOptions.AllNamespaces, "all-namespaces", "A", false, "Tap all namespaces")
|
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")
|
tapCmd.Flags().StringVarP(&mizuTapOptions.KubeConfigPath, "kube-config", "k", "", "Path to kube-config file")
|
||||||
|
@@ -2,7 +2,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -81,30 +80,6 @@ func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) {
|
|||||||
waitForFinish(ctx, cancel)
|
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 {
|
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 {
|
if err := createMizuAggregator(ctx, kubernetesProvider, tappingOptions, mizuApiFilteringOptions); err != nil {
|
||||||
@@ -225,10 +200,10 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case newTarget := <-added:
|
case newTarget := <-added:
|
||||||
fmt.Printf("+%s\n", newTarget.Name)
|
fmt.Printf(mizu.Green, fmt.Sprintf("+%s\n", newTarget.Name))
|
||||||
|
|
||||||
case removedTarget := <-removed:
|
case removedTarget := <-removed:
|
||||||
fmt.Printf("-%s\n", removedTarget.Name)
|
fmt.Printf(mizu.Red, fmt.Sprintf("-%s\n", removedTarget.Name))
|
||||||
restartTappersDebouncer.SetOn()
|
restartTappersDebouncer.SetOn()
|
||||||
|
|
||||||
case modifiedTarget := <-modified:
|
case modifiedTarget := <-modified:
|
||||||
@@ -274,14 +249,13 @@ func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
|
|||||||
cancel()
|
cancel()
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Web interface is now available at http://localhost:%d\n", tappingOptions.GuiPort)
|
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 {
|
if tappingOptions.Analyze {
|
||||||
token, _ := CreateAnonymousToken(tappingOptions.AnalyzeDestination)
|
if _, err := http.Get(fmt.Sprintf("http://localhost:%d/api/uploadEntries?dest=%s", tappingOptions.GuiPort, tappingOptions.AnalyzeDestination)); err != nil {
|
||||||
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 {
|
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Staring to upload and analyze the data, it may take a few minutes")
|
fmt.Printf(mizu.Yellow, "Traffic is uploading to UP9 cloud for further analsys")
|
||||||
fmt.Println("https://" + tappingOptions.AnalyzeDestination + "/share/" + token.Token)
|
fmt.Println()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,3 +15,14 @@ const (
|
|||||||
TapperPodName = "mizu-tapper"
|
TapperPodName = "mizu-tapper"
|
||||||
K8sAllNamespaces = ""
|
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"
|
||||||
|
)
|
||||||
|
@@ -1,28 +1,41 @@
|
|||||||
package shared
|
package shared
|
||||||
|
|
||||||
type WebSocketMessageType string
|
type WebSocketMessageType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
WebSocketMessageTypeEntry WebSocketMessageType = "entry"
|
WebSocketMessageTypeEntry WebSocketMessageType = "entry"
|
||||||
WebSocketMessageTypeTappedEntry WebSocketMessageType = "tappedEntry"
|
WebSocketMessageTypeTappedEntry WebSocketMessageType = "tappedEntry"
|
||||||
WebSocketMessageTypeUpdateStatus WebSocketMessageType = "status"
|
WebSocketMessageTypeUpdateStatus WebSocketMessageType = "status"
|
||||||
|
WebSocketMessageTypeAnalyzeStatus WebSocketMessageType = "analyzeStatus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebSocketMessageMetadata struct {
|
type WebSocketMessageMetadata struct {
|
||||||
MessageType WebSocketMessageType `json:"messageType,omitempty"`
|
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 {
|
type WebSocketStatusMessage struct {
|
||||||
*WebSocketMessageMetadata
|
*WebSocketMessageMetadata
|
||||||
TappingStatus TapStatus `json:"tappingStatus"`
|
TappingStatus TapStatus `json:"tappingStatus"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TapStatus struct {
|
type TapStatus struct {
|
||||||
Pods []PodInfo `json:"pods"`
|
Pods []PodInfo `json:"pods"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PodInfo struct {
|
type PodInfo struct {
|
||||||
Namespace string `json:"namespace"`
|
Namespace string `json:"namespace"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateWebSocketStatusMessage(tappingStatus TapStatus) WebSocketStatusMessage {
|
func CreateWebSocketStatusMessage(tappingStatus TapStatus) WebSocketStatusMessage {
|
||||||
@@ -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 {
|
type TrafficFilteringOptions struct {
|
||||||
PlainTextMaskingRegexes []*SerializableRegexp
|
PlainTextMaskingRegexes []*SerializableRegexp
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,8 @@
|
|||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
padding-left: 24px
|
padding-left: 24px
|
||||||
|
padding-right: 24px
|
||||||
|
justify-content: space-between
|
||||||
|
|
||||||
.title
|
.title
|
||||||
font-size: 45px
|
font-size: 45px
|
||||||
|
@@ -1,18 +1,43 @@
|
|||||||
import React from 'react';
|
import React, {useState} from 'react';
|
||||||
import {HarPage} from "./components/HarPage";
|
|
||||||
import './App.sass';
|
import './App.sass';
|
||||||
import logo from './components/assets/Mizu.svg';
|
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 App = () => {
|
||||||
return (
|
|
||||||
<div className="mizuApp">
|
const [analyzeStatus, setAnalyzeStatus] = useState(null);
|
||||||
<div className="header">
|
|
||||||
<div className="title"><img src={logo} alt="logo"/></div>
|
return (
|
||||||
<div className="description">Traffic viewer for Kubernetes</div>
|
<ThemeProvider theme={Theme}>
|
||||||
</div>
|
<div className="mizuApp">
|
||||||
<HarPage/>
|
<div className="header">
|
||||||
</div>
|
<div style={{display: "flex", alignItems: "center"}}>
|
||||||
);
|
<div className="title"><img src={logo} alt="logo"/></div>
|
||||||
|
<div className="description">Traffic viewer for Kubernetes</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@@ -35,7 +35,11 @@ enum ConnectionStatus {
|
|||||||
Paused
|
Paused
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HarPage: React.FC = () => {
|
interface HarPageProps {
|
||||||
|
setAnalyzeStatus: (status: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HarPage: React.FC<HarPageProps> = ({setAnalyzeStatus}) => {
|
||||||
|
|
||||||
const classes = useLayoutStyles();
|
const classes = useLayoutStyles();
|
||||||
|
|
||||||
@@ -60,21 +64,21 @@ export const HarPage: React.FC = () => {
|
|||||||
ws.current.onclose = () => setConnection(ConnectionStatus.Closed);
|
ws.current.onclose = () => setConnection(ConnectionStatus.Closed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ws.current) {
|
if (ws.current) {
|
||||||
ws.current.onmessage = e => {
|
ws.current.onmessage = e => {
|
||||||
if(!e?.data) return;
|
if (!e?.data) return;
|
||||||
const message = JSON.parse(e.data);
|
const message = JSON.parse(e.data);
|
||||||
|
|
||||||
switch (message.messageType) {
|
switch (message.messageType) {
|
||||||
case "entry":
|
case "entry":
|
||||||
const entry = message.data
|
const entry = message.data
|
||||||
if(connection === ConnectionStatus.Paused) {
|
if (connection === ConnectionStatus.Paused) {
|
||||||
setNoMoreDataBottom(false)
|
setNoMoreDataBottom(false)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!focusedEntryId) setFocusedEntryId(entry.id)
|
if (!focusedEntryId) setFocusedEntryId(entry.id)
|
||||||
let newEntries = [...entries];
|
let newEntries = [...entries];
|
||||||
if(entries.length === 1000) {
|
if (entries.length === 1000) {
|
||||||
newEntries = newEntries.splice(1);
|
newEntries = newEntries.splice(1);
|
||||||
setNoMoreDataTop(false);
|
setNoMoreDataTop(false);
|
||||||
}
|
}
|
||||||
@@ -83,6 +87,9 @@ export const HarPage: React.FC = () => {
|
|||||||
case "status":
|
case "status":
|
||||||
setTappingStatus(message.tappingStatus);
|
setTappingStatus(message.tappingStatus);
|
||||||
break
|
break
|
||||||
|
case "analyzeStatus":
|
||||||
|
setAnalyzeStatus(message.analyzeStatus);
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
console.error(`unsupported websocket message type, Got: ${message.messageType}`)
|
console.error(`unsupported websocket message type, Got: ${message.messageType}`)
|
||||||
}
|
}
|
||||||
@@ -94,19 +101,23 @@ export const HarPage: React.FC = () => {
|
|||||||
fetch(`http://localhost:8899/api/tapStatus`)
|
fetch(`http://localhost:8899/api/tapStatus`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => setTappingStatus(data));
|
.then(data => setTappingStatus(data));
|
||||||
|
|
||||||
|
fetch(`http://localhost:8899/api/analyzeStatus`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => setAnalyzeStatus(data));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(!focusedEntryId) return;
|
if (!focusedEntryId) return;
|
||||||
setSelectedHarEntry(null)
|
setSelectedHarEntry(null)
|
||||||
fetch(`http://localhost:8899/api/entries/${focusedEntryId}`)
|
fetch(`http://localhost:8899/api/entries/${focusedEntryId}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => setSelectedHarEntry(data));
|
.then(data => setSelectedHarEntry(data));
|
||||||
},[focusedEntryId])
|
}, [focusedEntryId])
|
||||||
|
|
||||||
const toggleConnection = () => {
|
const toggleConnection = () => {
|
||||||
setConnection(connection === ConnectionStatus.Connected ? ConnectionStatus.Paused : ConnectionStatus.Connected );
|
setConnection(connection === ConnectionStatus.Connected ? ConnectionStatus.Paused : ConnectionStatus.Connected);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getConnectionStatusClass = (isContainer) => {
|
const getConnectionStatusClass = (isContainer) => {
|
||||||
@@ -135,11 +146,12 @@ export const HarPage: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="HarPage">
|
<div className="HarPage">
|
||||||
<div className="harPageHeader">
|
<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">
|
<div className="connectionText">
|
||||||
{getConnectionTitle()}
|
{getConnectionTitle()}
|
||||||
<div className={"indicatorContainer " + getConnectionStatusClass(true)}>
|
<div className={"indicatorContainer " + getConnectionStatusClass(true)}>
|
||||||
<div className={"indicator " + getConnectionStatusClass(false)} />
|
<div className={"indicator " + getConnectionStatusClass(false)}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,7 +181,8 @@ export const HarPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.details}>
|
<div className={classes.details}>
|
||||||
{selectedHarEntry && <HAREntryDetailed harEntry={selectedHarEntry} classes={{root: classes.harViewer}}/>}
|
{selectedHarEntry &&
|
||||||
|
<HAREntryDetailed harEntry={selectedHarEntry} classes={{root: classes.harViewer}}/>}
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
{tappingStatus?.pods != null && <StatusBar tappingStatus={tappingStatus}/>}
|
{tappingStatus?.pods != null && <StatusBar tappingStatus={tappingStatus}/>}
|
||||||
|
366
ui/src/style/mui.theme.ts
Normal file
366
ui/src/style/mui.theme.ts
Normal 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;
|
||||||
|
|
Reference in New Issue
Block a user