Merge 'main' branch.
Change-Id: I6e8162d1a6caf75e89c9f9c9f9522730aebf83ae
11
Makefile
@ -73,6 +73,11 @@ build-storagenode-npm:
|
||||
|
||||
##@ Simulator
|
||||
|
||||
# Allow the caller to set GATEWAYPATH if desired. This controls where the new
|
||||
# go module is created to install the specific gateway version.
|
||||
ifndef GATEWAYPATH
|
||||
GATEWAYPATH=.build/gateway-tmp
|
||||
endif
|
||||
.PHONY: install-sim
|
||||
install-sim: ## install storj-sim
|
||||
@echo "Running ${@}"
|
||||
@ -86,9 +91,9 @@ install-sim: ## install storj-sim
|
||||
storj.io/storj/cmd/certificates
|
||||
|
||||
## install exact version of storj/gateway
|
||||
mkdir -p .build/gateway-tmp
|
||||
-cd .build/gateway-tmp && go mod init gatewaybuild
|
||||
cd .build/gateway-tmp && GO111MODULE=on go get storj.io/gateway@multipart-upload
|
||||
mkdir -p ${GATEWAYPATH}
|
||||
-cd ${GATEWAYPATH} && go mod init gatewaybuild
|
||||
cd ${GATEWAYPATH} && GO111MODULE=on go get storj.io/gateway@multipart-upload
|
||||
|
||||
##@ Test
|
||||
|
||||
|
@ -133,7 +133,7 @@ func init() {
|
||||
process.Bind(dashboardCmd, &dashboardCfg, defaults, cfgstruct.ConfDir(defaultDiagDir))
|
||||
process.Bind(gracefulExitInitCmd, &diagCfg, defaults, cfgstruct.ConfDir(defaultDiagDir))
|
||||
process.Bind(gracefulExitStatusCmd, &diagCfg, defaults, cfgstruct.ConfDir(defaultDiagDir))
|
||||
process.Bind(issueAPITokenCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
process.Bind(issueAPITokenCmd, &diagCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||
}
|
||||
|
||||
func cmdRun(cmd *cobra.Command, args []string) (err error) {
|
||||
@ -312,14 +312,14 @@ func cmdIssue(cmd *cobra.Command, args []string) (err error) {
|
||||
err = errs.Combine(err, db.Close())
|
||||
}()
|
||||
|
||||
service := apikeys.NewService(db.Secret())
|
||||
service := apikeys.NewService(db.APIKeys())
|
||||
|
||||
apiKey, err := service.Issue(ctx)
|
||||
if err != nil {
|
||||
return errs.New("Error while trying to issue new api key: %v", err)
|
||||
}
|
||||
|
||||
fmt.Print(apiKey.Secret.String())
|
||||
fmt.Println(apiKey.Secret.String())
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -25,7 +25,8 @@ type Flags struct {
|
||||
StorageNodeCount int
|
||||
Identities int
|
||||
|
||||
IsDev bool
|
||||
IsDev bool
|
||||
FailFast bool
|
||||
|
||||
OnlyEnv bool // only do things necessary for loading env vars
|
||||
|
||||
@ -64,7 +65,8 @@ func main() {
|
||||
rootCmd.PersistentFlags().IntVarP(&flags.Identities, "identities", "", 10, "number of identities to create")
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&printCommands, "print-commands", "x", false, "print commands as they are run")
|
||||
rootCmd.PersistentFlags().BoolVarP(&flags.IsDev, "dev", "", false, "use configuration values tuned for development")
|
||||
rootCmd.PersistentFlags().BoolVarP(&flags.IsDev, "dev", "", true, "use configuration values tuned for development")
|
||||
rootCmd.PersistentFlags().BoolVarP(&flags.FailFast, "failfast", "", true, "stop all processes when one of the processes fails")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&flags.Postgres, "postgres", "", os.Getenv("STORJ_SIM_POSTGRES"), "connection string for postgres (defaults to STORJ_SIM_POSTGRES)")
|
||||
rootCmd.PersistentFlags().StringVarP(&flags.Redis, "redis", "", os.Getenv("STORJ_SIM_REDIS"), "connection string for redis e.g. 127.0.0.1:6379 (defaults to STORJ_SIM_REDIS)")
|
||||
|
@ -169,8 +169,14 @@ func networkTest(flags *Flags, command string, args []string) error {
|
||||
|
||||
ctx, cancel := NewCLIContext(context.Background())
|
||||
|
||||
var group errgroup.Group
|
||||
processes.Start(ctx, &group, "run")
|
||||
var group *errgroup.Group
|
||||
if processes.FailFast {
|
||||
group, ctx = errgroup.WithContext(ctx)
|
||||
} else {
|
||||
group = &errgroup.Group{}
|
||||
}
|
||||
|
||||
processes.Start(ctx, group, "run")
|
||||
|
||||
for _, process := range processes.List {
|
||||
process.Status.Started.Wait(ctx)
|
||||
@ -218,14 +224,19 @@ func newNetwork(flags *Flags) (*Processes, error) {
|
||||
common := []string{"--metrics.app-suffix", "sim", "--log.level", "debug", "--config-dir", dir}
|
||||
if flags.IsDev {
|
||||
common = append(common, "--defaults", "dev")
|
||||
} else {
|
||||
common = append(common, "--defaults", "release")
|
||||
}
|
||||
for command, args := range all {
|
||||
all[command] = append(append(common, command), args...)
|
||||
full := append([]string{}, common...)
|
||||
full = append(full, command)
|
||||
full = append(full, args...)
|
||||
all[command] = full
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
processes := NewProcesses(flags.Directory)
|
||||
processes := NewProcesses(flags.Directory, flags.FailFast)
|
||||
|
||||
var host = flags.Host
|
||||
versioncontrol := processes.New(Info{
|
||||
@ -315,13 +326,17 @@ func newNetwork(flags *Flags) (*Processes, error) {
|
||||
apiProcess.Arguments = withCommon(apiProcess.Directory, Arguments{
|
||||
"setup": {
|
||||
"--identity-dir", apiProcess.Directory,
|
||||
|
||||
"--console.address", net.JoinHostPort(host, port(satellitePeer, i, publicHTTP)),
|
||||
"--console.static-dir", filepath.Join(storjRoot, "web/satellite/"),
|
||||
"--console.auth-token-secret", "my-suppa-secret-key",
|
||||
"--console.open-registration-enabled",
|
||||
"--console.rate-limit.burst", "100",
|
||||
|
||||
"--marketing.base-url", "",
|
||||
"--marketing.address", net.JoinHostPort(host, port(satellitePeer, i, privateHTTP)),
|
||||
"--marketing.static-dir", filepath.Join(storjRoot, "web/marketing/"),
|
||||
|
||||
"--server.address", apiProcess.Address,
|
||||
"--server.private-address", net.JoinHostPort(host, port(satellitePeer, i, privateRPC)),
|
||||
|
||||
@ -635,7 +650,7 @@ func newNetwork(flags *Flags) (*Processes, error) {
|
||||
}
|
||||
|
||||
func identitySetup(network *Processes) (*Processes, error) {
|
||||
processes := NewProcesses(network.Directory)
|
||||
processes := NewProcesses(network.Directory, network.FailFast)
|
||||
|
||||
for _, process := range network.List {
|
||||
if process.Info.Executable == "gateway" || process.Info.Executable == "redis-server" {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -29,25 +30,33 @@ type Processes struct {
|
||||
Directory string
|
||||
List []*Process
|
||||
|
||||
FailFast bool
|
||||
MaxStartupWait time.Duration
|
||||
}
|
||||
|
||||
const storjSimMaxLineLen = 10000
|
||||
|
||||
// NewProcesses returns a group of processes.
|
||||
func NewProcesses(dir string) *Processes {
|
||||
func NewProcesses(dir string, failfast bool) *Processes {
|
||||
return &Processes{
|
||||
Output: NewPrefixWriter("sim", storjSimMaxLineLen, os.Stdout),
|
||||
Directory: dir,
|
||||
List: nil,
|
||||
Output: NewPrefixWriter("sim", storjSimMaxLineLen, os.Stdout),
|
||||
Directory: dir,
|
||||
List: nil,
|
||||
|
||||
FailFast: failfast,
|
||||
MaxStartupWait: time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
// Exec executes a command on all processes.
|
||||
func (processes *Processes) Exec(ctx context.Context, command string) error {
|
||||
var group errgroup.Group
|
||||
processes.Start(ctx, &group, command)
|
||||
var group *errgroup.Group
|
||||
if processes.FailFast {
|
||||
group, ctx = errgroup.WithContext(ctx)
|
||||
} else {
|
||||
group = &errgroup.Group{}
|
||||
}
|
||||
processes.Start(ctx, group, command)
|
||||
return group.Wait()
|
||||
}
|
||||
|
||||
@ -221,7 +230,7 @@ func (process *Process) Exec(ctx context.Context, command string) (err error) {
|
||||
executable := process.Executable
|
||||
|
||||
// use executable inside the directory, if it exists
|
||||
localExecutable := filepath.Join(process.Directory, executable)
|
||||
localExecutable := exe(filepath.Join(process.Directory, executable))
|
||||
if _, err := os.Lstat(localExecutable); !os.IsNotExist(err) {
|
||||
executable = localExecutable
|
||||
}
|
||||
@ -335,3 +344,10 @@ func tryConnect(address string) bool {
|
||||
|
||||
// Close closes process resources.
|
||||
func (process *Process) Close() error { return nil }
|
||||
|
||||
func exe(name string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return name + ".exe"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ func parseAccess(access string) (sa string, apiKey string, ea string, err error)
|
||||
}
|
||||
|
||||
func accessRegister(cmd *cobra.Command, args []string) (err error) {
|
||||
access, err := getAccessFromArgZeroOrConfig(inspectCfg, args)
|
||||
access, err := getAccessFromArgZeroOrConfig(registerCfg.AccessConfig, args)
|
||||
if err != nil {
|
||||
return errs.New("no access specified: %w", err)
|
||||
}
|
||||
@ -168,7 +168,7 @@ func accessRegister(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
func getAccessFromArgZeroOrConfig(config AccessConfig, args []string) (access *uplink.Access, err error) {
|
||||
if len(args) != 0 {
|
||||
access, err = inspectCfg.GetNamedAccess(args[0])
|
||||
access, err = config.GetNamedAccess(args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -177,7 +177,7 @@ func getAccessFromArgZeroOrConfig(config AccessConfig, args []string) (access *u
|
||||
}
|
||||
return uplink.ParseAccess(args[0])
|
||||
}
|
||||
return inspectCfg.GetAccess()
|
||||
return config.GetAccess()
|
||||
}
|
||||
|
||||
// RegisterAccess registers an access grant with a Gateway Authorization Service.
|
||||
|
2
go.mod
@ -46,7 +46,7 @@ require (
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
|
||||
google.golang.org/api v0.20.0 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
storj.io/common v0.0.0-20201210184814-6206aefd1d48
|
||||
storj.io/common v0.0.0-20210104180112-e8500e1c37a0
|
||||
storj.io/drpc v0.0.16
|
||||
storj.io/monkit-jaeger v0.0.0-20200518165323-80778fc3f91b
|
||||
storj.io/private v0.0.0-20201126162939-6fbb1e924f51
|
||||
|
2
go.sum
@ -810,6 +810,8 @@ storj.io/common v0.0.0-20200424175742-65ac59022f4f/go.mod h1:pZyXiIE7bGETIRXtfs0
|
||||
storj.io/common v0.0.0-20201026135900-1aaeec90670b/go.mod h1:GqdmNf3fLm2UZX/7Zr0BLFCJ4gFjgm6eHrk/fnmr5jQ=
|
||||
storj.io/common v0.0.0-20201210184814-6206aefd1d48 h1:bxIYHG96eFQNsEazsICfiEHjFwo1YqqbXkGfg72d2mg=
|
||||
storj.io/common v0.0.0-20201210184814-6206aefd1d48/go.mod h1:6sepaQTRLuygvA+GNPzdgRPOB1+wFfjde76KBWofbMY=
|
||||
storj.io/common v0.0.0-20210104180112-e8500e1c37a0 h1:3EisqXNx2mjd1g+oBSlz1z5s3X7UFc+pXst2aNN/1m8=
|
||||
storj.io/common v0.0.0-20210104180112-e8500e1c37a0/go.mod h1:GhZn7vlakLMJBMePwaMvaNUS45FhqMTVWzAn7dZxLOg=
|
||||
storj.io/drpc v0.0.11/go.mod h1:TiFc2obNjL9/3isMW1Rpxjy8V9uE0B2HMeMFGiiI7Iw=
|
||||
storj.io/drpc v0.0.14/go.mod h1:82nfl+6YwRwF6UG31cEWWUqv/FaKvP5SGqUvoqTxCMA=
|
||||
storj.io/drpc v0.0.16 h1:9sxypc5lKi/0D69cR21BR0S21+IvXfON8L5nXMVNTwQ=
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
"storj.io/common/storj"
|
||||
"storj.io/storj/multinode/nodes"
|
||||
"storj.io/storj/private/multinodeauth"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -59,13 +60,13 @@ func (controller *Nodes) Add(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
apiSecret, err := nodes.APISecretFromBase64(payload.APISecret)
|
||||
apiSecret, err := multinodeauth.SecretFromBase64(payload.APISecret)
|
||||
if err != nil {
|
||||
controller.serveError(w, http.StatusBadRequest, ErrNodes.Wrap(err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = controller.service.Add(ctx, id, apiSecret, payload.PublicAddress); err != nil {
|
||||
if err = controller.service.Add(ctx, id, apiSecret[:], payload.PublicAddress); err != nil {
|
||||
// TODO: add more error checks in future, like bad payload if address is invalid or unauthorized if secret invalid.
|
||||
controller.log.Error("add node internal error", zap.Error(err))
|
||||
controller.serveError(w, http.StatusInternalServerError, ErrNodes.Wrap(err))
|
||||
@ -150,14 +151,14 @@ func (controller *Nodes) List(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
nodes, err := controller.service.List(ctx)
|
||||
list, err := controller.service.List(ctx)
|
||||
if err != nil {
|
||||
controller.log.Error("list nodes internal error", zap.Error(err))
|
||||
controller.serveError(w, http.StatusInternalServerError, ErrNodes.Wrap(err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = json.NewEncoder(w).Encode(nodes); err != nil {
|
||||
if err = json.NewEncoder(w).Encode(list); err != nil {
|
||||
controller.log.Error("failed to write json response", zap.Error(err))
|
||||
return
|
||||
}
|
||||
@ -193,6 +194,75 @@ func (controller *Nodes) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// ListInfos handles node basic info list retrieval.
|
||||
func (controller *Nodes) ListInfos(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
infos, err := controller.service.ListInfos(ctx)
|
||||
if err != nil {
|
||||
controller.log.Error("list node infos internal error", zap.Error(err))
|
||||
controller.serveError(w, http.StatusInternalServerError, ErrNodes.Wrap(err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = json.NewEncoder(w).Encode(infos); err != nil {
|
||||
controller.log.Error("failed to write json response", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ListInfosSatellite handles node satellite specific info list retrieval.
|
||||
func (controller *Nodes) ListInfosSatellite(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
satelliteID, err := storj.NodeIDFromString(vars["satelliteID"])
|
||||
if err != nil {
|
||||
controller.serveError(w, http.StatusBadRequest, ErrNodes.Wrap(err))
|
||||
return
|
||||
}
|
||||
|
||||
infos, err := controller.service.ListInfosSatellite(ctx, satelliteID)
|
||||
if err != nil {
|
||||
controller.log.Error("list node satellite infos internal error", zap.Error(err))
|
||||
controller.serveError(w, http.StatusInternalServerError, ErrNodes.Wrap(err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = json.NewEncoder(w).Encode(infos); err != nil {
|
||||
controller.log.Error("failed to write json response", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TrustedSatellites handles retrieval of unique trusted satellites node urls list.
|
||||
func (controller *Nodes) TrustedSatellites(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
nodeURLs, err := controller.service.TrustedSatellites(ctx)
|
||||
if err != nil {
|
||||
controller.log.Error("list node trusted satellites internal error", zap.Error(err))
|
||||
controller.serveError(w, http.StatusInternalServerError, ErrNodes.Wrap(err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = json.NewEncoder(w).Encode(nodeURLs); err != nil {
|
||||
controller.log.Error("failed to write json response", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// serveError set http statuses and send json error.
|
||||
func (controller *Nodes) serveError(w http.ResponseWriter, status int, err error) {
|
||||
w.WriteHeader(status)
|
||||
|
@ -58,6 +58,9 @@ func NewServer(log *zap.Logger, config Config, nodes *nodes.Service, listener ne
|
||||
nodesRouter := apiRouter.PathPrefix("/nodes").Subrouter()
|
||||
nodesRouter.HandleFunc("", nodesController.Add).Methods(http.MethodPost)
|
||||
nodesRouter.HandleFunc("", nodesController.List).Methods(http.MethodGet)
|
||||
nodesRouter.HandleFunc("/infos", nodesController.ListInfos).Methods(http.MethodGet)
|
||||
nodesRouter.HandleFunc("/infos/{satelliteID}", nodesController.ListInfosSatellite).Methods(http.MethodGet)
|
||||
nodesRouter.HandleFunc("/trusted-satellites", nodesController.TrustedSatellites).Methods(http.MethodGet)
|
||||
nodesRouter.HandleFunc("/{id}", nodesController.Get).Methods(http.MethodGet)
|
||||
nodesRouter.HandleFunc("/{id}", nodesController.UpdateName).Methods(http.MethodPatch)
|
||||
nodesRouter.HandleFunc("/{id}", nodesController.Delete).Methods(http.MethodDelete)
|
||||
|
@ -5,15 +5,13 @@ package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"time"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/common/storj"
|
||||
)
|
||||
|
||||
// TODO: should this file be placed outside of console in nodes package?
|
||||
|
||||
// DB exposes needed by MND NodesDB functionality.
|
||||
//
|
||||
// architecture: Database
|
||||
@ -42,7 +40,24 @@ type Node struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// APISecretFromBase64 decodes API secret from base 64 string.
|
||||
func APISecretFromBase64(s string) ([]byte, error) {
|
||||
return base64.URLEncoding.DecodeString(s)
|
||||
// NodeInfo contains basic node internal state.
|
||||
type NodeInfo struct {
|
||||
ID storj.NodeID
|
||||
Name string
|
||||
Version string
|
||||
LastContact time.Time
|
||||
DiskSpaceUsed int64
|
||||
DiskSpaceLeft int64
|
||||
BandwidthUsed int64
|
||||
}
|
||||
|
||||
// NodeInfoSatellite contains satellite specific node internal state.
|
||||
type NodeInfoSatellite struct {
|
||||
ID storj.NodeID
|
||||
Name string
|
||||
Version string
|
||||
LastContact time.Time
|
||||
OnlineScore float64
|
||||
AuditScore float64
|
||||
SuspensionScore float64
|
||||
}
|
||||
|
@ -4,13 +4,16 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/common/rpc"
|
||||
"storj.io/common/storj"
|
||||
"storj.io/storj/private/multinodepb"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -24,15 +27,17 @@ var (
|
||||
//
|
||||
// architecture: Service
|
||||
type Service struct {
|
||||
log *zap.Logger
|
||||
nodes DB
|
||||
log *zap.Logger
|
||||
dialer rpc.Dialer
|
||||
nodes DB
|
||||
}
|
||||
|
||||
// NewService creates new instance of Service.
|
||||
func NewService(log *zap.Logger, nodes DB) *Service {
|
||||
func NewService(log *zap.Logger, dialer rpc.Dialer, nodes DB) *Service {
|
||||
return &Service{
|
||||
log: log,
|
||||
nodes: nodes,
|
||||
log: log,
|
||||
dialer: dialer,
|
||||
nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,3 +83,227 @@ func (service *Service) Remove(ctx context.Context, id storj.NodeID) (err error)
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
return Error.Wrap(service.nodes.Remove(ctx, id))
|
||||
}
|
||||
|
||||
// ListInfos queries node basic info from all nodes via rpc.
|
||||
func (service *Service) ListInfos(ctx context.Context) (_ []NodeInfo, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
nodes, err := service.nodes.List(ctx)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
var infos []NodeInfo
|
||||
for _, node := range nodes {
|
||||
info, err := func() (_ NodeInfo, err error) {
|
||||
conn, err := service.dialer.DialNodeURL(ctx, storj.NodeURL{
|
||||
ID: node.ID,
|
||||
Address: node.PublicAddress,
|
||||
})
|
||||
if err != nil {
|
||||
return NodeInfo{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = errs.Combine(err, conn.Close())
|
||||
}()
|
||||
|
||||
nodeClient := multinodepb.NewDRPCNodeClient(conn)
|
||||
storageClient := multinodepb.NewDRPCStorageClient(conn)
|
||||
bandwidthClient := multinodepb.NewDRPCBandwidthClient(conn)
|
||||
|
||||
header := &multinodepb.RequestHeader{
|
||||
ApiKey: node.APISecret,
|
||||
}
|
||||
|
||||
nodeVersion, err := nodeClient.Version(ctx, &multinodepb.VersionRequest{Header: header})
|
||||
if err != nil {
|
||||
return NodeInfo{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
lastContact, err := nodeClient.LastContact(ctx, &multinodepb.LastContactRequest{Header: header})
|
||||
if err != nil {
|
||||
return NodeInfo{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
diskSpace, err := storageClient.DiskSpace(ctx, &multinodepb.DiskSpaceRequest{Header: header})
|
||||
if err != nil {
|
||||
return NodeInfo{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
bandwidthSummaryRequest := &multinodepb.BandwidthMonthSummaryRequest{
|
||||
Header: header,
|
||||
}
|
||||
bandwidthSummary, err := bandwidthClient.MonthSummary(ctx, bandwidthSummaryRequest)
|
||||
if err != nil {
|
||||
return NodeInfo{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
return NodeInfo{
|
||||
ID: node.ID,
|
||||
Name: node.Name,
|
||||
Version: nodeVersion.Version,
|
||||
LastContact: lastContact.LastContact,
|
||||
DiskSpaceUsed: diskSpace.GetUsedPieces() + diskSpace.GetUsedTrash(),
|
||||
DiskSpaceLeft: diskSpace.GetAvailable(),
|
||||
BandwidthUsed: bandwidthSummary.GetUsed(),
|
||||
}, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
// ListInfosSatellite queries node satellite specific info from all nodes via rpc.
|
||||
func (service *Service) ListInfosSatellite(ctx context.Context, satelliteID storj.NodeID) (_ []NodeInfoSatellite, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
nodes, err := service.nodes.List(ctx)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
var infos []NodeInfoSatellite
|
||||
for _, node := range nodes {
|
||||
info, err := func() (_ NodeInfoSatellite, err error) {
|
||||
conn, err := service.dialer.DialNodeURL(ctx, storj.NodeURL{
|
||||
ID: node.ID,
|
||||
Address: node.PublicAddress,
|
||||
})
|
||||
if err != nil {
|
||||
return NodeInfoSatellite{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = errs.Combine(err, conn.Close())
|
||||
}()
|
||||
|
||||
nodeClient := multinodepb.NewDRPCNodeClient(conn)
|
||||
|
||||
header := &multinodepb.RequestHeader{
|
||||
ApiKey: node.APISecret,
|
||||
}
|
||||
|
||||
nodeVersion, err := nodeClient.Version(ctx, &multinodepb.VersionRequest{Header: header})
|
||||
if err != nil {
|
||||
return NodeInfoSatellite{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
lastContact, err := nodeClient.LastContact(ctx, &multinodepb.LastContactRequest{Header: header})
|
||||
if err != nil {
|
||||
return NodeInfoSatellite{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
rep, err := nodeClient.Reputation(ctx, &multinodepb.ReputationRequest{
|
||||
Header: header,
|
||||
SatelliteId: satelliteID,
|
||||
})
|
||||
if err != nil {
|
||||
return NodeInfoSatellite{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
return NodeInfoSatellite{
|
||||
ID: node.ID,
|
||||
Name: node.Name,
|
||||
Version: nodeVersion.Version,
|
||||
LastContact: lastContact.LastContact,
|
||||
OnlineScore: rep.Online.Score,
|
||||
AuditScore: rep.Audit.Score,
|
||||
SuspensionScore: rep.Audit.SuspensionScore,
|
||||
}, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
// TrustedSatellites returns list of unique trusted satellites node urls.
|
||||
func (service *Service) TrustedSatellites(ctx context.Context) (_ storj.NodeURLs, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
nodes, err := service.nodes.List(ctx)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
var trustedSatellites storj.NodeURLs
|
||||
for _, node := range nodes {
|
||||
nodeURLs, err := service.trustedSatellites(ctx, node)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
trustedSatellites = appendUniqueNodeURLs(trustedSatellites, nodeURLs)
|
||||
}
|
||||
|
||||
return trustedSatellites, nil
|
||||
}
|
||||
|
||||
// trustedSatellites retrieves list of trusted satellites node urls for a node.
|
||||
func (service *Service) trustedSatellites(ctx context.Context, node Node) (_ storj.NodeURLs, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
conn, err := service.dialer.DialNodeURL(ctx, storj.NodeURL{
|
||||
ID: node.ID,
|
||||
Address: node.PublicAddress,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = errs.Combine(err, conn.Close())
|
||||
}()
|
||||
|
||||
nodeClient := multinodepb.NewDRPCNodeClient(conn)
|
||||
|
||||
header := &multinodepb.RequestHeader{
|
||||
ApiKey: node.APISecret,
|
||||
}
|
||||
|
||||
resp, err := nodeClient.TrustedSatellites(ctx, &multinodepb.TrustedSatellitesRequest{Header: header})
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
var nodeURLs storj.NodeURLs
|
||||
for _, url := range resp.TrustedSatellites {
|
||||
nodeURLs = append(nodeURLs, storj.NodeURL{
|
||||
ID: url.NodeId,
|
||||
Address: url.GetAddress(),
|
||||
})
|
||||
}
|
||||
|
||||
return nodeURLs, nil
|
||||
}
|
||||
|
||||
// appendUniqueNodeURLs appends unique node urls from incoming slice.
|
||||
func appendUniqueNodeURLs(slice storj.NodeURLs, nodeURLs storj.NodeURLs) storj.NodeURLs {
|
||||
for _, nodeURL := range nodeURLs {
|
||||
slice = appendUniqueNodeURL(slice, nodeURL)
|
||||
}
|
||||
|
||||
return slice
|
||||
}
|
||||
|
||||
// appendUniqueNodeURL appends node url if it is unique.
|
||||
func appendUniqueNodeURL(slice storj.NodeURLs, nodeURL storj.NodeURL) storj.NodeURLs {
|
||||
for _, existing := range slice {
|
||||
if bytes.Equal(existing.ID.Bytes(), nodeURL.ID.Bytes()) {
|
||||
return slice
|
||||
}
|
||||
}
|
||||
|
||||
slice = append(slice, nodeURL)
|
||||
return slice
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"storj.io/common/identity"
|
||||
"storj.io/common/peertls/tlsopts"
|
||||
"storj.io/common/rpc"
|
||||
"storj.io/private/debug"
|
||||
"storj.io/storj/multinode/console"
|
||||
"storj.io/storj/multinode/console/server"
|
||||
@ -55,6 +57,8 @@ type Peer struct {
|
||||
Identity *identity.FullIdentity
|
||||
DB DB
|
||||
|
||||
Dialer rpc.Dialer
|
||||
|
||||
// contains logic of nodes domain.
|
||||
Nodes struct {
|
||||
Service *nodes.Service
|
||||
@ -78,9 +82,22 @@ func New(log *zap.Logger, full *identity.FullIdentity, config Config, db DB) (_
|
||||
Servers: lifecycle.NewGroup(log.Named("servers")),
|
||||
}
|
||||
|
||||
tlsConfig := tlsopts.Config{
|
||||
UsePeerCAWhitelist: false,
|
||||
PeerIDVersions: "0",
|
||||
}
|
||||
|
||||
tlsOptions, err := tlsopts.NewOptions(peer.Identity, tlsConfig, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer.Dialer = rpc.NewDefaultDialer(tlsOptions)
|
||||
|
||||
{ // nodes setup
|
||||
peer.Nodes.Service = nodes.NewService(
|
||||
peer.Log.Named("nodes:service"),
|
||||
peer.Dialer,
|
||||
peer.DB.Nodes(),
|
||||
)
|
||||
}
|
||||
|
@ -1,579 +0,0 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: diskspace.proto
|
||||
|
||||
package multinodepb
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
math "math"
|
||||
time "time"
|
||||
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
|
||||
drpc "storj.io/drpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
var _ = time.Kitchen
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type GetDiskSpaceRequest struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *GetDiskSpaceRequest) Reset() { *m = GetDiskSpaceRequest{} }
|
||||
func (m *GetDiskSpaceRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetDiskSpaceRequest) ProtoMessage() {}
|
||||
func (*GetDiskSpaceRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_9d1904cedee84a32, []int{0}
|
||||
}
|
||||
func (m *GetDiskSpaceRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_GetDiskSpaceRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *GetDiskSpaceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_GetDiskSpaceRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *GetDiskSpaceRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_GetDiskSpaceRequest.Merge(m, src)
|
||||
}
|
||||
func (m *GetDiskSpaceRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_GetDiskSpaceRequest.Size(m)
|
||||
}
|
||||
func (m *GetDiskSpaceRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_GetDiskSpaceRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_GetDiskSpaceRequest proto.InternalMessageInfo
|
||||
|
||||
type GetDiskSpaceResponse struct {
|
||||
DiskSpace *DiskSpace `protobuf:"bytes,1,opt,name=disk_space,json=diskSpace,proto3" json:"disk_space,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *GetDiskSpaceResponse) Reset() { *m = GetDiskSpaceResponse{} }
|
||||
func (m *GetDiskSpaceResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetDiskSpaceResponse) ProtoMessage() {}
|
||||
func (*GetDiskSpaceResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_9d1904cedee84a32, []int{1}
|
||||
}
|
||||
func (m *GetDiskSpaceResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_GetDiskSpaceResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *GetDiskSpaceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_GetDiskSpaceResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *GetDiskSpaceResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_GetDiskSpaceResponse.Merge(m, src)
|
||||
}
|
||||
func (m *GetDiskSpaceResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_GetDiskSpaceResponse.Size(m)
|
||||
}
|
||||
func (m *GetDiskSpaceResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_GetDiskSpaceResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_GetDiskSpaceResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *GetDiskSpaceResponse) GetDiskSpace() *DiskSpace {
|
||||
if m != nil {
|
||||
return m.DiskSpace
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiskSpace stores all info about storagenode disk space.
|
||||
type DiskSpace struct {
|
||||
Used int64 `protobuf:"varint,1,opt,name=used,proto3" json:"used,omitempty"`
|
||||
Available int64 `protobuf:"varint,2,opt,name=available,proto3" json:"available,omitempty"`
|
||||
Trash int64 `protobuf:"varint,3,opt,name=trash,proto3" json:"trash,omitempty"`
|
||||
Overused int64 `protobuf:"varint,4,opt,name=overused,proto3" json:"overused,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DiskSpace) Reset() { *m = DiskSpace{} }
|
||||
func (m *DiskSpace) String() string { return proto.CompactTextString(m) }
|
||||
func (*DiskSpace) ProtoMessage() {}
|
||||
func (*DiskSpace) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_9d1904cedee84a32, []int{2}
|
||||
}
|
||||
func (m *DiskSpace) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_DiskSpace.Unmarshal(m, b)
|
||||
}
|
||||
func (m *DiskSpace) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_DiskSpace.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *DiskSpace) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DiskSpace.Merge(m, src)
|
||||
}
|
||||
func (m *DiskSpace) XXX_Size() int {
|
||||
return xxx_messageInfo_DiskSpace.Size(m)
|
||||
}
|
||||
func (m *DiskSpace) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_DiskSpace.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_DiskSpace proto.InternalMessageInfo
|
||||
|
||||
func (m *DiskSpace) GetUsed() int64 {
|
||||
if m != nil {
|
||||
return m.Used
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DiskSpace) GetAvailable() int64 {
|
||||
if m != nil {
|
||||
return m.Available
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DiskSpace) GetTrash() int64 {
|
||||
if m != nil {
|
||||
return m.Trash
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DiskSpace) GetOverused() int64 {
|
||||
if m != nil {
|
||||
return m.Overused
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type DailyStorageUsageRequest struct {
|
||||
From time.Time `protobuf:"bytes,1,opt,name=from,proto3,stdtime" json:"from"`
|
||||
To time.Time `protobuf:"bytes,2,opt,name=to,proto3,stdtime" json:"to"`
|
||||
SatelliteId NodeID `protobuf:"bytes,3,opt,name=satellite_id,json=satelliteId,proto3,customtype=NodeID" json:"satellite_id"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DailyStorageUsageRequest) Reset() { *m = DailyStorageUsageRequest{} }
|
||||
func (m *DailyStorageUsageRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*DailyStorageUsageRequest) ProtoMessage() {}
|
||||
func (*DailyStorageUsageRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_9d1904cedee84a32, []int{3}
|
||||
}
|
||||
func (m *DailyStorageUsageRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_DailyStorageUsageRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *DailyStorageUsageRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_DailyStorageUsageRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *DailyStorageUsageRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DailyStorageUsageRequest.Merge(m, src)
|
||||
}
|
||||
func (m *DailyStorageUsageRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_DailyStorageUsageRequest.Size(m)
|
||||
}
|
||||
func (m *DailyStorageUsageRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_DailyStorageUsageRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_DailyStorageUsageRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *DailyStorageUsageRequest) GetFrom() time.Time {
|
||||
if m != nil {
|
||||
return m.From
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (m *DailyStorageUsageRequest) GetTo() time.Time {
|
||||
if m != nil {
|
||||
return m.To
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
type DailyStorageUsageResponse struct {
|
||||
NodeId []byte `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
||||
DailyStorageUsage []*DailyStorageUsageResponse_StorageUsage `protobuf:"bytes,2,rep,name=daily_storage_usage,json=dailyStorageUsage,proto3" json:"daily_storage_usage,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DailyStorageUsageResponse) Reset() { *m = DailyStorageUsageResponse{} }
|
||||
func (m *DailyStorageUsageResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*DailyStorageUsageResponse) ProtoMessage() {}
|
||||
func (*DailyStorageUsageResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_9d1904cedee84a32, []int{4}
|
||||
}
|
||||
func (m *DailyStorageUsageResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_DailyStorageUsageResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *DailyStorageUsageResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_DailyStorageUsageResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *DailyStorageUsageResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DailyStorageUsageResponse.Merge(m, src)
|
||||
}
|
||||
func (m *DailyStorageUsageResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_DailyStorageUsageResponse.Size(m)
|
||||
}
|
||||
func (m *DailyStorageUsageResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_DailyStorageUsageResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_DailyStorageUsageResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *DailyStorageUsageResponse) GetNodeId() []byte {
|
||||
if m != nil {
|
||||
return m.NodeId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *DailyStorageUsageResponse) GetDailyStorageUsage() []*DailyStorageUsageResponse_StorageUsage {
|
||||
if m != nil {
|
||||
return m.DailyStorageUsage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DailyStorageUsageResponse_StorageUsage struct {
|
||||
AtRestTotal float64 `protobuf:"fixed64,1,opt,name=at_rest_total,json=atRestTotal,proto3" json:"at_rest_total,omitempty"`
|
||||
Timestamp time.Time `protobuf:"bytes,2,opt,name=timestamp,proto3,stdtime" json:"timestamp"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DailyStorageUsageResponse_StorageUsage) Reset() {
|
||||
*m = DailyStorageUsageResponse_StorageUsage{}
|
||||
}
|
||||
func (m *DailyStorageUsageResponse_StorageUsage) String() string { return proto.CompactTextString(m) }
|
||||
func (*DailyStorageUsageResponse_StorageUsage) ProtoMessage() {}
|
||||
func (*DailyStorageUsageResponse_StorageUsage) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_9d1904cedee84a32, []int{4, 0}
|
||||
}
|
||||
func (m *DailyStorageUsageResponse_StorageUsage) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_DailyStorageUsageResponse_StorageUsage.Unmarshal(m, b)
|
||||
}
|
||||
func (m *DailyStorageUsageResponse_StorageUsage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_DailyStorageUsageResponse_StorageUsage.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *DailyStorageUsageResponse_StorageUsage) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DailyStorageUsageResponse_StorageUsage.Merge(m, src)
|
||||
}
|
||||
func (m *DailyStorageUsageResponse_StorageUsage) XXX_Size() int {
|
||||
return xxx_messageInfo_DailyStorageUsageResponse_StorageUsage.Size(m)
|
||||
}
|
||||
func (m *DailyStorageUsageResponse_StorageUsage) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_DailyStorageUsageResponse_StorageUsage.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_DailyStorageUsageResponse_StorageUsage proto.InternalMessageInfo
|
||||
|
||||
func (m *DailyStorageUsageResponse_StorageUsage) GetAtRestTotal() float64 {
|
||||
if m != nil {
|
||||
return m.AtRestTotal
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DailyStorageUsageResponse_StorageUsage) GetTimestamp() time.Time {
|
||||
if m != nil {
|
||||
return m.Timestamp
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
type SatelliteSummaryRequest struct {
|
||||
From time.Time `protobuf:"bytes,1,opt,name=from,proto3,stdtime" json:"from"`
|
||||
To time.Time `protobuf:"bytes,2,opt,name=to,proto3,stdtime" json:"to"`
|
||||
SatelliteId NodeID `protobuf:"bytes,3,opt,name=satellite_id,json=satelliteId,proto3,customtype=NodeID" json:"satellite_id"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *SatelliteSummaryRequest) Reset() { *m = SatelliteSummaryRequest{} }
|
||||
func (m *SatelliteSummaryRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*SatelliteSummaryRequest) ProtoMessage() {}
|
||||
func (*SatelliteSummaryRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_9d1904cedee84a32, []int{5}
|
||||
}
|
||||
func (m *SatelliteSummaryRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_SatelliteSummaryRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *SatelliteSummaryRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_SatelliteSummaryRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *SatelliteSummaryRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_SatelliteSummaryRequest.Merge(m, src)
|
||||
}
|
||||
func (m *SatelliteSummaryRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_SatelliteSummaryRequest.Size(m)
|
||||
}
|
||||
func (m *SatelliteSummaryRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_SatelliteSummaryRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_SatelliteSummaryRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *SatelliteSummaryRequest) GetFrom() time.Time {
|
||||
if m != nil {
|
||||
return m.From
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (m *SatelliteSummaryRequest) GetTo() time.Time {
|
||||
if m != nil {
|
||||
return m.To
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
type SatelliteSummaryResponse struct {
|
||||
StorageUsage float64 `protobuf:"fixed64,1,opt,name=storage_usage,json=storageUsage,proto3" json:"storage_usage,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *SatelliteSummaryResponse) Reset() { *m = SatelliteSummaryResponse{} }
|
||||
func (m *SatelliteSummaryResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*SatelliteSummaryResponse) ProtoMessage() {}
|
||||
func (*SatelliteSummaryResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_9d1904cedee84a32, []int{6}
|
||||
}
|
||||
func (m *SatelliteSummaryResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_SatelliteSummaryResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *SatelliteSummaryResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_SatelliteSummaryResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *SatelliteSummaryResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_SatelliteSummaryResponse.Merge(m, src)
|
||||
}
|
||||
func (m *SatelliteSummaryResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_SatelliteSummaryResponse.Size(m)
|
||||
}
|
||||
func (m *SatelliteSummaryResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_SatelliteSummaryResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_SatelliteSummaryResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *SatelliteSummaryResponse) GetStorageUsage() float64 {
|
||||
if m != nil {
|
||||
return m.StorageUsage
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*GetDiskSpaceRequest)(nil), "diskspace.GetDiskSpaceRequest")
|
||||
proto.RegisterType((*GetDiskSpaceResponse)(nil), "diskspace.GetDiskSpaceResponse")
|
||||
proto.RegisterType((*DiskSpace)(nil), "diskspace.DiskSpace")
|
||||
proto.RegisterType((*DailyStorageUsageRequest)(nil), "diskspace.DailyStorageUsageRequest")
|
||||
proto.RegisterType((*DailyStorageUsageResponse)(nil), "diskspace.DailyStorageUsageResponse")
|
||||
proto.RegisterType((*DailyStorageUsageResponse_StorageUsage)(nil), "diskspace.DailyStorageUsageResponse.StorageUsage")
|
||||
proto.RegisterType((*SatelliteSummaryRequest)(nil), "diskspace.SatelliteSummaryRequest")
|
||||
proto.RegisterType((*SatelliteSummaryResponse)(nil), "diskspace.SatelliteSummaryResponse")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("diskspace.proto", fileDescriptor_9d1904cedee84a32) }
|
||||
|
||||
var fileDescriptor_9d1904cedee84a32 = []byte{
|
||||
// 528 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x53, 0xc1, 0x6e, 0xd3, 0x40,
|
||||
0x10, 0xc5, 0x4e, 0x08, 0xcd, 0xc4, 0x01, 0xba, 0x0d, 0xaa, 0xb1, 0x2a, 0x52, 0x39, 0x1c, 0x7a,
|
||||
0x72, 0xd4, 0x94, 0x03, 0x37, 0xa4, 0x28, 0x12, 0x8a, 0x90, 0x40, 0x72, 0xca, 0x05, 0x24, 0xac,
|
||||
0x0d, 0xde, 0x9a, 0xa5, 0x76, 0xd7, 0x78, 0xc7, 0x91, 0xfa, 0x15, 0xf0, 0x03, 0xfc, 0x09, 0xdc,
|
||||
0xf9, 0x06, 0x0e, 0xe5, 0x57, 0xd0, 0xae, 0x13, 0xdb, 0x69, 0x9b, 0xaa, 0x1c, 0xb9, 0xed, 0xcc,
|
||||
0xce, 0x9b, 0x7d, 0xfb, 0xde, 0x0c, 0x3c, 0x08, 0xb9, 0x3c, 0x95, 0x29, 0xfd, 0xc8, 0xbc, 0x34,
|
||||
0x13, 0x28, 0x48, 0xbb, 0x4c, 0x38, 0x10, 0x89, 0x48, 0x14, 0x69, 0xa7, 0x1f, 0x09, 0x11, 0xc5,
|
||||
0x6c, 0xa8, 0xa3, 0x79, 0x7e, 0x32, 0x44, 0x9e, 0x30, 0x89, 0x34, 0x49, 0x8b, 0x02, 0xf7, 0x11,
|
||||
0xec, 0xbc, 0x64, 0x38, 0xe1, 0xf2, 0x74, 0xa6, 0xc0, 0x3e, 0xfb, 0x92, 0x33, 0x89, 0xee, 0x2b,
|
||||
0xe8, 0xad, 0xa7, 0x65, 0x2a, 0xce, 0x24, 0x23, 0x47, 0x00, 0xea, 0xa1, 0x40, 0xbf, 0x64, 0x1b,
|
||||
0xfb, 0xc6, 0x41, 0x67, 0xd4, 0xf3, 0x2a, 0x32, 0x15, 0x42, 0x13, 0xd2, 0x47, 0x57, 0x40, 0xbb,
|
||||
0xcc, 0x13, 0x02, 0xcd, 0x5c, 0xb2, 0x50, 0x63, 0x1b, 0xbe, 0x3e, 0x93, 0x3d, 0x68, 0xd3, 0x05,
|
||||
0xe5, 0x31, 0x9d, 0xc7, 0xcc, 0x36, 0xf5, 0x45, 0x95, 0x20, 0x3d, 0xb8, 0x8b, 0x19, 0x95, 0x9f,
|
||||
0xec, 0x86, 0xbe, 0x29, 0x02, 0xe2, 0xc0, 0x96, 0x58, 0xb0, 0x4c, 0xf7, 0x6a, 0xea, 0x8b, 0x32,
|
||||
0x76, 0x7f, 0x1a, 0x60, 0x4f, 0x28, 0x8f, 0xcf, 0x67, 0x28, 0x32, 0x1a, 0xb1, 0xb7, 0x92, 0x46,
|
||||
0xab, 0xaf, 0x91, 0xe7, 0xd0, 0x3c, 0xc9, 0x44, 0xb2, 0x24, 0xef, 0x78, 0x85, 0x42, 0xde, 0x4a,
|
||||
0x21, 0xef, 0x78, 0xa5, 0xd0, 0x78, 0xeb, 0xd7, 0x45, 0xff, 0xce, 0xb7, 0x3f, 0x7d, 0xc3, 0xd7,
|
||||
0x08, 0xf2, 0x0c, 0x4c, 0x14, 0x9a, 0xdf, 0x6d, 0x71, 0x26, 0x0a, 0x72, 0x08, 0x96, 0xa4, 0xc8,
|
||||
0xe2, 0x98, 0x23, 0x0b, 0x78, 0xa8, 0x7f, 0x61, 0x8d, 0xef, 0xab, 0x9a, 0xdf, 0x17, 0xfd, 0xd6,
|
||||
0x6b, 0x11, 0xb2, 0xe9, 0xc4, 0xef, 0x94, 0x35, 0xd3, 0xd0, 0xfd, 0x6a, 0xc2, 0xe3, 0x6b, 0xf8,
|
||||
0x2f, 0x3d, 0xd8, 0x85, 0x7b, 0x67, 0x22, 0xd4, 0xbd, 0xd4, 0x1f, 0x2c, 0xbf, 0xa5, 0xc2, 0x69,
|
||||
0x48, 0x28, 0xec, 0x84, 0x0a, 0x15, 0xc8, 0x02, 0x16, 0xe4, 0x0a, 0x67, 0x9b, 0xfb, 0x8d, 0x83,
|
||||
0xce, 0xe8, 0xb0, 0xee, 0xd2, 0xa6, 0xde, 0xde, 0x5a, 0x72, 0x3b, 0xbc, 0x5c, 0xe7, 0x2c, 0xc0,
|
||||
0xaa, 0xc7, 0xc4, 0x85, 0x2e, 0xc5, 0x20, 0x63, 0x12, 0x03, 0x14, 0x48, 0x63, 0xcd, 0xc8, 0xf0,
|
||||
0x3b, 0x14, 0x7d, 0x26, 0xf1, 0x58, 0xa5, 0xc8, 0x18, 0xda, 0xe5, 0xd4, 0xfd, 0x93, 0x7a, 0x15,
|
||||
0xcc, 0xfd, 0x61, 0xc0, 0xee, 0x6c, 0xa5, 0xd0, 0x2c, 0x4f, 0x12, 0x9a, 0x9d, 0xff, 0x47, 0x86,
|
||||
0xbe, 0x00, 0xfb, 0x2a, 0xfb, 0xa5, 0x9d, 0x03, 0xe8, 0xae, 0xfb, 0x55, 0x48, 0x68, 0xc9, 0x9a,
|
||||
0xce, 0xa3, 0xef, 0x26, 0x74, 0x55, 0xe3, 0x6a, 0x8f, 0xde, 0x80, 0x55, 0xdf, 0x50, 0xf2, 0xa4,
|
||||
0xe6, 0xef, 0x35, 0x1b, 0xed, 0xf4, 0x37, 0xde, 0x2f, 0x79, 0x7c, 0x80, 0xed, 0x2b, 0x73, 0x41,
|
||||
0x06, 0x37, 0x4f, 0x4d, 0xd1, 0xfa, 0xe9, 0x6d, 0x46, 0x8b, 0xbc, 0x87, 0x87, 0x97, 0x35, 0x20,
|
||||
0x6e, 0x0d, 0xb9, 0xc1, 0x5e, 0x67, 0x70, 0x63, 0x4d, 0xd1, 0x7c, 0xbc, 0xf7, 0xce, 0x51, 0x7a,
|
||||
0x7d, 0xf6, 0xb8, 0x18, 0xea, 0xc3, 0x30, 0xc9, 0x63, 0xe4, 0x6a, 0x31, 0xd2, 0xf9, 0xbc, 0xa5,
|
||||
0x3d, 0x3d, 0xfa, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xb3, 0xd3, 0x33, 0x3c, 0x36, 0x05, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// --- DRPC BEGIN ---
|
||||
|
||||
type DRPCNodeDiskSpaceClient interface {
|
||||
DRPCConn() drpc.Conn
|
||||
|
||||
GetDiskSpace(ctx context.Context, in *GetDiskSpaceRequest) (*GetDiskSpaceResponse, error)
|
||||
DailyStorageUsage(ctx context.Context, in *DailyStorageUsageRequest) (*DailyStorageUsageResponse, error)
|
||||
SatelliteSummary(ctx context.Context, in *SatelliteSummaryRequest) (*SatelliteSummaryResponse, error)
|
||||
}
|
||||
|
||||
type drpcNodeDiskSpaceClient struct {
|
||||
cc drpc.Conn
|
||||
}
|
||||
|
||||
func NewDRPCNodeDiskSpaceClient(cc drpc.Conn) DRPCNodeDiskSpaceClient {
|
||||
return &drpcNodeDiskSpaceClient{cc}
|
||||
}
|
||||
|
||||
func (c *drpcNodeDiskSpaceClient) DRPCConn() drpc.Conn { return c.cc }
|
||||
|
||||
func (c *drpcNodeDiskSpaceClient) GetDiskSpace(ctx context.Context, in *GetDiskSpaceRequest) (*GetDiskSpaceResponse, error) {
|
||||
out := new(GetDiskSpaceResponse)
|
||||
err := c.cc.Invoke(ctx, "/diskspace.NodeDiskSpace/GetDiskSpace", in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcNodeDiskSpaceClient) DailyStorageUsage(ctx context.Context, in *DailyStorageUsageRequest) (*DailyStorageUsageResponse, error) {
|
||||
out := new(DailyStorageUsageResponse)
|
||||
err := c.cc.Invoke(ctx, "/diskspace.NodeDiskSpace/DailyStorageUsage", in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcNodeDiskSpaceClient) SatelliteSummary(ctx context.Context, in *SatelliteSummaryRequest) (*SatelliteSummaryResponse, error) {
|
||||
out := new(SatelliteSummaryResponse)
|
||||
err := c.cc.Invoke(ctx, "/diskspace.NodeDiskSpace/SatelliteSummary", in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type DRPCNodeDiskSpaceServer interface {
|
||||
GetDiskSpace(context.Context, *GetDiskSpaceRequest) (*GetDiskSpaceResponse, error)
|
||||
DailyStorageUsage(context.Context, *DailyStorageUsageRequest) (*DailyStorageUsageResponse, error)
|
||||
SatelliteSummary(context.Context, *SatelliteSummaryRequest) (*SatelliteSummaryResponse, error)
|
||||
}
|
||||
|
||||
type DRPCNodeDiskSpaceDescription struct{}
|
||||
|
||||
func (DRPCNodeDiskSpaceDescription) NumMethods() int { return 3 }
|
||||
|
||||
func (DRPCNodeDiskSpaceDescription) Method(n int) (string, drpc.Receiver, interface{}, bool) {
|
||||
switch n {
|
||||
case 0:
|
||||
return "/diskspace.NodeDiskSpace/GetDiskSpace",
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCNodeDiskSpaceServer).
|
||||
GetDiskSpace(
|
||||
ctx,
|
||||
in1.(*GetDiskSpaceRequest),
|
||||
)
|
||||
}, DRPCNodeDiskSpaceServer.GetDiskSpace, true
|
||||
case 1:
|
||||
return "/diskspace.NodeDiskSpace/DailyStorageUsage",
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCNodeDiskSpaceServer).
|
||||
DailyStorageUsage(
|
||||
ctx,
|
||||
in1.(*DailyStorageUsageRequest),
|
||||
)
|
||||
}, DRPCNodeDiskSpaceServer.DailyStorageUsage, true
|
||||
case 2:
|
||||
return "/diskspace.NodeDiskSpace/SatelliteSummary",
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCNodeDiskSpaceServer).
|
||||
SatelliteSummary(
|
||||
ctx,
|
||||
in1.(*SatelliteSummaryRequest),
|
||||
)
|
||||
}, DRPCNodeDiskSpaceServer.SatelliteSummary, true
|
||||
default:
|
||||
return "", nil, nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func DRPCRegisterNodeDiskSpace(mux drpc.Mux, impl DRPCNodeDiskSpaceServer) error {
|
||||
return mux.Register(impl, DRPCNodeDiskSpaceDescription{})
|
||||
}
|
||||
|
||||
type DRPCNodeDiskSpace_GetDiskSpaceStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*GetDiskSpaceResponse) error
|
||||
}
|
||||
|
||||
type drpcNodeDiskSpaceGetDiskSpaceStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcNodeDiskSpaceGetDiskSpaceStream) SendAndClose(m *GetDiskSpaceResponse) error {
|
||||
if err := x.MsgSend(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCNodeDiskSpace_DailyStorageUsageStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*DailyStorageUsageResponse) error
|
||||
}
|
||||
|
||||
type drpcNodeDiskSpaceDailyStorageUsageStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcNodeDiskSpaceDailyStorageUsageStream) SendAndClose(m *DailyStorageUsageResponse) error {
|
||||
if err := x.MsgSend(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCNodeDiskSpace_SatelliteSummaryStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*SatelliteSummaryResponse) error
|
||||
}
|
||||
|
||||
type drpcNodeDiskSpaceSatelliteSummaryStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcNodeDiskSpaceSatelliteSummaryStream) SendAndClose(m *SatelliteSummaryResponse) error {
|
||||
if err := x.MsgSend(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
// --- DRPC END ---
|
@ -1,56 +0,0 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
syntax = "proto3";
|
||||
option go_package = "storj.io/storj/multinodepb";
|
||||
|
||||
package diskspace;
|
||||
|
||||
import "gogo.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service NodeDiskSpace {
|
||||
rpc GetDiskSpace(GetDiskSpaceRequest) returns (GetDiskSpaceResponse);
|
||||
rpc DailyStorageUsage(DailyStorageUsageRequest) returns (DailyStorageUsageResponse);
|
||||
rpc SatelliteSummary(SatelliteSummaryRequest) returns (SatelliteSummaryResponse);
|
||||
}
|
||||
|
||||
message GetDiskSpaceRequest {}
|
||||
|
||||
message GetDiskSpaceResponse {
|
||||
DiskSpace disk_space = 1;
|
||||
}
|
||||
|
||||
// DiskSpace stores all info about storagenode disk space.
|
||||
message DiskSpace {
|
||||
int64 used = 1;
|
||||
int64 available = 2;
|
||||
int64 trash = 3;
|
||||
int64 overused = 4;
|
||||
}
|
||||
|
||||
message DailyStorageUsageRequest {
|
||||
google.protobuf.Timestamp from = 1 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
|
||||
google.protobuf.Timestamp to = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
|
||||
bytes satellite_id = 3 [(gogoproto.customtype) = "NodeID", (gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
message DailyStorageUsageResponse {
|
||||
message StorageUsage {
|
||||
double at_rest_total = 1;
|
||||
google.protobuf.Timestamp timestamp = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
bytes node_id = 1;
|
||||
repeated StorageUsage daily_storage_usage = 2;
|
||||
}
|
||||
|
||||
message SatelliteSummaryRequest {
|
||||
google.protobuf.Timestamp from = 1 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
|
||||
google.protobuf.Timestamp to = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
|
||||
bytes satellite_id = 3 [(gogoproto.customtype) = "NodeID", (gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
message SatelliteSummaryResponse {
|
||||
double storage_usage = 1;
|
||||
}
|
@ -1,466 +0,0 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: reputation.proto
|
||||
|
||||
package multinodepb
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
math "math"
|
||||
time "time"
|
||||
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
|
||||
drpc "storj.io/drpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
var _ = time.Kitchen
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type ReputationStats struct {
|
||||
TotalCount int64 `protobuf:"varint,1,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"`
|
||||
SuccessCount int64 `protobuf:"varint,2,opt,name=success_count,json=successCount,proto3" json:"success_count,omitempty"`
|
||||
ReputationAlpha float64 `protobuf:"fixed64,3,opt,name=reputation_alpha,json=reputationAlpha,proto3" json:"reputation_alpha,omitempty"`
|
||||
ReputationBeta float64 `protobuf:"fixed64,4,opt,name=reputation_beta,json=reputationBeta,proto3" json:"reputation_beta,omitempty"`
|
||||
ReputationScore float64 `protobuf:"fixed64,5,opt,name=reputation_score,json=reputationScore,proto3" json:"reputation_score,omitempty"`
|
||||
UnknownReputationAlpha float64 `protobuf:"fixed64,6,opt,name=unknown_reputation_alpha,json=unknownReputationAlpha,proto3" json:"unknown_reputation_alpha,omitempty"`
|
||||
UnknownReputationBeta float64 `protobuf:"fixed64,7,opt,name=unknown_reputation_beta,json=unknownReputationBeta,proto3" json:"unknown_reputation_beta,omitempty"`
|
||||
UnknownReputationScore float64 `protobuf:"fixed64,8,opt,name=unknown_reputation_score,json=unknownReputationScore,proto3" json:"unknown_reputation_score,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ReputationStats) Reset() { *m = ReputationStats{} }
|
||||
func (m *ReputationStats) String() string { return proto.CompactTextString(m) }
|
||||
func (*ReputationStats) ProtoMessage() {}
|
||||
func (*ReputationStats) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_b35a2508345eddf0, []int{0}
|
||||
}
|
||||
func (m *ReputationStats) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ReputationStats.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ReputationStats) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ReputationStats.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ReputationStats) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ReputationStats.Merge(m, src)
|
||||
}
|
||||
func (m *ReputationStats) XXX_Size() int {
|
||||
return xxx_messageInfo_ReputationStats.Size(m)
|
||||
}
|
||||
func (m *ReputationStats) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ReputationStats.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ReputationStats proto.InternalMessageInfo
|
||||
|
||||
func (m *ReputationStats) GetTotalCount() int64 {
|
||||
if m != nil {
|
||||
return m.TotalCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ReputationStats) GetSuccessCount() int64 {
|
||||
if m != nil {
|
||||
return m.SuccessCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ReputationStats) GetReputationAlpha() float64 {
|
||||
if m != nil {
|
||||
return m.ReputationAlpha
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ReputationStats) GetReputationBeta() float64 {
|
||||
if m != nil {
|
||||
return m.ReputationBeta
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ReputationStats) GetReputationScore() float64 {
|
||||
if m != nil {
|
||||
return m.ReputationScore
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ReputationStats) GetUnknownReputationAlpha() float64 {
|
||||
if m != nil {
|
||||
return m.UnknownReputationAlpha
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ReputationStats) GetUnknownReputationBeta() float64 {
|
||||
if m != nil {
|
||||
return m.UnknownReputationBeta
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ReputationStats) GetUnknownReputationScore() float64 {
|
||||
if m != nil {
|
||||
return m.UnknownReputationScore
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type GetBySatelliteIDRequest struct {
|
||||
SatelliteId NodeID `protobuf:"bytes,1,opt,name=satellite_id,json=satelliteId,proto3,customtype=NodeID" json:"satellite_id"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *GetBySatelliteIDRequest) Reset() { *m = GetBySatelliteIDRequest{} }
|
||||
func (m *GetBySatelliteIDRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetBySatelliteIDRequest) ProtoMessage() {}
|
||||
func (*GetBySatelliteIDRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_b35a2508345eddf0, []int{1}
|
||||
}
|
||||
func (m *GetBySatelliteIDRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_GetBySatelliteIDRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *GetBySatelliteIDRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_GetBySatelliteIDRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *GetBySatelliteIDRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_GetBySatelliteIDRequest.Merge(m, src)
|
||||
}
|
||||
func (m *GetBySatelliteIDRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_GetBySatelliteIDRequest.Size(m)
|
||||
}
|
||||
func (m *GetBySatelliteIDRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_GetBySatelliteIDRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_GetBySatelliteIDRequest proto.InternalMessageInfo
|
||||
|
||||
type GetBySatelliteIDResponse struct {
|
||||
AuditCheck *ReputationStats `protobuf:"bytes,1,opt,name=audit_check,json=auditCheck,proto3" json:"audit_check,omitempty"`
|
||||
Disqualified *time.Time `protobuf:"bytes,2,opt,name=disqualified,proto3,stdtime" json:"disqualified,omitempty"`
|
||||
Suspended *time.Time `protobuf:"bytes,3,opt,name=suspended,proto3,stdtime" json:"suspended,omitempty"`
|
||||
JoinedAt time.Time `protobuf:"bytes,4,opt,name=joined_at,json=joinedAt,proto3,stdtime" json:"joined_at"`
|
||||
OfflineSuspended *time.Time `protobuf:"bytes,5,opt,name=offline_suspended,json=offlineSuspended,proto3,stdtime" json:"offline_suspended,omitempty"`
|
||||
OnlineScore float64 `protobuf:"fixed64,6,opt,name=online_score,json=onlineScore,proto3" json:"online_score,omitempty"`
|
||||
OfflineUnderReview *time.Time `protobuf:"bytes,7,opt,name=offline_under_review,json=offlineUnderReview,proto3,stdtime" json:"offline_under_review,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *GetBySatelliteIDResponse) Reset() { *m = GetBySatelliteIDResponse{} }
|
||||
func (m *GetBySatelliteIDResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetBySatelliteIDResponse) ProtoMessage() {}
|
||||
func (*GetBySatelliteIDResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_b35a2508345eddf0, []int{2}
|
||||
}
|
||||
func (m *GetBySatelliteIDResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_GetBySatelliteIDResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *GetBySatelliteIDResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_GetBySatelliteIDResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *GetBySatelliteIDResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_GetBySatelliteIDResponse.Merge(m, src)
|
||||
}
|
||||
func (m *GetBySatelliteIDResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_GetBySatelliteIDResponse.Size(m)
|
||||
}
|
||||
func (m *GetBySatelliteIDResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_GetBySatelliteIDResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_GetBySatelliteIDResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *GetBySatelliteIDResponse) GetAuditCheck() *ReputationStats {
|
||||
if m != nil {
|
||||
return m.AuditCheck
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GetBySatelliteIDResponse) GetDisqualified() *time.Time {
|
||||
if m != nil {
|
||||
return m.Disqualified
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GetBySatelliteIDResponse) GetSuspended() *time.Time {
|
||||
if m != nil {
|
||||
return m.Suspended
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GetBySatelliteIDResponse) GetJoinedAt() time.Time {
|
||||
if m != nil {
|
||||
return m.JoinedAt
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (m *GetBySatelliteIDResponse) GetOfflineSuspended() *time.Time {
|
||||
if m != nil {
|
||||
return m.OfflineSuspended
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GetBySatelliteIDResponse) GetOnlineScore() float64 {
|
||||
if m != nil {
|
||||
return m.OnlineScore
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *GetBySatelliteIDResponse) GetOfflineUnderReview() *time.Time {
|
||||
if m != nil {
|
||||
return m.OfflineUnderReview
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AllRequest struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *AllRequest) Reset() { *m = AllRequest{} }
|
||||
func (m *AllRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AllRequest) ProtoMessage() {}
|
||||
func (*AllRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_b35a2508345eddf0, []int{3}
|
||||
}
|
||||
func (m *AllRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_AllRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *AllRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_AllRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *AllRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_AllRequest.Merge(m, src)
|
||||
}
|
||||
func (m *AllRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_AllRequest.Size(m)
|
||||
}
|
||||
func (m *AllRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_AllRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_AllRequest proto.InternalMessageInfo
|
||||
|
||||
type AllResponse struct {
|
||||
Reputation []*GetBySatelliteIDResponse `protobuf:"bytes,1,rep,name=reputation,proto3" json:"reputation,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *AllResponse) Reset() { *m = AllResponse{} }
|
||||
func (m *AllResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*AllResponse) ProtoMessage() {}
|
||||
func (*AllResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_b35a2508345eddf0, []int{4}
|
||||
}
|
||||
func (m *AllResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_AllResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *AllResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_AllResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *AllResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_AllResponse.Merge(m, src)
|
||||
}
|
||||
func (m *AllResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_AllResponse.Size(m)
|
||||
}
|
||||
func (m *AllResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_AllResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_AllResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *AllResponse) GetReputation() []*GetBySatelliteIDResponse {
|
||||
if m != nil {
|
||||
return m.Reputation
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*ReputationStats)(nil), "reputation.ReputationStats")
|
||||
proto.RegisterType((*GetBySatelliteIDRequest)(nil), "reputation.GetBySatelliteIDRequest")
|
||||
proto.RegisterType((*GetBySatelliteIDResponse)(nil), "reputation.GetBySatelliteIDResponse")
|
||||
proto.RegisterType((*AllRequest)(nil), "reputation.AllRequest")
|
||||
proto.RegisterType((*AllResponse)(nil), "reputation.AllResponse")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("reputation.proto", fileDescriptor_b35a2508345eddf0) }
|
||||
|
||||
var fileDescriptor_b35a2508345eddf0 = []byte{
|
||||
// 584 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xc1, 0x6e, 0xd3, 0x40,
|
||||
0x14, 0xac, 0x31, 0x2d, 0xed, 0xb3, 0x69, 0xcb, 0x0a, 0x5a, 0xcb, 0x20, 0xb9, 0xa4, 0x48, 0x84,
|
||||
0x8b, 0x23, 0x82, 0x54, 0x71, 0xe0, 0x12, 0xb7, 0x12, 0x54, 0x42, 0x48, 0x38, 0xc0, 0x01, 0x09,
|
||||
0x59, 0x1b, 0x7b, 0x93, 0x6e, 0xbb, 0xd9, 0x75, 0xbd, 0x6b, 0x2a, 0xae, 0x7c, 0x01, 0xff, 0xc0,
|
||||
0xcf, 0xf4, 0x1b, 0x38, 0x84, 0xcf, 0xe0, 0x8a, 0xbc, 0x76, 0x62, 0x93, 0xb4, 0x10, 0x6e, 0xde,
|
||||
0xf1, 0xcc, 0xbc, 0xb1, 0xdf, 0x68, 0x61, 0x3b, 0x23, 0x69, 0xae, 0xb0, 0xa2, 0x82, 0xfb, 0x69,
|
||||
0x26, 0x94, 0x40, 0x50, 0x23, 0x2e, 0x8c, 0xc4, 0x48, 0x94, 0xb8, 0xeb, 0x8d, 0x84, 0x18, 0x31,
|
||||
0xd2, 0xd1, 0xa7, 0x41, 0x3e, 0xec, 0x28, 0x3a, 0x26, 0x52, 0xe1, 0x71, 0x5a, 0x12, 0x5a, 0x5f,
|
||||
0x4d, 0xd8, 0x0a, 0x67, 0xda, 0xbe, 0xc2, 0x4a, 0x22, 0x0f, 0x2c, 0x25, 0x14, 0x66, 0x51, 0x2c,
|
||||
0x72, 0xae, 0x1c, 0x63, 0xcf, 0x68, 0x9b, 0x21, 0x68, 0xe8, 0xb0, 0x40, 0xd0, 0x3e, 0xdc, 0x96,
|
||||
0x79, 0x1c, 0x13, 0x29, 0x2b, 0xca, 0x0d, 0x4d, 0xb1, 0x2b, 0xb0, 0x24, 0x3d, 0x69, 0xc6, 0x8c,
|
||||
0x30, 0x4b, 0x4f, 0xb0, 0x63, 0xee, 0x19, 0x6d, 0x23, 0xdc, 0xaa, 0xf1, 0x5e, 0x01, 0xa3, 0xc7,
|
||||
0xd0, 0x80, 0xa2, 0x01, 0x51, 0xd8, 0xb9, 0xa9, 0x99, 0x9b, 0x35, 0x1c, 0x10, 0x85, 0xe7, 0x3c,
|
||||
0x65, 0x2c, 0x32, 0xe2, 0xac, 0xce, 0x7b, 0xf6, 0x0b, 0x18, 0x3d, 0x07, 0x27, 0xe7, 0x67, 0x5c,
|
||||
0x5c, 0xf0, 0x68, 0x21, 0xc6, 0x9a, 0x96, 0xec, 0x54, 0xef, 0xc3, 0xb9, 0x34, 0x07, 0xb0, 0x7b,
|
||||
0x85, 0x52, 0xa7, 0xba, 0xa5, 0x85, 0xf7, 0x16, 0x84, 0x3a, 0xdc, 0xd5, 0x13, 0xcb, 0x90, 0xeb,
|
||||
0xd7, 0x4c, 0xd4, 0x59, 0x5b, 0xaf, 0x61, 0xf7, 0x25, 0x51, 0xc1, 0x97, 0x3e, 0x56, 0x84, 0x31,
|
||||
0xaa, 0xc8, 0xf1, 0x51, 0x48, 0xce, 0x73, 0x22, 0x15, 0x7a, 0x0a, 0xb6, 0x9c, 0xa2, 0x11, 0x4d,
|
||||
0xf4, 0x32, 0xec, 0x60, 0xf3, 0x72, 0xe2, 0xad, 0xfc, 0x98, 0x78, 0x6b, 0x6f, 0x44, 0x52, 0x90,
|
||||
0xad, 0x19, 0xe7, 0x38, 0x69, 0xfd, 0x32, 0xc1, 0x59, 0xb4, 0x93, 0xa9, 0xe0, 0x92, 0xa0, 0x17,
|
||||
0x60, 0xe1, 0x3c, 0xa1, 0x2a, 0x8a, 0x4f, 0x48, 0x7c, 0xa6, 0xed, 0xac, 0xee, 0x7d, 0xbf, 0x51,
|
||||
0xa8, 0xb9, 0x36, 0x84, 0xa0, 0xf9, 0x87, 0x05, 0x1d, 0xbd, 0x02, 0x3b, 0xa1, 0xf2, 0x3c, 0xc7,
|
||||
0x8c, 0x0e, 0x29, 0x49, 0xf4, 0xde, 0xad, 0xae, 0xeb, 0x97, 0x2d, 0xf3, 0xa7, 0x2d, 0xf3, 0xdf,
|
||||
0x4d, 0x5b, 0x16, 0xac, 0x5f, 0x4e, 0x3c, 0xe3, 0xdb, 0x4f, 0xcf, 0x08, 0xff, 0x50, 0xa2, 0x00,
|
||||
0x36, 0x64, 0x2e, 0x53, 0xc2, 0x13, 0x92, 0xe8, 0x5a, 0x2c, 0x6b, 0x53, 0xcb, 0x50, 0x0f, 0x36,
|
||||
0x4e, 0x05, 0xe5, 0x24, 0x89, 0xb0, 0xd2, 0x85, 0xf9, 0xb7, 0xc7, 0x8a, 0xf6, 0x58, 0x2f, 0x65,
|
||||
0x3d, 0x85, 0xde, 0xc2, 0x1d, 0x31, 0x1c, 0x32, 0xca, 0x49, 0x54, 0xc7, 0x59, 0xfd, 0x8f, 0x38,
|
||||
0xdb, 0x95, 0xbc, 0x3f, 0x4b, 0xf5, 0x10, 0x6c, 0xc1, 0x4b, 0x47, 0xbd, 0xfa, 0xb2, 0x6c, 0x56,
|
||||
0x89, 0x95, 0xdd, 0xfc, 0x00, 0x77, 0xa7, 0x53, 0x73, 0x9e, 0x90, 0x2c, 0xca, 0xc8, 0x67, 0x4a,
|
||||
0x2e, 0x74, 0xbd, 0x96, 0x1d, 0x8c, 0x2a, 0x87, 0xf7, 0x85, 0x41, 0xa8, 0xf5, 0x2d, 0x1b, 0xa0,
|
||||
0xc7, 0x58, 0x55, 0x9d, 0x56, 0x1f, 0x2c, 0x7d, 0xaa, 0x36, 0x7f, 0x04, 0x8d, 0x4b, 0xc2, 0x31,
|
||||
0xf6, 0xcc, 0xb6, 0xd5, 0x7d, 0xd4, 0x5c, 0xfc, 0x75, 0x9d, 0x09, 0x1b, 0xba, 0xee, 0x77, 0x03,
|
||||
0xa0, 0x6e, 0x08, 0xfa, 0x04, 0xdb, 0xf3, 0x32, 0xb4, 0xff, 0x77, 0x53, 0x1d, 0xce, 0x5d, 0x6a,
|
||||
0x32, 0x3a, 0x00, 0xb3, 0xc7, 0x18, 0xda, 0x69, 0x92, 0xeb, 0x2f, 0x74, 0x77, 0x17, 0xf0, 0x52,
|
||||
0x17, 0x3c, 0xf8, 0xe8, 0x4a, 0x25, 0xb2, 0x53, 0x9f, 0x8a, 0x8e, 0x7e, 0xe8, 0x8c, 0x73, 0xa6,
|
||||
0x28, 0x17, 0x09, 0x49, 0x07, 0x83, 0x35, 0xfd, 0x63, 0x9f, 0xfd, 0x0e, 0x00, 0x00, 0xff, 0xff,
|
||||
0x13, 0x5c, 0xf1, 0xa4, 0x47, 0x05, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// --- DRPC BEGIN ---
|
||||
|
||||
type DRPCReputationClient interface {
|
||||
DRPCConn() drpc.Conn
|
||||
|
||||
GetBySatelliteID(ctx context.Context, in *GetBySatelliteIDRequest) (*GetBySatelliteIDResponse, error)
|
||||
All(ctx context.Context, in *AllRequest) (*AllResponse, error)
|
||||
}
|
||||
|
||||
type drpcReputationClient struct {
|
||||
cc drpc.Conn
|
||||
}
|
||||
|
||||
func NewDRPCReputationClient(cc drpc.Conn) DRPCReputationClient {
|
||||
return &drpcReputationClient{cc}
|
||||
}
|
||||
|
||||
func (c *drpcReputationClient) DRPCConn() drpc.Conn { return c.cc }
|
||||
|
||||
func (c *drpcReputationClient) GetBySatelliteID(ctx context.Context, in *GetBySatelliteIDRequest) (*GetBySatelliteIDResponse, error) {
|
||||
out := new(GetBySatelliteIDResponse)
|
||||
err := c.cc.Invoke(ctx, "/reputation.Reputation/GetBySatelliteID", in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcReputationClient) All(ctx context.Context, in *AllRequest) (*AllResponse, error) {
|
||||
out := new(AllResponse)
|
||||
err := c.cc.Invoke(ctx, "/reputation.Reputation/All", in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type DRPCReputationServer interface {
|
||||
GetBySatelliteID(context.Context, *GetBySatelliteIDRequest) (*GetBySatelliteIDResponse, error)
|
||||
All(context.Context, *AllRequest) (*AllResponse, error)
|
||||
}
|
||||
|
||||
type DRPCReputationDescription struct{}
|
||||
|
||||
func (DRPCReputationDescription) NumMethods() int { return 2 }
|
||||
|
||||
func (DRPCReputationDescription) Method(n int) (string, drpc.Receiver, interface{}, bool) {
|
||||
switch n {
|
||||
case 0:
|
||||
return "/reputation.Reputation/GetBySatelliteID",
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCReputationServer).
|
||||
GetBySatelliteID(
|
||||
ctx,
|
||||
in1.(*GetBySatelliteIDRequest),
|
||||
)
|
||||
}, DRPCReputationServer.GetBySatelliteID, true
|
||||
case 1:
|
||||
return "/reputation.Reputation/All",
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCReputationServer).
|
||||
All(
|
||||
ctx,
|
||||
in1.(*AllRequest),
|
||||
)
|
||||
}, DRPCReputationServer.All, true
|
||||
default:
|
||||
return "", nil, nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func DRPCRegisterReputation(mux drpc.Mux, impl DRPCReputationServer) error {
|
||||
return mux.Register(impl, DRPCReputationDescription{})
|
||||
}
|
||||
|
||||
type DRPCReputation_GetBySatelliteIDStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*GetBySatelliteIDResponse) error
|
||||
}
|
||||
|
||||
type drpcReputationGetBySatelliteIDStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcReputationGetBySatelliteIDStream) SendAndClose(m *GetBySatelliteIDResponse) error {
|
||||
if err := x.MsgSend(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCReputation_AllStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*AllResponse) error
|
||||
}
|
||||
|
||||
type drpcReputationAllStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcReputationAllStream) SendAndClose(m *AllResponse) error {
|
||||
if err := x.MsgSend(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
// --- DRPC END ---
|
@ -1,46 +0,0 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
syntax = "proto3";
|
||||
option go_package = "storj.io/storj/multinodepb";
|
||||
|
||||
package reputation;
|
||||
|
||||
import "gogo.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service Reputation {
|
||||
rpc GetBySatelliteID(GetBySatelliteIDRequest) returns (GetBySatelliteIDResponse);
|
||||
rpc All(AllRequest) returns (AllResponse);
|
||||
}
|
||||
|
||||
message ReputationStats {
|
||||
int64 total_count = 1;
|
||||
int64 success_count = 2;
|
||||
double reputation_alpha = 3;
|
||||
double reputation_beta = 4;
|
||||
double reputation_score = 5;
|
||||
double unknown_reputation_alpha = 6;
|
||||
double unknown_reputation_beta = 7;
|
||||
double unknown_reputation_score = 8;
|
||||
}
|
||||
|
||||
message GetBySatelliteIDRequest {
|
||||
bytes satellite_id = 1 [(gogoproto.customtype) = "NodeID", (gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
message GetBySatelliteIDResponse {
|
||||
ReputationStats audit_check = 1;
|
||||
google.protobuf.Timestamp disqualified = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = true];
|
||||
google.protobuf.Timestamp suspended = 3 [(gogoproto.stdtime) = true, (gogoproto.nullable) = true];
|
||||
google.protobuf.Timestamp joined_at = 4 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
|
||||
google.protobuf.Timestamp offline_suspended = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = true];
|
||||
double online_score = 6;
|
||||
google.protobuf.Timestamp offline_under_review = 7 [(gogoproto.stdtime) = true, (gogoproto.nullable) = true];
|
||||
}
|
||||
|
||||
message AllRequest {}
|
||||
|
||||
message AllResponse {
|
||||
repeated GetBySatelliteIDResponse reputation = 1;
|
||||
}
|
@ -1,202 +0,0 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: status.proto
|
||||
|
||||
package multinodepb
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
math "math"
|
||||
time "time"
|
||||
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
|
||||
drpc "storj.io/drpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
var _ = time.Kitchen
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type GetRequest struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *GetRequest) Reset() { *m = GetRequest{} }
|
||||
func (m *GetRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetRequest) ProtoMessage() {}
|
||||
func (*GetRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_dfe4fce6682daf5b, []int{0}
|
||||
}
|
||||
func (m *GetRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_GetRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *GetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_GetRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *GetRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_GetRequest.Merge(m, src)
|
||||
}
|
||||
func (m *GetRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_GetRequest.Size(m)
|
||||
}
|
||||
func (m *GetRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_GetRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_GetRequest proto.InternalMessageInfo
|
||||
|
||||
type GetResponse struct {
|
||||
StartedAt time.Time `protobuf:"bytes,1,opt,name=started_at,json=startedAt,proto3,stdtime" json:"started_at"`
|
||||
Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *GetResponse) Reset() { *m = GetResponse{} }
|
||||
func (m *GetResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetResponse) ProtoMessage() {}
|
||||
func (*GetResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_dfe4fce6682daf5b, []int{1}
|
||||
}
|
||||
func (m *GetResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_GetResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *GetResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_GetResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *GetResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_GetResponse.Merge(m, src)
|
||||
}
|
||||
func (m *GetResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_GetResponse.Size(m)
|
||||
}
|
||||
func (m *GetResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_GetResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_GetResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *GetResponse) GetStartedAt() time.Time {
|
||||
if m != nil {
|
||||
return m.StartedAt
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (m *GetResponse) GetVersion() string {
|
||||
if m != nil {
|
||||
return m.Version
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*GetRequest)(nil), "status.GetRequest")
|
||||
proto.RegisterType((*GetResponse)(nil), "status.GetResponse")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("status.proto", fileDescriptor_dfe4fce6682daf5b) }
|
||||
|
||||
var fileDescriptor_dfe4fce6682daf5b = []byte{
|
||||
// 225 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x8f, 0xb1, 0x4e, 0xc3, 0x30,
|
||||
0x10, 0x86, 0x09, 0x48, 0x81, 0x5e, 0x3b, 0x99, 0x25, 0xb2, 0x90, 0x52, 0x75, 0xea, 0xe4, 0x48,
|
||||
0x65, 0x61, 0xa5, 0x0c, 0xdd, 0x03, 0x13, 0x0b, 0x4a, 0xd4, 0xc3, 0x32, 0x4a, 0x72, 0xc6, 0x77,
|
||||
0xe6, 0x39, 0x78, 0x2c, 0x9e, 0x02, 0x5e, 0x05, 0xc9, 0x6e, 0x84, 0xd8, 0xee, 0xfb, 0x75, 0xf7,
|
||||
0xeb, 0x3b, 0x58, 0xb1, 0x74, 0x12, 0xd9, 0xf8, 0x40, 0x42, 0xaa, 0xcc, 0xa4, 0xc1, 0x92, 0xa5,
|
||||
0x9c, 0xe9, 0xda, 0x12, 0xd9, 0x01, 0x9b, 0x44, 0x7d, 0x7c, 0x6d, 0xc4, 0x8d, 0xc8, 0xd2, 0x8d,
|
||||
0x3e, 0x2f, 0x6c, 0x56, 0x00, 0x07, 0x94, 0x16, 0xdf, 0x23, 0xb2, 0x6c, 0x06, 0x58, 0x26, 0x62,
|
||||
0x4f, 0x13, 0xa3, 0x7a, 0x00, 0x60, 0xe9, 0x82, 0xe0, 0xf1, 0xa5, 0x93, 0xaa, 0x58, 0x17, 0xdb,
|
||||
0xe5, 0x4e, 0x9b, 0x5c, 0x69, 0xe6, 0x4a, 0xf3, 0x34, 0x57, 0xee, 0xaf, 0xbe, 0xbe, 0xeb, 0xb3,
|
||||
0xcf, 0x9f, 0xba, 0x68, 0x17, 0xa7, 0xbb, 0x7b, 0x51, 0x15, 0x5c, 0x7e, 0x60, 0x60, 0x47, 0x53,
|
||||
0x75, 0xbe, 0x2e, 0xb6, 0x8b, 0x76, 0xc6, 0xdd, 0x1d, 0x94, 0x8f, 0x49, 0x59, 0x19, 0xb8, 0x38,
|
||||
0xa0, 0x28, 0x65, 0x4e, 0x0f, 0xfd, 0x29, 0xe9, 0xeb, 0x7f, 0x59, 0x16, 0xdb, 0xdf, 0x3c, 0x6b,
|
||||
0x16, 0x0a, 0x6f, 0xc6, 0x51, 0x93, 0x86, 0x66, 0x8c, 0x83, 0xb8, 0x89, 0x8e, 0xe8, 0xfb, 0xbe,
|
||||
0x4c, 0x6a, 0xb7, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x81, 0x06, 0x90, 0x45, 0x1f, 0x01, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
||||
// --- DRPC BEGIN ---
|
||||
|
||||
type DRPCStatusClient interface {
|
||||
DRPCConn() drpc.Conn
|
||||
|
||||
Get(ctx context.Context, in *GetRequest) (*GetResponse, error)
|
||||
}
|
||||
|
||||
type drpcStatusClient struct {
|
||||
cc drpc.Conn
|
||||
}
|
||||
|
||||
func NewDRPCStatusClient(cc drpc.Conn) DRPCStatusClient {
|
||||
return &drpcStatusClient{cc}
|
||||
}
|
||||
|
||||
func (c *drpcStatusClient) DRPCConn() drpc.Conn { return c.cc }
|
||||
|
||||
func (c *drpcStatusClient) Get(ctx context.Context, in *GetRequest) (*GetResponse, error) {
|
||||
out := new(GetResponse)
|
||||
err := c.cc.Invoke(ctx, "/status.Status/Get", in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type DRPCStatusServer interface {
|
||||
Get(context.Context, *GetRequest) (*GetResponse, error)
|
||||
}
|
||||
|
||||
type DRPCStatusDescription struct{}
|
||||
|
||||
func (DRPCStatusDescription) NumMethods() int { return 1 }
|
||||
|
||||
func (DRPCStatusDescription) Method(n int) (string, drpc.Receiver, interface{}, bool) {
|
||||
switch n {
|
||||
case 0:
|
||||
return "/status.Status/Get",
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCStatusServer).
|
||||
Get(
|
||||
ctx,
|
||||
in1.(*GetRequest),
|
||||
)
|
||||
}, DRPCStatusServer.Get, true
|
||||
default:
|
||||
return "", nil, nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func DRPCRegisterStatus(mux drpc.Mux, impl DRPCStatusServer) error {
|
||||
return mux.Register(impl, DRPCStatusDescription{})
|
||||
}
|
||||
|
||||
type DRPCStatus_GetStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*GetResponse) error
|
||||
}
|
||||
|
||||
type drpcStatusGetStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcStatusGetStream) SendAndClose(m *GetResponse) error {
|
||||
if err := x.MsgSend(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
// --- DRPC END ---
|
@ -1,21 +0,0 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
syntax = "proto3";
|
||||
option go_package = "storj.io/storj/multinodepb";
|
||||
|
||||
package status;
|
||||
|
||||
import "gogo.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service Status {
|
||||
rpc Get(GetRequest) returns (GetResponse);
|
||||
}
|
||||
|
||||
message GetRequest {}
|
||||
|
||||
message GetResponse {
|
||||
google.protobuf.Timestamp started_at = 1 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
|
||||
string version = 2; // must be semver formatted
|
||||
}
|
61
private/multinodeauth/auth.go
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package multinodeauth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
)
|
||||
|
||||
// Secret crypto random 32 bytes array for multinode auth.
|
||||
type Secret [32]byte
|
||||
|
||||
// NewSecret creates new multinode auth secret.
|
||||
func NewSecret() (Secret, error) {
|
||||
var b [32]byte
|
||||
|
||||
_, err := rand.Read(b[:])
|
||||
if err != nil {
|
||||
return b, errs.New("error creating multinode auth secret")
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// String implements Stringer.
|
||||
func (secret Secret) String() string {
|
||||
return base64.URLEncoding.EncodeToString(secret[:])
|
||||
}
|
||||
|
||||
// IsZero returns if secret is not set.
|
||||
func (secret Secret) IsZero() bool {
|
||||
var zero Secret
|
||||
// this doesn't need to be constant-time, because we're explicitly testing
|
||||
// against a hardcoded, well-known value
|
||||
return bytes.Equal(secret[:], zero[:])
|
||||
}
|
||||
|
||||
// SecretFromBase64 creates new secret from base64 string.
|
||||
func SecretFromBase64(s string) (Secret, error) {
|
||||
b, err := base64.URLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return Secret{}, err
|
||||
}
|
||||
|
||||
return SecretFromBytes(b)
|
||||
}
|
||||
|
||||
// SecretFromBytes creates secret from bytes slice.
|
||||
func SecretFromBytes(b []byte) (Secret, error) {
|
||||
if len(b) != 32 {
|
||||
return Secret{}, errs.New("invalid secret")
|
||||
}
|
||||
|
||||
var secret Secret
|
||||
copy(secret[:], b)
|
||||
return secret, nil
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
// Package multinodepb contains protobuf definitions for Storj peers.
|
||||
// Package multinodepb contains protobuf definitions for storagenode multinode dashboard.
|
||||
package multinodepb
|
||||
|
||||
//go:generate go run gen.go
|
@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
mainpkg = flag.String("pkg", "storj.io/storj/multinodepb", "main package name")
|
||||
mainpkg = flag.String("pkg", "storj.io/storj/private/multinodepb", "main package name")
|
||||
protoc = flag.String("protoc", "protoc", "protoc compiler")
|
||||
)
|
||||
|
||||
@ -63,7 +63,7 @@ func main() {
|
||||
|
||||
protofiles = ignore(protofiles)
|
||||
|
||||
overrideImports := ",Mgoogle/protobuf/timestamp.proto=storj.io/storj/multinodepb"
|
||||
overrideImports := ",Mgoogle/protobuf/timestamp.proto=" + *mainpkg
|
||||
args := []string{
|
||||
"--lint_out=.",
|
||||
"--drpc_out=plugins=drpc,paths=source_relative" + overrideImports + ":.",
|
||||
@ -75,7 +75,9 @@ func main() {
|
||||
cmd := exec.Command(*protoc, args...)
|
||||
fmt.Println(strings.Join(cmd.Args, " "))
|
||||
out, err := cmd.CombinedOutput()
|
||||
fmt.Println(string(out))
|
||||
if len(out) > 0 {
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
check(err)
|
||||
}
|
||||
|
||||
@ -90,7 +92,9 @@ func main() {
|
||||
{
|
||||
// format code to get rid of extra imports
|
||||
out, err := exec.Command("goimports", "-local", "storj.io", "-w", ".").CombinedOutput()
|
||||
fmt.Println(string(out))
|
||||
if len(out) > 0 {
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
check(err)
|
||||
}
|
||||
}
|
@ -78,8 +78,8 @@ extend google.protobuf.FileOptions {
|
||||
optional bool gogoproto_import = 63027;
|
||||
optional bool protosizer_all = 63028;
|
||||
optional bool compare_all = 63029;
|
||||
optional bool typedecl_all = 63030;
|
||||
optional bool enumdecl_all = 63031;
|
||||
optional bool typedecl_all = 63030;
|
||||
optional bool enumdecl_all = 63031;
|
||||
|
||||
optional bool goproto_registration = 63032;
|
||||
optional bool messagename_all = 63033;
|
||||
@ -139,5 +139,4 @@ extend google.protobuf.FieldOptions {
|
||||
optional bool stdduration = 65011;
|
||||
optional bool wktpointer = 65012;
|
||||
optional bool compare = 65013;
|
||||
|
||||
}
|
1086
private/multinodepb/multinode.pb.go
Normal file
97
private/multinodepb/multinode.proto
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
syntax = "proto3";
|
||||
option go_package = "storj.io/storj/private/multinodepb";
|
||||
|
||||
package multinode;
|
||||
|
||||
import "gogo.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
message RequestHeader {
|
||||
bytes api_key = 1;
|
||||
}
|
||||
|
||||
service Storage {
|
||||
rpc DiskSpace(DiskSpaceRequest) returns (DiskSpaceResponse);
|
||||
}
|
||||
|
||||
message DiskSpaceRequest {
|
||||
RequestHeader header = 1;
|
||||
}
|
||||
|
||||
message DiskSpaceResponse {
|
||||
int64 allocated = 1;
|
||||
int64 used_pieces = 2;
|
||||
int64 used_trash = 3;
|
||||
int64 free = 4;
|
||||
int64 available = 5;
|
||||
int64 overused = 6;
|
||||
}
|
||||
|
||||
service Bandwidth {
|
||||
rpc MonthSummary(BandwidthMonthSummaryRequest) returns (BandwidthMonthSummaryResponse);
|
||||
}
|
||||
|
||||
message BandwidthMonthSummaryRequest {
|
||||
RequestHeader header = 1;
|
||||
}
|
||||
|
||||
message BandwidthMonthSummaryResponse {
|
||||
int64 used = 1;
|
||||
}
|
||||
|
||||
service Node {
|
||||
rpc Version(VersionRequest) returns (VersionResponse);
|
||||
rpc LastContact(LastContactRequest) returns (LastContactResponse);
|
||||
rpc Reputation(ReputationRequest) returns (ReputationResponse);
|
||||
rpc TrustedSatellites(TrustedSatellitesRequest) returns (TrustedSatellitesResponse);
|
||||
}
|
||||
|
||||
message VersionRequest {
|
||||
RequestHeader header = 1;
|
||||
}
|
||||
|
||||
message VersionResponse {
|
||||
string version = 1; // must be semver formatted
|
||||
}
|
||||
|
||||
message LastContactRequest {
|
||||
RequestHeader header = 1;
|
||||
}
|
||||
|
||||
message LastContactResponse {
|
||||
google.protobuf.Timestamp last_contact = 1 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
message ReputationRequest {
|
||||
RequestHeader header = 1;
|
||||
bytes satellite_id = 2 [(gogoproto.customtype) = "NodeID", (gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
message ReputationResponse {
|
||||
message Online {
|
||||
double score = 1;
|
||||
}
|
||||
message Audit {
|
||||
double score = 1;
|
||||
double suspension_score = 2;
|
||||
}
|
||||
|
||||
Online online = 1;
|
||||
Audit audit = 2;
|
||||
}
|
||||
|
||||
message TrustedSatellitesRequest {
|
||||
RequestHeader header = 1;
|
||||
}
|
||||
|
||||
message TrustedSatellitesResponse {
|
||||
message NodeURL {
|
||||
bytes node_id = 1 [(gogoproto.customtype) = "NodeID", (gogoproto.nullable) = false];
|
||||
string address = 2;
|
||||
}
|
||||
|
||||
repeated NodeURL trusted_satellites = 1;
|
||||
}
|
@ -335,6 +335,22 @@ func (client *Uplink) DeleteBucket(ctx context.Context, satellite *Satellite, bu
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListBuckets returns a list of all buckets in a project.
|
||||
func (client *Uplink) ListBuckets(ctx context.Context, satellite *Satellite) ([]*uplink.Bucket, error) {
|
||||
var buckets = []*uplink.Bucket{}
|
||||
project, err := client.GetProject(ctx, satellite)
|
||||
if err != nil {
|
||||
return buckets, err
|
||||
}
|
||||
defer func() { err = errs.Combine(err, project.Close()) }()
|
||||
|
||||
iter := project.ListBuckets(ctx, &uplink.ListBucketsOptions{})
|
||||
for iter.Next() {
|
||||
buckets = append(buckets, iter.Item())
|
||||
}
|
||||
return buckets, iter.Err()
|
||||
}
|
||||
|
||||
// GetProject returns a uplink.Project which allows interactions with a specific project.
|
||||
func (client *Uplink) GetProject(ctx context.Context, satellite *Satellite) (*uplink.Project, error) {
|
||||
access := client.Access[satellite.ID()]
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/common/testrand"
|
||||
"storj.io/storj/private/testplanet"
|
||||
"storj.io/storj/satellite/internalpb"
|
||||
"storj.io/uplink/private/ecclient"
|
||||
"storj.io/uplink/private/eestream"
|
||||
)
|
||||
@ -132,8 +133,8 @@ func newAddressedOrderLimit(ctx context.Context, action pb.PieceAction, satellit
|
||||
key := satellite.Config.Orders.EncryptionKeys.Default
|
||||
encrypted, err := key.EncryptMetadata(
|
||||
serialNumber,
|
||||
&pb.OrderLimitMetadata{
|
||||
ProjectBucketPrefix: []byte("testprojectid/testbucketname"),
|
||||
&internalpb.OrderLimitMetadata{
|
||||
CompactProjectBucketPrefix: []byte("0000111122223333testbucketname"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -269,7 +269,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
{ // setup contact service
|
||||
c := config.Contact
|
||||
if c.ExternalAddress == "" {
|
||||
c.ExternalAddress = peer.Addr()
|
||||
c.ExternalAddress = peer.Server.Addr().String()
|
||||
}
|
||||
|
||||
pbVersion, err := versionInfo.Proto()
|
||||
@ -338,10 +338,6 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
peer.Orders.DB,
|
||||
peer.DB.Buckets(),
|
||||
config.Orders,
|
||||
&pb.NodeAddress{
|
||||
Transport: pb.NodeTransport_TCP_TLS_GRPC,
|
||||
Address: config.Contact.ExternalAddress,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
@ -721,7 +717,9 @@ func (peer *API) Close() error {
|
||||
func (peer *API) ID() storj.NodeID { return peer.Identity.ID }
|
||||
|
||||
// Addr returns the public address.
|
||||
func (peer *API) Addr() string { return peer.Server.Addr().String() }
|
||||
func (peer *API) Addr() string {
|
||||
return peer.Contact.Service.Local().Node.Address.Address
|
||||
}
|
||||
|
||||
// URL returns the storj.NodeURL.
|
||||
func (peer *API) URL() storj.NodeURL {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/storj/private/testplanet"
|
||||
console "storj.io/storj/satellite/console/consolewasm"
|
||||
"storj.io/uplink"
|
||||
)
|
||||
|
||||
// TestGenerateAccessGrant confirms that the access grant produced by the wasm access code
|
||||
@ -38,3 +39,44 @@ func TestGenerateAccessGrant(t *testing.T) {
|
||||
require.Equal(t, wasmAccessString, uplinkCliAccessString)
|
||||
})
|
||||
}
|
||||
|
||||
// TestDefaultAccess confirms that you can perform basic uplink operations with
|
||||
// the default access grant created from wasm code.
|
||||
func TestDefaultAccess(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1, StorageNodeCount: 10, UplinkCount: 1,
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
satellitePeer := planet.Satellites[0]
|
||||
satelliteNodeURL := satellitePeer.NodeURL().String()
|
||||
uplinkPeer := planet.Uplinks[0]
|
||||
APIKey := uplinkPeer.APIKey[satellitePeer.ID()]
|
||||
projectID := uplinkPeer.Projects[0].ID.String()
|
||||
require.Equal(t, 1, len(uplinkPeer.Projects))
|
||||
|
||||
passphrase := "supersecretpassphrase"
|
||||
testbucket1 := "buckettest1"
|
||||
testfilename := "file.txt"
|
||||
testdata := []byte("fun data")
|
||||
|
||||
// Create an access with the console access grant code that allows full access.
|
||||
access, err := console.GenAccessGrant(satelliteNodeURL, APIKey.Serialize(), passphrase, projectID)
|
||||
require.NoError(t, err)
|
||||
newAccess, err := uplink.ParseAccess(access)
|
||||
require.NoError(t, err)
|
||||
uplinkPeer.Access[satellitePeer.ID()] = newAccess
|
||||
|
||||
// Confirm that we can create a bucket, upload/download/delete an object, and delete the bucket with the new access.
|
||||
require.NoError(t, uplinkPeer.CreateBucket(ctx, satellitePeer, testbucket1))
|
||||
err = uplinkPeer.Upload(ctx, satellitePeer, testbucket1, testfilename, testdata)
|
||||
require.NoError(t, err)
|
||||
data, err := uplinkPeer.Download(ctx, satellitePeer, testbucket1, testfilename)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, data, testdata)
|
||||
buckets, err := uplinkPeer.ListBuckets(ctx, satellitePeer)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(buckets), 1)
|
||||
err = uplinkPeer.DeleteObject(ctx, satellitePeer, testbucket1, testfilename)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, uplinkPeer.DeleteBucket(ctx, satellitePeer, testbucket1))
|
||||
})
|
||||
}
|
||||
|
@ -97,3 +97,53 @@ func TestSetPermissionWithBuckets(t *testing.T) {
|
||||
require.True(t, errs2.IsRPC(err, rpcstatus.PermissionDenied))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetPermissionUplinkOperations(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1, StorageNodeCount: 10, UplinkCount: 1,
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
satellitePeer := planet.Satellites[0]
|
||||
satelliteNodeURL := satellitePeer.NodeURL().String()
|
||||
uplinkPeer := planet.Uplinks[0]
|
||||
APIKey := uplinkPeer.APIKey[satellitePeer.ID()]
|
||||
apiKeyString := APIKey.Serialize()
|
||||
projectID := uplinkPeer.Projects[0].ID.String()
|
||||
require.Equal(t, 1, len(uplinkPeer.Projects))
|
||||
|
||||
allPermission := console.Permission{
|
||||
AllowDownload: true,
|
||||
AllowUpload: true,
|
||||
AllowList: true,
|
||||
AllowDelete: true,
|
||||
NotBefore: time.Now().Add(-24 * time.Hour),
|
||||
NotAfter: time.Now().Add(48 * time.Hour),
|
||||
}
|
||||
restrictedKey, err := console.SetPermission(apiKeyString, []string{}, allPermission)
|
||||
require.NoError(t, err)
|
||||
passphrase := "supersecretpassphrase"
|
||||
restrictedAccessGrant, err := console.GenAccessGrant(satelliteNodeURL, restrictedKey.Serialize(), passphrase, projectID)
|
||||
require.NoError(t, err)
|
||||
restrictedAccess, err := uplink.ParseAccess(restrictedAccessGrant)
|
||||
require.NoError(t, err)
|
||||
|
||||
uplinkPeer.APIKey[satellitePeer.ID()] = restrictedKey
|
||||
uplinkPeer.Access[satellitePeer.ID()] = restrictedAccess
|
||||
testbucket1 := "buckettest1"
|
||||
testfilename := "file.txt"
|
||||
testdata := []byte("fun data")
|
||||
|
||||
// Confirm that we can create a bucket, upload/download/delete an object, and delete the bucket with the new restricted access.
|
||||
require.NoError(t, uplinkPeer.CreateBucket(ctx, satellitePeer, testbucket1))
|
||||
err = uplinkPeer.Upload(ctx, satellitePeer, testbucket1, testfilename, testdata)
|
||||
require.NoError(t, err)
|
||||
data, err := uplinkPeer.Download(ctx, satellitePeer, testbucket1, testfilename)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, data, testdata)
|
||||
buckets, err := uplinkPeer.ListBuckets(ctx, satellitePeer)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(buckets), 1)
|
||||
err = uplinkPeer.DeleteObject(ctx, satellitePeer, testbucket1, testfilename)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, uplinkPeer.DeleteBucket(ctx, satellitePeer, testbucket1))
|
||||
})
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ var (
|
||||
|
||||
// Config contains configuration for console web server.
|
||||
type Config struct {
|
||||
Address string `help:"server address of the graphql api gateway and frontend app" devDefault:"127.0.0.1:8081" releaseDefault:":10100"`
|
||||
Address string `help:"server address of the graphql api gateway and frontend app" devDefault:"" releaseDefault:":10100"`
|
||||
StaticDir string `help:"path to static resources" default:""`
|
||||
ExternalAddress string `help:"external endpoint of the satellite if hosted" default:""`
|
||||
|
||||
|
@ -252,10 +252,6 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
peer.Orders.DB,
|
||||
peer.DB.Buckets(),
|
||||
config.Orders,
|
||||
&pb.NodeAddress{
|
||||
Transport: pb.NodeTransport_TCP_TLS_GRPC,
|
||||
Address: config.Contact.ExternalAddress,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
|
100
satellite/internalpb/ordersmeta.pb.go
Normal file
@ -0,0 +1,100 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: ordersmeta.proto
|
||||
|
||||
package internalpb
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
math "math"
|
||||
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// OrderLimitMetadata is used to transmit meta information about an order limit.
|
||||
// This data will be encrypted.
|
||||
type OrderLimitMetadata struct {
|
||||
BucketId []byte `protobuf:"bytes,1,opt,name=bucket_id,json=bucketId,proto3" json:"bucket_id,omitempty"`
|
||||
ProjectBucketPrefix []byte `protobuf:"bytes,2,opt,name=project_bucket_prefix,json=projectBucketPrefix,proto3" json:"project_bucket_prefix,omitempty"`
|
||||
CompactProjectBucketPrefix []byte `protobuf:"bytes,3,opt,name=compact_project_bucket_prefix,json=compactProjectBucketPrefix,proto3" json:"compact_project_bucket_prefix,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *OrderLimitMetadata) Reset() { *m = OrderLimitMetadata{} }
|
||||
func (m *OrderLimitMetadata) String() string { return proto.CompactTextString(m) }
|
||||
func (*OrderLimitMetadata) ProtoMessage() {}
|
||||
func (*OrderLimitMetadata) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_e00ef1afe54bb544, []int{0}
|
||||
}
|
||||
func (m *OrderLimitMetadata) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_OrderLimitMetadata.Unmarshal(m, b)
|
||||
}
|
||||
func (m *OrderLimitMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_OrderLimitMetadata.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *OrderLimitMetadata) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_OrderLimitMetadata.Merge(m, src)
|
||||
}
|
||||
func (m *OrderLimitMetadata) XXX_Size() int {
|
||||
return xxx_messageInfo_OrderLimitMetadata.Size(m)
|
||||
}
|
||||
func (m *OrderLimitMetadata) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_OrderLimitMetadata.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_OrderLimitMetadata proto.InternalMessageInfo
|
||||
|
||||
func (m *OrderLimitMetadata) GetBucketId() []byte {
|
||||
if m != nil {
|
||||
return m.BucketId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *OrderLimitMetadata) GetProjectBucketPrefix() []byte {
|
||||
if m != nil {
|
||||
return m.ProjectBucketPrefix
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *OrderLimitMetadata) GetCompactProjectBucketPrefix() []byte {
|
||||
if m != nil {
|
||||
return m.CompactProjectBucketPrefix
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*OrderLimitMetadata)(nil), "satellite.ordersmeta.OrderLimitMetadata")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("ordersmeta.proto", fileDescriptor_e00ef1afe54bb544) }
|
||||
|
||||
var fileDescriptor_e00ef1afe54bb544 = []byte{
|
||||
// 192 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x2f, 0x4a, 0x49,
|
||||
0x2d, 0x2a, 0xce, 0x4d, 0x2d, 0x49, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x29, 0x4e,
|
||||
0x2c, 0x49, 0xcd, 0xc9, 0xc9, 0x2c, 0x49, 0xd5, 0x43, 0xc8, 0x29, 0xad, 0x60, 0xe4, 0x12, 0xf2,
|
||||
0x07, 0x71, 0x7d, 0x32, 0x73, 0x33, 0x4b, 0x7c, 0x53, 0x4b, 0x12, 0x53, 0x12, 0x4b, 0x12, 0x85,
|
||||
0xa4, 0xb9, 0x38, 0x93, 0x4a, 0x93, 0xb3, 0x53, 0x4b, 0xe2, 0x33, 0x53, 0x24, 0x18, 0x15, 0x18,
|
||||
0x35, 0x78, 0x82, 0x38, 0x20, 0x02, 0x9e, 0x29, 0x42, 0x46, 0x5c, 0xa2, 0x05, 0x45, 0xf9, 0x59,
|
||||
0xa9, 0xc9, 0x25, 0xf1, 0x50, 0x45, 0x05, 0x45, 0xa9, 0x69, 0x99, 0x15, 0x12, 0x4c, 0x60, 0x85,
|
||||
0xc2, 0x50, 0x49, 0x27, 0xb0, 0x5c, 0x00, 0x58, 0x4a, 0xc8, 0x91, 0x4b, 0x36, 0x39, 0x3f, 0xb7,
|
||||
0x20, 0x31, 0x19, 0xa4, 0x18, 0x9b, 0x5e, 0x66, 0xb0, 0x5e, 0x29, 0xa8, 0xa2, 0x00, 0x4c, 0x23,
|
||||
0x9c, 0x54, 0xa3, 0x94, 0x8b, 0x4b, 0xf2, 0x8b, 0xb2, 0xf4, 0x32, 0xf3, 0xf5, 0xc1, 0x0c, 0x7d,
|
||||
0xb8, 0x8f, 0xf4, 0x33, 0xf3, 0x4a, 0x52, 0x8b, 0xf2, 0x12, 0x73, 0x0a, 0x92, 0x92, 0xd8, 0xc0,
|
||||
0xde, 0x35, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0xcc, 0x15, 0xb0, 0x02, 0x01, 0x00, 0x00,
|
||||
}
|
15
satellite/internalpb/ordersmeta.proto
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
syntax = "proto3";
|
||||
option go_package = "storj.io/storj/satellite/internalpb";
|
||||
|
||||
package satellite.ordersmeta;
|
||||
|
||||
// OrderLimitMetadata is used to transmit meta information about an order limit.
|
||||
// This data will be encrypted.
|
||||
message OrderLimitMetadata {
|
||||
bytes bucket_id = 1;
|
||||
bytes project_bucket_prefix = 2;
|
||||
bytes compact_project_bucket_prefix = 3;
|
||||
}
|
@ -68,11 +68,31 @@ func (loc BucketLocation) Verify() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseCompactBucketPrefix parses BucketPrefix.
|
||||
func ParseCompactBucketPrefix(compactPrefix []byte) (BucketLocation, error) {
|
||||
if len(compactPrefix) < 16 {
|
||||
return BucketLocation{}, Error.New("invalid prefix %q", compactPrefix)
|
||||
}
|
||||
|
||||
var loc BucketLocation
|
||||
copy(loc.ProjectID[:], compactPrefix)
|
||||
loc.BucketName = string(compactPrefix[16:])
|
||||
return loc, nil
|
||||
}
|
||||
|
||||
// Prefix converts bucket location into bucket prefix.
|
||||
func (loc BucketLocation) Prefix() BucketPrefix {
|
||||
return BucketPrefix(loc.ProjectID.String() + "/" + loc.BucketName)
|
||||
}
|
||||
|
||||
// CompactPrefix converts bucket location into bucket prefix with compact project ID.
|
||||
func (loc BucketLocation) CompactPrefix() []byte {
|
||||
xs := make([]byte, 0, 16+len(loc.BucketName))
|
||||
xs = append(xs, loc.ProjectID[:]...)
|
||||
xs = append(xs, []byte(loc.BucketName)...)
|
||||
return xs
|
||||
}
|
||||
|
||||
// ObjectKey is an encrypted object key encoded using Path Component Encoding.
|
||||
// It is not ascii safe.
|
||||
type ObjectKey string
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
"storj.io/common/pb"
|
||||
"storj.io/common/storj"
|
||||
"storj.io/storj/satellite/internalpb"
|
||||
)
|
||||
|
||||
// ErrEncryptionKey is error class used for keys.
|
||||
@ -82,7 +83,7 @@ func (key *EncryptionKey) Decrypt(ciphertext []byte, nonce storj.SerialNumber) (
|
||||
}
|
||||
|
||||
// EncryptMetadata encrypts order limit metadata.
|
||||
func (key *EncryptionKey) EncryptMetadata(serial storj.SerialNumber, metadata *pb.OrderLimitMetadata) ([]byte, error) {
|
||||
func (key *EncryptionKey) EncryptMetadata(serial storj.SerialNumber, metadata *internalpb.OrderLimitMetadata) ([]byte, error) {
|
||||
marshaled, err := pb.Marshal(metadata)
|
||||
if err != nil {
|
||||
return nil, ErrEncryptionKey.Wrap(err)
|
||||
@ -91,13 +92,13 @@ func (key *EncryptionKey) EncryptMetadata(serial storj.SerialNumber, metadata *p
|
||||
}
|
||||
|
||||
// DecryptMetadata decrypts order limit metadata.
|
||||
func (key *EncryptionKey) DecryptMetadata(serial storj.SerialNumber, encrypted []byte) (*pb.OrderLimitMetadata, error) {
|
||||
func (key *EncryptionKey) DecryptMetadata(serial storj.SerialNumber, encrypted []byte) (*internalpb.OrderLimitMetadata, error) {
|
||||
decrypted, err := key.Decrypt(encrypted, serial)
|
||||
if err != nil {
|
||||
return nil, ErrEncryptionKey.Wrap(err)
|
||||
}
|
||||
|
||||
metadata := &pb.OrderLimitMetadata{}
|
||||
metadata := &internalpb.OrderLimitMetadata{}
|
||||
err = pb.Unmarshal(decrypted, metadata)
|
||||
if err != nil {
|
||||
return nil, ErrEncryptionKey.Wrap(err)
|
||||
|
@ -649,14 +649,29 @@ func (endpoint *Endpoint) SettlementWithWindowFinal(stream pb.DRPCOrders_Settlem
|
||||
mon.Event("bucketinfo_from_orders_metadata_error_1")
|
||||
continue
|
||||
}
|
||||
bucketInfo, err := metabase.ParseBucketPrefix(
|
||||
metabase.BucketPrefix(metadata.GetProjectBucketPrefix()),
|
||||
)
|
||||
if err != nil {
|
||||
log.Debug("decrypt order: ParseBucketPrefix", zap.Error(err))
|
||||
mon.Event("bucketinfo_from_orders_metadata_error_2")
|
||||
|
||||
var bucketInfo metabase.BucketLocation
|
||||
switch {
|
||||
case len(metadata.CompactProjectBucketPrefix) > 0:
|
||||
bucketInfo, err = metabase.ParseCompactBucketPrefix(metadata.GetCompactProjectBucketPrefix())
|
||||
if err != nil {
|
||||
log.Debug("decrypt order: ParseCompactBucketPrefix", zap.Error(err))
|
||||
mon.Event("bucketinfo_from_orders_metadata_error_compact")
|
||||
continue
|
||||
}
|
||||
case len(metadata.ProjectBucketPrefix) > 0:
|
||||
bucketInfo, err = metabase.ParseBucketPrefix(metabase.BucketPrefix(metadata.GetProjectBucketPrefix()))
|
||||
if err != nil {
|
||||
log.Debug("decrypt order: ParseBucketPrefix", zap.Error(err))
|
||||
mon.Event("bucketinfo_from_orders_metadata_error_uncompact")
|
||||
continue
|
||||
}
|
||||
default:
|
||||
log.Debug("decrypt order: project bucket prefix missing", zap.Error(err))
|
||||
mon.Event("bucketinfo_from_orders_metadata_error_default")
|
||||
continue
|
||||
}
|
||||
|
||||
if bucketInfo.BucketName == "" || bucketInfo.ProjectID.IsZero() {
|
||||
log.Info("decrypt order: bucketName or projectID not set",
|
||||
zap.String("bucketName", bucketInfo.BucketName),
|
||||
|
@ -20,6 +20,8 @@ import (
|
||||
"storj.io/common/testrand"
|
||||
"storj.io/storj/private/testplanet"
|
||||
"storj.io/storj/satellite"
|
||||
"storj.io/storj/satellite/internalpb"
|
||||
"storj.io/storj/satellite/metainfo/metabase"
|
||||
"storj.io/storj/satellite/orders"
|
||||
)
|
||||
|
||||
@ -52,7 +54,10 @@ func TestSettlementWithWindowEndpointManyOrders(t *testing.T) {
|
||||
now := time.Now()
|
||||
projectID := testrand.UUID()
|
||||
bucketname := "testbucket"
|
||||
bucketID := storj.JoinPaths(projectID.String(), bucketname)
|
||||
bucketLocation := metabase.BucketLocation{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketname,
|
||||
}
|
||||
key := satellite.Config.Orders.EncryptionKeys.Default
|
||||
|
||||
// stop any async flushes because we want to be sure when some values are
|
||||
@ -84,8 +89,8 @@ func TestSettlementWithWindowEndpointManyOrders(t *testing.T) {
|
||||
serialNumber1 := testrand.SerialNumber()
|
||||
encrypted1, err := key.EncryptMetadata(
|
||||
serialNumber1,
|
||||
&pb.OrderLimitMetadata{
|
||||
ProjectBucketPrefix: []byte(bucketID),
|
||||
&internalpb.OrderLimitMetadata{
|
||||
CompactProjectBucketPrefix: bucketLocation.CompactPrefix(),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@ -93,8 +98,8 @@ func TestSettlementWithWindowEndpointManyOrders(t *testing.T) {
|
||||
serialNumber2 := testrand.SerialNumber()
|
||||
encrypted2, err := key.EncryptMetadata(
|
||||
serialNumber2,
|
||||
&pb.OrderLimitMetadata{
|
||||
ProjectBucketPrefix: []byte(bucketID),
|
||||
&internalpb.OrderLimitMetadata{
|
||||
CompactProjectBucketPrefix: bucketLocation.CompactPrefix(),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@ -210,7 +215,10 @@ func TestSettlementWithWindowEndpointSingleOrder(t *testing.T) {
|
||||
now := time.Now()
|
||||
projectID := testrand.UUID()
|
||||
bucketname := "testbucket"
|
||||
bucketID := storj.JoinPaths(projectID.String(), bucketname)
|
||||
bucketLocation := metabase.BucketLocation{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketname,
|
||||
}
|
||||
key := satellite.Config.Orders.EncryptionKeys.Default
|
||||
|
||||
// stop any async flushes because we want to be sure when some values are
|
||||
@ -231,8 +239,8 @@ func TestSettlementWithWindowEndpointSingleOrder(t *testing.T) {
|
||||
serialNumber := testrand.SerialNumber()
|
||||
encrypted, err := key.EncryptMetadata(
|
||||
serialNumber,
|
||||
&pb.OrderLimitMetadata{
|
||||
ProjectBucketPrefix: []byte(bucketID),
|
||||
&internalpb.OrderLimitMetadata{
|
||||
CompactProjectBucketPrefix: bucketLocation.CompactPrefix(),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@ -333,7 +341,10 @@ func TestSettlementWithWindowEndpointErrors(t *testing.T) {
|
||||
now := time.Now()
|
||||
projectID := testrand.UUID()
|
||||
bucketname := "testbucket"
|
||||
bucketID := storj.JoinPaths(projectID.String(), bucketname)
|
||||
bucketLocation := metabase.BucketLocation{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketname,
|
||||
}
|
||||
|
||||
// stop any async flushes because we want to be sure when some values are
|
||||
// written to avoid races
|
||||
@ -351,11 +362,11 @@ func TestSettlementWithWindowEndpointErrors(t *testing.T) {
|
||||
|
||||
// create serial number to use in test
|
||||
serialNumber1 := testrand.SerialNumber()
|
||||
err = ordersDB.CreateSerialInfo(ctx, serialNumber1, []byte(bucketID), now.AddDate(1, 0, 10))
|
||||
err = ordersDB.CreateSerialInfo(ctx, serialNumber1, []byte(bucketLocation.Prefix()), now.AddDate(1, 0, 10))
|
||||
require.NoError(t, err)
|
||||
|
||||
serialNumber2 := testrand.SerialNumber()
|
||||
err = ordersDB.CreateSerialInfo(ctx, serialNumber2, []byte(bucketID), now.AddDate(1, 0, 10))
|
||||
err = ordersDB.CreateSerialInfo(ctx, serialNumber2, []byte(bucketLocation.Prefix()), now.AddDate(1, 0, 10))
|
||||
require.NoError(t, err)
|
||||
|
||||
piecePublicKey1, piecePrivateKey1, err := storj.NewPieceKey()
|
||||
@ -366,8 +377,8 @@ func TestSettlementWithWindowEndpointErrors(t *testing.T) {
|
||||
key := satellite.Config.Orders.EncryptionKeys.Default
|
||||
encrypted, err := key.EncryptMetadata(
|
||||
serialNumber1,
|
||||
&pb.OrderLimitMetadata{
|
||||
ProjectBucketPrefix: []byte(bucketID),
|
||||
&internalpb.OrderLimitMetadata{
|
||||
CompactProjectBucketPrefix: bucketLocation.CompactPrefix(),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@ -468,8 +479,10 @@ func TestSettlementEndpointSingleOrder(t *testing.T) {
|
||||
now := time.Now()
|
||||
projectID := testrand.UUID()
|
||||
bucketname := "testbucket"
|
||||
bucketID := storj.JoinPaths(projectID.String(), bucketname)
|
||||
|
||||
bucketLocation := metabase.BucketLocation{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketname,
|
||||
}
|
||||
// stop any async flushes because we want to be sure when some values are
|
||||
// written to avoid races
|
||||
satellite.Orders.Chore.Loop.Pause()
|
||||
@ -486,7 +499,7 @@ func TestSettlementEndpointSingleOrder(t *testing.T) {
|
||||
|
||||
// create serial number to use in test
|
||||
serialNumber := testrand.SerialNumber()
|
||||
err = ordersDB.CreateSerialInfo(ctx, serialNumber, []byte(bucketID), now.AddDate(1, 0, 10))
|
||||
err = ordersDB.CreateSerialInfo(ctx, serialNumber, []byte(bucketLocation.Prefix()), now.AddDate(1, 0, 10))
|
||||
require.NoError(t, err)
|
||||
|
||||
piecePublicKey, piecePrivateKey, err := storj.NewPieceKey()
|
||||
@ -494,8 +507,8 @@ func TestSettlementEndpointSingleOrder(t *testing.T) {
|
||||
key := satellite.Config.Orders.EncryptionKeys.Default
|
||||
encrypted, err := key.EncryptMetadata(
|
||||
serialNumber,
|
||||
&pb.OrderLimitMetadata{
|
||||
ProjectBucketPrefix: []byte(bucketID),
|
||||
&internalpb.OrderLimitMetadata{
|
||||
CompactProjectBucketPrefix: bucketLocation.CompactPrefix(),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"storj.io/common/signing"
|
||||
"storj.io/common/storj"
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/satellite/internalpb"
|
||||
"storj.io/storj/satellite/metainfo/metabase"
|
||||
"storj.io/storj/satellite/overlay"
|
||||
"storj.io/uplink/private/eestream"
|
||||
@ -60,8 +61,7 @@ type Service struct {
|
||||
|
||||
encryptionKeys EncryptionKeys
|
||||
|
||||
satelliteAddress *pb.NodeAddress
|
||||
orderExpiration time.Duration
|
||||
orderExpiration time.Duration
|
||||
|
||||
rngMu sync.Mutex
|
||||
rng *mathrand.Rand
|
||||
@ -72,7 +72,6 @@ func NewService(
|
||||
log *zap.Logger, satellite signing.Signer, overlay *overlay.Service,
|
||||
orders DB, buckets BucketsDB,
|
||||
config Config,
|
||||
satelliteAddress *pb.NodeAddress,
|
||||
) (*Service, error) {
|
||||
if config.EncryptionKeys.Default.IsZero() {
|
||||
return nil, Error.New("encryption keys must be specified to include encrypted metadata")
|
||||
@ -87,8 +86,7 @@ func NewService(
|
||||
|
||||
encryptionKeys: config.EncryptionKeys,
|
||||
|
||||
satelliteAddress: satelliteAddress,
|
||||
orderExpiration: config.Expiration,
|
||||
orderExpiration: config.Expiration,
|
||||
|
||||
rng: mathrand.New(mathrand.NewSource(time.Now().UnixNano())),
|
||||
}, nil
|
||||
@ -583,7 +581,7 @@ func (service *Service) UpdatePutInlineOrder(ctx context.Context, bucket metabas
|
||||
}
|
||||
|
||||
// DecryptOrderMetadata decrypts the order metadata.
|
||||
func (service *Service) DecryptOrderMetadata(ctx context.Context, order *pb.OrderLimit) (_ *pb.OrderLimitMetadata, err error) {
|
||||
func (service *Service) DecryptOrderMetadata(ctx context.Context, order *pb.OrderLimit) (_ *internalpb.OrderLimitMetadata, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
var orderKeyID EncryptionKeyID
|
||||
|
@ -54,9 +54,7 @@ func TestOrderLimitsEncryptedMetadata(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
actualOrderMetadata, err := satellitePeer.Orders.Service.DecryptOrderMetadata(ctx, orderLimit1)
|
||||
require.NoError(t, err)
|
||||
actualBucketInfo, err := metabase.ParseBucketPrefix(
|
||||
metabase.BucketPrefix(actualOrderMetadata.GetProjectBucketPrefix()),
|
||||
)
|
||||
actualBucketInfo, err := metabase.ParseCompactBucketPrefix(actualOrderMetadata.GetCompactProjectBucketPrefix())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, bucketName, actualBucketInfo.BucketName)
|
||||
require.Equal(t, projectID, actualBucketInfo.ProjectID)
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"storj.io/common/pb"
|
||||
"storj.io/common/signing"
|
||||
"storj.io/common/storj"
|
||||
"storj.io/storj/satellite/internalpb"
|
||||
"storj.io/storj/satellite/metainfo/metabase"
|
||||
)
|
||||
|
||||
@ -148,8 +149,8 @@ func (signer *Signer) Sign(ctx context.Context, node storj.NodeURL, pieceNum int
|
||||
|
||||
encrypted, err := encryptionKey.EncryptMetadata(
|
||||
signer.Serial,
|
||||
&pb.OrderLimitMetadata{
|
||||
ProjectBucketPrefix: []byte(signer.Bucket.Prefix()),
|
||||
&internalpb.OrderLimitMetadata{
|
||||
CompactProjectBucketPrefix: signer.Bucket.CompactPrefix(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@ -173,7 +174,7 @@ func (signer *Signer) Sign(ctx context.Context, node storj.NodeURL, pieceNum int
|
||||
OrderCreation: signer.OrderCreation,
|
||||
OrderExpiration: signer.OrderExpiration,
|
||||
|
||||
SatelliteAddress: signer.Service.satelliteAddress,
|
||||
SatelliteAddress: nil,
|
||||
|
||||
EncryptedMetadataKeyId: signer.EncryptedMetadataKeyID,
|
||||
EncryptedMetadata: signer.EncryptedMetadata,
|
||||
|
@ -41,7 +41,7 @@ func TestSigner_EncryptedMetadata(t *testing.T) {
|
||||
|
||||
project, err := uplink.GetProject(ctx, satellite)
|
||||
require.NoError(t, err)
|
||||
bucketName := "testbucket"
|
||||
bucketName := "123456789012345678901234567890123456789012345678901234567890123"
|
||||
bucketLocation := metabase.BucketLocation{
|
||||
ProjectID: uplink.Projects[0].ID,
|
||||
BucketName: bucketName,
|
||||
@ -70,7 +70,7 @@ func TestSigner_EncryptedMetadata(t *testing.T) {
|
||||
metadata, err := ekeys.Default.DecryptMetadata(addressedLimit.Limit.SerialNumber, addressedLimit.Limit.EncryptedMetadata)
|
||||
require.NoError(t, err)
|
||||
|
||||
bucketInfo, err := metabase.ParseBucketPrefix(metabase.BucketPrefix(metadata.ProjectBucketPrefix))
|
||||
bucketInfo, err := metabase.ParseCompactBucketPrefix(metadata.CompactProjectBucketPrefix)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, bucketInfo.BucketName, bucketName)
|
||||
require.Equal(t, bucketInfo.ProjectID, uplink.Projects[0].ID)
|
||||
@ -96,11 +96,13 @@ func TestSigner_EncryptedMetadata_UploadDownload(t *testing.T) {
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
satellite, uplink := planet.Satellites[0], planet.Uplinks[0]
|
||||
|
||||
const bucket = "123456789012345678901234567890123456789012345678901234567890123"
|
||||
|
||||
testdata := testrand.Bytes(8 * memory.KiB)
|
||||
err := uplink.Upload(ctx, satellite, "testbucket", "data", testdata)
|
||||
err := uplink.Upload(ctx, satellite, bucket, "data", testdata)
|
||||
require.NoError(t, err)
|
||||
|
||||
downdata, err := uplink.Download(ctx, satellite, "testbucket", "data")
|
||||
downdata, err := uplink.Download(ctx, satellite, bucket, "data")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, testdata, downdata)
|
||||
|
@ -99,10 +99,6 @@ type DB interface {
|
||||
SuspendNodeUnknownAudit(ctx context.Context, nodeID storj.NodeID, suspendedAt time.Time) (err error)
|
||||
// UnsuspendNodeUnknownAudit unsuspends a storage node for unknown audits.
|
||||
UnsuspendNodeUnknownAudit(ctx context.Context, nodeID storj.NodeID) (err error)
|
||||
// SuspendNodeOfflineAudit suspends a storage node for offline audits.
|
||||
SuspendNodeOfflineAudit(ctx context.Context, nodeID storj.NodeID, suspendedAt time.Time) (err error)
|
||||
// UnsuspendNodeOfflineAudit unsuspends a storage node for offline audits.
|
||||
UnsuspendNodeOfflineAudit(ctx context.Context, nodeID storj.NodeID) (err error)
|
||||
|
||||
// TestVetNode directly sets a node's vetted_at timestamp to make testing easier
|
||||
TestVetNode(ctx context.Context, nodeID storj.NodeID) (vettedTime *time.Time, err error)
|
||||
|
@ -463,15 +463,11 @@ func TestKnownReliable(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.False(t, service.IsOnline(node))
|
||||
|
||||
// Suspend storage node #2 for unknown audits
|
||||
// Suspend storage node #2
|
||||
err = satellite.DB.OverlayCache().SuspendNodeUnknownAudit(ctx, planet.StorageNodes[2].ID(), time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Suspend storage node #3 for offline audits
|
||||
err = satellite.DB.OverlayCache().SuspendNodeOfflineAudit(ctx, planet.StorageNodes[3].ID(), time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that only storage nodes #4 is reliable
|
||||
// Check that only storage nodes #3 and #4 are reliable
|
||||
result, err := service.KnownReliable(ctx, []storj.NodeID{
|
||||
planet.StorageNodes[0].ID(),
|
||||
planet.StorageNodes[1].ID(),
|
||||
@ -480,10 +476,11 @@ func TestKnownReliable(t *testing.T) {
|
||||
planet.StorageNodes[4].ID(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, result, 1)
|
||||
require.Len(t, result, 2)
|
||||
|
||||
// Sort the storage nodes for predictable checks
|
||||
expectedReliable := []storj.NodeURL{
|
||||
planet.StorageNodes[3].NodeURL(),
|
||||
planet.StorageNodes[4].NodeURL(),
|
||||
}
|
||||
sort.Slice(expectedReliable, func(i, j int) bool { return expectedReliable[i].ID.Less(expectedReliable[j].ID) })
|
||||
|
@ -377,7 +377,7 @@ func (obs *checkerObserver) RemoteSegment(ctx context.Context, segment *metainfo
|
||||
Path: key,
|
||||
LostPieces: missingPieces,
|
||||
InsertedTime: time.Now().UTC(),
|
||||
}, float64(numHealthy))
|
||||
}, segmentHealth)
|
||||
if err != nil {
|
||||
obs.log.Error("error adding injured segment to queue", zap.Error(err))
|
||||
return nil
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"storj.io/common/identity"
|
||||
"storj.io/common/pb"
|
||||
"storj.io/common/peertls/extensions"
|
||||
"storj.io/common/peertls/tlsopts"
|
||||
"storj.io/common/rpc"
|
||||
@ -162,10 +161,6 @@ func NewRepairer(log *zap.Logger, full *identity.FullIdentity,
|
||||
peer.Orders.DB,
|
||||
bucketsDB,
|
||||
config.Orders,
|
||||
&pb.NodeAddress{
|
||||
Transport: pb.NodeTransport_TCP_TLS_GRPC,
|
||||
Address: config.Contact.ExternalAddress,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
|
@ -181,7 +181,6 @@ func nodeSelectionCondition(ctx context.Context, criteria *overlay.NodeCriteria,
|
||||
var conds conditions
|
||||
conds.add(`disqualified IS NULL`)
|
||||
conds.add(`unknown_audit_suspended IS NULL`)
|
||||
conds.add(`offline_suspended IS NULL`)
|
||||
conds.add(`exit_initiated_at IS NULL`)
|
||||
|
||||
conds.add(`type = ?`, int(pb.NodeType_STORAGE))
|
||||
|
@ -62,7 +62,6 @@ func (cache *overlaycache) selectAllStorageNodesUpload(ctx context.Context, sele
|
||||
FROM nodes ` + asOf + `
|
||||
WHERE disqualified IS NULL
|
||||
AND unknown_audit_suspended IS NULL
|
||||
AND offline_suspended IS NULL
|
||||
AND exit_initiated_at IS NULL
|
||||
AND type = $1
|
||||
AND free_disk >= $2
|
||||
@ -313,7 +312,6 @@ func (cache *overlaycache) knownUnreliableOrOffline(ctx context.Context, criteri
|
||||
WHERE id = any($1::bytea[])
|
||||
AND disqualified IS NULL
|
||||
AND unknown_audit_suspended IS NULL
|
||||
AND offline_suspended IS NULL
|
||||
AND exit_finished_at IS NULL
|
||||
AND last_contact_success > $2
|
||||
`), pgutil.NodeIDArray(nodeIDs), time.Now().Add(-criteria.OnlineWindow),
|
||||
@ -370,7 +368,6 @@ func (cache *overlaycache) knownReliable(ctx context.Context, onlineWindow time.
|
||||
WHERE id = any($1::bytea[])
|
||||
AND disqualified IS NULL
|
||||
AND unknown_audit_suspended IS NULL
|
||||
AND offline_suspended IS NULL
|
||||
AND exit_finished_at IS NULL
|
||||
AND last_contact_success > $2
|
||||
`), pgutil.NodeIDArray(nodeIDs), time.Now().Add(-onlineWindow),
|
||||
@ -419,7 +416,6 @@ func (cache *overlaycache) reliable(ctx context.Context, criteria *overlay.NodeC
|
||||
SELECT id FROM nodes `+asOf+`
|
||||
WHERE disqualified IS NULL
|
||||
AND unknown_audit_suspended IS NULL
|
||||
AND offline_suspended IS NULL
|
||||
AND exit_finished_at IS NULL
|
||||
AND last_contact_success > ?
|
||||
`), time.Now().Add(-criteria.OnlineWindow))
|
||||
@ -749,38 +745,6 @@ func (cache *overlaycache) UnsuspendNodeUnknownAudit(ctx context.Context, nodeID
|
||||
return nil
|
||||
}
|
||||
|
||||
// SuspendNodeOfflineAudit suspends a storage node for offline audits.
|
||||
func (cache *overlaycache) SuspendNodeOfflineAudit(ctx context.Context, nodeID storj.NodeID, suspendedAt time.Time) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
updateFields := dbx.Node_Update_Fields{}
|
||||
updateFields.OfflineSuspended = dbx.Node_OfflineSuspended(suspendedAt.UTC())
|
||||
|
||||
dbNode, err := cache.db.Update_Node_By_Id(ctx, dbx.Node_Id(nodeID.Bytes()), updateFields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dbNode == nil {
|
||||
return errs.New("unable to get node by ID: %v", nodeID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsuspendNodeOfflineAudit unsuspends a storage node for offline audits.
|
||||
func (cache *overlaycache) UnsuspendNodeOfflineAudit(ctx context.Context, nodeID storj.NodeID) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
updateFields := dbx.Node_Update_Fields{}
|
||||
updateFields.OfflineSuspended = dbx.Node_OfflineSuspended_Null()
|
||||
|
||||
dbNode, err := cache.db.Update_Node_By_Id(ctx, dbx.Node_Id(nodeID.Bytes()), updateFields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dbNode == nil {
|
||||
return errs.New("unable to get node by ID: %v", nodeID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllPieceCounts returns a map of node IDs to piece counts from the db.
|
||||
// NB: a valid, partial piece map can be returned even if node ID parsing error(s) are returned.
|
||||
func (cache *overlaycache) AllPieceCounts(ctx context.Context) (_ map[storj.NodeID]int, err error) {
|
||||
|
@ -4,78 +4,35 @@
|
||||
package apikeys
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"time"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/private/multinodeauth"
|
||||
)
|
||||
|
||||
// ErrNoSecret represents errors from the apikey database.
|
||||
var ErrNoSecret = errs.Class("no apikey error")
|
||||
// ErrNoAPIKey represents no api key error.
|
||||
var ErrNoAPIKey = errs.Class("no api key error")
|
||||
|
||||
// DB is interface for working with apikey tokens.
|
||||
// DB is interface for working with api keys.
|
||||
//
|
||||
// architecture: Database
|
||||
type DB interface {
|
||||
// Store stores apikey token into db.
|
||||
Store(ctx context.Context, secret APIKey) error
|
||||
// Store stores api key into db.
|
||||
Store(ctx context.Context, apiKey APIKey) error
|
||||
|
||||
// Check checks if unique apikey exists in db by token.
|
||||
Check(ctx context.Context, token Secret) error
|
||||
// Check checks if api key exists in db by secret.
|
||||
Check(ctx context.Context, secret multinodeauth.Secret) error
|
||||
|
||||
// Revoke removes token from db.
|
||||
Revoke(ctx context.Context, token Secret) error
|
||||
// Revoke removes api key from db.
|
||||
Revoke(ctx context.Context, secret multinodeauth.Secret) error
|
||||
}
|
||||
|
||||
// Secret stores token of storagenode APIkey.
|
||||
type Secret [32]byte
|
||||
|
||||
// APIKey describing apikey model in the database.
|
||||
// APIKey describing api key in the database.
|
||||
type APIKey struct {
|
||||
// Secret is PK of the table and keeps unique value sno apikey token
|
||||
Secret Secret
|
||||
// APIKeys is PK of the table and keeps unique value sno api key.
|
||||
Secret multinodeauth.Secret
|
||||
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// NewSecret creates new apikey secret.
|
||||
func NewSecret() (Secret, error) {
|
||||
var b [32]byte
|
||||
|
||||
_, err := rand.Read(b[:])
|
||||
if err != nil {
|
||||
return b, errs.New("error creating apikey token")
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// String implements Stringer.
|
||||
func (secret Secret) String() string {
|
||||
return base64.URLEncoding.EncodeToString(secret[:])
|
||||
}
|
||||
|
||||
// IsZero returns if the apikey token is not set.
|
||||
func (secret Secret) IsZero() bool {
|
||||
var zero Secret
|
||||
// this doesn't need to be constant-time, because we're explicitly testing
|
||||
// against a hardcoded, well-known value
|
||||
return bytes.Equal(secret[:], zero[:])
|
||||
}
|
||||
|
||||
// TokenSecretFromBase64 creates new apikey token from base64 string.
|
||||
func TokenSecretFromBase64(s string) (Secret, error) {
|
||||
var token Secret
|
||||
|
||||
b, err := base64.URLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
copy(token[:], b)
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
@ -10,42 +10,42 @@ import (
|
||||
"github.com/zeebo/assert"
|
||||
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/storj/private/multinodeauth"
|
||||
"storj.io/storj/storagenode"
|
||||
"storj.io/storj/storagenode/apikeys"
|
||||
"storj.io/storj/storagenode/storagenodedb/storagenodedbtest"
|
||||
)
|
||||
|
||||
func TestSecretDB(t *testing.T) {
|
||||
func TestAPIKeysDB(t *testing.T) {
|
||||
storagenodedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db storagenode.DB) {
|
||||
secrets := db.Secret()
|
||||
token, err := apikeys.NewSecret()
|
||||
apiKeys := db.APIKeys()
|
||||
secret, err := multinodeauth.NewSecret()
|
||||
assert.NoError(t, err)
|
||||
token2, err := apikeys.NewSecret()
|
||||
secret2, err := multinodeauth.NewSecret()
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("Test StoreSecret", func(t *testing.T) {
|
||||
err := secrets.Store(ctx, apikeys.APIKey{
|
||||
Secret: token,
|
||||
t.Run("Store", func(t *testing.T) {
|
||||
err := apiKeys.Store(ctx, apikeys.APIKey{
|
||||
Secret: secret,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Test CheckSecret", func(t *testing.T) {
|
||||
err := secrets.Check(ctx, token)
|
||||
t.Run("Check", func(t *testing.T) {
|
||||
err := apiKeys.Check(ctx, secret)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = secrets.Check(ctx, token2)
|
||||
err = apiKeys.Check(ctx, secret2)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Test RevokeSecret", func(t *testing.T) {
|
||||
err = secrets.Revoke(ctx, token)
|
||||
t.Run("Revoke", func(t *testing.T) {
|
||||
err = apiKeys.Revoke(ctx, secret)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = secrets.Check(ctx, token)
|
||||
err = apiKeys.Check(ctx, secret)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/private/multinodeauth"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -33,7 +35,7 @@ func NewService(db DB) *Service {
|
||||
// Issue generates new api key and stores it into db.
|
||||
func (service *Service) Issue(ctx context.Context) (apiKey APIKey, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
secret, err := NewSecret()
|
||||
secret, err := multinodeauth.NewSecret()
|
||||
if err != nil {
|
||||
return APIKey{}, ErrService.Wrap(err)
|
||||
}
|
||||
@ -50,14 +52,14 @@ func (service *Service) Issue(ctx context.Context) (apiKey APIKey, err error) {
|
||||
}
|
||||
|
||||
// Check returns error if api key does not exists.
|
||||
func (service *Service) Check(ctx context.Context, secret Secret) (err error) {
|
||||
func (service *Service) Check(ctx context.Context, secret multinodeauth.Secret) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
return service.store.Check(ctx, secret)
|
||||
}
|
||||
|
||||
// Remove revokes apikey, deletes it from db.
|
||||
func (service *Service) Remove(ctx context.Context, secret Secret) (err error) {
|
||||
func (service *Service) Remove(ctx context.Context, secret multinodeauth.Secret) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
return ErrService.Wrap(service.store.Revoke(ctx, secret))
|
||||
|
@ -244,6 +244,7 @@ type Satellite struct {
|
||||
Audit reputation.Metric `json:"audit"`
|
||||
Uptime reputation.Metric `json:"uptime"`
|
||||
OnlineScore float64 `json:"onlineScore"`
|
||||
AuditHistory reputation.AuditHistory `json:"auditHistory"`
|
||||
PriceModel PriceModel `json:"priceModel"`
|
||||
NodeJoinedAt time.Time `json:"nodeJoinedAt"`
|
||||
}
|
||||
@ -317,6 +318,7 @@ func (s *Service) GetSatelliteData(ctx context.Context, satelliteID storj.NodeID
|
||||
Audit: rep.Audit,
|
||||
Uptime: rep.Uptime,
|
||||
OnlineScore: rep.OnlineScore,
|
||||
AuditHistory: reputation.GetAuditHistoryFromPB(rep.AuditHistory),
|
||||
PriceModel: satellitePricing,
|
||||
NodeJoinedAt: rep.JoinedAt,
|
||||
}, nil
|
||||
|
@ -27,6 +27,16 @@ var (
|
||||
Error = errs.Class("piecestore monitor")
|
||||
)
|
||||
|
||||
// DiskSpace consolidates monitored disk space statistics.
|
||||
type DiskSpace struct {
|
||||
Allocated int64
|
||||
UsedForPieces int64
|
||||
UsedForTrash int64
|
||||
Free int64
|
||||
Available int64
|
||||
Overused int64
|
||||
}
|
||||
|
||||
// Config defines parameters for storage node disk and bandwidth usage monitoring.
|
||||
type Config struct {
|
||||
Interval time.Duration `help:"how frequently Kademlia bucket should be refreshed with node stats" default:"1h0m0s"`
|
||||
@ -81,9 +91,9 @@ func (service *Service) Run(ctx context.Context) (err error) {
|
||||
}
|
||||
freeDiskSpace := storageStatus.DiskFree
|
||||
|
||||
totalUsed, err := service.usedSpace(ctx)
|
||||
totalUsed, err := service.store.SpaceUsedForPiecesAndTrash(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
// check your hard drive is big enough
|
||||
@ -184,21 +194,13 @@ func (service *Service) updateNodeInformation(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) usedSpace(ctx context.Context) (_ int64, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
usedSpace, err := service.store.SpaceUsedForPiecesAndTrash(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return usedSpace, nil
|
||||
}
|
||||
|
||||
// AvailableSpace returns available disk space for upload.
|
||||
func (service *Service) AvailableSpace(ctx context.Context) (_ int64, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
usedSpace, err := service.usedSpace(ctx)
|
||||
|
||||
usedSpace, err := service.store.SpaceUsedForPiecesAndTrash(ctx)
|
||||
if err != nil {
|
||||
return 0, Error.Wrap(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
freeSpaceForStorj := service.allocatedDiskSpace - usedSpace
|
||||
@ -217,3 +219,41 @@ func (service *Service) AvailableSpace(ctx context.Context) (_ int64, err error)
|
||||
|
||||
return freeSpaceForStorj, nil
|
||||
}
|
||||
|
||||
// DiskSpace returns consolidated disk space state info.
|
||||
func (service *Service) DiskSpace(ctx context.Context) (_ DiskSpace, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
usedForPieces, _, err := service.store.SpaceUsedForPieces(ctx)
|
||||
if err != nil {
|
||||
return DiskSpace{}, Error.Wrap(err)
|
||||
}
|
||||
usedForTrash, err := service.store.SpaceUsedForTrash(ctx)
|
||||
if err != nil {
|
||||
return DiskSpace{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
storageStatus, err := service.store.StorageStatus(ctx)
|
||||
if err != nil {
|
||||
return DiskSpace{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
overused := int64(0)
|
||||
|
||||
available := service.allocatedDiskSpace - (usedForPieces + usedForTrash)
|
||||
if available < 0 {
|
||||
overused = -available
|
||||
}
|
||||
if storageStatus.DiskFree < available {
|
||||
available = storageStatus.DiskFree
|
||||
}
|
||||
|
||||
return DiskSpace{
|
||||
Allocated: service.allocatedDiskSpace,
|
||||
UsedForPieces: usedForPieces,
|
||||
UsedForTrash: usedForTrash,
|
||||
Free: storageStatus.DiskFree,
|
||||
Available: available,
|
||||
Overused: overused,
|
||||
}, nil
|
||||
}
|
||||
|
26
storagenode/multinode/auth.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package multinode
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"storj.io/storj/private/multinodeauth"
|
||||
"storj.io/storj/private/multinodepb"
|
||||
"storj.io/storj/storagenode/apikeys"
|
||||
)
|
||||
|
||||
// authenticate checks if request header contains valid api key.
|
||||
func authenticate(ctx context.Context, apiKeys *apikeys.Service, header *multinodepb.RequestHeader) error {
|
||||
secret, err := multinodeauth.SecretFromBytes(header.GetApiKey())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = apiKeys.Check(ctx, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
54
storagenode/multinode/bandwidth.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package multinode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/common/rpc/rpcstatus"
|
||||
"storj.io/storj/private/multinodepb"
|
||||
"storj.io/storj/storagenode/apikeys"
|
||||
"storj.io/storj/storagenode/bandwidth"
|
||||
)
|
||||
|
||||
var _ multinodepb.DRPCBandwidthServer = (*BandwidthEndpoint)(nil)
|
||||
|
||||
// BandwidthEndpoint implements multinode bandwidth endpoint.
|
||||
//
|
||||
// architecture: Endpoint
|
||||
type BandwidthEndpoint struct {
|
||||
log *zap.Logger
|
||||
apiKeys *apikeys.Service
|
||||
db bandwidth.DB
|
||||
}
|
||||
|
||||
// NewBandwidthEndpoint creates new multinode bandwidth endpoint.
|
||||
func NewBandwidthEndpoint(log *zap.Logger, apiKeys *apikeys.Service, db bandwidth.DB) *BandwidthEndpoint {
|
||||
return &BandwidthEndpoint{
|
||||
log: log,
|
||||
apiKeys: apiKeys,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// MonthSummary returns bandwidth used current month.
|
||||
func (bandwidth *BandwidthEndpoint) MonthSummary(ctx context.Context, req *multinodepb.BandwidthMonthSummaryRequest) (_ *multinodepb.BandwidthMonthSummaryResponse, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
if err = authenticate(ctx, bandwidth.apiKeys, req.GetHeader()); err != nil {
|
||||
return nil, rpcstatus.Wrap(rpcstatus.Unauthenticated, err)
|
||||
}
|
||||
|
||||
used, err := bandwidth.db.MonthSummary(ctx, time.Now())
|
||||
if err != nil {
|
||||
return nil, rpcstatus.Wrap(rpcstatus.Internal, err)
|
||||
}
|
||||
|
||||
return &multinodepb.BandwidthMonthSummaryResponse{
|
||||
Used: used,
|
||||
}, nil
|
||||
}
|
12
storagenode/multinode/common.go
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package multinode
|
||||
|
||||
import (
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
mon = monkit.Package()
|
||||
)
|
120
storagenode/multinode/node.go
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package multinode
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/common/rpc/rpcstatus"
|
||||
"storj.io/private/version"
|
||||
"storj.io/storj/private/multinodepb"
|
||||
"storj.io/storj/storagenode/apikeys"
|
||||
"storj.io/storj/storagenode/contact"
|
||||
"storj.io/storj/storagenode/reputation"
|
||||
"storj.io/storj/storagenode/trust"
|
||||
)
|
||||
|
||||
var _ multinodepb.DRPCNodeServer = (*NodeEndpoint)(nil)
|
||||
|
||||
// NodeEndpoint implements multinode node endpoint.
|
||||
//
|
||||
// architecture: Endpoint
|
||||
type NodeEndpoint struct {
|
||||
log *zap.Logger
|
||||
apiKeys *apikeys.Service
|
||||
version version.Info
|
||||
contact *contact.PingStats
|
||||
reputation reputation.DB
|
||||
trust *trust.Pool
|
||||
}
|
||||
|
||||
// NewNodeEndpoint creates new multinode node endpoint.
|
||||
func NewNodeEndpoint(log *zap.Logger, apiKeys *apikeys.Service, version version.Info, contact *contact.PingStats, reputation reputation.DB, trust *trust.Pool) *NodeEndpoint {
|
||||
return &NodeEndpoint{
|
||||
log: log,
|
||||
apiKeys: apiKeys,
|
||||
version: version,
|
||||
contact: contact,
|
||||
reputation: reputation,
|
||||
trust: trust,
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns node current version.
|
||||
func (node *NodeEndpoint) Version(ctx context.Context, req *multinodepb.VersionRequest) (_ *multinodepb.VersionResponse, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
if err = authenticate(ctx, node.apiKeys, req.GetHeader()); err != nil {
|
||||
return nil, rpcstatus.Wrap(rpcstatus.Unauthenticated, err)
|
||||
}
|
||||
|
||||
return &multinodepb.VersionResponse{
|
||||
Version: node.version.Version.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LastContact returns timestamp when node was last in contact with satellite.
|
||||
func (node *NodeEndpoint) LastContact(ctx context.Context, req *multinodepb.LastContactRequest) (_ *multinodepb.LastContactResponse, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
if err = authenticate(ctx, node.apiKeys, req.GetHeader()); err != nil {
|
||||
return nil, rpcstatus.Wrap(rpcstatus.Unauthenticated, err)
|
||||
}
|
||||
|
||||
return &multinodepb.LastContactResponse{
|
||||
LastContact: node.contact.WhenLastPinged(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Reputation returns reputation for specific satellite.
|
||||
func (node *NodeEndpoint) Reputation(ctx context.Context, req *multinodepb.ReputationRequest) (_ *multinodepb.ReputationResponse, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
if err = authenticate(ctx, node.apiKeys, req.GetHeader()); err != nil {
|
||||
return nil, rpcstatus.Wrap(rpcstatus.Unauthenticated, err)
|
||||
}
|
||||
|
||||
rep, err := node.reputation.Get(ctx, req.SatelliteId)
|
||||
if err != nil {
|
||||
return nil, rpcstatus.Wrap(rpcstatus.Internal, err)
|
||||
}
|
||||
|
||||
return &multinodepb.ReputationResponse{
|
||||
Online: &multinodepb.ReputationResponse_Online{
|
||||
Score: rep.OnlineScore,
|
||||
},
|
||||
Audit: &multinodepb.ReputationResponse_Audit{
|
||||
Score: rep.Audit.Score,
|
||||
SuspensionScore: rep.Audit.UnknownScore,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TrustedSatellites returns list of trusted satellites node urls.
|
||||
func (node *NodeEndpoint) TrustedSatellites(ctx context.Context, req *multinodepb.TrustedSatellitesRequest) (_ *multinodepb.TrustedSatellitesResponse, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
if err = authenticate(ctx, node.apiKeys, req.GetHeader()); err != nil {
|
||||
return nil, rpcstatus.Wrap(rpcstatus.Unauthenticated, err)
|
||||
}
|
||||
|
||||
response := new(multinodepb.TrustedSatellitesResponse)
|
||||
|
||||
satellites := node.trust.GetSatellites(ctx)
|
||||
for _, satellite := range satellites {
|
||||
nodeURL, err := node.trust.GetNodeURL(ctx, satellite)
|
||||
if err != nil {
|
||||
return nil, rpcstatus.Wrap(rpcstatus.Internal, err)
|
||||
}
|
||||
|
||||
response.TrustedSatellites = append(response.TrustedSatellites, &multinodepb.TrustedSatellitesResponse_NodeURL{
|
||||
NodeId: nodeURL.ID,
|
||||
Address: nodeURL.Address,
|
||||
})
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
59
storagenode/multinode/storage.go
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package multinode
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/common/rpc/rpcstatus"
|
||||
"storj.io/storj/private/multinodepb"
|
||||
"storj.io/storj/storagenode/apikeys"
|
||||
"storj.io/storj/storagenode/monitor"
|
||||
)
|
||||
|
||||
var _ multinodepb.DRPCStorageServer = (*StorageEndpoint)(nil)
|
||||
|
||||
// StorageEndpoint implements multinode storage endpoint.
|
||||
//
|
||||
// architecture: Endpoint
|
||||
type StorageEndpoint struct {
|
||||
log *zap.Logger
|
||||
apiKeys *apikeys.Service
|
||||
monitor *monitor.Service
|
||||
}
|
||||
|
||||
// NewStorageEndpoint creates new multinode storage endpoint.
|
||||
func NewStorageEndpoint(log *zap.Logger, apiKeys *apikeys.Service, monitor *monitor.Service) *StorageEndpoint {
|
||||
return &StorageEndpoint{
|
||||
log: log,
|
||||
apiKeys: apiKeys,
|
||||
monitor: monitor,
|
||||
}
|
||||
}
|
||||
|
||||
// DiskSpace returns disk space state.
|
||||
func (storage *StorageEndpoint) DiskSpace(ctx context.Context, req *multinodepb.DiskSpaceRequest) (_ *multinodepb.DiskSpaceResponse, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
if err = authenticate(ctx, storage.apiKeys, req.GetHeader()); err != nil {
|
||||
return nil, rpcstatus.Wrap(rpcstatus.Unauthenticated, err)
|
||||
}
|
||||
|
||||
diskSpace, err := storage.monitor.DiskSpace(ctx)
|
||||
if err != nil {
|
||||
storage.log.Error("disk space internal error", zap.Error(err))
|
||||
return nil, rpcstatus.Wrap(rpcstatus.Internal, err)
|
||||
}
|
||||
|
||||
return &multinodepb.DiskSpaceResponse{
|
||||
Allocated: diskSpace.Allocated,
|
||||
UsedPieces: diskSpace.UsedForPieces,
|
||||
UsedTrash: diskSpace.UsedForTrash,
|
||||
Free: diskSpace.Free,
|
||||
Available: diskSpace.Available,
|
||||
Overused: diskSpace.Overused,
|
||||
}, nil
|
||||
}
|
@ -28,6 +28,7 @@ import (
|
||||
"storj.io/private/version"
|
||||
"storj.io/storj/pkg/server"
|
||||
"storj.io/storj/private/lifecycle"
|
||||
"storj.io/storj/private/multinodepb"
|
||||
"storj.io/storj/private/version/checker"
|
||||
"storj.io/storj/storage"
|
||||
"storj.io/storj/storage/filestore"
|
||||
@ -42,6 +43,7 @@ import (
|
||||
"storj.io/storj/storagenode/inspector"
|
||||
"storj.io/storj/storagenode/internalpb"
|
||||
"storj.io/storj/storagenode/monitor"
|
||||
"storj.io/storj/storagenode/multinode"
|
||||
"storj.io/storj/storagenode/nodestats"
|
||||
"storj.io/storj/storagenode/notifications"
|
||||
"storj.io/storj/storagenode/orders"
|
||||
@ -88,7 +90,7 @@ type DB interface {
|
||||
Notifications() notifications.DB
|
||||
Payout() payout.DB
|
||||
Pricing() pricing.DB
|
||||
Secret() apikeys.DB
|
||||
APIKeys() apikeys.DB
|
||||
|
||||
Preflight(ctx context.Context) error
|
||||
}
|
||||
@ -279,6 +281,12 @@ type Peer struct {
|
||||
Bandwidth *bandwidth.Service
|
||||
|
||||
Reputation *reputation.Service
|
||||
|
||||
Multinode struct {
|
||||
Storage *multinode.StorageEndpoint
|
||||
Bandwidth *multinode.BandwidthEndpoint
|
||||
Node *multinode.NodeEndpoint
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new Storage Node.
|
||||
@ -769,6 +777,39 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten
|
||||
peer.Debug.Server.Panel.Add(
|
||||
debug.Cycle("Bandwidth", peer.Bandwidth.Loop))
|
||||
|
||||
{ // setup multinode endpoints
|
||||
// TODO: add to peer?
|
||||
apiKeys := apikeys.NewService(peer.DB.APIKeys())
|
||||
|
||||
peer.Multinode.Storage = multinode.NewStorageEndpoint(
|
||||
peer.Log.Named("multinode:storage-endpoint"),
|
||||
apiKeys,
|
||||
peer.Storage2.Monitor)
|
||||
|
||||
peer.Multinode.Bandwidth = multinode.NewBandwidthEndpoint(
|
||||
peer.Log.Named("multinode:bandwidth-endpoint"),
|
||||
apiKeys,
|
||||
peer.DB.Bandwidth())
|
||||
|
||||
peer.Multinode.Node = multinode.NewNodeEndpoint(
|
||||
peer.Log.Named("multinode:node-endpoint"),
|
||||
apiKeys,
|
||||
peer.Version.Service.Info,
|
||||
peer.Contact.PingStats,
|
||||
peer.DB.Reputation(),
|
||||
peer.Storage2.Trust)
|
||||
|
||||
if err = multinodepb.DRPCRegisterStorage(peer.Server.DRPC(), peer.Multinode.Storage); err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
if err = multinodepb.DRPCRegisterBandwidth(peer.Server.DRPC(), peer.Multinode.Bandwidth); err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
if err = multinodepb.DRPCRegisterNode(peer.Server.DRPC(), peer.Multinode.Node); err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
}
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
|
@ -53,3 +53,33 @@ type Metric struct {
|
||||
Score float64 `json:"score"`
|
||||
UnknownScore float64 `json:"unknownScore"`
|
||||
}
|
||||
|
||||
// AuditHistory encapsulates storagenode audit history.
|
||||
type AuditHistory struct {
|
||||
Score float64 `json:"score"`
|
||||
Windows []AuditHistoryWindow `json:"windows"`
|
||||
}
|
||||
|
||||
// AuditHistoryWindow encapsulates storagenode audit history window.
|
||||
type AuditHistoryWindow struct {
|
||||
WindowStart time.Time `json:"windowStart"`
|
||||
TotalCount int32 `json:"totalCount"`
|
||||
OnlineCount int32 `json:"onlineCount"`
|
||||
}
|
||||
|
||||
// GetAuditHistoryFromPB creates the AuditHistory json struct from a protobuf.
|
||||
func GetAuditHistoryFromPB(auditHistoryPB *pb.AuditHistory) AuditHistory {
|
||||
ah := AuditHistory{}
|
||||
if auditHistoryPB == nil {
|
||||
return ah
|
||||
}
|
||||
ah.Score = auditHistoryPB.Score
|
||||
for _, window := range auditHistoryPB.Windows {
|
||||
ah.Windows = append(ah.Windows, AuditHistoryWindow{
|
||||
WindowStart: window.WindowStart,
|
||||
TotalCount: window.TotalCount,
|
||||
OnlineCount: window.OnlineCount,
|
||||
})
|
||||
}
|
||||
return ah
|
||||
}
|
||||
|
@ -10,25 +10,26 @@ import (
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/private/multinodeauth"
|
||||
"storj.io/storj/storagenode/apikeys"
|
||||
)
|
||||
|
||||
// ensures that secretDB implements apikeys.DB interface.
|
||||
var _ apikeys.DB = (*secretDB)(nil)
|
||||
// ensures that apiKeysDB implements apikeys.DB interface.
|
||||
var _ apikeys.DB = (*apiKeysDB)(nil)
|
||||
|
||||
// ErrSecret represents errors from the apikey database.
|
||||
var ErrSecret = errs.Class("apikey db error")
|
||||
// ErrAPIKeysDB represents errors from the api keys database.
|
||||
var ErrAPIKeysDB = errs.Class("apikeys db error")
|
||||
|
||||
// SecretDBName represents the database name.
|
||||
const SecretDBName = "secret"
|
||||
// APIKeysDBName represents the database name.
|
||||
const APIKeysDBName = "secret"
|
||||
|
||||
// secretDB works with node apikey DB.
|
||||
type secretDB struct {
|
||||
// apiKeysDB works with node api keys DB.
|
||||
type apiKeysDB struct {
|
||||
dbContainerImpl
|
||||
}
|
||||
|
||||
// Store stores apikey into database.
|
||||
func (db *secretDB) Store(ctx context.Context, secret apikeys.APIKey) (err error) {
|
||||
// Store stores api key into database.
|
||||
func (db *apiKeysDB) Store(ctx context.Context, apiKey apikeys.APIKey) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
query := `INSERT INTO secret (
|
||||
@ -37,15 +38,15 @@ func (db *secretDB) Store(ctx context.Context, secret apikeys.APIKey) (err error
|
||||
) VALUES(?,?)`
|
||||
|
||||
_, err = db.ExecContext(ctx, query,
|
||||
secret.Secret[:],
|
||||
secret.CreatedAt,
|
||||
apiKey.Secret[:],
|
||||
apiKey.CreatedAt,
|
||||
)
|
||||
|
||||
return ErrSecret.Wrap(err)
|
||||
return ErrAPIKeysDB.Wrap(err)
|
||||
}
|
||||
|
||||
// Check checks if apikey exists in db by token.
|
||||
func (db *secretDB) Check(ctx context.Context, token apikeys.Secret) (err error) {
|
||||
// Check checks if api key exists in db by secret.
|
||||
func (db *apiKeysDB) Check(ctx context.Context, secret multinodeauth.Secret) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
var bytes []uint8
|
||||
@ -53,7 +54,7 @@ func (db *secretDB) Check(ctx context.Context, token apikeys.Secret) (err error)
|
||||
|
||||
rowStub := db.QueryRowContext(ctx,
|
||||
`SELECT token, created_at FROM secret WHERE token = ?`,
|
||||
token[:],
|
||||
secret[:],
|
||||
)
|
||||
|
||||
err = rowStub.Scan(
|
||||
@ -62,21 +63,21 @@ func (db *secretDB) Check(ctx context.Context, token apikeys.Secret) (err error)
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return apikeys.ErrNoSecret.Wrap(err)
|
||||
return apikeys.ErrNoAPIKey.Wrap(err)
|
||||
}
|
||||
return ErrSecret.Wrap(err)
|
||||
return ErrAPIKeysDB.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Revoke removes apikey from db.
|
||||
func (db *secretDB) Revoke(ctx context.Context, secret apikeys.Secret) (err error) {
|
||||
// Revoke removes api key from db.
|
||||
func (db *apiKeysDB) Revoke(ctx context.Context, secret multinodeauth.Secret) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
query := `DELETE FROM secret WHERE token = ?`
|
||||
|
||||
_, err = db.ExecContext(ctx, query, secret[:])
|
||||
|
||||
return ErrSecret.Wrap(err)
|
||||
return ErrAPIKeysDB.Wrap(err)
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ type DB struct {
|
||||
notificationsDB *notificationDB
|
||||
payoutDB *payoutDB
|
||||
pricingDB *pricingDB
|
||||
secretDB *secretDB
|
||||
apiKeysDB *apiKeysDB
|
||||
|
||||
SQLDBs map[string]DBContainer
|
||||
}
|
||||
@ -134,7 +134,7 @@ func OpenNew(ctx context.Context, log *zap.Logger, config Config) (*DB, error) {
|
||||
notificationsDB := ¬ificationDB{}
|
||||
payoutDB := &payoutDB{}
|
||||
pricingDB := &pricingDB{}
|
||||
secretDB := &secretDB{}
|
||||
apiKeysDB := &apiKeysDB{}
|
||||
|
||||
db := &DB{
|
||||
log: log,
|
||||
@ -157,7 +157,7 @@ func OpenNew(ctx context.Context, log *zap.Logger, config Config) (*DB, error) {
|
||||
notificationsDB: notificationsDB,
|
||||
payoutDB: payoutDB,
|
||||
pricingDB: pricingDB,
|
||||
secretDB: secretDB,
|
||||
apiKeysDB: apiKeysDB,
|
||||
|
||||
SQLDBs: map[string]DBContainer{
|
||||
DeprecatedInfoDBName: deprecatedInfoDB,
|
||||
@ -173,7 +173,7 @@ func OpenNew(ctx context.Context, log *zap.Logger, config Config) (*DB, error) {
|
||||
NotificationsDBName: notificationsDB,
|
||||
HeldAmountDBName: payoutDB,
|
||||
PricingDBName: pricingDB,
|
||||
SecretDBName: secretDB,
|
||||
APIKeysDBName: apiKeysDB,
|
||||
},
|
||||
}
|
||||
|
||||
@ -202,7 +202,7 @@ func OpenExisting(ctx context.Context, log *zap.Logger, config Config) (*DB, err
|
||||
notificationsDB := ¬ificationDB{}
|
||||
payoutDB := &payoutDB{}
|
||||
pricingDB := &pricingDB{}
|
||||
secretDB := &secretDB{}
|
||||
apiKeysDB := &apiKeysDB{}
|
||||
|
||||
db := &DB{
|
||||
log: log,
|
||||
@ -225,7 +225,7 @@ func OpenExisting(ctx context.Context, log *zap.Logger, config Config) (*DB, err
|
||||
notificationsDB: notificationsDB,
|
||||
payoutDB: payoutDB,
|
||||
pricingDB: pricingDB,
|
||||
secretDB: secretDB,
|
||||
apiKeysDB: apiKeysDB,
|
||||
|
||||
SQLDBs: map[string]DBContainer{
|
||||
DeprecatedInfoDBName: deprecatedInfoDB,
|
||||
@ -241,7 +241,7 @@ func OpenExisting(ctx context.Context, log *zap.Logger, config Config) (*DB, err
|
||||
NotificationsDBName: notificationsDB,
|
||||
HeldAmountDBName: payoutDB,
|
||||
PricingDBName: pricingDB,
|
||||
SecretDBName: secretDB,
|
||||
APIKeysDBName: apiKeysDB,
|
||||
},
|
||||
}
|
||||
|
||||
@ -274,7 +274,7 @@ func (db *DB) openDatabases(ctx context.Context) error {
|
||||
NotificationsDBName,
|
||||
HeldAmountDBName,
|
||||
PricingDBName,
|
||||
SecretDBName,
|
||||
APIKeysDBName,
|
||||
}
|
||||
|
||||
for _, dbName := range dbs {
|
||||
@ -543,9 +543,9 @@ func (db *DB) Pricing() pricing.DB {
|
||||
return db.pricingDB
|
||||
}
|
||||
|
||||
// Secret returns instance of the Secret database.
|
||||
func (db *DB) Secret() apikeys.DB {
|
||||
return db.secretDB
|
||||
// APIKeys returns instance of the APIKeys database.
|
||||
func (db *DB) APIKeys() apikeys.DB {
|
||||
return db.apiKeysDB
|
||||
}
|
||||
|
||||
// RawDatabases are required for testing purposes.
|
||||
@ -1812,11 +1812,11 @@ func (db *DB) Migration(ctx context.Context) *migrate.Migration {
|
||||
}),
|
||||
},
|
||||
{
|
||||
DB: &db.secretDB.DB,
|
||||
DB: &db.apiKeysDB.DB,
|
||||
Description: "Create secret table",
|
||||
Version: 46,
|
||||
CreateDB: func(ctx context.Context, log *zap.Logger) error {
|
||||
if err := db.openDatabase(ctx, SecretDBName); err != nil {
|
||||
if err := db.openDatabase(ctx, APIKeysDBName); err != nil {
|
||||
return ErrDatabase.Wrap(err)
|
||||
}
|
||||
|
||||
|
2
storagenode/storagenodedb/testdata/v46.go
vendored
@ -21,7 +21,7 @@ var v46 = MultiDBState{
|
||||
storagenodedb.NotificationsDBName: v43.DBStates[storagenodedb.NotificationsDBName],
|
||||
storagenodedb.HeldAmountDBName: v43.DBStates[storagenodedb.HeldAmountDBName],
|
||||
storagenodedb.PricingDBName: v43.DBStates[storagenodedb.PricingDBName],
|
||||
storagenodedb.SecretDBName: &DBState{
|
||||
storagenodedb.APIKeysDBName: &DBState{
|
||||
SQL: `
|
||||
-- table to hold storagenode secret token
|
||||
CREATE TABLE secret (
|
||||
|
2
storagenode/storagenodedb/testdata/v47.go
vendored
@ -51,6 +51,6 @@ var v47 = MultiDBState{
|
||||
storagenodedb.NotificationsDBName: v46.DBStates[storagenodedb.NotificationsDBName],
|
||||
storagenodedb.HeldAmountDBName: v46.DBStates[storagenodedb.HeldAmountDBName],
|
||||
storagenodedb.PricingDBName: v46.DBStates[storagenodedb.PricingDBName],
|
||||
storagenodedb.SecretDBName: v46.DBStates[storagenodedb.SecretDBName],
|
||||
storagenodedb.APIKeysDBName: v46.DBStates[storagenodedb.APIKeysDBName],
|
||||
},
|
||||
}
|
||||
|
814
web/multinode/package-lock.json
generated
@ -23,6 +23,8 @@
|
||||
"@vue/cli-service": "4.5.9",
|
||||
"babel-core": "6.26.3",
|
||||
"core-js": "3.8.1",
|
||||
"node-sass": "4.14.1",
|
||||
"sass-loader": "8.0.0",
|
||||
"stylelint": "13.8.0",
|
||||
"stylelint-config-standard": "20.0.0",
|
||||
"stylelint-scss": "3.18.0",
|
||||
@ -31,6 +33,7 @@
|
||||
"tslint-consistent-codestyle": "1.16.0",
|
||||
"tslint-loader": "3.5.4",
|
||||
"typescript": "3.7.4",
|
||||
"vue-svg-loader": "0.16.0",
|
||||
"vue-template-compiler": "2.6.11",
|
||||
"vue-tslint": "0.3.2",
|
||||
"vue-tslint-loader": "3.5.6",
|
||||
@ -61,7 +64,7 @@
|
||||
"rule-empty-line-before": "always-multi-line",
|
||||
"selector-pseudo-element-colon-notation": "single",
|
||||
"selector-pseudo-class-parentheses-space-inside": "never",
|
||||
"selector-max-type": 1,
|
||||
"selector-max-type": 3,
|
||||
"font-family-no-missing-generic-family-keyword": true,
|
||||
"at-rule-no-unknown": null,
|
||||
"scss/at-rule-no-unknown": true,
|
||||
|
@ -2,9 +2,9 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@ -14,3 +14,50 @@ import { Component, Vue } from 'vue-property-decorator';
|
||||
export default class App extends Vue {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'static/styles/variables';
|
||||
|
||||
body {
|
||||
margin: 0 !important;
|
||||
position: relative;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
p,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'font_regular';
|
||||
src: url('../../static/fonts/font_regular.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'font_medium';
|
||||
src: url('../../static/fonts/font_medium.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'font_semiBold';
|
||||
src: url('../../static/fonts/font_semiBold.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'font_bold';
|
||||
src: url('../../static/fonts/font_bold.ttf');
|
||||
}
|
||||
</style>
|
||||
|
172
web/multinode/src/app/components/common/HeaderedInput.vue
Normal file
@ -0,0 +1,172 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="input-container">
|
||||
<div v-if="!isOptional" class="label-container">
|
||||
<div class="label-container__main">
|
||||
<div v-if="error">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="20" height="20" rx="10" fill="#EB5757"/>
|
||||
<path d="M10.0012 11.7364C10.612 11.7364 11.1117 11.204 11.1117 10.5532V5.81218C11.1117 5.75302 11.108 5.68991 11.1006 5.63074C11.0192 5.06672 10.5565 4.62891 10.0012 4.62891C9.39037 4.62891 8.89062 5.16138 8.89062 5.81218V10.5492C8.89062 11.204 9.39037 11.7364 10.0012 11.7364Z" fill="white"/>
|
||||
<path d="M10.0001 12.8906C9.13977 12.8906 8.44531 13.5851 8.44531 14.4454C8.44531 15.3057 9.13977 16.0002 10.0001 16.0002C10.8604 16.0002 11.5548 15.3057 11.5548 14.4454C11.5583 13.5851 10.8638 12.8906 10.0001 12.8906Z" fill="white"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 v-if="!error" class="label-container__main__label">{{label}}</h3>
|
||||
<h3 v-if="!error" class="label-container__main__label add-label">{{additionalLabel}}</h3>
|
||||
<h3 class="label-container__main__error" v-if="error">{{error}}</h3>
|
||||
</div>
|
||||
<h3 v-if="isLimitShown" class="label-container__limit">{{currentLimit}}/{{maxSymbols}}</h3>
|
||||
</div>
|
||||
<div v-if="isOptional" class="optional-label-container">
|
||||
<h3 class="label-container__label">{{label}}</h3>
|
||||
<h4 class="optional-label-container__optional">Optional</h4>
|
||||
</div>
|
||||
<textarea
|
||||
class="headered-textarea"
|
||||
v-if="isMultiline"
|
||||
:id="this.label"
|
||||
:placeholder="this.placeholder"
|
||||
:style="style.inputStyle"
|
||||
:rows="5"
|
||||
:cols="40"
|
||||
wrap="hard"
|
||||
@input="onInput"
|
||||
@change="onInput"
|
||||
v-model="value">
|
||||
</textarea>
|
||||
<input
|
||||
class="headered-input"
|
||||
v-if="!isMultiline"
|
||||
:id="this.label"
|
||||
:placeholder="this.placeholder"
|
||||
:type="[isPassword ? 'password': 'text']"
|
||||
@input="onInput"
|
||||
@change="onInput"
|
||||
v-model="value"
|
||||
:style="style.inputStyle"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop } from 'vue-property-decorator';
|
||||
|
||||
import HeaderlessInput from './HeaderlessInput.vue';
|
||||
|
||||
// Custom input component with labeled header.
|
||||
@Component
|
||||
export default class HeaderedInput extends HeaderlessInput {
|
||||
@Prop({default: ''})
|
||||
private readonly initValue: string;
|
||||
@Prop({default: ''})
|
||||
private readonly additionalLabel: string;
|
||||
@Prop({default: 0})
|
||||
private readonly currentLimit: number;
|
||||
@Prop({default: false})
|
||||
private readonly isOptional: boolean;
|
||||
@Prop({default: false})
|
||||
private readonly isLimitShown: boolean;
|
||||
@Prop({default: false})
|
||||
private readonly isMultiline: boolean;
|
||||
|
||||
public value: string;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
this.value = this.initValue;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-top: 10px;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
}
|
||||
|
||||
.label-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&__main {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
&__label {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: var(--c-gray);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__error {
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: var(--c-error);
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__limit {
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: rgba(56, 75, 101, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.optional-label-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
&__optional {
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: #afb7c1;
|
||||
}
|
||||
}
|
||||
|
||||
.headered-input,
|
||||
.headered-textarea {
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
resize: none;
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
text-indent: 20px;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
border: 1px solid var(--c-gray--light);
|
||||
border-radius: var(--br-input);
|
||||
color: #354049;
|
||||
caret-color: var(--c-primary);
|
||||
box-sizing: border-box;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--c-placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
.headered-textarea {
|
||||
padding: 15px 22px;
|
||||
text-indent: 0;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.add-label {
|
||||
margin-left: 5px;
|
||||
color: rgba(56, 75, 101, 0.4);
|
||||
}
|
||||
</style>
|
267
web/multinode/src/app/components/common/HeaderlessInput.vue
Normal file
@ -0,0 +1,267 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="input-wrap">
|
||||
<div class="label-container">
|
||||
<div class="icon" v-if="error">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="20" height="20" rx="10" fill="#EB5757"/>
|
||||
<path d="M10.0012 11.7364C10.612 11.7364 11.1117 11.204 11.1117 10.5532V5.81218C11.1117 5.75302 11.108 5.68991 11.1006 5.63074C11.0192 5.06672 10.5565 4.62891 10.0012 4.62891C9.39037 4.62891 8.89062 5.16138 8.89062 5.81218V10.5492C8.89062 11.204 9.39037 11.7364 10.0012 11.7364Z" fill="white"/>
|
||||
<path d="M10.0001 12.8906C9.13977 12.8906 8.44531 13.5851 8.44531 14.4454C8.44531 15.3057 9.13977 16.0002 10.0001 16.0002C10.8604 16.0002 11.5548 15.3057 11.5548 14.4454C11.5583 13.5851 10.8638 12.8906 10.0001 12.8906Z" fill="white"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="label-container__label" v-if="isLabelShown" :style="style.labelStyle">{{label}}</p>
|
||||
<p class="label-container__error" v-if="error" :style="style.errorStyle">{{error}}</p>
|
||||
</div>
|
||||
<div
|
||||
class="headerless-input-container"
|
||||
:style="style.inputStyle"
|
||||
>
|
||||
<input
|
||||
class="headerless-input"
|
||||
:class="{'inputError' : error, 'password': isPassword}"
|
||||
@input="onInput"
|
||||
@change="onInput"
|
||||
v-model="value"
|
||||
:placeholder="placeholder"
|
||||
:type="type"
|
||||
@focus="showPasswordStrength"
|
||||
@blur="hidePasswordStrength"
|
||||
/>
|
||||
<!--2 conditions of eye image (crossed or not) -->
|
||||
<div
|
||||
class="input-wrap__image icon"
|
||||
v-if="isPasswordHiddenState"
|
||||
@click="changeVision"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="input-wrap__image__path" d="M10 4C4.70642 4 1 10 1 10C1 10 3.6999 16 10 16C16.3527 16 19 10 19 10C19 10 15.3472 4 10 4ZM10 13.8176C7.93537 13.8176 6.2946 12.1271 6.2946 10C6.2946 7.87285 7.93537 6.18239 10 6.18239C12.0646 6.18239 13.7054 7.87285 13.7054 10C13.7054 12.1271 12.0646 13.8176 10 13.8176Z" fill="#AFB7C1"/>
|
||||
<path d="M11.6116 9.96328C11.6116 10.8473 10.8956 11.5633 10.0116 11.5633C9.12763 11.5633 8.41162 10.8473 8.41162 9.96328C8.41162 9.07929 9.12763 8.36328 10.0116 8.36328C10.8956 8.36328 11.6116 9.07929 11.6116 9.96328Z" fill="#AFB7C1"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="input-wrap__image icon"
|
||||
v-if="isPasswordShownState"
|
||||
@click="changeVision"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="input-wrap__image__path" d="M10 4C4.70642 4 1 10 1 10C1 10 3.6999 16 10 16C16.3527 16 19 10 19 10C19 10 15.3472 4 10 4ZM10 13.8176C7.93537 13.8176 6.2946 12.1271 6.2946 10C6.2946 7.87285 7.93537 6.18239 10 6.18239C12.0646 6.18239 13.7054 7.87285 13.7054 10C13.7054 12.1271 12.0646 13.8176 10 13.8176Z" fill="#AFB7C1"/>
|
||||
<path d="M11.6121 9.96328C11.6121 10.8473 10.8961 11.5633 10.0121 11.5633C9.12812 11.5633 8.41211 10.8473 8.41211 9.96328C8.41211 9.07929 9.12812 8.36328 10.0121 8.36328C10.8961 8.36328 11.6121 9.07929 11.6121 9.96328Z" fill="#AFB7C1"/>
|
||||
<mask id="path-3-inside-1" fill="white">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 16.5L16 1L16.8155 1.57875L5.81551 17.0787L5 16.5Z"/>
|
||||
</mask>
|
||||
<path class="input-wrap__image__path" fill-rule="evenodd" clip-rule="evenodd" d="M5 16.5L16 1L16.8155 1.57875L5.81551 17.0787L5 16.5Z" fill="white"/>
|
||||
<path class="input-wrap__image__path" d="M16 1L16.5787 0.184493L15.7632 -0.394254L15.1845 0.421253L16 1ZM5 16.5L4.18449 15.9213L3.60575 16.7368L4.42125 17.3155L5 16.5ZM16.8155 1.57875L17.631 2.15749L18.2098 1.34199L17.3943 0.76324L16.8155 1.57875ZM5.81551 17.0787L5.23676 17.8943L6.05227 18.473L6.63101 17.6575L5.81551 17.0787ZM15.1845 0.421253L4.18449 15.9213L5.81551 17.0787L16.8155 1.57875L15.1845 0.421253ZM17.3943 0.76324L16.5787 0.184493L15.4213 1.81551L16.2368 2.39425L17.3943 0.76324ZM6.63101 17.6575L17.631 2.15749L16 1L5 16.5L6.63101 17.6575ZM4.42125 17.3155L5.23676 17.8943L6.39425 16.2632L5.57875 15.6845L4.42125 17.3155Z" fill="white" mask="url(#path-3-inside-1)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 17.5L16 2L16.8155 2.57875L5.81551 18.0787L5 17.5Z" fill="#AFB7C1"/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- end of image-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
/**
|
||||
* Custom input component for login page.
|
||||
*/
|
||||
@Component
|
||||
export default class HeaderlessInput extends Vue {
|
||||
private readonly textType: string = 'text';
|
||||
private readonly passwordType: string = 'password';
|
||||
|
||||
private type: string = this.textType;
|
||||
private isPasswordShown: boolean = false;
|
||||
|
||||
protected value: string = '';
|
||||
|
||||
@Prop({default: ''})
|
||||
protected readonly label: string;
|
||||
@Prop({default: 'default'})
|
||||
protected readonly placeholder: string;
|
||||
@Prop({default: false})
|
||||
protected readonly isPassword: boolean;
|
||||
@Prop({default: '48px'})
|
||||
protected readonly height: string;
|
||||
@Prop({default: '100%'})
|
||||
protected readonly width: string;
|
||||
@Prop({default: ''})
|
||||
protected readonly error: string;
|
||||
@Prop({default: Number.MAX_SAFE_INTEGER})
|
||||
protected readonly maxSymbols: number;
|
||||
|
||||
@Prop({default: false})
|
||||
private readonly isWhite: boolean;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
this.type = this.isPassword ? this.passwordType : this.textType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to set default value from parent component.
|
||||
* @param value
|
||||
*/
|
||||
public setValue(value: string): void {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public showPasswordStrength(): void {
|
||||
this.$emit('showPasswordStrength');
|
||||
}
|
||||
|
||||
public hidePasswordStrength(): void {
|
||||
this.$emit('hidePasswordStrength');
|
||||
}
|
||||
|
||||
/**
|
||||
* triggers on input.
|
||||
*/
|
||||
// @ts-ignore
|
||||
public onInput({ target }): void {
|
||||
if (!target || target.value) return;
|
||||
|
||||
if (target.value.length > this.maxSymbols) {
|
||||
this.value = target.value.slice(0, this.maxSymbols);
|
||||
} else {
|
||||
this.value = target.value;
|
||||
}
|
||||
|
||||
this.$emit('setData', this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers input type between text and password to show/hide symbols.
|
||||
*/
|
||||
public changeVision(): void {
|
||||
this.isPasswordShown = !this.isPasswordShown;
|
||||
this.type = this.isPasswordShown ? this.textType : this.passwordType;
|
||||
}
|
||||
|
||||
public get isLabelShown(): boolean {
|
||||
return !!(!this.error && this.label);
|
||||
}
|
||||
|
||||
public get isPasswordHiddenState(): boolean {
|
||||
return this.isPassword && !this.isPasswordShown;
|
||||
}
|
||||
|
||||
public get isPasswordShownState(): boolean {
|
||||
return this.isPassword && this.isPasswordShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns style objects depends on props.
|
||||
*/
|
||||
protected get style(): object {
|
||||
return {
|
||||
inputStyle: {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
},
|
||||
labelStyle: {
|
||||
color: this.isWhite ? 'white' : '#354049',
|
||||
},
|
||||
errorStyle: {
|
||||
color: this.isWhite ? 'white' : '#FF5560',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.input-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
|
||||
&__image {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 20;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover .input-wrap__image__path {
|
||||
fill: var(--c-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 8px;
|
||||
flex-direction: row;
|
||||
height: auto;
|
||||
|
||||
&__label {
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: var(--c-label);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__add-label {
|
||||
margin-left: 5px;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: rgba(56, 75, 101, 0.4);
|
||||
}
|
||||
|
||||
&__error {
|
||||
font-size: 16px;
|
||||
margin: 18px 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.headerless-input-container {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: 46px;
|
||||
}
|
||||
|
||||
.headerless-input {
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
resize: none;
|
||||
padding: 0 30px 0 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-indent: 20px;
|
||||
border: 1px solid rgba(56, 75, 101, 0.4);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.headerless-input::placeholder {
|
||||
color: #384b65;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.inputError::placeholder {
|
||||
color: #eb5757;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #ff5560;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.password {
|
||||
width: calc(100% - 75px) !important;
|
||||
padding-right: 75px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
</style>
|
146
web/multinode/src/app/components/common/VButton.vue
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<!-- if isDisabled check onPress in parent element -->
|
||||
<div
|
||||
class="container"
|
||||
:class="containerClassName"
|
||||
:style="style"
|
||||
@click="onPress"
|
||||
>
|
||||
<svg v-if="withPlus" class="plus" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M10 4.1665V15.8332" stroke="white" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.16797 10H15.8346" stroke="white" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<span class="label">{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
/**
|
||||
* Custom button component with label.
|
||||
*/
|
||||
@Component
|
||||
export default class VButton extends Vue {
|
||||
@Prop({default: 'Default'})
|
||||
private readonly label: string;
|
||||
@Prop({default: 'inherit'})
|
||||
private readonly width: string;
|
||||
@Prop({default: '48px'})
|
||||
private readonly height: string;
|
||||
@Prop({default: false})
|
||||
private readonly isWhite: boolean;
|
||||
@Prop({default: false})
|
||||
private readonly isTransparent: boolean;
|
||||
@Prop({default: false})
|
||||
private readonly isDeletion: boolean;
|
||||
@Prop({default: false})
|
||||
private readonly isBlueWhite: boolean;
|
||||
@Prop({default: false})
|
||||
private isDisabled: boolean;
|
||||
@Prop({default: false})
|
||||
private withPlus: boolean;
|
||||
@Prop({default: false})
|
||||
private inactive: boolean;
|
||||
@Prop({default: () => { return; }})
|
||||
private readonly onPress: Function;
|
||||
|
||||
public get style(): Object {
|
||||
return { width: this.width, height: this.height };
|
||||
}
|
||||
|
||||
public get containerClassName(): string {
|
||||
let className: string = `${this.inactive ? 'inactive' : ''}`;
|
||||
|
||||
switch (true) {
|
||||
case this.isDisabled:
|
||||
className = 'disabled';
|
||||
break;
|
||||
case this.isWhite:
|
||||
className = 'white';
|
||||
break;
|
||||
case this.isTransparent:
|
||||
className = 'transparent';
|
||||
break;
|
||||
case this.isDeletion:
|
||||
className = 'red';
|
||||
}
|
||||
|
||||
return className;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--c-primary);
|
||||
border-radius: var(--br-button);
|
||||
cursor: pointer;
|
||||
|
||||
.label {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
font-size: 16px;
|
||||
color: var(--c-button-label);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #004199;
|
||||
|
||||
&.white {
|
||||
box-shadow: none !important;
|
||||
background-color: var(--c-button-white-hover) !important;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&.red {
|
||||
box-shadow: none !important;
|
||||
background-color: var(--c-button-red-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.red {
|
||||
background-color: var(--c-button-red);
|
||||
}
|
||||
|
||||
.plus {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.white {
|
||||
background-color: var(--c-button-white);
|
||||
border: var(--b-button-white);
|
||||
|
||||
.label {
|
||||
color: var(--c-button-white--label);
|
||||
}
|
||||
|
||||
.plus {
|
||||
|
||||
path {
|
||||
stroke: var(--c-title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
background-color: var(--c-button-disabled);
|
||||
pointer-events: none !important;
|
||||
|
||||
.label {
|
||||
color: #acb0bc !important;
|
||||
}
|
||||
}
|
||||
|
||||
.inactive {
|
||||
opacity: 0.5;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
</style>
|
142
web/multinode/src/app/components/common/VDropdown.vue
Normal file
@ -0,0 +1,142 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="dropdown"
|
||||
@click.self="toggleOptions"
|
||||
:class="{ active: areOptionsShown }"
|
||||
>
|
||||
<span class="label">{{ selectedOption.label }}</span>
|
||||
<div class="dropdown__selection" v-show="areOptionsShown">
|
||||
<div class="dropdown__selection__overflow-container">
|
||||
<div v-for="option in allOptions" :key="option.label" class="dropdown__selection__option" @click="onOptionClick(option)">
|
||||
<span class="dropdown__selection__option__label">{{ option.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
|
||||
export class Option {
|
||||
public constructor(
|
||||
public label: string = '',
|
||||
public onClick: Function = () => { return; },
|
||||
) {}
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class VDropdown extends Vue {
|
||||
@Prop({default: 'All'})
|
||||
private readonly allLabel: string;
|
||||
@Prop({default: () => { return; }})
|
||||
private readonly onAllClick: Function;
|
||||
@Prop({default: []})
|
||||
private readonly options: Option[];
|
||||
|
||||
public areOptionsShown: boolean = false;
|
||||
|
||||
@Watch('options')
|
||||
public allOptions: Option[] = [ new Option(this.allLabel, this.onAllClick), ...this.options ];
|
||||
|
||||
@Watch('options')
|
||||
public selectedOption: Option = this.allOptions[0];
|
||||
|
||||
public toggleOptions(): void {
|
||||
this.areOptionsShown = !this.areOptionsShown;
|
||||
}
|
||||
|
||||
public closeOptions(): void {
|
||||
this.areOptionsShown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires on option click.
|
||||
* Calls callback and changes selection.
|
||||
* @param option
|
||||
*/
|
||||
public async onOptionClick(option: Option): Promise<void> {
|
||||
await option.onClick();
|
||||
this.selectedOption = option;
|
||||
this.closeOptions();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.dropdown {
|
||||
box-sizing: border-box;
|
||||
width: 300px;
|
||||
height: 40px;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
border: 1px solid var(--c-gray--light);
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
color: var(--c-title);
|
||||
cursor: pointer;
|
||||
font-family: 'font_medium', sans-serif;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--c-gray);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--c-primary);
|
||||
}
|
||||
|
||||
&__selection {
|
||||
position: absolute;
|
||||
top: 52px;
|
||||
left: 0;
|
||||
width: 300px;
|
||||
border: 1px solid var(--c-gray--light);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
&__overflow-container {
|
||||
overflow: overlay;
|
||||
overflow-x: hidden;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
&__option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 0 16px;
|
||||
height: 40px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 100% !important;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--c-gray--light);
|
||||
|
||||
&:hover {
|
||||
background: var(--c-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
box-shadow: inset 0 0 5px transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--c-gray--light);
|
||||
border-radius: 6px;
|
||||
height: 5px;
|
||||
}
|
||||
</style>
|
92
web/multinode/src/app/components/common/VModal.vue
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="modal-wrap"
|
||||
@click.self="$emit('close')"
|
||||
>
|
||||
<div class="modal">
|
||||
<div class="modal__header">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div class="modal__body">
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
<div class="modal__footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
<div class="modal__cross" @click="$emit('close')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
|
||||
<path d="M24 8L8 24" stroke="#676F84" stroke-width="2.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 8L24 24" stroke="#676F84" stroke-width="2.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
@Component
|
||||
export default class VModal extends Vue {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal-wrap {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(37, 42, 50, 0.7);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
|
||||
.modal {
|
||||
position: relative;
|
||||
background: white;
|
||||
padding: 80px 97px;
|
||||
height: auto;
|
||||
z-index: 1001;
|
||||
border-radius: 16px;
|
||||
|
||||
&__header {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 32px;
|
||||
color: var(--c-title);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__body {
|
||||
margin-top: 46px;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
&__cross {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
right: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
max-width: 32px;
|
||||
height: 32px;
|
||||
max-height: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
82
web/multinode/src/app/components/modals/AddNewNode.vue
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="add-new-node">
|
||||
<v-button :with-plus="true" label="New Node" :on-press="openModal" width="152px" />
|
||||
<v-modal v-if="isAddNewNodeModalShown" @close="closeModal">
|
||||
<h2 slot="header">Add New Node</h2>
|
||||
<div class="add-new-node__body" slot="body">
|
||||
<headered-input
|
||||
class="add-new-node__body__input"
|
||||
label="Public IP Address"
|
||||
/>
|
||||
<headered-input
|
||||
class="add-new-node__body__input"
|
||||
label="API Key"
|
||||
/>
|
||||
<headered-input
|
||||
class="add-new-node__body__input"
|
||||
label="Displayed Name"
|
||||
/>
|
||||
</div>
|
||||
<div class="add-new-node__footer" slot="footer">
|
||||
<v-button label="Cancel" :is-white="true" width="205px" :on-press="closeModal" />
|
||||
<v-button label="Continue" width="205px" />
|
||||
</div>
|
||||
</v-modal>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import HeaderedInput from '@/app/components/common/HeaderedInput.vue';
|
||||
import VButton from '@/app/components/common/VButton.vue';
|
||||
import VModal from '@/app/components/common/VModal.vue';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
VButton,
|
||||
HeaderedInput,
|
||||
VModal,
|
||||
},
|
||||
})
|
||||
export default class AddNewNode extends Vue {
|
||||
public isAddNewNodeModalShown: boolean = false;
|
||||
|
||||
public openModal(): void {
|
||||
this.isAddNewNodeModalShown = true;
|
||||
}
|
||||
|
||||
public closeModal(): void {
|
||||
this.isAddNewNodeModalShown = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.add-new-node {
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
width: 441px;
|
||||
|
||||
&__input:not(:first-of-type) {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
width: 441px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
</style>
|
125
web/multinode/src/app/components/navigation/NavigationArea.vue
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="navigation-area">
|
||||
<storj-logo class="navigation-area__logo" />
|
||||
<router-link
|
||||
:aria-label="navItem.name"
|
||||
class="navigation-area__item-container"
|
||||
v-for="navItem in navigation"
|
||||
:key="navItem.name"
|
||||
:to="navItem.path"
|
||||
>
|
||||
<div class="navigation-area__item-container__link">
|
||||
<component :is="navItem.icon"></component>
|
||||
<p class="navigation-area__item-container__link__title">{{ navItem.name }}</p>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import MyNodesIcon from '@/../static/images/icons/navigation/nodes.svg';
|
||||
import NotificationIcon from '@/../static/images/icons/navigation/notifications.svg';
|
||||
import PayoutsIcon from '@/../static/images/icons/navigation/payouts.svg';
|
||||
import ReputationIcon from '@/../static/images/icons/navigation/reputation.svg';
|
||||
import TrafficIcon from '@/../static/images/icons/navigation/traffic.svg';
|
||||
import StorjLogo from '@/../static/images/Logo.svg';
|
||||
|
||||
export class NavigationLink {
|
||||
constructor(
|
||||
public name: string = '',
|
||||
public path: string = '',
|
||||
public icon: Vue = new Vue(),
|
||||
) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
StorjLogo,
|
||||
MyNodesIcon,
|
||||
PayoutsIcon,
|
||||
ReputationIcon,
|
||||
TrafficIcon,
|
||||
NotificationIcon,
|
||||
},
|
||||
})
|
||||
export default class NavigationArea extends Vue {
|
||||
/**
|
||||
* Array of navigation links with icons.
|
||||
*/
|
||||
// TODO: add actual routes
|
||||
public readonly navigation: NavigationLink[] = [
|
||||
new NavigationLink('My Nodes', '/my-nodes', MyNodesIcon),
|
||||
new NavigationLink('Payouts', '/payouts', PayoutsIcon),
|
||||
new NavigationLink('Bandwidth & Disk', '/traffic', TrafficIcon),
|
||||
new NavigationLink('Reputation', '/reputation', ReputationIcon),
|
||||
new NavigationLink('Notifications', '/notifications', NotificationIcon),
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.navigation-area {
|
||||
box-sizing: border-box;
|
||||
padding: 30px 24px;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
border-right: 1px solid var(--c-gray--light);
|
||||
background: var(--c-block-gray);
|
||||
|
||||
&__logo {
|
||||
margin-bottom: 62px;
|
||||
}
|
||||
|
||||
&__item-container {
|
||||
flex: 0 0 auto;
|
||||
padding: 10px;
|
||||
width: calc(100% - 20px);
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
text-decoration: none;
|
||||
|
||||
path {
|
||||
fill: var(--c-label);
|
||||
}
|
||||
|
||||
&__link {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
&__title {
|
||||
font-family: 'font_semiBold', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 23px;
|
||||
margin: 0 0 0 15px;
|
||||
white-space: nowrap;
|
||||
color: var(--c-label);
|
||||
}
|
||||
}
|
||||
|
||||
&.router-link-active,
|
||||
&:hover {
|
||||
background: #e7e9eb;
|
||||
border-radius: 6px;
|
||||
|
||||
.navigation-area__item-container__link__title {
|
||||
color: var(--c-title);
|
||||
}
|
||||
|
||||
path {
|
||||
fill: var(--c-title) !important;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
101
web/multinode/src/app/components/tables/NodesTable.vue
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<table class="nodes-table" v-if="nodes.length" border="0" cellpadding="0" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="align-left">NODE</th>
|
||||
<th>DISK SPACE USED</th>
|
||||
<th>DISK SPACE LEFT</th>
|
||||
<th>BANDWIDTH USED</th>
|
||||
<th>EARNED</th>
|
||||
<th>VERSION</th>
|
||||
<th>STATUS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="node in nodes" :key="node.id">
|
||||
<th class="align-left">{{ node.name }}</th>
|
||||
<th>{{ node.diskSpaceUsed | bytesToBase10String }}</th>
|
||||
<th>{{ node.diskSpaceLeft | bytesToBase10String }}</th>
|
||||
<th>{{ node.bandwidthUsed | bytesToBase10String }}</th>
|
||||
<th>{{ node.earned | centsToDollars }}</th>
|
||||
<th>{{ node.version }}</th>
|
||||
<th :class="node.status">{{ node.status }}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import { Node } from '@/nodes';
|
||||
|
||||
@Component
|
||||
export default class NodesTable extends Vue {
|
||||
public nodes: Node[] = [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.nodes-table {
|
||||
width: 100%;
|
||||
border: 1px solid var(--c-gray--light);
|
||||
border-radius: var(--br-table);
|
||||
font-family: 'font_semiBold', sans-serif;
|
||||
overflow: hidden;
|
||||
|
||||
th {
|
||||
box-sizing: border-box;
|
||||
padding: 0 20px;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: var(--c-block-gray);
|
||||
|
||||
tr {
|
||||
height: 40px;
|
||||
font-size: 12px;
|
||||
color: var(--c-gray);
|
||||
border-radius: var(--br-table);
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
|
||||
tr {
|
||||
height: 56px;
|
||||
text-align: right;
|
||||
font-size: 16px;
|
||||
color: var(--c-line);
|
||||
|
||||
&:nth-of-type(even) {
|
||||
background: var(--c-block-gray);
|
||||
}
|
||||
|
||||
th:not(:first-of-type) {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.online {
|
||||
color: var(--c-success);
|
||||
}
|
||||
|
||||
.offline {
|
||||
color: var(--c-error);
|
||||
}
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -4,7 +4,10 @@
|
||||
import Router, { RouterMode } from 'vue-router';
|
||||
import { Component } from 'vue-router/types/router';
|
||||
|
||||
import AddFirstNode from '@/app/views/AddFirstNode.vue';
|
||||
import Dashboard from '@/app/views/Dashboard.vue';
|
||||
import MyNodes from '@/app/views/MyNodes.vue';
|
||||
import WelcomeScreen from '@/app/views/WelcomeScreen.vue';
|
||||
|
||||
/**
|
||||
* Metadata holds arbitrary information to routes like transition names, who can access the route, etc.
|
||||
@ -20,7 +23,7 @@ export class Route {
|
||||
public readonly path: string;
|
||||
public readonly name: string;
|
||||
public readonly component: Component;
|
||||
public readonly children?: Route[];
|
||||
public children?: Route[];
|
||||
public readonly meta?: Metadata;
|
||||
|
||||
/**
|
||||
@ -31,24 +34,39 @@ export class Route {
|
||||
* @param children - all nested components of current route.
|
||||
* @param meta - arbitrary information to routes like transition names, who can access the route, etc.
|
||||
*/
|
||||
public constructor(path: string, name: string, component: Component, children: Route[] | undefined = undefined, meta: Metadata | undefined = undefined) {
|
||||
public constructor(path: string, name: string, component: Component, meta: Metadata | undefined = undefined) {
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
this.component = component;
|
||||
this.children = children;
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds children routes to route.
|
||||
*/
|
||||
public addChildren(children: Route[]): Route {
|
||||
this.children = children;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Config contains configuration of all available routes for a Multinode Dashboard router.
|
||||
*/
|
||||
export class Config {
|
||||
public static Root: Route = new Route('/', 'Root', Dashboard, undefined, {requiresAuth: true});
|
||||
public static Root: Route = new Route('/', 'Root', Dashboard, {requiresAuth: true});
|
||||
public static Welcome: Route = new Route('/welcome', 'Welcome', WelcomeScreen);
|
||||
public static AddFirstNode: Route = new Route('/add-first-node', 'AddFirstNode', AddFirstNode);
|
||||
public static MyNodes: Route = new Route('/my-nodes', 'MyNodes', MyNodes);
|
||||
|
||||
public static mode: RouterMode = 'history';
|
||||
public static routes: Route[] = [
|
||||
Config.Root,
|
||||
Config.Root.addChildren([
|
||||
Config.MyNodes,
|
||||
]),
|
||||
Config.Welcome,
|
||||
Config.AddFirstNode,
|
||||
];
|
||||
}
|
||||
|
||||
|
15
web/multinode/src/app/utils/currency.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
/**
|
||||
* Size class contains currency related functionality such as convertation.
|
||||
*/
|
||||
export class Currency {
|
||||
/**
|
||||
* dollarsFromCents converts cents to dollars with prefix.
|
||||
* @param cents count
|
||||
*/
|
||||
public static dollarsFromCents(cents: number): string {
|
||||
return `$${(cents / 100).toFixed(2)}`;
|
||||
}
|
||||
}
|
44
web/multinode/src/app/utils/size.ts
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
/**
|
||||
* Base10 sizes.
|
||||
*/
|
||||
enum SizeBreakpoints {
|
||||
KB = 1e3,
|
||||
MB = 1e6,
|
||||
GB = 1e9,
|
||||
TB = 1e12,
|
||||
PB = 1e15,
|
||||
EB = 1e18,
|
||||
}
|
||||
|
||||
/**
|
||||
* Size class contains size related functionality such as convertation.
|
||||
*/
|
||||
export class Size {
|
||||
/**
|
||||
* Base10String converts size to a string using base-10 prefixes.
|
||||
* @param size in bytes
|
||||
*/
|
||||
public static toBase10String(size: number): string {
|
||||
const decimals = 2;
|
||||
|
||||
const _size = Math.abs(size);
|
||||
|
||||
switch (true) {
|
||||
case _size >= SizeBreakpoints.EB * 2 / 3:
|
||||
return `${parseFloat((size / SizeBreakpoints.EB).toFixed(decimals))}EB`;
|
||||
case _size >= SizeBreakpoints.PB * 2 / 3:
|
||||
return `${parseFloat((size / SizeBreakpoints.PB).toFixed(decimals))}PB`;
|
||||
case _size >= SizeBreakpoints.TB * 2 / 3:
|
||||
return `${parseFloat((size / SizeBreakpoints.TB).toFixed(decimals))}TB`;
|
||||
case _size >= SizeBreakpoints.MB * 2 / 3:
|
||||
return `${parseFloat((size / SizeBreakpoints.MB).toFixed(decimals))}MB`;
|
||||
case _size >= SizeBreakpoints.KB * 2 / 3:
|
||||
return `${parseFloat((size / SizeBreakpoints.KB).toFixed(decimals))}KB`;
|
||||
default:
|
||||
return `${size}B`;
|
||||
}
|
||||
}
|
||||
}
|
107
web/multinode/src/app/views/AddFirstNode.vue
Normal file
@ -2,14 +2,68 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="dashboard-area">
|
||||
<div class="dashboard-area__navigation-area">
|
||||
<navigation-area />
|
||||
</div>
|
||||
<div class="dashboard-area__right-area">
|
||||
<div class="dashboard-area__right-area__header">
|
||||
<add-new-node />
|
||||
</div>
|
||||
<div class="dashboard-area__right-area__content">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
@Component
|
||||
import AddNewNode from '@/app/components/modals/AddNewNode.vue';
|
||||
import NavigationArea from '@/app/components/navigation/NavigationArea.vue';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
AddNewNode,
|
||||
NavigationArea,
|
||||
},
|
||||
})
|
||||
export default class Dashboard extends Vue {}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dashboard-area {
|
||||
display: flex;
|
||||
|
||||
&__navigation-area {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
&__right-area {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
|
||||
&__header {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
padding: 0 60px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
border: 1px solid var(--c-gray--light);
|
||||
background: var(--c-block-gray);
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 80px);
|
||||
top: 80px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
36
web/multinode/src/app/views/MyNodes.vue
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="my-nodes">
|
||||
<h1 class="my-nodes__title">My Nodes</h1>
|
||||
<nodes-table />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import NodesTable from '@/app/components/tables/NodesTable.vue';
|
||||
|
||||
@Component({
|
||||
components: { NodesTable },
|
||||
})
|
||||
export default class MyNodes extends Vue {}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.my-nodes {
|
||||
box-sizing: border-box;
|
||||
padding: 60px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
&__title {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 32px;
|
||||
color: var(--c-title);
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
}
|
||||
</style>
|
81
web/multinode/src/app/views/WelcomeScreen.vue
Normal file
@ -7,11 +7,27 @@ import Router from 'vue-router';
|
||||
import App from '@/app/App.vue';
|
||||
import { router } from '@/app/router';
|
||||
import { store } from '@/app/store';
|
||||
import { Currency } from '@/app/utils/currency';
|
||||
import { Size } from '@/app/utils/size';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
/**
|
||||
* centsToDollars is a Vue filter that converts amount of cents in dollars string.
|
||||
*/
|
||||
Vue.filter('centsToDollars', (cents: number): string => {
|
||||
return Currency.dollarsFromCents(cents);
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts bytes to base-10 size.
|
||||
*/
|
||||
Vue.filter('bytesToBase10String', (amountInBytes: number): string => {
|
||||
return Size.toBase10String(amountInBytes);
|
||||
});
|
||||
|
||||
const app = new Vue({
|
||||
router,
|
||||
store,
|
||||
|
@ -2,9 +2,9 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
/**
|
||||
* Node is a representation of storagenode, that SNO could add to the Multinode Dashboard.
|
||||
* NodeToAdd is a representation of storagenode, that SNO could add to the Multinode Dashboard.
|
||||
*/
|
||||
export class Node {
|
||||
export class NodeToAdd {
|
||||
public id: string; // TODO: create ts analog of storj.NodeID;
|
||||
/**
|
||||
* apiSecret is a secret issued by storagenode, that will be main auth mechanism in MND <-> SNO api.
|
||||
@ -13,3 +13,28 @@ export class Node {
|
||||
public publicAddress: string;
|
||||
public name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes node online statuses.
|
||||
*/
|
||||
export enum NodeStatus {
|
||||
Online = 'online',
|
||||
Offline = 'offline',
|
||||
}
|
||||
|
||||
// TODO: refactor this
|
||||
/**
|
||||
* Node holds all information of node for the Multinode Dashboard.
|
||||
*/
|
||||
export class Node {
|
||||
public constructor(
|
||||
public id: string = '',
|
||||
public name: string = '',
|
||||
public diskSpaceUsed: number = 0,
|
||||
public diskSpaceLeft: number = 0,
|
||||
public bandwidthUsed: number = 0,
|
||||
public earned: number = 0,
|
||||
public version: string = '',
|
||||
public status: NodeStatus = NodeStatus.Offline,
|
||||
) {}
|
||||
}
|
||||
|
BIN
web/multinode/static/fonts/font_bold.ttf
Normal file
BIN
web/multinode/static/fonts/font_medium.ttf
Normal file
BIN
web/multinode/static/fonts/font_regular.ttf
Normal file
BIN
web/multinode/static/fonts/font_semiBold.ttf
Normal file
BIN
web/multinode/static/images/Illustration.png
Normal file
After Width: | Height: | Size: 64 KiB |
10
web/multinode/static/images/Logo.svg
Normal file
After Width: | Height: | Size: 8.6 KiB |
3
web/multinode/static/images/icons/navigation/nodes.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 2.50065C2.5 1.58018 3.24619 0.833984 4.16667 0.833984H15.8333C16.7538 0.833984 17.5 1.58018 17.5 2.50065V17.5006C17.5 18.4211 16.7538 19.1673 15.8333 19.1673H4.16667C3.24619 19.1673 2.5 18.4211 2.5 17.5006V2.50065ZM6.25 6.66732C6.71025 6.66732 7.08333 6.29423 7.08333 5.83398C7.08333 5.37373 6.71025 5.00065 6.25 5.00065C5.78975 5.00065 5.41667 5.37373 5.41667 5.83398C5.41667 6.29423 5.78975 6.66732 6.25 6.66732ZM7.91667 5.83398C7.91667 5.37375 8.28976 5.00065 8.75 5.00065H13.75C14.2102 5.00065 14.5833 5.37375 14.5833 5.83398C14.5833 6.29422 14.2102 6.66732 13.75 6.66732H8.75C8.28976 6.66732 7.91667 6.29422 7.91667 5.83398ZM8.75 9.16732C8.28976 9.16732 7.91667 9.54041 7.91667 10.0007C7.91667 10.4609 8.28976 10.834 8.75 10.834H13.75C14.2102 10.834 14.5833 10.4609 14.5833 10.0007C14.5833 9.54041 14.2102 9.16732 13.75 9.16732H8.75ZM8.75 13.334C8.28976 13.334 7.91667 13.7071 7.91667 14.1673C7.91667 14.6276 8.28976 15.0006 8.75 15.0006H13.75C14.2102 15.0006 14.5833 14.6276 14.5833 14.1673C14.5833 13.7071 14.2102 13.334 13.75 13.334H8.75ZM6.25 10.834C6.71025 10.834 7.08333 10.4609 7.08333 10.0007C7.08333 9.5404 6.71025 9.16732 6.25 9.16732C5.78975 9.16732 5.41667 9.5404 5.41667 10.0007C5.41667 10.4609 5.78975 10.834 6.25 10.834ZM6.25 15.0006C6.71025 15.0006 7.08333 14.6276 7.08333 14.1673C7.08333 13.7071 6.71025 13.334 6.25 13.334C5.78975 13.334 5.41667 13.7071 5.41667 14.1673C5.41667 14.6276 5.78975 15.0006 6.25 15.0006Z" fill="#586474"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 20C11.1 20 12 19.1 12 18H8C8 19.1 8.9 20 10 20ZM16 14V9C16 5.93 14.37 3.36 11.5 2.68V2C11.5 1.17 10.83 0.5 10 0.5C9.17 0.5 8.5 1.17 8.5 2V2.68C5.64 3.36 4 5.92 4 9V14L2 16V17H18V16L16 14ZM14 15H6V9C6 6.52 7.51 4.5 10 4.5C12.49 4.5 14 6.52 14 9V15Z" fill="#586474"/>
|
||||
</svg>
|
After Width: | Height: | Size: 382 B |
4
web/multinode/static/images/icons/navigation/payouts.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 10H16V14H14V10Z" fill="#586474"/>
|
||||
<path d="M17.2 5.50039V3.70039C17.2 2.70769 16.3927 1.90039 15.4 1.90039H3.7C2.2114 1.90039 1 3.11179 1 4.60039V15.4004C1 17.3813 2.6146 18.1004 3.7 18.1004H17.2C18.1927 18.1004 19 17.2931 19 16.3004V7.30039C19 6.30769 18.1927 5.50039 17.2 5.50039ZM3.7 3.70039H15.4V5.50039H3.7C3.46827 5.49002 3.24946 5.39067 3.08915 5.22303C2.92883 5.05538 2.83936 4.83235 2.83936 4.60039C2.83936 4.36843 2.92883 4.1454 3.08915 3.97775C3.24946 3.81011 3.46827 3.71076 3.7 3.70039ZM17.2 16.3004H3.7108C3.295 16.2896 2.8 16.1249 2.8 15.4004V7.13389C3.0826 7.23559 3.3823 7.30039 3.7 7.30039H17.2V16.3004Z" fill="#586474"/>
|
||||
</svg>
|
After Width: | Height: | Size: 756 B |
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.996 0.250003C10.3133 0.249188 10.6035 0.428639 10.7446 0.712863L13.1308 5.52073L18.4523 6.29668C18.7658 6.34239 19.0262 6.56186 19.1244 6.86304C19.2225 7.16422 19.1414 7.49502 18.9151 7.71666L15.0444 11.5074L15.9636 16.7734C16.0183 17.0871 15.8895 17.4047 15.6315 17.5915C15.3736 17.7784 15.0317 17.8019 14.7507 17.6521L9.99818 15.119L5.24675 17.652C4.96552 17.802 4.62341 17.7784 4.36543 17.5912C4.10745 17.4041 3.97879 17.0862 4.03398 16.7724L4.95973 11.5074L1.08283 7.71717C0.856142 7.49555 0.774837 7.1645 0.87307 6.86308C0.971303 6.56165 1.23206 6.34208 1.5458 6.29658L6.8962 5.52074L9.24979 0.716704C9.38939 0.431758 9.67869 0.250818 9.996 0.250003ZM10.003 2.96758L8.1994 6.64896C8.07832 6.89612 7.84301 7.06755 7.57064 7.10704L3.45354 7.70405L6.43915 10.6229C6.63585 10.8152 6.72498 11.0922 6.67734 11.3631L5.97158 15.3769L9.60611 13.4393C9.85112 13.3087 10.1451 13.3087 10.3901 13.4393L14.0282 15.3784L13.3272 11.3621C13.28 11.0917 13.369 10.8154 13.5651 10.6234L16.5469 7.70314L12.4582 7.10695C12.1876 7.06749 11.9535 6.89777 11.832 6.6528L10.003 2.96758Z" fill="#586474"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
4
web/multinode/static/images/icons/navigation/traffic.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.1595 5.95509C6.21995 6.23073 5.41536 7.00873 5.41536 8.13094C5.41536 8.92561 5.77129 9.55151 6.3613 9.94129C6.90384 10.2997 7.57734 10.4172 8.20591 10.4172L11.6654 10.4175L11.7852 10.4172C12.4138 10.4172 13.088 10.2997 13.6315 9.94223C14.2224 9.5536 14.582 8.92806 14.582 8.13094C14.582 7.37062 14.2809 6.75139 13.7476 6.34748C13.4693 6.13672 13.1547 6.00459 12.8355 5.92898C12.7176 5.41471 12.4741 4.95056 12.1066 4.57903C11.5663 4.0327 10.8214 3.75081 9.9986 3.75081C9.18143 3.75081 8.43641 4.02397 7.89429 4.57032C7.51798 4.94955 7.27357 5.42597 7.1595 5.95509ZM8.73903 8.75052C8.74653 8.75052 8.754 8.75062 8.76145 8.75081H11.2209C11.2283 8.75062 11.2358 8.75052 11.2433 8.75052H11.7852C12.241 8.75052 12.5485 8.65969 12.7157 8.54975C12.8354 8.47098 12.9153 8.37007 12.9153 8.13094C12.9153 7.844 12.8215 7.73679 12.7413 7.67608C12.6285 7.59064 12.4177 7.51441 12.1037 7.51148L12.0849 7.51169C11.6247 7.51169 11.2516 7.13859 11.2516 6.67836C11.2516 6.24477 11.1107 5.94219 10.9216 5.75103C10.7338 5.56108 10.4355 5.41748 9.9986 5.41748C9.55609 5.41748 9.26072 5.55947 9.07736 5.74425C8.89451 5.92852 8.75297 6.22612 8.75116 6.6697L8.7512 6.67806C8.7512 6.73474 8.74554 6.7901 8.73475 6.84361C8.65809 7.22469 8.32146 7.51169 7.91781 7.51169L7.90041 7.51151C7.34512 7.51902 7.08203 7.88433 7.08203 8.13094C7.08203 8.37251 7.1625 8.47307 7.27999 8.55068C7.44495 8.65966 7.75005 8.75052 8.20591 8.75052H8.73903Z" fill="#131D3A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.94915 1.91213C3.03015 1.52628 3.37046 1.25 3.76471 1.25H16.2573C16.6523 1.25 16.993 1.52729 17.0732 1.91404L19.1462 11.9058C19.1588 11.9632 19.1654 12.0229 19.1654 12.0841V17.5008C19.1654 17.9611 18.7923 18.3341 18.332 18.3341H1.66536C1.20513 18.3341 0.832031 17.9611 0.832031 17.5008V12.0841C0.832031 12.0208 0.83909 11.9592 0.852463 11.8999L2.94915 1.91213ZM15.5791 2.91667L17.3082 11.2508H2.69172L4.44127 2.91667H15.5791ZM15.832 14.7925C15.832 15.3678 15.3657 15.8341 14.7904 15.8341C14.2151 15.8341 13.7487 15.3678 13.7487 14.7925C13.7487 14.2172 14.2151 13.7508 14.7904 13.7508C15.3657 13.7508 15.832 14.2172 15.832 14.7925Z" fill="#131D3A"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
35
web/multinode/static/styles/_variables.scss
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
:root {
|
||||
// colors
|
||||
--c-title: #131d3a;
|
||||
--c-gray: #74777e;
|
||||
--c-label: #586474;
|
||||
--c-placeholder: #a2aebe;
|
||||
--c-background: #f4f6f9;
|
||||
--c-line: #131d3a;
|
||||
--c-gray--light: #e1e3e6;
|
||||
--c-block-gray: #f8f8f9;
|
||||
--c-primary: #0059d0;
|
||||
--c-error: #eb5757;
|
||||
--c-success: #04b978;
|
||||
--c-warning: #f4b740;
|
||||
--c-button-label: white;
|
||||
--c-button-common: #2683ff;
|
||||
--c-button-common-hover: #196cda;
|
||||
--c-button-white: transparent;
|
||||
--c-button-white--label: #354049;
|
||||
--c-button-white-hover: #ededed;
|
||||
--c-button-red: #ff4f4d;
|
||||
--c-button-red-hover: #de3e3d;
|
||||
--c-button-disabled: #dadde5;
|
||||
|
||||
// borders
|
||||
--b-button-white: 1px solid #afb7c1;
|
||||
|
||||
// border radiuses
|
||||
--br-button: 6px;
|
||||
--br-input: 6px;
|
||||
--br-table: 6px;
|
||||
}
|
@ -29,5 +29,16 @@ module.exports = {
|
||||
args[0].template = './index.html';
|
||||
return args
|
||||
});
|
||||
|
||||
const svgRule = config.module.rule('svg');
|
||||
|
||||
svgRule.uses.clear();
|
||||
|
||||
svgRule
|
||||
.use('babel-loader')
|
||||
.loader('babel-loader')
|
||||
.end()
|
||||
.use('vue-svg-loader')
|
||||
.loader('vue-svg-loader');
|
||||
}
|
||||
};
|
||||
|
@ -8,6 +8,17 @@
|
||||
Use this token to create your access grant in the CLI tool.
|
||||
</p>
|
||||
<div class="cli-container__token-area">
|
||||
<p class="cli-container__token-area__label">Satellite Address</p>
|
||||
<div class="cli-container__token-area__container">
|
||||
<p ref="addressContainer" class="cli-container__token-area__container__token" @click="selectAddress">{{ satelliteAddress }}</p>
|
||||
<VButton
|
||||
class="cli-container__token-area__container__button"
|
||||
label="Copy"
|
||||
width="66px"
|
||||
height="30px"
|
||||
:on-press="onCopyAddressClick"
|
||||
/>
|
||||
</div>
|
||||
<p class="cli-container__token-area__label">Token</p>
|
||||
<div class="cli-container__token-area__container">
|
||||
<p class="cli-container__token-area__container__token">{{ restrictedKey }}</p>
|
||||
@ -16,7 +27,7 @@
|
||||
label="Copy"
|
||||
width="66px"
|
||||
height="30px"
|
||||
:on-press="onCopyClick"
|
||||
:on-press="onCopyTokenClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -44,7 +44,7 @@
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
margin-bottom: 50px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
&__label {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
@ -61,6 +61,7 @@
|
||||
width: calc(100% - 30px);
|
||||
border: 1px solid rgba(56, 75, 101, 0.4);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__token {
|
||||
font-size: 16px;
|
||||
|