mirror of
https://github.com/absmach/magistrala.git
synced 2026-08-07 15:25:48 +00:00
Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 694d67ada2 | |||
| d6755e4a72 | |||
| 9cacf5708b | |||
| ad5c66fad2 | |||
| d89e91143b | |||
| 96bcc4cff4 | |||
| a8eb7ae039 | |||
| 6eade2c1d0 | |||
| 148c1aca0a | |||
| 70517bd907 | |||
| d5f0d7d225 | |||
| b20a846071 | |||
| 6600d26ef1 | |||
| 3de34062db | |||
| 8e9d7b4fdb | |||
| 5757380a63 | |||
| c944205080 | |||
| d825174ccb | |||
| 6408b8a9d3 | |||
| 845b69a4ac | |||
| 0c77d84176 | |||
| 970c1c86ca | |||
| 902630fb14 | |||
| 4ca443038d | |||
| 47c56cbac9 | |||
| 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 |
@@ -0,0 +1,15 @@
|
||||
Pull request title should be `MF-XXX - description` or `NOISSUE - description` where XXX is ID of issue that this PR relate to.
|
||||
Please review the [CONTRIBUTING.md](./CONTRIBUTING.md) file for detailed contributing guidelines.
|
||||
|
||||
### What does this do?
|
||||
|
||||
### Which issue(s) does this PR fix/relate to?
|
||||
Put here `Resolves #XXX` to auto-close the issue that your PR fixes (if such)
|
||||
|
||||
### List any changes that modify/break current functionality
|
||||
|
||||
### Have you included tests for your changes?
|
||||
|
||||
### Did you document any new/modified functionality?
|
||||
|
||||
### Notes
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
build
|
||||
*.pb.go
|
||||
|
||||
site/
|
||||
|
||||
|
||||
Generated
+487
-112
@@ -3,130 +3,137 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6da51e5ec493ad2b44cb04129e2d0a068c8fb9bd6cb5739d199573558696bb94"
|
||||
name = "github.com/Azure/go-ansiterm"
|
||||
packages = [
|
||||
".",
|
||||
"winterm"
|
||||
"winterm",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bf42be3cb1519bf8018dfd99720b1005ee028d947124cab3ccf965da59381df6"
|
||||
name = "github.com/Microsoft/go-winio"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
|
||||
version = "v0.4.7"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3721a10686511b80c052323423f0de17a8c06d417dbdd3b392b1578432a33aae"
|
||||
name = "github.com/Nvveen/Gotty"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "cd527374f1e5bff4938207604a14f2e38a9cf512"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:320e7ead93de9fd2b0e59b50fd92a4d50c1f8ab455d96bc2eb083267453a9709"
|
||||
name = "github.com/asaskevich/govalidator"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f"
|
||||
version = "v9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d"
|
||||
name = "github.com/beorn7/perks"
|
||||
packages = ["quantile"]
|
||||
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/cenkalti/backoff"
|
||||
packages = ["."]
|
||||
revision = "61153c768f31ee5f130071d08fc82b85208528de"
|
||||
version = "v1.1.0"
|
||||
pruneopts = "UT"
|
||||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:35ac403a135b8a138870950720be1f0399e0a48387d4b5cca841b0ed8cf7a40b"
|
||||
name = "github.com/buger/jsonparser"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2cac668e8456b4284edb0715e17e2af02d3ec993"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2209584c0f7c9b68c23374e659357ab546e1b70eec2761f03280f69a8fd23d77"
|
||||
name = "github.com/cenkalti/backoff"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2ea60e5f094469f9e65adb9cd103795b73ae743e"
|
||||
version = "v2.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:24310b2d7f85873dde28d21687a40f7eba239970827c6a5d68cb529c9803de3a"
|
||||
name = "github.com/cisco/senml"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "448c9510575e1dd6f780cb10addbad998cf80418"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:fc8dbcc2a5de7c093e167828ebbdf551641761d2ad75431d3a167d467a264115"
|
||||
name = "github.com/containerd/continuity"
|
||||
packages = ["pathdriver"]
|
||||
revision = "d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371"
|
||||
pruneopts = "UT"
|
||||
revision = "c6cef34830231743494fe2969284df7b82cc0ad0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "UT"
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ca71cdedcb487a361fe2588afeb3d373c0dfe21feaac229270fd6de234bd3c5e"
|
||||
name = "github.com/dereulenspiegel/coap-mux"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "e137e310a99955be01d60b2237da89153ea40987"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55"
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
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]]
|
||||
digest = "1:87dcb59127512b84097086504c16595cf8fef35b9e0bfca565dfc06e198158d7"
|
||||
name = "github.com/docker/go-connections"
|
||||
packages = ["nat"]
|
||||
pruneopts = "UT"
|
||||
revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6f82cacd0af5921e99bf3f46748705239b36489464f4529a1589bc895764fb18"
|
||||
name = "github.com/docker/go-units"
|
||||
packages = ["."]
|
||||
revision = "0dadbb0345b35ec7ef35e228dabb8de89a65bf52"
|
||||
version = "v0.3.2"
|
||||
pruneopts = "UT"
|
||||
revision = "47565b4f722fb6ceae66b95f853feed578a4a51c"
|
||||
version = "v0.3.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f77ee3cd73cbf60434b4f72ec46a66773c555d46764399735b5f9a28505f3ece"
|
||||
name = "github.com/dustin/go-coap"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ddcc80675fa42611359d91a6dfa5aa57fb90e72b"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fsouza/go-dockerclient"
|
||||
digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a"
|
||||
name = "github.com/fatih/color"
|
||||
packages = ["."]
|
||||
revision = "ca33ff277b527ce11b793e62f9ba244129b01caf"
|
||||
version = "v1.2.0"
|
||||
pruneopts = "UT"
|
||||
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3fd00c9248f27f544a9bff7254adf110e1c791d067522025fcaac7528b55087a"
|
||||
name = "github.com/go-kit/kit"
|
||||
packages = [
|
||||
"endpoint",
|
||||
@@ -134,264 +141,632 @@
|
||||
"metrics",
|
||||
"metrics/internal/lv",
|
||||
"metrics/prometheus",
|
||||
"transport/http"
|
||||
"transport/grpc",
|
||||
"transport/http",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4dc7be5d2d12881735283bcab7352178e190fc71"
|
||||
version = "v0.6.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659"
|
||||
name = "github.com/go-logfmt/logfmt"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7c2fd446293ff7799cc496d3446e674ee67902d119f244de645caf95dff1bb98"
|
||||
name = "github.com/go-redis/redis"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
"internal/consistenthash",
|
||||
"internal/hashtag",
|
||||
"internal/pool",
|
||||
"internal/proto",
|
||||
"internal/singleflight",
|
||||
"internal/util",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f3bba01df2026fc865f7782948845db9cf44cf23"
|
||||
version = "v6.14.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c4a2528ccbcabf90f9f3c464a5fc9e302d592861bbfd0b7135a7de8a943d0406"
|
||||
name = "github.com/go-stack/stack"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8dfa0b1b7c4b7398d46340aef0f2a58aad5a75959eeeb79e023ab826f0c7f7f0"
|
||||
name = "github.com/go-zoo/bone"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "fd0aebc74e908868b09ac140fb5a53cb363884c1"
|
||||
version = "1.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:cf497f8273ddb581939f686791154c7d349322f01a8cade02f21504c036abc5f"
|
||||
name = "github.com/gocql/gocql"
|
||||
packages = [
|
||||
".",
|
||||
"internal/lru",
|
||||
"internal/murmur",
|
||||
"internal/streams",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "651d6b1f343c6e168e45d119dcfe8f71e5bafb32"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9a688317f3231e0175b3429033f44411906c0ce119361b7b5019d01375f8cff7"
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = ["proto"]
|
||||
pruneopts = "UT"
|
||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:17fe264ee908afc795734e8c4e63db2accabaf57326dbf21763a7d6b86096260"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4a0c6bb4805508a6287675fac876be2ac1182539ca8a32468d8128882e9d5009"
|
||||
name = "github.com/golang/snappy"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:43dd08a10854b2056e615d1b1d22ac94559d822e1f8b6fcc92c1a1057e85188e"
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/jinzhu/gorm"
|
||||
packages = [
|
||||
".",
|
||||
"dialects/postgres"
|
||||
]
|
||||
revision = "6ed508ec6a4ecb3531899a69cbc746ccf65a4166"
|
||||
version = "v1.9.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jinzhu/inflection"
|
||||
digest = "1:364b908b9b27b97ab838f2f6f1b1f46281fa29b978a037d72a9b1d4f6d940190"
|
||||
name = "github.com/hailocab/go-hostpool"
|
||||
packages = ["."]
|
||||
revision = "04140366298a54a039076d798123ffa108fff46c"
|
||||
pruneopts = "UT"
|
||||
revision = "e80d13ce29ede4452c43dea11e79b9bc8a15b478"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:51a6dbf048dc420793ec106b7c8d151d1ffb119285ce044cc41d2dbb7e23732e"
|
||||
name = "github.com/hokaccha/go-prettyjson"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "e6b9231a2b1cc5798b336fdbf99c16cd8ad1e0c9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ce530152667dc8ba823a586c1c81175aff2c4e15500520883a2727d0ed17c6f1"
|
||||
name = "github.com/influxdata/influxdb"
|
||||
packages = [
|
||||
"client/v2",
|
||||
"models",
|
||||
"pkg/escape",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "02d7d4f043b34ecb4e9b2dbec298c6f9450c2a32"
|
||||
version = "v1.5.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a64e323dc06b73892e5bb5d040ced475c4645d456038333883f58934abbf6f72"
|
||||
name = "github.com/kr/logfmt"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:1e537dfb7b4d543e25efa2c812084dd65736f8010979c1b7e167a5b876563199"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [
|
||||
".",
|
||||
"hstore",
|
||||
"oid"
|
||||
"oid",
|
||||
]
|
||||
revision = "88edab0803230a3898347e77b474f8c1820a1f20"
|
||||
pruneopts = "UT"
|
||||
revision = "d34b9ff171c21ad295489235aec8b6626023cd04"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6"
|
||||
name = "github.com/matttproud/golang_protobuf_extensions"
|
||||
packages = ["pbutil"]
|
||||
pruneopts = "UT"
|
||||
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e274a8817ced03e0eaab6615aee825e4bd374a31180a3f5c2c640403840cc9dc"
|
||||
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",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "8df56f107cdd485351eb2523992c3364114b4b2e"
|
||||
version = "v0.0.10"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6ec5ca70ff99467ff3b134ca05ae1d247e42a0377a7c27a756ba932f4dfc3a88"
|
||||
name = "github.com/nats-io/go-nats"
|
||||
packages = [
|
||||
".",
|
||||
"encoders/builtin",
|
||||
"util"
|
||||
"util",
|
||||
]
|
||||
revision = "d66cb54e6b7bdd93f0b28afc8450d84c780dfb68"
|
||||
version = "v1.4.0"
|
||||
pruneopts = "UT"
|
||||
revision = "062418ea1c2181f52dc0f954f6204370519a868b"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c3cd663f2f30b92536b9f290ac85c6310dae36a14cb8961553ae9ccf0d85ae41"
|
||||
name = "github.com/nats-io/nuid"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "289cccf02c178dc782430d534e3c1f5b72af807f"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ee4d4af67d93cc7644157882329023ce9a7bcfce956a079069a9405521c7cc8d"
|
||||
name = "github.com/opencontainers/go-digest"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
|
||||
version = "v1.0.0-rc1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:11db38d694c130c800d0aefb502fb02519e514dc53d9804ce51d1ad25ec27db6"
|
||||
name = "github.com/opencontainers/image-spec"
|
||||
packages = [
|
||||
"specs-go",
|
||||
"specs-go/v1"
|
||||
"specs-go/v1",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d60099175f88c47cd379c4738d158884749ed235"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1869683e323ebff2bdf8adcb560f82bf6f8d94019d35099e3403f7df12e9c07e"
|
||||
name = "github.com/opencontainers/runc"
|
||||
packages = [
|
||||
"libcontainer/system",
|
||||
"libcontainer/user"
|
||||
"libcontainer/user",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "baf6536d6259209c3edfa2b22237af82942d3dfa"
|
||||
version = "v0.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3b830e06971be8448e7c9e57c9cc15ce53ab7e8846e7b7c39110a177c7fae466"
|
||||
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",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "2e92e7784b6fb199fd168aa46269a2f1b34f299e"
|
||||
version = "v3.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = "UT"
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d14a5f4bfecf017cb780bdde1b6483e5deb87e12c332544d2c430eda58734bcb"
|
||||
name = "github.com/prometheus/client_golang"
|
||||
packages = [
|
||||
"prometheus",
|
||||
"prometheus/promhttp"
|
||||
"prometheus/promhttp",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "c5b7fccd204277076155f10851dad72b76a49317"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:32d10bdfa8f09ecf13598324dba86ab891f11db3c538b6a34d1c3b5b99d7c36b"
|
||||
name = "github.com/prometheus/client_model"
|
||||
packages = ["go"]
|
||||
pruneopts = "UT"
|
||||
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e469cd65badf7694aeb44874518606d93c1d59e7735d3754ad442782437d3cc3"
|
||||
name = "github.com/prometheus/common"
|
||||
packages = [
|
||||
"expfmt",
|
||||
"internal/bitbucket.org/ww/goautoneg",
|
||||
"model"
|
||||
"model",
|
||||
]
|
||||
revision = "e4aa40a9169a88835b849a6efb71e05dc04b88f0"
|
||||
pruneopts = "UT"
|
||||
revision = "d811d2e9bf898806ecfb6ef6296774b13ffc314c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:570784e0ddbf67f14087c6d8cfb7b999b9c55e75518a755e88cb202566ea8a17"
|
||||
name = "github.com/prometheus/procfs"
|
||||
packages = [
|
||||
".",
|
||||
"internal/util",
|
||||
"nfs",
|
||||
"xfs"
|
||||
"xfs",
|
||||
]
|
||||
revision = "54d17b57dd7d4a3aa092476596b3f8a933bde349"
|
||||
pruneopts = "UT"
|
||||
revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c9f218adb858f881ce84d0c3d6ad9f65b40d7f31c705c8f00b04106294cb006e"
|
||||
name = "github.com/rubenv/sql-migrate"
|
||||
packages = [
|
||||
".",
|
||||
"sqlparse",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "081fe17d19ff4e2dd9f5a0c1158e6bcf74da6906"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:274f67cb6fed9588ea2521ecdac05a6d62a8c51c074c1fccc6a49a40ba80e925"
|
||||
name = "github.com/satori/go.uuid"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9e9193aa51197513b3abcb108970d831fbcf40ef96aa845c4f03276e1fa316d2"
|
||||
name = "github.com/sirupsen/logrus"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
|
||||
version = "v1.0.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f7805d1cc7776d00da2aa3dbacf85927e58e6dad630ba9f3ea5b92d719f96269"
|
||||
name = "github.com/sony/gobreaker"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "e9556a45379ef1da12e54847edb2fb3d7d566f36"
|
||||
version = "0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7e8d267900c7fa7f35129a2a37596e38ed0f11ca746d6d9ba727980ee138f9f6"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
packages = [
|
||||
"assert",
|
||||
"require",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:03aa6e485e528acb119fb32901cf99582c380225fc7d5a02758e08b180cb56c3"
|
||||
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"
|
||||
pruneopts = "UT"
|
||||
revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7310f5459b88177d24e26c5ec7deb100b78ec03c0437d7a7aaec3c7eac333a55"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"bcrypt",
|
||||
"blowfish",
|
||||
"ssh/terminal"
|
||||
"pbkdf2",
|
||||
"ssh/terminal",
|
||||
]
|
||||
revision = "374053ea96cb300f8671b8d3b07edeeb06e203b4"
|
||||
pruneopts = "UT"
|
||||
revision = "2d027ae1dddd4694d54f7a8b6cbe78dca8720226"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:49131f79ef90ca701638024267ba6e3b5cd0ce387e0b3ec79961a42430873db9"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp"
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"trace",
|
||||
]
|
||||
revision = "24dd3780ca4f75fed9f321890729414a4b5d3f13"
|
||||
pruneopts = "UT"
|
||||
revision = "f73e4c9ed3b7ebdd5f699a16a880c2b1994e50dd"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e0140c0c868c6e0f01c0380865194592c011fe521d6e12d78bfd33e756fe018a"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["semaphore"]
|
||||
pruneopts = "UT"
|
||||
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:66e34aa65b83061616603ba83517a4523c7261e18afd01c769dd215614bf7d70"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows"
|
||||
"windows",
|
||||
]
|
||||
revision = "01acb38716e021ed1fc03a602bdb5838e1358c5e"
|
||||
pruneopts = "UT"
|
||||
revision = "7dfd1290c7917b7ba22824b9d24954ab3002fe24"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
||||
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",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:cd018653a358d4b743a9d3bee89e825521f2ab2f2ec0770164bf7632d8d73ab7"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
pruneopts = "UT"
|
||||
revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bb5ad466f9c22e1d7f34537a408ca215ee192e759979723b2b5e3f26dc80fd1f"
|
||||
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",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "41344da2231b913fa3d983840a57a6b1b7b631a1"
|
||||
version = "v1.12.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fa9a7c0ef59217bd22f32eb7cc027894d73f340a5633258cc079dec025db6a7f"
|
||||
name = "gopkg.in/gorp.v1"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c87af80f3cc5036b55b83d77171e156791085e2e"
|
||||
version = "v1.7.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2d1fbdc6777e5408cabeb02bf336305e724b925ff4546ded0fa8715a7267922a"
|
||||
name = "gopkg.in/inf.v0"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
|
||||
version = "v0.9.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:784e96fed7cbc35a3fdefb802ed2f841c16dbc3cf6c02f6422cc94ff45d011fc"
|
||||
name = "gopkg.in/ory-am/dockertest.v3"
|
||||
packages = ["."]
|
||||
revision = "15c8e8835bba04e0d7c2b57958ffe294d5e643dc"
|
||||
version = "v3.1.6"
|
||||
pruneopts = "UT"
|
||||
revision = "2e92e7784b6fb199fd168aa46269a2f1b34f299e"
|
||||
version = "v3.3.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "c1b28e90e21e838cec2d16c95d8eb414b5de9bbe9667acb01b5375db047cc6db"
|
||||
input-imports = [
|
||||
"github.com/asaskevich/govalidator",
|
||||
"github.com/cisco/senml",
|
||||
"github.com/dereulenspiegel/coap-mux",
|
||||
"github.com/dgrijalva/jwt-go",
|
||||
"github.com/dustin/go-coap",
|
||||
"github.com/fatih/color",
|
||||
"github.com/go-kit/kit/endpoint",
|
||||
"github.com/go-kit/kit/log",
|
||||
"github.com/go-kit/kit/metrics",
|
||||
"github.com/go-kit/kit/metrics/prometheus",
|
||||
"github.com/go-kit/kit/transport/grpc",
|
||||
"github.com/go-kit/kit/transport/http",
|
||||
"github.com/go-redis/redis",
|
||||
"github.com/go-zoo/bone",
|
||||
"github.com/gocql/gocql",
|
||||
"github.com/gogo/protobuf/proto",
|
||||
"github.com/golang/protobuf/proto",
|
||||
"github.com/gorilla/websocket",
|
||||
"github.com/hokaccha/go-prettyjson",
|
||||
"github.com/influxdata/influxdb/client/v2",
|
||||
"github.com/lib/pq",
|
||||
"github.com/mongodb/mongo-go-driver/bson",
|
||||
"github.com/mongodb/mongo-go-driver/mongo",
|
||||
"github.com/mongodb/mongo-go-driver/mongo/findopt",
|
||||
"github.com/nats-io/go-nats",
|
||||
"github.com/prometheus/client_golang/prometheus",
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp",
|
||||
"github.com/rubenv/sql-migrate",
|
||||
"github.com/satori/go.uuid",
|
||||
"github.com/sony/gobreaker",
|
||||
"github.com/spf13/cobra",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/require",
|
||||
"golang.org/x/crypto/bcrypt",
|
||||
"golang.org/x/net/context",
|
||||
"google.golang.org/grpc",
|
||||
"google.golang.org/grpc/codes",
|
||||
"google.golang.org/grpc/status",
|
||||
"gopkg.in/ory-am/dockertest.v3",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
+33
-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,30 @@
|
||||
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"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/dereulenspiegel/coap-mux"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/dustin/go-coap"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
+14
-13
@@ -7,7 +7,7 @@
|
||||
|
||||
[[drasko]]
|
||||
Name = "Drasko DRASKOVIC"
|
||||
Email = "drasko.draskovic@mainflux.com"
|
||||
Email = "drasko@mainflux.com"
|
||||
GitHub = "drasko"
|
||||
|
||||
# However, this role serves only in dead-lock events, or in a special and very rare cases
|
||||
@@ -24,22 +24,23 @@
|
||||
|
||||
[maintainers]
|
||||
|
||||
[[aleksandar]]
|
||||
Name = "Aleksandar NOVAKOVIC"
|
||||
Email = "aleksandar@mainflux.com"
|
||||
GitHub = "anovakovic01"
|
||||
|
||||
[[dusan]]
|
||||
Name = "Dusan BOROVCANIN"
|
||||
Email = "dusan@mainflux.com"
|
||||
GitHub = "dusanb94"
|
||||
|
||||
[[manuel]]
|
||||
Name = "Manuel IMPERIALE"
|
||||
Email = "manuel.imperiale@gmail.com"
|
||||
Email = "manuel@mainflux.com"
|
||||
GitHub = "manuIO"
|
||||
|
||||
[[janko]]
|
||||
Name = "Janko ISIDOROVIC"
|
||||
Email = "janko.isidorovic@mainflux.com"
|
||||
GitHub = "janko-isidorovic"
|
||||
|
||||
[[nikola]]
|
||||
Name = "Nikola MARCETIC"
|
||||
Email = "nikola.marcetic@mainflux.com"
|
||||
Email = "nikola@mainflux.com"
|
||||
GitHub = "nmarcetic"
|
||||
|
||||
[[dejan]]
|
||||
Name = "Dejan MIJIC"
|
||||
Email = "dejan.mijic@mainflux.com"
|
||||
GitHub = "mijicd"
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
BUILD_DIR = build
|
||||
SERVICES = manager http normalizer ws
|
||||
SERVICES = users things http normalizer ws coap influxdb-writer influxdb-reader mongodb-writer mongodb-reader cassandra-writer cassandra-reader cli
|
||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
|
||||
CGO_ENABLED ?= 0
|
||||
GOOS ?= linux
|
||||
|
||||
@@ -9,34 +10,58 @@ define compile_service
|
||||
endef
|
||||
|
||||
define make_docker
|
||||
docker build --build-arg SVC_NAME=$(subst docker_,,$(1)) --tag=mainflux/$(subst docker_,,$(1)) -f docker/Dockerfile .
|
||||
docker build --no-cache --build-arg SVC_NAME=$(subst docker_,,$(1)) --tag=mainflux/$(subst docker_,,$(1)) -f docker/Dockerfile .
|
||||
endef
|
||||
|
||||
all: $(SERVICES)
|
||||
define make_docker_dev
|
||||
docker build --build-arg SVC_NAME=$(subst docker_dev_,,$(1)) --tag=mainflux/$(subst docker_dev_,,$(1)) -f docker/Dockerfile.dev ./build
|
||||
endef
|
||||
|
||||
.PHONY: all $(SERVICES) dockers latest release
|
||||
all: $(SERVICES) mqtt
|
||||
|
||||
.PHONY: all $(SERVICES) dockers dockers_dev 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 .
|
||||
|
||||
$(DOCKERS_DEV):
|
||||
$(call make_docker_dev,$(@))
|
||||
|
||||
dockers_dev: $(DOCKERS_DEV)
|
||||
|
||||
mqtt:
|
||||
cd mqtt && npm install
|
||||
|
||||
define docker_push
|
||||
for svc in $(SERVICES); do \
|
||||
docker push mainflux/$$svc:$(1); \
|
||||
done
|
||||
docker push mainflux/dashflux:$(1)
|
||||
docker push mainflux/mqtt:$(1)
|
||||
endef
|
||||
|
||||
latest: dockers
|
||||
for svc in $(SERVICES); do \
|
||||
docker push mainflux/$$svc; \
|
||||
done
|
||||
$(call docker_push,latest)
|
||||
|
||||
release:
|
||||
$(eval version = $(shell git describe --abbrev=0 --tags))
|
||||
@@ -44,5 +69,13 @@ release:
|
||||
$(MAKE) dockers
|
||||
for svc in $(SERVICES); do \
|
||||
docker tag mainflux/$$svc mainflux/$$svc:$(version); \
|
||||
docker push mainflux/$$svc:$(version); \
|
||||
done
|
||||
docker tag mainflux/dashflux mainflux/dashflux:$(version)
|
||||
docker tag mainflux/mqtt mainflux/mqtt:$(version)
|
||||
$(call docker_push,$(version))
|
||||
|
||||
rundev:
|
||||
cd scripts && ./run.sh
|
||||
|
||||
run:
|
||||
docker-compose -f docker/docker-compose.yml up
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
mfxsdk "github.com/mainflux/mainflux/sdk/go"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var channel mfxsdk.Channel
|
||||
if err := json.Unmarshal([]byte(args[0]), &channel); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := sdk.CreateChannel(channel, args[1])
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(id)
|
||||
},
|
||||
},
|
||||
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" {
|
||||
l, err := sdk.Channels(args[1], uint64(Offset), uint64(Limit))
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(l)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := sdk.Channel(args[0], args[1])
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(c)
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "update",
|
||||
Short: "update <JSON_string> <user_auth_token>",
|
||||
Long: `Updates channel record`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
|
||||
var channel mfxsdk.Channel
|
||||
if err := json.Unmarshal([]byte(args[0]), &channel); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := sdk.UpdateChannel(channel, args[1]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
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
|
||||
}
|
||||
|
||||
if err := sdk.DeleteChannel(args[0], args[1]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewChannelsCmd returns channels command.
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
const contentTypeSenml = "application/senml+json"
|
||||
|
||||
var cmdMessages = []cobra.Command{
|
||||
cobra.Command{
|
||||
Use: "send",
|
||||
Short: "send <channel_id> <JSON_string> <thing_token>",
|
||||
Long: `Sends message on the channel`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
|
||||
if err := sdk.SendMessage(args[0], args[1], args[2]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewMessagesCmd returns messages command.
|
||||
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
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import mfxsdk "github.com/mainflux/mainflux/sdk/go"
|
||||
|
||||
// Keep SDK handle in global var
|
||||
var sdk mfxsdk.SDK
|
||||
|
||||
// SetSDK sets mainflux SDK instance.
|
||||
func SetSDK(s mfxsdk.SDK) {
|
||||
sdk = s
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
mfxsdk "github.com/mainflux/mainflux/sdk/go"
|
||||
"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
|
||||
}
|
||||
|
||||
var thing mfxsdk.Thing
|
||||
if err := json.Unmarshal([]byte(args[0]), &thing); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := sdk.CreateThing(thing, args[1])
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(id)
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "get",
|
||||
Short: "get all/<thing_id> <user_auth_token>",
|
||||
Long: `Get all things or thing by id`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
|
||||
if args[0] == "all" {
|
||||
l, err := sdk.Things(args[1], uint64(Offset), uint64(Limit))
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
flush(l)
|
||||
return
|
||||
}
|
||||
|
||||
t, err := sdk.Thing(args[0], args[1])
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(t)
|
||||
},
|
||||
},
|
||||
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
|
||||
}
|
||||
|
||||
if err := sdk.DeleteThing(args[0], args[1]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
cobra.Command{
|
||||
Use: "update",
|
||||
Short: "update <JSON_string> <user_auth_token>",
|
||||
Long: `Update thing record`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsage(cmd.Short)
|
||||
return
|
||||
}
|
||||
|
||||
var thing mfxsdk.Thing
|
||||
if err := json.Unmarshal([]byte(args[0]), &thing); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := sdk.UpdateThing(thing, args[1]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
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
|
||||
}
|
||||
|
||||
if err := sdk.ConnectThing(args[0], args[1], args[2]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
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
|
||||
}
|
||||
|
||||
if err := sdk.DisconnectThing(args[0], args[1], args[2]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewThingsCmd returns things command.
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
mfxsdk "github.com/mainflux/mainflux/sdk/go"
|
||||
"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
|
||||
}
|
||||
|
||||
user := mfxsdk.User{
|
||||
Email: args[0],
|
||||
Password: args[1],
|
||||
}
|
||||
if err := sdk.CreateUser(user); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
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
|
||||
}
|
||||
|
||||
user := mfxsdk.User{
|
||||
Email: args[0],
|
||||
Password: args[1],
|
||||
}
|
||||
token, err := sdk.CreateToken(user)
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(token)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewUsersCmd returns users command.
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var (
|
||||
// Limit query parameter
|
||||
Limit uint = 10
|
||||
// Offset query parameter
|
||||
Offset uint
|
||||
)
|
||||
|
||||
func flush(i interface{}) {
|
||||
fmt.Printf("%s", color.BlueString(spew.Sdump(i)))
|
||||
}
|
||||
|
||||
func logUsage(u string) {
|
||||
fmt.Printf(color.YellowString("Usage: %s\n"), u)
|
||||
}
|
||||
|
||||
func logError(err error) {
|
||||
fmt.Printf("%s\n", color.RedString(err.Error()))
|
||||
}
|
||||
|
||||
func logOK() {
|
||||
fmt.Printf("%s\n", color.GreenString("OK"))
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package cli
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// NewVersionCmd returns version command.
|
||||
func NewVersionCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get version of Mainflux Things Service",
|
||||
Long: `Mainflux server health check`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
v, err := sdk.Version()
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
|
||||
flush(v)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/gocql/gocql"
|
||||
"github.com/mainflux/mainflux"
|
||||
"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 = ","
|
||||
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defCluster = "127.0.0.1"
|
||||
defKeyspace = "mainflux"
|
||||
defThingsURL = "localhost:8181"
|
||||
|
||||
envLogLevel = "MF_CASSANDRA_READER_LOG_LEVEL"
|
||||
envPort = "MF_CASSANDRA_READER_PORT"
|
||||
envCluster = "MF_CASSANDRA_READER_DB_CLUSTER"
|
||||
envKeyspace = "MF_CASSANDRA_READER_DB_KEYSPACE"
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
logLevel string
|
||||
port string
|
||||
cluster string
|
||||
keyspace string
|
||||
thingsURL string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
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{
|
||||
logLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
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 logger.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 logger.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 logger.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 logger.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,143 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/gocql/gocql"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
"github.com/mainflux/mainflux/writers/cassandra"
|
||||
"github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
queue = "cassandra-writer"
|
||||
sep = ","
|
||||
|
||||
defNatsURL = nats.DefaultURL
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defCluster = "127.0.0.1"
|
||||
defKeyspace = "mainflux"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envLogLevel = "MF_CASSANDRA_WRITER_LOG_LEVEL"
|
||||
envPort = "MF_CASSANDRA_WRITER_PORT"
|
||||
envCluster = "MF_CASSANDRA_WRITER_DB_CLUSTER"
|
||||
envKeyspace = "MF_CASSANDRA_WRITER_DB_KEYSPACE"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
natsURL string
|
||||
logLevel string
|
||||
port string
|
||||
cluster string
|
||||
keyspace string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
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, repo, queue, logger); 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),
|
||||
logLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
port: mainflux.Env(envPort, defPort),
|
||||
cluster: mainflux.Env(envCluster, defCluster),
|
||||
keyspace: mainflux.Env(envKeyspace, defKeyspace),
|
||||
}
|
||||
}
|
||||
|
||||
func connectToNATS(url string, logger logger.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 logger.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 logger.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 logger.Logger) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("Cassandra writer service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, cassandra.MakeHandler())
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/mainflux/mainflux/cli"
|
||||
"github.com/mainflux/mainflux/sdk/go"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
msgContentType := string(sdk.CTJSONSenML)
|
||||
sdkConf := sdk.Config{
|
||||
BaseURL: "http://localhost",
|
||||
UsersPrefix: "",
|
||||
ThingsPrefix: "",
|
||||
HTTPAdapterPrefix: "http",
|
||||
MsgContentType: sdk.ContentType(msgContentType),
|
||||
TLSVerification: false,
|
||||
}
|
||||
|
||||
// Root
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "mainflux-cli",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
sdkConf.MsgContentType = sdk.ContentType(msgContentType)
|
||||
s := sdk.NewSDK(sdkConf)
|
||||
cli.SetSDK(s)
|
||||
},
|
||||
}
|
||||
|
||||
// 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(
|
||||
&sdkConf.BaseURL,
|
||||
"mainflux-url",
|
||||
"m",
|
||||
sdkConf.BaseURL,
|
||||
"Mainflux host URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&sdkConf.UsersPrefix,
|
||||
"users-prefix",
|
||||
"u",
|
||||
sdkConf.UsersPrefix,
|
||||
"Mainflux users service prefix",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&sdkConf.ThingsPrefix,
|
||||
"things-prefix",
|
||||
"t",
|
||||
sdkConf.ThingsPrefix,
|
||||
"Mainflux things service prefix",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&sdkConf.HTTPAdapterPrefix,
|
||||
"http-prefix",
|
||||
"a",
|
||||
sdkConf.HTTPAdapterPrefix,
|
||||
"Mainflux http adapter prefix",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&msgContentType,
|
||||
"content-type",
|
||||
"c",
|
||||
msgContentType,
|
||||
"Mainflux message content type",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(
|
||||
&sdkConf.TLSVerification,
|
||||
"insecure",
|
||||
"i",
|
||||
sdkConf.TLSVerification,
|
||||
"Do not check for TLS cert",
|
||||
)
|
||||
|
||||
// Client and Channels Flags
|
||||
rootCmd.PersistentFlags().UintVarP(
|
||||
&cli.Limit,
|
||||
"limit",
|
||||
"l",
|
||||
100,
|
||||
"limit query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().UintVarP(
|
||||
&cli.Offset,
|
||||
"offset",
|
||||
"o",
|
||||
0,
|
||||
"offset query parameter",
|
||||
)
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
+84
-38
@@ -1,50 +1,101 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
gocoap "github.com/dustin/go-coap"
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/coap"
|
||||
"github.com/mainflux/mainflux/coap/api"
|
||||
"github.com/mainflux/mainflux/coap/nats"
|
||||
logger "github.com/mainflux/mainflux/logger"
|
||||
thingsapi "github.com/mainflux/mainflux/things/api/grpc"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
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"
|
||||
defPort = "5683"
|
||||
defNatsURL = broker.DefaultURL
|
||||
defThingsURL = "localhost:8181"
|
||||
defLogLevel = "error"
|
||||
|
||||
envPort = "MF_COAP_ADAPTER_PORT"
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
envLogLevel = "MF_COAP_ADAPTER_LOG_LEVEL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Port int
|
||||
NatsURL string
|
||||
port string
|
||||
natsURL string
|
||||
thingsURL string
|
||||
logLevel string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, _ := zap.NewProduction()
|
||||
defer logger.Sync() // flushes buffer, if any
|
||||
logger, err := logger.New(os.Stdout, cfg.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
nc := connectToNats(cfg, logger)
|
||||
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()
|
||||
|
||||
pub := nats.NewMessagePublisher(nc)
|
||||
ca := adapter.NewCoAPAdapter(logger, pub)
|
||||
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()
|
||||
|
||||
nc.Subscribe("src.http", ca.BridgeHandler)
|
||||
nc.Subscribe("src.mqtt", ca.BridgeHandler)
|
||||
cc := thingsapi.NewClient(conn)
|
||||
respChan := make(chan string, 10000)
|
||||
pubsub := nats.New(nc)
|
||||
svc := coap.New(pubsub, respChan)
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
|
||||
svc = api.MetricsMiddleware(
|
||||
svc,
|
||||
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "coap_adapter",
|
||||
Subsystem: "api",
|
||||
Name: "request_count",
|
||||
Help: "Number of requests received.",
|
||||
}, []string{"method"}),
|
||||
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "coap_adapter",
|
||||
Subsystem: "api",
|
||||
Name: "request_latency_microseconds",
|
||||
Help: "Total duration of requests in microseconds.",
|
||||
}, []string{"method"}),
|
||||
)
|
||||
|
||||
errs := make(chan error, 2)
|
||||
|
||||
go func() {
|
||||
coapAddr := fmt.Sprintf(":%d", cfg.Port)
|
||||
errs <- ca.Serve(coapAddr)
|
||||
}()
|
||||
go startHTTPServer(cfg.port, logger, errs)
|
||||
go startCOAPServer(cfg.port, svc, cc, respChan, logger, errs)
|
||||
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
@@ -52,32 +103,27 @@ func main() {
|
||||
errs <- fmt.Errorf("%s", <-c)
|
||||
}()
|
||||
|
||||
c := <-errs
|
||||
logger.Info("terminated", zap.String("error", c.Error()))
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("CoAP adapter terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() *config {
|
||||
return &config{
|
||||
NatsURL: env(envNatsURL, defNatsURL),
|
||||
Port: port,
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
thingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
natsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
port: mainflux.Env(envPort, defPort),
|
||||
logLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
}
|
||||
}
|
||||
|
||||
func env(key, fallback string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return value
|
||||
func startHTTPServer(port string, logger logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
logger.Info(fmt.Sprintf("CoAP service started, exposed port %s", port))
|
||||
errs <- http.ListenAndServe(p, api.MakeHTTPHandler())
|
||||
}
|
||||
|
||||
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
|
||||
func startCOAPServer(port string, svc coap.Service, auth mainflux.ThingsServiceClient, respChan chan<- string, l logger.Logger, errs chan error) {
|
||||
p := fmt.Sprintf(":%s", port)
|
||||
l.Info(fmt.Sprintf("CoAP adapter service started, exposed port %s", port))
|
||||
errs <- gocoap.ListenAndServe("udp", p, api.MakeCOAPHandler(svc, auth, l, respChan))
|
||||
}
|
||||
|
||||
+48
-19
@@ -1,7 +1,15 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -12,35 +20,39 @@ import (
|
||||
adapter "github.com/mainflux/mainflux/http"
|
||||
"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"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
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"
|
||||
defLogLevel string = "error"
|
||||
defNatsURL string = broker.DefaultURL
|
||||
defThingsURL string = "localhost:8181"
|
||||
envPort string = "MF_HTTP_ADAPTER_PORT"
|
||||
envLogLevel string = "MF_HTTP_ADAPTER_LOG_LEVEL"
|
||||
envNatsURL string = "MF_NATS_URL"
|
||||
envThingsURL string = "MF_THINGS_URL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
ManagerURL string
|
||||
NatsURL string
|
||||
Port string
|
||||
ThingsURL string
|
||||
NatsURL string
|
||||
LogLevel string
|
||||
Port string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
ManagerURL: mainflux.Env(envManagerURL, defManagerURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
}
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
nc, err := broker.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
@@ -49,6 +61,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 +93,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() {
|
||||
@@ -87,3 +106,13 @@ func main() {
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("HTTP adapter terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
ThingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"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"
|
||||
"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"
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "8086"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
envLogLevel = "MF_INFLUX_READER_LOG_LEVEL"
|
||||
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
|
||||
LogLevel string
|
||||
Port string
|
||||
DBName string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg, clientCfg := loadConfigs()
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
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),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
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 logger.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 logger.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 logger.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,174 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
influxdata "github.com/influxdata/influxdb/client/v2"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
"github.com/mainflux/mainflux/writers/influxdb"
|
||||
"github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
queue = "influxdb-writer"
|
||||
|
||||
defNatsURL = nats.DefaultURL
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defBatchSize = "5000"
|
||||
defBatchTimeout = "5"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "8086"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envLogLevel = "MF_INFLUX_WRITER_LOG_LEVEL"
|
||||
envPort = "MF_INFLUX_WRITER_PORT"
|
||||
envBatchSize = "MF_INFLUX_WRITER_BATCH_SIZE"
|
||||
envBatchTimeout = "MF_INFLUX_WRITER_BATCH_TIMEOUT"
|
||||
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
|
||||
LogLevel string
|
||||
Port string
|
||||
BatchSize string
|
||||
BatchTimeout string
|
||||
DBName string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg, clientCfg := loadConfigs()
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
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()
|
||||
|
||||
batchTimeout, err := strconv.Atoi(cfg.BatchTimeout)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Invalid value for batch timeout: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
batchSize, err := strconv.Atoi(cfg.BatchSize)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Invalid value of batch size: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
timeout := time.Duration(batchTimeout) * time.Second
|
||||
repo, err := influxdb.New(client, cfg.DBName, batchSize, timeout)
|
||||
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, repo, queue, logger); 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),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
BatchSize: mainflux.Env(envBatchSize, defBatchSize),
|
||||
BatchTimeout: mainflux.Env(envBatchTimeout, defBatchTimeout),
|
||||
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 logger.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,141 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
"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"
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "27017"
|
||||
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
envLogLevel = "MF_MONGO_READER_LOG_LEVEL"
|
||||
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
|
||||
logLevel string
|
||||
port string
|
||||
dbName string
|
||||
dbHost string
|
||||
dbPort string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfigs()
|
||||
logger, err := logger.New(os.Stdout, cfg.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
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),
|
||||
logLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
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 logger.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 logger.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 logger.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 logger.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,132 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
"github.com/mainflux/mainflux/writers"
|
||||
"github.com/mainflux/mainflux/writers/mongodb"
|
||||
"github.com/mongodb/mongo-go-driver/mongo"
|
||||
"github.com/nats-io/go-nats"
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
queue = "mongodb-writer"
|
||||
|
||||
defNatsURL = nats.DefaultURL
|
||||
defLogLevel = "error"
|
||||
defPort = "8180"
|
||||
defDBName = "mainflux"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "27017"
|
||||
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envLogLevel = "MF_MONGO_WRITER_LOG_LEVEL"
|
||||
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
|
||||
LogLevel string
|
||||
Port string
|
||||
DBName string
|
||||
DBHost string
|
||||
DBPort string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfigs()
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
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, repo, queue, logger); 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),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
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 logger.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())
|
||||
}
|
||||
+55
-31
@@ -1,54 +1,84 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"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
|
||||
defPort string = "8180"
|
||||
envNatsURL string = "MF_NATS_URL"
|
||||
envPort string = "MF_NORMALIZER_PORT"
|
||||
defNatsURL string = broker.DefaultURL
|
||||
defLogLevel string = "error"
|
||||
defPort string = "8180"
|
||||
envNatsURL string = "MF_NATS_URL"
|
||||
envLogLevel string = "MF_NORMALIZER_LOG_LEVEL"
|
||||
envPort string = "MF_NORMALIZER_PORT"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
NatsURL string
|
||||
Port string
|
||||
NatsURL string
|
||||
LogLevel string
|
||||
Port string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
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,22 +87,16 @@ 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))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/mainflux/mainflux"
|
||||
"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"
|
||||
rediscache "github.com/mainflux/mainflux/things/redis"
|
||||
"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 (
|
||||
defLogLevel = "error"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "5432"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
defDBName = "things"
|
||||
defCacheURL = "localhost:6379"
|
||||
defCachePass = ""
|
||||
defCacheDB = "0"
|
||||
defHTTPPort = "8180"
|
||||
defGRPCPort = "8181"
|
||||
defUsersURL = "localhost:8181"
|
||||
envLogLevel = "MF_THINGS_LOG_LEVEL"
|
||||
envDBHost = "MF_THINGS_DB_HOST"
|
||||
envDBPort = "MF_THINGS_DB_PORT"
|
||||
envDBUser = "MF_THINGS_DB_USER"
|
||||
envDBPass = "MF_THINGS_DB_PASS"
|
||||
envDBName = "MF_THINGS_DB"
|
||||
envCacheURL = "MF_THINGS_CACHE_URL"
|
||||
envCachePass = "MF_THINGS_CACHE_PASS"
|
||||
envCacheDB = "MF_THINGS_CACHE_DB"
|
||||
envHTTPPort = "MF_THINGS_HTTP_PORT"
|
||||
envGRPCPort = "MF_THINGS_GRPC_PORT"
|
||||
envUsersURL = "MF_USERS_URL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
DBName string
|
||||
CacheURL string
|
||||
CachePass string
|
||||
CacheDB string
|
||||
HTTPPort string
|
||||
GRPCPort string
|
||||
UsersURL string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
cache := connectToCache(cfg.CacheURL, cfg.CachePass, cfg.CacheDB, logger)
|
||||
|
||||
db := connectToDB(cfg, logger)
|
||||
defer db.Close()
|
||||
|
||||
conn := connectToUsersService(cfg.UsersURL, logger)
|
||||
defer conn.Close()
|
||||
|
||||
svc := newService(conn, db, cache, 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{
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
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),
|
||||
CacheURL: mainflux.Env(envCacheURL, defCacheURL),
|
||||
CachePass: mainflux.Env(envCachePass, defCachePass),
|
||||
CacheDB: mainflux.Env(envCacheDB, defCacheDB),
|
||||
HTTPPort: mainflux.Env(envHTTPPort, defHTTPPort),
|
||||
GRPCPort: mainflux.Env(envGRPCPort, defGRPCPort),
|
||||
UsersURL: mainflux.Env(envUsersURL, defUsersURL),
|
||||
}
|
||||
}
|
||||
|
||||
func connectToCache(cacheURL, cachePass string, cacheDB string, logger logger.Logger) *redis.Client {
|
||||
|
||||
db, err := strconv.Atoi(cacheDB)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to cache: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return redis.NewClient(&redis.Options{
|
||||
Addr: cacheURL,
|
||||
Password: cachePass,
|
||||
DB: db,
|
||||
})
|
||||
}
|
||||
|
||||
func connectToDB(cfg config, logger logger.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 logger.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, client *redis.Client, logger logger.Logger) things.Service {
|
||||
users := usersapi.NewClient(conn)
|
||||
thingsRepo := postgres.NewThingRepository(db, logger)
|
||||
channelsRepo := postgres.NewChannelRepository(db, logger)
|
||||
chanCache := rediscache.NewChannelCache(client)
|
||||
thingCache := rediscache.NewThingCache(client)
|
||||
idp := uuid.New()
|
||||
|
||||
svc := things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, 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 logger.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 logger.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,157 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
|
||||
"github.com/mainflux/mainflux"
|
||||
"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 (
|
||||
defLogLevel = "error"
|
||||
defDBHost = "localhost"
|
||||
defDBPort = "5432"
|
||||
defDBUser = "mainflux"
|
||||
defDBPass = "mainflux"
|
||||
defDBName = "users"
|
||||
defHTTPPort = "8180"
|
||||
defGRPCPort = "8181"
|
||||
defSecret = "users"
|
||||
envLogLevel = "MF_USERS_LOG_LEVEL"
|
||||
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 {
|
||||
LogLevel string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBUser string
|
||||
DBPass string
|
||||
DBName string
|
||||
HTTPPort string
|
||||
GRPCPort string
|
||||
Secret string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
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{
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
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 logger.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 logger.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 logger.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 logger.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)
|
||||
}
|
||||
+46
-20
@@ -1,7 +1,15 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -9,39 +17,41 @@ 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"
|
||||
"github.com/mainflux/mainflux/logger"
|
||||
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"
|
||||
defLogLevel = "error"
|
||||
defNatsURL = broker.DefaultURL
|
||||
defThingsURL = "localhost:8181"
|
||||
envPort = "MF_WS_ADAPTER_PORT"
|
||||
envLogLevel = "MF_WS_ADAPTER_LOG_LEVEL"
|
||||
envNatsURL = "MF_NATS_URL"
|
||||
envThingsURL = "MF_THINGS_URL"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
ManagerURL string
|
||||
NatsURL string
|
||||
Port string
|
||||
ThingsURL string
|
||||
NatsURL string
|
||||
LogLevel string
|
||||
Port string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{
|
||||
ManagerURL: mainflux.Env(envManagerURL, defManagerURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
cfg := loadConfig()
|
||||
|
||||
logger, err := logger.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
logger := log.New(os.Stdout)
|
||||
|
||||
nc, err := broker.Connect(cfg.NatsURL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to connect to NATS: %s", err))
|
||||
@@ -49,6 +59,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 +90,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() {
|
||||
@@ -86,3 +103,12 @@ func main() {
|
||||
err = <-errs
|
||||
logger.Error(fmt.Sprintf("WebSocket adapter terminated: %s", err))
|
||||
}
|
||||
|
||||
func loadConfig() config {
|
||||
return config{
|
||||
ThingsURL: mainflux.Env(envThingsURL, defThingsURL),
|
||||
NatsURL: mainflux.Env(envNatsURL, defNatsURL),
|
||||
LogLevel: mainflux.Env(envLogLevel, defLogLevel),
|
||||
Port: mainflux.Env(envPort, defPort),
|
||||
}
|
||||
}
|
||||
|
||||
+22
-13
@@ -9,9 +9,12 @@ 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 |
|
||||
| Variable | Description | Default |
|
||||
|---------------------------|------------------------|-----------------------|
|
||||
| MF_COAP_ADAPTER_PORT | Service listening port | 5683 |
|
||||
| MF_NATS_URL | NATS instance URL | nats://localhost:4222 |
|
||||
| MF_THINGS_URL | Things service URL | localhost:8181 |
|
||||
| MF_COAP_ADAPTER_LOG_LEVEL | Service log level | error |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -22,31 +25,37 @@ a compose file template that can be used to deploy the service container locally
|
||||
version: "2"
|
||||
services:
|
||||
adapter:
|
||||
image: mainflux/coap-adapter:[version]
|
||||
image: mainflux/coap:[version]
|
||||
container_name: [instance name]
|
||||
ports:
|
||||
- [host machine port]:5683
|
||||
- [host machine port]:[configured port]
|
||||
environment:
|
||||
COAP_ADAPTER_NATS_URL: [NATS instance URL]
|
||||
MF_COAP_ADAPTER_PORT: [Service HTTP port]
|
||||
MF_NATS_URL: [NATS instance URL]
|
||||
MF_THINGS_URL: [Things service URL]
|
||||
MF_COAP_ADAPTER_LOG_LEVEL: [Service log level]
|
||||
```
|
||||
|
||||
Running this service outside of container requires working instance of the NATS service.
|
||||
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
|
||||
cd $GOPATH/src/github.com/mainflux/mainflux
|
||||
|
||||
# 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
|
||||
# compile the http
|
||||
make coap
|
||||
|
||||
# copy binary to bin
|
||||
make install
|
||||
|
||||
# set the environment variables and run the service
|
||||
COAP_ADAPTER_NATS_URL=[NATS instance URL] app
|
||||
MF_THINGS_URL=[Things service URL] MF_NATS_URL=[NATS instance URL] MF_COAP_ADAPTER_PORT=[Service HTTP port] MF_COAP_ADAPTER_LOG_LEVEL=[Service log level] $GOBIN/mainflux-coap
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
For more information about service capabilities and its usage, please check out
|
||||
the [API documentation](swagger.yaml).
|
||||
|
||||
Since CoAP protocol does not support `Authorization` header (option), in order to send CoAP messages,
|
||||
client valid `authorization` value must be present in `Uri-Query` option.
|
||||
|
||||
+127
-38
@@ -1,61 +1,150 @@
|
||||
package adapter
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Package coap contains the domain concept definitions needed to support
|
||||
// Mainflux coap adapter service functionality. All constant values are taken
|
||||
// from RFC, and could be adjusted based on specific use case.
|
||||
package coap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-coap"
|
||||
"github.com/mainflux/mainflux"
|
||||
broker "github.com/nats-io/go-nats"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const protocol string = "coap"
|
||||
const (
|
||||
chanID = "id"
|
||||
keyHeader = "key"
|
||||
|
||||
type Observer struct {
|
||||
conn *net.UDPConn
|
||||
addr *net.UDPAddr
|
||||
message *coap.Message
|
||||
// AckRandomFactor is default ACK coefficient.
|
||||
AckRandomFactor = 1.5
|
||||
// AckTimeout is the amount of time to wait for a response.
|
||||
AckTimeout = 2000 * time.Millisecond
|
||||
// MaxRetransmit is the maximum number of times a message will be retransmitted.
|
||||
MaxRetransmit = 4
|
||||
)
|
||||
|
||||
var (
|
||||
errBadOption = errors.New("bad option")
|
||||
// ErrFailedMessagePublish indicates that message publishing failed.
|
||||
ErrFailedMessagePublish = errors.New("failed to publish message")
|
||||
|
||||
// ErrFailedSubscription indicates that client couldn't subscribe to specified channel.
|
||||
ErrFailedSubscription = errors.New("failed to subscribe to a channel")
|
||||
|
||||
// ErrFailedConnection indicates that service couldn't connect to message broker.
|
||||
ErrFailedConnection = errors.New("failed to connect to message broker")
|
||||
)
|
||||
|
||||
// Broker represents NATS broker instance.
|
||||
type Broker interface {
|
||||
mainflux.MessagePublisher
|
||||
|
||||
// Subscribes to channel with specified id and adds subscription to
|
||||
// service map of subscriptions under given ID.
|
||||
Subscribe(uint64, string, *Observer) error
|
||||
}
|
||||
|
||||
type CoAPAdapter struct {
|
||||
obsMap map[string][]Observer
|
||||
logger *zap.Logger
|
||||
pub mainflux.MessagePublisher
|
||||
// Service specifies coap service API.
|
||||
type Service interface {
|
||||
Broker
|
||||
// Unsubscribe method is used to stop observing resource.
|
||||
Unsubscribe(string)
|
||||
}
|
||||
|
||||
// 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),
|
||||
var _ Service = (*adapterService)(nil)
|
||||
|
||||
type adapterService struct {
|
||||
pubsub Broker
|
||||
obs map[string]*Observer
|
||||
obsLock sync.Mutex
|
||||
}
|
||||
|
||||
// New instantiates the CoAP adapter implementation.
|
||||
func New(pubsub Broker, responses <-chan string) Service {
|
||||
as := &adapterService{
|
||||
pubsub: pubsub,
|
||||
obs: make(map[string]*Observer),
|
||||
obsLock: sync.Mutex{},
|
||||
}
|
||||
|
||||
return ca
|
||||
go as.listenResponses(responses)
|
||||
return as
|
||||
}
|
||||
|
||||
// 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())
|
||||
func (svc *adapterService) get(obsID string) (*Observer, bool) {
|
||||
svc.obsLock.Lock()
|
||||
defer svc.obsLock.Unlock()
|
||||
|
||||
val, ok := svc.obs[obsID]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// 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))
|
||||
func (svc *adapterService) put(obsID string, o *Observer) {
|
||||
svc.obsLock.Lock()
|
||||
defer svc.obsLock.Unlock()
|
||||
|
||||
// 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
|
||||
val, ok := svc.obs[obsID]
|
||||
if ok {
|
||||
close(val.Cancel)
|
||||
}
|
||||
|
||||
svc.obs[obsID] = o
|
||||
}
|
||||
|
||||
func (svc *adapterService) remove(obsID string) {
|
||||
svc.obsLock.Lock()
|
||||
defer svc.obsLock.Unlock()
|
||||
|
||||
val, ok := svc.obs[obsID]
|
||||
if ok {
|
||||
close(val.Cancel)
|
||||
delete(svc.obs, obsID)
|
||||
}
|
||||
}
|
||||
|
||||
// ListenResponses method handles ACK messages received from client.
|
||||
func (svc *adapterService) listenResponses(responses <-chan string) {
|
||||
for {
|
||||
id := <-responses
|
||||
|
||||
val, ok := svc.get(id)
|
||||
if ok {
|
||||
val.StoreExpired(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *adapterService) Publish(msg mainflux.RawMessage) error {
|
||||
if err := svc.pubsub.Publish(msg); err != nil {
|
||||
switch err {
|
||||
case broker.ErrConnectionClosed, broker.ErrInvalidConnection:
|
||||
return ErrFailedConnection
|
||||
default:
|
||||
return ErrFailedMessagePublish
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Calling obsTransmit()")
|
||||
log.Println(m.Publisher, m.Protocol, m.Channel, m.Payload)
|
||||
ca.obsTransmit(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *adapterService) Subscribe(chanID uint64, obsID string, o *Observer) error {
|
||||
if err := svc.pubsub.Subscribe(chanID, obsID, o); err != nil {
|
||||
return ErrFailedSubscription
|
||||
}
|
||||
|
||||
// Put method removes Observer if already exists.
|
||||
svc.put(obsID, o)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *adapterService) Unsubscribe(obsID string) {
|
||||
svc.remove(obsID)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Package api contains API-related concerns: endpoint definitions, middlewares
|
||||
// and all resource representations.
|
||||
package api
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// +build !test
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/coap"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
)
|
||||
|
||||
var _ coap.Service = (*loggingMiddleware)(nil)
|
||||
|
||||
type loggingMiddleware struct {
|
||||
logger log.Logger
|
||||
svc coap.Service
|
||||
}
|
||||
|
||||
// LoggingMiddleware adds logging facilities to the adapter.
|
||||
func LoggingMiddleware(svc coap.Service, logger log.Logger) coap.Service {
|
||||
return &loggingMiddleware{logger, svc}
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Publish(msg mainflux.RawMessage) (err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method publish to channel %d took %s to complete", msg.Channel, time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Publish(msg)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Subscribe(chanID uint64, obsID string, o *coap.Observer) (err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method subscribe to channel %d for client %s took %s to complete", chanID, obsID, time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Subscribe(chanID, obsID, o)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Unsubscribe(obsID string) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method unsubscribe for the client %s took %s to complete without errors.", obsID, time.Since(begin))
|
||||
lm.logger.Info(fmt.Sprintf(message))
|
||||
}(time.Now())
|
||||
|
||||
lm.svc.Unsubscribe(obsID)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// +build !test
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/coap"
|
||||
)
|
||||
|
||||
var _ coap.Service = (*metricsMiddleware)(nil)
|
||||
|
||||
type metricsMiddleware struct {
|
||||
counter metrics.Counter
|
||||
latency metrics.Histogram
|
||||
svc coap.Service
|
||||
}
|
||||
|
||||
// MetricsMiddleware instruments adapter by tracking request count and latency.
|
||||
func MetricsMiddleware(svc coap.Service, counter metrics.Counter, latency metrics.Histogram) coap.Service {
|
||||
return &metricsMiddleware{
|
||||
counter: counter,
|
||||
latency: latency,
|
||||
svc: svc,
|
||||
}
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) Publish(msg mainflux.RawMessage) error {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "publish").Add(1)
|
||||
mm.latency.With("method", "publish").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.svc.Publish(msg)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) Subscribe(chanID uint64, clientID string, o *coap.Observer) error {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "subscribe").Add(1)
|
||||
mm.latency.With("method", "subscribe").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.svc.Subscribe(chanID, clientID, o)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) Unsubscribe(clientID string) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "unsubscribe").Add(1)
|
||||
mm.latency.With("method", "unsubscribe").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
mm.svc.Unsubscribe(clientID)
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-zoo/bone"
|
||||
"github.com/mainflux/mainflux/coap"
|
||||
log "github.com/mainflux/mainflux/logger"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
mux "github.com/dereulenspiegel/coap-mux"
|
||||
gocoap "github.com/dustin/go-coap"
|
||||
"github.com/mainflux/mainflux"
|
||||
)
|
||||
|
||||
const protocol = "coap"
|
||||
|
||||
var (
|
||||
errBadRequest = errors.New("bad request")
|
||||
errBadOption = errors.New("bad option")
|
||||
auth mainflux.ThingsServiceClient
|
||||
logger log.Logger
|
||||
)
|
||||
|
||||
type handler func(conn *net.UDPConn, addr *net.UDPAddr, msg *gocoap.Message) *gocoap.Message
|
||||
|
||||
func notFoundHandler(l *net.UDPConn, a *net.UDPAddr, m *gocoap.Message) *gocoap.Message {
|
||||
if m.IsConfirmable() {
|
||||
return &gocoap.Message{
|
||||
Type: gocoap.Acknowledgement,
|
||||
Code: gocoap.NotFound,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//MakeHTTPHandler creates handler for version endpoint.
|
||||
func MakeHTTPHandler() http.Handler {
|
||||
b := bone.New()
|
||||
b.GetFunc("/version", mainflux.Version(protocol))
|
||||
b.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// MakeCOAPHandler creates handler for CoAP messages.
|
||||
func MakeCOAPHandler(svc coap.Service, tc mainflux.ThingsServiceClient, l log.Logger, responses chan<- string) gocoap.Handler {
|
||||
auth = tc
|
||||
logger = l
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.Handle("/channels/{id}/messages", gocoap.FuncHandler(receive(svc))).Methods(gocoap.POST)
|
||||
r.Handle("/channels/{id}/messages", gocoap.FuncHandler(observe(svc, responses)))
|
||||
r.NotFoundHandler = gocoap.FuncHandler(notFoundHandler)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func authorize(msg *gocoap.Message, res *gocoap.Message, cid uint64) (uint64, error) {
|
||||
// Device Key is passed as Uri-Query parameter, which option ID is 15 (0xf).
|
||||
key, err := authKey(msg.Option(gocoap.URIQuery))
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errBadOption:
|
||||
res.Code = gocoap.BadOption
|
||||
case errBadRequest:
|
||||
res.Code = gocoap.BadRequest
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, err := auth.CanAccess(ctx, &mainflux.AccessReq{Token: key, ChanID: cid})
|
||||
|
||||
if err != nil {
|
||||
e, ok := status.FromError(err)
|
||||
if ok {
|
||||
switch e.Code() {
|
||||
case codes.PermissionDenied:
|
||||
res.Code = gocoap.Forbidden
|
||||
default:
|
||||
res.Code = gocoap.ServiceUnavailable
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
res.Code = gocoap.InternalServerError
|
||||
}
|
||||
return id.GetValue(), nil
|
||||
}
|
||||
|
||||
func receive(svc coap.Service) handler {
|
||||
return func(conn *net.UDPConn, addr *net.UDPAddr, msg *gocoap.Message) *gocoap.Message {
|
||||
// By default message is NonConfirmable, so
|
||||
// NonConfirmable response is sent back.
|
||||
res := &gocoap.Message{
|
||||
Type: gocoap.NonConfirmable,
|
||||
// According to https://tools.ietf.org/html/rfc7252#page-47: If the POST
|
||||
// succeeds but does not result in a new resource being created on the
|
||||
// server, the response SHOULD have a 2.04 (Changed) Response Code.
|
||||
Code: gocoap.Changed,
|
||||
MessageID: msg.MessageID,
|
||||
Token: msg.Token,
|
||||
Payload: []byte{},
|
||||
}
|
||||
|
||||
if msg.IsConfirmable() {
|
||||
res.Type = gocoap.Acknowledgement
|
||||
res.SetOption(gocoap.ContentFormat, gocoap.AppJSON)
|
||||
if len(msg.Payload) == 0 {
|
||||
res.Code = gocoap.BadRequest
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
channelID := mux.Var(msg, "id")
|
||||
|
||||
cid, err := strconv.ParseUint(channelID, 10, 64)
|
||||
if err != nil {
|
||||
res.Code = gocoap.NotFound
|
||||
return res
|
||||
}
|
||||
|
||||
publisher, err := authorize(msg, res, cid)
|
||||
if err != nil {
|
||||
res.Code = gocoap.Forbidden
|
||||
return res
|
||||
}
|
||||
|
||||
rawMsg := mainflux.RawMessage{
|
||||
Channel: cid,
|
||||
Publisher: publisher,
|
||||
Protocol: protocol,
|
||||
Payload: msg.Payload,
|
||||
}
|
||||
|
||||
if err := svc.Publish(rawMsg); err != nil {
|
||||
res.Code = gocoap.InternalServerError
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
func observe(svc coap.Service, responses chan<- string) handler {
|
||||
return func(conn *net.UDPConn, addr *net.UDPAddr, msg *gocoap.Message) *gocoap.Message {
|
||||
res := &gocoap.Message{
|
||||
Type: gocoap.Acknowledgement,
|
||||
Code: gocoap.Content,
|
||||
MessageID: msg.MessageID,
|
||||
Token: msg.Token,
|
||||
Payload: []byte{},
|
||||
}
|
||||
res.SetOption(gocoap.ContentFormat, gocoap.AppJSON)
|
||||
|
||||
id := mux.Var(msg, "id")
|
||||
chanID, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
res.Code = gocoap.NotFound
|
||||
return res
|
||||
}
|
||||
|
||||
publisher, err := authorize(msg, res, chanID)
|
||||
if err != nil {
|
||||
res.Code = gocoap.Forbidden
|
||||
logger.Warn(fmt.Sprintf("Failed to authorize: %s", err))
|
||||
return res
|
||||
}
|
||||
|
||||
obsID := fmt.Sprintf("%x-%d-%d", msg.Token, publisher, chanID)
|
||||
|
||||
if msg.Type == gocoap.Acknowledgement {
|
||||
responses <- obsID
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := msg.Option(gocoap.Observe).(uint32); (ok && value == 1) || msg.Type == gocoap.Reset {
|
||||
svc.Unsubscribe(obsID)
|
||||
}
|
||||
|
||||
if value, ok := msg.Option(gocoap.Observe).(uint32); ok && value == 0 {
|
||||
res.AddOption(gocoap.Observe, 1)
|
||||
o := coap.NewObserver()
|
||||
if err := svc.Subscribe(chanID, obsID, o); err != nil {
|
||||
logger.Warn(fmt.Sprintf("Failed to subscribe to NATS subject: %s", err))
|
||||
res.Code = gocoap.InternalServerError
|
||||
return res
|
||||
}
|
||||
|
||||
go handleMessage(conn, addr, o, msg)
|
||||
go ping(svc, obsID, conn, addr, o, msg)
|
||||
go cancel(o)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
func cancel(observer *coap.Observer) {
|
||||
<-observer.Cancel
|
||||
close(observer.Messages)
|
||||
observer.StoreExpired(true)
|
||||
}
|
||||
|
||||
func handleMessage(conn *net.UDPConn, addr *net.UDPAddr, o *coap.Observer, msg *gocoap.Message) {
|
||||
notifyMsg := *msg
|
||||
notifyMsg.Type = gocoap.NonConfirmable
|
||||
notifyMsg.Code = gocoap.Content
|
||||
notifyMsg.RemoveOption(gocoap.URIQuery)
|
||||
for {
|
||||
msg, ok := <-o.Messages
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
payload, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
logger.Warn(fmt.Sprintf("Failed to parse received message: %s", err))
|
||||
continue
|
||||
}
|
||||
|
||||
notifyMsg.Payload = payload
|
||||
notifyMsg.MessageID = o.LoadMessageID()
|
||||
buff := new(bytes.Buffer)
|
||||
observe := uint64(notifyMsg.MessageID)
|
||||
if err := binary.Write(buff, binary.BigEndian, observe); err != nil {
|
||||
logger.Warn(fmt.Sprintf("Failed to generate Observe option value: %s", err))
|
||||
continue
|
||||
}
|
||||
|
||||
observeVal := buff.Bytes()
|
||||
notifyMsg.SetOption(gocoap.Observe, observeVal[len(observeVal)-3:])
|
||||
|
||||
if err := gocoap.Transmit(conn, addr, notifyMsg); err != nil {
|
||||
logger.Warn(fmt.Sprintf("Failed to send message to observer: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ping(svc coap.Service, obsID string, conn *net.UDPConn, addr *net.UDPAddr, o *coap.Observer, msg *gocoap.Message) {
|
||||
pingMsg := *msg
|
||||
pingMsg.Payload = []byte{}
|
||||
pingMsg.Type = gocoap.Confirmable
|
||||
pingMsg.RemoveOption(gocoap.URIQuery)
|
||||
// According to RFC (https://tools.ietf.org/html/rfc7641#page-18), CON message must be sent at least every
|
||||
// 24 hours. Since 24 hours is too long for our purposes, we use 12.
|
||||
t := time.NewTicker(12 * time.Hour)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-t.C:
|
||||
if !ok || o.LoadExpired() {
|
||||
return
|
||||
}
|
||||
|
||||
o.StoreExpired(true)
|
||||
timeout := float64(coap.AckTimeout)
|
||||
logger.Info(fmt.Sprintf("Ping client %s.", obsID))
|
||||
for i := 0; i < coap.MaxRetransmit; i++ {
|
||||
pingMsg.MessageID = o.LoadMessageID()
|
||||
gocoap.Transmit(conn, addr, pingMsg)
|
||||
time.Sleep(time.Duration(timeout * coap.AckRandomFactor))
|
||||
if !o.LoadExpired() {
|
||||
break
|
||||
}
|
||||
timeout = 2 * timeout
|
||||
}
|
||||
|
||||
if o.LoadExpired() {
|
||||
svc.Unsubscribe(obsID)
|
||||
return
|
||||
}
|
||||
case <-o.Cancel:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package api
|
||||
|
||||
import "strings"
|
||||
|
||||
func authKey(opt interface{}) (string, error) {
|
||||
val, ok := opt.(string)
|
||||
if !ok {
|
||||
return "", errBadRequest
|
||||
}
|
||||
|
||||
arr := strings.Split(val, "=")
|
||||
if len(arr) != 2 || strings.ToLower(arr[0]) != "authorization" {
|
||||
return "", errBadOption
|
||||
}
|
||||
|
||||
return arr[1], nil
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Package ats contains NATS message publisher implementation.
|
||||
package nats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/mainflux/mainflux"
|
||||
"github.com/mainflux/mainflux/coap"
|
||||
broker "github.com/nats-io/go-nats"
|
||||
)
|
||||
|
||||
const prefix = "channel"
|
||||
|
||||
var _ mainflux.MessagePublisher = (*natsPublisher)(nil)
|
||||
|
||||
type natsPublisher struct {
|
||||
nc *broker.Conn
|
||||
}
|
||||
|
||||
// New instantiates NATS message publisher.
|
||||
func New(nc *broker.Conn) coap.Broker {
|
||||
return &natsPublisher{nc}
|
||||
}
|
||||
|
||||
func (pubsub *natsPublisher) Publish(msg mainflux.RawMessage) error {
|
||||
data, err := proto.Marshal(&msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("%s.%d", prefix, msg.Channel)
|
||||
return pubsub.nc.Publish(subject, data)
|
||||
}
|
||||
|
||||
func (pubsub *natsPublisher) Subscribe(chanID uint64, obsID string, observer *coap.Observer) error {
|
||||
sub, err := pubsub.nc.Subscribe(fmt.Sprintf("%s.%d", prefix, chanID), func(msg *broker.Msg) {
|
||||
if msg == nil {
|
||||
return
|
||||
}
|
||||
var rawMsg mainflux.RawMessage
|
||||
if err := proto.Unmarshal(msg.Data, &rawMsg); err != nil {
|
||||
return
|
||||
}
|
||||
observer.Messages <- rawMsg
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-observer.Cancel
|
||||
sub.Unsubscribe()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright (c) 2018
|
||||
// Mainflux
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package coap
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/mainflux/mainflux"
|
||||
)
|
||||
|
||||
// Observer is used to handle CoAP subscription.
|
||||
type Observer struct {
|
||||
// Expired flag is used to mark that ticker sent a
|
||||
// CON message, but response is not received yet.
|
||||
// The flag changes its value once ACK message is
|
||||
// received from the client. If Expired is true
|
||||
// when ticker is triggered, Observer should be canceled
|
||||
// and removed from the Service map.
|
||||
expired bool
|
||||
|
||||
// Message ID for notification messages.
|
||||
msgID uint16
|
||||
|
||||
expiredLock, msgIDLock sync.Mutex
|
||||
|
||||
// Messages is used to receive messages from NATS.
|
||||
Messages chan mainflux.RawMessage
|
||||
|
||||
// Cancel channel is used to cancel observing resource.
|
||||
// Cancel channel should not be used to send or receive any
|
||||
// data, it's purpose is to be closed once Observer canceled.
|
||||
Cancel chan bool
|
||||
}
|
||||
|
||||
// NewObserver instantiates a new Observer.
|
||||
func NewObserver() *Observer {
|
||||
return &Observer{
|
||||
Messages: make(chan mainflux.RawMessage),
|
||||
Cancel: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadExpired reads Expired flag in thread-safe way.
|
||||
func (o *Observer) LoadExpired() bool {
|
||||
o.expiredLock.Lock()
|
||||
defer o.expiredLock.Unlock()
|
||||
|
||||
return o.expired
|
||||
}
|
||||
|
||||
// StoreExpired stores Expired flag in thread-safe way.
|
||||
func (o *Observer) StoreExpired(val bool) {
|
||||
o.expiredLock.Lock()
|
||||
defer o.expiredLock.Unlock()
|
||||
|
||||
o.expired = val
|
||||
}
|
||||
|
||||
// LoadMessageID reads MessageID and increments
|
||||
// its value in thread-safe way.
|
||||
func (o *Observer) LoadMessageID() uint16 {
|
||||
o.msgIDLock.Lock()
|
||||
defer o.msgIDLock.Unlock()
|
||||
|
||||
o.msgID++
|
||||
return o.msgID
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user