mirror of
https://github.com/absmach/magistrala.git
synced 2026-08-07 15:25:48 +00:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0fd0e6a13f | |||
| 8a86593dc3 | |||
| effade00aa | |||
| 8edf6907bc | |||
| 507bd2ee84 | |||
| 86f0d8e0d6 | |||
| 6461761985 | |||
| ec03d877f0 | |||
| 4e137a3214 | |||
| af281da430 | |||
| 1683d17830 | |||
| 7e83b2d612 | |||
| 312d30fd69 | |||
| 2c988c19b0 | |||
| 455fedf075 | |||
| 58cdf2cddc | |||
| 6d48446c16 | |||
| 29d146636a | |||
| f3f14e803d | |||
| b090aa3e02 | |||
| 0b954847fd | |||
| 44f5a7241f | |||
| b694f08ea2 | |||
| f1537528f9 | |||
| 344eed7976 | |||
| d8357b500c | |||
| 993398b8ab | |||
| 426f59d392 | |||
| 2b393ad50f | |||
| 9d7f4544c7 | |||
| 781d358f38 | |||
| 7b6030d8ad | |||
| 89e69d132a | |||
| 7590578745 | |||
| 97c2d9c30a | |||
| c38a032fa5 | |||
| 1d75268ffa | |||
| ef3627f4ee | |||
| 808ac50792 | |||
| 4c8aeb395c | |||
| 64e71edf95 | |||
| c5a40aeffa | |||
| 3d134d52a6 | |||
| f0cbc47ee6 | |||
| e0e2b2aad0 | |||
| bbf2d5560c | |||
| 69bfb8668e | |||
| 7043dea81a | |||
| e04d0aeaff | |||
| 9c6904d9b3 | |||
| 38a107919a | |||
| 48484210c2 | |||
| ceb630c782 | |||
| 570ebc1c72 | |||
| 50e14f0c65 | |||
| d2bce84c06 | |||
| 89aa9603ec | |||
| 2ae581368f | |||
| 99a3ec5d2d | |||
| 89a0fa3e9c | |||
| 1bc4dc9575 | |||
| bca3d0774f | |||
| 816c172823 | |||
| 6a361209c8 | |||
| 27e1be23f5 | |||
| c9bf245010 | |||
| ff30957614 | |||
| db6165aa6b | |||
| c088e664c3 | |||
| d499068e43 | |||
| 38d4c3fcea | |||
| aea7db14b7 | |||
| 58f3c735a0 | |||
| 789963447a |
+1
-1
@@ -1,4 +1,4 @@
|
||||
build
|
||||
*.pb.go
|
||||
|
||||
site/
|
||||
|
||||
|
||||
Generated
+282
-106
@@ -32,13 +32,19 @@
|
||||
branch = "master"
|
||||
name = "github.com/beorn7/perks"
|
||||
packages = ["quantile"]
|
||||
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
|
||||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/buger/jsonparser"
|
||||
packages = ["."]
|
||||
revision = "2cac668e8456b4284edb0715e17e2af02d3ec993"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/cenkalti/backoff"
|
||||
packages = ["."]
|
||||
revision = "61153c768f31ee5f130071d08fc82b85208528de"
|
||||
version = "v1.1.0"
|
||||
revision = "2ea60e5f094469f9e65adb9cd103795b73ae743e"
|
||||
version = "v2.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -50,7 +56,7 @@
|
||||
branch = "master"
|
||||
name = "github.com/containerd/continuity"
|
||||
packages = ["pathdriver"]
|
||||
revision = "d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371"
|
||||
revision = "c6cef34830231743494fe2969284df7b82cc0ad0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
@@ -58,50 +64,12 @@
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/dereulenspiegel/coap-mux"
|
||||
packages = ["."]
|
||||
revision = "e137e310a99955be01d60b2237da89153ea40987"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
packages = ["."]
|
||||
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
|
||||
version = "v3.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/docker"
|
||||
packages = [
|
||||
"api/types",
|
||||
"api/types/blkiodev",
|
||||
"api/types/container",
|
||||
"api/types/filters",
|
||||
"api/types/mount",
|
||||
"api/types/network",
|
||||
"api/types/registry",
|
||||
"api/types/strslice",
|
||||
"api/types/swarm",
|
||||
"api/types/swarm/runtime",
|
||||
"api/types/versions",
|
||||
"opts",
|
||||
"pkg/archive",
|
||||
"pkg/fileutils",
|
||||
"pkg/homedir",
|
||||
"pkg/idtools",
|
||||
"pkg/ioutils",
|
||||
"pkg/jsonmessage",
|
||||
"pkg/longpath",
|
||||
"pkg/mount",
|
||||
"pkg/pools",
|
||||
"pkg/stdcopy",
|
||||
"pkg/system",
|
||||
"pkg/term",
|
||||
"pkg/term/windows"
|
||||
]
|
||||
revision = "72ba7f593fa4dbb628cf5ee83cd7daf955934cf5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/go-connections"
|
||||
packages = ["nat"]
|
||||
@@ -111,20 +79,14 @@
|
||||
[[projects]]
|
||||
name = "github.com/docker/go-units"
|
||||
packages = ["."]
|
||||
revision = "0dadbb0345b35ec7ef35e228dabb8de89a65bf52"
|
||||
version = "v0.3.2"
|
||||
revision = "47565b4f722fb6ceae66b95f853feed578a4a51c"
|
||||
version = "v0.3.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/dustin/go-coap"
|
||||
name = "github.com/fatih/color"
|
||||
packages = ["."]
|
||||
revision = "ddcc80675fa42611359d91a6dfa5aa57fb90e72b"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fsouza/go-dockerclient"
|
||||
packages = ["."]
|
||||
revision = "ca33ff277b527ce11b793e62f9ba244129b01caf"
|
||||
version = "v1.2.0"
|
||||
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-kit/kit"
|
||||
@@ -134,6 +96,7 @@
|
||||
"metrics",
|
||||
"metrics/internal/lv",
|
||||
"metrics/prometheus",
|
||||
"transport/grpc",
|
||||
"transport/http"
|
||||
]
|
||||
revision = "4dc7be5d2d12881735283bcab7352178e190fc71"
|
||||
@@ -157,6 +120,17 @@
|
||||
revision = "fd0aebc74e908868b09ac140fb5a53cb363884c1"
|
||||
version = "1.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/gocql/gocql"
|
||||
packages = [
|
||||
".",
|
||||
"internal/lru",
|
||||
"internal/murmur",
|
||||
"internal/streams"
|
||||
]
|
||||
revision = "651d6b1f343c6e168e45d119dcfe8f71e5bafb32"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = ["proto"]
|
||||
@@ -165,9 +139,21 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp"
|
||||
]
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/snappy"
|
||||
packages = ["."]
|
||||
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
@@ -176,19 +162,31 @@
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/jinzhu/gorm"
|
||||
packages = [
|
||||
".",
|
||||
"dialects/postgres"
|
||||
]
|
||||
revision = "6ed508ec6a4ecb3531899a69cbc746ccf65a4166"
|
||||
version = "v1.9.1"
|
||||
name = "github.com/hailocab/go-hostpool"
|
||||
packages = ["."]
|
||||
revision = "e80d13ce29ede4452c43dea11e79b9bc8a15b478"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jinzhu/inflection"
|
||||
name = "github.com/hokaccha/go-prettyjson"
|
||||
packages = ["."]
|
||||
revision = "04140366298a54a039076d798123ffa108fff46c"
|
||||
revision = "e6b9231a2b1cc5798b336fdbf99c16cd8ad1e0c9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/influxdata/influxdb"
|
||||
packages = [
|
||||
"client/v2",
|
||||
"models",
|
||||
"pkg/escape"
|
||||
]
|
||||
revision = "02d7d4f043b34ecb4e9b2dbec298c6f9450c2a32"
|
||||
version = "v1.5.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -201,10 +199,21 @@
|
||||
name = "github.com/lib/pq"
|
||||
packages = [
|
||||
".",
|
||||
"hstore",
|
||||
"oid"
|
||||
]
|
||||
revision = "88edab0803230a3898347e77b474f8c1820a1f20"
|
||||
revision = "d34b9ff171c21ad295489235aec8b6626023cd04"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/matttproud/golang_protobuf_extensions"
|
||||
@@ -212,6 +221,61 @@
|
||||
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mongodb/mongo-go-driver"
|
||||
packages = [
|
||||
"bson",
|
||||
"bson/builder",
|
||||
"bson/decimal",
|
||||
"bson/elements",
|
||||
"bson/objectid",
|
||||
"bson/parser",
|
||||
"bson/parser/ast",
|
||||
"core/address",
|
||||
"core/auth",
|
||||
"core/auth/internal/gssapi",
|
||||
"core/command",
|
||||
"core/compressor",
|
||||
"core/connection",
|
||||
"core/connstring",
|
||||
"core/description",
|
||||
"core/dispatch",
|
||||
"core/event",
|
||||
"core/option",
|
||||
"core/readconcern",
|
||||
"core/readpref",
|
||||
"core/result",
|
||||
"core/session",
|
||||
"core/tag",
|
||||
"core/topology",
|
||||
"core/uuid",
|
||||
"core/version",
|
||||
"core/wiremessage",
|
||||
"core/writeconcern",
|
||||
"internal",
|
||||
"mongo",
|
||||
"mongo/aggregateopt",
|
||||
"mongo/changestreamopt",
|
||||
"mongo/clientopt",
|
||||
"mongo/collectionopt",
|
||||
"mongo/countopt",
|
||||
"mongo/dbopt",
|
||||
"mongo/deleteopt",
|
||||
"mongo/distinctopt",
|
||||
"mongo/dropcollopt",
|
||||
"mongo/findopt",
|
||||
"mongo/indexopt",
|
||||
"mongo/insertopt",
|
||||
"mongo/listcollectionopt",
|
||||
"mongo/listdbopt",
|
||||
"mongo/mongoopt",
|
||||
"mongo/replaceopt",
|
||||
"mongo/runcmdopt",
|
||||
"mongo/updateopt"
|
||||
]
|
||||
revision = "8df56f107cdd485351eb2523992c3364114b4b2e"
|
||||
version = "v0.0.10"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/nats-io/go-nats"
|
||||
packages = [
|
||||
@@ -219,8 +283,8 @@
|
||||
"encoders/builtin",
|
||||
"util"
|
||||
]
|
||||
revision = "d66cb54e6b7bdd93f0b28afc8450d84c780dfb68"
|
||||
version = "v1.4.0"
|
||||
revision = "062418ea1c2181f52dc0f954f6204370519a868b"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/nats-io/nuid"
|
||||
@@ -252,6 +316,37 @@
|
||||
revision = "baf6536d6259209c3edfa2b22237af82942d3dfa"
|
||||
version = "v0.1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/ory/dockertest"
|
||||
packages = [
|
||||
"docker",
|
||||
"docker/opts",
|
||||
"docker/pkg/archive",
|
||||
"docker/pkg/fileutils",
|
||||
"docker/pkg/homedir",
|
||||
"docker/pkg/idtools",
|
||||
"docker/pkg/ioutils",
|
||||
"docker/pkg/jsonmessage",
|
||||
"docker/pkg/longpath",
|
||||
"docker/pkg/mount",
|
||||
"docker/pkg/pools",
|
||||
"docker/pkg/stdcopy",
|
||||
"docker/pkg/system",
|
||||
"docker/pkg/term",
|
||||
"docker/pkg/term/windows",
|
||||
"docker/types",
|
||||
"docker/types/blkiodev",
|
||||
"docker/types/container",
|
||||
"docker/types/filters",
|
||||
"docker/types/mount",
|
||||
"docker/types/network",
|
||||
"docker/types/registry",
|
||||
"docker/types/strslice",
|
||||
"docker/types/versions"
|
||||
]
|
||||
revision = "2e92e7784b6fb199fd168aa46269a2f1b34f299e"
|
||||
version = "v3.3.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
@@ -287,7 +382,7 @@
|
||||
"internal/bitbucket.org/ww/goautoneg",
|
||||
"model"
|
||||
]
|
||||
revision = "e4aa40a9169a88835b849a6efb71e05dc04b88f0"
|
||||
revision = "d811d2e9bf898806ecfb6ef6296774b13ffc314c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -298,7 +393,16 @@
|
||||
"nfs",
|
||||
"xfs"
|
||||
]
|
||||
revision = "54d17b57dd7d4a3aa092476596b3f8a933bde349"
|
||||
revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/rubenv/sql-migrate"
|
||||
packages = [
|
||||
".",
|
||||
"sqlparse"
|
||||
]
|
||||
revision = "081fe17d19ff4e2dd9f5a0c1158e6bcf74da6906"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/satori/go.uuid"
|
||||
@@ -318,42 +422,32 @@
|
||||
revision = "e9556a45379ef1da12e54847edb2fb3d7d566f36"
|
||||
version = "0.3.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
packages = [
|
||||
"assert",
|
||||
"require"
|
||||
]
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/ugorji/go"
|
||||
packages = ["codec"]
|
||||
revision = "9831f2c3ac1068a78f50999a30db84270f647af6"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "go.uber.org/atomic"
|
||||
packages = ["."]
|
||||
revision = "8474b86a5a6f79c443ce4b2992817ff32cf208b8"
|
||||
version = "v1.3.1"
|
||||
|
||||
[[projects]]
|
||||
name = "go.uber.org/multierr"
|
||||
packages = ["."]
|
||||
revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "go.uber.org/zap"
|
||||
packages = [
|
||||
".",
|
||||
"buffer",
|
||||
"internal/bufferpool",
|
||||
"internal/color",
|
||||
"internal/exit",
|
||||
"zapcore"
|
||||
]
|
||||
revision = "35aad584952c3e7020db7b839f6b102de6271f89"
|
||||
version = "v1.7.1"
|
||||
revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -361,18 +455,30 @@
|
||||
packages = [
|
||||
"bcrypt",
|
||||
"blowfish",
|
||||
"pbkdf2",
|
||||
"ssh/terminal"
|
||||
]
|
||||
revision = "374053ea96cb300f8671b8d3b07edeeb06e203b4"
|
||||
revision = "2d027ae1dddd4694d54f7a8b6cbe78dca8720226"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp"
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"trace"
|
||||
]
|
||||
revision = "24dd3780ca4f75fed9f321890729414a4b5d3f13"
|
||||
revision = "f73e4c9ed3b7ebdd5f699a16a880c2b1994e50dd"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["semaphore"]
|
||||
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -381,17 +487,87 @@
|
||||
"unix",
|
||||
"windows"
|
||||
]
|
||||
revision = "01acb38716e021ed1fc03a602bdb5838e1358c5e"
|
||||
revision = "7dfd1290c7917b7ba22824b9d24954ab3002fe24"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable"
|
||||
]
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
"balancer",
|
||||
"balancer/base",
|
||||
"balancer/roundrobin",
|
||||
"channelz",
|
||||
"codes",
|
||||
"connectivity",
|
||||
"credentials",
|
||||
"encoding",
|
||||
"encoding/proto",
|
||||
"grpclb/grpc_lb_v1/messages",
|
||||
"grpclog",
|
||||
"internal",
|
||||
"keepalive",
|
||||
"metadata",
|
||||
"naming",
|
||||
"peer",
|
||||
"resolver",
|
||||
"resolver/dns",
|
||||
"resolver/passthrough",
|
||||
"stats",
|
||||
"status",
|
||||
"tap",
|
||||
"transport"
|
||||
]
|
||||
revision = "41344da2231b913fa3d983840a57a6b1b7b631a1"
|
||||
version = "v1.12.0"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/gorp.v1"
|
||||
packages = ["."]
|
||||
revision = "c87af80f3cc5036b55b83d77171e156791085e2e"
|
||||
version = "v1.7.1"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/inf.v0"
|
||||
packages = ["."]
|
||||
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
|
||||
version = "v0.9.1"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/ory-am/dockertest.v3"
|
||||
packages = ["."]
|
||||
revision = "15c8e8835bba04e0d7c2b57958ffe294d5e643dc"
|
||||
version = "v3.1.6"
|
||||
revision = "2e92e7784b6fb199fd168aa46269a2f1b34f299e"
|
||||
version = "v3.3.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "c1b28e90e21e838cec2d16c95d8eb414b5de9bbe9667acb01b5375db047cc6db"
|
||||
inputs-digest = "e3b07d2098253644fc2f77fede1f00fcc077a20e4f46c59466ffd61c4998bb00"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
+25
-13
@@ -6,17 +6,13 @@
|
||||
branch = "master"
|
||||
name = "github.com/cisco/senml"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/dereulenspiegel/coap-mux"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
version = "3.2.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/dustin/go-coap"
|
||||
name = "github.com/fatih/color"
|
||||
version = "1.7.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-kit/kit"
|
||||
@@ -28,11 +24,11 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/golang/protobuf"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/jinzhu/gorm"
|
||||
version = "1.9.1"
|
||||
branch = "master"
|
||||
name = "github.com/hokaccha/go-prettyjson"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
@@ -55,12 +51,12 @@
|
||||
version = "0.3.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.2.1"
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "0.0.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "go.uber.org/zap"
|
||||
version = "1.7.1"
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.2.1"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
@@ -74,6 +70,22 @@
|
||||
name = "github.com/gorilla/websocket"
|
||||
version = "1.2.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/rubenv/sql-migrate"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/influxdata/influxdb"
|
||||
version = "1.5.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mongodb/mongo-go-driver"
|
||||
version = "0.0.5"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/gocql/gocql"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
BUILD_DIR = build
|
||||
SERVICES = manager http normalizer ws
|
||||
SERVICES = users things http normalizer ws influxdb-writer influxdb-reader mongodb-writer mongodb-reader cassandra-writer cassandra-reader cli
|
||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||
CGO_ENABLED ?= 0
|
||||
GOOS ?= linux
|
||||
@@ -12,31 +12,42 @@ define make_docker
|
||||
docker build --build-arg SVC_NAME=$(subst docker_,,$(1)) --tag=mainflux/$(subst docker_,,$(1)) -f docker/Dockerfile .
|
||||
endef
|
||||
|
||||
all: $(SERVICES)
|
||||
all: $(SERVICES) mqtt
|
||||
|
||||
.PHONY: all $(SERVICES) dockers latest release
|
||||
.PHONY: all $(SERVICES) dockers latest release mqtt
|
||||
|
||||
clean:
|
||||
rm -rf ${BUILD_DIR}
|
||||
rm -rf mqtt/node_modules
|
||||
|
||||
install:
|
||||
cp ${BUILD_DIR}/* $(GOBIN)
|
||||
|
||||
proto:
|
||||
protoc --go_out=. *.proto
|
||||
test:
|
||||
GOCACHE=off go test -v -race -tags test $(shell go list ./... | grep -v 'vendor\|cmd')
|
||||
|
||||
$(SERVICES): proto
|
||||
proto:
|
||||
protoc --go_out=plugins=grpc:. *.proto
|
||||
|
||||
$(SERVICES):
|
||||
$(call compile_service,$(@))
|
||||
|
||||
$(DOCKERS):
|
||||
$(call make_docker,$(@))
|
||||
|
||||
dockers: $(DOCKERS)
|
||||
docker build --tag=mainflux/dashflux -f dashflux/docker/Dockerfile dashflux
|
||||
docker build --tag=mainflux/mqtt -f mqtt/Dockerfile .
|
||||
|
||||
mqtt:
|
||||
cd mqtt && npm install
|
||||
|
||||
latest: dockers
|
||||
for svc in $(SERVICES); do \
|
||||
docker push mainflux/$$svc; \
|
||||
done
|
||||
docker push mainflux/dashflux
|
||||
docker push mainflux/mqtt
|
||||
|
||||
release:
|
||||
$(eval version = $(shell git describe --abbrev=0 --tags))
|
||||
@@ -46,3 +57,10 @@ release:
|
||||
docker tag mainflux/$$svc mainflux/$$svc:$(version); \
|
||||
docker push mainflux/$$svc:$(version); \
|
||||
done
|
||||
docker tag mainflux/dashflux mainflux/dashflux:$(version)
|
||||
docker push mainflux/dashflux:$(version)
|
||||
docker tag mainflux/mqtt mainflux/mqtt:$(version)
|
||||
docker push mainflux/mqtt:$(version)
|
||||
|
||||
run:
|
||||
cd scripts && ./run.sh
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mainflux
|
||||
|
||||
// Response contains HTTP response specific methods.
|
||||
type Response interface {
|
||||
// Code returns HTTP response code.
|
||||
Code() int
|
||||
|
||||
// Headers returns map of HTTP headers with their values.
|
||||
Headers() map[string]string
|
||||
|
||||
// Empty indicates if HTTP response has content.
|
||||
Empty() bool
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
# Mainflux CLI
|
||||
## Build
|
||||
From the project root:
|
||||
```
|
||||
make cli
|
||||
```
|
||||
|
||||
## Usage
|
||||
### Service
|
||||
#### Get the service verison
|
||||
```
|
||||
mainflux-cli version
|
||||
```
|
||||
|
||||
### User management
|
||||
#### Create User
|
||||
```
|
||||
mainflux-cli users create john.doe@email.com password
|
||||
```
|
||||
|
||||
#### Login User
|
||||
```
|
||||
mainflux-cli users token john.doe@email.com password
|
||||
```
|
||||
|
||||
### System Provisioning
|
||||
#### Provision Device
|
||||
```
|
||||
mainflux-cli things create '{"type":"device", "name":"nyDevice"}' <user_auth_token>
|
||||
```
|
||||
|
||||
#### Provision Application
|
||||
```
|
||||
mainflux-cli things create '{"type":"app", "name":"nyDevice"}' <user_auth_token>
|
||||
```
|
||||
|
||||
#### Retrieve All Things
|
||||
```
|
||||
mainflux-cli things get all --offset=1 --limit=5 <user_auth_token>
|
||||
```
|
||||
|
||||
#### Retrieve Thing By ID
|
||||
```
|
||||
mainflux-cli things get <thing_id> --offset=1 --limit=5 <user_auth_token>
|
||||
```
|
||||
|
||||
#### Remove Thing
|
||||
```
|
||||
mainflux-cli things delete <thing_id> <user_auth_token>
|
||||
```
|
||||
|
||||
#### Provision Channel
|
||||
```
|
||||
mainflux-cli channels create '{"name":"nyChannel"}' <user_auth_token>
|
||||
```
|
||||
|
||||
#### Retrieve All Channels
|
||||
```
|
||||
mainflux-cli channels get all --offset=1 --limit=5 <user_auth_token>
|
||||
```
|
||||
|
||||
#### Retrievie Channel By ID
|
||||
```
|
||||
mainflux-cli channels get <channel_id> --offset=1 --limit=5 <user_auth_token>
|
||||
```
|
||||
|
||||
#### Remove Channel
|
||||
```
|
||||
mainflux-cli channels delete <channel_id> <user_auth_token>
|
||||
```
|
||||
|
||||
### Access control
|
||||
#### Connect Thing to a Channel
|
||||
```
|
||||
mainflux-cli things connect <thing_id> <channel_id> <user_auth_token>
|
||||
```
|
||||
|
||||
#### Disconnect Things from a Channel
|
||||
```
|
||||
mainflux-cli things disconnect <thing_id> <channel_id> <user_auth_token>
|
||||
```
|
||||
|
||||
### Messaging
|
||||
#### Send a message over HTTP
|
||||
```
|
||||
mainflux-cli msg send <channel_id> '[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]' <thing_auth_token>
|
||||
```
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const channelsEP = "channels"
|
||||
|
||||
var cmdChannels = []cobra.Command{
|
||||
cobra.Command{
|
||||
Use: "create",
|
||||
Short: "create <JSON_channel> <user_auth_token>",
|
||||
Long: `Creates new channel and generates it's UUID`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
CreateChannel(args[0], args[1])
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "get",
|
||||
Short: "get all/<channel_id> <user_auth_token>",
|
||||
Long: `Gets list of all channels or gets channel by id`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
if args[0] == "all" {
|
||||
GetChannels(args[1])
|
||||
return
|
||||
}
|
||||
GetChannel(args[0], args[1])
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "update",
|
||||
Short: "update <channel_id> <JSON_string> <user_auth_token>",
|
||||
Long: `Updates channel record`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
UpdateChannel(args[0], args[1], args[2])
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "delete <channel_id> <user_auth_token>",
|
||||
Long: `Delete channel by ID`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
DeleteChannel(args[0], args[1])
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func NewChannelsCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "channels",
|
||||
Short: "Manipulation with channels",
|
||||
Long: `Manipulation with channels: create, delete or update channels`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
LogUsage(cmd.Short)
|
||||
},
|
||||
}
|
||||
|
||||
for i, _ := range cmdChannels {
|
||||
cmd.AddCommand(&cmdChannels[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// CreateChannel - creates new channel and generates UUID
|
||||
func CreateChannel(data, token string) {
|
||||
url := fmt.Sprintf("%s/%s", serverAddr, channelsEP)
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data))
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// GetChannels - gets all channels
|
||||
func GetChannels(token string) {
|
||||
url := fmt.Sprintf("%s/%s?offset=%s&limit=%s",
|
||||
serverAddr, channelsEP, strconv.Itoa(Offset), strconv.Itoa(Limit))
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// GetChannel - gets channel by ID
|
||||
func GetChannel(id, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, channelsEP, id)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// UpdateChannel - update a channel
|
||||
func UpdateChannel(id, data, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, channelsEP, id)
|
||||
req, err := http.NewRequest("PUT", url, strings.NewReader(data))
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// DeleteChannel - removes channel
|
||||
func DeleteChannel(id, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, channelsEP, id)
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
)
|
||||
|
||||
const (
|
||||
defCertsPath = "/src/github.com/mainflux/mainflux/docker/ssl/certs/"
|
||||
envCertFile = "MF_CERT_FILE"
|
||||
envKeyFile = "MF_KEY_FILE"
|
||||
envCaFile = "MF_CA_FILE"
|
||||
)
|
||||
|
||||
var (
|
||||
httpClient = &http.Client{}
|
||||
serverAddr = fmt.Sprintf("https://%s", "localhost")
|
||||
|
||||
defCertFile = fmt.Sprintf("%s%s%s", os.Getenv("GOPATH"), defCertsPath, "mainflux-server.crt")
|
||||
defKeyFile = fmt.Sprintf("%s%s%s", os.Getenv("GOPATH"), defCertsPath, "mainflux-server.key")
|
||||
defCaFile = fmt.Sprintf("%s%s%s", os.Getenv("GOPATH"), defCertsPath, "ca.crt")
|
||||
)
|
||||
|
||||
// SetServerAddr - set addr using host and port
|
||||
func SetServerAddr(proto string, host string, port int) {
|
||||
serverAddr = fmt.Sprintf("%s://%s", proto, host)
|
||||
|
||||
if port != 0 {
|
||||
serverAddr = fmt.Sprintf("%s:%s", serverAddr, strconv.Itoa(port))
|
||||
}
|
||||
}
|
||||
|
||||
func SetCerts() {
|
||||
// Set certificates paths
|
||||
certFile := mainflux.Env(envCertFile, defCertFile)
|
||||
keyFile := mainflux.Env(envKeyFile, defKeyFile)
|
||||
caFile := mainflux.Env(envCaFile, defCaFile)
|
||||
|
||||
// Load client cert
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Load CA cert
|
||||
caCert, err := ioutil.ReadFile(caFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
// Setup HTTPS client
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: caCertPool,
|
||||
}
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
transport := &http.Transport{TLSClientConfig: tlsConfig}
|
||||
httpClient = &http.Client{Transport: transport}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const contentTypeSenml = "application/senml+json"
|
||||
|
||||
var cmdMessages = []cobra.Command{
|
||||
cobra.Command{
|
||||
Use: "send",
|
||||
Short: "send <channel_id> <JSON_string> <client_token>",
|
||||
Long: `Sends message on the channel`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
SendMsg(args[0], args[1], args[2])
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func NewMessagesCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "msg",
|
||||
Short: "Send or retrieve messages",
|
||||
Long: `Send or retrieve messages: control message flow on the channel`,
|
||||
}
|
||||
|
||||
for i, _ := range cmdMessages {
|
||||
cmd.AddCommand(&cmdMessages[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// SendMsg - publishes SenML message on the channel
|
||||
func SendMsg(id, msg, token string) {
|
||||
url := serverAddr + "/http/channels/" + id + "/messages"
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(msg))
|
||||
LogError(err)
|
||||
|
||||
req.Header.Set("Authorization", token)
|
||||
req.Header.Add("Content-Type", contentTypeSenml)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
FormatResLog(resp, err)
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const thingsEP = "things"
|
||||
|
||||
var cmdThings = []cobra.Command{
|
||||
cobra.Command{
|
||||
Use: "create",
|
||||
Short: "create <JSON_thing> <user_auth_token>",
|
||||
Long: `Create new thing, generate his UUID and store it`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
CreateThing(args[0], args[1])
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "get",
|
||||
Short: "get all/<thing_id> <user_auth_token>",
|
||||
Long: `Get all thingss or thing by id`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
if args[0] == "all" {
|
||||
GetThings(args[1])
|
||||
return
|
||||
}
|
||||
GetThing(args[0], args[1])
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "delete <thing_id> <user_auth_token>",
|
||||
Long: `Removes thing from database`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
DeleteThing(args[0], args[1])
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "update",
|
||||
Short: "update <thing_id> <JSON_string> <user_auth_token>",
|
||||
Long: `Update thing record`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
UpdateThing(args[0], args[1], args[2])
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "connect",
|
||||
Short: "connect <thing_id> <channel_id> <user_auth_token>",
|
||||
Long: `Connect thing to the channel`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
ConnectThing(args[0], args[1], args[2])
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "disconnect",
|
||||
Short: "disconnect <thing_id> <channel_id> <user_auth_token>",
|
||||
Long: `Disconnect thing to the channel`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
DisconnectThing(args[0], args[1], args[2])
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func NewThingsCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "things",
|
||||
Short: "things <options>",
|
||||
Long: `Things handling: create, delete or update things.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
LogUsage(cmd.Short)
|
||||
},
|
||||
}
|
||||
|
||||
for i, _ := range cmdThings {
|
||||
cmd.AddCommand(&cmdThings[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// CreateThing - creates new thing and generates thing UUID
|
||||
func CreateThing(data, token string) {
|
||||
url := fmt.Sprintf("%s/%s", serverAddr, thingsEP)
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data))
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// GetThings - gets all things
|
||||
func GetThings(token string) {
|
||||
url := fmt.Sprintf("%s/%s?offset=%s&limit=%s",
|
||||
serverAddr, thingsEP, strconv.Itoa(Offset), strconv.Itoa(Limit))
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// GetThing - gets thing by ID
|
||||
func GetThing(id, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, thingsEP, id)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// UpdateThing - updates thing by ID
|
||||
func UpdateThing(id, data, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, thingsEP, id)
|
||||
req, err := http.NewRequest("PUT", url, strings.NewReader(data))
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// DeleteThing - removes thing
|
||||
func DeleteThing(id, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s", serverAddr, thingsEP, id)
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// ConnectThing - connect thing to a channel
|
||||
func ConnectThing(cliId, chanId, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s", serverAddr, channelsEP,
|
||||
chanId, thingsEP, cliId)
|
||||
req, err := http.NewRequest("PUT", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
|
||||
// DisconnectThing - connect thing to a channel
|
||||
func DisconnectThing(cliId, chanId, token string) {
|
||||
url := fmt.Sprintf("%s/%s/%s/%s/%s", serverAddr, channelsEP,
|
||||
chanId, thingsEP, cliId)
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
SendRequest(req, token, err)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdUsers = []cobra.Command{
|
||||
cobra.Command{
|
||||
Use: "create",
|
||||
Short: "create <username> <password>",
|
||||
Long: `Creates new user`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
CreateUser(args[0], args[1])
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "token",
|
||||
Short: "token <username> <password>",
|
||||
Long: `Creates new token`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
LogUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
CreateToken(args[0], args[1])
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func NewUsersCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "users",
|
||||
Short: "users create/token <email> <password>",
|
||||
Long: `Manages users in the system (create account or token)`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
LogUsage(cmd.Short)
|
||||
},
|
||||
}
|
||||
|
||||
for i, _ := range cmdUsers {
|
||||
cmd.AddCommand(&cmdUsers[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// CreateUser - create user
|
||||
func CreateUser(user, pwd string) {
|
||||
msg := fmt.Sprintf(`{"email": "%s", "password": "%s"}`, user, pwd)
|
||||
url := fmt.Sprintf("%s/users", serverAddr)
|
||||
resp, err := httpClient.Post(url, contentType, strings.NewReader(msg))
|
||||
FormatResLog(resp, err)
|
||||
}
|
||||
|
||||
// CreateToken - create user token
|
||||
func CreateToken(user, pwd string) {
|
||||
msg := fmt.Sprintf(`{"email": "%s", "password": "%s"}`, user, pwd)
|
||||
url := fmt.Sprintf("%s/tokens", serverAddr)
|
||||
resp, err := httpClient.Post(url, contentType, strings.NewReader(msg))
|
||||
FormatResLog(resp, err)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/hokaccha/go-prettyjson"
|
||||
)
|
||||
|
||||
const contentType = "application/json"
|
||||
|
||||
var Limit = 10
|
||||
var Offset = 0
|
||||
|
||||
func SendRequest(req *http.Request, token string, e error) {
|
||||
req.Header.Set("Authorization", token)
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
if e != nil {
|
||||
LogError(e)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
FormatResLog(resp, err)
|
||||
}
|
||||
|
||||
// FormatResLog - format http response
|
||||
func FormatResLog(resp *http.Response, err error) {
|
||||
if err != nil {
|
||||
LogError(err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
fmt.Printf(color.CyanString("%s %s\nContent-Length: %v\n\n"),
|
||||
resp.Proto, resp.Status, resp.ContentLength)
|
||||
|
||||
if len(resp.Header.Get("Location")) != 0 {
|
||||
fmt.Printf(color.BlueString("Resource location: %s\n\n"),
|
||||
resp.Header.Get("Location"))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
LogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(body) != 0 {
|
||||
pj, err := prettyjson.Format([]byte(body))
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n\n", color.BlueString(string(body)))
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n\n", string(pj))
|
||||
}
|
||||
}
|
||||
|
||||
func LogUsage(u string) {
|
||||
fmt.Printf(color.YellowString("Usage: %s\n\n"), u)
|
||||
}
|
||||
|
||||
func LogError(err error) {
|
||||
fmt.Printf("%s\n\n", color.RedString(err.Error()))
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewVersionCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get version of Mainflux Things Service",
|
||||
Long: `Mainflux server health checkt.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Version()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Version - server health check
|
||||
func Version() {
|
||||
url := fmt.Sprintf("%s/version", serverAddr)
|
||||
FormatResLog(httpClient.Get(url))
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/gocql/gocql"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/readers"
|
||||
"github.com/mainflux/mainflux/readers/api"
|
||||
"github.com/mainflux/mainflux/readers/cassandra"
|
||||
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
sep = ","
|
||||
|
||||
defPort = "8180"
|
||||
defCluster = "127.0.0.1"
|
||||
defKeyspace = "mainflux"
|
||||
defThingsURL = "localhost:8181"
|
||||
|
||||
envPort = "MF_CASSANDRA_READER_PORT"
|
||||
envCluster = "MF_CASSANDRA_READER_DB_CLUSTER"
|
||||
envKeyspace = "MF_CASSANDRA_READER_DB_KEYSPACE"
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
port string
|
||||
cluster string
|
||||
keyspace string
|
||||
thingsURL string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
session := connectToCassandra(cfg.cluster, cfg.keyspace, logger)
|
||||
defer session.Close()
|
||||
|
||||
conn := connectToThings(cfg.thingsURL, logger)
|
||||
defer conn.Close()
|
||||
|
||||
tc := thingsapi.NewClient(conn)
|
||||
repo := newService(session, logger)
|
||||
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go startHTTPServer(repo, tc, cfg.port, errs, logger)
|
||||
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
err := <-errs
|
||||
logger.Error(fmt.Sprintf("Cassandra reader service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
port: mainflux.Env(envPort, defPort),
|
||||
cluster: mainflux.Env(envCluster, defCluster),
|
||||
keyspace: mainflux.Env(envKeyspace, defKeyspace),
|
||||
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
}
|
||||
}
|
||||
|
||||
func connectToCassandra(cluster, keyspace string, logger log.Logger) *gocql.Session {
|
||||
session, err := cassandra.Connect(strings.Split(cluster, sep), keyspace)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to Cassandra cluster: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
|
||||
conn, err := grpc.Dial(url, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func newService(session *gocql.Session, logger log.Logger) readers.MessageRepository {
|
||||
repo := cassandra.New(session)
|
||||
repo = api.LoggingMiddleware(repo, logger)
|
||||
repo = api.MetricsMiddleware(
|
||||
repo,
|
||||
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "cassandra",
|
||||
Subsystem: "message_reader",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"}),
|
||||
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "cassandra",
|
||||
Subsystem: "message_reader",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func startHTTPServer(repo readers.MessageRepository, tc mainflux.ThingsServiceClient, port string, errs chan error, logger log.Logger) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Cassandra reader service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(repo, tc, "cassandra-reader"))
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/gocql/gocql"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
"github.com/mainflux/mainflux/writers/cassandra"
|
||||
nats "github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
sep = ","
|
||||
|
||||
defNatsURL = nats.DefaultURL
|
||||
defPort = "8180"
|
||||
defCluster = "127.0.0.1"
|
||||
defKeyspace = "mainflux"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envPort = "MF_CASSANDRA_WRITER_PORT"
|
||||
envCluster = "MF_CASSANDRA_WRITER_DB_CLUSTER"
|
||||
envKeyspace = "MF_CASSANDRA_WRITER_DB_KEYSPACE"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
natsURL string
|
||||
port string
|
||||
cluster string
|
||||
keyspace string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
nc := connectToNATS(cfg.natsURL, logger)
|
||||
defer nc.Close()
|
||||
|
||||
session := connectToCassandra(cfg.cluster, cfg.keyspace, logger)
|
||||
defer session.Close()
|
||||
|
||||
repo := newService(session, logger)
|
||||
if err := writers.Start(nc, logger, repo); err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to create Cassandra writer: %s", err))
|
||||
}
|
||||
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go startHTTPServer(cfg.port, errs, logger)
|
||||
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
err := <-errs
|
||||
logger.Error(fmt.Sprintf("Cassandra writer service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
natsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
port: mainflux.Env(envPort, defPort),
|
||||
cluster: mainflux.Env(envCluster, defCluster),
|
||||
keyspace: mainflux.Env(envKeyspace, defKeyspace),
|
||||
}
|
||||
}
|
||||
|
||||
func connectToNATS(url string, logger log.Logger) *nats.Conn {
|
||||
nc, err := nats.Connect(url)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nc
|
||||
}
|
||||
|
||||
func connectToCassandra(cluster, keyspace string, logger log.Logger) *gocql.Session {
|
||||
session, err := cassandra.Connect(strings.Split(cluster, sep), keyspace)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to Cassandra cluster: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
func newService(session *gocql.Session, logger log.Logger) writers.MessageRepository {
|
||||
repo := cassandra.New(session)
|
||||
repo = writers.LoggingMiddleware(repo, logger)
|
||||
repo = writers.MetricsMiddleware(
|
||||
repo,
|
||||
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "cassandra",
|
||||
Subsystem: "message_writer",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"}),
|
||||
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "cassandra",
|
||||
Subsystem: "message_writer",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func startHTTPServer(port string, errs chan error, logger log.Logger) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Cassandra writer service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, cassandra.MakeHandler())
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/mainflux/mainflux/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
conf := struct {
|
||||
host string
|
||||
port int
|
||||
insecure bool
|
||||
}{
|
||||
"localhost",
|
||||
0,
|
||||
false,
|
||||
}
|
||||
|
||||
// Root
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "mainflux-cli",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
var proto string
|
||||
|
||||
if conf.insecure {
|
||||
proto = "http"
|
||||
} else {
|
||||
proto = "https"
|
||||
cli.SetCerts()
|
||||
}
|
||||
|
||||
cli.SetServerAddr(proto, conf.host, conf.port)
|
||||
},
|
||||
}
|
||||
|
||||
// API commands
|
||||
versionCmd := cli.NewVersionCmd()
|
||||
usersCmd := cli.NewUsersCmd()
|
||||
thingsCmd := cli.NewThingsCmd()
|
||||
channelsCmd := cli.NewChannelsCmd()
|
||||
messagesCmd := cli.NewMessagesCmd()
|
||||
|
||||
// Root Commands
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
rootCmd.AddCommand(usersCmd)
|
||||
rootCmd.AddCommand(thingsCmd)
|
||||
rootCmd.AddCommand(channelsCmd)
|
||||
rootCmd.AddCommand(messagesCmd)
|
||||
|
||||
// Root Flags
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&conf.host, "host", "m", conf.host, "HTTP Host address")
|
||||
rootCmd.PersistentFlags().IntVarP(
|
||||
&conf.port, "port", "p", conf.port, "HTTP Host Port")
|
||||
rootCmd.PersistentFlags().BoolVarP(
|
||||
&conf.insecure, "insecure", "i", false, "do not use TLS")
|
||||
|
||||
// Client and Channels Flags
|
||||
rootCmd.PersistentFlags().IntVarP(
|
||||
&cli.Limit, "limit", "l", 100, "limit query parameter")
|
||||
rootCmd.PersistentFlags().IntVarP(
|
||||
&cli.Offset, "offset", "o", 0, "offset query parameter")
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/mainflux/mainflux/coap"
|
||||
"github.com/mainflux/mainflux/coap/nats"
|
||||
|
||||
broker "github.com/nats-io/go-nats"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
port int = 5683
|
||||
defNatsURL string = broker.DefaultURL
|
||||
envNatsURL string = "COAP_ADAPTER_NATS_URL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Port int
|
||||
NatsURL string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, _ := zap.NewProduction()
|
||||
defer logger.Sync() // flushes buffer, if any
|
||||
|
||||
nc := connectToNats(cfg, logger)
|
||||
defer nc.Close()
|
||||
|
||||
pub := nats.NewMessagePublisher(nc)
|
||||
ca := adapter.NewCoAPAdapter(logger, pub)
|
||||
|
||||
nc.Subscribe("src.http", ca.BridgeHandler)
|
||||
nc.Subscribe("src.mqtt", ca.BridgeHandler)
|
||||
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go func() {
|
||||
coapAddr := fmt.Sprintf(":%d", cfg.Port)
|
||||
errs <- ca.Serve(coapAddr)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
c := <-errs
|
||||
logger.Info("terminated", zap.String("error", c.Error()))
|
||||
}
|
||||
|
||||
func loadConfig() *config {
|
||||
return &config{
|
||||
NatsURL: env(envNatsURL, defNatsURL),
|
||||
Port: port,
|
||||
}
|
||||
}
|
||||
|
||||
func env(key, fallback string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func connectToNats(cfg *config, logger *zap.Logger) *broker.Conn {
|
||||
nc, err := broker.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
logger.Error("Failed to connect to NATS", zap.Error(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nc
|
||||
}
|
||||
+30
-15
@@ -1,3 +1,10 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -13,31 +20,32 @@ import (
|
||||
"github.com/mainflux/mainflux/http/api"
|
||||
"github.com/mainflux/mainflux/http/nats"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
manager "github.com/mainflux/mainflux/manager/client"
|
||||
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
|
||||
broker "github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defPort string = "8180"
|
||||
defNatsURL string = broker.DefaultURL
|
||||
defManagerURL string = "http://localhost:8180"
|
||||
envPort string = "MF_HTTP_ADAPTER_PORT"
|
||||
envNatsURL string = "MF_NATS_URL"
|
||||
envManagerURL string = "MF_MANAGER_URL"
|
||||
defPort string = "8180"
|
||||
defNatsURL string = broker.DefaultURL
|
||||
defThingsURL string = "localhost:8181"
|
||||
envPort string = "MF_HTTP_ADAPTER_PORT"
|
||||
envNatsURL string = "MF_NATS_URL"
|
||||
envThingsURL string = "MF_THINGS_URL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
ManagerURL string
|
||||
NatsURL string
|
||||
Port string
|
||||
ThingsURL string
|
||||
NatsURL string
|
||||
Port string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
ManagerURL: mainflux.Env(envManagerURL, defManagerURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
ThingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
}
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
@@ -49,6 +57,14 @@ func main() {
|
||||
}
|
||||
defer nc.Close()
|
||||
|
||||
conn, err := grpc.Dial(cfg.ThingsURL, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
cc := thingsapi.NewClient(conn)
|
||||
pub := nats.NewMessagePublisher(nc)
|
||||
|
||||
svc := adapter.New(pub)
|
||||
@@ -73,9 +89,8 @@ func main() {
|
||||
|
||||
go func() {
|
||||
p := fmt.Sprintf(":%s", cfg.Port)
|
||||
mc := manager.NewClient(cfg.ManagerURL)
|
||||
logger.Info(fmt.Sprintf("HTTP adapter service started, exposed port %s", cfg.Port))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(svc, mc))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(svc, cc))
|
||||
}()
|
||||
|
||||
go func() {
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
influxdata "github.com/influxdata/influxdb/client/v2"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/readers"
|
||||
"github.com/mainflux/mainflux/readers/api"
|
||||
"github.com/mainflux/mainflux/readers/influxdb"
|
||||
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defThingsURL = "localhost:8181"
|
||||
defPort = "8180"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "8086"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
envPort = "MF_INFLUX_READER_PORT"
|
||||
envDBName = "MF_INFLUX_READER_DB_NAME"
|
||||
envDBHost = "MF_INFLUX_READER_DB_HOST"
|
||||
envDBPort = "MF_INFLUX_READER_DB_PORT"
|
||||
envDBUser = "MF_INFLUX_READER_DB_USER"
|
||||
envDBPass = "MF_INFLUX_READER_DB_PASS"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
ThingsURL string
|
||||
Port string
|
||||
DBName string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg, clientCfg := loadConfigs()
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
conn := connectToThings(cfg.ThingsURL, logger)
|
||||
defer conn.Close()
|
||||
|
||||
tc := thingsapi.NewClient(conn)
|
||||
|
||||
client, err := influxdata.NewHTTPClient(clientCfg)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to create InfluxDB client: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
repo, err := influxdb.New(client, cfg.DBName)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to create InfluxDB writer: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
errs := make(chan error, 2)
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
go startHTTPServer(repo, tc, cfg.Port, logger, errs)
|
||||
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("InfluxDB writer service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfigs() (config, influxdata.HTTPConfig) {
|
||||
cfg := config{
|
||||
ThingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
DBUser: mainflux.Env(envDBUser, defDBUser),
|
||||
DBPass: mainflux.Env(envDBPass, defDBPass),
|
||||
}
|
||||
|
||||
clientCfg := influxdata.HTTPConfig{
|
||||
Addr: fmt.Sprintf("http://%s:%s", cfg.DBHost, cfg.DBPort),
|
||||
Username: cfg.DBUser,
|
||||
Password: cfg.DBPass,
|
||||
}
|
||||
|
||||
return cfg, clientCfg
|
||||
}
|
||||
|
||||
func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
|
||||
conn, err := grpc.Dial(url, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func newService(client influxdata.Client, logger log.Logger) readers.MessageRepository {
|
||||
repo, _ := influxdb.New(client, "mainflux")
|
||||
repo = api.LoggingMiddleware(repo, logger)
|
||||
repo = api.MetricsMiddleware(
|
||||
repo,
|
||||
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "influxdb",
|
||||
Subsystem: "message_reader",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"}),
|
||||
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "influxdb",
|
||||
Subsystem: "message_reader",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func startHTTPServer(repo readers.MessageRepository, tc mainflux.ThingsServiceClient, port string, logger log.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("InfluxDB reader service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(repo, tc, "influxdb-reader"))
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
influxdata "github.com/influxdata/influxdb/client/v2"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
"github.com/mainflux/mainflux/writers/influxdb"
|
||||
nats "github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
defNatsURL = nats.DefaultURL
|
||||
defPort = "8180"
|
||||
defPointName = "messages"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "8086"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envPort = "MF_INFLUX_WRITER_PORT"
|
||||
envDBName = "MF_INFLUX_WRITER_DB_NAME"
|
||||
envDBHost = "MF_INFLUX_WRITER_DB_HOST"
|
||||
envDBPort = "MF_INFLUX_WRITER_DB_PORT"
|
||||
envDBUser = "MF_INFLUX_WRITER_DB_USER"
|
||||
envDBPass = "MF_INFLUX_WRITER_DB_PASS"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
NatsURL string
|
||||
Port string
|
||||
DBName string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg, clientCfg := loadConfigs()
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
nc, err := nats.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer nc.Close()
|
||||
|
||||
client, err := influxdata.NewHTTPClient(clientCfg)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to create InfluxDB client: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
repo, err := influxdb.New(client, cfg.DBName)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to create InfluxDB writer: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
counter, latency := makeMetrics()
|
||||
repo = writers.LoggingMiddleware(repo, logger)
|
||||
repo = writers.MetricsMiddleware(repo, counter, latency)
|
||||
if err := writers.Start(nc, logger, repo); err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to start message writer: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
errs := make(chan error, 2)
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
go startHTTPService(cfg.Port, logger, errs)
|
||||
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("InfluxDB writer service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfigs() (config, influxdata.HTTPConfig) {
|
||||
cfg := config{
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
DBUser: mainflux.Env(envDBUser, defDBUser),
|
||||
DBPass: mainflux.Env(envDBPass, defDBPass),
|
||||
}
|
||||
|
||||
clientCfg := influxdata.HTTPConfig{
|
||||
Addr: fmt.Sprintf("http://%s:%s", cfg.DBHost, cfg.DBPort),
|
||||
Username: cfg.DBUser,
|
||||
Password: cfg.DBPass,
|
||||
}
|
||||
|
||||
return cfg, clientCfg
|
||||
}
|
||||
|
||||
func makeMetrics() (*kitprometheus.Counter, *kitprometheus.Summary) {
|
||||
counter := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "influxdb",
|
||||
Subsystem: "message_writer",
|
||||
Name: "request_count",
|
||||
Help: "Number of database inserts.",
|
||||
}, []string{"method"})
|
||||
|
||||
latency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "influxdb",
|
||||
Subsystem: "message_writer",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of inserts in microseconds.",
|
||||
}, []string{"method"})
|
||||
|
||||
return counter, latency
|
||||
}
|
||||
|
||||
func startHTTPService(port string, logger log.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Influxdb writer service started, exposed port %s", p))
|
||||
errs <- http.ListenAndServe(p, influxdb.MakeHandler())
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/manager"
|
||||
"github.com/mainflux/mainflux/manager/api"
|
||||
"github.com/mainflux/mainflux/manager/bcrypt"
|
||||
"github.com/mainflux/mainflux/manager/jwt"
|
||||
"github.com/mainflux/mainflux/manager/postgres"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
defDBHost string = "localhost"
|
||||
defDBPort string = "5432"
|
||||
defDBUser string = "mainflux"
|
||||
defDBPass string = "mainflux"
|
||||
defDBName string = "manager"
|
||||
defPort string = "8180"
|
||||
defSecret string = "manager"
|
||||
envDBHost string = "MF_DB_HOST"
|
||||
envDBPort string = "MF_DB_PORT"
|
||||
envDBUser string = "MF_DB_USER"
|
||||
envDBPass string = "MF_DB_PASS"
|
||||
envDBName string = "MF_MANAGER_DB"
|
||||
envPort string = "MF_MANAGER_PORT"
|
||||
envSecret string = "MF_MANAGER_SECRET"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
DBName string
|
||||
Port string
|
||||
Secret string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
DBUser: mainflux.Env(envDBUser, defDBUser),
|
||||
DBPass: mainflux.Env(envDBPass, defDBPass),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
Secret: mainflux.Env(envSecret, defSecret),
|
||||
}
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
db, err := postgres.Connect(cfg.DBHost, cfg.DBPort, cfg.DBName, cfg.DBUser, cfg.DBPass)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
users := postgres.NewUserRepository(db)
|
||||
clients := postgres.NewClientRepository(db)
|
||||
channels := postgres.NewChannelRepository(db)
|
||||
hasher := bcrypt.New()
|
||||
idp := jwt.New(cfg.Secret)
|
||||
|
||||
svc := manager.New(users, clients, channels, hasher, idp)
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
svc = api.MetricsMiddleware(
|
||||
svc,
|
||||
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "manager",
|
||||
Subsystem: "api",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"}),
|
||||
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "manager",
|
||||
Subsystem: "api",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go func() {
|
||||
p := fmt.Sprintf(":%s", cfg.Port)
|
||||
logger.Info(fmt.Sprintf("Manager service started, exposed port %s", cfg.Port))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(svc))
|
||||
}()
|
||||
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("Manager service terminated: %s", err))
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/readers"
|
||||
"github.com/mainflux/mainflux/readers/api"
|
||||
"github.com/mainflux/mainflux/readers/mongodb"
|
||||
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
|
||||
"github.com/mongodb/mongo-go-driver/mongo"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defThingsURL = "localhost:8181"
|
||||
defPort = "8180"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "27017"
|
||||
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
envPort = "MF_MONGO_READER_PORT"
|
||||
envDBName = "MF_MONGO_READER_DB_NAME"
|
||||
envDBHost = "MF_MONGO_READER_DB_HOST"
|
||||
envDBPort = "MF_MONGO_READER_DB_PORT"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
thingsURL string
|
||||
port string
|
||||
dbName string
|
||||
dbHost string
|
||||
dbPort string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfigs()
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
conn := connectToThings(cfg.thingsURL, logger)
|
||||
defer conn.Close()
|
||||
|
||||
tc := thingsapi.NewClient(conn)
|
||||
|
||||
db := connectToMongoDB(cfg.dbHost, cfg.dbPort, cfg.dbName, logger)
|
||||
|
||||
repo := newService(db, logger)
|
||||
|
||||
errs := make(chan error, 2)
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
go startHTTPServer(repo, tc, cfg.port, logger, errs)
|
||||
|
||||
err := <-errs
|
||||
logger.Error(fmt.Sprintf("MongoDB reader service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfigs() config {
|
||||
return config{
|
||||
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
port: mainflux.Env(envPort, defPort),
|
||||
dbName: mainflux.Env(envDBName, defDBName),
|
||||
dbHost: mainflux.Env(envDBHost, defDBHost),
|
||||
dbPort: mainflux.Env(envDBPort, defDBPort),
|
||||
}
|
||||
}
|
||||
|
||||
func connectToMongoDB(host, port, name string, logger log.Logger) *mongo.Database {
|
||||
client, err := mongo.Connect(context.Background(), fmt.Sprintf("mongodb://%s:%s", host, port), nil)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to database: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return client.Database(name)
|
||||
}
|
||||
|
||||
func connectToThings(url string, logger log.Logger) *grpc.ClientConn {
|
||||
conn, err := grpc.Dial(url, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to things service: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func newService(db *mongo.Database, logger log.Logger) readers.MessageRepository {
|
||||
repo := mongodb.New(db)
|
||||
repo = api.LoggingMiddleware(repo, logger)
|
||||
repo = api.MetricsMiddleware(
|
||||
repo,
|
||||
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "mongodb",
|
||||
Subsystem: "message_reader",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"}),
|
||||
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "mongodb",
|
||||
Subsystem: "message_reader",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func startHTTPServer(repo readers.MessageRepository, tc mainflux.ThingsServiceClient, port string, logger log.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Mongo reader service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(repo, tc, "cassandra-reader"))
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
mongodb "github.com/mainflux/mainflux/writers/mongodb"
|
||||
"github.com/mongodb/mongo-go-driver/mongo"
|
||||
nats "github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
defNatsURL = nats.DefaultURL
|
||||
defPort = "8180"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "27017"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envPort = "MF_MONGO_WRITER_PORT"
|
||||
envDBName = "MF_MONGO_WRITER_DB_NAME"
|
||||
envDBHost = "MF_MONGO_WRITER_DB_HOST"
|
||||
envDBPort = "MF_MONGO_WRITER_DB_PORT"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
NatsURL string
|
||||
Port string
|
||||
DBName string
|
||||
DBHost string
|
||||
DBPort string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfigs()
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
nc, err := nats.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer nc.Close()
|
||||
|
||||
client, err := mongo.Connect(context.Background(), fmt.Sprintf("mongodb://%s:%s", cfg.DBHost, cfg.DBPort), nil)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to database: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
db := client.Database(cfg.DBName)
|
||||
repo := mongodb.New(db)
|
||||
|
||||
counter, latency := makeMetrics()
|
||||
repo = writers.LoggingMiddleware(repo, logger)
|
||||
repo = writers.MetricsMiddleware(repo, counter, latency)
|
||||
if err := writers.Start(nc, logger, repo); err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to start message writer: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
errs := make(chan error, 2)
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
go startHTTPService(cfg.Port, logger, errs)
|
||||
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("MongoDB writer service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfigs() config {
|
||||
return config{
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
}
|
||||
}
|
||||
|
||||
func makeMetrics() (*kitprometheus.Counter, *kitprometheus.Summary) {
|
||||
counter := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "mongodb",
|
||||
Subsystem: "message_writer",
|
||||
Name: "request_count",
|
||||
Help: "Number of database inserts.",
|
||||
}, []string{"method"})
|
||||
|
||||
latency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "mongodb",
|
||||
Subsystem: "message_writer",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of inserts in microseconds.",
|
||||
}, []string{"method"})
|
||||
|
||||
return counter, latency
|
||||
}
|
||||
|
||||
func startHTTPService(port string, logger log.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Mongodb writer service started, exposed port %s", p))
|
||||
errs <- http.ListenAndServe(p, mongodb.MakeHandler())
|
||||
}
|
||||
+32
-19
@@ -1,3 +1,10 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -10,14 +17,16 @@ import (
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/normalizer"
|
||||
nats "github.com/nats-io/go-nats"
|
||||
"github.com/mainflux/mainflux/normalizer/api"
|
||||
"github.com/mainflux/mainflux/normalizer/nats"
|
||||
broker "github.com/nats-io/go-nats"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
defNatsURL string = nats.DefaultURL
|
||||
defNatsURL string = broker.DefaultURL
|
||||
defPort string = "8180"
|
||||
envNatsURL string = "MF_NATS_URL"
|
||||
envPort string = "MF_NORMALIZER_PORT"
|
||||
@@ -36,19 +45,37 @@ func main() {
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
nc, err := nats.Connect(cfg.NatsURL)
|
||||
nc, err := broker.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer nc.Close()
|
||||
|
||||
svc := normalizer.New()
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
svc = api.MetricsMiddleware(
|
||||
svc,
|
||||
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "normalizer",
|
||||
Subsystem: "api",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"}),
|
||||
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "normalizer",
|
||||
Subsystem: "api",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go func() {
|
||||
p := fmt.Sprintf(":%s", cfg.Port)
|
||||
logger.Info(fmt.Sprintf("Normalizer service started, exposed port %s", cfg.Port))
|
||||
errs <- http.ListenAndServe(p, normalizer.MakeHandler())
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler())
|
||||
}()
|
||||
|
||||
go func() {
|
||||
@@ -57,21 +84,7 @@ func main() {
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
counter := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "normalizer",
|
||||
Subsystem: "api",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"})
|
||||
|
||||
latency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "normalizer",
|
||||
Subsystem: "api",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"})
|
||||
|
||||
normalizer.Subscribe(nc, logger, counter, latency)
|
||||
nats.Subscribe(svc, nc, logger)
|
||||
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("Normalizer service terminated: %s", err))
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/things"
|
||||
"github.com/mainflux/mainflux/things/api"
|
||||
grpcapi "github.com/mainflux/mainflux/things/api/grpc"
|
||||
httpapi "github.com/mainflux/mainflux/things/api/http"
|
||||
"github.com/mainflux/mainflux/things/postgres"
|
||||
"github.com/mainflux/mainflux/things/uuid"
|
||||
usersapi "github.com/mainflux/mainflux/users/api/grpc"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "5432"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
defDBName = "things"
|
||||
defHTTPPort = "8180"
|
||||
defGRPCPort = "8181"
|
||||
defUsersURL = "localhost:8181"
|
||||
envDBHost = "MF_THINGS_DB_HOST"
|
||||
envDBPort = "MF_THINGS_DB_PORT"
|
||||
envDBUser = "MF_THINGS_DB_USER"
|
||||
envDBPass = "MF_THINGS_DB_PASS"
|
||||
envDBName = "MF_THINGS_DB"
|
||||
envHTTPPort = "MF_THINGS_HTTP_PORT"
|
||||
envGRPCPort = "MF_THINGS_GRPC_PORT"
|
||||
envUsersURL = "MF_USERS_URL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
DBName string
|
||||
HTTPPort string
|
||||
GRPCPort string
|
||||
UsersURL string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
db := connectToDB(cfg, logger)
|
||||
defer db.Close()
|
||||
|
||||
conn := connectToUsersService(cfg.UsersURL, logger)
|
||||
defer conn.Close()
|
||||
|
||||
svc := newService(conn, db, logger)
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go startHTTPServer(svc, cfg.HTTPPort, logger, errs)
|
||||
go startGRPCServer(svc, cfg.GRPCPort, logger, errs)
|
||||
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
err := <-errs
|
||||
logger.Error(fmt.Sprintf("Things service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
DBUser: mainflux.Env(envDBUser, defDBUser),
|
||||
DBPass: mainflux.Env(envDBPass, defDBPass),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
HTTPPort: mainflux.Env(envHTTPPort, defHTTPPort),
|
||||
GRPCPort: mainflux.Env(envGRPCPort, defGRPCPort),
|
||||
UsersURL: mainflux.Env(envUsersURL, defUsersURL),
|
||||
}
|
||||
}
|
||||
|
||||
func connectToDB(cfg config, logger log.Logger) *sql.DB {
|
||||
db, err := postgres.Connect(cfg.DBHost, cfg.DBPort, cfg.DBName, cfg.DBUser, cfg.DBPass)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func connectToUsersService(usersAddr string, logger log.Logger) *grpc.ClientConn {
|
||||
conn, err := grpc.Dial(usersAddr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to users service: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func newService(conn *grpc.ClientConn, db *sql.DB, logger log.Logger) things.Service {
|
||||
users := usersapi.NewClient(conn)
|
||||
thingsRepo := postgres.NewThingRepository(db, logger)
|
||||
channelsRepo := postgres.NewChannelRepository(db, logger)
|
||||
idp := uuid.New()
|
||||
|
||||
svc := things.New(users, thingsRepo, channelsRepo, idp)
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
svc = api.MetricsMiddleware(
|
||||
svc,
|
||||
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "things",
|
||||
Subsystem: "api",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"}),
|
||||
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "things",
|
||||
Subsystem: "api",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
return svc
|
||||
}
|
||||
|
||||
func startHTTPServer(svc things.Service, port string, logger log.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Things service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, httpapi.MakeHandler(svc))
|
||||
}
|
||||
|
||||
func startGRPCServer(svc things.Service, port string, logger log.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
listener, err := net.Listen("tcp", p)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to listen on port %s: %s", port, err))
|
||||
}
|
||||
server := grpc.NewServer()
|
||||
mainflux.RegisterThingsServiceServer(server, grpcapi.NewServer(svc))
|
||||
logger.Info(fmt.Sprintf("Things gRPC service started, exposed port %s", port))
|
||||
errs <- server.Serve(listener)
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/users"
|
||||
"github.com/mainflux/mainflux/users/api"
|
||||
grpcapi "github.com/mainflux/mainflux/users/api/grpc"
|
||||
httpapi "github.com/mainflux/mainflux/users/api/http"
|
||||
"github.com/mainflux/mainflux/users/bcrypt"
|
||||
"github.com/mainflux/mainflux/users/jwt"
|
||||
"github.com/mainflux/mainflux/users/postgres"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "5432"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
defDBName = "users"
|
||||
defHTTPPort = "8180"
|
||||
defGRPCPort = "8181"
|
||||
defSecret = "users"
|
||||
envDBHost = "MF_USERS_DB_HOST"
|
||||
envDBPort = "MF_USERS_DB_PORT"
|
||||
envDBUser = "MF_USERS_DB_USER"
|
||||
envDBPass = "MF_USERS_DB_PASS"
|
||||
envDBName = "MF_USERS_DB"
|
||||
envHTTPPort = "MF_USERS_HTTP_PORT"
|
||||
envGRPCPort = "MF_USERS_GRPC_PORT"
|
||||
envSecret = "MF_USERS_SECRET"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
DBName string
|
||||
HTTPPort string
|
||||
GRPCPort string
|
||||
Secret string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
db := connectToDB(cfg, logger)
|
||||
defer db.Close()
|
||||
|
||||
svc := newService(db, cfg.Secret, logger)
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go startHTTPServer(svc, cfg.HTTPPort, logger, errs)
|
||||
go startGRPCServer(svc, cfg.GRPCPort, logger, errs)
|
||||
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
err := <-errs
|
||||
logger.Error(fmt.Sprintf("Users service terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
DBHost: mainflux.Env(envDBHost, defDBHost),
|
||||
DBPort: mainflux.Env(envDBPort, defDBPort),
|
||||
DBUser: mainflux.Env(envDBUser, defDBUser),
|
||||
DBPass: mainflux.Env(envDBPass, defDBPass),
|
||||
DBName: mainflux.Env(envDBName, defDBName),
|
||||
HTTPPort: mainflux.Env(envHTTPPort, defHTTPPort),
|
||||
GRPCPort: mainflux.Env(envGRPCPort, defGRPCPort),
|
||||
Secret: mainflux.Env(envSecret, defSecret),
|
||||
}
|
||||
}
|
||||
|
||||
func connectToDB(cfg config, logger log.Logger) *sql.DB {
|
||||
db, err := postgres.Connect(cfg.DBHost, cfg.DBPort, cfg.DBName, cfg.DBUser, cfg.DBPass)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to postgres: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func newService(db *sql.DB, secret string, logger log.Logger) users.Service {
|
||||
repo := postgres.New(db)
|
||||
hasher := bcrypt.New()
|
||||
idp := jwt.New(secret)
|
||||
|
||||
svc := users.New(repo, hasher, idp)
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
svc = api.MetricsMiddleware(
|
||||
svc,
|
||||
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "users",
|
||||
Subsystem: "api",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"}),
|
||||
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "users",
|
||||
Subsystem: "api",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
return svc
|
||||
}
|
||||
|
||||
func startHTTPServer(svc users.Service, port string, logger log.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Users HTTP service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, httpapi.MakeHandler(svc, logger))
|
||||
}
|
||||
|
||||
func startGRPCServer(svc users.Service, port string, logger log.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
listener, err := net.Listen("tcp", p)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to listen on port %s: %s", port, err))
|
||||
}
|
||||
server := grpc.NewServer()
|
||||
mainflux.RegisterUsersServiceServer(server, grpcapi.NewServer(svc))
|
||||
logger.Info(fmt.Sprintf("Users gRPC service started, exposed port %s", port))
|
||||
errs <- server.Serve(listener)
|
||||
}
|
||||
+30
-15
@@ -1,3 +1,10 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -10,34 +17,35 @@ import (
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
manager "github.com/mainflux/mainflux/manager/client"
|
||||
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
|
||||
adapter "github.com/mainflux/mainflux/ws"
|
||||
"github.com/mainflux/mainflux/ws/api"
|
||||
"github.com/mainflux/mainflux/ws/nats"
|
||||
broker "github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defPort = "8180"
|
||||
defNatsURL = broker.DefaultURL
|
||||
defManagerURL = "http://localhost:8180"
|
||||
envPort = "MF_WS_ADAPTER_PORT"
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envManagerURL = "MF_MANAGER_URL"
|
||||
defPort = "8180"
|
||||
defNatsURL = broker.DefaultURL
|
||||
defThingsURL = "localhost:8181"
|
||||
envPort = "MF_WS_ADAPTER_PORT"
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
ManagerURL string
|
||||
NatsURL string
|
||||
Port string
|
||||
ThingsURL string
|
||||
NatsURL string
|
||||
Port string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
ManagerURL: mainflux.Env(envManagerURL, defManagerURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
ThingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
}
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
@@ -49,6 +57,14 @@ func main() {
|
||||
}
|
||||
defer nc.Close()
|
||||
|
||||
conn, err := grpc.Dial(cfg.ThingsURL, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to users service: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
cc := thingsapi.NewClient(conn)
|
||||
pubsub := nats.New(nc)
|
||||
svc := adapter.New(pubsub)
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
@@ -72,9 +88,8 @@ func main() {
|
||||
|
||||
go func() {
|
||||
p := fmt.Sprintf(":%s", cfg.Port)
|
||||
mc := manager.NewClient(cfg.ManagerURL)
|
||||
logger.Info(fmt.Sprintf("WebSocket adapter service started, exposed port %s", cfg.Port))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(svc, mc, logger))
|
||||
errs <- http.ListenAndServe(p, api.MakeHandler(svc, cc, logger))
|
||||
}()
|
||||
|
||||
go func() {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# Mainflux CoAP Adapter
|
||||
|
||||
Mainflux CoAP adapter provides an [CoAP](http://coap.technology/) API for sending messages through the
|
||||
platform.
|
||||
|
||||
## Configuration
|
||||
|
||||
The service is configured using the environment variables presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|-----------------------|-------------------|-----------------------|
|
||||
| COAP_ADAPTER_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
|
||||
## Deployment
|
||||
|
||||
The service is distributed as Docker container. The following snippet provides
|
||||
a compose file template that can be used to deploy the service container locally:
|
||||
|
||||
```yaml
|
||||
version: "2"
|
||||
services:
|
||||
adapter:
|
||||
image: mainflux/coap-adapter:[version]
|
||||
container_name: [instance name]
|
||||
ports:
|
||||
- [host machine port]:5683
|
||||
environment:
|
||||
COAP_ADAPTER_NATS_URL: [NATS instance URL]
|
||||
```
|
||||
|
||||
To start the service outside of the container, execute the following shell script:
|
||||
|
||||
```bash
|
||||
# download the latest version of the service
|
||||
go get github.com/mainflux/mainflux
|
||||
|
||||
cd $GOPATH/src/github.com/mainflux/mainflux/cmd/coap
|
||||
|
||||
# compile the app; make sure to set the proper GOOS value
|
||||
CGO_ENABLED=0 GOOS=[platform identifier] go build -ldflags "-s" -a -installsuffix cgo -o app
|
||||
|
||||
# set the environment variables and run the service
|
||||
COAP_ADAPTER_NATS_URL=[NATS instance URL] app
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
For more information about service capabilities and its usage, please check out
|
||||
the [API documentation](swagger.yaml).
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/dustin/go-coap"
|
||||
"github.com/mainflux/mainflux"
|
||||
broker "github.com/nats-io/go-nats"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const protocol string = "coap"
|
||||
|
||||
type Observer struct {
|
||||
conn *net.UDPConn
|
||||
addr *net.UDPAddr
|
||||
message *coap.Message
|
||||
}
|
||||
|
||||
type CoAPAdapter struct {
|
||||
obsMap map[string][]Observer
|
||||
logger *zap.Logger
|
||||
pub mainflux.MessagePublisher
|
||||
}
|
||||
|
||||
// NewCoAPAdapter creates new CoAP adapter struct
|
||||
func NewCoAPAdapter(logger *zap.Logger, pub mainflux.MessagePublisher) *CoAPAdapter {
|
||||
ca := &CoAPAdapter{
|
||||
logger: logger,
|
||||
pub: pub,
|
||||
obsMap: make(map[string][]Observer),
|
||||
}
|
||||
|
||||
return ca
|
||||
}
|
||||
|
||||
// Serve function starts CoAP server
|
||||
func (ca *CoAPAdapter) Serve(addr string) error {
|
||||
ca.logger.Info("Starting CoAP server", zap.String("address", addr))
|
||||
return coap.ListenAndServe("udp", addr, ca.COAPServer())
|
||||
}
|
||||
|
||||
// BridgeHandler functions is a handler for messages received via NATS
|
||||
func (ca *CoAPAdapter) BridgeHandler(nm *broker.Msg) {
|
||||
log.Printf("Received a message: %s\n", string(nm.Data))
|
||||
|
||||
// And write it into the database
|
||||
m := mainflux.RawMessage{}
|
||||
if len(nm.Data) > 0 {
|
||||
if err := json.Unmarshal(nm.Data, &m); err != nil {
|
||||
log.Println("Can not decode adapter msg")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Calling obsTransmit()")
|
||||
log.Println(m.Publisher, m.Protocol, m.Channel, m.Payload)
|
||||
ca.obsTransmit(m)
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
mux "github.com/dereulenspiegel/coap-mux"
|
||||
coap "github.com/dustin/go-coap"
|
||||
"github.com/mainflux/mainflux"
|
||||
)
|
||||
|
||||
func (ca *CoAPAdapter) sendMessage(l *net.UDPConn, a *net.UDPAddr, m *coap.Message) *coap.Message {
|
||||
log.Printf("Got message in sendMessage: path=%q: %#v from %v", m.Path(), m, a)
|
||||
var res *coap.Message
|
||||
if m.IsConfirmable() {
|
||||
res = &coap.Message{
|
||||
Type: coap.Acknowledgement,
|
||||
Code: coap.Content,
|
||||
MessageID: m.MessageID,
|
||||
Token: m.Token,
|
||||
Payload: []byte(""),
|
||||
}
|
||||
res.SetOption(coap.ContentFormat, coap.AppJSON)
|
||||
}
|
||||
|
||||
if len(m.Payload) == 0 {
|
||||
if m.IsConfirmable() {
|
||||
res.Code = coap.BadRequest
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Channel ID
|
||||
cid := mux.Var(m, "channel_id")
|
||||
|
||||
n := mainflux.RawMessage{
|
||||
Channel: cid,
|
||||
Publisher: "",
|
||||
Protocol: protocol,
|
||||
Payload: m.Payload,
|
||||
}
|
||||
|
||||
if err := ca.pub.Publish(n); err != nil {
|
||||
if m.IsConfirmable() {
|
||||
res.Code = coap.InternalServerError
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
if m.IsConfirmable() {
|
||||
res.Code = coap.Changed
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (ca *CoAPAdapter) registerObserver(o Observer, cid string) {
|
||||
found := false
|
||||
for _, v := range ca.obsMap[cid] {
|
||||
if v.addr == o.addr && bytes.Compare(v.message.Token, o.message.Token) == 0 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Println("Register " + cid)
|
||||
log.Printf("o.message = %v", o.message)
|
||||
ca.obsMap[cid] = append(ca.obsMap[cid], o)
|
||||
}
|
||||
}
|
||||
|
||||
func (ca *CoAPAdapter) deregisterObserver(o Observer, cid string) {
|
||||
for k, v := range ca.obsMap[cid] {
|
||||
if bytes.Compare(v.message.Token, o.message.Token) == 0 {
|
||||
// Observer found, remove it from array
|
||||
log.Println("Deregister " + cid)
|
||||
ca.obsMap[cid] = append((ca.obsMap[cid])[:k], (ca.obsMap[cid])[k+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ca *CoAPAdapter) observeMessage(l *net.UDPConn, a *net.UDPAddr, m *coap.Message) *coap.Message {
|
||||
log.Printf("Got message in observeMessage: path=%q: %#v from %v", m.Path(), m, a)
|
||||
var res *coap.Message
|
||||
|
||||
if m.IsConfirmable() {
|
||||
res = &coap.Message{
|
||||
Type: coap.Acknowledgement,
|
||||
Code: coap.Content,
|
||||
MessageID: m.MessageID,
|
||||
Token: m.Token,
|
||||
Payload: []byte(""),
|
||||
}
|
||||
res.SetOption(coap.ContentFormat, coap.AppJSON)
|
||||
}
|
||||
|
||||
// Channel ID
|
||||
cid := mux.Var(m, "channel_id")
|
||||
|
||||
// Observer
|
||||
o := Observer{
|
||||
conn: l,
|
||||
addr: a,
|
||||
message: m,
|
||||
}
|
||||
|
||||
if m.Option(coap.Observe) == nil {
|
||||
if m.IsConfirmable() {
|
||||
res.Code = coap.BadRequest
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
if value, ok := m.Option(coap.Observe).(uint32); ok && value == 0 {
|
||||
ca.registerObserver(o, cid)
|
||||
} else {
|
||||
ca.deregisterObserver(o, cid)
|
||||
}
|
||||
|
||||
if m.IsConfirmable() {
|
||||
res.Code = coap.Valid
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (ca *CoAPAdapter) obsTransmit(n mainflux.RawMessage) {
|
||||
for _, v := range ca.obsMap[n.Channel] {
|
||||
msg := *(v.message)
|
||||
msg.Payload = n.Payload
|
||||
|
||||
log.Printf("ca.obsMap[cid] = %v", v)
|
||||
log.Printf("msg = %v", msg)
|
||||
|
||||
msg.SetOption(coap.ContentFormat, coap.AppJSON)
|
||||
msg.SetOption(coap.LocationPath, msg.Path())
|
||||
|
||||
log.Printf("Transmitting %v", msg)
|
||||
err := coap.Transmit(v.conn, v.addr, msg)
|
||||
if err != nil {
|
||||
log.Printf("Error on transmitter, stopping: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Package nats contains NATS-specific message repository implementation.
|
||||
package nats
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/mainflux/mainflux"
|
||||
broker "github.com/nats-io/go-nats"
|
||||
)
|
||||
|
||||
const topic string = "src.coap"
|
||||
|
||||
var _ mainflux.MessagePublisher = (*natsPublisher)(nil)
|
||||
|
||||
type natsPublisher struct {
|
||||
nc *broker.Conn
|
||||
}
|
||||
|
||||
// NewMessagePublisher instantiates NATS message publisher.
|
||||
func NewMessagePublisher(nc *broker.Conn) mainflux.MessagePublisher {
|
||||
return &natsPublisher{nc}
|
||||
}
|
||||
|
||||
func (pub *natsPublisher) Publish(msg mainflux.RawMessage) error {
|
||||
data, err := proto.Marshal(&msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return pub.nc.Publish(topic, data)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/dereulenspiegel/coap-mux"
|
||||
"github.com/dustin/go-coap"
|
||||
)
|
||||
|
||||
func notFoundHandler(l *net.UDPConn, a *net.UDPAddr, m *coap.Message) *coap.Message {
|
||||
log.Printf("Got message in notFoundHandler: path=%q: %#v from %v", m.Path(), m, a)
|
||||
if m.IsConfirmable() {
|
||||
return &coap.Message{
|
||||
Type: coap.Acknowledgement,
|
||||
Code: coap.NotFound,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ca *CoAPAdapter) COAPServer() *mux.Router {
|
||||
r := mux.NewRouter()
|
||||
r.Handle("/channels/{channel_id}/messages", coap.FuncHandler(ca.sendMessage)).Methods(coap.POST)
|
||||
r.Handle("/channels/{channel_id}/messages", coap.FuncHandler(ca.observeMessage)).Methods(coap.GET)
|
||||
|
||||
r.NotFoundHandler = coap.FuncHandler(notFoundHandler)
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"project": {
|
||||
"name": "mainflux-ui"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"root": "src",
|
||||
"outDir": "dist",
|
||||
"assets": [
|
||||
"assets",
|
||||
"favicon.png"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "main.ts",
|
||||
"polyfills": "polyfills.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.app.json",
|
||||
"testTsconfig": "tsconfig.spec.json",
|
||||
"prefix": "app",
|
||||
"styles": [
|
||||
"styles.scss"
|
||||
],
|
||||
"scripts": [],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": [
|
||||
{
|
||||
"project": "src/tsconfig.app.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "src/tsconfig.spec.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "e2e/tsconfig.e2e.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
}
|
||||
],
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "scss",
|
||||
"component": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,58 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/bower_components
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
/.vscode
|
||||
/out-tsc
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage/*
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# e2e
|
||||
/e2e/*.js
|
||||
/e2e/*.map
|
||||
|
||||
#System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
#json-server database
|
||||
db.json
|
||||
db.*
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Mainflux UI Dashboad
|
||||
|
||||
Mainflux UI, dashboard for [Mainflux](https://github.com/mainflux/mainflux) Industrial IoT Messaging and Device Management Server.
|
||||
|
||||
> **N.B.** Mainflux UI service is WIP and not suitable for deployment at this moment. You are welcome to contribute and improve it.
|
||||
> ## Development
|
||||
>- Follow angular-cli [documentation](https://github.com/angular/angular-cli)
|
||||
>- Follow [official angular style guide](https://angular.io/styleguide)
|
||||
|
||||
## Requirements
|
||||
|
||||
You'll need the following software installed to get started.
|
||||
|
||||
- [Node](https://nodejs.org/en/) 6 or higher, we recommend current LTS version, together with NPM 3 or higher.
|
||||
- [Angular-cli](https://github.com/angular/angular-cli) Newest version with Webpack integration
|
||||
- - Depending on how Node is configured on your machine, you may need to run installation command with `sudo`
|
||||
- [Git](http://git-scm.com/downloads): Use the installer for your OS.
|
||||
- Windows users can also try [Git for Windows](http://git-for-windows.github.io/).
|
||||
- For local Development with [Mainflux composition](https://github.com/mainflux/mainflux) running locally, [Chrome extension for Cross origin](https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi?utm_source=chrome-app-launcher-info-dialog) is required. Because composition is running on different port then our Angular app, we have cross origin.
|
||||
|
||||
## Configuration
|
||||
|
||||
Change into the directory.
|
||||
|
||||
```bash
|
||||
cd dashflux
|
||||
```
|
||||
|
||||
Install the dependencies. If you're running Mac OS or Linux, you may need to run `sudo npm install` instead, depending on how your machine is configured.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Set appropriate endpoint URLs in **environment.ts** (for local development will probably be 0.0.0.0:<_port_>) or **environment.prod.ts** for production.
|
||||
|
||||
To start the server, run:
|
||||
|
||||
```bash
|
||||
ng serve
|
||||
```
|
||||
|
||||
This will run and assemble our app.
|
||||
**Now go to `localhost:4200` in your browser to see it in action.**
|
||||
|
||||
## Deployment
|
||||
|
||||
Dashflux is distributed as Docker container. We use nginx to serve dashflux from docker container, supporting environments using docker multi-stage builds.
|
||||
Dashflux docker image is available on [Dockerhub mainflux/dashflux](https://hub.docker.com/r/mainflux/dashflux/)
|
||||
|
||||
If you want to build image locally, you can build image using the **development** environment:
|
||||
|
||||
```bash
|
||||
docker build -f docker/Dockerfile -t dashflux:dev --build-arg env=dev .
|
||||
```
|
||||
|
||||
Build image using the **production** environment:
|
||||
|
||||
```bash
|
||||
docker build -t dashflux:prod -f ./docker/Dockerfile .
|
||||
```
|
||||
|
||||
**Note:** before running *docker build* command, please make sure appropriate endpoint URLs in *environment.ts* or *environment.prod.ts* are set up.
|
||||
|
||||
You can test image running
|
||||
|
||||
```bash
|
||||
docker run -p 80:80 dashflux:dev
|
||||
```
|
||||
|
||||
This will run dashflux in docker container.
|
||||
|
||||
Now go to `http://localhost` in your browser to see it in action.
|
||||
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
@@ -0,0 +1,13 @@
|
||||
# Stage 0, based on Node.js, to build and compile Angular
|
||||
FROM node:9.5 as node
|
||||
WORKDIR /app
|
||||
COPY package.json /app/
|
||||
RUN npm install
|
||||
COPY ./ /app/
|
||||
ARG env=prod
|
||||
RUN npm run build -- --prod --environment $env
|
||||
|
||||
# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx
|
||||
FROM nginx:1.13-alpine
|
||||
COPY --from=node /app/dist/ /usr/share/nginx/html
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
@@ -0,0 +1,8 @@
|
||||
server {
|
||||
listen 3000;
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { AppPage } from './app.po';
|
||||
|
||||
describe('mainflux-ui App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getParagraphText()).toEqual('Welcome to app!');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"baseUrl": "./",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular/cli'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular/cli/plugins/karma')
|
||||
],
|
||||
client:{
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
reports: [ 'html', 'lcovonly' ],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
angularCli: {
|
||||
environment: 'dev'
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['ChromeHeadless'],
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
Generated
+10394
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "mainflux-ui",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache License, version 2.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve --proxy-config proxy-config.json",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^5.2.4",
|
||||
"@angular/common": "^5.2.4",
|
||||
"@angular/compiler": "^5.2.4",
|
||||
"@angular/core": "^5.2.4",
|
||||
"@angular/flex-layout": "2.0.0-beta.10-4905443",
|
||||
"@angular/forms": "^5.2.4",
|
||||
"@angular/http": "^5.2.4",
|
||||
"@angular/platform-browser": "^5.2.4",
|
||||
"@angular/platform-browser-dynamic": "^5.2.4",
|
||||
"@angular/router": "^5.2.4",
|
||||
"core-js": "^2.4.1",
|
||||
"mobx": "^3.5.1",
|
||||
"mobx-angular": "^2.1.1",
|
||||
"ngx-auth": "^2.2.0",
|
||||
"rxjs": "^5.5.2",
|
||||
"zone.js": "^0.8.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cdk": "^5.2.0",
|
||||
"@angular/cli": "^1.7.3",
|
||||
"@angular/compiler-cli": "^5.2.4",
|
||||
"@angular/language-service": "^5.2.4",
|
||||
"@angular/material": "^5.2.0",
|
||||
"@types/jasmine": "~2.5.53",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/node": "^6.0.100",
|
||||
"codelyzer": "~3.2.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"jasmine-core": "~2.5.2",
|
||||
"jasmine-spec-reporter": "~4.1.0",
|
||||
"karma": "~1.7.0",
|
||||
"karma-chrome-launcher": "~2.1.1",
|
||||
"karma-cli": "~1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "^1.2.1",
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"karma-phantomjs-launcher": "^1.0.4",
|
||||
"protractor": "~5.1.2",
|
||||
"ts-node": "~3.2.0",
|
||||
"tslint": "~5.7.0",
|
||||
"typescript": "~2.4.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./e2e/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: 'e2e/tsconfig.e2e.json'
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"/api/*": {
|
||||
"target": "http://localhost:8180",
|
||||
"secure":false,
|
||||
"pathRewrite": {
|
||||
"^/api": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Route, RouterModule } from '@angular/router';
|
||||
import { ProtectedGuard, PublicGuard } from 'ngx-auth';
|
||||
|
||||
import { LoginComponent } from './components/auth/login/login.component';
|
||||
import { SignupComponent } from './components/auth/signup/signup.component';
|
||||
import { ChannelsComponent } from './components/channels/channels.component';
|
||||
import { ThingsComponent } from './components/things/things.component';
|
||||
|
||||
const routes: Route[] = [
|
||||
{ path: '', redirectTo: 'things', pathMatch: 'full'},
|
||||
{ path: 'login', component: LoginComponent, canActivate: [PublicGuard]},
|
||||
{ path: 'signup', component: SignupComponent, canActivate: [PublicGuard]},
|
||||
{ path: 'things', component: ThingsComponent, canActivate: [ProtectedGuard]},
|
||||
{ path: 'channels', component: ChannelsComponent, canActivate: [ProtectedGuard]}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forRoot(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
Copyright (c) 2018
|
||||
Mainflux
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<div *mobxAutorun>
|
||||
<div class="loading" *ngIf="uiStore.loading" fxLayoutAlign="center center">
|
||||
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner>
|
||||
</div>
|
||||
<mat-toolbar color="primary" class="mat-elevation-z6">
|
||||
<h1 fxFlex>Mainflux</h1>
|
||||
<ng-container *ngIf="authStore.isAuthenticated">
|
||||
<a [routerLink]="['/things']" mat-button>Things</a>
|
||||
<a [routerLink]="['/channels']" mat-button>Channels</a>
|
||||
<a (click)="logout()" mat-button>Logout</a>
|
||||
</ng-container>
|
||||
|
||||
</mat-toolbar>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { MaterialModule } from './core/material/material.module';
|
||||
import { AuthenticationService } from './core/services/auth/authentication.service';
|
||||
import { TokenStorage } from './core/services/auth/token-storage.service';
|
||||
import { ChannelsService } from './core/services/channels/channels.service';
|
||||
import { ThingsService } from './core/services/things/things.service';
|
||||
import { UiStore } from './core/store/ui.store';
|
||||
import { AuthStore } from './core/store/auth.store';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
HttpClientModule,
|
||||
RouterTestingModule
|
||||
],
|
||||
providers: [
|
||||
UiStore,
|
||||
AuthStore,
|
||||
AuthenticationService,
|
||||
TokenStorage,
|
||||
ThingsService,
|
||||
ChannelsService,
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { reaction } from 'mobx';
|
||||
|
||||
import { UiStore } from './core/store/ui.store';
|
||||
import { AuthStore } from './core/store/auth.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
constructor(
|
||||
private snackBar: MatSnackBar,
|
||||
public uiStore: UiStore,
|
||||
public authStore: AuthStore,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
reaction(() => this.authStore.authError, (authError) => {
|
||||
if (authError) {
|
||||
this.snackBar.open(authError, '', {
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.authStore.logout();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import './rxjs-extensions.ts';
|
||||
import 'hammerjs';
|
||||
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { MobxAngularModule } from 'mobx-angular';
|
||||
import { AUTH_SERVICE, AuthModule, PROTECTED_FALLBACK_PAGE_URI, PUBLIC_FALLBACK_PAGE_URI } from 'ngx-auth';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { LoginComponent } from './components/auth/login/login.component';
|
||||
import { SignupComponent } from './components/auth/signup/signup.component';
|
||||
import { ChannelDialogComponent } from './components/channels/channel-dialog/channel-dialog.component';
|
||||
import { ChannelsComponent } from './components/channels/channels.component';
|
||||
import { ThingDialogComponent } from './components/things/thing-dialog/thing-dialog.component';
|
||||
import { ThingsComponent } from './components/things/things.component';
|
||||
import { ConfirmationDialogComponent } from './components/shared/confirmation-dialog/confirmation-dialog.component';
|
||||
import { MaterialModule } from './core/material/material.module';
|
||||
import { AuthenticationService } from './core/services/auth/authentication.service';
|
||||
import { TokenStorage } from './core/services/auth/token-storage.service';
|
||||
import { ChannelsService } from './core/services/channels/channels.service';
|
||||
import { ThingsService } from './core/services/things/things.service';
|
||||
import { MockAuthService } from './core/services/mock-auth.service';
|
||||
import { MockChannelsService } from './core/services/mock-channels.service';
|
||||
import { MockThingsService } from './core/services/mock-things.service';
|
||||
import { ChannelsStore } from './core/store/channels.store';
|
||||
import { ThingsStore } from './core/store/things.store';
|
||||
import { UiStore } from './core/store/ui.store';
|
||||
import { AuthStore } from './core/store/auth.store';
|
||||
import { UnauthorizedInterceptor } from './core/services/auth/unauthorized.interceptor';
|
||||
|
||||
export function factory(authenticationService: AuthenticationService) {
|
||||
return authenticationService;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ThingsComponent,
|
||||
ChannelsComponent,
|
||||
SignupComponent,
|
||||
LoginComponent,
|
||||
ThingDialogComponent,
|
||||
ConfirmationDialogComponent,
|
||||
ChannelDialogComponent,
|
||||
],
|
||||
imports: [
|
||||
AuthModule,
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
AppRoutingModule,
|
||||
HttpClientModule,
|
||||
MaterialModule,
|
||||
FlexLayoutModule,
|
||||
ReactiveFormsModule,
|
||||
MobxAngularModule,
|
||||
],
|
||||
providers: [
|
||||
UiStore,
|
||||
ThingsStore,
|
||||
ChannelsStore,
|
||||
AuthStore,
|
||||
MockAuthService,
|
||||
MockThingsService,
|
||||
MockChannelsService,
|
||||
ThingsService,
|
||||
ChannelsService,
|
||||
TokenStorage,
|
||||
AuthenticationService,
|
||||
{ provide: PROTECTED_FALLBACK_PAGE_URI, useValue: '/' },
|
||||
{ provide: PUBLIC_FALLBACK_PAGE_URI, useValue: '/login' },
|
||||
{
|
||||
provide: AUTH_SERVICE,
|
||||
deps: [AuthenticationService],
|
||||
useFactory: factory
|
||||
},
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true },
|
||||
{ provide: APP_BASE_HREF, useValue: '/app/' }
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
entryComponents: [
|
||||
ThingDialogComponent,
|
||||
ChannelDialogComponent,
|
||||
ConfirmationDialogComponent
|
||||
]
|
||||
})
|
||||
export class AppModule { }
|
||||
@@ -0,0 +1,35 @@
|
||||
<!--
|
||||
Copyright (c) 2018
|
||||
Mainflux
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<div class="container" style="height: 80vh" fxLayoutAlign="center center">
|
||||
<form fxLayout="column" [formGroup]="loginForm" (ngSubmit)="login()" fxFlex="30%" fxFlex.sm="70%" fxFlex.xs="90%">
|
||||
<mat-card>
|
||||
<mat-card-title>Login</mat-card-title>
|
||||
<mat-card-content class="loginCard" fxLayout="column" fxLayoutAlign="space-evenly">
|
||||
<mat-form-field fxFlex="40%">
|
||||
<input type="text" class="emailInput" required matInput placeholder="Email" formControlName="email"/>
|
||||
<mat-error *ngIf="loginForm.get('email').errors?.required">
|
||||
Email is required
|
||||
</mat-error>
|
||||
<mat-error *ngIf="loginForm.get('email').errors?.email">
|
||||
Email must be valid
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="30%">
|
||||
<input type="password" class="passwordInput" required matInput placeholder="Password" formControlName="password" />
|
||||
<mat-error *ngIf="loginForm.get('password').errors?.required">
|
||||
Password is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
<mat-card-actions fxLayout="column">
|
||||
<button type="submit" [disabled]="loginForm.invalid" mat-raised-button color="primary" class="loginButton">Login</button>
|
||||
<button mat-button (click)="signup()" class="signupButton">Sign Up with Email</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
::ng-deep mat-card-content.loginCard {
|
||||
height: 200px;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { MaterialModule } from '../../../core/material/material.module';
|
||||
import { AuthenticationService } from '../../../core/services/auth/authentication.service';
|
||||
import { TokenStorage } from '../../../core/services/auth/token-storage.service';
|
||||
import { LoginComponent } from './login.component';
|
||||
import { UiStore } from '../../../core/store/ui.store';
|
||||
import { AuthStore } from '../../../core/store/auth.store';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoginComponent ],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
HttpClientModule,
|
||||
RouterTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NoopAnimationsModule
|
||||
],
|
||||
providers: [
|
||||
UiStore,
|
||||
AuthStore,
|
||||
AuthenticationService,
|
||||
TokenStorage,
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call the store goToSignup when clicked on Sign Up with Email', inject([UiStore], (store: UiStore) => {
|
||||
const signupButton = fixture.debugElement.nativeElement.querySelector('.signupButton');
|
||||
const signupSpy = spyOn(store, 'goToSignup').and.stub();
|
||||
|
||||
signupButton.click();
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(signupSpy).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
import { UiStore } from '../../../core/store/ui.store';
|
||||
import { AuthStore } from '../../../core/store/auth.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.scss']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
loginForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private uiStore: UiStore,
|
||||
private authStore: AuthStore,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.loginForm = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
password: ['', [Validators.required]],
|
||||
});
|
||||
}
|
||||
|
||||
login() {
|
||||
this.authStore.login(this.getUserDataFromForm());
|
||||
}
|
||||
|
||||
signup() {
|
||||
this.uiStore.goToSignup();
|
||||
}
|
||||
|
||||
getUserDataFromForm() {
|
||||
return {
|
||||
email: this.loginForm.get('email').value,
|
||||
password: this.loginForm.get('password').value
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<!--
|
||||
Copyright (c) 2018
|
||||
Mainflux
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<div class="container" style="height: 80vh" fxLayoutAlign="center center">
|
||||
<form fxLayout="column" [formGroup]="signupForm" (ngSubmit)="signup()" fxFlex="30%" fxFlex.sm="70%" fxFlex.xs="90%">
|
||||
<mat-card>
|
||||
<mat-card-title>Signup</mat-card-title>
|
||||
<mat-card-content class="loginCard" fxLayout="column" fxLayoutAlign="space-evenly">
|
||||
<mat-form-field fxFlex="40%">
|
||||
<input type="text" required matInput placeholder="Email" formControlName="email"/>
|
||||
<mat-error *ngIf="signupForm.get('email').errors?.required">
|
||||
Email is required
|
||||
</mat-error>
|
||||
<mat-error *ngIf="signupForm.get('email').errors?.email">
|
||||
Email must be valid
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="30%" formGroupName="passwords">
|
||||
<input type="password" required matInput placeholder="Password" formControlName="password" />
|
||||
<mat-error *ngIf="signupForm.get('passwords.password').errors?.required">
|
||||
Password is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="30%" formGroupName="passwords">
|
||||
<input type="password" required matInput placeholder="Repeat password" formControlName="repeatPassword" />
|
||||
<mat-error *ngIf="signupForm.get('passwords.repeatPassword').errors?.required">
|
||||
Repeat the password
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
<mat-card-actions fxLayout="column">
|
||||
<button type="submit" [disabled]="signupForm.invalid" mat-raised-button color="primary" class="large-button">Signup</button>
|
||||
<a mat-button (click)="login()">Login</a>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
::ng-deep mat-card-content.loginCard {
|
||||
height: 200px;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { MaterialModule } from '../../../core/material/material.module';
|
||||
import { AuthenticationService } from '../../../core/services/auth/authentication.service';
|
||||
import { TokenStorage } from '../../../core/services/auth/token-storage.service';
|
||||
import { SignupComponent } from './signup.component';
|
||||
import { AuthStore } from '../../../core/store/auth.store';
|
||||
import { UiStore } from '../../../core/store/ui.store';
|
||||
|
||||
describe('SignupComponent', () => {
|
||||
let component: SignupComponent;
|
||||
let fixture: ComponentFixture<SignupComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SignupComponent ],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
HttpClientModule,
|
||||
RouterTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NoopAnimationsModule
|
||||
],
|
||||
providers: [
|
||||
UiStore,
|
||||
AuthStore,
|
||||
AuthenticationService,
|
||||
TokenStorage
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SignupComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
import { AuthStore } from '../../../core/store/auth.store';
|
||||
import { UiStore } from '../../../core/store/ui.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-signup',
|
||||
templateUrl: './signup.component.html',
|
||||
styleUrls: ['./signup.component.scss']
|
||||
})
|
||||
export class SignupComponent implements OnInit {
|
||||
signupForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private uiStore: UiStore,
|
||||
private authStore: AuthStore,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.signupForm = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
passwords: this.fb.group({
|
||||
password: ['', [Validators.required]],
|
||||
repeatPassword: ['', [Validators.required]]
|
||||
}, { validator: this.comparePasswords })
|
||||
});
|
||||
}
|
||||
|
||||
comparePasswords(c: AbstractControl): { [key: string]: boolean } {
|
||||
const pass = c.get('password');
|
||||
const repeatPassword = c.get('repeatPassword');
|
||||
|
||||
if (pass.value !== repeatPassword.value) {
|
||||
return { 'match': true };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
signup() {
|
||||
this.authStore.signup(this.getUserDataFromForm());
|
||||
}
|
||||
|
||||
login() {
|
||||
this.uiStore.goToLogin();
|
||||
}
|
||||
|
||||
getUserDataFromForm() {
|
||||
return {
|
||||
email: this.signupForm.get('email').value,
|
||||
password: this.signupForm.get('passwords.password').value
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<!--
|
||||
Copyright (c) 2018
|
||||
Mainflux
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<form [formGroup]="addChannelForm" (ngSubmit)="onAddChannel()">
|
||||
<h1 *ngIf="!editMode" mat-dialog-title>Add channel</h1>
|
||||
<h1 *ngIf="editMode" mat-dialog-title>Edit channel</h1>
|
||||
<div mat-dialog-content fxLayout="column">
|
||||
<input type="hidden" formControlName="id">
|
||||
<mat-form-field>
|
||||
<input matInput tabindex="1" placeholder="Name" formControlName="name" />
|
||||
<mat-error *ngIf="addChannelForm.get('name').errors?.required">
|
||||
Name is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="Connected things" formControlName="connected" [compareWith]="compareFunction" multiple>
|
||||
<mat-option *ngFor="let thing of thingsStore.things" [value]="thing">{{thing.name}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close>Cancel</button>
|
||||
<button mat-button mat-raised-button color="primary" type="submit" [disabled]="addChannelForm.invalid">Ok</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { MaterialModule } from '../../../core/material/material.module';
|
||||
import { AuthenticationService } from '../../../core/services/auth/authentication.service';
|
||||
import { TokenStorage } from '../../../core/services/auth/token-storage.service';
|
||||
import { ChannelsService } from '../../../core/services/channels/channels.service';
|
||||
import { ThingsService } from '../../../core/services/things/things.service';
|
||||
import { ChannelDialogComponent } from './channel-dialog.component';
|
||||
import { ThingsStore } from '../../../core/store/things.store';
|
||||
import { UiStore } from '../../../core/store/ui.store';
|
||||
|
||||
describe('ChannelDialogComponent', () => {
|
||||
let component: ChannelDialogComponent;
|
||||
let fixture: ComponentFixture<ChannelDialogComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChannelDialogComponent ],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
MatDialogModule,
|
||||
HttpClientModule,
|
||||
RouterTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NoopAnimationsModule
|
||||
],
|
||||
providers: [
|
||||
ThingsStore,
|
||||
UiStore,
|
||||
AuthenticationService,
|
||||
TokenStorage,
|
||||
ThingsService,
|
||||
ChannelsService,
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: [] },
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChannelDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
||||
import { toJS } from 'mobx';
|
||||
|
||||
import { ThingsStore } from '../../../core/store/things.store';
|
||||
import { Channel, Thing } from '../../../core/store/models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-channel-dialog',
|
||||
templateUrl: './channel-dialog.component.html',
|
||||
styleUrls: ['./channel-dialog.component.scss']
|
||||
})
|
||||
export class ChannelDialogComponent implements OnInit {
|
||||
addChannelForm: FormGroup;
|
||||
@Output() submit: EventEmitter<Channel> = new EventEmitter<Channel>();
|
||||
editMode: boolean;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private dialogRef: MatDialogRef<ChannelDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: Channel,
|
||||
public thingsStore: ThingsStore,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.addChannelForm = this.fb.group(
|
||||
{
|
||||
id: [''],
|
||||
name: [''],
|
||||
connected: [[]]
|
||||
}
|
||||
);
|
||||
|
||||
if (this.data) {
|
||||
this.editMode = true;
|
||||
this.addChannelForm.patchValue(toJS(this.data));
|
||||
} else {
|
||||
this.editMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
onAddChannel() {
|
||||
const channel = this.addChannelForm.value;
|
||||
this.submit.emit(channel);
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
compareFunction(obj1: Thing, obj2: Thing) {
|
||||
return obj1.id === obj2.id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<!--
|
||||
Copyright (c) 2018
|
||||
Mainflux
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<div class="channelsContainer" fxLayout="row" fxLayoutWrap>
|
||||
<ng-container [ngSwitch]="channelsStore.channels?.length > 0">
|
||||
<ng-container *ngSwitchCase="true">
|
||||
<!-- List -->
|
||||
<div class="channel-list-container">
|
||||
<mat-table #table [dataSource]="channelsStore.channels.toJS()" matSort class="mat-cell">
|
||||
<!-- Columns -->
|
||||
<ng-container matColumnDef="id">
|
||||
<mat-header-cell fxFlex="32%" *matHeaderCellDef mat-sort-header>Id</mat-header-cell>
|
||||
<mat-cell fxFlex="32%" *matCellDef="let row" >{{row.id}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="name">
|
||||
<mat-header-cell fxFlex="30%" *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
|
||||
<mat-cell fxFlex="30%" *matCellDef="let row"> {{row.name}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="connected">
|
||||
<mat-header-cell fxFlex="30%" *matHeaderCellDef mat-sort-header>Connected</mat-header-cell>
|
||||
<mat-cell fxFlex="30%" *matCellDef="let row">
|
||||
<li *ngFor="let item of row.connected"> {{item.id}} </li>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<!-- Actions -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<mat-header-cell fxFlex="8%" *matHeaderCellDef mat-sort-header></mat-header-cell>
|
||||
<mat-cell fxFlex="8%" *matCellDef="let row" >
|
||||
<button mat-icon-button color="accent" (click)="editChannel(row)">
|
||||
<mat-icon aria-label="Delete">edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="accent" (click)="deleteChannel(row)">
|
||||
<mat-icon aria-label="Delete">delete</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
</mat-table>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchDefault>
|
||||
<h3>It looks like you don't have any channels in your account.</h3>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<button mat-mini-fab class="addButton" (click)="addChannel()">
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
.addButton {
|
||||
position: fixed;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
/* List container */
|
||||
.channel-list-container {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { MaterialModule } from '../../core/material/material.module';
|
||||
import { AuthenticationService } from '../../core/services/auth/authentication.service';
|
||||
import { TokenStorage } from '../../core/services/auth/token-storage.service';
|
||||
import { ChannelsService } from '../../core/services/channels/channels.service';
|
||||
import { ThingsService } from '../../core/services/things/things.service';
|
||||
import { ChannelsComponent } from './channels.component';
|
||||
import { ThingsStore } from '../../core/store/things.store';
|
||||
import { UiStore } from '../../core/store/ui.store';
|
||||
import { ChannelsStore } from '../../core/store/channels.store';
|
||||
|
||||
describe('ChannelsComponent', () => {
|
||||
let component: ChannelsComponent;
|
||||
let fixture: ComponentFixture<ChannelsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChannelsComponent ],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
MatDialogModule,
|
||||
HttpClientModule,
|
||||
RouterTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NoopAnimationsModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ThingsStore,
|
||||
useClass: class {
|
||||
getThings = jasmine.createSpy('getThings');
|
||||
}
|
||||
},
|
||||
UiStore,
|
||||
{
|
||||
provide: ChannelsStore,
|
||||
useClass: class {
|
||||
getChannels = jasmine.createSpy('getChannels');
|
||||
}
|
||||
},
|
||||
AuthenticationService,
|
||||
TokenStorage,
|
||||
ThingsService,
|
||||
ChannelsService,
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: [] },
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChannelsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ChannelsStore } from '../../core/store/channels.store';
|
||||
import { ThingsStore } from '../../core/store/things.store';
|
||||
import { Channel } from '../../core/store/models';
|
||||
import { ConfirmationDialogComponent } from '../shared/confirmation-dialog/confirmation-dialog.component';
|
||||
import { ChannelDialogComponent } from './channel-dialog/channel-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-channels',
|
||||
templateUrl: './channels.component.html',
|
||||
styleUrls: ['./channels.component.scss'],
|
||||
})
|
||||
export class ChannelsComponent implements OnInit {
|
||||
channels: Observable<Channel[]>;
|
||||
displayedColumns = ['id', 'name', 'connected', 'actions'];
|
||||
|
||||
constructor(
|
||||
private dialog: MatDialog,
|
||||
public thingsStore: ThingsStore,
|
||||
public channelsStore: ChannelsStore,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.channelsStore.getChannels();
|
||||
this.thingsStore.getThings();
|
||||
}
|
||||
|
||||
addChannel() {
|
||||
const dialogRef = this.dialog.open(ChannelDialogComponent);
|
||||
|
||||
dialogRef.componentInstance.submit.subscribe((channel: Channel) => {
|
||||
this.channelsStore.addChannel(channel);
|
||||
});
|
||||
}
|
||||
|
||||
editChannel(channel: Channel) {
|
||||
const dialogRef = this.dialog.open(ChannelDialogComponent, {
|
||||
data: channel
|
||||
});
|
||||
|
||||
dialogRef.componentInstance.submit.subscribe((editedChannel: Channel) => {
|
||||
this.channelsStore.editChannel(editedChannel);
|
||||
});
|
||||
}
|
||||
|
||||
deleteChannel(channel: Channel) {
|
||||
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||
data: {
|
||||
question: 'Are you sure you want to delete the channel?'
|
||||
}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
this.channelsStore.deleteChannel(channel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<!--
|
||||
Copyright (c) 2018
|
||||
Mainflux
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<h1 mat-dialog-title>{{data.question}}</h1>
|
||||
<div mat-dialog-actions fxLayoutAlign="end end">
|
||||
<button mat-button mat-dialog-close>Cancel</button>
|
||||
<button mat-button mat-raised-button color="primary" [mat-dialog-close]="true">Ok</button>
|
||||
</div>
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { MaterialModule } from '../../../core/material/material.module';
|
||||
import { ConfirmationDialogComponent } from './confirmation-dialog.component';
|
||||
|
||||
describe('ConfirmationDialogComponent', () => {
|
||||
let component: ConfirmationDialogComponent;
|
||||
let fixture: ComponentFixture<ConfirmationDialogComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ConfirmationDialogComponent ],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
MatDialogModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NoopAnimationsModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: [] },
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConfirmationDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material';
|
||||
|
||||
@Component({
|
||||
selector: 'app-confirmation-dialog',
|
||||
templateUrl: './confirmation-dialog.component.html',
|
||||
styleUrls: ['./confirmation-dialog.component.scss']
|
||||
})
|
||||
export class ConfirmationDialogComponent implements OnInit {
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: any) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<form [formGroup]="addThingForm" (ngSubmit)="onAddThing()">
|
||||
<h1 mat-dialog-title>Add thing</h1>
|
||||
<div mat-dialog-content fxLayout="column">
|
||||
<input type="hidden" formControlName="id">
|
||||
<mat-form-field *ngIf="!editMode">
|
||||
<mat-select placeholder="Thing type" formControlName="type">
|
||||
<mat-option value="app">
|
||||
App
|
||||
</mat-option>
|
||||
<mat-option value="device">
|
||||
Device
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="addThingForm.get('type').errors?.required">
|
||||
Type is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<input matInput tabindex="1" placeholder="Name" formControlName="name" />
|
||||
<mat-error *ngIf="addThingForm.get('name').errors?.required">
|
||||
Name is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<textarea formControlName="metadata" matInput placeholder="Metadata" matTextareaAutosize matAutosizeMinRows="2"
|
||||
matAutosizeMaxRows="5"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close>Cancel</button>
|
||||
<button mat-button mat-raised-button color="primary" type="submit" [disabled]="addThingForm.invalid">Ok</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,52 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { MaterialModule } from '../../../core/material/material.module';
|
||||
import { AuthenticationService } from '../../../core/services/auth/authentication.service';
|
||||
import { TokenStorage } from '../../../core/services/auth/token-storage.service';
|
||||
import { ChannelsService } from '../../../core/services/channels/channels.service';
|
||||
import { ThingsService } from '../../../core/services/things/things.service';
|
||||
import { ThingDialogComponent } from './thing-dialog.component';
|
||||
|
||||
describe('ThingDialogComponent', () => {
|
||||
let component: ThingDialogComponent;
|
||||
let fixture: ComponentFixture<ThingDialogComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ThingDialogComponent ],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
MatDialogModule,
|
||||
HttpClientModule,
|
||||
RouterTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NoopAnimationsModule
|
||||
],
|
||||
providers: [
|
||||
AuthenticationService,
|
||||
TokenStorage,
|
||||
ThingsService,
|
||||
ChannelsService,
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: [] },
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ThingDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
||||
|
||||
import { Thing } from '../../../core/store/models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-thing-dialog',
|
||||
templateUrl: './thing-dialog.component.html',
|
||||
styleUrls: ['./thing-dialog.component.scss']
|
||||
})
|
||||
export class ThingDialogComponent implements OnInit {
|
||||
addThingForm: FormGroup;
|
||||
@Output() submit: EventEmitter<Thing> = new EventEmitter<Thing>();
|
||||
editMode: boolean;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private dialogRef: MatDialogRef<ThingDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: Thing
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.addThingForm = this.fb.group(
|
||||
{
|
||||
id: null,
|
||||
type: ['', [Validators.required]],
|
||||
name: ['', [Validators.required, Validators.minLength(5)]],
|
||||
metadata: ['']
|
||||
}
|
||||
);
|
||||
|
||||
if (this.data) {
|
||||
this.editMode = true;
|
||||
this.addThingForm.patchValue(this.data);
|
||||
}
|
||||
}
|
||||
|
||||
onAddThing() {
|
||||
const thing = this.addThingForm.value;
|
||||
this.submit.emit(thing);
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
Copyright (c) 2018
|
||||
Mainflux
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<div class="thingsContainer" fxLayout="row" fxLayoutWrap>
|
||||
<ng-container [ngSwitch]="thingsStore.things?.length > 0">
|
||||
<ng-container *ngSwitchCase="true">
|
||||
<!-- List -->
|
||||
<div class="things-list-container">
|
||||
<mat-table #table [dataSource]="thingsStore.things.toJS()" matSort class="mat-cell">
|
||||
<!-- Columns -->
|
||||
<ng-container matColumnDef="id">
|
||||
<mat-header-cell fxFlex="32%" *matHeaderCellDef mat-sort-header>Id</mat-header-cell>
|
||||
<mat-cell fxFlex="32%" *matCellDef="let row" >{{row.id}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="name">
|
||||
<mat-header-cell fxFlex="15%" *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
|
||||
<mat-cell fxFlex="15%" *matCellDef="let row"> {{row.name}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="type">
|
||||
<mat-header-cell fxFlex="10%" *matHeaderCellDef mat-sort-header>Type</mat-header-cell>
|
||||
<mat-cell fxFlex="10%" *matCellDef="let row"> {{row.type}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="metadata">
|
||||
<mat-header-cell fxFlex="35%" *matHeaderCellDef mat-sort-header>Metadata</mat-header-cell>
|
||||
<mat-cell fxFlex="35%" *matCellDef="let row"> {{row.metadata | json}}</mat-cell>
|
||||
</ng-container>
|
||||
<!-- Actions -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<mat-header-cell fxFlex="8%" *matHeaderCellDef mat-sort-header></mat-header-cell>
|
||||
<mat-cell fxFlex="8%" *matCellDef="let row" >
|
||||
<button mat-icon-button color="accent" (click)="editThing(row)">
|
||||
<mat-icon aria-label="Delete">edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="accent" (click)="deleteThing(row)">
|
||||
<mat-icon aria-label="Delete">delete</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
</mat-table>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<h3 >It looks like you don't have any device in your account.</h3>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<button mat-mini-fab class="addButton" (click)="addThing()">
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
.addButton {
|
||||
position: fixed;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
/* List container */
|
||||
.things-list-container {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { MaterialModule } from '../../core/material/material.module';
|
||||
import { AuthenticationService } from '../../core/services/auth/authentication.service';
|
||||
import { TokenStorage } from '../../core/services/auth/token-storage.service';
|
||||
import { ChannelsService } from '../../core/services/channels/channels.service';
|
||||
import { ThingsService } from '../../core/services/things/things.service';
|
||||
import { ThingsComponent } from './things.component';
|
||||
import { UiStore } from '../../core/store/ui.store';
|
||||
import { ThingsStore } from '../../core/store/things.store';
|
||||
import { ChannelsStore } from '../../core/store/channels.store';
|
||||
|
||||
describe('ThingsComponent', () => {
|
||||
let component: ThingsComponent;
|
||||
let fixture: ComponentFixture<ThingsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ThingsComponent ],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
MatDialogModule,
|
||||
HttpClientModule,
|
||||
RouterTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NoopAnimationsModule,
|
||||
],
|
||||
providers: [
|
||||
UiStore,
|
||||
{
|
||||
provide: ThingsStore,
|
||||
useClass: class {
|
||||
getThings = jasmine.createSpy('getThings');
|
||||
}
|
||||
},
|
||||
ChannelsStore,
|
||||
AuthenticationService,
|
||||
TokenStorage,
|
||||
ThingsService,
|
||||
ChannelsService,
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: [] },
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ThingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material';
|
||||
import { toJS } from 'mobx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Thing } from '../../core/store/models';
|
||||
import { ConfirmationDialogComponent } from '../shared/confirmation-dialog/confirmation-dialog.component';
|
||||
import { ThingDialogComponent } from './thing-dialog/thing-dialog.component';
|
||||
import { ThingsStore } from '../../core/store/things.store';
|
||||
import { ChannelsStore } from '../../core/store/channels.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-things',
|
||||
templateUrl: './things.component.html',
|
||||
styleUrls: ['./things.component.scss']
|
||||
})
|
||||
export class ThingsComponent implements OnInit {
|
||||
things: Observable<Thing[]>;
|
||||
displayedColumns = ['id', 'name', 'type', 'metadata', 'actions'];
|
||||
|
||||
constructor(
|
||||
private dialog: MatDialog,
|
||||
public thingsStore: ThingsStore,
|
||||
public channelsStore: ChannelsStore,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.thingsStore.getThings();
|
||||
this.channelsStore.getChannels();
|
||||
}
|
||||
|
||||
addThing() {
|
||||
const dialogRef = this.dialog.open(ThingDialogComponent);
|
||||
|
||||
dialogRef.componentInstance.submit.subscribe((thing: Thing) => {
|
||||
this.thingsStore.addThing(thing);
|
||||
});
|
||||
}
|
||||
|
||||
editThing(thing: Thing) {
|
||||
const dialogRef = this.dialog.open(ThingDialogComponent, {
|
||||
data: thing
|
||||
});
|
||||
|
||||
dialogRef.componentInstance.submit.subscribe((editedThing: Thing) => {
|
||||
this.thingsStore.editThing(toJS(editedThing));
|
||||
});
|
||||
}
|
||||
|
||||
deleteThing(thing: Thing) {
|
||||
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||
data: {
|
||||
question: 'Are you sure you want to delete the thing?'
|
||||
}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
this.thingsStore.deleteThing(toJS(thing));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import {
|
||||
MatButtonModule,
|
||||
MatButtonToggleModule,
|
||||
MatCardModule,
|
||||
MatCheckboxModule,
|
||||
MatChipsModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
MatMenuModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSelectModule,
|
||||
MatSidenavModule,
|
||||
MatSliderModule,
|
||||
MatSlideToggleModule,
|
||||
MatSnackBarModule,
|
||||
MatTabsModule,
|
||||
MatToolbarModule,
|
||||
MatTooltipModule,
|
||||
MatTableModule,
|
||||
} from '@angular/material';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatButtonToggleModule,
|
||||
MatCardModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
MatMenuModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSelectModule,
|
||||
MatSidenavModule,
|
||||
MatSnackBarModule,
|
||||
MatTabsModule,
|
||||
MatToolbarModule,
|
||||
MatTooltipModule,
|
||||
MatCheckboxModule,
|
||||
MatSliderModule,
|
||||
MatSlideToggleModule,
|
||||
MatChipsModule,
|
||||
MatTableModule
|
||||
],
|
||||
exports: [
|
||||
MatButtonModule,
|
||||
MatButtonToggleModule,
|
||||
MatCardModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
MatMenuModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSelectModule,
|
||||
MatSidenavModule,
|
||||
MatSnackBarModule,
|
||||
MatTabsModule,
|
||||
MatToolbarModule,
|
||||
MatTooltipModule,
|
||||
MatCheckboxModule,
|
||||
MatSliderModule,
|
||||
MatSlideToggleModule,
|
||||
MatChipsModule,
|
||||
MatTableModule
|
||||
],
|
||||
})
|
||||
export class MaterialModule { }
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import '../../../rxjs-extensions';
|
||||
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AuthService } from 'ngx-auth';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { TokenStorage } from './token-storage.service';
|
||||
|
||||
|
||||
interface AccessData {
|
||||
token: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AuthenticationService implements AuthService {
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private tokenStorage: TokenStorage
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Check, if user already authorized.
|
||||
* @description Should return Observable with true or false values
|
||||
* @returns {Observable<boolean>}
|
||||
* @memberOf AuthService
|
||||
*/
|
||||
public isAuthorized(): Observable<boolean> {
|
||||
return this.tokenStorage
|
||||
.getAccessToken()
|
||||
.map(token => !!token);
|
||||
}
|
||||
|
||||
public getHeaders(token) {
|
||||
return {
|
||||
'Authorization': token
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access token
|
||||
* @description Should return access token in Observable from e.g.
|
||||
* localStorage
|
||||
* @returns {Observable<string>}
|
||||
*/
|
||||
public getAccessToken(): Observable<string> {
|
||||
return this.tokenStorage.getAccessToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function, that should perform refresh token verifyTokenRequest
|
||||
* @description Should be successfully completed so interceptor
|
||||
* can execute pending requests or retry original one
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
public refreshToken(): Observable<AccessData> {
|
||||
return this.tokenStorage
|
||||
.getRefreshToken()
|
||||
.switchMap((refreshToken: string) => {
|
||||
return this.http.post(`http://localhost:3000/refresh`, { refreshToken });
|
||||
})
|
||||
.do(this.saveAccessData.bind(this))
|
||||
.catch((err) => {
|
||||
this.logout();
|
||||
|
||||
return Observable.throw(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function, checks response of failed request to determine,
|
||||
* whether token be refreshed or not.
|
||||
* @description Essentialy checks status
|
||||
* @param {Response} response
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public refreshShouldHappen(response: HttpErrorResponse): boolean {
|
||||
return response.status === 401;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that outgoing request is refresh-token,
|
||||
* so interceptor won't intercept this request
|
||||
* @param {string} url
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public verifyTokenRequest(url: string): boolean {
|
||||
return url.endsWith('/refresh');
|
||||
}
|
||||
|
||||
/**
|
||||
* EXTRA AUTH METHODS
|
||||
*/
|
||||
|
||||
public login(payload): Observable<any> {
|
||||
return this.http.post(environment.loginUrl, payload)
|
||||
.do((tokens: AccessData) => this.saveAccessData(tokens))
|
||||
.catch((error) => {
|
||||
let message = '';
|
||||
|
||||
if (error.status === 403) {
|
||||
message = 'Wrong password or email';
|
||||
} else {
|
||||
message = 'Server side error';
|
||||
}
|
||||
|
||||
return Observable.throw(message);
|
||||
});
|
||||
}
|
||||
|
||||
public signup(payload): Observable<any> {
|
||||
return this.http.post(environment.signupUrl, payload)
|
||||
.catch((error) => {
|
||||
let message = '';
|
||||
if (error.status === 409) {
|
||||
message = 'Already existing email address';
|
||||
} else {
|
||||
message = 'Server side error';
|
||||
}
|
||||
|
||||
return Observable.throw(message);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
public logout(): void {
|
||||
this.tokenStorage.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save access data in the storage
|
||||
*
|
||||
* @private
|
||||
* @param {AccessData} data
|
||||
*/
|
||||
private saveAccessData({ token }: AccessData) {
|
||||
this.tokenStorage
|
||||
.setAccessToken(token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
@Injectable()
|
||||
export class TokenStorage {
|
||||
|
||||
/**
|
||||
* Get access token
|
||||
* @returns {Observable<string>}
|
||||
*/
|
||||
public getAccessToken(): Observable<string> {
|
||||
const token: string = <string>localStorage.getItem('accessToken');
|
||||
return Observable.of(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get refresh token
|
||||
* @returns {Observable<string>}
|
||||
*/
|
||||
public getRefreshToken(): Observable<string> {
|
||||
const token: string = <string>localStorage.getItem('refreshToken');
|
||||
return Observable.of(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set access token
|
||||
* @returns {TokenStorage}
|
||||
*/
|
||||
public setAccessToken(token: string): TokenStorage {
|
||||
localStorage.setItem('accessToken', token);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set refresh token
|
||||
* @returns {TokenStorage}
|
||||
*/
|
||||
public setRefreshToken(token: string): TokenStorage {
|
||||
localStorage.setItem('refreshToken', token);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove tokens
|
||||
*/
|
||||
public clear() {
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/do';
|
||||
|
||||
import { AuthStore } from '../../store/auth.store';
|
||||
|
||||
@Injectable()
|
||||
export class UnauthorizedInterceptor implements HttpInterceptor {
|
||||
constructor(
|
||||
private authStore: AuthStore
|
||||
) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return next.handle(req).do(event => {}, err => {
|
||||
if (err instanceof HttpErrorResponse && err.status === 403) {
|
||||
this.authStore.logout();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { HttpClient, HttpResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { forkJoin } from 'rxjs/observable/forkJoin';
|
||||
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { Channel, Thing } from '../../store/models';
|
||||
|
||||
interface ChannelsPayload {
|
||||
channels: Channel[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ChannelsService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getChannels() {
|
||||
return this.http.get(environment.channelsUrl).switchMap((payload: ChannelsPayload) => {
|
||||
const allChannels = forkJoin(this.createChannelsRequests(payload.channels));
|
||||
return allChannels;
|
||||
}).switchMap((responses: Channel[]) => {
|
||||
responses.forEach(channel => {
|
||||
channel.connected = channel.connected ? channel.connected : [];
|
||||
});
|
||||
return Observable.of(responses);
|
||||
});
|
||||
}
|
||||
|
||||
createChannelsRequests(channels) {
|
||||
return channels.map((channel => this.http.get(environment.channelsUrl + '/' + channel.id)));
|
||||
}
|
||||
|
||||
addChannel(channel: Channel) {
|
||||
const payload = {
|
||||
name: channel.name
|
||||
};
|
||||
|
||||
if (!channel.connected.length) {
|
||||
console.log('send add ch');
|
||||
return this.http.post(environment.channelsUrl, payload);
|
||||
}
|
||||
return this.http.post(environment.channelsUrl, payload, { observe: 'response' })
|
||||
.switchMap((res) => {
|
||||
const id = this.getChannelIdFrom(res);
|
||||
return forkJoin(this.createThingsConnectRequests(id, channel.connected));
|
||||
});
|
||||
}
|
||||
|
||||
private getChannelIdFrom(res: HttpResponse<Object>) {
|
||||
const location = res.headers.get('Location');
|
||||
return location.replace('/channels/', '');
|
||||
}
|
||||
|
||||
deleteChannel(channel: Channel) {
|
||||
return this.http.delete(environment.channelsUrl + '/' + channel.id);
|
||||
}
|
||||
|
||||
editChannel(channelFormData: Channel, channel: Channel) {
|
||||
const payload = {
|
||||
name: channelFormData.name
|
||||
};
|
||||
|
||||
const editChannel = this.http.put(environment.channelsUrl + '/' + channel.id, payload);
|
||||
|
||||
return editChannel.switchMap(() => {
|
||||
const thingsToAdd = this.getThingsToAdd(channelFormData, channel);
|
||||
if (thingsToAdd.length) {
|
||||
return forkJoin(this.createThingsConnectRequests(channel.id, thingsToAdd));
|
||||
} else {
|
||||
return Observable.of([]);
|
||||
}
|
||||
}).switchMap(() => {
|
||||
const thingsToDelete = this.getThingsToDelete(channelFormData, channel);
|
||||
console.log(thingsToDelete);
|
||||
if (thingsToDelete.length) {
|
||||
return forkJoin(this.createThingsDisconnectRequests(channel.id, thingsToDelete));
|
||||
} else {
|
||||
return Observable.of([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getThingsToDelete(channelFormData: Channel, channel: Channel) {
|
||||
return channel.connected.filter(thing => {
|
||||
return channelFormData.connected.find(th => th.id === thing.id) === undefined;
|
||||
});
|
||||
}
|
||||
|
||||
getThingsToAdd(channelFormData: Channel, channel: Channel) {
|
||||
return channelFormData.connected.filter(thing => {
|
||||
return channel.connected.find(th => th.id === thing.id) === undefined;
|
||||
});
|
||||
}
|
||||
|
||||
createThingsConnectRequests(channelId: string , connected: Thing[]) {
|
||||
return connected.map((connection) => {
|
||||
return this.http.put(environment.channelsUrl + '/' + channelId + '/things/' + connection.id, {});
|
||||
});
|
||||
}
|
||||
|
||||
createThingsDisconnectRequests(channelId: string , connected: Thing[]) {
|
||||
return connected.map((connection) => {
|
||||
return this.http.delete(environment.channelsUrl + '/' + channelId + '/things/' + connection.id, {});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { User } from '../store/models';
|
||||
|
||||
export const MOCK_USER = {
|
||||
email: 'asdf@asdf.com',
|
||||
password: 'asdf'
|
||||
};
|
||||
|
||||
export const EXISTING_USER = {
|
||||
email: '1234@1234.com',
|
||||
password: '1234'
|
||||
};
|
||||
|
||||
export const INVALID_CREDENTIALS_USER = {
|
||||
email: 'pera@pera.com',
|
||||
password: 'pera'
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class MockAuthService {
|
||||
public signup(user: User): Observable<User> {
|
||||
if (user.email === MOCK_USER.email) {
|
||||
return Observable.of(user).delay(5000);
|
||||
}
|
||||
|
||||
if (user.email === EXISTING_USER.email) {
|
||||
return Observable.throw(new Error('User with email already exists.'));
|
||||
}
|
||||
|
||||
return Observable.throw(new Error('Failed connecting to server.'));
|
||||
}
|
||||
|
||||
public login(user: User): Observable<User> {
|
||||
if (user.email === MOCK_USER.email) {
|
||||
return Observable.of(user).delay(1000);
|
||||
}
|
||||
|
||||
if (user.email === INVALID_CREDENTIALS_USER.email) {
|
||||
return Observable.throw(new Error('Invalid credentials'));
|
||||
}
|
||||
|
||||
return Observable.throw(new Error('Cannot connect to server'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Thing } from '../store/models';
|
||||
|
||||
const MOCK_CHANNELS = {
|
||||
channels: [
|
||||
{
|
||||
name: 'pera'
|
||||
},
|
||||
{
|
||||
name: 'dzoni'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class MockChannelsService {
|
||||
getChannels() {
|
||||
return Observable.of(MOCK_CHANNELS).delay(1000);
|
||||
}
|
||||
|
||||
addChannel(client: Thing) {
|
||||
MOCK_CHANNELS.channels.push(client);
|
||||
return Observable.of(1).delay(1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Thing } from '../store/models';
|
||||
|
||||
const MOCK_THINGS = [
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class MockThingsService {
|
||||
getThings() {
|
||||
return Observable.of(MOCK_THINGS).delay(1000);
|
||||
}
|
||||
|
||||
addThing(thing: Thing) {
|
||||
MOCK_THINGS.push(thing);
|
||||
return Observable.of(1).delay(1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2018
|
||||
* Mainflux
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { Thing } from '../../store/models';
|
||||
|
||||
@Injectable()
|
||||
export class ThingsService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getThings() {
|
||||
return this.http.get(environment.thingsUrl);
|
||||
}
|
||||
|
||||
addThing(thing: Thing) {
|
||||
return this.http.post(environment.thingsUrl, thing);
|
||||
}
|
||||
|
||||
deleteThing(thing: Thing) {
|
||||
return this.http.delete(environment.thingsUrl + '/' + thing.id);
|
||||
}
|
||||
|
||||
editThing(thing: Thing) {
|
||||
return this.http.put(environment.thingsUrl + '/' + thing.id, thing);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { AuthenticationService } from '../services/auth/authentication.service';
|
||||
import { TokenStorage } from '../services/auth/token-storage.service';
|
||||
import { ChannelsService } from '../services/channels/channels.service';
|
||||
import { ThingsService } from '../services/things/things.service';
|
||||
import { AuthStore } from './auth.store';
|
||||
import { UiStore } from './ui.store';
|
||||
|
||||
describe('AuthStore', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
RouterTestingModule.withRoutes([])
|
||||
],
|
||||
providers: [
|
||||
AuthStore,
|
||||
UiStore,
|
||||
TokenStorage,
|
||||
AuthenticationService,
|
||||
ThingsService,
|
||||
ChannelsService,
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([AuthStore], (authStore: AuthStore) => {
|
||||
expect(authStore).toBeTruthy();
|
||||
}));
|
||||
|
||||
describe('login', () => {
|
||||
const user = {
|
||||
email: 'user@user.com',
|
||||
password: 'userPassword',
|
||||
};
|
||||
|
||||
it('should set the loading flag to true before calling service', inject([AuthStore, UiStore, AuthenticationService],
|
||||
(authStore: AuthStore, uiStore: UiStore, authService: AuthenticationService) => {
|
||||
const spy = spyOn(authService, 'login').and.returnValue({ subscribe: () => { } });
|
||||
|
||||
authStore.login(user);
|
||||
|
||||
expect(uiStore.loading).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should set the isAuthenticated flag to true when successfully authenticated', inject([AuthStore, AuthenticationService, Router],
|
||||
(authStore: AuthStore, authService: AuthenticationService, router: Router) => {
|
||||
const spy = spyOn(authService, 'login').and.returnValue(Observable.of(true));
|
||||
const routerSpy = spyOn(router, 'navigate').and.stub();
|
||||
|
||||
authStore.login(user);
|
||||
|
||||
expect(authStore.isAuthenticated).toBeTruthy();
|
||||
}));
|
||||
|
||||
|
||||
it('should navigate to /things when successfully authenticated', inject([AuthStore, AuthenticationService, Router],
|
||||
(authStore: AuthStore, authService: AuthenticationService, router: Router) => {
|
||||
const spy = spyOn(authService, 'login').and.returnValue(Observable.of(true));
|
||||
const routerSpy = spyOn(router, 'navigate').and.stub();
|
||||
|
||||
authStore.login(user);
|
||||
|
||||
expect(routerSpy).toHaveBeenCalledWith(['/things']);
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false when successfully authenticated',
|
||||
inject([AuthStore, UiStore, AuthenticationService, Router],
|
||||
(authStore: AuthStore, uiStore: UiStore, authService: AuthenticationService, router: Router) => {
|
||||
const spy = spyOn(authService, 'login').and.returnValue(Observable.of(true));
|
||||
const routerSpy = spyOn(router, 'navigate').and.stub();
|
||||
|
||||
authStore.login(user);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false when authentication failed', inject([AuthStore, UiStore, AuthenticationService],
|
||||
(authStore: AuthStore, uiStore: UiStore, authService: AuthenticationService) => {
|
||||
const spy = spyOn(authService, 'login').and.returnValue(Observable.throw({ status: 403 }));
|
||||
|
||||
authStore.login(user);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should set the authError to authentication error', inject([AuthStore, AuthenticationService],
|
||||
(authStore: AuthStore, authService: AuthenticationService) => {
|
||||
const spy = spyOn(authService, 'login').and.returnValue(Observable.throw('Auth failed'));
|
||||
|
||||
authStore.login(user);
|
||||
|
||||
expect(authStore.authError).toEqual('Auth failed');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('signup', () => {
|
||||
const user = {
|
||||
email: 'user@user.com',
|
||||
password: 'userPassword',
|
||||
};
|
||||
|
||||
it('should set the loading flag to true before calling service', inject([AuthStore, UiStore, AuthenticationService],
|
||||
(authStore: AuthStore, uiStore: UiStore, authService: AuthenticationService) => {
|
||||
const spy = spyOn(authService, 'signup').and.returnValue({ subscribe: () => { } });
|
||||
|
||||
authStore.signup(user);
|
||||
|
||||
expect(uiStore.loading).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should call the login when signup successfull', inject([AuthStore, AuthenticationService, Router],
|
||||
(authStore: AuthStore, authService: AuthenticationService, router: Router) => {
|
||||
const spy = spyOn(authService, 'signup').and.returnValue(Observable.of(true));
|
||||
const routerSpy = spyOn(router, 'navigate').and.stub();
|
||||
const loginSpy = spyOn(authStore, 'login').and.stub();
|
||||
|
||||
authStore.signup(user);
|
||||
|
||||
expect(loginSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false when signup failed', inject([AuthStore, UiStore, AuthenticationService],
|
||||
(authStore: AuthStore, uiStore: UiStore, authService: AuthenticationService) => {
|
||||
const spy = spyOn(authService, 'signup').and.returnValue(Observable.throw('Signup failed'));
|
||||
|
||||
authStore.signup(user);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should set the authError to signup error', inject([AuthStore, AuthenticationService],
|
||||
(authStore: AuthStore, authService: AuthenticationService) => {
|
||||
const spy = spyOn(authService, 'signup').and.returnValue(Observable.throw('Signup failed'));
|
||||
|
||||
authStore.signup(user);
|
||||
|
||||
expect(authStore.authError).toEqual('Signup failed');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('logout', () => {
|
||||
it('should call the authentication service logout', inject([AuthStore, AuthenticationService, Router],
|
||||
(authStore: AuthStore, authService: AuthenticationService, router: Router) => {
|
||||
const spy = spyOn(authService, 'logout');
|
||||
const routerSpy = spyOn(router, 'navigate').and.stub();
|
||||
|
||||
authStore.logout();
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should set the isAuthenticated flag to false', inject([AuthStore, AuthenticationService, Router],
|
||||
(authStore: AuthStore, authService: AuthenticationService, router: Router) => {
|
||||
const spy = spyOn(authService, 'logout');
|
||||
const routerSpy = spyOn(router, 'navigate').and.stub();
|
||||
|
||||
authStore.logout();
|
||||
|
||||
expect(authStore.isAuthenticated).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should navigate to /login', inject([AuthStore, AuthenticationService, Router],
|
||||
(authStore: AuthStore, authService: AuthenticationService, router: Router) => {
|
||||
const spy = spyOn(authService, 'logout');
|
||||
const routerSpy = spyOn(router, 'navigate').and.stub();
|
||||
|
||||
authStore.logout();
|
||||
|
||||
expect(routerSpy).toHaveBeenCalledWith(['/login']);
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
import { AuthenticationService } from '../services/auth/authentication.service';
|
||||
import { User } from './models';
|
||||
import { UiStore } from './ui.store';
|
||||
|
||||
@Injectable()
|
||||
export class AuthStore {
|
||||
@observable isAuthenticated = false;
|
||||
@observable authError = '';
|
||||
|
||||
constructor(
|
||||
private authenticationService: AuthenticationService,
|
||||
private uiState: UiStore,
|
||||
private router: Router,
|
||||
) {
|
||||
this.authenticationService.isAuthorized().subscribe(
|
||||
(isAuthenticated) => {
|
||||
this.isAuthenticated = isAuthenticated;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
login(user: User) {
|
||||
this.uiState.loading = true;
|
||||
|
||||
this.authenticationService.login(user)
|
||||
.subscribe(() => {
|
||||
this.uiState.loading = false;
|
||||
this.isAuthenticated = true;
|
||||
this.router.navigate(['/things']);
|
||||
},
|
||||
(error) => {
|
||||
this.uiState.loading = false;
|
||||
this.authError = error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
signup(user: User) {
|
||||
this.uiState.loading = true;
|
||||
|
||||
this.authenticationService.signup(user)
|
||||
.subscribe(() => {
|
||||
this.login(user);
|
||||
},
|
||||
(error) => {
|
||||
this.uiState.loading = false;
|
||||
this.authError = error;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
logout() {
|
||||
this.authenticationService.logout();
|
||||
this.isAuthenticated = false;
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { toJS } from 'mobx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ChannelsService } from '../services/channels/channels.service';
|
||||
import { ChannelsStore } from './channels.store';
|
||||
import { Channel } from './models';
|
||||
import { UiStore } from './ui.store';
|
||||
|
||||
describe('ChannelsStore', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
RouterTestingModule.withRoutes([])
|
||||
],
|
||||
providers: [
|
||||
ChannelsStore,
|
||||
UiStore,
|
||||
ChannelsService,
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([ChannelsStore], (channelsStore: ChannelsStore) => {
|
||||
expect(channelsStore).toBeTruthy();
|
||||
}));
|
||||
|
||||
describe('getChannels', () => {
|
||||
it('should set the loading flag to true before service call', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const getChannels = spyOn(channelsService, 'getChannels').and.returnValue({ subscribe: () => { } });
|
||||
|
||||
channelsStore.getChannels();
|
||||
|
||||
expect(uiStore.loading).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after successful get', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const getChannels = spyOn(channelsService, 'getChannels').and.returnValue(Observable.of(true));
|
||||
|
||||
channelsStore.getChannels();
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should set the channels property to the returned channels from the service', inject([ChannelsStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, channelsService: ChannelsService) => {
|
||||
const serviceReturnValue = [];
|
||||
const getChannels = spyOn(channelsService, 'getChannels').and.returnValue(Observable.of(serviceReturnValue));
|
||||
|
||||
channelsStore.getChannels();
|
||||
|
||||
expect(toJS(channelsStore.channels)).toEqual(serviceReturnValue);
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after failed get', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const getChannels = spyOn(channelsService, 'getChannels').and.returnValue(Observable.throw(''));
|
||||
|
||||
channelsStore.getChannels();
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('addChannel', () => {
|
||||
it('should set the loading flag to true before service call', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const addChannel = spyOn(channelsService, 'addChannel').and.returnValue({ subscribe: () => { } });
|
||||
const newChannel: Channel = {
|
||||
name: 'new channel',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
channelsStore.addChannel(newChannel);
|
||||
|
||||
expect(uiStore.loading).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after successful service call', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const addChannel = spyOn(channelsService, 'addChannel').and.returnValue(Observable.of(true));
|
||||
const storeGetChannelsSpy = spyOn(channelsStore, 'getChannels').and.stub();
|
||||
const newChannel: Channel = {
|
||||
name: 'new channel',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
channelsStore.addChannel(newChannel);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after failed service call', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const addChannel = spyOn(channelsService, 'addChannel').and.returnValue(Observable.throw(''));
|
||||
const storeGetChannelsSpy = spyOn(channelsStore, 'getChannels').and.stub();
|
||||
const newChannel: Channel = {
|
||||
name: 'new channel',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
channelsStore.addChannel(newChannel);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should call the channelsStore.getChannels after successful add', inject([ChannelsStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, channelsService: ChannelsService) => {
|
||||
const addChannel = spyOn(channelsService, 'addChannel').and.returnValue(Observable.of(true));
|
||||
const storeGetChannelsSpy = spyOn(channelsStore, 'getChannels').and.stub();
|
||||
|
||||
const newChannel: Channel = {
|
||||
name: 'new channel',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
channelsStore.addChannel(newChannel);
|
||||
|
||||
expect(storeGetChannelsSpy).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('editChannel', () => {
|
||||
it('should set the loading flag to true before service call', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const addChannel = spyOn(channelsService, 'editChannel').and.returnValue({ subscribe: () => { } });
|
||||
const editedChannel: Channel = {
|
||||
name: 'edited channel',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
channelsStore.editChannel(editedChannel);
|
||||
|
||||
expect(uiStore.loading).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after successful service call', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const editChannel = spyOn(channelsService, 'editChannel').and.returnValue(Observable.of(true));
|
||||
const storeGetChannelsSpy = spyOn(channelsStore, 'getChannels').and.stub();
|
||||
const editedChannel: Channel = {
|
||||
name: 'edited channel',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
|
||||
channelsStore.editChannel(editedChannel);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after failed service call', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const editChannel = spyOn(channelsService, 'editChannel').and.returnValue(Observable.throw(''));
|
||||
const storeGetChannelsSpy = spyOn(channelsStore, 'getChannels').and.stub();
|
||||
const editedChannel: Channel = {
|
||||
name: 'edited channel',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
|
||||
channelsStore.editChannel(editedChannel);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should call the channelsStore.getChannels after successful add', inject([ChannelsStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, channelsService: ChannelsService) => {
|
||||
const editChannel = spyOn(channelsService, 'editChannel').and.returnValue(Observable.of(true));
|
||||
const storeGetChannelsSpy = spyOn(channelsStore, 'getChannels').and.stub();
|
||||
|
||||
const editedChannel: Channel = {
|
||||
name: 'edited channel',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
channelsStore.editChannel(editedChannel);
|
||||
|
||||
expect(storeGetChannelsSpy).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('deleteChannel', () => {
|
||||
it('should set the loading flag to true before service call', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const deleteChannel = spyOn(channelsService, 'deleteChannel').and.returnValue({ subscribe: () => { } });
|
||||
const channelToBeDeleted: Channel = {
|
||||
name: 'channelToBeDeleted',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
channelsStore.deleteChannel(channelToBeDeleted);
|
||||
|
||||
expect(uiStore.loading).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after successful service call', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const deleteChannel = spyOn(channelsService, 'deleteChannel').and.returnValue(Observable.of(true));
|
||||
const storeGetChannelsSpy = spyOn(channelsStore, 'getChannels').and.stub();
|
||||
const channelToBeDeleted: Channel = {
|
||||
name: 'channelToBeDeleted',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
|
||||
channelsStore.deleteChannel(channelToBeDeleted);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after failed service call', inject([ChannelsStore, UiStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, uiStore: UiStore, channelsService: ChannelsService) => {
|
||||
const deleteChannel = spyOn(channelsService, 'deleteChannel').and.returnValue(Observable.throw(''));
|
||||
const storeGetChannelsSpy = spyOn(channelsStore, 'getChannels').and.stub();
|
||||
const channelToBeDeleted: Channel = {
|
||||
name: 'channelToBeDeleted',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
|
||||
channelsStore.deleteChannel(channelToBeDeleted);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should call the channelsStore.getChannels after successful add', inject([ChannelsStore, ChannelsService],
|
||||
(channelsStore: ChannelsStore, channelsService: ChannelsService) => {
|
||||
const deleteChannel = spyOn(channelsService, 'deleteChannel').and.returnValue(Observable.of(true));
|
||||
const storeGetChannelsSpy = spyOn(channelsStore, 'getChannels').and.stub();
|
||||
|
||||
const channelToBeDeleted: Channel = {
|
||||
name: 'channelToBeDeleted',
|
||||
connected: [],
|
||||
};
|
||||
|
||||
channelsStore.deleteChannel(channelToBeDeleted);
|
||||
|
||||
expect(storeGetChannelsSpy).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
import { ChannelsService } from '../services/channels/channels.service';
|
||||
import { Channel } from './models';
|
||||
import { UiStore } from './ui.store';
|
||||
|
||||
@Injectable()
|
||||
export class ChannelsStore {
|
||||
@observable channels: Channel[] = [];
|
||||
|
||||
constructor(
|
||||
private uiState: UiStore,
|
||||
private channelsService: ChannelsService,
|
||||
) { }
|
||||
|
||||
@action
|
||||
getChannels() {
|
||||
this.uiState.loading = true;
|
||||
this.channelsService.getChannels()
|
||||
.subscribe((payload: any) => {
|
||||
this.uiState.loading = false;
|
||||
this.channels = payload;
|
||||
}, () => {
|
||||
this.uiState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
addChannel(channel: Channel) {
|
||||
this.uiState.loading = true;
|
||||
console.log('add ch');
|
||||
this.channelsService.addChannel(channel)
|
||||
.subscribe(resp => {
|
||||
console.log(resp);
|
||||
this.uiState.loading = false;
|
||||
this.getChannels();
|
||||
}, () => {
|
||||
this.uiState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
editChannel(editedChannel: Channel) {
|
||||
this.uiState.loading = true;
|
||||
this.channelsService.editChannel(editedChannel, this.getChannelById(editedChannel.id))
|
||||
.subscribe(() => {
|
||||
this.uiState.loading = false;
|
||||
this.getChannels();
|
||||
}, () => {
|
||||
this.uiState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
private getChannelById(id: string) {
|
||||
return this.channels.find(ch => ch.id === id);
|
||||
}
|
||||
|
||||
@action
|
||||
deleteChannel(channel: Channel) {
|
||||
this.uiState.loading = true;
|
||||
this.channelsService.deleteChannel(channel)
|
||||
.subscribe(() => {
|
||||
this.uiState.loading = false;
|
||||
this.getChannels();
|
||||
}, () => {
|
||||
this.uiState.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface Channel {
|
||||
id?: '';
|
||||
name: string;
|
||||
connected: Thing[];
|
||||
}
|
||||
|
||||
export interface Thing {
|
||||
id?: '';
|
||||
type: string;
|
||||
name: string;
|
||||
metadata: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { toJS } from 'mobx';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ThingsService } from '../services/things/things.service';
|
||||
import { ThingsStore } from './things.store';
|
||||
import { Thing } from './models';
|
||||
import { UiStore } from './ui.store';
|
||||
|
||||
describe('ThingsStore', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
RouterTestingModule.withRoutes([])
|
||||
],
|
||||
providers: [
|
||||
ThingsStore,
|
||||
UiStore,
|
||||
ThingsService,
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([ThingsStore], (thingsStore: ThingsStore) => {
|
||||
expect(thingsStore).toBeTruthy();
|
||||
}));
|
||||
|
||||
describe('getThings', () => {
|
||||
it('should set the loading flag to true before service call', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const getThings = spyOn(thingsService, 'getThings').and.returnValue({ subscribe: () => { } });
|
||||
|
||||
thingsStore.getThings();
|
||||
|
||||
expect(uiStore.loading).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after successful get', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const getThings = spyOn(thingsService, 'getThings').and.returnValue(Observable.of(true));
|
||||
|
||||
thingsStore.getThings();
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should set the things property to the returned things from the service', inject([ThingsStore, ThingsService],
|
||||
(thingsStore: ThingsStore, thingsService: ThingsService) => {
|
||||
const serviceReturnValue = { things: [] };
|
||||
const getChannels = spyOn(thingsService, 'getThings').and.returnValue(Observable.of(serviceReturnValue));
|
||||
|
||||
thingsStore.getThings();
|
||||
|
||||
expect(toJS(thingsStore.things)).toEqual(serviceReturnValue.things);
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after failed get', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const getThings = spyOn(thingsService, 'getThings').and.returnValue(Observable.throw(''));
|
||||
|
||||
thingsStore.getThings();
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('addThing', () => {
|
||||
it('should set the loading flag to true before service call', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const addThing = spyOn(thingsService, 'addThing').and.returnValue({ subscribe: () => { } });
|
||||
const newThing: Thing = {
|
||||
name: 'new thing',
|
||||
type: 'app',
|
||||
metadata: '',
|
||||
};
|
||||
|
||||
thingsStore.addThing(newThing);
|
||||
|
||||
expect(uiStore.loading).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after successful service call', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const addThing = spyOn(thingsService, 'addThing').and.returnValue(Observable.of(true));
|
||||
const storeGetThingsSpy = spyOn(thingsStore, 'getThings').and.stub();
|
||||
const newThing: Thing = {
|
||||
name: 'new thing',
|
||||
type: 'app',
|
||||
metadata: '',
|
||||
};
|
||||
|
||||
thingsStore.addThing(newThing);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should call the thingsStore.getThings after successful add', inject([ThingsStore, ThingsService],
|
||||
(thingsStore: ThingsStore, thingsService: ThingsService) => {
|
||||
const addThing = spyOn(thingsService, 'addThing').and.returnValue(Observable.of(true));
|
||||
const storeGetThingsSpy = spyOn(thingsStore, 'getThings').and.stub();
|
||||
const newThing: Thing = {
|
||||
name: 'new thing',
|
||||
type: 'app',
|
||||
metadata: '',
|
||||
};
|
||||
|
||||
thingsStore.addThing(newThing);
|
||||
|
||||
expect(storeGetThingsSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after failed add', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const addThing = spyOn(thingsService, 'addThing').and.returnValue(Observable.throw(''));
|
||||
const storeGetThingsSpy = spyOn(thingsStore, 'getThings').and.stub();
|
||||
|
||||
const newThing: Thing = {
|
||||
name: 'new thing',
|
||||
type: 'app',
|
||||
metadata: '',
|
||||
};
|
||||
|
||||
thingsStore.addThing(newThing);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('editThing', () => {
|
||||
it('should set the loading flag to true before service call', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const editThing = spyOn(thingsService, 'editThing').and.returnValue({ subscribe: () => { } });
|
||||
const editedThing: Thing = {
|
||||
name: 'edited thing',
|
||||
type: 'app',
|
||||
metadata: '',
|
||||
};
|
||||
|
||||
thingsStore.editThing(editedThing);
|
||||
|
||||
expect(uiStore.loading).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after successful service call', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const editThing = spyOn(thingsService, 'editThing').and.returnValue(Observable.of(true));
|
||||
const storeGetThingsSpy = spyOn(thingsStore, 'getThings').and.stub();
|
||||
const editedThing: Thing = {
|
||||
name: 'edited thing',
|
||||
type: 'app',
|
||||
metadata: '',
|
||||
};
|
||||
|
||||
thingsStore.editThing(editedThing);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should call the thingsStore.getChannels after successful edit', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const editThing = spyOn(thingsService, 'editThing').and.returnValue(Observable.of(true));
|
||||
const storeGetThingsSpy = spyOn(thingsStore, 'getThings').and.stub();
|
||||
|
||||
const editedThing: Thing = {
|
||||
name: 'edited thing',
|
||||
type: 'app',
|
||||
metadata: '',
|
||||
};
|
||||
|
||||
thingsStore.editThing(editedThing);
|
||||
|
||||
expect(storeGetThingsSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after failed edit', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const editThing = spyOn(thingsService, 'editThing').and.returnValue(Observable.throw(''));
|
||||
const storeGetThingsSpy = spyOn(thingsStore, 'getThings').and.stub();
|
||||
const editedThing: Thing = {
|
||||
name: 'edited thing',
|
||||
type: 'app',
|
||||
metadata: '',
|
||||
};
|
||||
|
||||
thingsStore.editThing(editedThing);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('deleteThing', () => {
|
||||
it('should set the loading flag to true before service call', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const deleteThing = spyOn(thingsService, 'deleteThing').and.returnValue({ subscribe: () => { } });
|
||||
const thingToBeDeleted: Thing = {
|
||||
name: 'thingToBeDeleted',
|
||||
type: 'app',
|
||||
metadata: ''
|
||||
};
|
||||
|
||||
thingsStore.deleteThing(thingToBeDeleted);
|
||||
|
||||
expect(uiStore.loading).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after successful service call', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const deleteThing = spyOn(thingsService, 'deleteThing').and.returnValue(Observable.of(true));
|
||||
const storeGetThingsSpy = spyOn(thingsStore, 'getThings').and.stub();
|
||||
const thingToBeDeleted: Thing = {
|
||||
name: 'thingToBeDeleted',
|
||||
type: 'app',
|
||||
metadata: ''
|
||||
};
|
||||
|
||||
|
||||
thingsStore.deleteThing(thingToBeDeleted);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should call the thingsStore.getChannels after successful add', inject([ThingsStore, ThingsService],
|
||||
(thingsStore: ThingsStore, thingsService: ThingsService) => {
|
||||
const deleteThing = spyOn(thingsService, 'deleteThing').and.returnValue(Observable.of(true));
|
||||
const storeGetThingsSpy = spyOn(thingsStore, 'getThings').and.stub();
|
||||
|
||||
const thingToBeDeleted: Thing = {
|
||||
name: 'thingToBeDeleted',
|
||||
type: 'app',
|
||||
metadata: ''
|
||||
};
|
||||
|
||||
thingsStore.deleteThing(thingToBeDeleted);
|
||||
|
||||
expect(storeGetThingsSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should set the loading flag to false after failed delete', inject([ThingsStore, UiStore, ThingsService],
|
||||
(thingsStore: ThingsStore, uiStore: UiStore, thingsService: ThingsService) => {
|
||||
const deleteThing = spyOn(thingsService, 'deleteThing').and.returnValue(Observable.throw(''));
|
||||
const storeGetThingsSpy = spyOn(thingsStore, 'getThings').and.stub();
|
||||
const thingToBeDeleted: Thing = {
|
||||
name: 'thingToBeDeleted',
|
||||
type: 'app',
|
||||
metadata: ''
|
||||
};
|
||||
|
||||
thingsStore.deleteThing(thingToBeDeleted);
|
||||
|
||||
expect(uiStore.loading).toBeFalsy();
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
import { ThingsService } from '../services/things/things.service';
|
||||
import { Thing } from './models';
|
||||
import { UiStore } from './ui.store';
|
||||
|
||||
@Injectable()
|
||||
export class ThingsStore {
|
||||
@observable things: Thing[] = [];
|
||||
|
||||
constructor(
|
||||
private uiState: UiStore,
|
||||
private thingsService: ThingsService,
|
||||
) { }
|
||||
|
||||
@action
|
||||
getThings() {
|
||||
this.uiState.loading = true;
|
||||
this.thingsService.getThings()
|
||||
.subscribe((payload: any) => {
|
||||
this.uiState.loading = false;
|
||||
this.things = payload.things;
|
||||
}, () => {
|
||||
this.uiState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
addThing(thing: Thing) {
|
||||
this.uiState.loading = true;
|
||||
this.thingsService.addThing(thing)
|
||||
.subscribe(() => {
|
||||
this.uiState.loading = false;
|
||||
this.getThings();
|
||||
}, () => {
|
||||
this.uiState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
editThing(thing: Thing) {
|
||||
this.uiState.loading = true;
|
||||
this.thingsService.editThing(thing)
|
||||
.subscribe(() => {
|
||||
this.uiState.loading = false;
|
||||
this.getThings();
|
||||
}, () => {
|
||||
this.uiState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
deleteThing(thing: Thing) {
|
||||
this.uiState.loading = true;
|
||||
this.thingsService.deleteThing(thing)
|
||||
.subscribe(() => {
|
||||
this.uiState.loading = false;
|
||||
this.getThings();
|
||||
}, () => {
|
||||
this.uiState.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { AuthenticationService } from '../services/auth/authentication.service';
|
||||
import { TokenStorage } from '../services/auth/token-storage.service';
|
||||
import { ChannelsService } from '../services/channels/channels.service';
|
||||
import { ThingsService } from '../services/things/things.service';
|
||||
import { UiStore } from './ui.store';
|
||||
|
||||
describe('State', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
RouterTestingModule.withRoutes([])
|
||||
],
|
||||
providers: [
|
||||
UiStore,
|
||||
UiStore,
|
||||
TokenStorage,
|
||||
AuthenticationService,
|
||||
ThingsService,
|
||||
ChannelsService,
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([UiStore], (uiStore: UiStore) => {
|
||||
expect(uiStore).toBeTruthy();
|
||||
}));
|
||||
|
||||
describe('goToSignup', () => {
|
||||
it('should navigate to /signup', inject([UiStore, Router],
|
||||
(uiStore: UiStore, router: Router) => {
|
||||
const routerSpy = spyOn(router, 'navigate').and.stub();
|
||||
|
||||
uiStore.goToSignup();
|
||||
|
||||
expect(routerSpy).toHaveBeenCalledWith(['/signup']);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('goToLogin', () => {
|
||||
it('should navigate to /login', inject([UiStore, Router],
|
||||
(uiStore: UiStore, router: Router) => {
|
||||
const routerSpy = spyOn(router, 'navigate').and.stub();
|
||||
|
||||
uiStore.goToLogin();
|
||||
|
||||
expect(routerSpy).toHaveBeenCalledWith(['/login']);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
@Injectable()
|
||||
export class UiStore {
|
||||
@observable loading = false;
|
||||
|
||||
constructor(
|
||||
private router: Router
|
||||
) { }
|
||||
|
||||
|
||||
@action
|
||||
goToSignup() {
|
||||
this.router.navigate(['/signup']);
|
||||
}
|
||||
|
||||
@action
|
||||
goToLogin() {
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user