Open a new port on satellite for admin GUI (#1901)

* Set up new port 8090 for in offers

Clean up commented code

Rename offers to offersweb

Remove unused code

Add todos for adding front-end templates

Add middleware for only allow local access

Add comment

Fix linting error

Remove commented code

Update storj-sim

Check request IP against Host IP

Use net pakcage to retrieve IP address

Rename service to marketing

* Add wrapper for all errors

* fix conflicts

* update the config file

* fix linting error

* remove unused packages

* remove global runtime var and add flag to storj-sim for mar static dir

* remove debugging lines

* add new config for test data and check if static dir flag is set before passing to mux

* change 'console' to 'marketing' for test data config

* fix linting errors

* update config flag

* Trigger Jenkins

* Trigger CLA
This commit is contained in:
Yingrong Zhao 2019-06-11 11:00:59 -04:00 committed by GitHub
parent 252c8ac189
commit af66d9c6e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 487 additions and 2 deletions

View File

@ -48,6 +48,7 @@ const (
publicGRPC = 0
privateGRPC = 1
publicHTTP = 2
privateHTTP = 3
debugHTTP = 9
)
@ -268,6 +269,8 @@ func newNetwork(flags *Flags) (*Processes, error) {
"--console.static-dir", filepath.Join(storjRoot, "web/satellite/"),
// TODO: remove console.auth-token after vanguard release
"--console.auth-token", consoleAuthToken,
"--marketing.address", net.JoinHostPort(host, port(satellitePeer, i, privateHTTP)),
"--marketing.static-dir", filepath.Join(storjRoot, "satellite/marketing/marketingweb/static/"),
"--server.address", process.Address,
"--server.private-address", net.JoinHostPort(host, port(satellitePeer, i, privateGRPC)),
@ -279,7 +282,6 @@ func newNetwork(flags *Flags) (*Processes, error) {
"--mail.smtp-server-address", "smtp.gmail.com:587",
"--mail.from", "Storj <yaroslav-satellite-test@storj.io>",
"--mail.template-path", filepath.Join(storjRoot, "web/satellite/static/emails"),
"--version.server-address", fmt.Sprintf("http://%s/", versioncontrol.Address),
"--debug.addr", net.JoinHostPort(host, port(satellitePeer, i, debugHTTP)),
},

2
go.mod
View File

@ -43,7 +43,7 @@ require (
github.com/google/go-cmp v0.3.0
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/gorilla/handlers v1.4.0 // indirect
github.com/gorilla/mux v1.7.0 // indirect
github.com/gorilla/mux v1.7.0
github.com/gorilla/rpc v1.1.0 // indirect
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
github.com/hashicorp/go-msgpack v0.5.3 // indirect

2
go.sum
View File

@ -131,7 +131,9 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.0.0-beta.2+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=

View File

@ -0,0 +1,116 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package marketingweb
import (
"context"
"html/template"
"net"
"net/http"
"github.com/gorilla/mux"
"github.com/zeebo/errs"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
)
// Error is satellite marketing error type
var Error = errs.Class("satellite marketing error")
// Config contains configuration for marketing offersweb server
type Config struct {
Address string `help:"server address of the marketing Admin GUI" default:"0.0.0.0:8090"`
StaticDir string `help:"path to static resources" default:""`
}
// Server represents marketing offersweb server
type Server struct {
log *zap.Logger
config Config
listener net.Listener
server http.Server
}
// The three pages contained in addPages are pages all templates require
// This exists in order to limit handler verbosity
func (s *Server) addPages(assets []string) []string {
rp := s.config.StaticDir + "/pages/"
pages := []string{rp + "base.html", rp + "index.html", rp + "banner.html"}
for _, page := range assets {
pages = append(pages, page)
}
return pages
}
// NewServer creates new instance of offersweb server
func NewServer(logger *zap.Logger, config Config, listener net.Listener) *Server {
server := Server{
log: logger,
config: config,
listener: listener,
}
logger.Sugar().Debugf("Starting Marketing Admin UI on %s...", server.listener.Addr().String())
fs := http.FileServer(http.Dir(server.config.StaticDir))
mux := mux.NewRouter()
if server.config.StaticDir != "" {
mux.Handle("/static/", http.StripPrefix("/static", fs))
mux.Handle("/", http.HandlerFunc(server.appHandler))
}
server.server = http.Server{
Handler: mux,
}
return &server
}
// appHandler is web app http handler function
func (s *Server) appHandler(w http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/" {
s.serveError(w, req)
return
}
rp := s.config.StaticDir + "/pages/"
pages := []string{rp + "home.html", rp + "refOffers.html", rp + "freeOffers.html", rp + "roModal.html", rp + "foModal.html"}
files := s.addPages(pages)
home := template.Must(template.New("landingPage").ParseFiles(files...))
err := home.ExecuteTemplate(w, "base", nil)
if err != nil {
s.serveError(w, req)
}
}
func (s *Server) serveError(w http.ResponseWriter, req *http.Request) {
rp := s.config.StaticDir + "/pages/"
files := s.addPages([]string{rp + "404.html"})
unavailable := template.Must(template.New("404").ParseFiles(files...))
err := unavailable.ExecuteTemplate(w, "base", nil)
if err != nil {
s.serveError(w, req)
}
}
// Run starts the server that host admin web app and api endpoint
func (s *Server) Run(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
var group errgroup.Group
group.Go(func() error {
<-ctx.Done()
return Error.Wrap(s.server.Shutdown(nil))
})
group.Go(func() error {
defer cancel()
return Error.Wrap(s.server.Serve(s.listener))
})
return group.Wait()
}
// Close closes server and underlying listener
func (s *Server) Close() error {
return Error.Wrap(s.server.Close())
}

View File

@ -0,0 +1,41 @@
html{
height:100vh;
width:100vw;
}
body{
height:100%;
width:100%;
background-color: #DDDDDD;
}
.home-link:hover,.home-link p:hover{
text-decoration: none;
}
.banner{
height: 80px;
overflow: hidden;
}
.banner-txt{
font-family: 'Inter';
font-size: 18px;
color:white;
}
.offer-label{
font-family: 'Inter';
font-size: 11px;
line-height: 49px;
letter-spacing: -0.100741px;
color: #656565;
}
.toggler{
margin-top: -15px;
}
.toggler:hover{
cursor:pointer;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,3 @@
{{define "main"}}
<h1>404 unavailable</h1>
{{end}}

View File

@ -0,0 +1,13 @@
{{define "banner"}}
<div class="container-fluid bg-secondary banner w-100">
<div class="row">
<a class="home-link" href="/">
<img class="ml-5" src="/static/img/horizontal-tar-white.svg" height="75" width="180">
</a>
<span class="ml-3 mr-3 mt-4 text-white">|</span>
<a class="home-link" href="/">
<p class="mt-4 banner-txt text-white">Marketing Credits Dashboard</p>
</a>
</div>
</div>
{{end}}

View File

@ -0,0 +1,12 @@
{{define "base"}}
<!DOCTYPE html>
<html>
<head>
{{template "head" .}}
</head>
<body>
{{template "body" .}}
{{template "bootstrap" .}}
</body>
</html>
{{end}}

View File

@ -0,0 +1,50 @@
{{define "foModal"}}
<div class="modal fade" id="foModal" tabindex="-1" role="dialog" aria-labelledby="foModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="foModalLabel">Create Free Credit</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-row">
<div class="form-group col-md-4">
<label for="offer-name">Offer Name</label>
<input type="text" class="form-control" id="offer-name" placeholder="May Referral">
</div>
<div class="form-group col-md-4">
<label for="description">Description</label>
<input type="text" class="form-control" id="description" placeholder="Our test with $50 for May">
</div>
<div class="form-group col-md-4">
<label for="expiration">Credit Exp Date</label>
<input type="date" class="form-control" id="expiration" placeholder="06/01/19">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-4">
<label for="give-credit">Give Credit</label>
<input type="number" id="give-credit" value="$50">
</div>
<div class="form-group col-md-4">
<label for="give-credit-exp">Give Credit Exp.</label>
<input type="date" class="form-control" id="give-credit-exp">
</div>
<div class="form-group col-md-4">
<label for="redeemable-capacity">Redeemable Capacity</label>
<input type="text" class="form-control" id="redeemable-capacity">
</div>
</div>
<div class="m-1 text-left">
<button type="submit" class="btn btn-secondary">Create Offer</button>
<a class="ml-3" href="/">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
{{end}}

View File

@ -0,0 +1,51 @@
{{define "freeOffers"}}
<table class="w-100 mt-5 mb-5 rounded table table-light table-responsive">
<tr>
<th>Name</th>
<th>Give Credit</th>
<th>Free Credits Used</th>
<th>Redeemable Capacity</th>
<th>Created</th>
<th>Expiration</th>
<th>Status</th>
</tr>
<th class="text-dark m-2"><p>DEFAULT&nbsp;OFFER</p></th>
<tr>
<td>Default Free Credit</td>
<td>$20</td>
<td>48</td>
<td>N/A</td>
<td>04/18/19</td>
<td>N/A</td>
<td><img class="toggler" src="/static/img/on.jpg" height="50" width="50"></td>
</tr>
<th class="text-dark m-2"><p>CUSTOM&nbsp;OFFER(S)</p></th>
<tr>
<td>May Free Credit</td>
<td>$50</td>
<td>30</td>
<td>200</td>
<td>05/18/19</td>
<td>06/18/2019</td>
<td><img class="toggler" src="/static/img/off.jpg" height="50" width="50"></td>
</tr>
<tr>
<td>June Free Credit</td>
<td>$40</td>
<td>0</td>
<td>200</td>
<td>05/18/19</td>
<td>06/18/2019</td>
<td><img class="toggler" src="/static/img/off.jpg" height="50" width="50"></td>
</tr>
<tr>
<td>July Free Credit</td>
<td>$30</td>
<td>0</td>
<td>200</td>
<td>07/01/19</td>
<td>08/01/2019</td>
<td><img class="toggler" src="/static/img/off.jpg" height="50" width="50"></td>
</tr>
</table>
{{end}}

View File

@ -0,0 +1,16 @@
{{define "main"}}
<div class="container">
<div class="row mt-4 mb-4">
<h3 class="m-4 text-dark">Referral Credit Offers</h3>
<button type="button" class="btn btn-outline-dark m-4" data-toggle="modal" data-target="#roModal">+ Create Referral Credit</button>
</div>
{{template "refOffers" .}}
{{template "roModal" .}}
<div class="row mt-4 mb-4">
<h3 class="m-4 text-dark">Free Credit Offers</h3>
<button type="button" class="btn btn-outline-dark m-4" data-toggle="modal" data-target="#foModal">+ Create Free Credit</button>
</div>
{{template "freeOffers" .}}
{{template "foModal" .}}
</div>
{{end}}

View File

@ -0,0 +1,24 @@
{{define "head"}}
<title>Referral Admin Portal</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/static/css/style.css">
{{end}}
{{define "body"}}
{{template "banner" .}}
<div class="container">
<div class="row justify-content-md-center">
<div class="col-12">
<div class="text-center">
{{block "main" .}}{{end}}
</div>
</div>
</div>
</div>
{{end}}
{{ define "bootstrap" }}
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
{{end}}

View File

@ -0,0 +1,60 @@
{{define "refOffers"}}
<table class="w-100 mt-5 mb-5 rounded table table-light table-responsive">
<thead>
<tr>
<th>Name</th>
<th>Give Credit</th>
<th>Get Credit</th>
<th>Referrals Used</th>
<th>Redeemable Capacity</th>
<th>Created</th>
<th>Expiration</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<th class="text-dark m-2"><p>CURRENT&nbsp;OFFER</p></th>
<tr>
<td>May Referral</td>
<td>$50</td>
<td>$50</td>
<td>48</td>
<td>200</td>
<td>05/01/19</td>
<td>06/01/19</td>
<th><img class="toggler" src="/static/img/on.jpg" height="50" width="50"></th>
</tr>
<th class="text-dark m-2"><p>OTHER&nbsp;OFFERS</p></th>
<tr>
<td>Default Offer</td>
<td>$20</td>
<td>$20</td>
<td>30</td>
<td>N/A</td>
<td>05/18/19</td>
<td>N/A</td>
<th><img class="toggler" src="/static/img/off.jpg" height="50" width="50"></th>
</tr>
<tr>
<td>June Referral</td>
<td>$50</td>
<td>$50</td>
<td>0</td>
<td>200</td>
<td>05/18/19</td>
<td>06/18/2019</td>
<th><img class="toggler" src="/static/img/off.jpg" height="50" width="50"></th>
</tr>
<tr>
<td>June Referral</td>
<td>$50</td>
<td>$50</td>
<td>0</td>
<td>200</td>
<td>05/18/19</td>
<td>06/18/2019</td>
<th><img class="toggler" src="/static/img/off.jpg" height="50" width="50"></th>
</tr>
</tbody>
</table>
{{end}}

View File

@ -0,0 +1,56 @@
{{define "roModal"}}
<div class="modal fade" id="roModal" tabindex="-1" role="dialog" aria-labelledby="roModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="roModalLabel">Create Referral Credit</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-row">
<div class="form-group col-md-4">
<label for="offer-name">Offer Name</label>
<input type="text" class="form-control" id="offer-name" placeholder="May Referral">
</div>
<div class="form-group col-md-4">
<label for="description">Description</label>
<input type="text" class="form-control" id="description" placeholder="Our test with $50 for May">
</div>
<div class="form-group col-md-4">
<label for="expiration">Credit Exp Date</label>
<input type="date" class="form-control" id="expiration" placeholder="06/01/19">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-2">
<label for="give-credit">Give Credit</label>
<input type="number" class="form-control" id="give-credit" value="$50"> </div>
<div class="form-group col-md-2">
<label for="give-credit-exp">Give Credit Exp.</label>
<input type="date" class="form-control" id="give-credit-exp">
</div>
<div class="form-group col-md-2">
<label for="get-credit">Get Credit</label>
<input type="number" class="form-control" id="get-credit" value="$50"> </div>
<div class="form-group col-md-3">
<label for="get-credit-exp">Get Credit Exp.</label>
<input type="date" class="form-control" id="get-credit-exp">
</div>
<div class="form-group col-md-3">
<label for="redeemable-capacity">Redeemable Capacity</label>
<input type="text" class="form-control" id="redeemable-capacity">
</div>
</div>
<div class="m-1 text-left">
<button type="submit" class="btn btn-secondary">Create Offer</button>
<a class="ml-3" href="/">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
{{end}}

View File

@ -50,6 +50,7 @@ import (
"storj.io/storj/satellite/mailservice"
"storj.io/storj/satellite/mailservice/simulate"
"storj.io/storj/satellite/marketing"
"storj.io/storj/satellite/marketing/marketingweb"
"storj.io/storj/satellite/metainfo"
"storj.io/storj/satellite/orders"
"storj.io/storj/satellite/payments"
@ -120,6 +121,8 @@ type Config struct {
Mail mailservice.Config
Console consoleweb.Config
Marketing marketingweb.Config
Vouchers vouchers.Config
Version version.Config
@ -204,6 +207,11 @@ type Peer struct {
Service *console.Service
Endpoint *consoleweb.Server
}
Marketing struct {
Listener net.Listener
Endpoint *marketingweb.Server
}
}
// New creates a new satellite
@ -579,6 +587,22 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, config *Config, ve
)
}
{ // setup marketing portal
log.Debug("Setting up marketing server")
marketingConfig := config.Marketing
peer.Marketing.Listener, err = net.Listen("tcp", marketingConfig.Address)
if err != nil {
return nil, errs.Combine(err, peer.Close())
}
peer.Marketing.Endpoint = marketingweb.NewServer(
peer.Log.Named("marketing:endpoint"),
marketingConfig,
peer.Marketing.Listener,
)
}
return peer, nil
}
@ -626,6 +650,9 @@ func (peer *Peer) Run(ctx context.Context) (err error) {
group.Go(func() error {
return errs2.IgnoreCanceled(peer.Console.Endpoint.Run(ctx))
})
group.Go(func() error {
return errs2.IgnoreCanceled(peer.Marketing.Endpoint.Run(ctx))
})
return group.Wait()
}
@ -651,6 +678,12 @@ func (peer *Peer) Close() error {
errlist.Add(peer.Mail.Service.Close())
}
if peer.Marketing.Endpoint != nil {
errlist.Add(peer.Marketing.Endpoint.Close())
} else if peer.Marketing.Listener != nil {
errlist.Add(peer.Marketing.Listener.Close())
}
// close services in reverse initialization order
if peer.Repair.Repairer != nil {
errlist.Add(peer.Repair.Repairer.Close())

View File

@ -157,6 +157,12 @@ kademlia.operator.wallet: ""
# uri which is used when retrieving new access token
# mail.token-uri: ""
# server address of the marketing Admin GUI
# marketing.address: "0.0.0.0:8090"
# path to static resources
# marketing.static-dir: ""
# lifespan of bandwidth agreements in days
# metainfo.bw-expiration: 45