diff --git a/README.md b/README.md index f2c9eadd..e9735b24 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,10 @@ Supports Linux, macOS and Windows - notes below - Add users (for admins) - Devices control (most of interaction is wrapped around Appium APIs) - Live video - - Basic remote control - tap, swipe, touch&hold - - Basic functionalities - Home, Lock, Unlock, Type text - - Take high quality screenshots + - **NB** Videos are essentially MJPEG streams so they are very bandwidth hungry + - Basic remote control - tap, swipe, touch&hold, home, lock, unlock, type text to active element, get clipboard - Install/Uninstall apps + - Take high quality screenshots - Reservation - loading a device sets it `In use` and can't be used by another person until it is released - Backend - Serving the web interface @@ -45,15 +45,16 @@ Supports Linux, macOS and Windows - notes below ### Provider features - Straightforward dependencies setup -- Automatic provisioning when devices are connected +- Devices administration via the hub UI +- Automatic provisioning when registered devices are connected - Dependencies automatically installed on devices - - Appium server set up and started for each device - - Optionally Selenium Grid 4 node can be registered for each device Appium server + - Appium server set up and started for each device - Remote control APIs for the hub - - iOS video stream using [WebDriverAgent](https://github.com/appium/WebDriverAgent) - - Android video stream using [GADS-Android-stream](https://github.com/shamanec/GADS-Android-stream) - - Limited interaction wrapped around Appium - tap, swipe, touch&hold, type text, lock and unlock device + - iOS MJPEG video stream using [WebDriverAgent](https://github.com/appium/WebDriverAgent) + - Android MJPEG video stream using [GADS-Android-stream](https://github.com/shamanec/GADS-Android-stream) + - Interaction wrapped around Appium - tap, swipe, touch&hold, type text, lock and unlock device, get clipboard - Appium test execution - each device has its Appium server proxied on a provider endpoint for easier access +- Optionally Selenium Grid 4 nodes can be registered for each device Appium server - macOS - Supports both Android and iOS - Linux @@ -101,8 +102,21 @@ or |[go-ios](https://github.com/danielpaulus/go-ios)| Many thanks for creating this CLI tool to communicate with iOS devices, perfect for installing/reinstalling and running WebDriverAgentRunner without Xcode | |[Appium](https://github.com/appium)| It would be impossible to control the devices remotely without Appium for the control and WebDriverAgent for the iOS screen stream, kudos! | -### Demo video -https://github.com/shamanec/GADS/assets/60219580/271c5025-7f2c-4d7c-a276-eb0e9cceb787 +### Videos +#### Start hub +https://github.com/user-attachments/assets/7a6dab5a-52d1-4c48-882d-48b67e180c89 + +#### Add provider configuration +https://github.com/user-attachments/assets/07c94ecf-217e-4185-9465-8b8054ddef7e + +#### Add devices and start provider +https://github.com/user-attachments/assets/a1b323da-0169-463e-9a37-b0364fc52480 + +#### Run Appium tests in parallel with TestNG +https://github.com/user-attachments/assets/cb2da413-6a72-4ead-9433-c4d2b41d5f4b + +#### Remote control +https://github.com/user-attachments/assets/2d6b29fc-3e83-46be-88c4-d7a563205975 diff --git a/common/constants/constants.go b/common/constants/constants.go index 35a4c9c0..1769a288 100644 --- a/common/constants/constants.go +++ b/common/constants/constants.go @@ -1,7 +1,5 @@ package constants -import "GADS/common/models" - type IndexSort int const ( @@ -23,654 +21,3 @@ var AndroidVersionToSDK = map[string]string{ "33": "13", "34": "14", } - -var IOSDeviceInfoMap = map[string]models.IOSModelData{ - "iPhone12,8": { - Width: "375", - Height: "667", - Model: "iPhone SE (2nd gen)", - }, - // iPhone 5 - "iPhone5,1": { - Width: "320", - Height: "568", - Model: "iPhone 5", - }, - // iPhone 5 - "iPhone5,2": { - Width: "320", - Height: "568", - Model: "iPhone 5", - }, - // iPhone 5c - "iPhone5,3": { - Width: "320", - Height: "568", - Model: "iPhone 5c", - }, - // iPhone 5c - "iPhone5,4": { - Width: "320", - Height: "568", - Model: "iPhone 5c", - }, - // iPhone 5s - "iPhone6,1": { - Width: "320", - Height: "568", - Model: "iPhone 5S", - }, - // iPhone 5s - "iPhone6,2": { - Width: "320", - Height: "568", - Model: "iPhone 5S", - }, - // iPhone 6 - "iPhone7,2": { - Width: "375", - Height: "667", - Model: "iPhone 6", - }, - // iPhone 6 Plus - "iPhone7,1": { - Width: "414", - Height: "736", - Model: "iPhone 6 Plus", - }, - // iPhone 6s - "iPhone8,1": { - Width: "375", - Height: "667", - Model: "iPhone 6S", - }, - // iPhone 6s Plus - "iPhone8,2": { - Width: "414", - Height: "736", - Model: "iPhone 6S Plus", - }, - // iPhone SE (1st gen) - "iPhone8,4": { - Width: "320", - Height: "568", - Model: "iPhone SE (1st gen)", - }, - // iPhone 7 - "iPhone9,1": { - Width: "375", - Height: "667", - Model: "iPhone 7", - }, - // iPhone 7 - "iPhone9,3": { - Width: "375", - Height: "667", - Model: "iPhone 7", - }, - // iPhone 7 Plus - "iPhone9,2": { - Width: "414", - Height: "736", - Model: "iPhone 7 Plus", - }, - // iPhone 7 Plus - "iPhone9,4": { - Width: "414", - Height: "736", - Model: "iPhone 7 Plus", - }, - // iPhone 8 - "iPhone10,1": { - Width: "375", - Height: "667", - Model: "iPhone 8", - }, - // iPhone 8 - "iPhone10,4": { - Width: "375", - Height: "667", - Model: "iPhone 8", - }, - // iPhone 8 plus - "iPhone10,2": { - Width: "414", - Height: "736", - Model: "iPhone 8 Plus", - }, - // iPhone 8 plus - "iPhone10,5": { - Width: "414", - Height: "736", - Model: "iPhone 8 Plus", - }, - // iPhone X - "iPhone10,3": { - Width: "375", - Height: "812", - Model: "iPhone X", - }, - // iPhone X - "iPhone10,6": { - Width: "375", - Height: "812", - Model: "iPhone X", - }, - // iPhone XR - "iPhone11,8": { - Width: "414", - Height: "896", - Model: "iPhone XR", - }, - // iPhone XS - "iPhone11,2": { - Width: "375", - Height: "812", - Model: "iPhone XS", - }, - // iPhone XS Max - "iPhone11,4": { - Width: "414", - Height: "896", - Model: "iPhone XS Max", - }, - // iPhone XS Max - "iPhone11,6": { - Width: "414", - Height: "896", - Model: "iPhone XS Max", - }, - // iPhone 11 - "iPhone12,1": { - Width: "414", - Height: "896", - Model: "iPhone 11", - }, - // iPhone 11 Pro - "iPhone12,3": { - Width: "375", - Height: "812", - Model: "iPhone 11 Pro", - }, - // iPhone 11 Pro Max - "iPhone12,5": { - Width: "414", - Height: "896", - Model: "iPhone 11 Pro Max", - }, - // iPhone 12 Mini - "iPhone13,1": { - Width: "360", - Height: "780", - Model: "iPhone 12 Mini", - }, - // iPhone 12 - "iPhone13,2": { - Width: "390", - Height: "844", - Model: "iPhone 12", - }, - // iPhone 12 Pro - "iPhone13,3": { - Width: "390", - Height: "844", - Model: "iPhone 12 Pro", - }, - // iPhone 12 Pro Max - "iPhone13,4": { - Width: "428", - Height: "926", - Model: "iPhone 12 Pro Max", - }, - // iPhone 13 Mini - "iPhone14,4": { - Width: "360", - Height: "780", - Model: "iPhone 13 Mini", - }, - // iPhone 13 - "iPhone14,5": { - Width: "390", - Height: "844", - Model: "iPhone 13", - }, - // iPhone 13 Pro - "iPhone14,2": { - Width: "390", - Height: "844", - Model: "iPhone 13 Pro", - }, - // iPhone 13 Pro Max - "iPhone14,3": { - Width: "428", - Height: "926", - Model: "iPhone 13 Pro Max", - }, - // iPhone SE (3rd gen) - "iPhone14,6": { - Width: "375", - Height: "667", - Model: "iPhone SE (3rd gen)", - }, - // iPhone 14 - "iPhone14,7": { - Width: "390", - Height: "844", - Model: "iPhone 14", - }, - // iPhone 14 Plus - "iPhone14,8": { - Width: "428", - Height: "926", - Model: "iPhone 14 Plus", - }, - // iPhone 14 Pro - "iPhone15,2": { - Width: "393", - Height: "852", - Model: "iPhone 14 Pro", - }, - // iPhone 14 Pro Max - "iPhone15,3": { - Width: "430", - Height: "932", - Model: "iPhone 14 Pro Max", - }, - // iPhone 15 - "iPhone15,4": { - Width: "393", - Height: "852", - Model: "iPhone 15", - }, - // iPhone 15 Plus - "iPhone15,5": { - Width: "430", - Height: "932", - Model: "iPhone 15 Plus", - }, - // iPhone 15 Pro - "iPhone16,1": { - Width: "393", - Height: "852", - Model: "iPhone 15 Pro", - }, - // iPhone 15 Pro Max - "iPhone16,2": { - Width: "430", - Height: "932", - Model: "iPhone 15 Pro Max", - }, - // iPads - // iPad Air - "iPad4,1": { - Width: "768", - Height: "1024", - Model: "iPad Air", - }, - // iPad Air - "iPad4,2": { - Width: "768", - Height: "1024", - Model: "iPad Air", - }, - // iPad Air - "iPad4,3": { - Width: "768", - Height: "1024", - Model: "iPad Air", - }, - // iPad Mini 3 - "iPad4,7": { - Width: "768", - Height: "1024", - Model: "iPad Mini 3", - }, - // iPad Mini 3 - "iPad4,8": { - Width: "768", - Height: "1024", - Model: "iPad Mini 3", - }, - // iPad Mini 3 - "iPad4,9": { - Width: "768", - Height: "1024", - Model: "iPad Mini 3", - }, - // iPad Air 2 - "iPad5,3": { - Width: "768", - Height: "1024", - Model: "iPad Air 2", - }, - // iPad Air 2 - "iPad5,4": { - Width: "768", - Height: "1024", - Model: "iPad Air 2", - }, - // iPad Mini 4 - "iPad5,1": { - Width: "768", - Height: "1024", - Model: "iPad Mini 4", - }, - // iPad Mini 4 - "iPad5,2": { - Width: "768", - Height: "1024", - Model: "iPad Mini 4", - }, - // iPad Pro (9.7) - "iPad6,3": { - Width: "768", - Height: "1024", - Model: "iPad Pro (9.7)", - }, - // iPad Pro (9.7) - "iPad6,4": { - Width: "768", - Height: "1024", - Model: "iPad Pro (9.7)", - }, - // iPad Pro (12.9 2nd Gen) - "iPad7,1": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 2nd Gen)", - }, - // iPad Pro (12.9 2nd Gen) - "iPad7,2": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 2nd Gen)", - }, - // iPad Pro (10.5) - "iPad7,3": { - Width: "834", - Height: "1112", - Model: "iPad Pro (10.5)", - }, - // iPad Pro (10.5) - "iPad7,4": { - Width: "834", - Height: "1112", - Model: "iPad Pro (10.5)", - }, - // iPad (5th gen) - "iPad6,11": { - Width: "768", - Height: "1024", - Model: "iPad (5th gen)", - }, - // iPad (5th gen) - "iPad6,12": { - Width: "768", - Height: "1024", - Model: "iPad (5th gen)", - }, - // iPad Pro (12.9 3rd Gen) - "iPad8,5": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 3rd Gen)", - }, - // iPad Pro (12.9 3rd Gen) - "iPad8,6": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 3rd Gen)", - }, - // iPad Pro (12.9 3rd Gen) - "iPad8,7": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 3rd Gen)", - }, - // iPad Pro (12.9 3rd Gen) - "iPad8,8": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 3rd Gen)", - }, - // iPad Pro (11) - "iPad8,1": { - Width: "834", - Height: "1194", - Model: "iPad Pro (11)", - }, - // iPad Pro (11) - "iPad8,2": { - Width: "834", - Height: "1194", - Model: "iPad Pro (11)", - }, - // iPad Pro (11) - "iPad8,3": { - Width: "834", - Height: "1194", - Model: "iPad Pro (11)", - }, - // iPad Pro (11) - "iPad8,4": { - Width: "834", - Height: "1194", - Model: "iPad Pro (11)", - }, - // iPad (6th Gen) - "iPad7,5": { - Width: "768", - Height: "1024", - Model: "iPad (6th Gen)", - }, - // iPad (6th Gen) - "iPad7,6": { - Width: "768", - Height: "1024", - Model: "iPad (6th Gen)", - }, - // iPad Mini 5 - "iPad11,1": { - Width: "768", - Height: "1024", - Model: "iPad Mini 5", - }, - // iPad Mini 5 - "iPad11,2": { - Width: "768", - Height: "1024", - Model: "iPad Mini 5", - }, - // iPad Air 3 - "iPad11,3": { - Width: "834", - Height: "1112", - Model: "iPad Air 3", - }, - // iPad Air 3 - "iPad11,4": { - Width: "834", - Height: "1112", - Model: "iPad Air 3", - }, - // iPad (7th Gen) - "iPad7,11": { - Width: "810", - Height: "1080", - Model: "iPad (7th Gen)", - }, - // iPad (7th Gen) - "iPad7,12": { - Width: "810", - Height: "1080", - Model: "iPad (7th Gen)", - }, - // iPad Pro (12.9 4th Gen) - "iPad8,11": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 4th Gen)", - }, - // iPad Pro (12.9 4th Gen) - "iPad8,12": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 4th Gen)", - }, - // iPad Pro (11 2nd Gen) - "iPad8,9": { - Width: "", - Height: "", - Model: "iPad Pro (11 2nd Gen)", - }, - // iPad Pro (11 2nd Gen) - "iPad8,10": { - Width: "", - Height: "", - Model: "iPad Pro (11 2nd Gen)", - }, - // iPad Air 4 - "iPad13,1": { - Width: "820", - Height: "1180", - Model: "iPad Air 4", - }, - // iPad Air 4 - "iPad13,2": { - Width: "820", - Height: "1180", - Model: "iPad Air 4", - }, - // iPad (8th Gen) - "iPad11,6": { - Width: "810", - Height: "1180", - Model: "iPad (8th Gen)", - }, - // iPad (8th Gen) - "iPad11,7": { - Width: "810", - Height: "1180", - Model: "iPad (8th Gen)", - }, - // iPad Pro (12.9 5th Gen) - "iPad13,8": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 5th Gen)", - }, - // iPad Pro (12.9 5th Gen) - "iPad13,9": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 5th Gen)", - }, - // iPad Pro (12.9 5th Gen) - "iPad13,10": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 5th Gen)", - }, - // iPad Pro (12.9 5th Gen) - "iPad13,11": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 5th Gen)", - }, - // iPad Pro (11 3rd Gen) - "iPad13,4": { - Width: "834", - Height: "1194", - Model: "iPad Pro (11 3rd Gen)", - }, - // iPad Pro (11 3rd Gen) - "iPad13,5": { - Width: "834", - Height: "1194", - Model: "iPad Pro (11 3rd Gen)", - }, - // iPad Pro (11 3rd Gen) - "iPad13,6": { - Width: "834", - Height: "1194", - Model: "iPad Pro (11 3rd Gen)", - }, - // iPad Pro (11 3rd Gen) - "iPad13,7": { - Width: "834", - Height: "1194", - Model: "iPad Pro (11 3rd Gen)", - }, - // iPad mini 6 - "iPad14,1": { - Width: "", - Height: "", - Model: "iPad mini 6", - }, - // iPad mini 6 - "iPad14,2": { - Width: "744", - Height: "1133", - Model: "iPad mini 6", - }, - // iPad (9th Gen) - "iPad12,1": { - Width: "810", - Height: "1080", - Model: "iPad (9th Gen)", - }, - // iPad (9th Gen) - "iPad12,2": { - Width: "810", - Height: "1080", - Model: "iPad (9th Gen)", - }, - // iPad Pro (12.9 6th Gen) - "iPad14,5": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 6th Gen)", - }, - // iPad Pro (12.9 6th Gen) - "iPad14,6": { - Width: "1024", - Height: "1366", - Model: "iPad Pro (12.9 6th Gen)", - }, - // iPad Pro (11 4th Gen) - "iPad14,3": { - Width: "834", - Height: "1194", - Model: "iPad Pro (11 4th Gen)", - }, - // iPad Pro (11 4th Gen) - "iPad14,4": { - Width: "834", - Height: "1194", - Model: "iPad Pro (11 4th Gen)", - }, - // iPad Air 5 - "iPad13,16": { - Width: "820", - Height: "1180", - Model: "iPad Air 5", - }, - // iPad Air 5 - "iPad13,17": { - Width: "820", - Height: "1180", - Model: "iPad Air 5", - }, - // iPad (10th Gen) - "iPad13,18": { - Width: "820", - Height: "1180", - Model: "iPad (10th Gen)", - }, - // iPad (10th Gen) - "iPad13,19": { - Width: "820", - Height: "1180", - Model: "iPad (10th Gen)", - }, -} diff --git a/common/db/db.go b/common/db/db.go index 63c19508..7459e067 100644 --- a/common/db/db.go +++ b/common/db/db.go @@ -5,11 +5,12 @@ import ( "GADS/common/models" "context" "fmt" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo/gridfs" "io" "time" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo/gridfs" + "slices" "go.mongodb.org/mongo-driver/bson" @@ -79,20 +80,20 @@ func checkDBConnection() { } } -func GetProviderFromDB(nickname string) (models.ProviderDB, error) { - var provider models.ProviderDB +func GetProviderFromDB(nickname string) (models.Provider, error) { + var provider models.Provider coll := mongoClient.Database("gads").Collection("providers") filter := bson.D{{Key: "nickname", Value: nickname}} err := coll.FindOne(context.TODO(), filter).Decode(&provider) if err != nil { - return models.ProviderDB{}, err + return models.Provider{}, err } return provider, nil } -func GetProvidersFromDB() []models.ProviderDB { - var providers []models.ProviderDB +func GetProvidersFromDB() []models.Provider { + var providers []models.Provider ctx, cancel := context.WithTimeout(mongoClientCtx, 10*time.Second) defer cancel() @@ -201,7 +202,7 @@ func GetUserFromDB(username string) (models.User, error) { return user, nil } -func AddOrUpdateProvider(provider models.ProviderDB) error { +func AddOrUpdateProvider(provider models.Provider) error { update := bson.M{ "$set": provider, } @@ -244,64 +245,121 @@ func GetDBDevices() []models.Device { return dbDevices } -func GetDBDevicesUDIDs() []string { - dbDevices := GetDBDevices() - var udids []string +func GetUsers() []models.User { + var users []models.User + collection := mongoClient.Database("gads").Collection("users") - for _, dbDevice := range dbDevices { - udids = append(udids, dbDevice.UDID) + cursor, err := collection.Find(mongoClientCtx, bson.D{{}}, nil) + if err != nil { + log.WithFields(log.Fields{ + "event": "get_db_users", + }).Error(fmt.Sprintf("Could not get db cursor when trying to get latest user info from db - %s", err)) + return users } - return udids -} + if err := cursor.All(mongoClientCtx, &users); err != nil { + log.WithFields(log.Fields{ + "event": "get_db_users", + }).Error(fmt.Sprintf("Could not get users latest info from db cursor - %s", err)) + return users + } -func UpsertDeviceDB(device models.Device) error { - update := bson.M{ - "$set": device, + if err := cursor.Err(); err != nil { + log.WithFields(log.Fields{ + "event": "get_db_devices", + }).Error(fmt.Sprintf("Encountered db cursor error - %s", err)) + return users } - coll := mongoClient.Database("gads").Collection("devices") - filter := bson.D{{Key: "udid", Value: device.UDID}} - opts := options.Update().SetUpsert(true) - _, err := coll.UpdateOne(mongoClientCtx, filter, update, opts) - if err != nil { - return err + + if err := cursor.Err(); err != nil { + log.WithFields(log.Fields{ + "event": "get_db_users", + }).Error(fmt.Sprintf("Encountered db cursor error - %s", err)) + return users } - return nil + + cursor.Close(mongoClientCtx) + + return users } -func GetDevices() []models.Device { +func GetDBDeviceNew() []models.Device { + var dbDevices []models.Device // Access the database and collection - collection := MongoClient().Database("gads").Collection("devices") - latestDevices := []models.Device{} + collection := MongoClient().Database("gads").Collection("new_devices") - cursor, err := collection.Find(context.Background(), bson.D{{}}, options.Find()) + cursor, err := collection.Find(context.Background(), bson.D{{}}, nil) if err != nil { log.WithFields(log.Fields{ "event": "get_db_devices", }).Error(fmt.Sprintf("Could not get db cursor when trying to get latest device info from db - %s", err)) - return latestDevices } - if err := cursor.All(context.Background(), &latestDevices); err != nil { + if err := cursor.All(context.Background(), &dbDevices); err != nil { log.WithFields(log.Fields{ "event": "get_db_devices", }).Error(fmt.Sprintf("Could not get devices latest info from db cursor - %s", err)) - return latestDevices } if err := cursor.Err(); err != nil { log.WithFields(log.Fields{ "event": "get_db_devices", }).Error(fmt.Sprintf("Encountered db cursor error - %s", err)) - return latestDevices } - err = cursor.Close(context.TODO()) + cursor.Close(context.TODO()) + + return dbDevices +} + +func UpsertDeviceDB(device models.Device) error { + update := bson.M{ + "$set": device, + } + coll := mongoClient.Database("gads").Collection("new_devices") + filter := bson.D{{Key: "udid", Value: device.UDID}} + opts := options.Update().SetUpsert(true) + _, err := coll.UpdateOne(mongoClientCtx, filter, update, opts) if err != nil { - //stuff + return err } + return nil +} - return latestDevices +func DeleteDeviceDB(udid string) error { + coll := mongoClient.Database("gads").Collection("new_devices") + filter := bson.M{"udid": udid} + + _, err := coll.DeleteOne(mongoClientCtx, filter) + if err != nil { + return err + } + + return nil +} + +func DeleteUserDB(nickname string) error { + coll := mongoClient.Database("gads").Collection("users") + filter := bson.M{"username": nickname} + + _, err := coll.DeleteOne(mongoClientCtx, filter) + if err != nil { + return err + } + + return nil +} + +func DeleteProviderDB(nickname string) error { + coll := mongoClient.Database("gads").Collection("providers") + filter := bson.M{"nickname": nickname} + + _, err := coll.DeleteOne(mongoClientCtx, filter) + if err != nil { + return err + } + + return nil } func AddAdminUserIfMissing() error { @@ -375,7 +433,3 @@ func UploadFileGridFS(file io.Reader, fileName string, force bool) error { return nil } } - -func DownloadFileGridFS(fileName string, filePath string) { - -} diff --git a/common/models/config.go b/common/models/config.go index 1b49d46f..f7c69757 100644 --- a/common/models/config.go +++ b/common/models/config.go @@ -1,33 +1,29 @@ package models -type ConfigJsonData struct { - EnvConfig ProviderDB `json:"env-config" bson:"env-config"` -} - -type ProviderDB struct { - OS string `json:"os" bson:"os"` - Nickname string `json:"nickname" bson:"nickname"` - HostAddress string `json:"host_address" bson:"host_address"` - Port int `json:"port" bson:"port"` - UseSeleniumGrid bool `json:"use_selenium_grid" bson:"use_selenium_grid"` - SeleniumGrid string `json:"selenium_grid" bson:"selenium_grid"` - ProvideAndroid bool `json:"provide_android" bson:"provide_android"` - ProvideIOS bool `json:"provide_ios" bson:"provide_ios"` - WdaBundleID string `json:"wda_bundle_id" bson:"wda_bundle_id"` - WdaRepoPath string `json:"wda_repo_path" bson:"wda_repo_path"` - SupervisionPassword string `json:"supervision_password" bson:"supervision_password"` - ProviderFolder string `json:"-" bson:"-"` - LastUpdatedTimestamp int64 `json:"last_updated" bson:"last_updated"` - ProvidedDevices []Device `json:"provided_devices" bson:"provided_devices"` - ConnectedDevices []ConnectedDevice `json:"connected_devices" bson:"connected_devices"` - WebDriverBinary string `json:"-" bson:"-"` - UseGadsIosStream bool `json:"use_gads_ios_stream" bson:"use_gads_ios_stream"` - UseCustomWDA bool `json:"use_custom_wda" bson:"use_custom_wda"` +type Provider struct { + OS string `json:"os" bson:"os"` + Nickname string `json:"nickname" bson:"nickname"` + HostAddress string `json:"host_address" bson:"host_address"` + Port int `json:"port" bson:"port"` + UseSeleniumGrid bool `json:"use_selenium_grid" bson:"use_selenium_grid"` + SeleniumGrid string `json:"selenium_grid" bson:"selenium_grid"` + ProvideAndroid bool `json:"provide_android" bson:"provide_android"` + ProvideIOS bool `json:"provide_ios" bson:"provide_ios"` + WdaBundleID string `json:"wda_bundle_id" bson:"wda_bundle_id"` + WdaRepoPath string `json:"wda_repo_path" bson:"wda_repo_path"` + SupervisionPassword string `json:"supervision_password" bson:"supervision_password"` + ProviderFolder string `json:"-" bson:"-"` + LastUpdatedTimestamp int64 `json:"last_updated" bson:"last_updated"` + ProvidedDevices []Device `json:"provided_devices" bson:"provided_devices"` + WebDriverBinary string `json:"-" bson:"-"` + UseGadsIosStream bool `json:"use_gads_ios_stream" bson:"use_gads_ios_stream"` + UseCustomWDA bool `json:"use_custom_wda" bson:"use_custom_wda"` + HubAddress string `json:"hub_address" bson:"-"` } type ProviderData struct { - ProviderData ProviderDB `json:"provider"` - DeviceData []*Device `json:"device_data"` + ProviderData Provider `json:"provider"` + DeviceData []Device `json:"device_data"` } type HubConfig struct { diff --git a/common/models/models.go b/common/models/models.go index b5fee136..e605fd68 100644 --- a/common/models/models.go +++ b/common/models/models.go @@ -2,6 +2,8 @@ package models import ( "context" + "sync" + "github.com/danielpaulus/go-ios/ios" ) @@ -24,12 +26,6 @@ func (a ByUDID) Len() int { return len(a) } func (a ByUDID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByUDID) Less(i, j int) bool { return a[i].UDID < a[j].UDID } -type IOSModelData struct { - Width string - Height string - Model string -} - type User struct { Username string `json:"username" bson:"username"` Password string `json:"password" bson:"password"` @@ -38,41 +34,41 @@ type User struct { } type Device struct { - Connected bool `json:"connected" bson:"connected"` // common value - if device is currently connected - UDID string `json:"udid" bson:"udid"` // common value - device UDID - OS string `json:"os" bson:"os"` // common value - device OS - Name string `json:"name" bson:"name"` // common value - name of the device - OSVersion string `json:"os_version" bson:"os_version"` // common value - OS version of the device - Model string `json:"model" bson:"model"` // common value - device model - Host string `json:"host" bson:"host"` // common value - IP address of the device host(provider) - Provider string `json:"provider" bson:"provider"` // common value - nickname of the device host(provider) - ScreenWidth string `json:"screen_width" bson:"screen_width"` // common value - screen width of device - ScreenHeight string `json:"screen_height" bson:"screen_height"` // common value - screen height of device - HardwareModel string `json:"hardware_model,omitempty" bson:"hardware_model,omitempty"` // common value - hardware model of device - InstalledApps []string `json:"installed_apps" bson:"-"` // provider value - list of installed apps on device - IOSProductType string `json:"ios_product_type,omitempty" bson:"ios_product_type,omitempty"` // provider value - product type of iOS devices - LastUpdatedTimestamp int64 `json:"last_updated_timestamp" bson:"last_updated_timestamp"` // common value - last time the device data was updated - WdaReadyChan chan bool `json:"-" bson:"-"` // provider value - channel for checking that WebDriverAgent is up after start - Context context.Context `json:"-" bson:"-"` // provider value - context used to control the device set up since we have multiple goroutines - CtxCancel context.CancelFunc `json:"-" bson:"-"` // provider value - cancel func for the context above, can be used to stop all running device goroutines - GoIOSDeviceEntry ios.DeviceEntry `json:"-" bson:"-"` // provider value - `go-ios` device entry object used for `go-ios` library interactions - IsResetting bool `json:"is_resetting" bson:"is_resetting"` // common value - if device setup is currently being reset - Logger CustomLogger `json:"-" bson:"-"` // provider value - CustomLogger object for the device - AppiumSessionID string `json:"appiumSessionID" bson:"-"` // provider value - current Appium session ID - WDASessionID string `json:"wdaSessionID" bson:"-"` // provider value - current WebDriverAgent session ID - AppiumPort string `json:"appium_port" bson:"-"` // provider value - port assigned to the device for the Appium server - StreamPort string `json:"stream_port" bson:"-"` // provider value - port assigned to the device for the video stream - WDAStreamPort string `json:"wda_stream_port" bson:"-"` // provider value - port assigned to iOS devices for the WebDriverAgent stream - WDAPort string `json:"wda_port" bson:"-"` // provider value - port assigned to iOS devices for the WebDriverAgent instance - AppiumLogger AppiumLogger `json:"-" bson:"-"` // provider value - AppiumLogger object for logging appium actions - Available bool `json:"available" bson:"-"` // provider value - if device is currently available - not only connected, but setup completed - ProviderState string `json:"provider_state" bson:"provider_state"` // common value - current state of the device on the provider - init, preparing, live -} - -type ConnectedDevice struct { - OS string `json:"os" bson:"os"` - UDID string `json:"udid" bson:"udid"` - IsConfigured bool `json:"is_configured" bson:"-"` + // DB DATA + UDID string `json:"udid" bson:"udid"` // device UDID + OS string `json:"os" bson:"os"` // device OS + Name string `json:"name" bson:"name"` // name of the device + OSVersion string `json:"os_version" bson:"os_version"` // OS version of the device + Provider string `json:"provider" bson:"provider"` // nickname of the device host(provider) + Usage string `json:"usage" bson:"usage"` // what is the device used for: enabled(automation and remote control), automation(only Appium testing), remote(only remote control), disabled + ScreenWidth string `json:"screen_width" bson:"screen_width"` // screen width of device + ScreenHeight string `json:"screen_height" bson:"screen_height"` // screen height of device + DeviceType string `json:"device_type" bson:"device_type"` // The type of device - `real` or `emulator` + // NON-DB DATA + /// COMMON VALUES + Host string `json:"host" bson:"-"` // IP address of the device host(provider) + HardwareModel string `json:"hardware_model" bson:"-"` // hardware model of device + LastUpdatedTimestamp int64 `json:"last_updated_timestamp" bson:"-"` // last time the device data was updated + Connected bool `json:"connected" bson:"-"` // if device is currently connected + IsResetting bool `json:"is_resetting" bson:"-"` // if device setup is currently being reset + ProviderState string `json:"provider_state" bson:"-"` // current state of the device on the provider - init, preparing, live + /// PROVIDER ONLY VALUES + //// RETURNABLE VALUES + InstalledApps []string `json:"installed_apps" bson:"-"` // list of installed apps on device + ///// NON-RETURNABLE VALUES + AppiumSessionID string `json:"-" bson:"-"` // current Appium session ID + WDASessionID string `json:"-" bson:"-"` // current WebDriverAgent session ID + AppiumPort string `json:"-" bson:"-"` // port assigned to the device for the Appium server + StreamPort string `json:"-" bson:"-"` // port assigned to the device for the video stream + WDAStreamPort string `json:"-" bson:"-"` // port assigned to iOS devices for the WebDriverAgent stream + WDAPort string `json:"-" bson:"-"` // port assigned to iOS devices for the WebDriverAgent instance + WdaReadyChan chan bool `json:"-" bson:"-"` // channel for checking that WebDriverAgent is up after start + Context context.Context `json:"-" bson:"-"` // context used to control the device set up since we have multiple goroutines + CtxCancel context.CancelFunc `json:"-" bson:"-"` // cancel func for the context above, can be used to stop all running device goroutines + GoIOSDeviceEntry ios.DeviceEntry `json:"-" bson:"-"` // `go-ios` device entry object used for `go-ios` library interactions + Logger CustomLogger `json:"-" bson:"-"` // CustomLogger object for the device + AppiumLogger AppiumLogger `json:"-" bson:"-"` // AppiumLogger object for logging appium actions + Mutex sync.Mutex `json:"-" bson:"-"` // Mutex to lock resources - especially on device reset } type LocalHubDevice struct { @@ -85,4 +81,5 @@ type LocalHubDevice struct { InUseTS int64 `json:"in_use_ts"` AppiumNewCommandTimeout int64 `json:"appium_new_command_timeout"` IsAvailableForAutomation bool `json:"is_available_for_automation"` + Available bool `json:"available" bson:"-"` // if device is currently available - not only connected, but setup completed } diff --git a/docs/hub.md b/docs/hub.md index db126ac0..749841ce 100644 --- a/docs/hub.md +++ b/docs/hub.md @@ -7,8 +7,7 @@ Follow the setup steps to create and run a provider instance. You can have multiple provider instances on different hosts providing devices. ### Starting hub instance -Run `./GADS hub` with the following flags: -- `--auth=` - `true/false` to enable actual user authentication (default is `false`) +Run `./GADS hub` with the following flags: - `--host-address=` - local IP address of the host machine, e.g. `192.168.1.6` (default is `localhost`, I would advise against using the default value) - `--port=` - port on which the UI and backend service will run - `--mongo-db=` - IP address and port of the MongoDB instance, e.g `192.168.1.6:27017` (default is `localhost:27017`) - tested only on local network @@ -24,8 +23,25 @@ If you want to work on the React UI with hot reload you need to add a proxy in ` 4. Run `npm start` ### Additional notes +#### Users administration +You can add/delete users and change their roles/passwords via the `Admin` panel. +There are no limitations on usernames and passwords - only the default `admin` user cannot be deleted and its role changed(you can change its password though) + +#### Providers administration +For each provider instance you need to create a provider configuration via the `Admin` panel. +All fields have tooltips to help you with the required information. + +#### Devices administration +Device configurations are added via the `Admin` panel. +You have to provide all the required information and assign each device to a provider. +Changes to the device configuration require the respective provider instance restarted. +All fields have tooltips to help you with the required information. + #### Experimental Appium grid -Using Selenium Grid 4 is a bit of a hassle and some versions do not work properly with Appium relay nodes. For this reason I created an experimental grid implementation into the hub itself. I haven't even read the Selenium Grid implementation and made up something myself - it might not work properly but could be the better alternative if it does work properly. The experimental grid was tested only using latest Appium and Selenium Java client versions and with TestNG. Tests can be executed sequentially or in parallel using TestNG with `methods` or `classes` with multiple threads. I assume it should support any type of session creation with any Appium language client +Using Selenium Grid 4 is a bit of a hassle and some versions do not work properly with Appium relay nodes. +For this reason I created an experimental grid implementation into the hub itself. +I haven't even read the Selenium Grid implementation and made up something myself - it might not work properly but could be the better alternative if it does work properly. +The experimental grid was tested only using latest Appium and Selenium Java client versions and with TestNG. Tests can be executed sequentially or in parallel using TestNG with `methods` or `classes` with multiple threads. I assume it should support any type of session creation with any Appium language client * The grid is accessible on your hub instance e.g. `http://192.168.1.6:10000/grid` and should be used as Appium/Selenium driver URL target. You just try to start a session as you usually do with Selenium Grid * The grid allows targeting devices by UDID @@ -33,11 +49,12 @@ Using Selenium Grid 4 is a bit of a hassle and some versions do not work properl * Additionally the grid allows filtering by `appium:platformVersion` capability which supports exact version e.g. `17.5.1` or a major version e.g. `17`, `11` etc #### Selenium Grid -Devices can be automatically connected to Selenium Grid 4 instance. You need to create the Selenium Grid hub instance yourself and then set it up in the provider configuration to connect to it. +Devices can be automatically connected to Selenium Grid 4 instance. +You need to create the Selenium Grid hub instance yourself and then set it up in the provider configuration to connect to it. * Start your Selenium hub instance, e.g. `java -jar selenium.jar --host 192.168.1.6 --port 4444` -* When adding/updating provider configuration from `Admin > Provider administration` you need to supply the Selenium hub address, e.g. `http://192.168.1.6:4444` +* When adding/updating provider configuration from `Admin > Provider` you need to supply the Selenium hub address, e.g. `http://192.168.1.6:4444` * You also need to upload the respective Selenium jar file so the provider instances have access to it - * Log in to the hub with admin user, go to `Admin > Files administration` and upload the Selenium jar file - v4.13 is recommended. + * Log in to the hub with admin user, go to `Admin > Files` and upload the Selenium jar file - v4.13 is recommended. * The file will be stored in Mongo and providers will download it on start automatically. **NB** At the time support for Selenium Grid was implemented latest Selenium version was 4.15. The latest version that actually worked with Appium relay nodes was 4.13. I haven't tested with lower versions. Use lower versions at your own risk. Versions > 4.15 might also work but it wasn't tested as well. \ No newline at end of file diff --git a/docs/provider.md b/docs/provider.md index 4ed9546f..01366ddc 100644 --- a/docs/provider.md +++ b/docs/provider.md @@ -17,9 +17,9 @@ The provider component is what actually sets up the Appium servers and all other Provider configuration is added through the GADS UI - Log in the hub UI with an admin user. - Go to the `Admin` section. -- Open `Providers administration` +- Open `Providers` - On the `New provider` tab fill in all needed data and save. -- You should see a new provider tab with the nickname you provided. You can now start up a provider instance using the newly added configuration. +- You should see a new provider component with the configuration you provided. You can now start up a provider instance using the newly added configuration. ## Provider data folder - optional The provider needs a persistent folder where logs, apps and other files might be stored. @@ -89,6 +89,7 @@ Refer to the `--provider-folder` flag in [Running a provider instance](#running- - `--mongo-db=` - optional, IP address and port of the MongoDB instance (default is `localhost:27017`) - `--provider-folder=` - optional, folder where provider should store logs and apps and other needed files. Can be relative path to the folder where provider binary is located or full path on the host - `./test`, `.`, `./test/test1`, `/Users/shamanec/Desktop/test` are all valid. Default is the folder where the binary is currently located - `.` - `--log-level=` - optional, how verbose should the provider logs be (default is `info`, use `debug` for more log output) + - `--hub=` - mandatory, the address of the hub instance so the provider can push data to it automatically, e.g `http://192.168.68.109:10000` ### Dependencies notes #### Appium diff --git a/go.mod b/go.mod index 888be64b..8a707d28 100644 --- a/go.mod +++ b/go.mod @@ -3,41 +3,48 @@ module GADS go 1.21 require ( - github.com/danielpaulus/go-ios v1.0.121 + github.com/Masterminds/semver v1.5.0 + github.com/danielpaulus/go-ios v1.0.123 github.com/gin-contrib/static v0.0.1 github.com/gin-gonic/gin v1.9.0 github.com/gobwas/ws v1.4.0 - github.com/sirupsen/logrus v1.8.1 + github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 ) require ( - github.com/Masterminds/semver v1.5.0 // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/golang/snappy v0.0.1 // indirect + github.com/grandcat/zeroconf v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.13.6 // indirect + github.com/miekg/dns v1.1.61 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/stretchr/testify v1.8.4 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/text v0.9.0 // indirect + go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.22.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 // indirect + howett.net/plist v1.0.1 // indirect + software.sslmate.com/src/go-pkcs12 v0.4.0 // indirect ) require ( @@ -45,7 +52,7 @@ require ( github.com/gin-contrib/cors v1.4.0 github.com/go-playground/validator/v10 v10.12.0 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/google/uuid v1.4.0 + github.com/google/uuid v1.6.0 github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.2.3 // indirect github.com/mattn/go-isatty v0.0.18 // indirect @@ -54,7 +61,6 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect go.mongodb.org/mongo-driver v1.12.1 golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.8.0 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/protobuf v1.30.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index 47a57320..d12b7d7e 100644 --- a/go.sum +++ b/go.sum @@ -3,18 +3,18 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ= github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/danielpaulus/go-ios v1.0.121 h1:PvaYooOlhl3t6H5JGYkdTbNcz0iWBkyywk5fOLuPK+k= -github.com/danielpaulus/go-ios v1.0.121/go.mod h1:Rtn5ICcWo8dhRyhmX/O++hgmiaqrXMwuWR/QykADB6Q= +github.com/danielpaulus/go-ios v1.0.123 h1:Pyv+9xdFIaaGXJFQHL11l1rBde1pJhSA+N0Id1M9x+A= +github.com/danielpaulus/go-ios v1.0.123/go.mod h1:tCOjUoiimx1wL8WJ7GoTF1bT9o4xAN1Fpif+vzSHXkc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -54,11 +54,14 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE= +github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -86,6 +89,9 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= +github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -98,14 +104,16 @@ github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZO github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -114,7 +122,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -122,8 +129,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -143,28 +151,38 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= +go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= +go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -173,10 +191,11 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -186,25 +205,27 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -212,6 +233,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc= -howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= +howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= +software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/hub/devices/devices.go b/hub/devices/devices.go index 1fd5dee5..31366b51 100644 --- a/hub/devices/devices.go +++ b/hub/devices/devices.go @@ -28,37 +28,84 @@ func CalculateCanvasDimensions(device *models.Device) (canvasWidth string, canva return } -var HubDevicesMap = make(map[string]*models.LocalHubDevice) +type HubDevices struct { + Mu sync.Mutex + Devices map[string]*models.LocalHubDevice +} + +var HubDevicesData HubDevices + +func InitHubDevicesData() { + HubDevicesData = HubDevices{ + Devices: make(map[string]*models.LocalHubDevice), + } +} // Get the latest devices information from MongoDB each second func GetLatestDBDevices() { var latestDBDevices []models.Device for { - latestDBDevices = db.GetDevices() + latestDBDevices = db.GetDBDeviceNew() + + HubDevicesData.Mu.Lock() + for udid, _ := range HubDevicesData.Devices { + found := false + for _, dbDevice := range latestDBDevices { + if dbDevice.UDID == udid { + found = true + break + } + } + if !found { + delete(HubDevicesData.Devices, udid) + } + } + HubDevicesData.Mu.Unlock() + for _, dbDevice := range latestDBDevices { - hubDevice, ok := HubDevicesMap[dbDevice.UDID] + HubDevicesData.Mu.Lock() + hubDevice, ok := HubDevicesData.Devices[dbDevice.UDID] if ok { - hubDevice.Device = dbDevice + // Update data only if needed + if hubDevice.Device.OSVersion != dbDevice.OSVersion { + hubDevice.Device.OSVersion = dbDevice.OSVersion + } + if hubDevice.Device.Name != dbDevice.Name { + hubDevice.Device.Name = dbDevice.Name + } + if hubDevice.Device.ScreenWidth != dbDevice.ScreenWidth { + hubDevice.Device.ScreenWidth = dbDevice.ScreenWidth + } + if hubDevice.Device.ScreenHeight != dbDevice.ScreenHeight { + hubDevice.Device.ScreenHeight = dbDevice.ScreenHeight + } + if hubDevice.Device.Usage != dbDevice.Usage { + hubDevice.Device.Usage = dbDevice.Usage + } + if hubDevice.Device.Provider != dbDevice.Provider { + hubDevice.Device.Provider = dbDevice.Provider + } } else { - HubDevicesMap[dbDevice.UDID] = &models.LocalHubDevice{ + HubDevicesData.Devices[dbDevice.UDID] = &models.LocalHubDevice{ Device: dbDevice, IsRunningAutomation: false, IsAvailableForAutomation: true, LastAutomationActionTS: 0, } } + HubDevicesData.Mu.Unlock() } time.Sleep(1 * time.Second) } } -var getDeviceMu sync.Mutex +var getDeviceMu sync.RWMutex func GetHubDeviceByUDID(udid string) *models.LocalHubDevice { getDeviceMu.Lock() defer getDeviceMu.Unlock() - for _, hubDevice := range HubDevicesMap { + for _, hubDevice := range HubDevicesData.Devices { if hubDevice.Device.UDID == udid { return hubDevice } diff --git a/hub/gads-ui/src/Gads.js b/hub/gads-ui/src/Gads.js index 830fec59..e1063d4f 100644 --- a/hub/gads-ui/src/Gads.js +++ b/hub/gads-ui/src/Gads.js @@ -1,16 +1,16 @@ import './Gads.css' import DeviceSelection from './components/DeviceSelection/DeviceSelection' -import {Routes, Route, Navigate} from 'react-router-dom' +import { Routes, Route, Navigate } from 'react-router-dom' import NavBar from './components/TopNavigationBar/TopNavigationBar' import DeviceControl from './components/DeviceControl/DeviceControl' import Login from './components/Login/Login' -import {useContext, useEffect} from 'react' +import { useContext, useEffect } from 'react' import { Auth } from './contexts/Auth' import AdminDashboard from './components/Admin/AdminDashboard' import axiosInterceptor from './services/axiosInterceptor' function Gads() { - const {authToken, logout} = useContext(Auth) + const { authToken, logout } = useContext(Auth) // Set the logout function from the Auth context on the axiosInterceptor to automatically logout on each 401 axiosInterceptor(logout) @@ -21,17 +21,17 @@ function Gads() { }, []) if (!authToken) { - return + return } return ( -
- +
+ - }/> - }/> - }/> - }/> + } /> + } /> + } /> + } />
) diff --git a/hub/gads-ui/src/components/Admin/AdminDashboard.css b/hub/gads-ui/src/components/Admin/AdminDashboard.css deleted file mode 100644 index e69de29b..00000000 diff --git a/hub/gads-ui/src/components/Admin/AdminDashboard.js b/hub/gads-ui/src/components/Admin/AdminDashboard.js index 02f55e33..ed595086 100644 --- a/hub/gads-ui/src/components/Admin/AdminDashboard.js +++ b/hub/gads-ui/src/components/Admin/AdminDashboard.js @@ -3,8 +3,9 @@ import Tab from '@mui/material/Tab'; import { useState } from "react"; import { Box } from "@mui/material"; import UsersAdministration from "./Users/UsersAdministration"; -import ProvidersAdministration from "./Providers/ProvidersAdministration"; import FilesAdministration from "./Files/FilesAdministration"; +import DevicesAdministration from './Devices/DevicesAdministration'; +import ProvidersAdministration from './Providers/ProvidersAdministration'; export default function AdminDashboard() { @@ -38,7 +39,7 @@ export default function AdminDashboard() { }} > + - {currentTabIndex === 0 && } - {currentTabIndex === 1 && } - {currentTabIndex === 2 && } + {currentTabIndex === 0 && } + {currentTabIndex === 1 && } + {currentTabIndex === 2 && } + {currentTabIndex === 3 && } ) } \ No newline at end of file diff --git a/hub/gads-ui/src/components/Admin/Devices/DevicesAdministration.js b/hub/gads-ui/src/components/Admin/Devices/DevicesAdministration.js new file mode 100644 index 00000000..cffbcb3f --- /dev/null +++ b/hub/gads-ui/src/components/Admin/Devices/DevicesAdministration.js @@ -0,0 +1,639 @@ +import { useContext, useState, useEffect } from "react" +import { api } from "../../../services/api" +import { Box, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControl, Grid, MenuItem, Stack, TextField, Tooltip } from "@mui/material" +import { Auth } from "../../../contexts/Auth" +import CircularProgress from "@mui/material/CircularProgress"; +import CheckIcon from "@mui/icons-material/Check"; +import CloseIcon from "@mui/icons-material/Close"; + +export default function DevicesAdministration() { + const [devices, setDevices] = useState([]) + const [providers, setProviders] = useState([]) + const { logout } = useContext(Auth) + + function handleGetDeviceData() { + let url = `/admin/devices` + + api.get(url) + .then(response => { + setDevices(response.data.devices) + setProviders(response.data.providers) + }) + .catch(error => { + if (error.response) { + if (error.response.status === 401) { + logout() + } + } + }) + } + + useEffect(() => { + handleGetDeviceData() + }, []) + + return ( + + + + + + + + {devices.map((device) => { + return ( + + + + + ) + }) + } + + + + ) +} + +function NewDevice({ providers, handleGetDeviceData }) { + const [udid, setUdid] = useState('') + const [provider, setProvider] = useState('') + const [os, setOS] = useState('') + const [name, setName] = useState('') + const [osVersion, setOSVersion] = useState('') + const [screenHeight, setScreenHeight] = useState('') + const [screenWidth, setScreenWidth] = useState('') + const [usage, setUsage] = useState('enabled') + const [type, setType] = useState('real') + + const [loading, setLoading] = useState(false); + const [addDeviceStatus, setAddDeviceStatus] = useState(null) + + function handleAddDevice(event) { + setLoading(true) + setAddDeviceStatus(null) + event.preventDefault() + + let url = `/admin/device` + + const deviceData = { + udid: udid, + name: name, + os_version: osVersion, + provider: provider, + screen_height: screenHeight, + screen_width: screenWidth, + os: os, + usage: usage, + device_type: type + } + + api.post(url, deviceData) + .then(() => { + setAddDeviceStatus('success') + setUdid('') + setProvider('') + setOS('') + setName('') + setOSVersion('') + setScreenHeight('') + setScreenWidth('') + setUsage('enabled') + }) + .catch(() => { + setAddDeviceStatus('error') + }) + .finally(() => { + setTimeout(() => { + setLoading(false) + handleGetDeviceData() + setTimeout(() => { + setAddDeviceStatus(null) + }, 2000) + }, 1000) + }) + } + + return ( + +
+ + + + setOS(e.target.value)} + select + label="Device OS" + required + > + Android + iOS + + + + + + setType(e.target.value)} + select + label="Device type" + required + > + Real device + Emulator/Simulator - TODO + + + + Unique device identifier
Use `adb devices` to get Android device UDID
Use `ios list` to get iOS device UDID with `go-ios`
} + arrow + placement='top' + > + setUdid(event.target.value)} + /> + + + setName(event.target.value)} + /> + + + setOSVersion(event.target.value)} + /> + + Device screen width
For Android - go to `https://whatismyandroidversion.com` and use the displayed `Screen size`, not `Viewport size`
For iOS - you can get it on https://whatismyviewport.com (ScreenSize: at the bottom)} + arrow + placement='top' + > + setScreenWidth(event.target.value)} + /> +
+ Device screen height
For Android - go to `https://whatismyandroidversion.com` and use the displayed `Screen size`, not `Viewport size`
For iOS - you can get it on https://whatismyviewport.com (ScreenSize: at the bottom)} + arrow + placement='top' + > + setScreenHeight(event.target.value)} + /> +
+ Intended usage of the device
Enabled: Can be used for automation and remote control
Automation: Can be used only as automation target
Remote control: Can be used only for remote control testing
Disabled: Device will not be provided} + arrow + placement='top' + > + + setUsage(e.target.value)} + select + label="Device usage" + required + > + Enabled + Automation + Remote control + Disabled + + +
+ + + setProvider(e.target.value)} + select + label="Provider" + required + > + {providers.map((providerName) => { + return ( + {providerName} + ) + }) + } + + + + +
All updates to existing devices require respective provider restart
+ + + + ) +} + +function ExistingDevice({ deviceData, providersData, handleGetDeviceData }) { + const [provider, setProvider] = useState(deviceData.provider) + const [os, setOS] = useState(deviceData.os) + const [name, setName] = useState(deviceData.name) + const [osVersion, setOSVersion] = useState(deviceData.os_version) + const [screenHeight, setScreenHeight] = useState(deviceData.screen_height) + const [screenWidth, setScreenWidth] = useState(deviceData.screen_width) + const [usage, setUsage] = useState(deviceData.usage) + const [type, setType] = useState(deviceData.device_type) + const udid = deviceData.udid + + const [loading, setLoading] = useState(false); + const [updateDeviceStatus, setUpdateDeviceStatus] = useState(null) + + useEffect(() => { + setProvider(deviceData.provider) + setOS(deviceData.os) + setName(deviceData.name) + setOSVersion(deviceData.os_version) + setScreenHeight(deviceData.screen_height) + setScreenWidth(deviceData.screen_width) + }, [deviceData]) + + function handleUpdateDevice(event) { + setLoading(true) + setUpdateDeviceStatus(null) + event.preventDefault() + + let url = `/admin/device` + + const reqData = { + udid: udid, + name: name, + os_version: osVersion, + provider: provider, + screen_height: screenHeight, + screen_width: screenWidth, + os: os, + usage: usage, + device_type: type + } + + api.put(url, reqData) + .then(() => { + setUpdateDeviceStatus('success') + }) + .catch(() => { + setUpdateDeviceStatus('error') + }) + .finally(() => { + setTimeout(() => { + setLoading(false) + handleGetDeviceData() + setTimeout(() => { + setUpdateDeviceStatus(null) + }, 2000) + }, 1000) + }) + } + + function handleDeleteDevice(event) { + event.preventDefault() + + let url = `/admin/device/${udid}` + + api.delete(url) + .catch(e => { + }) + .finally(() => { + handleGetDeviceData() + setOpenAlert(false) + }) + } + + const [openAlert, setOpenAlert] = useState(false) + + return ( + +
+ + + + setOS(e.target.value)} + select + label="Device OS" + required + > + Android + iOS + + + + + + + Real device + Emulator/Simulator + + + + + + + + setName(event.target.value)} + /> + + + setOSVersion(event.target.value)} + /> + + Device screen width
For Android - go to `https://whatismyandroidversion.com` and use the displayed `Screen size`, not `Viewport size`
For iOS - you can get it on https://whatismyviewport.com (ScreenSize: at the bottom)} + arrow + placement='top' + > + setScreenWidth(event.target.value)} + /> +
+ Device screen height
For Android - go to `https://whatismyandroidversion.com` and use the displayed `Screen size`, not `Viewport size`
For iOS - you can get it on https://whatismyviewport.com (ScreenSize: at the bottom)} + arrow + placement='top' + > + setScreenHeight(event.target.value)} + /> +
+ Intended usage of the device
Enabled: Can be used for automation and remote control
Automation: Can be used only as automation target
Remote control: Can be used only for remote control testing
Disabled: Device will not be provided} + arrow + placement='top' + > + + setUsage(e.target.value)} + select + label="Device usage" + required + > + Enabled + Automation + Remote control + Disabled + + +
+ + + setProvider(e.target.value)} + select + label="Provider" + required + > + {providersData.map((providerName) => { + return ( + {providerName} + ) + }) + } + + + + + + setOpenAlert(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + + {"Delete device from DB?"} + + + + Device with UDID `{udid}`, assigned to provider `{provider}`. + + + + + + + +
+
+
+ ) +} \ No newline at end of file diff --git a/hub/gads-ui/src/components/Admin/Files/UploadSeleniumJar.js b/hub/gads-ui/src/components/Admin/Files/UploadSeleniumJar.js index a593b8fa..2a797743 100644 --- a/hub/gads-ui/src/components/Admin/Files/UploadSeleniumJar.js +++ b/hub/gads-ui/src/components/Admin/Files/UploadSeleniumJar.js @@ -58,7 +58,12 @@ export default function UploadSeleniumJar() { setAlertSeverity('error') setAlertText('Failed uploading Selenium jar file') setShowAlert(true) - }); + }) + .finally(() => { + setTimeout(() => { + setShowAlert(false) + }, 5000) + }) } } @@ -66,7 +71,12 @@ export default function UploadSeleniumJar() {

Upload Selenium jar

@@ -78,7 +88,7 @@ export default function UploadSeleniumJar() { - {isUploading && - - } - {showAlert && {alertText}} + {showAlert && {alertText}}
) } \ No newline at end of file diff --git a/hub/gads-ui/src/components/Admin/Providers/Provider/Provider.js b/hub/gads-ui/src/components/Admin/Providers/Provider/Provider.js deleted file mode 100644 index 28585d2d..00000000 --- a/hub/gads-ui/src/components/Admin/Providers/Provider/Provider.js +++ /dev/null @@ -1,156 +0,0 @@ -import { Box, Skeleton, Stack } from "@mui/material"; -import ProviderConfig from "./ProviderConfig"; -import { useEffect, useState } from "react"; -import ProviderInfo from "./ProviderInfo"; -import ProviderDevice from "./ProviderDevice" -import ProviderLogsTable from "./ProviderLogsTable/ProviderLogsTable"; - -export default function Provider({ info }) { - return ( - - - - - - - - - - ) -} - -function InfoBox({ os, isOnline }) { - return ( - - ) -} - - -function LiveProviderBox({ nickname, os }) { - let infoSocket = null; - let [devicesData, setDevicesData] = useState(null) - const [isLoading, setIsLoading] = useState(true) - const [isOnline, setIsOnline] = useState(false) - const [providerData, setProviderData] = useState(null) - - useEffect(() => { - // Use specific full address for local development, proxy does not seem to work okay - // const evtSource = new EventSource(`http://192.168.1.6:10000/admin/provider/${nickname}/info`); - const evtSource = new EventSource(`/admin/provider/${nickname}/info`); - - evtSource.onmessage = (event) => { - let providerJSON = JSON.parse(event.data) - setProviderData(providerJSON) - setDevicesData(providerJSON.provided_devices) - - let unixTimestamp = new Date().getTime(); - let diff = unixTimestamp - providerJSON.last_updated - if (diff > 4000) { - setIsOnline(false) - } else { - setIsOnline(true) - } - - if (isLoading) { - setIsLoading(false) - } - } - - return () => { - evtSource.close() - } - }, []) - - if (isLoading) { - return ( - - ) - } else { - return ( - - - - - ) - } -} - -function ProviderDevices({ devicesData, isOnline }) { - if (!isOnline || devicesData === null) { - return ( -
No device data or provider offline
- ) - } else { - return ( - <> - - {devicesData.map((device) => { - return ( - - - ) - }) - } - - - ) - } -} \ No newline at end of file diff --git a/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderConfig.js b/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderConfig.js deleted file mode 100644 index 933d11b0..00000000 --- a/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderConfig.js +++ /dev/null @@ -1,336 +0,0 @@ -import { Alert, Button, MenuItem, Select, Stack, TextField } from '@mui/material' -import { useContext, useEffect, useState } from 'react' -import { Auth } from '../../../../contexts/Auth' -import { api } from '../../../../services/api.js' - -export default function ProviderConfig({ isNew, data, setProviders }) { - useEffect(() => { - if (data) { - setOS(data.os) - setHostAddress(data.host_address) - setNickname(data.nickname) - setPort(data.port) - setAndroid(data.provide_android) - setIos(data.provide_ios) - setUseSeleniumGrid(data.use_selenium_grid) - setSeleniumGrid(data.selenium_grid) - setWdaBundleId(data.wda_bundle_id) - setWdaRepoPath(data.wda_repo_path) - setSupervisionPassword(data.supervision_password) - setButtonText('Update') - setUrlPath('update') - setUseCustomWda(data.use_custom_wda) - } - }, [data]) - // Main - const {logout} = useContext(Auth) - // OS - const [os, setOS] = useState('windows') - // Host address - const [hostAddress, setHostAddress] = useState('') - // Nickname - const [nickname, setNickname] = useState('') - // Port - const [port, setPort] = useState(0) - // Provide Android - const [android, setAndroid] = useState(false) - // Provide iOS - const [ios, setIos] = useState(false) - // Use Selenium Grid - const [useSeleniumGrid, setUseSeleniumGrid] = useState(false) - // Selenium Grid - const [seleniumGrid, setSeleniumGrid] = useState('') - // Supervision password - const [supervisionPassword, setSupervisionPassword] = useState('') - // Custom WebDriverAgent - const [useCustomWda, setUseCustomWda] = useState(false) - // WebDriverAgent bundle id - const [wdaBundleId, setWdaBundleId] = useState('') - // WebDriverAgent repo path - MacOS - const [wdaRepoPath, setWdaRepoPath] = useState('') - // Error - const [showError, setShowError] = useState(false) - const [errorText, setErrorText] = useState('') - // Button - const [buttonText, setButtonText] = useState('Add') - // URL path - const [urlPath, setUrlPath] = useState('add') - - // On successful provider creation reset the form data - function resetForm() { - setOS('windows') - setHostAddress('') - setNickname('') - setPort(0) - setAndroid(false) - setIos(false) - setUseSeleniumGrid(false) - setSeleniumGrid('') - setSupervisionPassword('') - setWdaBundleId('') - setWdaRepoPath('') - setUseCustomWda(false) - } - - // On pressing Add/Update - function handleAddClick() { - setShowError(false) - let url = `/admin/providers/${urlPath}` - let bodyString = buildPayload() - - api.post(url, bodyString, {}) - .then(response => { - if (isNew) { - resetForm() - } - if (urlPath === 'add') { - setProviders(response.data) - } - }) - .catch(error => { - if (error.response) { - if (error.response.status === 401) { - logout() - return - } - handleError(error.response.data.error) - return - } - handleError('Failure') - }) - } - - // Create the payload for adding/updating provider request - function buildPayload() { - let body = {} - body.os = os - body.host_address = hostAddress - body.nickname = nickname - body.port = port - body.provide_android = android - body.provide_ios = ios - if (ios) { - body.wda_bundle_id = wdaBundleId - body.wda_repo_path = wdaRepoPath - body.supervision_password = supervisionPassword - body.use_custom_wda = useCustomWda - } - body.use_selenium_grid = useSeleniumGrid - if (useSeleniumGrid) { - body.selenium_grid = seleniumGrid - } - - let bodyString = JSON.stringify(body) - return bodyString - } - - function handleError(msg) { - setErrorText(msg) - setShowError(true) - } - - return ( - - - - - setNickname(e.target.value)} - label='Nickname' - required - id='outlined-required' - autoComplete='off' - helperText='Unique nickname for the provider' - fullWidth - size='small' - value={nickname} - disabled={!isNew} - /> - setHostAddress(e.target.value)} - label='Host address' - required - id='outlined-required' - autoComplete='off' - helperText='Local IP address of the provider host without scheme, e.g. 192.168.1.10' - fullWidth - size='small' - value={hostAddress} - InputLabelProps={{ style: { fontSize: 14 } }} - /> - setPort(Number(e.target.value))} - label='Port' - required - id='outlined-required' - autoComplete='off' - helperText='The port on which you want the provider instance to run' - fullWidth - size='small' - value={port} - /> -
Provide Android?
- -
Provide iOS?
- -
- - setWdaBundleId(e.target.value)} - label='WebDriverAgent bundle ID' - required - id='outlined-required' - autoComplete='off' - disabled={!ios} - helperText='Bundle ID of the prebuilt WebDriverAgent.ipa, used by `go-ios` to start it' - value={wdaBundleId} - size='small' - fullWidth - /> - setWdaRepoPath(e.target.value)} - label='WebDriverAgent repo path' - required - id='outlined-required' - autoComplete='off' - helperText='Path on the host to the WebDriverAgent repo to build from, e.g. /Users/shamanec/WebDriverAgent-5.8.3' - disabled={!ios || (ios && os !== 'darwin')} - value={wdaRepoPath} - size='small' - fullWidth - /> - setSupervisionPassword(e.target.value)} - label='Supervision password' - id='outlined-required' - autoComplete='off' - helperText='Password for the supervision profile for iOS devices(leave empty if devices not supervised)' - disabled={!ios} - value={supervisionPassword} - size='small' - fullWidth - /> -
Use custom WebDriverAgent?
- -
Use Selenium Grid?
- - setSeleniumGrid(e.target.value)} - label='Selenium Grid' - required - id='outlined-required' - autoComplete='off' - helperText='Address of the Selenium Grid instance, e.g. http://192.168.1.28:4444' - disabled={!useSeleniumGrid} - value={seleniumGrid} - size='small' - fullWidth - /> - -
-
- - {showError && - {errorText} - } -
- - ) -} \ No newline at end of file diff --git a/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderDevice.js b/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderDevice.js deleted file mode 100644 index b30812e5..00000000 --- a/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderDevice.js +++ /dev/null @@ -1,97 +0,0 @@ -import { Box, Button, Stack } from "@mui/material"; -import { useContext, useEffect, useState } from "react"; -import { Auth } from "../../../../contexts/Auth"; -import { api } from '../../../../services/api.js' - -export default function ProviderDevice({ deviceInfo }) { - let img_src = deviceInfo.os === 'android' ? './images/android-logo.png' : './images/apple-logo.png' - const [statusColor, setStatusColor] = useState('red') - const [buttonDisabled, setButtonDisabled] = useState(false) - const { logout } = useContext(Auth) - - useEffect(() => { - if (deviceInfo.connected && deviceInfo.provider_state === 'live') { - setStatusColor('green') - } else if (deviceInfo.connected && deviceInfo.provider_state === 'preparing') { - setStatusColor('orange') - } else { - setStatusColor('red') - } - if (deviceInfo.provider_state !== 'init') { - setButtonDisabled(false) - } else { - setButtonDisabled(true) - } - }) - - function handleResetClick() { - let url = `/device/${deviceInfo.udid}/reset` - - api.post(url) - .catch(error => { - if (error.response) { - if (error.response.status === 401) { - logout() - } - } - }) - } - - return ( - - -
- -
-
-
-
UDID
-
{deviceInfo.udid}
-
Last provider state: {deviceInfo.provider_state}
-
Name: {deviceInfo.name}
-
Width: {deviceInfo.screen_width}
-
Height: {deviceInfo.screen_height}
- -
- ) -} - -function OSImage({ img_src }) { - return ( - OS logo - ) -} \ No newline at end of file diff --git a/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderInfo.js b/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderInfo.js deleted file mode 100644 index 50be7016..00000000 --- a/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderInfo.js +++ /dev/null @@ -1,64 +0,0 @@ -import { Box, Stack } from "@mui/material" -import { useEffect, useState } from "react" - -export default function ProviderInfo({ os, isOnline }) { - const [statusColor, setStatusColor] = useState('') - const [status, setStatus] = useState('') - const logoPath = `./images/${os}-logo.png` - - useEffect(() => { - if (isOnline) { - setStatus('Online') - setStatusColor('green') - } else { - setStatus('Offline') - setStatusColor('red') - } - }, [isOnline]) - - return ( - - - - ) -} - -function Status({ logoPath, statusColor }) { - return ( - - -
Status
-
-
- ) -} \ No newline at end of file diff --git a/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderLogsTable/ProviderLogsTable.js b/hub/gads-ui/src/components/Admin/Providers/ProviderLogsTable.js similarity index 82% rename from hub/gads-ui/src/components/Admin/Providers/Provider/ProviderLogsTable/ProviderLogsTable.js rename to hub/gads-ui/src/components/Admin/Providers/ProviderLogsTable.js index 1c66369a..1432238b 100644 --- a/hub/gads-ui/src/components/Admin/Providers/Provider/ProviderLogsTable/ProviderLogsTable.js +++ b/hub/gads-ui/src/components/Admin/Providers/ProviderLogsTable.js @@ -1,5 +1,5 @@ import { useContext, useState } from "react"; -import {Auth} from "../../../../../contexts/Auth"; +import { Auth } from "../../../contexts/Auth.js"; import { Box, Button, @@ -10,13 +10,14 @@ import { TableBody, TableCell, TableContainer, TableFooter, TablePagination, TableRow, + Tooltip, useTheme } from "@mui/material"; import FirstPageIcon from '@mui/icons-material/FirstPage'; import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft'; import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'; import LastPageIcon from '@mui/icons-material/LastPage'; -import { api } from '../../../../../services/api.js' +import { api } from '../../../services/api.js' function TablePaginationActions(props) { const theme = useTheme(); @@ -92,6 +93,7 @@ export default function ProviderLogsTable({ nickname }) { api.get(url) .then(response => { setLogData(response.data) + setPage(0) }) .catch(error => { if (error.response) { @@ -104,7 +106,7 @@ export default function ProviderLogsTable({ nickname }) { } return ( -
+