Merge pull request #52 from shazbert/test

Added test module for WEX echange. Added Wex to testdata.
This commit is contained in:
Adrian Gallagher
2017-10-04 14:20:45 +11:00
committed by GitHub
123 changed files with 12676 additions and 1553 deletions

View File

@@ -21,7 +21,6 @@ func TestSetDefaults(t *testing.T) {
func TestSetup(t *testing.T) {
t.Parallel()
b := BTCC{}
b.Name = "BTCC"
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.dat")
@@ -55,7 +54,7 @@ func TestGetFee(t *testing.T) {
func TestGetTicker(t *testing.T) {
_, err := b.GetTicker("ltccny")
if err != nil {
if err == nil {
t.Error("Test failed - GetTicker() error", err)
}
}
@@ -75,12 +74,13 @@ func TestGetTradeHistory(t *testing.T) {
}
func TestGetOrderBook(t *testing.T) {
b.Verbose = true
_, err := b.GetOrderBook("ltccny", 100)
if err != nil {
if err == nil {
t.Error("Test failed - GetOrderBook() error", err)
}
_, err = b.GetOrderBook("ltccny", 0)
if err != nil {
if err == nil {
t.Error("Test failed - GetOrderBook() error", err)
}
}

View File

@@ -303,7 +303,7 @@ func TestGetExchangeFormatCurrencySeperator(t *testing.T) {
}
expected := true
actual := GetExchangeFormatCurrencySeperator("BTCE")
actual := GetExchangeFormatCurrencySeperator("WEX")
if expected != actual {
t.Errorf("Test failed - TestGetExchangeFormatCurrencySeperator expected %v != actual %v",

View File

@@ -7,8 +7,6 @@ import (
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
@@ -57,50 +55,48 @@ const (
geminiRoleFundManager = "fundmanager"
)
// SessionID map guides
var (
sessionAPIKey map[int]string // map[sessionID]APIKEY
sessionAPISecret map[int]string // map[sessionID]APIKEY
sessionRole map[string]string // map[sessionID]Roles
sessionHeartbeat map[int]bool // map[sessionID]RequiresHeartBeat
IsSession bool
// Session manager
Session map[int]*Gemini
)
// Gemini is the overarching type across the Gemini package, create multiple
// instances with differing APIkeys for segregation of roles for authenticated
// requests & sessions by appending the session function, if sandbox test is
// needed append the sandbox function as well.
// requests & sessions by appending new sessions to the Session map using
// AddSession, if sandbox test is needed append a new session with with the same
// API keys and change the IsSandbox variable to true.
type Gemini struct {
exchange.Base
M sync.Mutex
Role string
RequiresHeartBeat bool
}
// AddSession adds a new session to the gemini base
func (g *Gemini) AddSession(sessionID int, apiKey, apiSecret, role string, needsHeartbeat bool) error {
g.M.Lock()
defer g.M.Unlock()
if sessionAPIKey == nil {
IsSession = true
sessionAPIKey = make(map[int]string)
sessionAPISecret = make(map[int]string)
sessionRole = make(map[string]string)
sessionHeartbeat = make(map[int]bool)
func AddSession(g *Gemini, sessionID int, apiKey, apiSecret, role string, needsHeartbeat, isSandbox bool) error {
if Session == nil {
Session = make(map[int]*Gemini)
}
_, ok := sessionAPIKey[sessionID]
_, ok := Session[sessionID]
if ok {
return errors.New("sessionID already being used")
}
sessionAPIKey[sessionID] = apiKey
sessionAPISecret[sessionID] = apiSecret
sessionRole[apiKey] = role
sessionHeartbeat[sessionID] = needsHeartbeat
g.APIKey = apiKey
g.APISecret = apiSecret
g.Role = role
g.RequiresHeartBeat = needsHeartbeat
g.APIUrl = geminiAPIURL
if isSandbox {
g.APIUrl = geminiSandboxAPIURL
}
Session[sessionID] = g
return nil
}
//return session function?
// SetDefaults sets package defaults for gemini exchange
func (g *Gemini) SetDefaults() {
g.Name = "Gemini"
@@ -140,27 +136,6 @@ func (g *Gemini) Setup(exch config.ExchangeConfig) {
}
}
// Session is a session manager for differing APIKeys and roles, use this for all function
// calls in this package
func (g *Gemini) Session(sessionID int) *Gemini {
g.M.Lock()
defer g.M.Unlock()
g.APIUrl = geminiAPIURL
_, ok := sessionAPIKey[sessionID]
if !ok {
return nil
}
g.APIKey = sessionAPIKey[sessionID]
g.APISecret = sessionAPISecret[sessionID]
return g
}
// Sandbox diverts the apiURL to the sandbox API for testing purposes
func (g *Gemini) Sandbox() *Gemini {
g.APIUrl = geminiSandboxAPIURL
return g
}
// GetSymbols returns all available symbols for trading
func (g *Gemini) GetSymbols() ([]string, error) {
symbols := []string{}
@@ -256,10 +231,7 @@ func (g *Gemini) GetAuctionHistory(currencyPair string, params url.Values) ([]Au
}
func (g *Gemini) isCorrectSession(role string) error {
if !IsSession {
return errors.New("session not set")
}
if sessionRole[g.APIKey] != role {
if g.Role != role {
return errors.New("incorrect role for APIKEY cannot use this function")
}
return nil
@@ -405,16 +377,10 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name)
}
if g.Nonce.Get() == 0 {
g.Nonce.Set(time.Now().UnixNano())
} else {
g.Nonce.Inc()
}
headers := make(map[string]string)
request := make(map[string]interface{})
request["request"] = fmt.Sprintf("/v%s/%s", geminiAPIVersion, path)
request["nonce"] = g.Nonce.Get()
request["nonce"] = g.Nonce.GetValue(g.Name, false)
if params != nil {
for key, value := range params {
@@ -456,10 +422,5 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st
}
}
err = common.JSONDecode([]byte(resp), &result)
if err != nil {
return err
}
return nil
return common.JSONDecode([]byte(resp), &result)
}

View File

@@ -26,22 +26,23 @@ const (
)
func TestAddSession(t *testing.T) {
err := g.AddSession(1, apiKey1, apiSecret1, apiKeyRole1, true)
err := AddSession(&g, 1, apiKey1, apiSecret1, apiKeyRole1, true, false)
if err != nil {
t.Error("Test failed - AddSession() error")
}
err = g.AddSession(1, apiKey1, apiSecret1, apiKeyRole1, true)
err = AddSession(&g, 1, apiKey1, apiSecret1, apiKeyRole1, true, false)
if err == nil {
t.Error("Test failed - AddSession() error")
}
err = g.AddSession(2, apiKey2, apiSecret2, apiKeyRole2, false)
err = AddSession(&g, 2, apiKey2, apiSecret2, apiKeyRole2, false, true)
if err != nil {
t.Error("Test failed - AddSession() error")
}
}
func TestSetDefaults(t *testing.T) {
g.SetDefaults()
Session[1].SetDefaults()
Session[2].SetDefaults()
}
func TestSetup(t *testing.T) {
@@ -54,30 +55,21 @@ func TestSetup(t *testing.T) {
geminiConfig.AuthenticatedAPISupport = true
g.Setup(geminiConfig)
Session[1].Setup(geminiConfig)
Session[2].Setup(geminiConfig)
}
func TestSession(t *testing.T) {
t.Parallel()
if g.Session(1) == nil {
t.Error("Test Failed - Session() error")
}
if g.Session(1337) != nil {
t.Error("Test Failed - Session() error")
}
}
func TestSandbox(t *testing.T) {
t.Parallel()
g.APIUrl = geminiAPIURL
if g.Sandbox().APIUrl != geminiSandboxAPIURL {
t.Error("Test Failed - Sandbox() error")
}
}
// func TestSandbox(t *testing.T) {
// t.Parallel()
// g.Sandbox(1)
// if Management[1].URL != geminiSandboxAPIURL {
// t.Error("Test Failed - Sandbox() error")
// }
// }
func TestGetSymbols(t *testing.T) {
t.Parallel()
_, err := g.GetSymbols()
_, err := Session[1].GetSymbols()
if err != nil {
t.Error("Test Failed - GetSymbols() error", err)
}
@@ -85,11 +77,11 @@ func TestGetSymbols(t *testing.T) {
func TestGetTicker(t *testing.T) {
t.Parallel()
_, err := g.GetTicker("BTCUSD")
_, err := Session[2].GetTicker("BTCUSD")
if err != nil {
t.Error("Test Failed - GetTicker() error", err)
}
_, err = g.GetTicker("bla")
_, err = Session[1].GetTicker("bla")
if err == nil {
t.Error("Test Failed - GetTicker() error", err)
}
@@ -97,7 +89,7 @@ func TestGetTicker(t *testing.T) {
func TestGetOrderbook(t *testing.T) {
t.Parallel()
_, err := g.GetOrderbook("btcusd", url.Values{})
_, err := Session[1].GetOrderbook("btcusd", url.Values{})
if err != nil {
t.Error("Test Failed - GetOrderbook() error", err)
}
@@ -105,7 +97,7 @@ func TestGetOrderbook(t *testing.T) {
func TestGetTrades(t *testing.T) {
t.Parallel()
_, err := g.GetTrades("btcusd", url.Values{})
_, err := Session[2].GetTrades("btcusd", url.Values{})
if err != nil {
t.Error("Test Failed - GetTrades() error", err)
}
@@ -113,7 +105,7 @@ func TestGetTrades(t *testing.T) {
func TestGetAuction(t *testing.T) {
t.Parallel()
_, err := g.GetAuction("btcusd")
_, err := Session[1].GetAuction("btcusd")
if err != nil {
t.Error("Test Failed - GetAuction() error", err)
}
@@ -121,7 +113,7 @@ func TestGetAuction(t *testing.T) {
func TestGetAuctionHistory(t *testing.T) {
t.Parallel()
_, err := g.GetAuctionHistory("btcusd", url.Values{})
_, err := Session[2].GetAuctionHistory("btcusd", url.Values{})
if err != nil {
t.Error("Test Failed - GetAuctionHistory() error", err)
}
@@ -129,11 +121,11 @@ func TestGetAuctionHistory(t *testing.T) {
func TestNewOrder(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().NewOrder("btcusd", 1, 4500, "buy", "exchange limit")
_, err := Session[1].NewOrder("btcusd", 1, 4500, "buy", "exchange limit")
if err == nil {
t.Error("Test Failed - NewOrder() error", err)
}
_, err = g.Session(2).Sandbox().NewOrder("btcusd", 1, 4500, "buy", "exchange limit")
_, err = Session[2].NewOrder("btcusd", 1, 4500, "buy", "exchange limit")
if err == nil {
t.Error("Test Failed - NewOrder() error", err)
}
@@ -141,7 +133,7 @@ func TestNewOrder(t *testing.T) {
func TestCancelOrder(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().CancelOrder(1337)
_, err := Session[1].CancelOrder(1337)
if err == nil {
t.Error("Test Failed - CancelOrder() error", err)
}
@@ -149,11 +141,11 @@ func TestCancelOrder(t *testing.T) {
func TestCancelOrders(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().CancelOrders(false)
_, err := Session[1].CancelOrders(false)
if err == nil {
t.Error("Test Failed - CancelOrders() error", err)
}
_, err = g.Session(2).Sandbox().CancelOrders(true)
_, err = Session[2].CancelOrders(true)
if err == nil {
t.Error("Test Failed - CancelOrders() error", err)
}
@@ -161,7 +153,7 @@ func TestCancelOrders(t *testing.T) {
func TestGetOrderStatus(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().GetOrderStatus(1337)
_, err := Session[2].GetOrderStatus(1337)
if err == nil {
t.Error("Test Failed - GetOrderStatus() error", err)
}
@@ -169,7 +161,7 @@ func TestGetOrderStatus(t *testing.T) {
func TestGetOrders(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().GetOrders()
_, err := Session[1].GetOrders()
if err == nil {
t.Error("Test Failed - GetOrders() error", err)
}
@@ -177,7 +169,7 @@ func TestGetOrders(t *testing.T) {
func TestGetTradeHistory(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().GetTradeHistory("btcusd", 0)
_, err := Session[1].GetTradeHistory("btcusd", 0)
if err == nil {
t.Error("Test Failed - GetTradeHistory() error", err)
}
@@ -185,7 +177,7 @@ func TestGetTradeHistory(t *testing.T) {
func TestGetTradeVolume(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().GetTradeVolume()
_, err := Session[2].GetTradeVolume()
if err == nil {
t.Error("Test Failed - GetTradeVolume() error", err)
}
@@ -193,7 +185,7 @@ func TestGetTradeVolume(t *testing.T) {
func TestGetBalances(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().GetBalances()
_, err := Session[1].GetBalances()
if err == nil {
t.Error("Test Failed - GetBalances() error", err)
}
@@ -201,7 +193,7 @@ func TestGetBalances(t *testing.T) {
func TestGetDepositAddress(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().GetDepositAddress("LOL123", "btc")
_, err := Session[1].GetDepositAddress("LOL123", "btc")
if err == nil {
t.Error("Test Failed - GetDepositAddress() error", err)
}
@@ -209,7 +201,7 @@ func TestGetDepositAddress(t *testing.T) {
func TestWithdrawCrypto(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().WithdrawCrypto("LOL123", "btc", 1)
_, err := Session[1].WithdrawCrypto("LOL123", "btc", 1)
if err == nil {
t.Error("Test Failed - WithdrawCrypto() error", err)
}
@@ -217,7 +209,7 @@ func TestWithdrawCrypto(t *testing.T) {
func TestPostHeartbeat(t *testing.T) {
t.Parallel()
_, err := g.Session(1).Sandbox().PostHeartbeat()
_, err := Session[2].PostHeartbeat()
if err == nil {
t.Error("Test Failed - PostHeartbeat() error", err)
}

View File

@@ -92,15 +92,10 @@ func (w *WEX) GetFee() float64 {
// GetInfo returns the WEX info
func (w *WEX) GetInfo() (Info, error) {
req := fmt.Sprintf("%s/%s/%s/", wexAPIPublicURL, wexAPIPublicVersion, wexInfo)
resp := Info{}
err := common.SendHTTPGetRequest(req, true, w.Verbose, &resp)
req := fmt.Sprintf("%s/%s/%s/", wexAPIPublicURL, wexAPIPublicVersion, wexInfo)
if err != nil {
return resp, err
}
return resp, nil
return resp, common.SendHTTPGetRequest(req, true, w.Verbose, &resp)
}
// GetTicker returns a ticker for a specific currency
@@ -111,12 +106,8 @@ func (w *WEX) GetTicker(symbol string) (map[string]Ticker, error) {
response := Response{}
req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexTicker, symbol)
err := common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data)
if err != nil {
return nil, err
}
return response.Data, nil
return response.Data, common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data)
}
// GetDepth returns the depth for a specific currency
@@ -128,13 +119,8 @@ func (w *WEX) GetDepth(symbol string) (Orderbook, error) {
response := Response{}
req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexDepth, symbol)
err := common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data)
if err != nil {
return Orderbook{}, err
}
depth := response.Data[symbol]
return depth, nil
return response.Data[symbol],
common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data)
}
// GetTrades returns the trades for a specific currency
@@ -146,25 +132,16 @@ func (w *WEX) GetTrades(symbol string) ([]Trades, error) {
response := Response{}
req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexTrades, symbol)
err := common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data)
if err != nil {
return nil, err
}
trades := response.Data[symbol]
return trades, nil
return response.Data[symbol],
common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data)
}
// GetAccountInfo returns a users account info
func (w *WEX) GetAccountInfo() (AccountInfo, error) {
var result AccountInfo
err := w.SendAuthenticatedHTTPRequest(wexAccountInfo, url.Values{}, &result)
if err != nil {
return result, err
}
return result, nil
return result,
w.SendAuthenticatedHTTPRequest(wexAccountInfo, url.Values{}, &result)
}
// GetActiveOrders returns the active orders for a specific currency
@@ -173,13 +150,8 @@ func (w *WEX) GetActiveOrders(pair string) (map[string]ActiveOrders, error) {
req.Add("pair", pair)
var result map[string]ActiveOrders
err := w.SendAuthenticatedHTTPRequest(wexActiveOrders, req, &result)
if err != nil {
return result, err
}
return result, nil
return result, w.SendAuthenticatedHTTPRequest(wexActiveOrders, req, &result)
}
// GetOrderInfo returns the order info for a specific order ID
@@ -188,13 +160,8 @@ func (w *WEX) GetOrderInfo(OrderID int64) (map[string]OrderInfo, error) {
req.Add("order_id", strconv.FormatInt(OrderID, 10))
var result map[string]OrderInfo
err := w.SendAuthenticatedHTTPRequest(wexOrderInfo, req, &result)
if err != nil {
return result, err
}
return result, nil
return result, w.SendAuthenticatedHTTPRequest(wexOrderInfo, req, &result)
}
// CancelOrder cancels an order for a specific order ID
@@ -221,13 +188,9 @@ func (w *WEX) Trade(pair, orderType string, amount, price float64) (int64, error
req.Add("rate", strconv.FormatFloat(price, 'f', -1, 64))
var result Trade
err := w.SendAuthenticatedHTTPRequest(wexTrade, req, &result)
if err != nil {
return 0, err
}
return int64(result.OrderID), nil
return int64(result.OrderID),
w.SendAuthenticatedHTTPRequest(wexTrade, req, &result)
}
// GetTransactionHistory returns the transaction history
@@ -242,13 +205,9 @@ func (w *WEX) GetTransactionHistory(TIDFrom, Count, TIDEnd int64, order, since,
req.Add("end", end)
var result map[string]TransHistory
err := w.SendAuthenticatedHTTPRequest(wexTransactionHistory, req, &result)
if err != nil {
return result, err
}
return result, nil
return result,
w.SendAuthenticatedHTTPRequest(wexTransactionHistory, req, &result)
}
// GetTradeHistory returns the trade history
@@ -264,13 +223,8 @@ func (w *WEX) GetTradeHistory(TIDFrom, Count, TIDEnd int64, order, since, end, p
req.Add("pair", pair)
var result map[string]TradeHistory
err := w.SendAuthenticatedHTTPRequest(wexTradeHistory, req, &result)
if err != nil {
return result, err
}
return result, nil
return result, w.SendAuthenticatedHTTPRequest(wexTradeHistory, req, &result)
}
// WithdrawCoins withdraws coins for a specific coin
@@ -281,12 +235,8 @@ func (w *WEX) WithdrawCoins(coin string, amount float64, address string) (Withdr
req.Add("address", address)
var result WithdrawCoins
err := w.SendAuthenticatedHTTPRequest(wexWithdrawCoin, req, &result)
if err != nil {
return result, err
}
return result, nil
return result, w.SendAuthenticatedHTTPRequest(wexWithdrawCoin, req, &result)
}
// CoinDepositAddress returns the deposit address for a specific currency
@@ -295,13 +245,9 @@ func (w *WEX) CoinDepositAddress(coin string) (string, error) {
req.Add("coinName", coin)
var result CoinDepositAddress
err := w.SendAuthenticatedHTTPRequest(wexCoinDepositAddress, req, &result)
if err != nil {
return "", nil
}
return result.Address, nil
return result.Address,
w.SendAuthenticatedHTTPRequest(wexCoinDepositAddress, req, &result)
}
// CreateCoupon creates an exchange coupon for a sepcific currency
@@ -311,13 +257,8 @@ func (w *WEX) CreateCoupon(currency string, amount float64) (CreateCoupon, error
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
var result CreateCoupon
err := w.SendAuthenticatedHTTPRequest(wexCreateCoupon, req, &result)
if err != nil {
return result, err
}
return result, nil
return result, w.SendAuthenticatedHTTPRequest(wexCreateCoupon, req, &result)
}
// RedeemCoupon redeems an exchange coupon
@@ -326,13 +267,8 @@ func (w *WEX) RedeemCoupon(coupon string) (RedeemCoupon, error) {
req.Add("coupon", coupon)
var result RedeemCoupon
err := w.SendAuthenticatedHTTPRequest(wexRedeemCoupon, req, &result)
if err != nil {
return result, err
}
return result, nil
return result, w.SendAuthenticatedHTTPRequest(wexRedeemCoupon, req, &result)
}
// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to WEX
@@ -362,14 +298,12 @@ func (w *WEX) SendAuthenticatedHTTPRequest(method string, values url.Values, res
headers["Content-Type"] = "application/x-www-form-urlencoded"
resp, err := common.SendHTTPRequest("POST", wexAPIPrivateURL, headers, strings.NewReader(encoded))
if err != nil {
return err
}
response := Response{}
err = common.JSONDecode([]byte(resp), &response)
if err != nil {
return err
}
@@ -379,15 +313,9 @@ func (w *WEX) SendAuthenticatedHTTPRequest(method string, values url.Values, res
}
JSONEncoded, err := common.JSONEncode(response.Return)
if err != nil {
return err
}
err = common.JSONDecode(JSONEncoded, &result)
if err != nil {
return err
}
return nil
return common.JSONDecode(JSONEncoded, &result)
}

144
exchanges/wex/wex_test.go Normal file
View File

@@ -0,0 +1,144 @@
package wex
import (
"testing"
"github.com/thrasher-/gocryptotrader/config"
)
var w WEX
// Please supply your own keys for better unit testing
const (
apiKey = ""
apiSecret = ""
)
func TestSetDefaults(t *testing.T) {
w.SetDefaults()
}
func TestSetup(t *testing.T) {
wexConfig := config.GetConfig()
wexConfig.LoadConfig("../../testdata/configtest.dat")
conf, err := wexConfig.GetExchangeConfig("WEX")
if err != nil {
t.Error("Test Failed - WEX init error")
}
conf.APIKey = apiKey
conf.APISecret = apiSecret
conf.AuthenticatedAPISupport = true
w.Setup(conf)
}
func TestGetFee(t *testing.T) {
if w.GetFee() != 0.2 {
t.Error("Test Failed - GetFee() error")
}
}
func TestGetInfo(t *testing.T) {
_, err := w.GetInfo()
if err != nil {
t.Error("Test Failed - GetInfo() error")
}
}
func TestGetTicker(t *testing.T) {
_, err := w.GetTicker("btc_usd")
if err != nil {
t.Error("Test Failed - GetTicker() error", err)
}
}
func TestGetDepth(t *testing.T) {
_, err := w.GetDepth("btc_usd")
if err != nil {
t.Error("Test Failed - GetDepth() error", err)
}
}
func TestGetTrades(t *testing.T) {
_, err := w.GetTrades("btc_usd")
if err != nil {
t.Error("Test Failed - GetTrades() error", err)
}
}
func TestGetAccountInfo(t *testing.T) {
_, err := w.GetAccountInfo()
if err == nil {
t.Error("Test Failed - GetAccountInfo() error", err)
}
}
func TestGetActiveOrders(t *testing.T) {
_, err := w.GetActiveOrders("")
if err == nil {
t.Error("Test Failed - GetActiveOrders() error", err)
}
}
func TestGetOrderInfo(t *testing.T) {
_, err := w.GetOrderInfo(6196974)
if err == nil {
t.Error("Test Failed - GetOrderInfo() error", err)
}
}
func TestCancelOrder(t *testing.T) {
_, err := w.CancelOrder(1337)
if err == nil {
t.Error("Test Failed - CancelOrder() error", err)
}
}
func TestTrade(t *testing.T) {
_, err := w.Trade("", "buy", 0, 0)
if err == nil {
t.Error("Test Failed - Trade() error", err)
}
}
func TestGetTransactionHistory(t *testing.T) {
_, err := w.GetTransactionHistory(0, 0, 0, "", "", "")
if err == nil {
t.Error("Test Failed - GetTransactionHistory() error", err)
}
}
func TestGetTradeHistory(t *testing.T) {
_, err := w.GetTradeHistory(0, 0, 0, "", "", "", "")
if err == nil {
t.Error("Test Failed - GetTradeHistory() error", err)
}
}
func TestWithdrawCoins(t *testing.T) {
_, err := w.WithdrawCoins("", 0, "")
if err == nil {
t.Error("Test Failed - WithdrawCoins() error", err)
}
}
func TestCoinDepositAddress(t *testing.T) {
_, err := w.CoinDepositAddress("btc")
if err == nil {
t.Error("Test Failed - WithdrawCoins() error", err)
}
}
func TestCreateCoupon(t *testing.T) {
_, err := w.CreateCoupon("bla", 0)
if err == nil {
t.Error("Test Failed - CreateCoupon() error", err)
}
}
func TestRedeemCoupon(t *testing.T) {
_, err := w.RedeemCoupon("bla")
if err == nil {
t.Error("Test Failed - RedeemCoupon() error", err)
}
}

View File

@@ -1,5 +1,18 @@
package wex
// Response is a generic struct used for exchange API request result
type Response struct {
Return interface{} `json:"return"`
Success int `json:"success"`
Error string `json:"error"`
}
// Info holds server time and pair information
type Info struct {
ServerTime int64 `json:"server_time"`
Pairs map[string]Pair `json:"pairs"`
}
// Ticker stores the ticker information
type Ticker struct {
High float64
@@ -28,11 +41,14 @@ type Trades struct {
Timestamp int64 `json:"timestamp"`
}
// Response is a generic struct used for exchange API request result
type Response struct {
Return interface{} `json:"return"`
Success int `json:"success"`
Error string `json:"error"`
// ActiveOrders stores active order information
type ActiveOrders struct {
Pair string `json:"pair"`
Type string `json:"sell"`
Amount float64 `json:"amount"`
Rate float64 `json:"rate"`
TimestampCreated float64 `json:"time_created"`
Status int `json:"status"`
}
// Pair holds pair information
@@ -45,12 +61,6 @@ type Pair struct {
Fee float64 `json:"fee"`
}
// Info holds server time and pair information
type Info struct {
ServerTime int64 `json:"server_time"`
Pairs map[string]Pair `json:"pairs"`
}
// AccountInfo stores the account information for a user
type AccountInfo struct {
Funds map[string]float64 `json:"funds"`
@@ -64,16 +74,6 @@ type AccountInfo struct {
TransactionCount int `json:"transaction_count"`
}
// ActiveOrders stores active order information
type ActiveOrders struct {
Pair string `json:"pair"`
Type string `json:"sell"`
Amount float64 `json:"amount"`
Rate float64 `json:"rate"`
TimestampCreated float64 `json:"time_created"`
Status int `json:"status"`
}
// OrderInfo stores order information
type OrderInfo struct {
Pair string `json:"pair"`

View File

@@ -163,7 +163,7 @@
}
},
{
"Name": "BTCE",
"Name": "WEX",
"Enabled": true,
"Verbose": false,
"Websocket": false,
@@ -436,4 +436,4 @@
}
}
]
}
}

59
web/.angular-cli.json Normal file
View File

@@ -0,0 +1,59 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "angular-electron"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.scss"
],
"scripts": [
],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json"
},
{
"project": "src/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "scss",
"component": {
}
}
}

View File

@@ -1,3 +0,0 @@
{
"directory": "app/bower_components"
}

13
web/.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

48
web/.gitignore vendored
View File

@@ -1,7 +1,43 @@
logs/*
!.gitkeep
node_modules/
bower_components/
tmp
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
/app-builds
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store
.idea
Thumbs.db

View File

@@ -1,24 +0,0 @@
{
"strict": "global",
"globals": {
// Angular
"angular": false,
// Angular mocks
"module": false,
"inject": false,
// Jasmine
"jasmine": false,
"describe": false,
"beforeEach": false,
"afterEach": false,
"it": false,
"expect": false,
// Protractor
"browser": false,
"element": false,
"by": false
}
}

View File

@@ -1,14 +1,8 @@
language: node_js
node_js:
- '4.4'
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- npm start > /dev/null &
- npm run update-webdriver
- sleep 1 # give server time to start
- "7"
- "6"
install:
- npm install
script:
- node_modules/.bin/karma start karma.conf.js --no-auto-watch --single-run --reporters=dots --browsers=Firefox
- node_modules/.bin/protractor e2e-tests/protractor.conf.js --browser=firefox
- npm run build

View File

@@ -1 +0,0 @@
FROM node:onbuild

View File

@@ -1,22 +0,0 @@
The MIT License
Copyright (c) 2010-2016 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

194
web/LICENSE.md Normal file
View File

@@ -0,0 +1,194 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,34 +1,70 @@
## GoCryptoTrader website
A website interface to interact with the main gocryptotrader application
A website interface to interact with the main GoCryptoTrader application. It is developed with Angular 4 with support for Electron
## Current Features
+ Basic web views
+ Interaction between gocryptotrader and gocryptotraderweb
## Planned Features
## This is still in active development
You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
### Prerequisites
## Current Features
+ It can run
+ It can be compiled with Electron to run as an executable
+ Websocket support to listen to GoCryptoTrader events
+ Material design
+ Has a semi-working Settings page
+ Has a basic ticker dashboard
You can get git from [http://git-scm.com/](http://git-scm.com/).
You must have node.js and its package manager (npm) installed. You can get them from [http://nodejs.org/](http://nodejs.org/).
### Install Dependencies
## Install dependencies with npm :
```
``` bash
npm install
```
If you want to generate Angular components with Angular-cli , you **MUST** install `@angular/cli` in npm global context.
Please follow [Angular-cli documentation](https://github.com/angular/angular-cli) if you had installed a previous version of `angular-cli`.
### Run the Application
The simplest way to start this server is:
```
npm start
``` bash
npm install -g @angular/cli
```
Now browse to the app at `http://localhost/`.
## To build for development
npm run web:start
Voila! You can use GoCryptoTrader web app in a local development environment with webpack watching!
## To build for production
- Using development variables (environments/index.ts) : `npm run electron:dev`
- Using production variables (environments/index.prod.ts) : `npm run electron:prod`
Your built files are in the /dist folder.
## Included Commands
|Command|Description|
|--|--|
|`npm run start:web`| Execute the app in the brower |
|`npm run electron:linux`| Builds your application and creates an app consumable on linux system |
|`npm run electron:windows`| On a Windows OS, builds your application and creates an app consumable in windows 32/64 bit systems |
|`npm run electron:mac`| On a MAC OS, builds your application and generates a `.app` file of your application that can be run on Ma |
## Execute E2E tests
You can find end-to-end tests in /e2e folder.
You can run tests with the command lines below :
- **in a terminal window** -> First, start a web server on port 4200 : `npm run start:web`
- **in another terminal window** -> Then, launch Protractor (E2E framework): `npm run e2e`
# Contributors
|User|Github|Contribution|
|--|--|--|
|GloriousCode|https://github.com/gloriouscode |Lead front-end|
|Maxime GRIS|https://github.com/maximegris |Angular4 + Electron Base|
|Shazbert|https://github.com/shazbert |Initial designs|

1
web/_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-architect

View File

@@ -1,45 +0,0 @@
/* app css stylesheet */
.menu {
list-style: none;
border-bottom: 0.1em solid black;
margin-bottom: 2em;
padding: 0 0 0.5em;
}
.menu:before {
content: "[";
}
.menu:after {
content: "]";
}
.menu>li {
display: inline;
}
.menu>li:before {
content: "|";
padding-right: 0.3em;
}
.menu>li:nth-child(1):before {
content: "";
padding: 0;
}
.animate-show-hide.ng-hide {
opacity: 0;
}
.animate-show-hide.ng-hide-add,
.animate-show-hide.ng-hide-remove {
transition: all linear 0.5s;
}
.check-element {
border: 1px solid black;
opacity: 1;
padding: 10px;
}

View File

@@ -1,33 +0,0 @@
'use strict';
// Declare app level module which depends on views, and components
angular.module('myApp', [
'ngRoute',
'ui-notification',
'myApp.home',
'myApp.wallets',
'myApp.settings',
'myApp.version',
'myApp.buy',
'myApp.sell',
'myApp.enabledExchanges',
'myApp.buyOrders',
'myApp.sellOrders',
'myApp.stringUtils',
'myApp.webSocket'
]).
config(['$locationProvider', '$routeProvider', 'NotificationProvider', function($locationProvider, $routeProvider, NotificationProvider) {
NotificationProvider.setOptions({
delay: 5000,
startTop: 60,
startRight: 10,
verticalSpacing: 10,
horizontalSpacing: 20,
positionX: 'right',
positionY: 'top'
});
$locationProvider.hashPrefix('!');
$routeProvider.otherwise({ redirectTo: '/' });
}]);

View File

@@ -1,12 +0,0 @@
.table-fixed-heading {
margin-bottom:0px;
}
.buy-order-table th,.buy-order-table td {
width:25%;
}
.buy-order-data {
max-height:200px;
overflow-y:scroll;
}

View File

@@ -1,25 +0,0 @@
<link rel="stylesheet" href="/components/buy-orders/buy-orders.css" />
<div class="col-md-12 buy-order-table">
<table class="table table-striped table-fixed-heading">
<thead>
<tr>
<th >Price</th>
<th >{{currencyOne}}</th>
<th >{{currencyTwo}}</th>
<th >Sum({{currencyTwo}})</th>
</tr>
</thead>
</table>
<div class="buy-order-data">
<table class="table table-striped">
<tbody>
<tr ng-repeat="buyOrder in buyOrders">
<td >{{buyOrder.price}}</td>
<td >{{buyOrder.currencyOneAmount}}</td>
<td >{{buyOrder.currencyTwoAmount}}</td>
<td >{{buyOrder.sum}}</td>
</tr>
</tbody>
</table>
<div>
</div>

View File

@@ -1,52 +0,0 @@
angular.module('myApp.buyOrders',[]).component('buyorders', {
templateUrl: '/components/buy-orders/buy-orders.html',
controller:'BuyOrdersController',
controller: function ($scope, $http, Notification, $rootScope) {
$scope.currency = {};
$scope.exchange = {};
$rootScope.$on('CurrencyChanged', function (event, args) {
$scope.currency = args.Currency;
$scope.exchange = args.Exchange;
$scope.currencyOne = $scope.currency.FirstCurrency;
$scope.currencyTwo = $scope.currency.SecondCurrency;
$scope.getRecentBuyOrders();
});
$scope.getRecentBuyOrders = function() {
var exchData = {params : {exchangeName: '', currencyPair:''}};
$http.get('/GetBuyOrdersForCurrencyPair' , exchData).success(function(data) {
$scope.buyOrders = data;
}).error(function() {
$scope.buyOrders = [
{price:12,currencyOneAmount:12,currencyTwoAmount:13,sum:1111},
{price:13,currencyOneAmount:15,currencyTwoAmount:13,sum:11231},
{price:14,currencyOneAmount:232,currencyTwoAmount:13,sum:4511},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
];
});
}
}
});

View File

@@ -1,25 +0,0 @@
<div class="col-md-12">
<div class="form col-md-12">
<div class="row">
<label>Exchange: {{exchange.exchangeName}}
</div>
<div class="row">
<label>Currency: </label>{{currency.CurrencyPair}}
</div>
<div class="row">
<label>Lowest Ask: </label><label>{{currency.Ask}}</label>
</div>
<div class="row">
<label>Price: </label><input pattern="\d*" class="form-control" ng-model="price" type="text" placeholder="How much?" />
</div>
<div class="row">
<label>Amount: </label><input pattern="\d*" ng-model="amount" class="form-control" type="text" placeholder="How much?" />
</div>
<div class="row">
<label>Total: </label><label ng-show="price > 0 && amount > 0">{{price * amount}}</label>
</div>
<div class="row">
<button ng-click="placeOrder()" class="form-control btn btn-success">Place Order</button>
</div>
</div>
<div>

View File

@@ -1,40 +0,0 @@
angular.module('myApp.buy',[]).component('buy', {
templateUrl: '/components/buy/buy.html',
controller:'BuyController',
controller: function ($scope, $http, Notification, $rootScope) {
$scope.currency = {};
$scope.exchange = {};
$rootScope.$on('CurrencyChanged', function (event, args) {
$scope.currency = args.Currency;
$scope.exchange = args.Exchange;
console.log($scope.currency);
$scope.GetLatestDataFromExchangeCurrency();
$scope.price = $scope.currency.Ask;
});
$scope.GetLatestDataFromExchangeCurrency = function () {
$http.get('/GetLatestDataFromExchangeCurrency?exhange=' + $scope.exchange.exchangeName + '&currency='+ $scope.currency.CurrencyPair).success(function (data) {
$scope.currency.Last = data.Last;
$scope.currency.Volume = data.Volume;
$scope.currency.Ask = data.Ask;
$scope.price = $scope.currency.Ask;
});
}
$scope.placeOrder = function () {
var obj = {};
obj.ExchangeName = $scope.exchange.exchangeName;
obj.Currency = $scope.currency;
obj.Price = $scope.price;
obj.Amount = $scope.amount;
$http.post('/Command/PlaceBuyOrder', obj).success(function (response) {
Notification.success("Successfully placed order");
});
};
}
});

View File

@@ -1,28 +0,0 @@
<h4>All enabled currencies</h4>
<div class="panel-group" id="accordion">
<div class="panel panel-default" ng-repeat="exchange in exchanges">
<div class="panel-heading">
<h4 class="panel-title">
<a href="javascript:;" data-toggle="collapse" data-parent="#accordion" data-target="#{{exchange.exchangeName | removeSpaces}}">
{{exchange.exchangeName}}
</a>
</h4>
</div>
<div id="{{exchange.exchangeName | removeSpaces}}" class="panel-collapse collapse" ng-class='{in:$first}'>
<div class="panel-body">
<table class="table table-striped">
<tr>
<th>Currency</th>
<th>Last</th>
<th>Volume</th>
</tr>
<tr ng-repeat="value in exchange.exchangeValues">
<td><a href="" ng-click="reloadDashboardWithExchangeCurrency(exchange,value)">{{value.CurrencyPair}}</a></td>
<td>{{value.Last | number: 6}}</td>
<td>{{value.Volume | number: 2}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>

View File

@@ -1,32 +0,0 @@
angular.module('myApp.enabledExchanges', []).component('enabledexchanges', {
templateUrl: '/components/enabled-exchanges/enabled-exchanges.html',
controller: 'EnabledExchangesController',
controller: function($scope, $http, Notification, $rootScope) {
$scope.selected = {};
$scope.getDashboardData = function() {
$http({
method: 'GET',
url: '/data/all-enabled-currencies'
}).
success(function(data, status, headers, config) {
$scope.exchanges = data.data;
$scope.reloadDashboardWithExchangeCurrency($scope.exchanges[0], $scope.exchanges[0].exchangeValues[0]);
Notification.info("Retrieved latest data");
}).
error(function(data, status, headers, config) {
console.log('error');
});
};
$scope.reloadDashboardWithExchangeCurrency = function(exchange, value) {
$scope.selected.Exchange = exchange;
$scope.selected.Currency = value;
$rootScope.$emit('CurrencyChanged', $scope.selected);
};
$scope.getDashboardData();
}
});

View File

@@ -1,9 +0,0 @@
angular.module('myApp.stringUtils', [])
.filter('removeSpaces', [function() {
return function(string) {
if (!angular.isString(string)) {
return string;
}
return string.replace(/[\s]/g, '');
};
}]);

View File

@@ -1,21 +0,0 @@
angular.module('myApp.webSocket', ['ngWebSocket'])
.factory('webSocket', function($websocket) {
// Open a WebSocket connection
var dataStream = $websocket('ws://localhost:9050/');
var collection = [];
dataStream.onMessage(function(message) {
collection.push(JSON.parse(message.data));
});
var methods = {
collection: collection,
get: function() {
dataStream.send(JSON.stringify({ action: 'get' }));
}
};
return methods;
})

View File

@@ -1,12 +0,0 @@
.table-fixed-heading {
margin-bottom:0px;
}
.buy-order-table th,.buy-order-table td {
width:25%;
}
.buy-order-data {
max-height:200px;
overflow-y:scroll;
}

View File

@@ -1,25 +0,0 @@
<link rel="stylesheet" href="/components/sell-orders/sell-orders.css" />
<div class="col-md-12 buy-order-table">
<table class="table table-striped table-fixed-heading">
<thead>
<tr>
<th >Price</th>
<th >{{currencyOne}}</th>
<th >{{currencyTwo}}</th>
<th >Sum({{currencyTwo}})</th>
</tr>
</thead>
</table>
<div class="buy-order-data">
<table class="table table-striped">
<tbody>
<tr ng-repeat="sellOrder in sellOrders">
<td >{{sellOrder.price}}</td>
<td >{{sellOrder.currencyOneAmount}}</td>
<td >{{sellOrder.currencyTwoAmount}}</td>
<td >{{sellOrder.sum}}</td>
</tr>
</tbody>
</table>
<div>
</div>

View File

@@ -1,52 +0,0 @@
angular.module('myApp.sellOrders',[]).component('sellorders', {
templateUrl: '/components/sell-orders/sell-orders.html',
controller:'SellOrdersController',
controller: function ($scope, $http, Notification, $rootScope) {
$scope.currency = {};
$scope.exchange = {};
$rootScope.$on('CurrencyChanged', function (event, args) {
$scope.currency = args.Currency;
$scope.exchange = args.Exchange;
$scope.currencyOne = $scope.currency.FirstCurrency;
$scope.currencyTwo = $scope.currency.SecondCurrency;
$scope.getRecentSellOrders();
});
$scope.getRecentSellOrders = function() {
var exchData = {params : {exchangeName: '', currencyPair:''}};
$http.get('/GetSellOrdersForCurrencyPair' , exchData).success(function(data) {
$scope.sellOrders = data;
}).error(function() {
$scope.sellOrders = [
{price:456,currencyOneAmount:12,currencyTwoAmount:13,sum:1111},
{price:234,currencyOneAmount:15,currencyTwoAmount:13,sum:11231},
{price:12344,currencyOneAmount:232,currencyTwoAmount:13,sum:4511},
{price:15467,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:6717,currencyOneAmount:2452,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:34522,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
{price:17,currencyOneAmount:22,currencyTwoAmount:13,sum:11212311},
];
});
}
}
});

View File

@@ -1,25 +0,0 @@
<div class="col-md-12">
<div class="form col-md-12">
<div class="row">
<label>Exchange: {{exchange.exchangeName}}
</div>
<div class="row">
<label>Currency: </label>{{currency.CurrencyPair}}
</div>
<div class="row">
<label>Bid: </label><label>{{currency.Bid}}</label>
</div>
<div class="row">
<label>Price: </label><input pattern="\d*" class="form-control" ng-model="price" type="text" placeholder="How much?" />
</div>
<div class="row">
<label>Amount: </label><input pattern="\d*" ng-model="amount" class="form-control" type="text" placeholder="How much?" />
</div>
<div class="row" >
<label>Total: </label><label ng-show="price > 0 && amount > 0">{{price * amount}}</label>
</div>
<div class="row">
<button ng-click="placeOrder()" class="form-control btn btn-success">Place Order</button>
</div>
</div>
<div>

View File

@@ -1,40 +0,0 @@
angular.module('myApp.sell',[]).component('sell', {
templateUrl: '/components/sell/sell.html',
controller:'SellController',
controller: function ($scope, $http, Notification, $rootScope) {
$scope.currency = {};
$scope.exchange = {};
$rootScope.$on('CurrencyChanged', function (event, args) {
$scope.currency = args.Currency;
$scope.exchange = args.Exchange;
console.log($scope.currency);
$scope.GetLatestDataFromExchangeCurrency();
$scope.price = $scope.currency.Bid;
});
$scope.GetLatestDataFromExchangeCurrency = function () {
$http.get('/GetLatestDataFromExchangeCurrency?exhange=' + $scope.exchange.exchangeName + '&currency='+ $scope.currency.CurrencyPair).success(function (data) {
$scope.currency.Last = data.Last;
$scope.currency.Volume = data.Volume;
$scope.currency.Bid = data.Bid;
$scope.price = $scope.currency.Bid;
});
}
$scope.placeOrder = function () {
var obj = {};
obj.ExchangeName = $scope.exchange.exchangeName;
obj.Currency = $scope.currency;
obj.Price = $scope.price;
obj.Amount = $scope.amount;
$http.post('/Command/PlaceSellOrder', obj).success(function (response) {
Notification.success("Successfully placed order");
});
};
}
});

View File

@@ -1,9 +0,0 @@
'use strict';
angular.module('myApp.version.interpolate-filter', [])
.filter('interpolate', ['version', function(version) {
return function(text) {
return String(text).replace(/\%VERSION\%/mg, version);
};
}]);

View File

@@ -1,15 +0,0 @@
'use strict';
describe('myApp.version module', function() {
beforeEach(module('myApp.version'));
describe('interpolate filter', function() {
beforeEach(module(function($provide) {
$provide.value('version', 'TEST_VER');
}));
it('should replace VERSION', inject(function(interpolateFilter) {
expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after');
}));
});
});

View File

@@ -1,9 +0,0 @@
'use strict';
angular.module('myApp.version.version-directive', [])
.directive('appVersion', ['version', function(version) {
return function(scope, elm, attrs) {
elm.text(version);
};
}]);

View File

@@ -1,17 +0,0 @@
'use strict';
describe('myApp.version module', function() {
beforeEach(module('myApp.version'));
describe('app-version directive', function() {
it('should print current version', function() {
module(function($provide) {
$provide.value('version', 'TEST_VER');
});
inject(function($compile, $rootScope) {
var element = $compile('<span app-version></span>')($rootScope);
expect(element.text()).toEqual('TEST_VER');
});
});
});
});

View File

@@ -1,8 +0,0 @@
'use strict';
angular.module('myApp.version', [
'myApp.version.interpolate-filter',
'myApp.version.version-directive'
])
.value('version', '0.1');

View File

@@ -1,11 +0,0 @@
'use strict';
describe('myApp.version module', function() {
beforeEach(module('myApp.version'));
describe('version service', function() {
it('should return current version', inject(function(version) {
expect(version).toEqual('0.1');
}));
});
});

File diff suppressed because one or more lines are too long

View File

@@ -1,58 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="bower_components/html5-boilerplate/css/normalize.css">
<link rel="stylesheet" href="bower_components/html5-boilerplate/css/main.css">
<style>
[ng-cloak] {
display: none;
}
</style>
<script src="bower_components/html5-boilerplate/js/vendor/modernizr-2.6.2.min.js"></script>
<script>
// include angular loader, which allows the files to load in any order
//@@NG_LOADER_START@@
// You need to run `npm run update-index-async` to inject the angular async code here
//@@NG_LOADER_END@@
// include a third-party async loader library
/*!
* $script.js v1.3
* https://github.com/ded/script.js
* Copyright: @ded & @fat - Dustin Diaz, Jacob Thornton 2011
* Follow our software http://twitter.com/dedfat
* License: MIT
*/
!function(a,b,c){function t(a,c){var e=b.createElement("script"),f=j;e.onload=e.onerror=e[o]=function(){e[m]&&!/^c|loade/.test(e[m])||f||(e.onload=e[o]=null,f=1,c())},e.async=1,e.src=a,d.insertBefore(e,d.firstChild)}function q(a,b){p(a,function(a){return!b(a)})}var d=b.getElementsByTagName("head")[0],e={},f={},g={},h={},i="string",j=!1,k="push",l="DOMContentLoaded",m="readyState",n="addEventListener",o="onreadystatechange",p=function(a,b){for(var c=0,d=a.length;c<d;++c)if(!b(a[c]))return j;return 1};!b[m]&&b[n]&&(b[n](l,function r(){b.removeEventListener(l,r,j),b[m]="complete"},j),b[m]="loading");var s=function(a,b,d){function o(){if(!--m){e[l]=1,j&&j();for(var a in g)p(a.split("|"),n)&&!q(g[a],n)&&(g[a]=[])}}function n(a){return a.call?a():e[a]}a=a[k]?a:[a];var i=b&&b.call,j=i?b:d,l=i?a.join(""):b,m=a.length;c(function(){q(a,function(a){h[a]?(l&&(f[l]=1),o()):(h[a]=1,l&&(f[l]=1),t(s.path?s.path+a+".js":a,o))})},0);return s};s.get=t,s.ready=function(a,b,c){a=a[k]?a:[a];var d=[];!q(a,function(a){e[a]||d[k](a)})&&p(a,function(a){return e[a]})?b():!function(a){g[a]=g[a]||[],g[a][k](b),c&&c(d)}(a.join("|"));return s};var u=a.$script;s.noConflict=function(){a.$script=u;return this},typeof module!="undefined"&&module.exports?module.exports=s:a.$script=s}(this,document,setTimeout)
// load all of the dependencies asynchronously.
$script([
'bower_components/angular/angular.js',
'bower_components/angular-route/angular-route.js',
'app.js',
'view1/view1.js',
'view2/view2.js',
'components/version/version.js',
'components/version/version-directive.js',
'components/version/interpolate-filter.js'
], function() {
// when all is done, execute bootstrap angular application
angular.bootstrap(document, ['myApp']);
});
</script>
<title>My AngularJS App</title>
<link rel="stylesheet" href="app.css">
</head>
<body ng-cloak>
<ul class="menu">
<li><a href="#!/view1">view1</a></li>
<li><a href="#!/view2">view2</a></li>
</ul>
<div ng-view></div>
<div>Angular seed app: v<span app-version></span></div>
</body>
</html>

View File

@@ -1,80 +0,0 @@
<!DOCTYPE html>
<!--[if lt IE 7]> <html lang="en" ng-app="myApp" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html lang="en" ng-app="myApp" class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html lang="en" ng-app="myApp" class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html lang="en" ng-app="myApp" class="no-js">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>GoCrypto Trader</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/bower_components/html5-boilerplate/dist/css/normalize.css">
<link rel="stylesheet" href="/bower_components/html5-boilerplate/dist/css/main.css">
<link rel="stylesheet" href="/bower_components/angular-ui-notification/dist/angular-ui-notification.min.css">
<link rel="stylesheet" href="/app.css">
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/darktheme.min.css">
<script src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
<script src="/bower_components/html5-boilerplate/dist/js/vendor/modernizr-2.8.3.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top navbar" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="/">
GoCrypto Trader
</a>
</div>
<ul class="nav navbar-nav">
<li><a href="#!/">Dashboard</a></li>
<li><a href="#!/wallets">Wallets</a></li>
</ul>
<ul class="nav navbar-nav pull-right">
<li class=""><a href="#!/settings">Settings</a></li>
</ul>
</div>
</nav>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<div ng-view style="padding-top:60px;"></div>
<p class="text-center text-muted">Copyright 2016 GoCrypto Trader</p>
<!-- In production use:
<script src="//ajax.googleapis.com/ajax/libs/angularjs/x.x.x/angular.min.js"></script>
-->
<script src="/bower_components/angular/angular.js"></script>
<script src="/bower_components/angular-ui-notification/dist/angular-ui-notification.min.js"></script>
<script src="/bower_components/angular-route/angular-route.js"></script>
<script src="/bower_components/angular-websocket/dist/angular-websocket.js"></script>
<script src="/app.js"></script>
<script src="/views/settings/settings.js"></script>
<script src="/views/home/home.js"></script>
<script src="/views/wallets/wallets.js"></script>
<script src="/components/buy/buy.js"></script>
<script src="/components/sell/sell.js"></script>
<script src="/components/enabled-exchanges/enabled-exchanges.js"></script>
<script src="/components/version/version.js"></script>
<script src="/components/buy-orders/buy-orders.js"></script>
<script src="/components/sell-orders/sell-orders.js"></script>
<script src="/components/version/version-directive.js"></script>
<script src="/components/version/interpolate-filter.js"></script>
<script src="/components/helpers/stringUtils.js"></script>
<script src="/components/helpers/webSocket.js"></script>
</body>
</html>

View File

@@ -1,73 +0,0 @@
<div class="col-md-12">
<div ng-show="loaded">
<div class="col-md-12">
<h2>{{exchange.exchangeName}} Exchange</h2>
<h4>{{currency.CurrencyPair}}</h4>
</div>
<div class="col-md-8">
<div class="col-md-12">
<!-- This is to get a sense of scale, I don't intend on any hardcoded heights!' -->
<div class="col-md-6" style=" height: 300px;">
<h3>Sell orders</h3>
<!--plceholder-->
<sellorders></sellorders>
</div>
<div class="col-md-6" style=" height: 300px;">
<h3>Buy orders</h3>
<buyorders></buyorders>
</div>
</div>
<div class="col-md-12" style=" height: 300px;">
<h3>Market depth chart</h3>
</div>
<div class="col-md-4">
<h4>Buy</h4>
<buy></buy>
</div>
<div class="col-md-4" style=" height: 300px;">
<h4>Sell</h4>
<sell></sell>
</div>
<div class="col-md-4" style=" height: 300px;">
<h4>Wallet quick look</h4>
See current holding of selected currency
</div>
<div class="col-md-12">
<div class="col-md-6" style=" height: 400px;">
<h3>Trade History</h3>
</div>
<div class="col-md-6" style=" height: 400px;">
<h3>My Open Orders</h3>
</div>
</div>
</div>
<div class="col-md-4">
<enabledexchanges></enabledexchanges>
</div>
</div>
<div ng-show="!loaded && !loadFailed" class="col-md-12 text-center">
<br />
<br />
<br />
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="120" height="120" fill="#40bfc0">
<path opacity=".25" d="M16 0 A16 16 0 0 0 16 32 A16 16 0 0 0 16 0 M16 4 A12 12 0 0 1 16 28 A12 12 0 0 1 16 4"/>
<path d="M16 0 A16 16 0 0 1 32 16 L28 16 A12 12 0 0 0 16 4z">
<animateTransform attributeName="transform" type="rotate" from="0 16 16" to="360 16 16" dur="0.8s" repeatCount="indefinite" />
</path>
</svg>
<br />
<br />
<br />
</div>
<div ng-show="loadFailed">
<br />
<br />
<h1 class="text-center">D:</h1>
<br />
<h4 class="text-center">Something went wrong! Make sure Gocryptotrader is running. If things still aren't cool, open a ticket <a href="https://github.com/thrasher-/gocryptotrader/issues" target="blank">here</a></h4>
<br />
<br />
<br />
</div>
</div>

View File

@@ -1,28 +0,0 @@
'use strict';
angular.module('myApp.home', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
templateUrl: '/views/home/home.html',
controller: 'HomeController'
});
}])
.controller('HomeController', function($scope, $rootScope, $timeout, webSocket) {
$scope.loaded = false;
$rootScope.$on('CurrencyChanged', function(event, args) {
$scope.currency = args.Currency;
$scope.exchange = args.Exchange;
$scope.loaded = true;
});
$timeout(function() {
if ($scope.currency) {
$scope.loaded = true;
} else {
$scope.loadFailed = true;
}
}, 10000);
});

View File

@@ -1,16 +0,0 @@
'use strict';
describe('myApp.view1 module', function() {
beforeEach(module('myApp.view1'));
describe('view1 controller', function(){
it('should ....', inject(function($controller) {
//spec body
var view1Ctrl = $controller('View1Ctrl');
expect(view1Ctrl).toBeDefined();
}));
});
});

View File

@@ -1,60 +0,0 @@
<h2>Settings</h2>
<div class="col-md-12">
<div class="col-md-10 container " >
<h4>Exchange config</h4>
<div ng-repeat="exchange in config.Exchanges">
<div class="row">
<h3>{{exchange.Name}}</h3>
</div>
<div class="row">
<div class="col-md-2">
<label class="input-form">Enabled:</label>
</div>
<div class="col-md-10">
<input type="checkbox" class="input-form" ng-model="exchange.Enabled"/>
</div>
</div>
<div ng-show="exchange.Enabled">
<div class="row">
<div class="col-md-2">
<label class="input-form">API Key:</label>
</div>
<div class="col-md-10">
<input type="text" class="input-form" ng-model="exchange.APIKey"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<label class="input-form">API Secret:</label>
</div>
<div class="col-md-10">
<input type="text" class="input-form" ng-model="exchange.APISecret"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<label class="input-form">Client ID:</label>
</div>
<div class="col-md-10">
<input type="text" class="input-form" ng-model="exchange.ClientID"/>
</div>
</div>
<div class="row">
<div class="col-md-2">
<label class="input-form">Currencies to check:</label>
</div>
<div class="col-md-10">
<div class="col-md-2" ng-repeat="availableCurrency in exchange.AvailablePairsSplit">
<div class="col-md-8">{{availableCurrency}}</div>
<div class="col-md-4"><input type="checkbox" class="input-form" ng-click="toggleCurrencyToEnabledCurrencies(availableCurrency, exchange)" ng-checked="exchange.EnabledPairsSplit.indexOf(availableCurrency) > -1"/></div>
</div>
</div>
</div>
<button class="btn btn-success btn-lg" ng-click="saveAllSettings()">Save</button>
</div>
</div>
</div>
<div class="col-md-2">
<button class="btn btn-success btn-lg" style="float:right;position:fixed;top:120px;right:60px;" ng-click="saveAllSettings()">Save</button>
</div>
</div>

View File

@@ -1,80 +0,0 @@
'use strict';
angular.module('myApp.settings', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/settings', {
templateUrl: '/views/settings/settings.html',
controller: 'SettingsController'
});
}])
.controller('SettingsController', function ($scope, $http, Notification) {
$scope.getconfigData = function() {
$http({
method: 'GET',
url: '/config/all'
}).
success(function (data, status, headers, config) {
for(var i=0; i<data.Exchanges.length;i++) {
data.Exchanges[i].AvailablePairsSplit = data.Exchanges[i].AvailablePairs.split(",");
data.Exchanges[i].EnabledPairsSplit = data.Exchanges[i].EnabledPairs.split(",");
}
$scope.config = data;
Notification.info('Settings loaded');
}).
error(function (data, status, headers, config) {
console.log('error');
});
};
$scope.getconfigData();
$scope.toggleCurrencyToEnabledCurrencies = function(currency, exchange) {
for(var i=0; i<$scope.config.Exchanges.length;i++) {
if($scope.config.Exchanges[i].Name == exchange.Name) {
if(exchange.EnabledPairsSplit.indexOf(currency) > -1) {
$scope.config.Exchanges[i].EnabledPairsSplit.splice($scope.config.Exchanges[i].EnabledPairsSplit.indexOf(currency),1);
//I feel there's a better way to do this, but for right now, whatever
$scope.config.Exchanges[i].EnabledPairs = $scope.config.Exchanges[i].EnabledPairs.replace(currency,"");
$scope.config.Exchanges[i].EnabledPairs = $scope.config.Exchanges[i].EnabledPairs.replace(",,","");
$scope.config.Exchanges[i].EnabledPairs = $scope.config.Exchanges[i].EnabledPairs.replace(/,\s*$/, "");
} else {
$scope.config.Exchanges[i].EnabledPairsSplit.push(currency);
$scope.config.Exchanges[i].EnabledPairs = $scope.config.Exchanges[i].EnabledPairs + "," + currency;
}
}
}
}
$scope.saveAllSettings = function() {
$scope.postObject = jQuery.extend(true, {}, $scope.config);
//Purge any unnecessary post data
delete $scope.postObject.Webserver;
for(var i=0; i<$scope.postObject.Exchanges.length;i++) {
delete $scope.postObject.Exchanges[i].AvailablePairsSplit;
delete $scope.postObject.Exchanges[i].AvailablePairs;
delete $scope.postObject.Exchanges[i].BaseCurrencies;
delete $scope.postObject.Exchanges[i].EnabledPairsSplit;
}
//Send to be saved
$http({
method: 'POST',
url: '/config/all/save',
data: $scope.postObject
}).
success(function (data) {
Notification.success('Saved settings');
for(var i=0; i<data.Exchanges.length;i++) {
data.Exchanges[i].AvailablePairsSplit = data.Exchanges[i].AvailablePairs.split(",");
data.Exchanges[i].EnabledPairsSplit = data.Exchanges[i].EnabledPairs.split(",");
}
$scope.config = data;
Notification.info('Settings loaded');
}).
error(function (data) {
Notification.error('Save failed');
});
}
});

View File

@@ -1,16 +0,0 @@
'use strict';
describe('myApp.view1 module', function() {
beforeEach(module('myApp.view1'));
describe('view1 controller', function(){
it('should ....', inject(function($controller) {
//spec body
var view1Ctrl = $controller('View1Ctrl');
expect(view1Ctrl).toBeDefined();
}));
});
});

View File

@@ -1,17 +0,0 @@
<h2>Wallets</h2>
<h3>All your currency, all in one place</h3>
<div ng-repeat="wallet in wallets">
<h4>{{wallet.ExchangeName}}</h4>
<table class="table table-striped">
<tr>
<th>Currency Name</th>
<th>Total</th>
<th>Hold</th>
</tr>
<tr ng-repeat="value in wallet.Currencies">
<td>{{value.CurrencyName}}</td>
<td>{{value.TotalValue}}</td>
<td>{{value.Hold}}</td>
</tr>
</table>
</div>

View File

@@ -1,29 +0,0 @@
'use strict';
angular.module('myApp.wallets', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/wallets', {
templateUrl: '/views/wallets/wallets.html',
controller: 'WalletsController'
});
}])
.controller('WalletsController', function ($scope, $http, Notification) {
$scope.getDashboardData = function() {
$http({
method: 'GET',
url: '/data/all-enabled-exchange-account-info'
}).
success(function (data, status, headers, config) {
$scope.wallets = data.data;
Notification.info("Got your wallet!");
}).
error(function (data, status, headers, config) {
console.log('error');
});
};
$scope.getDashboardData();
});

View File

@@ -1,16 +0,0 @@
'use strict';
describe('myApp.view1 module', function() {
beforeEach(module('myApp.view1'));
describe('view1 controller', function(){
it('should ....', inject(function($controller) {
//spec body
var view1Ctrl = $controller('View1Ctrl');
expect(view1Ctrl).toBeDefined();
}));
});
});

View File

@@ -1,18 +0,0 @@
{
"name": "angular-seed",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-seed",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "~1.5.0",
"angular-route": "~1.5.0",
"angular-loader": "~1.5.0",
"angular-mocks": "~1.5.0",
"html5-boilerplate": "^5.3.0",
"angular-ui-notification":"latest",
"angular-websocket":"2.0.0",
"bootstrap":"3.3.7"
}
}

View File

@@ -1,22 +0,0 @@
//jshint strict: false
exports.config = {
allScriptsTimeout: 11000,
specs: [
'*.js'
],
capabilities: {
'browserName': 'chrome'
},
baseUrl: 'http://localhost:8000/',
framework: 'jasmine',
jasmineNodeOpts: {
defaultTimeoutInterval: 30000
}
};

View File

@@ -1,42 +0,0 @@
'use strict';
/* https://github.com/angular/protractor/blob/master/docs/toc.md */
describe('my app', function() {
it('should automatically redirect to /view1 when location hash/fragment is empty', function() {
browser.get('index.html');
expect(browser.getLocationAbsUrl()).toMatch("/view1");
});
describe('view1', function() {
beforeEach(function() {
browser.get('index.html#!/view1');
});
it('should render view1 when user navigates to /view1', function() {
expect(element.all(by.css('[ng-view] p')).first().getText()).
toMatch(/partial for view 1/);
});
});
describe('view2', function() {
beforeEach(function() {
browser.get('index.html#!/view2');
});
it('should render view2 when user navigates to /view2', function() {
expect(element.all(by.css('[ng-view] p')).first().getText()).
toMatch(/partial for view 2/);
});
});
});

14
web/e2e/app.e2e-spec.ts Normal file
View File

@@ -0,0 +1,14 @@
import { AngularElectronPage } from './app.po';
import { browser, element, by } from 'protractor';
describe('angular-electron App', () => {
let page: AngularElectronPage;
beforeEach(() => {
page = new AngularElectronPage();
});
it('should display message saying App works !', () => {
expect(element(by.css('app-home h1')).getText()).toMatch('App works !');
});
});

8
web/e2e/app.po.ts Normal file
View File

@@ -0,0 +1,8 @@
import { browser, element, by } from 'protractor';
/* tslint:disable */
export class AngularElectronPage {
navigateTo(route: string) {
return browser.get(route);
}
}

12
web/e2e/tsconfig.e2e.json Normal file
View File

@@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types":[
"jasmine",
"node"
]
}
}

View File

@@ -1,34 +1,44 @@
//jshint strict: false
module.exports = function(config) {
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: './app',
files: [
'bower_components/angular/angular.js',
'bower_components/angular-route/angular-route.js',
'bower_components/angular-mocks/angular-mocks.js',
'components/**/*.js',
'view*/**/*.js'
],
autoWatch: true,
frameworks: ['jasmine'],
browsers: ['Chrome'],
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-jasmine',
'karma-junit-reporter'
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
junitReporter: {
outputFile: 'test_out/unit.xml',
suite: 'unit'
}
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
files: [
{ pattern: './src/test.ts', watched: false }
],
preprocessors: {
'./src/test.ts': ['@angular/cli']
},
mime: {
'text/x-typescript': ['ts','tsx']
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: config.angularCli && config.angularCli.codeCoverage
? ['progress', 'coverage-istanbul']
: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

BIN
web/logo-angular.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
web/logo-electron.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

71
web/main.ts Normal file
View File

@@ -0,0 +1,71 @@
import { app, BrowserWindow, screen } from 'electron';
import * as path from 'path';
let win, serve;
const args = process.argv.slice(1);
serve = args.some(val => val === '--serve');
if (serve) {
require('electron-reload')(__dirname, {
});
}
function createWindow() {
const electronScreen = screen;
const size = electronScreen.getPrimaryDisplay().workAreaSize;
// Create the browser window.
win = new BrowserWindow({
x: 0,
y: 0,
width: size.width,
height: size.height
});
// and load the index.html of the app.
win.loadURL('file://' + __dirname + '/index.html');
// Open the DevTools.
if (serve) {
win.webContents.openDevTools();
}
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store window
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null;
});
}
try {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow();
}
});
} catch (e) {
// Catch Error
// throw e;
}

9930
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

60
web/package.js Normal file
View File

@@ -0,0 +1,60 @@
"use strict";
var packager = require('electron-packager');
const pkg = require('./package.json');
const argv = require('minimist')(process.argv.slice(1));
const appName = argv.name || pkg.name;
const buildVersion = pkg.version || '1.0';
const shouldUseAsar = argv.asar || false;
const shouldBuildAll = argv.all || false;
const arch = argv.arch || 'all';
const platform = argv.platform || 'darwin';
const DEFAULT_OPTS = {
dir: './dist',
name: appName,
asar: shouldUseAsar,
buildVersion: buildVersion
};
pack(platform, arch, function done(err, appPath) {
if (err) {
console.log(err);
} else {
console.log('Application packaged successfuly!', appPath);
}
});
function pack(plat, arch, cb) {
// there is no darwin ia32 electron
if (plat === 'darwin' && arch === 'ia32') return;
let icon = 'src/favicon';
if (icon) {
DEFAULT_OPTS.icon = icon + (() => {
let extension = '.png';
if (plat === 'darwin') {
extension = '.icns';
} else if (plat === 'win32') {
extension = '.ico';
}
return extension;
})();
}
const opts = Object.assign({}, DEFAULT_OPTS, {
platform: plat,
arch,
prune: true,
overwrite: true,
all: shouldBuildAll,
out: `app-builds`
});
console.log(opts)
packager(opts, cb);
}

View File

@@ -1,38 +1,117 @@
{
"name": "angular-seed",
"private": true,
"version": "0.0.0",
"description": "A starter project for AngularJS",
"repository": "https://github.com/angular/angular-seed",
"license": "MIT",
"devDependencies": {
"bower": "^1.7.7",
"http-server": "^0.9.0",
"jasmine-core": "^2.4.1",
"karma": "^0.13.22",
"karma-chrome-launcher": "^0.2.3",
"karma-firefox-launcher": "^0.1.7",
"karma-jasmine": "^0.3.8",
"karma-junit-reporter": "^0.4.1",
"protractor": "^3.2.2",
"express": "latest",
"requestify": "latest",
"body-parser": "latest"
},
"scripts": {
"postinstall": "bower install",
"prestart": "npm install",
"start": "node server.js",
"pretest": "npm install",
"test": "karma start karma.conf.js",
"test-single-run": "karma start karma.conf.js --single-run",
"preupdate-webdriver": "npm install",
"update-webdriver": "webdriver-manager update",
"preprotractor": "npm run update-webdriver",
"protractor": "protractor e2e-tests/protractor.conf.js",
"update-index-async": "node -e \"var fs=require('fs'),indexFile='app/index-async.html',loaderFile='app/bower_components/angular-loader/angular-loader.min.js',loaderText=fs.readFileSync(loaderFile,'utf-8').split(/sourceMappingURL=angular-loader.min.js.map/).join('sourceMappingURL=bower_components/angular-loader/angular-loader.min.js.map'),indexText=fs.readFileSync(indexFile,'utf-8').split(/\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/).join('//@@NG_LOADER_START@@\\n'+loaderText+' //@@NG_LOADER_END@@');fs.writeFileSync(indexFile,indexText);\""
},
"dependencies": {
"request": "^2.74.0"
}
"name": "gocryptotrader-web",
"version": "0.1.0",
"description": "Front-end interface for GoCryptoTrader",
"homepage": "https://github.com/thrasher-/gocryptotrader",
"author": {
"name": "Scott",
"email": "scott@gloriousedge.com",
"github": "https://github.com/gloriousCode/",
"website": "https://www.gloriousedge.com"
},
"contributers": [
{
"name": "Maxime GRIS",
"email": "maxime.gris@gmail.com",
"github": "https://github.com/maximegris/"
}
],
"keywords": [
"angular",
"electron",
"typescript",
"sass",
"bitcoin",
"exchange"
],
"main": "main.js",
"private": true,
"scripts": {
"ng": "ng",
"lint": "ng lint",
"start": "webpack --watch",
"start:web": "webpack-dev-server --content-base . --port 4200 --inline",
"build:electron:main": "tsc main.ts --outDir dist && copyfiles package.json dist && cd dist && npm install --prod && cd ..",
"build": "webpack --display-error-details && npm run build:electron:main",
"build:prod": "cross-env NODE_ENV=production npm run build",
"electron:serve": "npm run build:electron:main && electron ./dist --serve",
"electron:test": "electron ./dist",
"electron:dev": "npm run build && electron ./dist",
"electron:prod": "npm run build:prod && electron ./dist",
"electron:linux": "npm run build:prod && node package.js --asar --platform=linux --arch=x64",
"electron:windows": "npm run build:prod && node package.js --asar --platform=win32 --arch=ia32",
"electron:mac": "npm run build:prod && node package.js --asar --platform=darwin --arch=x64",
"test": "karma start ./karma.conf.js",
"pree2e": "webdriver-manager update --standalone false --gecko false --quiet && npm run build",
"e2e": "protractor ./protractor.conf.js"
},
"dependencies": {
"@angular/animations": "^4.3.4",
"@angular/cdk": "^2.0.0-beta.10",
"@angular/common": "4.3.0",
"@angular/compiler": "4.3.0",
"@angular/core": "4.3.4",
"@angular/forms": "4.3.0",
"@angular/http": "4.3.0",
"@angular/material": "^2.0.0-beta.10",
"@angular/platform-browser": "4.3.0",
"@angular/platform-browser-dynamic": "4.3.0",
"@angular/router": "4.3.0",
"core-js": "2.4.1",
"enhanced-resolve": "3.3.0",
"rxjs": "^5.4.3",
"zone.js": "0.8.12"
},
"devDependencies": {
"@angular/cli": "1.2.1",
"@angular/compiler-cli": "4.3.0",
"@types/bluebird": "3.5.8",
"@types/core-js": "0.9.36",
"@types/jasmine": "2.5.53",
"@types/node": "7.0.7",
"autoprefixer": "7.1.1",
"codelyzer": "3.1.1",
"copyfiles": "1.2.0",
"cross-env": "5.0.1",
"css-loader": "0.28.4",
"cssnano": "3.10.0",
"electron": "1.6.11",
"electron-packager": "8.7.2",
"electron-reload": "1.2.1",
"exports-loader": "0.6.4",
"extract-zip": "=1.6.5",
"file-loader": "0.11.2",
"html-loader": "0.4.5",
"istanbul-instrumenter-loader": "2.0.0",
"jasmine-core": "2.6.4",
"jasmine-spec-reporter": "4.1.1",
"json-loader": "0.5.4",
"karma": "1.7.0",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1",
"karma-coverage-istanbul-reporter": "0.2.0",
"karma-jasmine": "1.1.0",
"karma-jasmine-html-reporter": "0.2.2",
"karma-sourcemap-loader": "0.3.7",
"less-loader": "4.0.4",
"minimist": "1.2.0",
"mkdirp": "0.5.1",
"postcss-loader": "1.3.3",
"postcss-url": "7.0.0",
"protractor": "5.1.2",
"raw-loader": "0.5.1",
"sass-loader": "6.0.6",
"script-loader": "0.7.0",
"source-map-loader": "0.2.1",
"style-loader": "0.18.2",
"stylus-loader": "3.0.1",
"ts-node": "3.1.0",
"tslint": "5.4.3",
"typescript": "2.4.1",
"url-loader": "0.5.9",
"webdriver-manager": "12.0.6",
"webpack": "3.3.0",
"webpack-dev-server": "2.5.0"
},
"license": "SEE LICENSE IN LICENSE.md"
}

36
web/protractor.conf.js Normal file
View File

@@ -0,0 +1,36 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 25000,
getPageTimeout: 15000,
delayBrowserTimeInSeconds: 0,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome',
chromeOptions: {
binary: './node_modules/electron/dist/electron.exe',
args: ['--test-type=webdriver', 'app=dist/main.js']
}
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine2',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function () { }
},
beforeLaunch: function () {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
},
onPrepare() {
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@@ -1,63 +0,0 @@
var express = require('express')
, app = express();
var requestify = require('requestify');
var bodyParser = require('body-parser')
var request = require('request');
var path = __dirname + '/app/';
app.use("/bower_components", express.static(path + '/bower_components'));
app.use( bodyParser.json() );
app.get("/",function(req,res){
res.sendFile(path + "index.html");
});
app.use("/", express.static(path + '/'));
app.get('/data/all-enabled-currencies', function (req, res) {
request({
url :'http://localhost:9050/exchanges/enabled/latest/all'
},function(err, resp, body){
res.send(body);
})
});
app.get('/data/all-enabled-exchange-account-info', function (req, res) {
request({
url :'http://localhost:9050/exchanges/enabled/accounts/all'
},function(err, resp, body){
res.send(body);
})
});
app.get('/config/all', function (req, res) {
request({
url :'http://localhost:9050/config/all'
},function(err, resp, body){
res.send(body);
})
});
////////////////////////////////////////////////////////
// Posts
///////////////////////////////////////////////////////
app.post('/config/all/save', function(req, res) {
requestify.post('http://localhost:9050/config/all/save', {
Data: req.body
})
.then(function(response) {
console.log(response);
res.send(response.body);
});
});
var port = process.env.GCT_WEB_PORT || 80;
app.listen(port, function(){
console.log(`GoCyptoTrader website running! Enter http://localhost:${port}/ into browser`);
});

View File

@@ -0,0 +1,33 @@
import { HomeComponent } from './pages/home/home.component';
import { SettingsComponent } from './pages/settings/settings.component';
import { AboutComponent } from './pages/about/about.component';
import { DashboardComponent } from './pages/dashboard/dashboard.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: '',
component: DashboardComponent
},
{
path:'about',
component: AboutComponent
},
{
path:'dashboard',
component: DashboardComponent
},
{
path: 'settings',
component: SettingsComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, {useHash: true})],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@@ -0,0 +1,9 @@
<app-navbar></app-navbar>
<div class="sidebar"></div>
<div class="main">
<div class="main-content">
<router-outlet></router-outlet>
</div>
</div>

View File

@@ -0,0 +1,24 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { ElectronService } from 'app/providers/electron.service';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
providers : [
ElectronService
],
imports: [RouterTestingModule]
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
});

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { ElectronService } from './providers/electron.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
constructor(public electronService: ElectronService) {
if (electronService.isElectron()) {
console.log('Mode electron');
// Check if electron is correctly injected (see externals in webpack.config.js)
console.log('c', electronService.ipcRenderer);
// Check if nodeJs childProcess is correctly injected (see externals in webpack.config.js)
console.log('c', electronService.childProcess);
} else {
console.log('Mode web');
}
}
}

77
web/src/app/app.module.ts Normal file
View File

@@ -0,0 +1,77 @@
import 'zone.js/dist/zone-mix';
import 'reflect-metadata';
import 'polyfills';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpModule } from '@angular/http';
import { NgModule, Injectable } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
MdButtonModule,
MdCardModule,
MdMenuModule,
MdToolbarModule,
MdIconModule,
MdFormFieldModule,
MdInputModule,
MdCheckboxModule,
MdGridListModule,
MdProgressSpinnerModule,
} from '@angular/material';
import { AppComponent } from './app.component';
import { HomeComponent } from './pages/home/home.component';
import { AboutComponent } from './pages/about/about.component';
import { SettingsComponent } from './pages/settings/settings.component';
import { DashboardComponent } from './pages/dashboard/dashboard.component';
import { NavbarComponent } from './shared/navbar/navbar.component';
import { ExchangeCurrencyTickerComponent } from './shared/exchange-currency-ticker/exchange-currency-ticker.component';
import { AllEnabledCurrencyTickersComponent } from './shared/all-enabled-currency-tickers/all-enabled-currency-tickers.component';
//services
import { WebsocketService } from './services/websocket/websocket.service';
import { WebsocketHandlerService } from './services/websocket-handler/websocket-handler.service';
import { ElectronService } from './providers/electron.service';
//Routing
import { AppRoutingModule } from './app-routing.module';
import * as Rx from 'rxjs/Rx';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
AboutComponent,
NavbarComponent,
SettingsComponent,
DashboardComponent,
ExchangeCurrencyTickerComponent,
AllEnabledCurrencyTickersComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
AppRoutingModule,
BrowserAnimationsModule,
MdButtonModule,
MdMenuModule,
MdCardModule,
MdToolbarModule,
MdIconModule,
MdFormFieldModule,
MdInputModule,
MdCheckboxModule,
MdGridListModule,
MdProgressSpinnerModule,
],
providers: [ElectronService,WebsocketService,WebsocketHandlerService],
bootstrap: [AppComponent]
})
export class AppModule {
}

View File

@@ -0,0 +1,3 @@
<p>
about works!
</p>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AboutComponent } from './about.component';
describe('AboutComponent', () => {
let component: AboutComponent;
let fixture: ComponentFixture<AboutComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AboutComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AboutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss']
})
export class AboutComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1 @@
<app-all-enabled-currency-tickers></app-all-enabled-currency-tickers>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DashboardComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
})
export class DashboardComponent implements OnInit {
constructor()
{
}
ngOnInit() {
}
}

View File

@@ -0,0 +1,2 @@
<div class="container">
</div>

View File

@@ -0,0 +1,38 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it(`should have as title 'App works !'`, async(() => {
fixture = TestBed.createComponent(HomeComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('App works !');
}));
it('should render title in a h1 tag', async(() => {
fixture = TestBed.createComponent(HomeComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('App works !');
}));
});

View File

@@ -0,0 +1,17 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
})
export class HomeComponent implements OnInit {
title = `App works !`;
constructor() { }
ngOnInit() {
}
}

View File

@@ -0,0 +1,93 @@
<div class="loading-spinner" *ngIf="settings === null">
<md-progress-spinner mode="indeterminate"></md-progress-spinner>
</div>
<div *ngIf="settings !== null">
<button (click)="saveSettings()" md-fab color="accent" class="md-fab md-fab-bottom-right">Save</button>
<form *ngIf="settings.SMSGlobal != null">
<md-card class="exchange-card">
<md-card-header>
<md-card-title>SMS Global Settings</md-card-title>
</md-card-header>
<md-card-content>
<table cellspacing="0">
<tr>
<td>
<md-checkbox name="smsEnabled" [(ngModel)]="settings.SMSGlobal.Enabled">Enabled</md-checkbox>
</td>
</tr>
</table>
<md-grid-list cols="2" rowHeight="3:1">
<md-grid-tile>
<md-form-field>
<input mdInput name="smsUsername" [(ngModel)]="settings.SMSGlobal.Username" [disabled]="!settings?.SMSGlobal.Enabled" placeholder="Username">
</md-form-field>
</md-grid-tile>
<md-grid-tile>
<md-form-field>
<input mdInput name="smsPassword" [(ngModel)]="settings.SMSGlobal.Password" [disabled]="!settings?.SMSGlobal.Enabled" placeholder="Password">
</md-form-field>
</md-grid-tile>
</md-grid-list>
<md-grid-list cols="3" rowHeight="2:1" *ngFor="let contact of settings.SMSGlobal?.Contacts">
<md-grid-tile>
<md-checkbox name="contactEnabled" [disabled]="!settings?.SMSGlobal.Enabled" [(ngModel)]="contact.Enabled">Enabled</md-checkbox>
</md-grid-tile>
<md-grid-tile>
<md-form-field>
<input mdInput name="contactUsername" [(ngModel)]="contact.Name" [disabled]="!settings.SMSGlobal.Enabled || !contact.Enabled" placeholder="Contact Name">
</md-form-field>
</md-grid-tile>
<md-grid-tile>
<md-form-field>
<input mdInput name="contactPassword" [(ngModel)]="contact.Number" [disabled]="!settings.SMSGlobal.Enabled || !contact.Enabled" placeholder="Contact Number">
</md-form-field>
</md-grid-tile>
</md-grid-list>
</md-card-content>
</md-card>
</form>
<form *ngFor="let exchange of settings?.Exchanges">
<md-card class="exchange-card">
<md-card-header>
<md-card-title>{{exchange.Name}} Exchange Settings</md-card-title>
</md-card-header>
<md-card-content>
<table cellspacing="0">
<tr>
<td>
<md-checkbox name="exchangeEnabled" [(ngModel)]="exchange.Enabled">Enabled</md-checkbox>
</td>
</tr>
</table>
<md-grid-list cols="3" rowHeight="2:1">
<md-grid-tile>
<md-form-field>
<input mdInput name="apiKey" [(ngModel)]="exchange.APIKey" [disabled]="!exchange.Enabled" placeholder="Exchange API Key">
</md-form-field>
</md-grid-tile>
<md-grid-tile>
<md-form-field>
<input mdInput name="apiSecretKey" [(ngModel)]="exchange.APISecret" [disabled]="!exchange.Enabled" placeholder="Exchange API Secret Key">
</md-form-field>
</md-grid-tile>
<md-grid-tile>
<md-form-field>
<input mdInput name="apiClientId" [(ngModel)]="exchange.ClientID" [disabled]="!exchange.Enabled" placeholder="Exchange API ClientID (optional)">
</md-form-field>
</md-grid-tile>
</md-grid-list>
<label>Enabled Currencies</label>
<md-grid-list cols="6" rowHeight="2:1">
<md-grid-tile *ngFor="let currency of exchange.AvailablePairs.split(',')">
<md-checkbox name="availableCurrency" [disabled]="true || !exchange.Enabled">{{currency}}</md-checkbox>
</md-grid-tile>
</md-grid-list>
</md-card-content>
</md-card>
</form>
</div>

View File

@@ -0,0 +1,20 @@
.example-form {
min-width: 150px;
width: 100%;
}
.example-full-width {
width: 100%;
}
.exchange-card {
margin-bottom: 20px;
width: 1000px;
}
.md-fab {
margin: 0;
position: fixed;
bottom: 5%;
right: 2%;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SettingsComponent } from './settings.component';
describe('SettingsComponent', () => {
let component: SettingsComponent;
let fixture: ComponentFixture<SettingsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SettingsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,145 @@
import { Component, OnInit } from '@angular/core';
import { WebsocketHandlerService } from './../../services/websocket-handler/websocket-handler.service';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
export class SettingsComponent implements OnInit {
public settings: Config = null;
private ws: WebsocketHandlerService;
private failCount = 0;
private timer: any;
private getSettingsMessage = {
Event: 'GetConfig',
data: null,
};
constructor(private websocketHandler: WebsocketHandlerService) {
this.ws = websocketHandler;
this.ws.messages.subscribe(msg => {
if (msg.Event === 'GetConfig') {
this.settings = <Config>msg.data;
} else if (msg.Event === 'SaveConfig') {
// something!
}
});
}
ngOnInit() {
this.getSettings();
}
private getSettings(): void {
this.ws.messages.next(this.getSettingsMessage);
this.resendMessageIfPageRefreshed();
}
private saveSettings(): void {
//Send the message
var settingsSave = {
Event: 'SaveConfig',
data: this.settings,
}
this.ws.messages.next(settingsSave);
}
//there has to be a better way
private resendMessageIfPageRefreshed(): void {
if (this.failCount <= 10) {
setTimeout(() => {
if (this.settings === null) {
//console.log(this.failCount);
//console.log('Settings hasnt been set. Trying again');
this.failCount++;
this.getSettings();
}
}, 1000);
} else {
// something has gone wrong
console.log('Could not load settings. Check if GocryptoTrader server is running, otherwise open a ticket');
}
}
}
export interface CurrencyPairFormat {
Uppercase: boolean;
Delimiter: string;
}
export interface PortfolioAddresses {
Addresses?: any;
}
export interface Contact {
Name: string;
Number: string;
Enabled: boolean;
}
export interface SMSGlobal {
Enabled: boolean;
Username: string;
Password: string;
Contacts: Contact[];
}
export interface Webserver {
Enabled: boolean;
AdminUsername: string;
AdminPassword: string;
ListenAddress: string;
WebsocketConnectionLimit: number;
WebsocketAllowInsecureOrigin: boolean;
}
export interface ConfigCurrencyPairFormat {
Uppercase: boolean;
Index: string;
Delimiter: string;
}
export interface RequestCurrencyPairFormat {
Uppercase: boolean;
Index: string;
Delimiter: string;
Separator: string;
}
export interface Exchange {
Name: string;
Enabled: boolean;
Verbose: boolean;
Websocket: boolean;
RESTPollingDelay: number;
AuthenticatedAPISupport: boolean;
APIKey: string;
APISecret: string;
AvailablePairs: string;
EnabledPairs: string;
BaseCurrencies: string;
AssetTypes: string;
ConfigCurrencyPairFormat: ConfigCurrencyPairFormat;
RequestCurrencyPairFormat: RequestCurrencyPairFormat;
ClientID: string;
}
export interface Config {
Name: string;
EncryptConfig?: number;
Cryptocurrencies: string;
CurrencyExchangeProvider: string;
CurrencyPairFormat: CurrencyPairFormat;
PortfolioAddresses: PortfolioAddresses;
SMSGlobal: SMSGlobal;
Webserver: Webserver;
Exchanges: Exchange[];
}

View File

@@ -0,0 +1,26 @@
import { Injectable } from '@angular/core';
// If you import a module but never use any of the imported values other than as TypeScript types,
// the resulting javascript file will look as if you never imported the module at all.
import { ipcRenderer } from 'electron';
import * as childProcess from 'child_process';
@Injectable()
export class ElectronService {
ipcRenderer: typeof ipcRenderer;
childProcess: typeof childProcess;
constructor() {
// Conditional imports
if (this.isElectron()) {
this.ipcRenderer = window.require('electron').ipcRenderer;
this.childProcess = window.require('child_process');
}
}
isElectron = () => {
return window && window.process && window.process.type;
}
}

View File

@@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { WebsocketHandlerService } from './websocket-handler.service';
describe('WebsocketHandlerService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [WebsocketHandlerService]
});
});
it('should be created', inject([WebsocketHandlerService], (service: WebsocketHandlerService) => {
expect(service).toBeTruthy();
}));
});

View File

@@ -0,0 +1,44 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs/Rx';
import { WebsocketService } from './../../services/websocket/websocket.service';
const WEBSOCKET_URL = 'ws://localhost:9050/ws';
export interface Message {
Event: string,
data:any,
Exchange:string,
AssetType:string
}
@Injectable()
export class WebsocketHandlerService {
public messages: Subject<any>;
private authenticateMessage = {
Event:'auth',
data:{"username":"admin","password":"e7cf3ef4f17c3999a94f2c6f612e8a888e5b1026878e4e19398b23bd38ec221a"},
}
public authenticate() {
this.messages.next(this.authenticateMessage);
}
constructor(wsService: WebsocketService) {
this.messages = <Subject<Message>>wsService
.connect(WEBSOCKET_URL)
.map((response: MessageEvent): Message => {
let data = JSON.parse(response.data);
// variables aren't consistent yet. Here's a hack!
var dataData = data.Data === undefined ? data.data : data.Data;
var eventEvent = data.Event === undefined ? data.event : data.Event;
return {
Event: eventEvent,
data: dataData,
Exchange: data.exchange,
AssetType: data.assetType
}
});
}
}

View File

@@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { WebsocketService } from './websocket.service';
describe('WebsocketService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [WebsocketService]
});
});
it('should be created', inject([WebsocketService], (service: WebsocketService) => {
expect(service).toBeTruthy();
}));
});

View File

@@ -0,0 +1,45 @@
import { Injectable } from '@angular/core';
import * as Rx from 'rxjs/Rx';
@Injectable()
export class WebsocketService {
constructor() { }
private subject: Rx.Subject<MessageEvent>;
public connect(url): Rx.Subject<MessageEvent> {
if (!this.subject) {
this.subject = this.create(url);
}
return this.subject;
}
private authenticateMessage = {
Event:'auth',
data:{"username":"admin","password":"e7cf3ef4f17c3999a94f2c6f612e8a888e5b1026878e4e19398b23bd38ec221a"},
}
private isAuth = false;
private create(url): Rx.Subject<MessageEvent> {
let ws = new WebSocket(url);
let observable = Rx.Observable.create(
(obs: Rx.Observer<MessageEvent>) => {
ws.onmessage = obs.next.bind(obs);
ws.onerror = obs.error.bind(obs);
ws.onclose = obs.complete.bind(obs);
return ws.close.bind(ws);
})
let observer = {
next: (data: any) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(this.authenticateMessage));
ws.send(JSON.stringify(data));
}
}
}
return Rx.Subject.create(observer, observable);
}
}

View File

@@ -0,0 +1,8 @@
<div class="loading-spinner" *ngIf="tickerCards === null || tickerCards.length === 0">
<md-progress-spinner mode="indeterminate"></md-progress-spinner>
</div>
<md-grid-list cols="4" rowHeight="4:3" >
<md-grid-tile *ngFor="let ticker of tickerCards">
<app-exchange-currency-ticker [ticker]="ticker" ></app-exchange-currency-ticker>
</md-grid-tile>
</md-grid-list>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AllEnabledCurrencyTickersComponent } from './all-enabled-currency-tickers.component';
describe('AllEnabledCurrencyTickersComponent', () => {
let component: AllEnabledCurrencyTickersComponent;
let fixture: ComponentFixture<AllEnabledCurrencyTickersComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AllEnabledCurrencyTickersComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AllEnabledCurrencyTickersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,77 @@
import { Component, OnInit } from '@angular/core';
import { WebsocketHandlerService } from './../../services/websocket-handler/websocket-handler.service';
@Component({
selector: 'app-all-enabled-currency-tickers',
templateUrl: './all-enabled-currency-tickers.component.html',
styleUrls: ['./all-enabled-currency-tickers.component.scss']
})
export class AllEnabledCurrencyTickersComponent implements OnInit {
private ws: WebsocketHandlerService;
allCurrencies:ExchangeCurrency[];
tickerCards: TickerUpdate[];
constructor(private websocketHandler: WebsocketHandlerService) {
this.ws = websocketHandler;
this.allCurrencies = <ExchangeCurrency[]>[];
this.tickerCards = <TickerUpdate[]>[];
this.ws.messages.subscribe(msg => {
if (msg.Event === 'ticker_update') {
var modal = <ExchangeCurrency>{};
modal.currencyPair = msg.data.CurrencyPair;
modal.exchangeName = msg.Exchange;
var found = false;
for(var i = 0; i< this.allCurrencies.length; i++) {
if(this.allCurrencies[i].currencyPair === msg.data.CurrencyPair &&
this.allCurrencies[i].exchangeName === msg.Exchange) {
found = true;
}
}
if(!found) {
//time to add
var ticker = <TickerUpdate>msg.data;
ticker.Exchange = msg.Exchange;
this.tickerCards.push(ticker);
this.allCurrencies.push(modal);
} else {
//time to replace
for(var j = 0; j< this.tickerCards.length; j++) {
if(this.tickerCards[j].Exchange === msg.Exchange
&& this.tickerCards[j].CurrencyPair === msg.data.CurrencyPair) {
var ticker = <TickerUpdate>msg.data;
this.tickerCards[j] = ticker;
this.tickerCards[j].Exchange = msg.Exchange;
return;
}
}
}
}
});
}
ngOnInit() { }
}
export interface ExchangeCurrency {
currencyPair: string;
exchangeName:string;
}
export interface CurrencyPair {
delimiter: string;
first_currency: string;
second_currency: string;
}
export interface TickerUpdate {
Pair: CurrencyPair;
CurrencyPair: string;
Last: number;
High: number;
Low: number;
Bid: number;
Ask: number;
Volume: number;
PriceATH: number;
Exchange:string;
}

View File

@@ -0,0 +1,28 @@
<md-card class="exchange-card one-time-animation" >
<md-card-header>
<md-card-title>{{ticker?.Exchange}} {{ticker?.CurrencyPair}} Ticker update</md-card-title>
</md-card-header>
<md-card-content>
<md-grid-list cols="3" rowHeight="16:9" >
<md-grid-tile>
<p>Last: {{ticker?.Last | number:'1.0-1'}}</p>
</md-grid-tile>
<md-grid-tile>
<p> Low: {{ticker?.Low | number:'1.0-1'}}</p>
</md-grid-tile>
<md-grid-tile>
<p> High: {{ticker?.High | number:'1.0-1'}}</p>
</md-grid-tile>
<md-grid-tile>
<p> Bid: {{ticker?.Bid | number:'1.0-1'}}</p>
</md-grid-tile>
<md-grid-tile>
<p> Ask: {{ticker?.Ask | number:'1.0-1'}}</p>
</md-grid-tile>
<md-grid-tile>
<p>Volume: {{ticker?.Volume | number:'1.0-1'}}</p>
</md-grid-tile>
</md-grid-list>
</md-card-content>

Some files were not shown because too many files have changed in this diff Show More