Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

plumbing for backend API server, hitting /api/v1/version endpoint #166

Merged
merged 9 commits into from
May 6, 2019
55 changes: 46 additions & 9 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (

"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/rs/cors"
"github.com/spf13/cobra"
"github.com/stellar/kelp/gui"
"github.com/stellar/kelp/gui/backend"
)

var serverCmd = &cobra.Command{
Expand All @@ -22,20 +24,34 @@ var serverCmd = &cobra.Command{
}

type serverInputs struct {
port *uint16
dev *bool
port *uint16
dev *bool
devAPIPort *uint16
}

func init() {
options := serverInputs{}
options.port = serverCmd.Flags().Uint16P("port", "p", 8000, "port on which to serve")
options.dev = serverCmd.Flags().Bool("dev", false, "run in dev mode for hot-reloading of JS code")
options.devAPIPort = serverCmd.Flags().Uint16("dev-api-port", 8001, "port on which to run API server when in dev mode")

serverCmd.Run = func(ccmd *cobra.Command, args []string) {
s, e := backend.MakeAPIServer()
if e != nil {
panic(e)
}

if env == envDev && *options.dev {
checkHomeDir()
// the frontend app checks the REACT_APP_API_PORT variable to be set when serving
os.Setenv("REACT_APP_API_PORT", fmt.Sprintf("%d", *options.devAPIPort))
go runAPIServerDevBlocking(s, *options.port, *options.devAPIPort)
runWithYarn(options)
return
} else {
options.devAPIPort = nil
// the frontend app checks the REACT_APP_API_PORT variable to be set when serving
os.Setenv("REACT_APP_API_PORT", fmt.Sprintf("%d", *options.port))
}

if env == envDev {
Expand All @@ -44,21 +60,41 @@ func init() {
}

r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
setMiddleware(r)
backend.SetRoutes(r, s)
// gui.FS is automatically compiled based on whether this is a local or deployment build
gui.FileServer(r, "/", gui.FS)

portString := fmt.Sprintf(":%d", *options.port)
log.Printf("Serving on HTTP port: %d\n", *options.port)
e := http.ListenAndServe(portString, r)
log.Printf("Serving frontend and API server on HTTP port: %d\n", *options.port)
e = http.ListenAndServe(portString, r)
log.Fatal(e)
}
}

func setMiddleware(r *chi.Mux) {
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
}

func runAPIServerDevBlocking(s *backend.APIServer, frontendPort uint16, devAPIPort uint16) {
r := chi.NewRouter()
// Add CORS middleware around every request since both ports are different when running server in dev mode
r.Use(cors.New(cors.Options{
AllowedOrigins: []string{fmt.Sprintf("http://localhost:%d", frontendPort)},
}).Handler)

setMiddleware(r)
backend.SetRoutes(r, s)
portString := fmt.Sprintf(":%d", devAPIPort)
log.Printf("Serving API server on HTTP port: %d\n", devAPIPort)
e := http.ListenAndServe(portString, r)
log.Fatal(e)
}

func checkHomeDir() {
op, e := exec.Command("pwd").Output()
if e != nil {
Expand All @@ -75,6 +111,7 @@ func runWithYarn(options serverInputs) {
// yarn requires the PORT variable to be set when serving
os.Setenv("PORT", fmt.Sprintf("%d", *options.port))

log.Printf("Serving frontend via yarn on HTTP port: %d\n", *options.port)
e := runCommandStreamOutput(exec.Command("yarn", "--cwd", "gui/web", "start"))
if e != nil {
panic(e)
Expand Down
6 changes: 4 additions & 2 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@ import:
version: v4.0.2
- package: github.com/shurcooL/vfsgen
version: 6a9ea43bcacdf716a5c1b38efff722c07adf0069
- package: github.com/rs/cors
version: v1.6.0
34 changes: 34 additions & 0 deletions gui/backend/api_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package backend

import (
"fmt"
"os"
"os/exec"
"path/filepath"
)

// APIServer is an instance of the API service
type APIServer struct {
binPath string
}

// MakeAPIServer is a factory method
func MakeAPIServer() (*APIServer, error) {
binPath, e := filepath.Abs(os.Args[0])
if e != nil {
return nil, fmt.Errorf("could not get binPath of currently running binary: %s", e)
}

return &APIServer{
binPath: binPath,
}, nil
}

func (s *APIServer) runCommand(cmd string) ([]byte, error) {
cmdString := fmt.Sprintf("%s %s", s.binPath, cmd)
bytes, e := exec.Command("bash", "-c", cmdString).Output()
if e != nil {
return nil, fmt.Errorf("could not run bash command '%s': %s", cmd, e)
}
return bytes, nil
}
14 changes: 14 additions & 0 deletions gui/backend/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package backend

import (
"net/http"

"github.com/go-chi/chi"
)

// SetRoutes
func SetRoutes(r *chi.Mux, s *APIServer) {
r.Route("/api/v1", func(r chi.Router) {
r.Get("/version", http.HandlerFunc(s.version))
})
}
15 changes: 15 additions & 0 deletions gui/backend/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package backend

import (
"fmt"
"net/http"
)

func (s *APIServer) version(w http.ResponseWriter, r *http.Request) {
bytes, e := s.runCommand("version | grep version | cut -d':' -f3")
if e != nil {
w.Write([]byte(fmt.Sprintf("error in version command: %s\n", e)))
return
}
w.Write(bytes)
}
45 changes: 44 additions & 1 deletion gui/web/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,59 @@ import Header from './components/molecules/Header/Header';
import Bots from './components/screens/Bots/Bots';
import NewBot from './components/screens/NewBot/NewBot';
import Details from './components/screens/Details/Details';
// import Welcome from './components/molecules/Welcome/Welcome';
// import Modal from './components/molecules/Modal/Modal';

import version from './kelp-ops-api/version';

let baseUrl = function() {
let origin = window.location.origin
if (process.env.REACT_APP_API_PORT) {
let parts = origin.split(":")
return parts[0] + ":" + parts[1] + ":" + process.env.REACT_APP_API_PORT;
}
return origin;
}()

class App extends Component {
constructor(props) {
super(props);
this.state = {
version: ""
};
}

componentDidMount() {
this._asyncRequest = version(baseUrl).then(resp => {
this._asyncRequest = null;
this.setState({version: resp});
});
}

componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
this._asyncRequest = null;
}
}

render() {
return (
<div>
<Router>
<Header version="v1.04"/>
<Header version={this.state.version}/>
<Route exact path="/" component={Bots} />
<Route path="/new" component={NewBot} />
<Route path="/details" component={Details} />
</Router>
{/* <Modal
type="error"
title="Harry the Green Plankton has two warnings:"
actionLabel="Go to bot settings"
bullets={['Funds are low', 'Another warning example']}
/>
<Welcome/> */}
</div>
);
}
}
Expand Down
3 changes: 2 additions & 1 deletion gui/web/src/components/molecules/BotCard/BotCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class BotCard extends Component {
test: PropTypes.bool,
warnings: PropTypes.number,
errors: PropTypes.number,
showDetailsFn: PropTypes.func,
};

toggleBot() {
Expand Down Expand Up @@ -108,7 +109,7 @@ class BotCard extends Component {
</div>

<div className={styles.firstColumn}>
<h2 className={styles.title}>{this.props.name}</h2>
<h2 className={styles.title} onClick={this.props.showDetailsFn}>{this.props.name}</h2>
<div className={styles.botDetailsLine}>
<BotExchangeInfo/>
</div>
Expand Down
5 changes: 5 additions & 0 deletions gui/web/src/components/molecules/BotCard/BotCard.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ $small-spacing: 3px;
color: $color-contrast-1;
padding: 12/16+rem 22/16+rem;
display: flex;
cursor: default;
&:hover {
.sortingArrows {
opacity: 1;
Expand Down Expand Up @@ -77,6 +78,10 @@ $small-spacing: 3px;
font-weight: 400;
margin: 0;
margin-bottom: 0.3rem;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}

.botDetailsLine {
Expand Down
10 changes: 8 additions & 2 deletions gui/web/src/components/molecules/EmptyList/EmptyList.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';

import styles from './EmptyList.module.scss';

Expand All @@ -7,14 +8,19 @@ import Button from '../../atoms/Button/Button';


class EmptyList extends Component {
static propTypes = {
autogenerateFn: PropTypes.func.isRequired,
createBotFn: PropTypes.func.isRequired,
};

render() {
return (
<div className={styles.empty}>
<img src={emptyIcon} className={styles.icon} alt="Empty icon"/>
<h2 className={styles.title}>Your Kelp forest is empty</h2>
<Button onClick={this.props.onClick}>Autogenerate your first test bot</Button>
<Button onClick={this.props.autogenerateFn}>Autogenerate your first test bot</Button>
<span className={styles.separator}>or</span>
<Button onClick={this.props.onClick} variant="link">Create your first bot</Button>
<Button onClick={this.props.createBotFn} variant="link">Create your first bot</Button>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion gui/web/src/components/molecules/Form/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Form extends Component {
return (
<div>
<div className={grid.container}>
<ScreenHeader title="New Bot" backButton>
<ScreenHeader title="New Bot" backButtonFn={this.props.router.goBack}>
<Switch></Switch>
<Label>Helper Fields</Label>
</ScreenHeader>
Expand Down
21 changes: 13 additions & 8 deletions gui/web/src/components/molecules/ScreenHeader/ScreenHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@ import Button from '../../atoms/Button/Button';

class ScreenHeader extends Component {
render() {
let backButton = null;
if (this.props.backButtonFn) {
backButton = (
<div className={styles.buttonWrapper}>
<Button
icon="back"
variant="transparent"
hsize="round"
onClick={this.props.backButtonFn}/>
</div>
);
}
return (
<div className={styles.wrapper}>
<div className={styles.titleWrapper}>
{ this.props.backButton && (
<div className={styles.buttonWrapper}>
<Button
icon="back"
variant="transparent"
hsize="round"/>
</div>
)}
{backButton}
<ScreenTitle>{this.props.title}</ScreenTitle>
</div>
<div className={styles.childrenWrapper}>
Expand Down
Loading