93 Commits

Author SHA1 Message Date
324d2ac7f4 upload to docker hub
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-31 23:26:41 +01:00
8a0212a139 Merge pull request 'Renamed frontend service to Message Queue Service' (#5) from feature/rabbit-mq into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #5
2021-01-17 18:33:27 +01:00
802806b4c2 Renamed frontend service to Message Queue Service
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-01-17 18:01:15 +01:00
1d4bf2d0b6 Merge pull request 'feature/rabbit-mq' (#4) from feature/rabbit-mq into master
Some checks reported errors
continuous-integration/drone/push Build was killed
Reviewed-on: #4
2021-01-17 17:16:53 +01:00
79dcb4d75a Added RabbitMq IsConnected
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-01-17 17:13:26 +01:00
7c67fa7de0 Array empty
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-17 16:54:39 +01:00
89a416ac38 Merge pull request 'Added new rabbitmq configs' (#3) from feature/rabbit-mq into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #3
2021-01-17 16:50:57 +01:00
e9ffe514dd Added new rabbitmq configs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-01-17 16:45:11 +01:00
6a579772df Merge pull request 'Moved Dockerfile, fixed RabbitMq connection fail in constructor' (#2) from feature/rabbit-mq into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #2
2021-01-17 16:00:25 +01:00
20a4b4d349 Moved Dockerfile, fixed RabbitMq connection fail in constructor
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-01-17 15:41:06 +01:00
0085b95198 Merge pull request 'Adding RabbitMq support' (#1) from feature/rabbit-mq into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #1
2021-01-17 14:51:29 +01:00
579481ce16 Fixed Nlog.config
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-01-17 13:11:14 +01:00
645f2bb44b Merge branch 'master' of ssh://git.kmlabz.com:2222/birbnetes/birbmap into feature/rabbit-mq
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-01-17 13:05:43 +01:00
c3bbbd3d13 add drone config
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-17 12:11:06 +01:00
0df5b350d9 Added RabbitMq support 2021-01-16 15:23:10 +01:00
265a59d4c3 ... 2020-12-09 17:50:54 +01:00
e9fcfd4ffa Added ref dots 2020-12-09 17:49:51 +01:00
3b5b544a3e Fixed all grammer mistakes 2020-12-09 17:36:15 +01:00
57998e3bc5 Another grammar mistake 2020-12-08 22:42:07 +01:00
9cae969803 Martha's contribution 2020-12-08 22:39:18 +01:00
37ea775d59 And now for the final touches 0.2 2020-12-08 21:16:58 +01:00
c9c631b947 And now for the final touches 0.1 2020-12-08 21:15:01 +01:00
433b235929 I can't believe I have a (semi) finished version 2020-12-08 21:12:28 +01:00
7588b58453 My brain is fried 2020-12-07 23:19:09 +01:00
251a00eaa6 Removed appendices 2020-12-06 18:06:28 +01:00
5768b78619 Added last few chapter titles 2020-12-06 18:05:21 +01:00
08df5a624b Deleted unnecessary files 2020-12-06 18:03:09 +01:00
c0ae0a30fe Finished frontend description 2020-12-06 18:02:22 +01:00
8970d4fec3 Added frontend description 2020-12-06 15:41:59 +01:00
dbad96be95 Deleted .aux files 2020-12-04 09:31:42 +01:00
8764e1b45a Made system info requests multi threaded 2020-12-04 09:25:48 +01:00
6e61fc7756 Moved all services from API to BLL project 2020-12-03 18:52:25 +01:00
f0af8f08e3 Added API layer description 2020-12-03 16:37:13 +01:00
76c3787107 Added API layer logging 2020-12-02 21:41:01 +01:00
544df78ac8 Added Birdnetes models' 2020-12-01 16:27:26 +01:00
02df37692f Added DAL and BLL descriptions 2020-12-01 14:21:26 +01:00
ed4e207ff0 Finished birdmap-technologies 2020-12-01 09:44:11 +01:00
a5a37fdd1b Added birdmap-technologies 2020-11-30 16:34:08 +01:00
1b52e16db5 Added birdmap-technologies part 2020-11-28 17:38:33 +01:00
c9550ea5b8 Modified build batch file, and some formatting 2020-11-27 17:47:55 +01:00
3de33014e5 Added more content files 2020-11-27 16:44:16 +01:00
a458ad1712 Added birdnetes introduction 2020-11-27 15:37:25 +01:00
030b259d32 Added introduction 0.1 2020-11-26 19:44:53 +01:00
022852b163 Added abtract 0.4 2020-11-26 17:33:49 +01:00
2d5eca233e Added abtract 0.3 2020-11-26 16:28:33 +01:00
9d6e0cd453 Added abtract 0.2 2020-11-26 16:24:02 +01:00
a2c3112f81 Added abtract 0.1 2020-11-26 15:35:03 +01:00
c7e3fcabcf Added thesis base, and ideas.txt 2020-11-26 12:15:43 +01:00
70c4c91035 Fixed LogService baseUrl 2020-11-26 10:11:49 +01:00
aa39597541 Modified log folder path 2020-11-26 10:01:47 +01:00
bcbab1383a Added Logs navigation 2020-11-26 09:35:50 +01:00
bab0984c30 Added logdownloader 2020-11-25 14:10:47 +01:00
0667c6ec39 Added environment specific appsettings 2020-11-25 12:22:51 +01:00
0d71899ce1 Fixed docker-compose issues 2020-11-25 12:16:14 +01:00
966d8bd79e Some changes 2020-11-23 13:31:09 +01:00
5b42ce9f43 Multiple configuration modifications
Added Enviroment variable support
Addes baseUrl variable for live services
Included default env variables in docker-compose.yml
Updated sql and nodejs versions
2020-11-23 10:50:10 +01:00
85320d3cf3 Added dockerfile, added compose 2020-11-23 09:23:05 +01:00
9d55c39e33 Added Mqtt messages buffer to help unload frontend 2020-11-22 10:39:51 +01:00
d75e9d378d Added further optimazation 2020-11-22 09:56:32 +01:00
3cdaa2dc35 Increased timeout for ui update 2020-11-21 20:14:30 +01:00
9af0ba1bb8 Best optimization so far 2020-11-21 19:42:14 +01:00
04c27560ea Added timeout processing 2020-11-21 19:18:29 +01:00
73157520ab Optimized chart update (did not achieve much) 2020-11-21 18:32:12 +01:00
f862e4b8da Added all charts (pre optimization) 2020-11-21 18:10:36 +01:00
f85346aea9 Added charts 2020-11-21 17:40:05 +01:00
1d438bc349 Modified dashboard 2020-11-21 10:38:52 +01:00
86999cd646 Moved service components to services folder 2020-11-21 10:22:11 +01:00
8979ad6db3 Added dashboard services
Added GetCount endpoint
Added ServiceInfo Skeletons
2020-11-19 20:43:01 +01:00
779e21909c Added All Device handler 2020-11-18 18:34:48 +01:00
f84ea8f0c5 Extended Devices with buttons and status 2020-11-18 14:04:57 +01:00
181985859e Added componentDidMount heatmap initializer 2020-11-18 12:35:33 +01:00
39a38fe8eb Added context to heatmap, and devices 2020-11-18 12:04:32 +01:00
490f0f3265 Added context 2020-11-18 09:56:30 +01:00
41bf14a4e5 Merge branch 'master' into feature/Contexts 2020-11-18 09:19:05 +01:00
3f267cb009 Added background color, Added Accordion onChange handler 2020-11-17 22:20:50 +01:00
0e3eb8720f Added Devices Accordions 2020-11-17 21:51:53 +01:00
3f2467f6c6 Added Device Context (not working) 2020-11-17 18:58:28 +01:00
2a83856622 Removed exact from NavLink to fix highlighting 2020-11-13 19:11:25 +01:00
d726273431 Added position offset, changed color 2020-11-12 22:11:42 +01:00
412647617b Added markers with device openers 2020-11-12 21:20:04 +01:00
53ff60ae5a Added devices id route parameter 2020-11-12 18:57:27 +01:00
f273823c93 Migrated to .NET 5, added records 2020-11-12 18:13:23 +01:00
c92808ac7d Added single update 2020-11-11 20:56:21 +01:00
f13133829a Extended DeviceHub with update notifiers 2020-11-11 20:35:26 +01:00
4c1258dc33 Added Trello docs 2020-11-11 17:13:13 +01:00
a52f6acd71 Added google maps heatmap 2020-11-11 16:55:50 +01:00
4281c2d524 Added isAdmin 2020-11-09 18:30:43 +01:00
639e6edac6 Moved interface 2020-11-09 18:13:02 +01:00
3632e56dc4 Added Mqtt and SignalR 2020-11-09 18:12:07 +01:00
b87d90e5a4 Preparing mqtt 2020-11-08 23:29:50 +01:00
f1c1ad69cc Renamed some files 2020-11-08 19:11:12 +01:00
f102b89a21 Added MQTT tester, added input service 2020-11-08 18:51:19 +01:00
e1a596dae9 Updated csproj 2020-11-07 14:20:58 +01:00
180 changed files with 43407 additions and 1601 deletions

25
.dockerignore Normal file
View File

@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/docs
**/bin
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

45
.drone.yml Normal file
View File

@ -0,0 +1,45 @@
kind: pipeline
type: docker
name: default
steps:
- name: code-analysis
image: aosapps/drone-sonar-plugin
settings:
sonar_host:
from_secret: SONAR_HOST
sonar_token:
from_secret: SONAR_CODE
- name: kaniko
image: banzaicloud/drone-kaniko
settings:
registry: registry.kmlabz.com
repo: birbnetes/${DRONE_REPO_NAME}
username:
from_secret: DOCKER_USERNAME
password:
from_secret: DOCKER_PASSWORD
tags:
- latest
- ${DRONE_BUILD_NUMBER}
- name: dockerhub
image: plugins/docker
settings:
repo: birbnetes/${DRONE_REPO_NAME}
username:
from_secret: DOCKERHUB_USER
password:
from_secret: DOCKERHUB_PASSWORD
tags:
- latest
- ${DRONE_BUILD_NUMBER}
- name: ms-teams
image: kuperiu/drone-teams
settings:
webhook:
from_secret: TEAMS_WEBHOOK
when:
status: [ failure ]

View File

@ -1,12 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<AssemblyName>Birdmap.API</AssemblyName>
<UserSecretsId>a919c854-b332-49ee-8e38-96549f828836</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@ -20,9 +24,9 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="10.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.9" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.9">
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@ -30,7 +34,12 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.5" />
<PackageReference Include="NLog.Web" Version="4.9.3" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.9.3" />
<PackageReference Include="NSwag.AspNetCore" Version="13.9.0" />
</ItemGroup>
<ItemGroup>
@ -42,21 +51,19 @@
<ItemGroup>
<None Remove="ClientApp\src\common\components\BirdmapTitle.tsx" />
<None Remove="ClientApp\src\common\ErrorDispatcher.ts" />
<None Remove="ClientApp\src\common\ServiceBase.ts" />
<None Remove="ClientApp\src\components\auth\Auth.tsx" />
<None Remove="ClientApp\src\components\auth\AuthClient.ts" />
<None Remove="ClientApp\src\components\auth\AuthService.ts" />
<None Remove="ClientApp\src\components\dashboard\ServiceInfoService.ts" />
<None Remove="ClientApp\src\components\devices\DeviceService.ts" />
</ItemGroup>
<ItemGroup>
<TypeScriptCompile Include="ClientApp\src\components\auth\Auth.tsx" />
<TypeScriptCompile Include="ClientApp\src\common\components\BirdmapTitle.tsx" />
</ItemGroup>
<ItemGroup>
<Folder Include="ClientApp\src\components\dashboard\" />
<Folder Include="ClientApp\src\components\devices\" />
<Folder Include="ClientApp\src\components\heatmap\" />
<Folder Include="ClientApp\src\common\components\" />
</ItemGroup>
<ItemGroup>

View File

@ -1199,6 +1199,11 @@
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
"@googlemaps/js-api-loader": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.8.0.tgz",
"integrity": "sha512-aFxlJVFOC00KELhlaqU6tpnxj9szVdG7OSHxQzc9uhp4Ky8/zvYNnzZg2S+pPvhe+WkJPugQL8fowP5nYTBXUg=="
},
"@hapi/address": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
@ -1424,6 +1429,11 @@
"@types/yargs": "^13.0.0"
}
},
"@mapbox/point-geometry": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
"integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI="
},
"@material-ui/core": {
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz",
@ -1559,6 +1569,33 @@
"react-is": "^16.8.0"
}
},
"@microsoft/signalr": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-5.0.0.tgz",
"integrity": "sha512-AsbU1ZB4Q9JsZ77W13VGT8gi/cVrFn3XbvVfULSwrC9DVCXF2JpkBDh0cCmRaYs9M3kqKohiVM1WPqNeAGil/g==",
"requires": {
"abort-controller": "^3.0.0",
"eventsource": "^1.0.7",
"fetch-cookie": "^0.7.3",
"node-fetch": "^2.6.0",
"ws": "^6.0.0"
},
"dependencies": {
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"ws": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
},
"@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@ -1573,6 +1610,25 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
},
"@rollup/plugin-babel": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.2.1.tgz",
"integrity": "sha512-Jd7oqFR2dzZJ3NWANDyBjwTtX/lYbZpVcmkHrfQcpvawHs9E4c0nYk5U2mfZ6I/DZcIvy506KZJi54XK/jxH7A==",
"requires": {
"@babel/helper-module-imports": "^7.10.4",
"@rollup/pluginutils": "^3.1.0"
}
},
"@rollup/pluginutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"requires": {
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
"picomatch": "^2.2.2"
}
},
"@svgr/babel-plugin-add-jsx-attribute": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
@ -1724,6 +1780,11 @@
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
"integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag=="
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="
},
"@types/glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
@ -2170,6 +2231,14 @@
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
"integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
},
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"requires": {
"event-target-shim": "^5.0.0"
}
},
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@ -2311,6 +2380,20 @@
"normalize-path": "^2.1.1"
}
},
"apexcharts": {
"version": "3.22.2",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.22.2.tgz",
"integrity": "sha512-pR+cmApk7dhfYILBpe8RVb+FdLfVCt/RDWvAJO1F5feeSQ8lKDgFkRuVu9KOeEarHVXjUpnhLqHNMx7YaprK8A==",
"requires": {
"@rollup/plugin-babel": "^5.2.1",
"svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0",
"svg.filter.js": "^2.0.2",
"svg.pathmorphing.js": "^0.1.3",
"svg.resize.js": "^1.4.3",
"svg.select.js": "^3.0.1"
}
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
@ -3406,6 +3489,11 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"can-use-dom": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/can-use-dom/-/can-use-dom-0.1.0.tgz",
"integrity": "sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo="
},
"caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@ -3450,6 +3538,11 @@
"supports-color": "^5.3.0"
}
},
"change-emitter": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
"integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
},
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
@ -4857,6 +4950,24 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"requires": {
"iconv-lite": "^0.6.2"
},
"dependencies": {
"iconv-lite": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
}
}
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -4972,6 +5083,11 @@
"next-tick": "~1.0.0"
}
},
"es6-denodeify": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/es6-denodeify/-/es6-denodeify-0.1.5.tgz",
"integrity": "sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8="
},
"es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
@ -5488,6 +5604,11 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
},
"estree-walker": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@ -5498,6 +5619,11 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@ -5857,6 +5983,44 @@
"bser": "2.1.1"
}
},
"fbjs": {
"version": "0.8.17",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
"integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
"requires": {
"core-js": "^1.0.0",
"isomorphic-fetch": "^2.1.1",
"loose-envify": "^1.0.0",
"object-assign": "^4.1.0",
"promise": "^7.1.1",
"setimmediate": "^1.0.5",
"ua-parser-js": "^0.7.18"
},
"dependencies": {
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
},
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"requires": {
"asap": "~2.0.3"
}
}
}
},
"fetch-cookie": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.7.3.tgz",
"integrity": "sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA==",
"requires": {
"es6-denodeify": "^0.1.1",
"tough-cookie": "^2.3.3"
}
},
"figgy-pudding": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
@ -6326,6 +6490,17 @@
}
}
},
"google-map-react": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/google-map-react/-/google-map-react-2.1.9.tgz",
"integrity": "sha512-//Pa0o6sdspU2H0ehVztSDQSnYYeV6TY4Z6ftty34yiCJYLliOzeq17dA9uFkyUFdL+XwbTU6e9mfs+bjBMIzw==",
"requires": {
"@googlemaps/js-api-loader": "^1.7.0",
"@mapbox/point-geometry": "^0.1.0",
"eventemitter3": "^4.0.4",
"prop-types": "^15.7.2"
}
},
"google-maps": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/google-maps/-/google-maps-4.3.3.tgz",
@ -6334,6 +6509,11 @@
"@types/googlemaps": "^3.39.1"
}
},
"google-maps-infobox": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/google-maps-infobox/-/google-maps-infobox-2.0.0.tgz",
"integrity": "sha512-hTuWmWZZSOxf5D/z7l3/hTF1grgRvLG53BEKMdjiKOG+FcK/kH7vqseUeyIU9Zj2ZIqKTOaro0nknxpAuRq4Vw=="
},
"google-maps-react": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/google-maps-react/-/google-maps-react-2.0.6.tgz",
@ -7207,6 +7387,15 @@
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
},
"isomorphic-fetch": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
"requires": {
"node-fetch": "^1.0.1",
"whatwg-fetch": ">=0.10.0"
}
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@ -8321,6 +8510,16 @@
"object-visit": "^1.0.0"
}
},
"marker-clusterer-plus": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/marker-clusterer-plus/-/marker-clusterer-plus-2.1.4.tgz",
"integrity": "sha1-+O/3TVmdqzt9Dj/tUmTqDnBPXWc="
},
"markerwithlabel": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/markerwithlabel/-/markerwithlabel-2.0.2.tgz",
"integrity": "sha512-C/cbm1A0h/u54gwHk5ZJNdUU3V3+1BbCpRPMsMyFA7vF4yL+aB4rWpxACz29TpQ+cTg6/iQroExh0PMSRGtQFg=="
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -8734,6 +8933,15 @@
"tslib": "^1.10.0"
}
},
"node-fetch": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
"requires": {
"encoding": "^0.1.11",
"is-stream": "^1.0.1"
}
},
"node-forge": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
@ -10634,6 +10842,14 @@
"prop-types": "^15.6.2"
}
},
"react-apexcharts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.3.7.tgz",
"integrity": "sha512-2OFhEHd70/WHN0kmrJtVx37UfaL71ZogVkwezmDqwQWgwhK6upuhlnEEX7tEq4xvjA+RFDn6hiUTNIuC/Q7Zqw==",
"requires": {
"prop-types": "^15.5.7"
}
},
"react-app-polyfill": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz",
@ -10809,6 +11025,50 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
"integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA=="
},
"react-google-maps": {
"version": "9.4.5",
"resolved": "https://registry.npmjs.org/react-google-maps/-/react-google-maps-9.4.5.tgz",
"integrity": "sha512-8z5nX9DxIcBCXuEiurmRT1VXVwnzx0C6+3Es6lxB2/OyY2SLax2/LcDu6Aldxnl3HegefTL7NJzGeaKAJ61pOA==",
"requires": {
"babel-runtime": "^6.11.6",
"can-use-dom": "^0.1.0",
"google-maps-infobox": "^2.0.0",
"invariant": "^2.2.1",
"lodash": "^4.16.2",
"marker-clusterer-plus": "^2.1.4",
"markerwithlabel": "^2.0.1",
"prop-types": "^15.5.8",
"recompose": "^0.26.0",
"scriptjs": "^2.5.8",
"warning": "^3.0.0"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
},
"recompose": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz",
"integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==",
"requires": {
"change-emitter": "^0.1.2",
"fbjs": "^0.8.1",
"hoist-non-react-statics": "^2.3.1",
"symbol-observable": "^1.0.4"
}
},
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"requires": {
"loose-envify": "^1.0.0"
}
}
}
},
"react-is": {
"version": "16.12.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
@ -11189,6 +11449,26 @@
"util.promisify": "^1.0.0"
}
},
"recompose": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz",
"integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==",
"requires": {
"@babel/runtime": "^7.0.0",
"change-emitter": "^0.1.2",
"fbjs": "^0.8.1",
"hoist-non-react-statics": "^2.3.1",
"react-lifecycles-compat": "^3.0.2",
"symbol-observable": "^1.0.4"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
}
}
},
"recursive-readdir": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
@ -11748,6 +12028,11 @@
}
}
},
"scriptjs": {
"version": "2.5.9",
"resolved": "https://registry.npmjs.org/scriptjs/-/scriptjs-2.5.9.tgz",
"integrity": "sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg=="
},
"seamless-immutable": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/seamless-immutable/-/seamless-immutable-7.1.4.tgz",
@ -12832,6 +13117,70 @@
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="
},
"svg.draggable.js": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
"requires": {
"svg.js": "^2.0.1"
}
},
"svg.easing.js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
"integrity": "sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=",
"requires": {
"svg.js": ">=2.3.x"
}
},
"svg.filter.js": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
"integrity": "sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=",
"requires": {
"svg.js": "^2.2.5"
}
},
"svg.js": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA=="
},
"svg.pathmorphing.js": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
"requires": {
"svg.js": "^2.4.0"
}
},
"svg.resize.js": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
"requires": {
"svg.js": "^2.6.5",
"svg.select.js": "^2.1.2"
},
"dependencies": {
"svg.select.js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
"requires": {
"svg.js": "^2.2.5"
}
}
}
},
"svg.select.js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
"requires": {
"svg.js": "^2.6.5"
}
},
"svgo": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.0.tgz",
@ -13262,6 +13611,11 @@
"integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==",
"dev": true
},
"ua-parser-js": {
"version": "0.7.22",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
},
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",

View File

@ -6,8 +6,11 @@
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.56",
"@microsoft/signalr": "^5.0.0",
"apexcharts": "^3.22.2",
"bootstrap": "^4.3.1",
"connected-react-router": "6.5.2",
"google-map-react": "^2.1.9",
"google-maps": "^4.3.3",
"google-maps-react": "^2.0.6",
"history": "4.10.1",
@ -15,12 +18,15 @@
"merge": "1.2.1",
"popper.js": "^1.16.0",
"react": "^16.11.0",
"react-apexcharts": "^1.3.7",
"react-dom": "16.11.0",
"react-google-maps": "^9.4.5",
"react-redux": "7.1.1",
"react-router": "5.1.2",
"react-router-dom": "5.1.2",
"react-scripts": "^3.4.4",
"reactstrap": "8.1.1",
"recompose": "^0.30.0",
"redux": "4.0.4",
"redux-thunk": "2.3.0",
"svgo": "1.3.0"

View File

@ -23,10 +23,20 @@
<title>Birdmap</title>
</head>
<body>
<style>
body {
height: 100vh;
-ms-overflow-style: none;
}
body::-webkit-scrollbar {
display:none;
}
</style>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<div id="root" style="height: 100vh;"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

View File

@ -1,37 +1,38 @@
import { Box, Container, IconButton, Menu, MenuItem, MenuList, Paper, Grow, Popper } from '@material-ui/core';
import AccountCircle from '@material-ui/icons/AccountCircle';
import AppBar from '@material-ui/core/AppBar';
import blue from '@material-ui/core/colors/blue';
import orange from '@material-ui/core/colors/orange';
import { Box, Paper } from '@material-ui/core';
import { blueGrey, grey, orange } from '@material-ui/core/colors';
import { createMuiTheme, createStyles, makeStyles, Theme } from '@material-ui/core/styles';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import { ThemeProvider } from '@material-ui/styles';
import React, { useState, } from 'react';
import { BrowserRouter, NavLink, Redirect, Route, Switch, Link } from 'react-router-dom';
import BirdmapTitle from './common/components/BirdmapTitle';
import React, { useState } from 'react';
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
import BirdmapBar from './components/appBar/BirdmapBar';
import Auth from './components/auth/Auth';
import AuthService from './components/auth/AuthService';
import { ClickAwayListener } from '@material-ui/core';
import Dashboard from './components/dashboard/Dashboard';
import Devices from './components/devices/Devices';
import MapContainer from './components/heatmap/Heatmap';
import Logs from './components/logs/Logs';
import DevicesContextProvider from './contexts/DevicesContextProvider';
const theme = createMuiTheme({
palette: {
primary: {
main: blue[900],
main: blueGrey[900],
dark: grey[400],
},
secondary: {
main: orange[200],
main: blueGrey[700],
dark: blueGrey[50],
}
},
});
function App() {
const [authenticated, setAuthenticated] = useState(AuthService.isAuthenticated());
const [isAdmin, setIsAdmin] = useState(AuthService.isAdmin());
const onAuthenticated = () => {
setAuthenticated(AuthService.isAuthenticated());
setIsAdmin(AuthService.isAdmin());
};
const AuthComponent = () => {
@ -40,26 +41,68 @@ function App() {
);
}
const LogsComponent = () => {
return <Logs/>
}
const DashboardComponent = () => {
return <Typography>Dashboard</Typography>;
return <Dashboard isAdmin={isAdmin}/>;
};
const DevicesComponent = () => {
return <Typography>Devices</Typography>;
return <Devices isAdmin={isAdmin}/>;
};
const HeatmapComponent = () => {
return <Typography>Heatmap</Typography>;
return (
<Paper elevation={0}>
<MapContainer />
</Paper>
);
};
const HeaderComponent = () => {
return (
<BirdmapBar onLogout={AuthService.logout} isAdmin={isAdmin} isAuthenticated={authenticated}/>
);
}
const PredicateRoute = ({ component: Component, predicate: Predicate, ...rest }: { [x: string]: any, component: any, predicate: any }) => {
return (
<PredicateRouteInternal {...rest} header={HeaderComponent} body={Component} predicate={Predicate}/>
);
}
const PublicRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => {
return (
<PredicateRoute {...rest} component={Component} predicate={true}/>
);
}
const PrivateRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => {
return (
<PredicateRoute {...rest} component={Component} predicate={authenticated}/>
);
}
const AdminRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => {
return (
<PredicateRoute {...rest} component={Component} predicate={authenticated && isAdmin}/>
);
}
return (
<ThemeProvider theme={theme}>
<BrowserRouter>
<Switch>
<PublicRoute path="/login" component={AuthComponent} />
<PrivateRoute path="/" exact authenticated={authenticated} component={DashboardComponent} />
<PrivateRoute path="/devices" exact authenticated={authenticated} component={DevicesComponent} />
<PrivateRoute path="/heatmap" exact authenticated={authenticated} component={HeatmapComponent} />
<PublicRoute exact path="/login" component={AuthComponent} />
<AdminRoute exact path="/logs" component={LogsComponent} />
<DevicesContextProvider>
<PrivateRoute exact path="/" component={DashboardComponent} />
<PrivateRoute exact path="/devices/:id?" component={DevicesComponent} />
<PrivateRoute exact path="/heatmap" component={HeatmapComponent} />
</DevicesContextProvider>
</Switch>
</BrowserRouter>
</ThemeProvider>
@ -68,112 +111,26 @@ function App() {
export default App;
const PublicRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any}) => {
const PredicateRouteInternal = ({ header: HeaderComponent, body: BodyComponent, predicate: Predicate, ...rest }: { [x: string]: any, header: any, body: any, predicate: any }) => {
return (
<Route {...rest} render={matchProps => (
<DefaultLayout component={Component} authenticated={false} {...matchProps} />
)} />
);
}
const PrivateRoute = ({ component: Component, authenticated: Authenticated, ...rest }: { [x: string]: any, component: any, authenticated: any }) => {
return (
<Route {...rest} render={matchProps => (
Authenticated
? <DefaultLayout component={Component} authenticated={Authenticated} {...matchProps} />
Predicate
? <DefaultLayoutInternal header={HeaderComponent} body={BodyComponent} {...matchProps} />
: <Redirect to='/login' />
)} />
);
};
const DefaultLayout = ({ component: Component, authenticated: Authenticated, ...rest }: { [x: string]: any, component: any, authenticated: any }) => {
const DefaultLayoutInternal = ({ header: HeaderComponent, body: BodyComponent, ...rest }: { [x: string]: any, header: any, body: any }) => {
const classes = useDefaultLayoutStyles();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef<HTMLButtonElement>(null);
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event: React.MouseEvent<EventTarget>) => {
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
return;
}
setOpen(false);
};
const handleLogout = (event: React.MouseEvent<EventTarget>) => {
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
return;
}
AuthService.logout();
setOpen(false);
};
function handleListKeyDown(event: React.KeyboardEvent) {
if (event.key === 'Tab') {
event.preventDefault();
setOpen(false);
}
}
const prevOpen = React.useRef(open);
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
anchorRef.current!.focus();
}
prevOpen.current = open;
}, [open]);
const renderNavLinks = () => {
return Authenticated
? <Container className={classes.nav_menu}>
<NavLink exact to="/" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Dashboard</NavLink>
<NavLink exact to="/devices" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Devices</NavLink>
<NavLink exact to="/heatmap" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Heatmap</NavLink>
<IconButton className={classes.nav_menu_icon}
ref={anchorRef}
aria-haspopup="true"
aria-controls={open ? 'menu-list-grow' : undefined}
aria-label="account of current user"
onClick={handleToggle}>
<AccountCircle/>
</IconButton>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleLogout} component={Link} {...{ to: '/login' }}>Logout</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</Container>
: null;
};
return (
<React.Fragment>
<AppBar position="static">
<Toolbar>
<BirdmapTitle />
<Typography component={'span'} className={classes.typo}>
{renderNavLinks()}
</Typography>
</Toolbar>
</AppBar>
<Box style={{ margin: '32px' }}>
<Component {...rest} />
<Box className={classes.header}>
<HeaderComponent />
</Box>
<Box className={classes.body}>
<BodyComponent {...rest} />
</Box>
</React.Fragment>
);
@ -181,39 +138,12 @@ const DefaultLayout = ({ component: Component, authenticated: Authenticated, ...
const useDefaultLayoutStyles = makeStyles((theme: Theme) =>
createStyles({
typo: {
marginLeft: 'auto',
color: 'white',
header: {
height: '7%',
},
nav_menu: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
nav_menu_icon: {
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
body: {
backgroundColor: theme.palette.primary.dark,
height: '93%',
}
},
nav_menu_item: {
textDecoration: 'none',
fontWeight: 'normal',
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
nav_menu_item_active: {
textDecoration: 'underline',
fontWeight: 'bold',
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
}),
);

View File

@ -0,0 +1,5 @@
export default {
probability_method_name: 'NotifyMessagesAsync',
update_method_name: 'NotifyDeviceUpdatedAsync',
update_all_method_name: 'NotifyAllUpdatedAsync',
};

View File

@ -21,7 +21,7 @@ var __extends = (this && this.__extends) || (function () {
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiException = exports.SensorStatus = exports.Coordinates = exports.DeviceStatus = exports.Sensor = exports.Device = exports.DeviceService = void 0;
exports.ApiException = exports.SensorStatus = exports.Coordinates = exports.DeviceStatus = exports.Sensor = exports.Device = void 0;
var DeviceService = /** @class */ (function () {
function DeviceService(baseUrl, http) {
this.jsonParseReviver = undefined;
@ -38,7 +38,8 @@ var DeviceService = /** @class */ (function () {
var options_ = {
method: "GET",
headers: {
"Accept": "application/json"
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
@ -89,7 +90,9 @@ var DeviceService = /** @class */ (function () {
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "POST",
headers: {}
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processOfflineall(_response);
@ -129,7 +132,9 @@ var DeviceService = /** @class */ (function () {
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "POST",
headers: {}
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processOnlineall(_response);
@ -174,7 +179,8 @@ var DeviceService = /** @class */ (function () {
var options_ = {
method: "GET",
headers: {
"Accept": "application/json"
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
@ -223,7 +229,9 @@ var DeviceService = /** @class */ (function () {
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "POST",
headers: {}
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processOfflinedevice(_response);
@ -267,7 +275,9 @@ var DeviceService = /** @class */ (function () {
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "POST",
headers: {}
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processOnlinedevice(_response);
@ -316,7 +326,8 @@ var DeviceService = /** @class */ (function () {
var options_ = {
method: "GET",
headers: {
"Accept": "application/json"
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
@ -369,7 +380,9 @@ var DeviceService = /** @class */ (function () {
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "POST",
headers: {}
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processOfflinesensor(_response);
@ -417,7 +430,9 @@ var DeviceService = /** @class */ (function () {
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "POST",
headers: {}
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processOnlinesensor(_response);
@ -449,7 +464,7 @@ var DeviceService = /** @class */ (function () {
};
return DeviceService;
}());
exports.DeviceService = DeviceService;
exports.default = DeviceService;
var Device = /** @class */ (function () {
function Device(data) {
if (data) {

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@
//----------------------
// ReSharper disable InconsistentNaming
export class DeviceService {
export default class DeviceService {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
@ -27,7 +27,8 @@ export class DeviceService {
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/json"
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
@ -73,6 +74,7 @@ export class DeviceService {
let options_ = <RequestInit>{
method: "POST",
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
@ -111,6 +113,7 @@ export class DeviceService {
let options_ = <RequestInit>{
method: "POST",
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
@ -153,7 +156,8 @@ export class DeviceService {
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/json"
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
@ -199,6 +203,7 @@ export class DeviceService {
let options_ = <RequestInit>{
method: "POST",
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
@ -241,6 +246,7 @@ export class DeviceService {
let options_ = <RequestInit>{
method: "POST",
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
@ -287,7 +293,8 @@ export class DeviceService {
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/json"
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
@ -337,6 +344,7 @@ export class DeviceService {
let options_ = <RequestInit>{
method: "POST",
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
@ -383,6 +391,7 @@ export class DeviceService {
let options_ = <RequestInit>{
method: "POST",
headers: {
'Authorization': sessionStorage.getItem('user')
}
};

View File

@ -1,14 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var ErrorDispatcher = {
errorHandlers: [],
registerErrorHandler: function (errorHandlerFn) {
this.errorHandlers.push(errorHandlerFn);
},
raiseError: function (errorMessage) {
for (var i = 0; i < this.errorHandlers.length; i++)
this.errorHandlers[i](errorMessage);
}
};
exports.default = ErrorDispatcher;
//# sourceMappingURL=ErrorDispatcher.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"ErrorDispatcher.js","sourceRoot":"","sources":["ErrorDispatcher.ts"],"names":[],"mappings":";;AAAA,IAAM,eAAe,GAAG;IACtB,aAAa,EAAE,EAAE;IAEjB,oBAAoB,YAAC,cAAc;QACjC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC1C,CAAC;IAED,UAAU,YAAC,YAAY;QACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,EAAE;YAChD,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACxC,CAAC;CACF,CAAC;AAEF,kBAAe,eAAe,CAAC"}

View File

@ -1,14 +0,0 @@
const ErrorDispatcher = {
errorHandlers: [],
registerErrorHandler(errorHandlerFn) {
this.errorHandlers.push(errorHandlerFn);
},
raiseError(errorMessage) {
for (let i = 0; i < this.errorHandlers.length; i++)
this.errorHandlers[i](errorMessage);
}
};
export default ErrorDispatcher;

View File

@ -1,50 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var ErrorDispatcher_1 = require("./ErrorDispatcher");
function get(url) {
var options = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': sessionStorage.getItem('user')
}
};
return makeRequest(url, options);
}
function post(url, request) {
var options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': sessionStorage.getItem('user')
},
body: "",
};
if (request)
options.body = JSON.stringify(request);
return makeRequest(url, options);
}
function makeRequest(url, options) {
return fetch(url, options)
.then(ensureResponseSuccess)
.catch(errorHandler);
}
function ensureResponseSuccess(response) {
if (!response.ok)
return response.json()
.then(function (data) { return errorHandler(data); });
return response.text()
.then(function (text) { return text.length ? JSON.parse(text) : {}; });
}
function errorHandler(response) {
console.log(response);
if (response && response.Error)
ErrorDispatcher_1.default.raiseError(response.Error);
return Promise.reject();
}
exports.default = {
get: get,
post: post,
makeRequest: makeRequest
};
//# sourceMappingURL=ServiceBase.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"ServiceBase.js","sourceRoot":"","sources":["ServiceBase.ts"],"names":[],"mappings":";;AAAA,qDAAgD;AAEhD,SAAS,GAAG,CAAC,GAAW;IACpB,IAAI,OAAO,GAAG;QACV,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACL,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC;SAClD;KACJ,CAAC;IAEF,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,IAAI,CAAC,GAAW,EAAE,OAAY;IACnC,IAAI,OAAO,GAAG;QACV,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACL,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC;SAClD;QACD,IAAI,EAAE,EAAE;KACX,CAAC;IAEF,IAAI,OAAO;QACP,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAE3C,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,OAAY;IAC1C,OAAO,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC;SACrB,IAAI,CAAC,qBAAqB,CAAC;SAC3B,KAAK,CAAC,YAAY,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAa;IACxC,IAAI,CAAC,QAAQ,CAAC,EAAE;QACZ,OAAO,QAAQ,CAAC,IAAI,EAAE;aACjB,IAAI,CAAC,UAAC,IAAS,IAAK,OAAA,YAAY,CAAC,IAAI,CAAC,EAAlB,CAAkB,CAAC,CAAC;IAEjD,OAAO,QAAQ,CAAC,IAAI,EAAE;SACjB,IAAI,CAAC,UAAC,IAAS,IAAK,OAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAnC,CAAmC,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,YAAY,CAAC,QAAa;IAC/B,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAEtB,IAAI,QAAQ,IAAI,QAAQ,CAAC,KAAK;QAC1B,yBAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE/C,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,kBAAe;IACX,GAAG,KAAA;IACH,IAAI,MAAA;IACJ,WAAW,aAAA;CACd,CAAC"}

View File

@ -1,59 +0,0 @@
import ErrorDispatcher from './ErrorDispatcher';
function get(url: string) {
let options = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': sessionStorage.getItem('user')
}
};
return makeRequest(url, options);
}
function post(url: string, request: any) {
let options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': sessionStorage.getItem('user')
},
body: "",
};
if (request)
options.body = JSON.stringify(request);
return makeRequest(url, options);
}
function makeRequest(url: string, options: any) {
return fetch(url, options)
.then(ensureResponseSuccess)
.catch(errorHandler);
}
function ensureResponseSuccess(response: any) {
if (!response.ok)
return response.json()
.then((data: any) => errorHandler(data));
return response.text()
.then((text: any) => text.length ? JSON.parse(text) : {});
}
function errorHandler(response: any) {
console.log(response);
if (response && response.Error)
ErrorDispatcher.raiseError(response.Error);
return Promise.reject();
}
export default {
get,
post,
makeRequest
};

View File

@ -0,0 +1,136 @@
import { ClickAwayListener, Container, createStyles, Grow, IconButton, makeStyles, MenuItem, MenuList, Paper, Popper, Theme } from '@material-ui/core';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import AccountCircle from '@material-ui/icons/AccountCircle';
import React from 'react';
import { Link, NavLink } from 'react-router-dom';
import BirdmapTitle from './BirdmapTitle';
export default function BirdmapBar(props: { onLogout: () => void; isAuthenticated: any; isAdmin: any; }) {
const classes = useAppbarStyles();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef<HTMLButtonElement>(null);
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event: React.MouseEvent<EventTarget>) => {
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
return;
}
setOpen(false);
};
const handleLogout = (event: React.MouseEvent<EventTarget>) => {
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
return;
}
props.onLogout();
setOpen(false);
};
function handleListKeyDown(event: React.KeyboardEvent) {
if (event.key === 'Tab') {
event.preventDefault();
setOpen(false);
}
}
const prevOpen = React.useRef(open);
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
anchorRef.current!.focus();
}
prevOpen.current = open;
}, [open]);
const renderNavLinks = () => {
return props.isAuthenticated
? <Container className={classes.nav_menu}>
<NavLink exact to="/" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Dashboard</NavLink>
{props.isAdmin ? <NavLink exact to="/logs" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Logs</NavLink> : null}
<NavLink to="/devices" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Devices</NavLink>
<NavLink exact to="/heatmap" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Heatmap</NavLink>
<IconButton className={classes.nav_menu_icon}
ref={anchorRef}
aria-haspopup="true"
aria-controls={open ? 'menu-list-grow' : undefined}
aria-label="account of current user"
onClick={handleToggle}>
<AccountCircle />
</IconButton>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleLogout} component={Link} {...{ to: '/login' }}>Logout</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</Container>
: null;
};
return (
<AppBar position="static">
<Toolbar>
<BirdmapTitle />
<Typography component={'span'} className={classes.typo}>
{renderNavLinks()}
</Typography>
</Toolbar>
</AppBar>
)
};
const useAppbarStyles = makeStyles((theme: Theme) =>
createStyles({
typo: {
marginLeft: 'auto',
color: 'white',
},
nav_menu: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
nav_menu_icon: {
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
nav_menu_item: {
textDecoration: 'none',
fontWeight: 'normal',
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
nav_menu_item_active: {
textDecoration: 'underline',
fontWeight: 'bold',
color: 'inherit',
marginLeft: '24px',
'&:hover': {
color: 'inherit',
}
},
}),
);

View File

@ -55,13 +55,13 @@ export default function Auth(props: any) {
setIsLoggingIn(true);
AuthService.login(username, password)
.then(() => {
setIsLoggingIn(false);
props.onAuthenticated();
history.push('/');
}).catch(() => {
setShowError(true);
setErrorMessage('Invalid credentials');
}).finally(() => {
setIsLoggingIn(false);
setErrorMessage('Invalid credentials');
});
};

View File

@ -0,0 +1,256 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiException = exports.RegisterRequest = exports.AuthenticateRequest = exports.HttpStatusCode = void 0;
var AuthClient = /** @class */ (function () {
function AuthClient(baseUrl, http) {
this.jsonParseReviver = undefined;
this.http = http ? http : window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "";
}
AuthClient.prototype.authenticate = function (model) {
var _this = this;
var url_ = this.baseUrl + "/api/Auth/authenticate";
url_ = url_.replace(/[?&]$/, "");
var content_ = JSON.stringify(model);
var options_ = {
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processAuthenticate(_response);
});
};
AuthClient.prototype.processAuthenticate = function (response) {
var _this = this;
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 200) {
return response.text().then(function (_responseText) {
var result200 = null;
var resultData200 = _responseText === "" ? null : JSON.parse(_responseText, _this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : null;
return result200;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
AuthClient.prototype.register = function (model) {
var _this = this;
var url_ = this.baseUrl + "/api/Auth/register";
url_ = url_.replace(/[?&]$/, "");
var content_ = JSON.stringify(model);
var options_ = {
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processRegister(_response);
});
};
AuthClient.prototype.processRegister = function (response) {
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 204) {
return response.text().then(function (_responseText) {
return;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
return AuthClient;
}());
exports.default = AuthClient;
var HttpStatusCode;
(function (HttpStatusCode) {
HttpStatusCode["Continue"] = "Continue";
HttpStatusCode["SwitchingProtocols"] = "SwitchingProtocols";
HttpStatusCode["Processing"] = "Processing";
HttpStatusCode["EarlyHints"] = "EarlyHints";
HttpStatusCode["OK"] = "OK";
HttpStatusCode["Created"] = "Created";
HttpStatusCode["Accepted"] = "Accepted";
HttpStatusCode["NonAuthoritativeInformation"] = "NonAuthoritativeInformation";
HttpStatusCode["NoContent"] = "NoContent";
HttpStatusCode["ResetContent"] = "ResetContent";
HttpStatusCode["PartialContent"] = "PartialContent";
HttpStatusCode["MultiStatus"] = "MultiStatus";
HttpStatusCode["AlreadyReported"] = "AlreadyReported";
HttpStatusCode["IMUsed"] = "IMUsed";
HttpStatusCode["MultipleChoices"] = "Ambiguous";
HttpStatusCode["Ambiguous"] = "Ambiguous";
HttpStatusCode["MovedPermanently"] = "Moved";
HttpStatusCode["Moved"] = "Moved";
HttpStatusCode["Found"] = "Redirect";
HttpStatusCode["Redirect"] = "Redirect";
HttpStatusCode["SeeOther"] = "RedirectMethod";
HttpStatusCode["RedirectMethod"] = "RedirectMethod";
HttpStatusCode["NotModified"] = "NotModified";
HttpStatusCode["UseProxy"] = "UseProxy";
HttpStatusCode["Unused"] = "Unused";
HttpStatusCode["TemporaryRedirect"] = "TemporaryRedirect";
HttpStatusCode["RedirectKeepVerb"] = "TemporaryRedirect";
HttpStatusCode["PermanentRedirect"] = "PermanentRedirect";
HttpStatusCode["BadRequest"] = "BadRequest";
HttpStatusCode["Unauthorized"] = "Unauthorized";
HttpStatusCode["PaymentRequired"] = "PaymentRequired";
HttpStatusCode["Forbidden"] = "Forbidden";
HttpStatusCode["NotFound"] = "NotFound";
HttpStatusCode["MethodNotAllowed"] = "MethodNotAllowed";
HttpStatusCode["NotAcceptable"] = "NotAcceptable";
HttpStatusCode["ProxyAuthenticationRequired"] = "ProxyAuthenticationRequired";
HttpStatusCode["RequestTimeout"] = "RequestTimeout";
HttpStatusCode["Conflict"] = "Conflict";
HttpStatusCode["Gone"] = "Gone";
HttpStatusCode["LengthRequired"] = "LengthRequired";
HttpStatusCode["PreconditionFailed"] = "PreconditionFailed";
HttpStatusCode["RequestEntityTooLarge"] = "RequestEntityTooLarge";
HttpStatusCode["RequestUriTooLong"] = "RequestUriTooLong";
HttpStatusCode["UnsupportedMediaType"] = "UnsupportedMediaType";
HttpStatusCode["RequestedRangeNotSatisfiable"] = "RequestedRangeNotSatisfiable";
HttpStatusCode["ExpectationFailed"] = "ExpectationFailed";
HttpStatusCode["MisdirectedRequest"] = "MisdirectedRequest";
HttpStatusCode["UnprocessableEntity"] = "UnprocessableEntity";
HttpStatusCode["Locked"] = "Locked";
HttpStatusCode["FailedDependency"] = "FailedDependency";
HttpStatusCode["UpgradeRequired"] = "UpgradeRequired";
HttpStatusCode["PreconditionRequired"] = "PreconditionRequired";
HttpStatusCode["TooManyRequests"] = "TooManyRequests";
HttpStatusCode["RequestHeaderFieldsTooLarge"] = "RequestHeaderFieldsTooLarge";
HttpStatusCode["UnavailableForLegalReasons"] = "UnavailableForLegalReasons";
HttpStatusCode["InternalServerError"] = "InternalServerError";
HttpStatusCode["NotImplemented"] = "NotImplemented";
HttpStatusCode["BadGateway"] = "BadGateway";
HttpStatusCode["ServiceUnavailable"] = "ServiceUnavailable";
HttpStatusCode["GatewayTimeout"] = "GatewayTimeout";
HttpStatusCode["HttpVersionNotSupported"] = "HttpVersionNotSupported";
HttpStatusCode["VariantAlsoNegotiates"] = "VariantAlsoNegotiates";
HttpStatusCode["InsufficientStorage"] = "InsufficientStorage";
HttpStatusCode["LoopDetected"] = "LoopDetected";
HttpStatusCode["NotExtended"] = "NotExtended";
HttpStatusCode["NetworkAuthenticationRequired"] = "NetworkAuthenticationRequired";
})(HttpStatusCode = exports.HttpStatusCode || (exports.HttpStatusCode = {}));
var AuthenticateRequest = /** @class */ (function () {
function AuthenticateRequest(data) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
this[property] = data[property];
}
}
}
AuthenticateRequest.prototype.init = function (_data) {
if (_data) {
this.username = _data["username"];
this.password = _data["password"];
}
};
AuthenticateRequest.fromJS = function (data) {
data = typeof data === 'object' ? data : {};
var result = new AuthenticateRequest();
result.init(data);
return result;
};
AuthenticateRequest.prototype.toJSON = function (data) {
data = typeof data === 'object' ? data : {};
data["username"] = this.username;
data["password"] = this.password;
return data;
};
return AuthenticateRequest;
}());
exports.AuthenticateRequest = AuthenticateRequest;
var RegisterRequest = /** @class */ (function () {
function RegisterRequest(data) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
this[property] = data[property];
}
}
}
RegisterRequest.prototype.init = function (_data) {
if (_data) {
this.username = _data["username"];
this.password = _data["password"];
this.confirmPassword = _data["confirmPassword"];
}
};
RegisterRequest.fromJS = function (data) {
data = typeof data === 'object' ? data : {};
var result = new RegisterRequest();
result.init(data);
return result;
};
RegisterRequest.prototype.toJSON = function (data) {
data = typeof data === 'object' ? data : {};
data["username"] = this.username;
data["password"] = this.password;
data["confirmPassword"] = this.confirmPassword;
return data;
};
return RegisterRequest;
}());
exports.RegisterRequest = RegisterRequest;
var ApiException = /** @class */ (function (_super) {
__extends(ApiException, _super);
function ApiException(message, status, response, headers, result) {
var _this = _super.call(this) || this;
_this.isApiException = true;
_this.message = message;
_this.status = status;
_this.response = response;
_this.headers = headers;
_this.result = result;
return _this;
}
ApiException.isApiException = function (obj) {
return obj.isApiException === true;
};
return ApiException;
}(Error));
exports.ApiException = ApiException;
function throwException(message, status, response, headers, result) {
if (result !== null && result !== undefined)
throw result;
else
throw new ApiException(message, status, response, headers, null);
}
//# sourceMappingURL=AuthClient.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,274 @@
export default class AuthClient {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
this.http = http ? http : <any>window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "";
}
authenticate(model: AuthenticateRequest): Promise<any> {
let url_ = this.baseUrl + "/api/Auth/authenticate";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(model);
let options_ = <RequestInit>{
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processAuthenticate(_response);
});
}
protected processAuthenticate(response: Response): Promise<any> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : <any>null;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<any>(<any>null);
}
register(model: RegisterRequest): Promise<void> {
let url_ = this.baseUrl + "/api/Auth/register";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(model);
let options_ = <RequestInit>{
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processRegister(_response);
});
}
protected processRegister(response: Response): Promise<void> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 204) {
return response.text().then((_responseText) => {
return;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<void>(<any>null);
}
}
export enum HttpStatusCode {
Continue = "Continue",
SwitchingProtocols = "SwitchingProtocols",
Processing = "Processing",
EarlyHints = "EarlyHints",
OK = "OK",
Created = "Created",
Accepted = "Accepted",
NonAuthoritativeInformation = "NonAuthoritativeInformation",
NoContent = "NoContent",
ResetContent = "ResetContent",
PartialContent = "PartialContent",
MultiStatus = "MultiStatus",
AlreadyReported = "AlreadyReported",
IMUsed = "IMUsed",
MultipleChoices = "Ambiguous",
Ambiguous = "Ambiguous",
MovedPermanently = "Moved",
Moved = "Moved",
Found = "Redirect",
Redirect = "Redirect",
SeeOther = "RedirectMethod",
RedirectMethod = "RedirectMethod",
NotModified = "NotModified",
UseProxy = "UseProxy",
Unused = "Unused",
TemporaryRedirect = "TemporaryRedirect",
RedirectKeepVerb = "TemporaryRedirect",
PermanentRedirect = "PermanentRedirect",
BadRequest = "BadRequest",
Unauthorized = "Unauthorized",
PaymentRequired = "PaymentRequired",
Forbidden = "Forbidden",
NotFound = "NotFound",
MethodNotAllowed = "MethodNotAllowed",
NotAcceptable = "NotAcceptable",
ProxyAuthenticationRequired = "ProxyAuthenticationRequired",
RequestTimeout = "RequestTimeout",
Conflict = "Conflict",
Gone = "Gone",
LengthRequired = "LengthRequired",
PreconditionFailed = "PreconditionFailed",
RequestEntityTooLarge = "RequestEntityTooLarge",
RequestUriTooLong = "RequestUriTooLong",
UnsupportedMediaType = "UnsupportedMediaType",
RequestedRangeNotSatisfiable = "RequestedRangeNotSatisfiable",
ExpectationFailed = "ExpectationFailed",
MisdirectedRequest = "MisdirectedRequest",
UnprocessableEntity = "UnprocessableEntity",
Locked = "Locked",
FailedDependency = "FailedDependency",
UpgradeRequired = "UpgradeRequired",
PreconditionRequired = "PreconditionRequired",
TooManyRequests = "TooManyRequests",
RequestHeaderFieldsTooLarge = "RequestHeaderFieldsTooLarge",
UnavailableForLegalReasons = "UnavailableForLegalReasons",
InternalServerError = "InternalServerError",
NotImplemented = "NotImplemented",
BadGateway = "BadGateway",
ServiceUnavailable = "ServiceUnavailable",
GatewayTimeout = "GatewayTimeout",
HttpVersionNotSupported = "HttpVersionNotSupported",
VariantAlsoNegotiates = "VariantAlsoNegotiates",
InsufficientStorage = "InsufficientStorage",
LoopDetected = "LoopDetected",
NotExtended = "NotExtended",
NetworkAuthenticationRequired = "NetworkAuthenticationRequired",
}
export class AuthenticateRequest implements IAuthenticateRequest {
username!: string;
password!: string;
constructor(data?: IAuthenticateRequest) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(_data?: any) {
if (_data) {
this.username = _data["username"];
this.password = _data["password"];
}
}
static fromJS(data: any): AuthenticateRequest {
data = typeof data === 'object' ? data : {};
let result = new AuthenticateRequest();
result.init(data);
return result;
}
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["username"] = this.username;
data["password"] = this.password;
return data;
}
}
export interface IAuthenticateRequest {
username: string;
password: string;
}
export class RegisterRequest implements IRegisterRequest {
username!: string;
password!: string;
confirmPassword!: string;
constructor(data?: IRegisterRequest) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(_data?: any) {
if (_data) {
this.username = _data["username"];
this.password = _data["password"];
this.confirmPassword = _data["confirmPassword"];
}
}
static fromJS(data: any): RegisterRequest {
data = typeof data === 'object' ? data : {};
let result = new RegisterRequest();
result.init(data);
return result;
}
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["username"] = this.username;
data["password"] = this.password;
data["confirmPassword"] = this.confirmPassword;
return data;
}
}
export interface IRegisterRequest {
username: string;
password: string;
confirmPassword: string;
}
export interface FileResponse {
data: Blob;
status: number;
fileName?: string;
headers?: { [name: string]: any };
}
export class ApiException extends Error {
message: string;
status: number;
response: string;
headers: { [key: string]: any; };
result: any;
constructor(message: string, status: number, response: string, headers: { [key: string]: any; }, result: any) {
super();
this.message = message;
this.status = status;
this.response = response;
this.headers = headers;
this.result = result;
}
protected isApiException = true;
static isApiException(obj: any): obj is ApiException {
return obj.isApiException === true;
}
}
function throwException(message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any {
if (result !== null && result !== undefined)
throw result;
else
throw new ApiException(message, status, response, headers, null);
}

View File

@ -1,10 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var ServiceBase_1 = require("../../common/ServiceBase");
var login_url = 'api/auth/authenticate';
var AuthClient_1 = require("./AuthClient");
exports.default = {
isAuthenticated: function () {
return sessionStorage.getItem('user') !== null;
return sessionStorage.getItem('user') !== null && sessionStorage.getItem('user') !== undefined;
},
isAdmin: function () {
return sessionStorage.getItem('role') === 'Admin';
@ -14,21 +13,16 @@ exports.default = {
sessionStorage.removeItem('role');
},
login: function (username, password) {
var body = {
var service = new AuthClient_1.default();
var request = new AuthClient_1.AuthenticateRequest({
username: username,
password: password
};
var options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
};
return ServiceBase_1.default.makeRequest(login_url, options)
});
return service.authenticate(request)
.then(function (response) {
sessionStorage.setItem('user', response.token_type + " " + response.access_token);
sessionStorage.setItem('role', response.role);
//console.log(response);
sessionStorage.setItem('user', response.tokenType + " " + response.accessToken);
sessionStorage.setItem('role', response.userRole);
return Promise.resolve();
});
}

View File

@ -1 +1 @@
{"version":3,"file":"AuthService.js","sourceRoot":"","sources":["AuthService.ts"],"names":[],"mappings":";;AAAA,wDAAmD;AAEnD,IAAM,SAAS,GAAG,uBAAuB,CAAC;AAE1C,kBAAe;IACX,eAAe;QACX,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACnD,CAAC;IAED,OAAO;QACH,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IACtD,CAAC;IAED,MAAM;QACF,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAClC,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,EAAL,UAAM,QAAgB,EAAE,QAAgB;QACpC,IAAI,IAAI,GAAG;YACP,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,QAAQ;SACrB,CAAC;QACF,IAAI,OAAO,GAAG;YACV,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACL,cAAc,EAAE,kBAAkB;aACrC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC7B,CAAC;QAEF,OAAO,qBAAW,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC;aAC7C,IAAI,CAAC,UAAA,QAAQ;YACV,cAAc,CAAC,OAAO,CAAC,MAAM,EAAK,QAAQ,CAAC,UAAU,SAAI,QAAQ,CAAC,YAAc,CAAC,CAAC;YAClF,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC9C,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;IACX,CAAC;CACJ,CAAA"}
{"version":3,"file":"AuthService.js","sourceRoot":"","sources":["AuthService.ts"],"names":[],"mappings":";;AAAA,2CAA+D;AAE/D,kBAAe;IACX,eAAe;QACX,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC;IACnG,CAAC;IAED,OAAO;QACH,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IACtD,CAAC;IAED,MAAM;QACF,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAClC,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,EAAL,UAAM,QAAgB,EAAE,QAAgB;QACpC,IAAM,OAAO,GAAG,IAAI,oBAAU,EAAE,CAAC;QAEjC,IAAI,OAAO,GAAG,IAAI,gCAAmB,CAAC;YAClC,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,QAAQ;SACrB,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC;aAC/B,IAAI,CAAC,UAAA,QAAQ;YACV,wBAAwB;YACxB,cAAc,CAAC,OAAO,CAAC,MAAM,EAAK,QAAQ,CAAC,SAAS,SAAI,QAAQ,CAAC,WAAa,CAAC,CAAC;YAChF,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAClD,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;IACX,CAAC;CACJ,CAAA"}

View File

@ -1,10 +1,8 @@
import ServiceBase from '../../common/ServiceBase';
const login_url = 'api/auth/authenticate';
import AuthClient, { AuthenticateRequest } from './AuthClient';
export default {
isAuthenticated() {
return sessionStorage.getItem('user') !== null;
return sessionStorage.getItem('user') !== null && sessionStorage.getItem('user') !== undefined;
},
isAdmin() {
@ -17,22 +15,18 @@ export default {
},
login(username: string, password: string) {
let body = {
const service = new AuthClient();
let request = new AuthenticateRequest({
username: username,
password: password
};
let options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
};
});
return ServiceBase.makeRequest(login_url, options)
return service.authenticate(request)
.then(response => {
sessionStorage.setItem('user', `${response.token_type} ${response.access_token}`);
sessionStorage.setItem('role', response.role);
//console.log(response);
sessionStorage.setItem('user', `${response.tokenType} ${response.accessToken}`);
sessionStorage.setItem('role', response.userRole);
return Promise.resolve();
});
}

View File

@ -0,0 +1,328 @@
import React, { Component } from 'react';
import { withStyles } from '@material-ui/styles';
import Services from './services/Services';
import { blueGrey } from '@material-ui/core/colors';
import { Box, Grid, IconButton, Paper, Typography } from '@material-ui/core';
import DonutChart from './charts/DonutChart';
import HeatmapChart from './charts/HeatmapChart';
import BarChart from './charts/BarChart';
import LineChart from './charts/LineChart';
import DevicesContext from '../../contexts/DevicesContext';
import C from '../../common/Constants';
const styles = theme => ({
root: {
flexGrow: 1,
padding: '64px',
backgroundColor: theme.palette.primary.dark,
},
typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
paper: {
backgroundColor: blueGrey[50],
padding: '16px',
}
});
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
deviceSeries: [],
sensorSeries: [],
heatmapSecondsSeries: [],
heatmapMinutesSeries: [],
barSeries: [],
barCategories: [],
lineSeries: [],
};
this.updateSeries = this.updateSeries.bind(this);
this.updateDynamic = this.updateDynamic.bind(this);
this.performTask = this.performTask.bind(this);
}
static contextType = DevicesContext;
componentDidMount() {
this.context.addHandler(C.update_all_method_name, this.updateSeries);
this.context.addHandler(C.update_method_name, this.updateSeries);
this.updateSeries();
this.updateDynamic();
}
componentWillUnmount() {
this.context.removeHandler(C.update_all_method_name, this.updateSeries);
this.context.removeHandler(C.update_method_name, this.updateSeries);
if (this.updateTimer) {
clearTimeout(this.updateTimer);
}
}
getItemsWithStatus(iterate, status) {
const items = [];
for (var d of iterate) {
if (d.status == status) {
items.push(d);
}
}
return items;
}
getDevicesWithStatus(status) {
return this.getItemsWithStatus(this.context.devices, status);
}
getSensorsWithStatus(status) {
const sensors = [];
for (var d of this.context.devices) {
sensors.push(...d.sensors)
}
return this.getItemsWithStatus(sensors, status);
}
getDeviceSeries() {
var online = this.getDevicesWithStatus("Online").length;
var offline = this.getDevicesWithStatus("Offline").length;
var error = this.getDevicesWithStatus("Error").length;
return [online, offline, error]
}
getSensorSeries() {
var online = this.getSensorsWithStatus("Online").length;
var offline = this.getSensorsWithStatus("Offline").length;
var unknown = this.getSensorsWithStatus("Unknown").length;
return [online, offline, unknown]
}
updateSeries() {
this.setState({
deviceSeries: this.getDeviceSeries(),
sensorSeries: this.getSensorSeries()
});
}
updateDynamic = () => {
const secondAgo = new Date();
secondAgo.setMilliseconds(0);
const minuteAgo = new Date(Date.now() - 1000 * 60);
const hourAgo = new Date(Date.now() - 1000 * 60 * 60);
const minuteDevicePoints = {};
const hourDevicePoints = {};
const barDevicePoints = {};
const linePoints = {};
for (var d of this.context.devices) {
minuteDevicePoints[d.id] = Array(60).fill(0);
hourDevicePoints[d.id] = Array(60).fill(0);
barDevicePoints[d.id] = Array(3).fill(0);
}
const processHeatmapItem = (items, index) => {
const p = items[index];
if (p.date > minuteAgo) {
var seconds = Math.floor((p.date.getTime() - minuteAgo.getTime()) / 1000);
var oldProb = minuteDevicePoints[p.deviceId][seconds];
if (oldProb < p.prob) {
minuteDevicePoints[p.deviceId][seconds] = p.prob;
}
}
if (p.date > hourAgo) {
var minutes = Math.floor((p.date.getTime() - hourAgo.getTime()) / (1000 * 60));
var oldProb = hourDevicePoints[p.deviceId][minutes];
if (oldProb < p.prob) {
hourDevicePoints[p.deviceId][minutes] = p.prob;
}
}
if (p.prob > 0.5 && p.prob <= 0.7) {
barDevicePoints[p.deviceId][0] += 1;
}
if (p.prob > 0.7 && p.prob <= 0.9) {
barDevicePoints[p.deviceId][1] += 1;
}
if (p.prob > 0.9) {
barDevicePoints[p.deviceId][2] += 1;
}
if (p.date < secondAgo) {
var shortDate = p.date.toUTCString();
var point = linePoints[shortDate];
if (point === undefined) {
linePoints[shortDate] = 1;
} else {
linePoints[shortDate] += 1;
}
}
}
const onFinished = () => {
const minuteHeatmapSeries = [];
var i = 0;
for (var p in minuteDevicePoints) {
minuteHeatmapSeries.push({
name: "Device " + i,
data: minuteDevicePoints[p].map((value, index) => ({
x: new Date(Date.now() - (60 - index) * 1000).toLocaleTimeString('hu-HU'),
y: value
})),
});
i++;
};
const hourHeatmapSeries = [];
var i = 0;
for (var p in hourDevicePoints) {
hourHeatmapSeries.push({
name: "Device " + i,
data: hourDevicePoints[p].map((value, index) => ({
x: new Date(Date.now() - (60 - index) * 1000 * 60).toLocaleTimeString('hu-HU').substring(0, 5),
y: value
})),
});
i++;
};
const barSeries = [];
const getCount = column => {
var counts = [];
for (var p in barDevicePoints) {
counts.unshift(barDevicePoints[p][column]);
}
return counts;
};
barSeries.push({
name: "Prob > 0.5",
data: getCount(0),
});
barSeries.push({
name: "Prob > 0.7",
data: getCount(1),
});
barSeries.push({
name: "Prob > 0.9",
data: getCount(2),
});
const lineSeries = [{ name: "message/sec", data: [] }];
for (var m in linePoints) {
lineSeries[0].data.push({
x: new Date(m).getTime(),
y: linePoints[m],
})
}
const getBarCategories = () => {
const categories = [];
for (var i = this.context.devices.length - 1; i >= 0; i--) {
categories.push("Device " + i)
}
return categories;
}
const toUpdate = [
{ heatmapSecondsSeries: minuteHeatmapSeries },
{ heatmapMinutesSeries: hourHeatmapSeries },
{ barSeries: barSeries },
{ barCategories: getBarCategories() },
{ lineSeries: lineSeries }
];
//Set states must be done separately otherwise ApexChart's UI update freezes the page.
this.performTask(toUpdate, 2, 300, (list, index) => {
this.setState(list[index]);
},
() => {
this.updateTimer = setTimeout(this.updateDynamic, 1000);
});
}
this.performTask(this.context.heatmapPoints, Math.ceil(this.context.heatmapPoints.length / 50), 20,
processHeatmapItem, onFinished);
}
performTask(items, numToProcess, wait, processItem, onFinished) {
var pos = 0;
// This is run once for every numToProcess items.
function iteration() {
// Calculate last position.
var j = Math.min(pos + numToProcess, items.length);
// Start at current position and loop to last position.
for (var i = pos; i < j; i++) {
processItem(items, i);
}
// Increment current position.
pos += numToProcess;
// Only continue if there are more items to process.
if (pos < items.length)
setTimeout(iteration, wait); // Wait 10 ms to let the UI update.
else
onFinished();
}
iteration();
}
render() {
const { classes } = this.props;
return (
<Box className={classes.root}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Services isAdmin={this.props.isAdmin} />
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<DonutChart totalLabel="Devices" series={this.state.deviceSeries} />
</Paper>
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<DonutChart totalLabel="Sensors" series={this.state.sensorSeries} />
</Paper>
</Grid>
<Grid item xs={12}>
<Paper className={classes.paper}>
<HeatmapChart label="Highest probability per second by devices" series={this.state.heatmapSecondsSeries} />
</Paper>
</Grid>
<Grid item xs={12}>
<Paper className={classes.paper}>
<HeatmapChart label="Highest probability per minute by devices" series={this.state.heatmapMinutesSeries} />
</Paper>
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<BarChart label="# of messages by devices" series={this.state.barSeries} categories={this.state.barCategories} />
</Paper>
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<LineChart label="# of messages per second" series={this.state.lineSeries} />
</Paper>
</Grid>
</Grid>
</Box>
);
}
}
export default withStyles(styles)(Dashboard);

View File

@ -0,0 +1,92 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red, orange, amber } from '@material-ui/core/colors';
export class BarChart extends Component {
constructor(props) {
super(props)
this.state = {
options: {},
};
}
componentDidUpdate(prevProps) {
if (prevProps.categories !== this.props.categories) {
this.setState({options: {
chart: {
stacked: true,
animations: {
enabled: true,
easing: 'linear',
speed: 250,
animateGradually: {
enabled: false,
},
dynamicAnimation: {
enabled: true,
speed: 250
}
},
},
plotOptions: {
bar: {
horizontal: true,
},
},
colors: [blueGrey[500], blueGrey[700], blueGrey[900]],
stroke: {
width: 1,
colors: ['#fff']
},
title: {
text: this.props.label,
style: {
fontSize: '22px',
fontWeight: 600,
fontFamily: 'Helvetica, Arial, sans-serif',
},
},
xaxis: {
categories: this.props.categories,
labels: {
formatter: function (val) {
return val;
}
}
},
yaxis: {
title: {
text: undefined
},
},
tooltip: {
y: {
formatter: function (val) {
return val;
}
}
},
fill: {
opacity: 1
},
legend: {
position: 'top',
}
}});
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="bar"
height={600}
/>
)
}
}
export default BarChart;

View File

@ -0,0 +1,67 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red } from '@material-ui/core/colors';
export class DonutChart extends Component {
constructor(props) {
super(props);
this.state = {
options: {
legend: {
fontSize: '18px',
},
plotOptions: {
pie: {
startAngle: 0,
expandOnClick: false,
offsetX: 0,
offsetY: 0,
customScale: 1,
dataLabels: {
offset: 0,
minAngleToShowLabel: 10
},
donut: {
size: '65%',
background: 'transparent',
labels: {
show: true,
total: {
show: true,
showAlways: true,
label: props.totalLabel,
fontSize: '22px',
fontFamily: 'Helvetica, Arial, sans-serif',
fontWeight: 600,
color: '#373d3f',
formatter: function (w) {
return w.globals.seriesTotals.reduce((a, b) => {
return a + b
}, 0)
}
}
}
},
}
},
dataLabels: {
enabled: false
},
colors: [green[500], blueGrey[500], red[500]],
labels: ['Online', 'Offline', 'Error / Unknown']},
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="donut"/>
)
}
}
export default DonutChart;

View File

@ -0,0 +1,55 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red } from '@material-ui/core/colors';
export class HeatmapChart extends Component {
constructor(props) {
super(props)
this.state = {
options: {
chart: {
animations: {
enabled: true,
easing: 'linear',
speed: 250,
animateGradually: {
enabled: false,
speed: 250,
},
dynamicAnimation: {
enabled: true,
speed: 250
}
}
},
dataLabels: {
enabled: false
},
colors: [blueGrey[900]],
title: {
text: props.label,
style: {
fontSize: '22px',
fontWeight: 600,
fontFamily: 'Helvetica, Arial, sans-serif',
},
},
},
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="heatmap"
height={600}
/>
)
}
}
export default HeatmapChart

View File

@ -0,0 +1,74 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red } from '@material-ui/core/colors';
export class LineChart extends Component {
constructor(props) {
super(props)
this.state = {
options: {
chart: {
animations: {
enabled: true,
easing: 'linear',
speed: 250,
animateGradually: {
enabled: false,
},
dynamicAnimation: {
enabled: true,
speed: 250
}
},
zoom: {
enabled: false
}
},
colors: [blueGrey[900]],
dataLabels: {
enabled: false
},
stroke: {
curve: 'straight'
},
title: {
text: this.props.label,
align: 'left',
style: {
fontSize: '22px',
fontWeight: 600,
fontFamily: 'Helvetica, Arial, sans-serif',
},
},
grid: {
row: {
colors: ['#f3f3f3', 'transparent'], // takes an array which will be repeated on columns
opacity: 0.5
},
},
xaxis: {
type: 'datetime',
labels: {
formatter: function (val) {
return new Date(val).toLocaleTimeString('hu-HU');
}
}
}
},
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="line"
height={600}
/>
)
}
}
export default LineChart

View File

@ -0,0 +1,61 @@
import { TextField } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import React, { useState } from 'react';
export default function AddNewDialog(props) {
const [name, setName] = useState("");
const [url, setUrl] = useState("");
const onNameChange = (event) => {
setName(event.target.value);
}
const onUrlChange = (event) => {
setUrl(event.target.value);
}
return (
<div>
<Dialog
open={props.open}
onClose={props.handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{"Add new service."}</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
id="name"
label="Name"
type="text"
onChange={onNameChange}
fullWidth
/>
<TextField
autoFocus
margin="dense"
id="url"
label="Url"
type="text"
onChange={onUrlChange}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={props.handleClose} color="primary">
Cancel
</Button>
<Button onClick={() => props.handleAdd(name, url)} color="primary" autoFocus>
Add
</Button>
</DialogActions>
</Dialog>
</div>
);
}

View File

@ -0,0 +1,35 @@
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import React from 'react';
export default function DeleteDialog(props) {
return (
<div>
<Dialog
open={props.open}
onClose={() => props.handleClose(false)}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{"Are you sure?"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Deleting is permament.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => props.handleClose(false)} color="primary">
Cancel
</Button>
<Button onClick={() => props.handleClose(true)} color="primary" autoFocus>
OK
</Button>
</DialogActions>
</Dialog>
</div>
);
}

View File

@ -0,0 +1,234 @@
import { Box, FormControlLabel, Grid, IconButton, Paper, TextField, Typography } from '@material-ui/core';
import Accordion from '@material-ui/core/Accordion';
import AccordionDetails from '@material-ui/core/AccordionDetails';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import { blueGrey, green, red } from '@material-ui/core/colors';
import { CancelRounded, CheckCircleRounded, Delete, Edit } from '@material-ui/icons/';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { withStyles } from '@material-ui/styles';
import React, { Component } from 'react';
import DeleteDialog from './DeleteDialog';
const styles = theme => ({
root: {
flexGrow: 1,
padding: '64px',
backgroundColor: theme.palette.primary.dark,
},
acc_summary: {
backgroundColor: blueGrey[50],
padding: theme.spacing(2),
textAlign: 'center',
height: '75px',
},
acc_details: {
backgroundColor: blueGrey[100],
},
grid_typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
grid_typo_2: {
marginLeft: '5px',
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
color: theme.palette.text.secondary,
},
typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
icon_box: {
marginLeft: '30px',
},
paper: {
backgroundColor: blueGrey[50],
padding: theme.spacing(2),
textAlign: 'center',
height: '75px',
}
});
class ServiceInfoComponent extends Component {
constructor(props) {
super(props);
this.state = {
isDialogOpen: false,
isEditing: false,
name: "",
url: "",
}
this.handleDialogClose = this.handleDialogClose.bind(this);
this.onNameChange = this.onNameChange.bind(this);
this.onUrlChange = this.onUrlChange.bind(this);
this.handleSaveCancel = this.handleSaveCancel.bind(this);
}
componentDidMount() {
this.setState({ name: this.props.info.service.name, url: this.props.info.service.uri });
if (this.props.isEditing !== undefined) {
this.setState({ isEditing: this.props.isEditing });
}
}
onNameChange(event) {
this.setState({ name: event.target.value });
}
onUrlChange(event) {
this.setState({ url: event.target.value });
}
getColor(status) {
if (status === "OK")
return { color: green[600] };
else
return { color: red[600] };
}
handleDialogClose(result) {
this.setState({ isDialogOpen: false });
if (result === true) {
this.props.service.delete(this.props.info.service.id);
}
}
handleEditCancel(value) {
this.setState({ isEditing: value });
}
handleSaveCancel() {
let request = {
...this.props.info.service
};
request.name = this.state.name;
request.uri = this.state.url;
if (request.id > 0) {
this.props.service.put(request).catch(ex => {
console.log(ex);
});
}
else {
this.props.service.post(request).catch(ex => {
console.log(ex);
});
}
this.setState({ isEditing: false });
}
renderSaveCancel() {
return (
<Box styles={{ marginLeft: 'auto' }}>
<IconButton color="primary" onClick={this.handleSaveCancel}>
<CheckCircleRounded fontSize="large" />
</IconButton>
<IconButton color="primary" onClick={() => this.setState({ isEditing: false })}>
<CancelRounded fontSize="large" />
</IconButton>
</Box>
);
}
renderButtons() {
const renderEditDelete = () => {
return (
<React.Fragment>
<DeleteDialog open={this.state.isDialogOpen} handleClose={this.handleDialogClose}/>
<IconButton color="primary" onClick={() => this.handleEditCancel(true)}>
<Edit fontSize="large"/>
</IconButton>
<IconButton style={{color: red[600]}} onClick={() => this.setState({ isDialogOpen: true })}>
<Delete fontSize="large"/>
</IconButton>
</React.Fragment>
);
}
const { classes } = this.props;
return (
<Box className={classes.icon_box}>
{this.props.isAdmin && this.props.info.service.name !== "Mqtt Client Service" ? renderEditDelete() : null}
</Box>
);
}
render() {
const { classes } = this.props;
const renderAccordion = () => {
return (
<Accordion>
<AccordionSummary className={classes.acc_summary}
expandIcon={<ExpandMoreIcon />}
aria-controls={"device-panel-/" + this.props.info.service.name}
id={"device-panel-/" + this.props.info.service.name}>
<Grid container
spacing={1}
direction="row"
justify="flex-start"
alignItems="center">
<Grid item>
<Typography className={classes.grid_typo}>{this.props.info.service.name}</Typography>
</Grid>
<Grid item>
<Typography className={classes.grid_typo_2}>{this.props.info.service.uri}</Typography>
</Grid>
<Grid item style={{ marginLeft: 'auto' }}>
<Grid container
spacing={1}
direction="row"
justify="flex-start"
alignItems="center">
<Grid item>
<FormControlLabel
onClick={(event) => event.stopPropagation()}
onFocus={(event) => event.stopPropagation()}
control={this.renderButtons()} />
</Grid>
<Grid item>
<Typography style={this.getColor(this.props.info.statusCode)}>Status: <b>{this.props.info.statusCode}</b></Typography>
</Grid>
</Grid>
</Grid>
</Grid>
</AccordionSummary>
<AccordionDetails className={classes.acc_details}>
{this.props.info.response}
</AccordionDetails>
</Accordion>
);
};
const renderTextFields = () => {
return (
<Paper className={classes.acc_summary}>
<Grid container
spacing={1}
direction="row"
justify="flex-start"
alignItems="center">
<Grid item xs>
<TextField label="Name" type="text" defaultValue={this.props.info.service.name} onChange={this.onNameChange} />
</Grid>
<Grid item xs={6}>
<TextField label="Url" type="text" fullWidth defaultValue={this.props.info.service.uri} onChange={this.onUrlChange}/>
</Grid>
<Grid item xs>
{this.renderSaveCancel()}
</Grid>
</Grid>
</Paper>
);
};
return this.state.isEditing ? renderTextFields() : renderAccordion();
}
}
export default withStyles(styles)(ServiceInfoComponent);

View File

@ -0,0 +1,386 @@
"use strict";
/* tslint:disable */
/* eslint-disable */
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiException = exports.HttpStatusCode = exports.ServiceRequest = exports.ServiceInfo = void 0;
var ServiceInfoService = /** @class */ (function () {
function ServiceInfoService(baseUrl, http) {
this.jsonParseReviver = undefined;
this.http = http ? http : window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "";
}
ServiceInfoService.prototype.getCount = function () {
var _this = this;
var url_ = this.baseUrl + "/api/Services/count";
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processGetCount(_response);
});
};
ServiceInfoService.prototype.processGetCount = function (response) {
var _this = this;
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 200) {
return response.text().then(function (_responseText) {
var result200 = null;
var resultData200 = _responseText === "" ? null : JSON.parse(_responseText, _this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : null;
return result200;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
ServiceInfoService.prototype.get = function () {
var _this = this;
var url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processGet(_response);
});
};
ServiceInfoService.prototype.processGet = function (response) {
var _this = this;
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 200) {
return response.text().then(function (_responseText) {
var result200 = null;
var resultData200 = _responseText === "" ? null : JSON.parse(_responseText, _this.jsonParseReviver);
if (Array.isArray(resultData200)) {
result200 = [];
for (var _i = 0, resultData200_1 = resultData200; _i < resultData200_1.length; _i++) {
var item = resultData200_1[_i];
result200.push(ServiceInfo.fromJS(item));
}
}
return result200;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
ServiceInfoService.prototype.post = function (request) {
var _this = this;
var url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
var content_ = JSON.stringify(request);
var options_ = {
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processPost(_response);
});
};
ServiceInfoService.prototype.processPost = function (response) {
var _this = this;
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 201) {
return response.text().then(function (_responseText) {
var result201 = null;
var resultData201 = _responseText === "" ? null : JSON.parse(_responseText, _this.jsonParseReviver);
result201 = ServiceRequest.fromJS(resultData201);
return result201;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
ServiceInfoService.prototype.put = function (request) {
var _this = this;
var url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
var content_ = JSON.stringify(request);
var options_ = {
body: content_,
method: "PUT",
headers: {
"Content-Type": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processPut(_response);
});
};
ServiceInfoService.prototype.processPut = function (response) {
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 204) {
return response.text().then(function (_responseText) {
return;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
ServiceInfoService.prototype.delete = function (id) {
var _this = this;
var url_ = this.baseUrl + "/api/Services/{id}";
if (id === undefined || id === null)
throw new Error("The parameter 'id' must be defined.");
url_ = url_.replace("{id}", encodeURIComponent("" + id));
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "DELETE",
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processDelete(_response);
});
};
ServiceInfoService.prototype.processDelete = function (response) {
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 204) {
return response.text().then(function (_responseText) {
return;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
return ServiceInfoService;
}());
exports.default = ServiceInfoService;
var ServiceInfo = /** @class */ (function () {
function ServiceInfo(data) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
this[property] = data[property];
}
}
}
ServiceInfo.prototype.init = function (_data) {
if (_data) {
this.service = _data["service"] ? ServiceRequest.fromJS(_data["service"]) : undefined;
this.statusCode = _data["statusCode"];
this.response = _data["response"];
}
};
ServiceInfo.fromJS = function (data) {
data = typeof data === 'object' ? data : {};
var result = new ServiceInfo();
result.init(data);
return result;
};
ServiceInfo.prototype.toJSON = function (data) {
data = typeof data === 'object' ? data : {};
data["service"] = this.service ? this.service.toJSON() : undefined;
data["statusCode"] = this.statusCode;
data["response"] = this.response;
return data;
};
return ServiceInfo;
}());
exports.ServiceInfo = ServiceInfo;
var ServiceRequest = /** @class */ (function () {
function ServiceRequest(data) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
this[property] = data[property];
}
}
}
ServiceRequest.prototype.init = function (_data) {
if (_data) {
this.id = _data["id"];
this.name = _data["name"];
this.uri = _data["uri"];
}
};
ServiceRequest.fromJS = function (data) {
data = typeof data === 'object' ? data : {};
var result = new ServiceRequest();
result.init(data);
return result;
};
ServiceRequest.prototype.toJSON = function (data) {
data = typeof data === 'object' ? data : {};
data["id"] = this.id;
data["name"] = this.name;
data["uri"] = this.uri;
return data;
};
return ServiceRequest;
}());
exports.ServiceRequest = ServiceRequest;
var HttpStatusCode;
(function (HttpStatusCode) {
HttpStatusCode["Continue"] = "Continue";
HttpStatusCode["SwitchingProtocols"] = "SwitchingProtocols";
HttpStatusCode["Processing"] = "Processing";
HttpStatusCode["EarlyHints"] = "EarlyHints";
HttpStatusCode["OK"] = "OK";
HttpStatusCode["Created"] = "Created";
HttpStatusCode["Accepted"] = "Accepted";
HttpStatusCode["NonAuthoritativeInformation"] = "NonAuthoritativeInformation";
HttpStatusCode["NoContent"] = "NoContent";
HttpStatusCode["ResetContent"] = "ResetContent";
HttpStatusCode["PartialContent"] = "PartialContent";
HttpStatusCode["MultiStatus"] = "MultiStatus";
HttpStatusCode["AlreadyReported"] = "AlreadyReported";
HttpStatusCode["IMUsed"] = "IMUsed";
HttpStatusCode["MultipleChoices"] = "Ambiguous";
HttpStatusCode["Ambiguous"] = "Ambiguous";
HttpStatusCode["MovedPermanently"] = "Moved";
HttpStatusCode["Moved"] = "Moved";
HttpStatusCode["Found"] = "Redirect";
HttpStatusCode["Redirect"] = "Redirect";
HttpStatusCode["SeeOther"] = "RedirectMethod";
HttpStatusCode["RedirectMethod"] = "RedirectMethod";
HttpStatusCode["NotModified"] = "NotModified";
HttpStatusCode["UseProxy"] = "UseProxy";
HttpStatusCode["Unused"] = "Unused";
HttpStatusCode["TemporaryRedirect"] = "TemporaryRedirect";
HttpStatusCode["RedirectKeepVerb"] = "TemporaryRedirect";
HttpStatusCode["PermanentRedirect"] = "PermanentRedirect";
HttpStatusCode["BadRequest"] = "BadRequest";
HttpStatusCode["Unauthorized"] = "Unauthorized";
HttpStatusCode["PaymentRequired"] = "PaymentRequired";
HttpStatusCode["Forbidden"] = "Forbidden";
HttpStatusCode["NotFound"] = "NotFound";
HttpStatusCode["MethodNotAllowed"] = "MethodNotAllowed";
HttpStatusCode["NotAcceptable"] = "NotAcceptable";
HttpStatusCode["ProxyAuthenticationRequired"] = "ProxyAuthenticationRequired";
HttpStatusCode["RequestTimeout"] = "RequestTimeout";
HttpStatusCode["Conflict"] = "Conflict";
HttpStatusCode["Gone"] = "Gone";
HttpStatusCode["LengthRequired"] = "LengthRequired";
HttpStatusCode["PreconditionFailed"] = "PreconditionFailed";
HttpStatusCode["RequestEntityTooLarge"] = "RequestEntityTooLarge";
HttpStatusCode["RequestUriTooLong"] = "RequestUriTooLong";
HttpStatusCode["UnsupportedMediaType"] = "UnsupportedMediaType";
HttpStatusCode["RequestedRangeNotSatisfiable"] = "RequestedRangeNotSatisfiable";
HttpStatusCode["ExpectationFailed"] = "ExpectationFailed";
HttpStatusCode["MisdirectedRequest"] = "MisdirectedRequest";
HttpStatusCode["UnprocessableEntity"] = "UnprocessableEntity";
HttpStatusCode["Locked"] = "Locked";
HttpStatusCode["FailedDependency"] = "FailedDependency";
HttpStatusCode["UpgradeRequired"] = "UpgradeRequired";
HttpStatusCode["PreconditionRequired"] = "PreconditionRequired";
HttpStatusCode["TooManyRequests"] = "TooManyRequests";
HttpStatusCode["RequestHeaderFieldsTooLarge"] = "RequestHeaderFieldsTooLarge";
HttpStatusCode["UnavailableForLegalReasons"] = "UnavailableForLegalReasons";
HttpStatusCode["InternalServerError"] = "InternalServerError";
HttpStatusCode["NotImplemented"] = "NotImplemented";
HttpStatusCode["BadGateway"] = "BadGateway";
HttpStatusCode["ServiceUnavailable"] = "ServiceUnavailable";
HttpStatusCode["GatewayTimeout"] = "GatewayTimeout";
HttpStatusCode["HttpVersionNotSupported"] = "HttpVersionNotSupported";
HttpStatusCode["VariantAlsoNegotiates"] = "VariantAlsoNegotiates";
HttpStatusCode["InsufficientStorage"] = "InsufficientStorage";
HttpStatusCode["LoopDetected"] = "LoopDetected";
HttpStatusCode["NotExtended"] = "NotExtended";
HttpStatusCode["NetworkAuthenticationRequired"] = "NetworkAuthenticationRequired";
})(HttpStatusCode = exports.HttpStatusCode || (exports.HttpStatusCode = {}));
var ApiException = /** @class */ (function (_super) {
__extends(ApiException, _super);
function ApiException(message, status, response, headers, result) {
var _this = _super.call(this) || this;
_this.isApiException = true;
_this.message = message;
_this.status = status;
_this.response = response;
_this.headers = headers;
_this.result = result;
return _this;
}
ApiException.isApiException = function (obj) {
return obj.isApiException === true;
};
return ApiException;
}(Error));
exports.ApiException = ApiException;
function throwException(message, status, response, headers, result) {
if (result !== null && result !== undefined)
throw result;
else
throw new ApiException(message, status, response, headers, null);
}
//# sourceMappingURL=SystemInfoService.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,389 @@
/* tslint:disable */
/* eslint-disable */
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming
export default class ServiceInfoService {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
this.http = http ? http : <any>window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "";
}
getCount(): Promise<number> {
let url_ = this.baseUrl + "/api/Services/count";
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processGetCount(_response);
});
}
protected processGetCount(response: Response): Promise<number> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : <any>null;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<number>(<any>null);
}
get(): Promise<ServiceInfo[]> {
let url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processGet(_response);
});
}
protected processGet(response: Response): Promise<ServiceInfo[]> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
if (Array.isArray(resultData200)) {
result200 = [] as any;
for (let item of resultData200)
result200!.push(ServiceInfo.fromJS(item));
}
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<ServiceInfo[]>(<any>null);
}
post(request: ServiceRequest): Promise<ServiceRequest> {
let url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(request);
let options_ = <RequestInit>{
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processPost(_response);
});
}
protected processPost(response: Response): Promise<ServiceRequest> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 201) {
return response.text().then((_responseText) => {
let result201: any = null;
let resultData201 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result201 = ServiceRequest.fromJS(resultData201);
return result201;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<ServiceRequest>(<any>null);
}
put(request: ServiceRequest): Promise<void> {
let url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(request);
let options_ = <RequestInit>{
body: content_,
method: "PUT",
headers: {
"Content-Type": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processPut(_response);
});
}
protected processPut(response: Response): Promise<void> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 204) {
return response.text().then((_responseText) => {
return;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<void>(<any>null);
}
delete(id: number): Promise<void> {
let url_ = this.baseUrl + "/api/Services/{id}";
if (id === undefined || id === null)
throw new Error("The parameter 'id' must be defined.");
url_ = url_.replace("{id}", encodeURIComponent("" + id));
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "DELETE",
headers: {
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processDelete(_response);
});
}
protected processDelete(response: Response): Promise<void> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 204) {
return response.text().then((_responseText) => {
return;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<void>(<any>null);
}
}
export class ServiceInfo implements IServiceInfo {
service?: ServiceRequest | undefined;
statusCode!: HttpStatusCode;
response?: string | undefined;
constructor(data?: IServiceInfo) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(_data?: any) {
if (_data) {
this.service = _data["service"] ? ServiceRequest.fromJS(_data["service"]) : <any>undefined;
this.statusCode = _data["statusCode"];
this.response = _data["response"];
}
}
static fromJS(data: any): ServiceInfo {
data = typeof data === 'object' ? data : {};
let result = new ServiceInfo();
result.init(data);
return result;
}
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["service"] = this.service ? this.service.toJSON() : <any>undefined;
data["statusCode"] = this.statusCode;
data["response"] = this.response;
return data;
}
}
export interface IServiceInfo {
service?: ServiceRequest | undefined;
statusCode: HttpStatusCode;
response?: string | undefined;
}
export class ServiceRequest implements IServiceRequest {
id!: number;
name?: string | undefined;
uri?: string | undefined;
constructor(data?: IServiceRequest) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(_data?: any) {
if (_data) {
this.id = _data["id"];
this.name = _data["name"];
this.uri = _data["uri"];
}
}
static fromJS(data: any): ServiceRequest {
data = typeof data === 'object' ? data : {};
let result = new ServiceRequest();
result.init(data);
return result;
}
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["id"] = this.id;
data["name"] = this.name;
data["uri"] = this.uri;
return data;
}
}
export interface IServiceRequest {
id: number;
name?: string | undefined;
uri?: string | undefined;
}
export enum HttpStatusCode {
Continue = "Continue",
SwitchingProtocols = "SwitchingProtocols",
Processing = "Processing",
EarlyHints = "EarlyHints",
OK = "OK",
Created = "Created",
Accepted = "Accepted",
NonAuthoritativeInformation = "NonAuthoritativeInformation",
NoContent = "NoContent",
ResetContent = "ResetContent",
PartialContent = "PartialContent",
MultiStatus = "MultiStatus",
AlreadyReported = "AlreadyReported",
IMUsed = "IMUsed",
MultipleChoices = "Ambiguous",
Ambiguous = "Ambiguous",
MovedPermanently = "Moved",
Moved = "Moved",
Found = "Redirect",
Redirect = "Redirect",
SeeOther = "RedirectMethod",
RedirectMethod = "RedirectMethod",
NotModified = "NotModified",
UseProxy = "UseProxy",
Unused = "Unused",
TemporaryRedirect = "TemporaryRedirect",
RedirectKeepVerb = "TemporaryRedirect",
PermanentRedirect = "PermanentRedirect",
BadRequest = "BadRequest",
Unauthorized = "Unauthorized",
PaymentRequired = "PaymentRequired",
Forbidden = "Forbidden",
NotFound = "NotFound",
MethodNotAllowed = "MethodNotAllowed",
NotAcceptable = "NotAcceptable",
ProxyAuthenticationRequired = "ProxyAuthenticationRequired",
RequestTimeout = "RequestTimeout",
Conflict = "Conflict",
Gone = "Gone",
LengthRequired = "LengthRequired",
PreconditionFailed = "PreconditionFailed",
RequestEntityTooLarge = "RequestEntityTooLarge",
RequestUriTooLong = "RequestUriTooLong",
UnsupportedMediaType = "UnsupportedMediaType",
RequestedRangeNotSatisfiable = "RequestedRangeNotSatisfiable",
ExpectationFailed = "ExpectationFailed",
MisdirectedRequest = "MisdirectedRequest",
UnprocessableEntity = "UnprocessableEntity",
Locked = "Locked",
FailedDependency = "FailedDependency",
UpgradeRequired = "UpgradeRequired",
PreconditionRequired = "PreconditionRequired",
TooManyRequests = "TooManyRequests",
RequestHeaderFieldsTooLarge = "RequestHeaderFieldsTooLarge",
UnavailableForLegalReasons = "UnavailableForLegalReasons",
InternalServerError = "InternalServerError",
NotImplemented = "NotImplemented",
BadGateway = "BadGateway",
ServiceUnavailable = "ServiceUnavailable",
GatewayTimeout = "GatewayTimeout",
HttpVersionNotSupported = "HttpVersionNotSupported",
VariantAlsoNegotiates = "VariantAlsoNegotiates",
InsufficientStorage = "InsufficientStorage",
LoopDetected = "LoopDetected",
NotExtended = "NotExtended",
NetworkAuthenticationRequired = "NetworkAuthenticationRequired",
}
export class ApiException extends Error {
message: string;
status: number;
response: string;
headers: { [key: string]: any; };
result: any;
constructor(message: string, status: number, response: string, headers: { [key: string]: any; }, result: any) {
super();
this.message = message;
this.status = status;
this.response = response;
this.headers = headers;
this.result = result;
}
protected isApiException = true;
static isApiException(obj: any): obj is ApiException {
return obj.isApiException === true;
}
}
function throwException(message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any {
if (result !== null && result !== undefined)
throw result;
else
throw new ApiException(message, status, response, headers, null);
}

View File

@ -0,0 +1,88 @@
import { Grid, Typography } from '@material-ui/core';
import Accordion from '@material-ui/core/Accordion';
import AccordionDetails from '@material-ui/core/AccordionDetails';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import { blueGrey } from '@material-ui/core/colors';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { Skeleton } from '@material-ui/lab';
import { withStyles } from '@material-ui/styles';
import React, { Component } from 'react';
const styles = theme => ({
acc_summary: {
backgroundColor: blueGrey[50],
padding: theme.spacing(2),
textAlign: 'center',
height: '75px',
},
acc_details: {
backgroundColor: blueGrey[100],
},
grid_typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
grid_typo_2: {
marginLeft: '5px',
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
color: theme.palette.text.secondary,
},
});
class ServiceInfoSkeleton extends Component {
render() {
const { classes } = this.props;
return (
<Accordion>
<AccordionSummary className={classes.acc_summary}
expandIcon={<ExpandMoreIcon />}>
<Grid container
spacing={1}
direction="row"
justify="flex-start"
alignItems="center">
<Grid item>
<Typography className={classes.grid_typo}><Skeleton width={200} /></Typography>
</Grid>
<Grid item>
<Typography className={classes.grid_typo_2}><Skeleton width={300} /></Typography>
</Grid>
<Grid item style={{ marginLeft: 'auto' }}>
<Grid container
spacing={1}
direction="row"
justify="flex-start"
alignItems="center">
<Grid item>
<Typography><Skeleton width={150} /></Typography>
</Grid>
</Grid>
</Grid>
</Grid>
</AccordionSummary>
<AccordionDetails className={classes.acc_details}>
<Grid container
spacing={1}
direction="column"
justify="flex-start"
alignItems="flex-start">
<Grid item>
<Typography><Skeleton width={800} /></Typography>
</Grid>
<Grid item>
<Typography><Skeleton width={300} /></Typography>
</Grid>
<Grid item>
<Typography><Skeleton width={500} /></Typography>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
);
}
}
export default withStyles(styles)(ServiceInfoSkeleton);

View File

@ -0,0 +1,146 @@
import { Grid, IconButton, Paper, Typography } from '@material-ui/core';
import { blueGrey } from '@material-ui/core/colors';
import { AddBox, Refresh } from '@material-ui/icons/';
import { withStyles } from '@material-ui/styles';
import { HubConnectionBuilder } from '@microsoft/signalr';
import React, { Component } from 'react';
import AddNewDialog from './AddNewDialog';
import ServiceInfoService, { ServiceRequest } from './ServiceInfoService';
import ServiceInfoComponent from './ServiceInfoComponent';
import ServiceInfoSkeleton from './ServiceInfoSkeleton';
const styles = theme => ({
typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
paper: {
backgroundColor: blueGrey[50],
height: '60px',
}
});
const hub_url = "/hubs/services";
const notify_method_name = "NotifyUpdatedAsync";
class Services extends Component {
constructor(props) {
super(props);
this.state = {
hubConnection: null,
isDialogOpen: false,
isLoading: false,
service: new ServiceInfoService(),
services: [],
serviceCount: [1, 2, 3],
}
this.handleDevicesUpdated = this.handleDevicesUpdated.bind(this);
this.addDevice = this.addDevice.bind(this);
}
handleDevicesUpdated() {
this.setState({ isLoading: true });
this.state.service.getCount().then(result => {
const updatedCount = [];
for (var i = 0; i < result; i++) {
updatedCount.push(i);
}
this.setState({ serviceCount: updatedCount });
}).catch(ex => {
console.log(ex);
});
this.state.service.get().then(result => {
const updatedServices = [];
for (var s of result) {
updatedServices.push(s);
}
this.setState({ services: updatedServices });
}).catch(ex => {
console.log(ex);
}).finally(() => this.setState({ isLoading: false }));
}
componentDidMount() {
this.handleDevicesUpdated();
const newConnection = new HubConnectionBuilder()
.withUrl(hub_url)
.withAutomaticReconnect()
.build();
this.setState({ hubConnection: newConnection });
newConnection.start()
.then(_ => {
console.log('Services hub Connected!');
newConnection.on(notify_method_name, () => this.handleDevicesUpdated());
}).catch(e => console.log('Services hub Connection failed: ', e));
}
componentWillUnmount() {
if (this.state.hubConnection != null) {
this.state.hubConnection.off(notify_method_name);
console.log('Services hub Disconnected!');
}
}
addDevice(name, url) {
this.setState({ isDialogOpen: false });
let request = new ServiceRequest();
request.id = 0;
request.name = name;
request.uri = url;
this.state.service.post(request).catch(ex => {
console.log(ex);
});
}
render() {
const { classes } = this.props;
const ServiceComponents = this.state.services.map((info, index) => (
<ServiceInfoComponent key={index} isAdmin={this.props.isAdmin} info={info} service={this.state.service} />
));
const Skeletons = this.state.serviceCount.map((i, index) => (
<ServiceInfoSkeleton key={index} />
));
return (
<React.Fragment>
<Paper className={classes.paper} square>
<Grid container
spacing={0}
direction="row"
justify="center"
alignItems="center">
<Grid item>
<Typography className={classes.typo}>Services</Typography>
</Grid>
<Grid item>
{this.props.isAdmin ?
<IconButton color="primary" onClick={() => this.setState({ isDialogOpen: true })}>
<AddBox fontSize="large" />
</IconButton>
: null
}
</Grid>
<Grid item>
<IconButton color="primary" onClick={this.handleDevicesUpdated}>
<Refresh fontSize="large" />
</IconButton>
</Grid>
<AddNewDialog open={this.state.isDialogOpen} handleClose={() => this.setState({ isDialogOpen: false })} handleAdd={this.addDevice} />
</Grid>
</Paper>
{this.state.isLoading ? Skeletons : ServiceComponents}
</React.Fragment>
);
}
}
export default withStyles(styles)(Services);

View File

@ -0,0 +1,192 @@
import { Box, FormControlLabel, Grid, IconButton, Typography } from '@material-ui/core';
import Accordion from '@material-ui/core/Accordion';
import AccordionDetails from '@material-ui/core/AccordionDetails';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import { blueGrey, green, orange, red } from '@material-ui/core/colors';
import { Power, PowerOff, Refresh } from '@material-ui/icons/';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { withStyles } from '@material-ui/styles';
import React, { Component } from 'react';
import { withRouter } from "react-router";
import DeviceService from '../../common/DeviceService';
import DevicesContext from '../../contexts/DevicesContext';
const styles = theme => ({
acc_summary: {
backgroundColor: blueGrey[50],
},
acc_details: {
backgroundColor: blueGrey[100],
},
grid_typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
grid_typo_2: {
marginLeft: '5px',
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
color: theme.palette.text.secondary,
},
grid_item: {
width: '100%',
marginLeft: '5px',
marginRight: '30px',
},
grid_item_typo: {
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
},
grid_item_typo_2: {
marginLeft: '5px',
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
color: theme.palette.text.secondary,
},
icon_box: {
marginRight: '15px',
}
});
class DeviceComponent extends Component {
constructor(props) {
super(props);
this.state = {
expanded: "",
}
}
static contextType = DevicesContext;
getColor(status) {
if (status == "Online") {
return { color: green[600] };
} else if (status == "Offline") {
return { color: blueGrey[500] };
} else /* if (device.status == "Unknown" || device.status == "Error") */ {
return { color: red[800] };
}
}
componentDidMount() {
const id = this.props.match.params.id;
this.setState({ expanded: id });
}
renderSensorButtons(device, sensor) {
var service = new DeviceService();
return this.renderButtons(
() => service.onlinesensor(device.id, sensor.id),
() => service.offlinesensor(device.id, sensor.id),
() => this.context.updateDevice(device.id)
);
}
renderDeviceButtons(device) {
var service = new DeviceService();
return this.renderButtons(
() => service.onlinedevice(device.id),
() => service.offlinedevice(device.id),
() => this.context.updateDevice(device.id)
);
}
renderButtons(onPower, onPowerOff, onRefresh) {
const renderOnOff = () => {
return (
<React.Fragment>
<IconButton color="primary" onClick={onPower}>
<Power />
</IconButton>
<IconButton color="primary" onClick={onPowerOff}>
<PowerOff />
</IconButton>
</React.Fragment>
);
}
const { classes } = this.props;
return (
<Box className={classes.icon_box}>
{this.props.isAdmin ? renderOnOff() : null}
<IconButton color="primary" onClick={onRefresh}>
<Refresh />
</IconButton>
</Box>
);
}
render() {
const { classes } = this.props;
const Sensors = this.props.device.sensors.map((sensor, index) => (
<Grid item className={classes.grid_item} key={sensor.id}>
<Grid container
spacing={3}
direction="row"
justify="space-between"
alignItems="center">
<Grid item>
<Typography className={classes.grid_item_typo}>Sensor {index}</Typography>
</Grid>
<Grid item>
<Typography className={classes.grid_item_typo_2}>{sensor.id}</Typography>
</Grid>
<Grid item>
<Typography style={this.getColor(sensor.status)}>Status: <b>{sensor.status}</b></Typography>
</Grid>
<Grid item>
{this.renderSensorButtons(this.props.device, sensor)}
</Grid>
</Grid>
</Grid>
));
const handleChange = (panel) => (event, isExpanded) => {
this.setState({ expanded: isExpanded ? panel : "" });
};
return (
<Accordion expanded={this.state.expanded === this.props.device.id} onChange={handleChange(this.props.device.id)}>
<AccordionSummary className={classes.acc_summary}
expandIcon={<ExpandMoreIcon />}
aria-controls={"device-panel-/" + this.props.device.id}
id={"device-panel-/" + this.props.device.id}>
<Grid container
spacing={3}
direction="row"
justify="space-between"
alignItems="center">
<Grid item>
<Typography className={classes.grid_typo}>Device {this.props.index}</Typography>
</Grid>
<Grid item>
<Typography className={classes.grid_typo_2}>{this.props.device.id}</Typography>
</Grid>
<Grid item>
<Typography style={this.getColor(this.props.device.status)}>Status: <b>{this.props.device.status}</b></Typography>
</Grid>
<Grid item>
<FormControlLabel
onClick={(event) => event.stopPropagation()}
onFocus={(event) => event.stopPropagation()}
control={this.renderDeviceButtons(this.props.device)}/>
</Grid>
</Grid>
</AccordionSummary>
<AccordionDetails className={classes.acc_details}>
<Grid className={classes.grid_item}
container
spacing={3}
direction="column"
justify="center"
alignItems="flex-start">
{Sensors}
</Grid>
</AccordionDetails>
</Accordion>
);
}
}
export default withRouter(withStyles(styles)(DeviceComponent));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,89 @@
import { Box, Grid, IconButton, Paper, Typography } from '@material-ui/core';
import { blueGrey } from '@material-ui/core/colors';
import { Power, PowerOff, Refresh } from '@material-ui/icons/';
import { withStyles } from '@material-ui/styles';
import React from 'react';
import DeviceService from '../../common/DeviceService';
import DevicesContext from '../../contexts/DevicesContext';
import DeviceComponent from './DeviceComponent';
const styles = theme => ({
root: {
padding: '64px',
backgroundColor: theme.palette.primary.dark,
},
paper: {
backgroundColor: blueGrey[50],
height: '60px',
},
typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
});
class Devices extends React.Component {
constructor(props) {
super(props);
}
static contextType = DevicesContext;
componentDidMount() {
}
renderButtons() {
var service = new DeviceService();
const renderOnOff = () => {
return (
<React.Fragment>
<IconButton color="primary" onClick={() => service.onlineall()}>
<Power />
</IconButton>
<IconButton color="primary" onClick={() => service.offlineall()}>
<PowerOff />
</IconButton>
</React.Fragment>
);
}
return (
<Box>
{this.props.isAdmin ? renderOnOff() : null}
<IconButton color="primary" onClick={() => this.context.updateAllDevices()}>
<Refresh />
</IconButton>
</Box>
);
}
render() {
const { classes } = this.props;
const Devices = this.context.devices.map((device, index) => (
<DeviceComponent isAdmin={this.props.isAdmin} device={device} index={index} key={device.id}/>
));
return (
<Box className={classes.root}>
<Paper className={classes.paper} square>
<Grid container
spacing={0}
direction="row"
justify="center"
alignItems="center">
<Grid item>
<Typography className={classes.typo}>All Devices</Typography>
</Grid>
<Grid item>
{this.renderButtons()}
</Grid>
</Grid>
</Paper>
{Devices}
</Box>
);
}
}
export default withStyles(styles)(Devices);

View File

@ -0,0 +1,61 @@
import { Box, Tooltip } from '@material-ui/core';
import { blue, red, yellow } from '@material-ui/core/colors';
import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked';
import { makeStyles } from '@material-ui/styles';
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
class DeviceMarker extends Component {
constructor(props) {
super(props);
this.state = {
AnchorEl: null
}
}
getColor() {
const { device } = this.props;
if (device.status === "Online") {
return { color: blue[800] };
} else if (device.status === "Offline") {
return { color: yellow[800] };
} else /* if (device.status == "unknown") */ {
return { color: red[800] };
}
}
useStyles() {
return makeStyles(theme => ({
root: {
display: 'grid'
},
icon: {
}
}));
}
render() {
const classes = this.useStyles(this.props);
const { device } = this.props;
const onClick = () => {
this.props.history.push('/devices/' + device.id)
};
const open = Boolean(this.state.AnchorEl);
return (
<Box className={classes.root} boxShadow={5}>
<Tooltip title={<div>ID: {device.id}<br />Status: {device.status}</div>} placement="top" leaveDelay={200} arrow>
<RadioButtonCheckedIcon fontSize="small" style={this.getColor()}
onClick={onClick}>
</RadioButtonCheckedIcon>
</Tooltip>
</Box>
);
}
}
export default withRouter(DeviceMarker);

View File

@ -0,0 +1,124 @@
/*global google*/
import { Box, withStyles } from '@material-ui/core';
import GoogleMapReact from 'google-map-react';
import React, { Component } from 'react';
import C from '../../common/Constants';
import DevicesContext from '../../contexts/DevicesContext';
import DeviceMarker from './DeviceMarker';
const lat_offset = 0.000038;
const lng_offset = -0.000058;
const styles = theme => ({
root: {
height: '93vh',
width: '100%',
}
});
class MapContainer extends Component {
constructor(props) {
super(props);
this.state = {
center: {
lat: 48.275939, lng: 21.469640
},
heatmapPoints: [],
};
this.probabilityHandler = this.probabilityHandler.bind(this);
this.handlePoint = this.handlePoint.bind(this);
}
static contextType = DevicesContext;
probabilityHandler(points) {
for (var point of points) {
this.handlePoint(point);
}
}
handlePoint(point) {
if (point.prob > 0.5) {
this.setState({
heatmapPoints: [...this.state.heatmapPoints, point]
});
if (this._googleMap !== undefined) {
const newPoint = { location: new google.maps.LatLng(point.lat, point.lng), weight: point.prob };
if (this._googleMap.heatmap !== null) {
this._googleMap.heatmap.data.push(newPoint)
}
}
}
}
componentDidMount() {
this.context.addHandler(C.probability_method_name, this.probabilityHandler);
const newPoints = [];
for (var p of this.context.heatmapPoints) {
if (p.prob > 0.5) {
newPoints.push(p)
}
}
this.setState({ heatmapPoints: newPoints });
}
componentWillUnmount() {
this.context.removeHandler(C.probability_method_name, this.probabilityHandler);
}
render() {
const {classes} = this.props;
const heatMapData = {
positions: this.state.heatmapPoints,
options: {
radius: 50,
opacity: 0.6,
}
}
const mapOptions = {
disableDefaultUI: true,
zoomControl: true,
mapTypeControl: true,
overviewMapControl: true,
streetViewControl: false,
scaleControl: true,
mapTypeId: 'satellite'
}
const Markers = this.context.devices.map((device, index) => (
<DeviceMarker
key={device.id}
lat={device.coordinates.latitude + lat_offset}
lng={device.coordinates.longitude + lng_offset}
device={device}
/>
));
return (
<Box className={classes.root}>
<GoogleMapReact
bootstrapURLKeys={{
key: ["AIzaSyCZ51VFfxqZ2GkCmVrcNZdUKsM0fuBQUCY"],
libraries: ['visualization']
}}
ref={(el) => this._googleMap = el}
options={mapOptions}
defaultZoom={18}
heatmapLibrary={true}
heatmap={heatMapData}
defaultCenter={this.state.center}>
{Markers}
</GoogleMapReact>
</Box>
);
}
}
export default withStyles(styles)(MapContainer);

View File

@ -1,555 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initMap = void 0;
var google_maps_1 = require("google-maps");
var map, heatmap;
function initMap() {
var options = { /* todo */};
var loader = new google_maps_1.Loader('AIzaSyCZ51VFfxqZ2GkCmVrcNZdUKsM0fuBQUCY', options);
loader.load().then(function (google) {
map = new google.maps.Map(document.getElementById("map"), {
zoom: 13,
center: { lat: 37.775, lng: -122.434 },
mapTypeId: "satellite",
});
heatmap = new google.maps.visualization.HeatmapLayer({
data: getPoints(),
map: map,
});
});
}
exports.initMap = initMap;
function toggleHeatmap() {
heatmap.setMap(heatmap.getMap() ? null : map);
}
function changeGradient() {
var gradient = [
"rgba(0, 255, 255, 0)",
"rgba(0, 255, 255, 1)",
"rgba(0, 191, 255, 1)",
"rgba(0, 127, 255, 1)",
"rgba(0, 63, 255, 1)",
"rgba(0, 0, 255, 1)",
"rgba(0, 0, 223, 1)",
"rgba(0, 0, 191, 1)",
"rgba(0, 0, 159, 1)",
"rgba(0, 0, 127, 1)",
"rgba(63, 0, 91, 1)",
"rgba(127, 0, 63, 1)",
"rgba(191, 0, 31, 1)",
"rgba(255, 0, 0, 1)",
];
heatmap.set("gradient", heatmap.get("gradient") ? null : gradient);
}
function changeRadius() {
heatmap.set("radius", heatmap.get("radius") ? null : 20);
}
function changeOpacity() {
heatmap.set("opacity", heatmap.get("opacity") ? null : 0.2);
}
// Heatmap data: 500 Points
function getPoints() {
return [
new google.maps.LatLng(37.782551, -122.445368),
new google.maps.LatLng(37.782745, -122.444586),
new google.maps.LatLng(37.782842, -122.443688),
new google.maps.LatLng(37.782919, -122.442815),
new google.maps.LatLng(37.782992, -122.442112),
new google.maps.LatLng(37.7831, -122.441461),
new google.maps.LatLng(37.783206, -122.440829),
new google.maps.LatLng(37.783273, -122.440324),
new google.maps.LatLng(37.783316, -122.440023),
new google.maps.LatLng(37.783357, -122.439794),
new google.maps.LatLng(37.783371, -122.439687),
new google.maps.LatLng(37.783368, -122.439666),
new google.maps.LatLng(37.783383, -122.439594),
new google.maps.LatLng(37.783508, -122.439525),
new google.maps.LatLng(37.783842, -122.439591),
new google.maps.LatLng(37.784147, -122.439668),
new google.maps.LatLng(37.784206, -122.439686),
new google.maps.LatLng(37.784386, -122.43979),
new google.maps.LatLng(37.784701, -122.439902),
new google.maps.LatLng(37.784965, -122.439938),
new google.maps.LatLng(37.78501, -122.439947),
new google.maps.LatLng(37.78536, -122.439952),
new google.maps.LatLng(37.785715, -122.44003),
new google.maps.LatLng(37.786117, -122.440119),
new google.maps.LatLng(37.786564, -122.440209),
new google.maps.LatLng(37.786905, -122.44027),
new google.maps.LatLng(37.786956, -122.440279),
new google.maps.LatLng(37.800224, -122.43352),
new google.maps.LatLng(37.800155, -122.434101),
new google.maps.LatLng(37.80016, -122.43443),
new google.maps.LatLng(37.800378, -122.434527),
new google.maps.LatLng(37.800738, -122.434598),
new google.maps.LatLng(37.800938, -122.43465),
new google.maps.LatLng(37.801024, -122.434889),
new google.maps.LatLng(37.800955, -122.435392),
new google.maps.LatLng(37.800886, -122.435959),
new google.maps.LatLng(37.800811, -122.436275),
new google.maps.LatLng(37.800788, -122.436299),
new google.maps.LatLng(37.800719, -122.436302),
new google.maps.LatLng(37.800702, -122.436298),
new google.maps.LatLng(37.800661, -122.436273),
new google.maps.LatLng(37.800395, -122.436172),
new google.maps.LatLng(37.800228, -122.436116),
new google.maps.LatLng(37.800169, -122.43613),
new google.maps.LatLng(37.800066, -122.436167),
new google.maps.LatLng(37.784345, -122.422922),
new google.maps.LatLng(37.784389, -122.422926),
new google.maps.LatLng(37.784437, -122.422924),
new google.maps.LatLng(37.784746, -122.422818),
new google.maps.LatLng(37.785436, -122.422959),
new google.maps.LatLng(37.78612, -122.423112),
new google.maps.LatLng(37.786433, -122.423029),
new google.maps.LatLng(37.786631, -122.421213),
new google.maps.LatLng(37.78666, -122.421033),
new google.maps.LatLng(37.786801, -122.420141),
new google.maps.LatLng(37.786823, -122.420034),
new google.maps.LatLng(37.786831, -122.419916),
new google.maps.LatLng(37.787034, -122.418208),
new google.maps.LatLng(37.787056, -122.418034),
new google.maps.LatLng(37.787169, -122.417145),
new google.maps.LatLng(37.787217, -122.416715),
new google.maps.LatLng(37.786144, -122.416403),
new google.maps.LatLng(37.785292, -122.416257),
new google.maps.LatLng(37.780666, -122.390374),
new google.maps.LatLng(37.780501, -122.391281),
new google.maps.LatLng(37.780148, -122.392052),
new google.maps.LatLng(37.780173, -122.391148),
new google.maps.LatLng(37.780693, -122.390592),
new google.maps.LatLng(37.781261, -122.391142),
new google.maps.LatLng(37.781808, -122.39173),
new google.maps.LatLng(37.78234, -122.392341),
new google.maps.LatLng(37.782812, -122.393022),
new google.maps.LatLng(37.7833, -122.393672),
new google.maps.LatLng(37.783809, -122.394275),
new google.maps.LatLng(37.784246, -122.394979),
new google.maps.LatLng(37.784791, -122.395958),
new google.maps.LatLng(37.785675, -122.396746),
new google.maps.LatLng(37.786262, -122.39578),
new google.maps.LatLng(37.786776, -122.395093),
new google.maps.LatLng(37.787282, -122.394426),
new google.maps.LatLng(37.787783, -122.393767),
new google.maps.LatLng(37.788343, -122.393184),
new google.maps.LatLng(37.788895, -122.392506),
new google.maps.LatLng(37.789371, -122.391701),
new google.maps.LatLng(37.789722, -122.390952),
new google.maps.LatLng(37.790315, -122.390305),
new google.maps.LatLng(37.790738, -122.389616),
new google.maps.LatLng(37.779448, -122.438702),
new google.maps.LatLng(37.779023, -122.438585),
new google.maps.LatLng(37.778542, -122.438492),
new google.maps.LatLng(37.7781, -122.438411),
new google.maps.LatLng(37.777986, -122.438376),
new google.maps.LatLng(37.77768, -122.438313),
new google.maps.LatLng(37.777316, -122.438273),
new google.maps.LatLng(37.777135, -122.438254),
new google.maps.LatLng(37.776987, -122.438303),
new google.maps.LatLng(37.776946, -122.438404),
new google.maps.LatLng(37.776944, -122.438467),
new google.maps.LatLng(37.776892, -122.438459),
new google.maps.LatLng(37.776842, -122.438442),
new google.maps.LatLng(37.776822, -122.438391),
new google.maps.LatLng(37.776814, -122.438412),
new google.maps.LatLng(37.776787, -122.438628),
new google.maps.LatLng(37.776729, -122.43865),
new google.maps.LatLng(37.776759, -122.438677),
new google.maps.LatLng(37.776772, -122.438498),
new google.maps.LatLng(37.776787, -122.438389),
new google.maps.LatLng(37.776848, -122.438283),
new google.maps.LatLng(37.77687, -122.438239),
new google.maps.LatLng(37.777015, -122.438198),
new google.maps.LatLng(37.777333, -122.438256),
new google.maps.LatLng(37.777595, -122.438308),
new google.maps.LatLng(37.777797, -122.438344),
new google.maps.LatLng(37.77816, -122.438442),
new google.maps.LatLng(37.778414, -122.438508),
new google.maps.LatLng(37.778445, -122.438516),
new google.maps.LatLng(37.778503, -122.438529),
new google.maps.LatLng(37.778607, -122.438549),
new google.maps.LatLng(37.77867, -122.438644),
new google.maps.LatLng(37.778847, -122.438706),
new google.maps.LatLng(37.77924, -122.438744),
new google.maps.LatLng(37.779738, -122.438822),
new google.maps.LatLng(37.780201, -122.438882),
new google.maps.LatLng(37.7804, -122.438905),
new google.maps.LatLng(37.780501, -122.438921),
new google.maps.LatLng(37.780892, -122.438986),
new google.maps.LatLng(37.781446, -122.439087),
new google.maps.LatLng(37.781985, -122.439199),
new google.maps.LatLng(37.782239, -122.439249),
new google.maps.LatLng(37.782286, -122.439266),
new google.maps.LatLng(37.797847, -122.429388),
new google.maps.LatLng(37.797874, -122.42918),
new google.maps.LatLng(37.797885, -122.429069),
new google.maps.LatLng(37.797887, -122.42905),
new google.maps.LatLng(37.797933, -122.428954),
new google.maps.LatLng(37.798242, -122.42899),
new google.maps.LatLng(37.798617, -122.429075),
new google.maps.LatLng(37.798719, -122.429092),
new google.maps.LatLng(37.798944, -122.429145),
new google.maps.LatLng(37.79932, -122.429251),
new google.maps.LatLng(37.79959, -122.429309),
new google.maps.LatLng(37.799677, -122.429324),
new google.maps.LatLng(37.799966, -122.42936),
new google.maps.LatLng(37.800288, -122.42943),
new google.maps.LatLng(37.800443, -122.429461),
new google.maps.LatLng(37.800465, -122.429474),
new google.maps.LatLng(37.800644, -122.42954),
new google.maps.LatLng(37.800948, -122.42962),
new google.maps.LatLng(37.801242, -122.429685),
new google.maps.LatLng(37.801375, -122.429702),
new google.maps.LatLng(37.8014, -122.429703),
new google.maps.LatLng(37.801453, -122.429707),
new google.maps.LatLng(37.801473, -122.429709),
new google.maps.LatLng(37.801532, -122.429707),
new google.maps.LatLng(37.801852, -122.429729),
new google.maps.LatLng(37.802173, -122.429789),
new google.maps.LatLng(37.802459, -122.429847),
new google.maps.LatLng(37.802554, -122.429825),
new google.maps.LatLng(37.802647, -122.429549),
new google.maps.LatLng(37.802693, -122.429179),
new google.maps.LatLng(37.802729, -122.428751),
new google.maps.LatLng(37.766104, -122.409291),
new google.maps.LatLng(37.766103, -122.409268),
new google.maps.LatLng(37.766138, -122.409229),
new google.maps.LatLng(37.766183, -122.409231),
new google.maps.LatLng(37.766153, -122.409276),
new google.maps.LatLng(37.766005, -122.409365),
new google.maps.LatLng(37.765897, -122.40957),
new google.maps.LatLng(37.765767, -122.409739),
new google.maps.LatLng(37.765693, -122.410389),
new google.maps.LatLng(37.765615, -122.411201),
new google.maps.LatLng(37.765533, -122.412121),
new google.maps.LatLng(37.765467, -122.412939),
new google.maps.LatLng(37.765444, -122.414821),
new google.maps.LatLng(37.765444, -122.414964),
new google.maps.LatLng(37.765318, -122.415424),
new google.maps.LatLng(37.763961, -122.415296),
new google.maps.LatLng(37.763115, -122.415196),
new google.maps.LatLng(37.762967, -122.415183),
new google.maps.LatLng(37.762278, -122.415127),
new google.maps.LatLng(37.761675, -122.415055),
new google.maps.LatLng(37.760932, -122.414988),
new google.maps.LatLng(37.759337, -122.414862),
new google.maps.LatLng(37.773187, -122.421922),
new google.maps.LatLng(37.773043, -122.422118),
new google.maps.LatLng(37.773007, -122.422165),
new google.maps.LatLng(37.772979, -122.422219),
new google.maps.LatLng(37.772865, -122.422394),
new google.maps.LatLng(37.772779, -122.422503),
new google.maps.LatLng(37.772676, -122.422701),
new google.maps.LatLng(37.772606, -122.422806),
new google.maps.LatLng(37.772566, -122.42284),
new google.maps.LatLng(37.772508, -122.422852),
new google.maps.LatLng(37.772387, -122.423011),
new google.maps.LatLng(37.772099, -122.423328),
new google.maps.LatLng(37.771704, -122.423783),
new google.maps.LatLng(37.771481, -122.424081),
new google.maps.LatLng(37.7714, -122.424179),
new google.maps.LatLng(37.771352, -122.42422),
new google.maps.LatLng(37.771248, -122.424327),
new google.maps.LatLng(37.770904, -122.424781),
new google.maps.LatLng(37.77052, -122.425283),
new google.maps.LatLng(37.770337, -122.425553),
new google.maps.LatLng(37.770128, -122.425832),
new google.maps.LatLng(37.769756, -122.426331),
new google.maps.LatLng(37.7693, -122.426902),
new google.maps.LatLng(37.769132, -122.427065),
new google.maps.LatLng(37.769092, -122.427103),
new google.maps.LatLng(37.768979, -122.427172),
new google.maps.LatLng(37.768595, -122.427634),
new google.maps.LatLng(37.768372, -122.427913),
new google.maps.LatLng(37.768337, -122.427961),
new google.maps.LatLng(37.768244, -122.428138),
new google.maps.LatLng(37.767942, -122.428581),
new google.maps.LatLng(37.767482, -122.429094),
new google.maps.LatLng(37.767031, -122.429606),
new google.maps.LatLng(37.766732, -122.429986),
new google.maps.LatLng(37.76668, -122.430058),
new google.maps.LatLng(37.766633, -122.430109),
new google.maps.LatLng(37.76658, -122.430211),
new google.maps.LatLng(37.766367, -122.430594),
new google.maps.LatLng(37.76591, -122.431137),
new google.maps.LatLng(37.765353, -122.431806),
new google.maps.LatLng(37.764962, -122.432298),
new google.maps.LatLng(37.764868, -122.432486),
new google.maps.LatLng(37.764518, -122.432913),
new google.maps.LatLng(37.763435, -122.434173),
new google.maps.LatLng(37.762847, -122.434953),
new google.maps.LatLng(37.762291, -122.435935),
new google.maps.LatLng(37.762224, -122.436074),
new google.maps.LatLng(37.761957, -122.436892),
new google.maps.LatLng(37.761652, -122.438886),
new google.maps.LatLng(37.761284, -122.439955),
new google.maps.LatLng(37.76121, -122.440068),
new google.maps.LatLng(37.761064, -122.44072),
new google.maps.LatLng(37.76104, -122.441411),
new google.maps.LatLng(37.761048, -122.442324),
new google.maps.LatLng(37.760851, -122.443118),
new google.maps.LatLng(37.759977, -122.444591),
new google.maps.LatLng(37.759913, -122.444698),
new google.maps.LatLng(37.759623, -122.445065),
new google.maps.LatLng(37.758902, -122.445158),
new google.maps.LatLng(37.758428, -122.44457),
new google.maps.LatLng(37.757687, -122.44334),
new google.maps.LatLng(37.757583, -122.44324),
new google.maps.LatLng(37.757019, -122.442787),
new google.maps.LatLng(37.756603, -122.442322),
new google.maps.LatLng(37.75638, -122.441602),
new google.maps.LatLng(37.75579, -122.441382),
new google.maps.LatLng(37.754493, -122.442133),
new google.maps.LatLng(37.754361, -122.442206),
new google.maps.LatLng(37.753719, -122.44265),
new google.maps.LatLng(37.753096, -122.442915),
new google.maps.LatLng(37.751617, -122.443211),
new google.maps.LatLng(37.751496, -122.443246),
new google.maps.LatLng(37.750733, -122.443428),
new google.maps.LatLng(37.750126, -122.443536),
new google.maps.LatLng(37.750103, -122.443784),
new google.maps.LatLng(37.75039, -122.44401),
new google.maps.LatLng(37.750448, -122.444013),
new google.maps.LatLng(37.750536, -122.44404),
new google.maps.LatLng(37.750493, -122.444141),
new google.maps.LatLng(37.790859, -122.402808),
new google.maps.LatLng(37.790864, -122.402768),
new google.maps.LatLng(37.790995, -122.402539),
new google.maps.LatLng(37.791148, -122.402172),
new google.maps.LatLng(37.791385, -122.401312),
new google.maps.LatLng(37.791405, -122.400776),
new google.maps.LatLng(37.791288, -122.400528),
new google.maps.LatLng(37.791113, -122.400441),
new google.maps.LatLng(37.791027, -122.400395),
new google.maps.LatLng(37.791094, -122.400311),
new google.maps.LatLng(37.791211, -122.400183),
new google.maps.LatLng(37.79106, -122.399334),
new google.maps.LatLng(37.790538, -122.398718),
new google.maps.LatLng(37.790095, -122.398086),
new google.maps.LatLng(37.789644, -122.39736),
new google.maps.LatLng(37.789254, -122.396844),
new google.maps.LatLng(37.788855, -122.396397),
new google.maps.LatLng(37.788483, -122.395963),
new google.maps.LatLng(37.788015, -122.395365),
new google.maps.LatLng(37.787558, -122.394735),
new google.maps.LatLng(37.787472, -122.394323),
new google.maps.LatLng(37.78763, -122.394025),
new google.maps.LatLng(37.787767, -122.393987),
new google.maps.LatLng(37.787486, -122.394452),
new google.maps.LatLng(37.786977, -122.395043),
new google.maps.LatLng(37.786583, -122.395552),
new google.maps.LatLng(37.78654, -122.39561),
new google.maps.LatLng(37.786516, -122.395659),
new google.maps.LatLng(37.786378, -122.395707),
new google.maps.LatLng(37.786044, -122.395362),
new google.maps.LatLng(37.785598, -122.394715),
new google.maps.LatLng(37.785321, -122.394361),
new google.maps.LatLng(37.785207, -122.394236),
new google.maps.LatLng(37.785751, -122.394062),
new google.maps.LatLng(37.785996, -122.393881),
new google.maps.LatLng(37.786092, -122.39383),
new google.maps.LatLng(37.785998, -122.393899),
new google.maps.LatLng(37.785114, -122.394365),
new google.maps.LatLng(37.785022, -122.394441),
new google.maps.LatLng(37.784823, -122.394635),
new google.maps.LatLng(37.784719, -122.394629),
new google.maps.LatLng(37.785069, -122.394176),
new google.maps.LatLng(37.7855, -122.39365),
new google.maps.LatLng(37.78577, -122.393291),
new google.maps.LatLng(37.785839, -122.393159),
new google.maps.LatLng(37.782651, -122.400628),
new google.maps.LatLng(37.782616, -122.400599),
new google.maps.LatLng(37.782702, -122.40047),
new google.maps.LatLng(37.782915, -122.400192),
new google.maps.LatLng(37.783137, -122.399887),
new google.maps.LatLng(37.783414, -122.399519),
new google.maps.LatLng(37.783629, -122.399237),
new google.maps.LatLng(37.783688, -122.399157),
new google.maps.LatLng(37.783716, -122.399106),
new google.maps.LatLng(37.783798, -122.399072),
new google.maps.LatLng(37.783997, -122.399186),
new google.maps.LatLng(37.784271, -122.399538),
new google.maps.LatLng(37.784577, -122.399948),
new google.maps.LatLng(37.784828, -122.40026),
new google.maps.LatLng(37.784999, -122.400477),
new google.maps.LatLng(37.785113, -122.400651),
new google.maps.LatLng(37.785155, -122.400703),
new google.maps.LatLng(37.785192, -122.400749),
new google.maps.LatLng(37.785278, -122.400839),
new google.maps.LatLng(37.785387, -122.400857),
new google.maps.LatLng(37.785478, -122.40089),
new google.maps.LatLng(37.785526, -122.401022),
new google.maps.LatLng(37.785598, -122.401148),
new google.maps.LatLng(37.785631, -122.401202),
new google.maps.LatLng(37.78566, -122.401267),
new google.maps.LatLng(37.803986, -122.426035),
new google.maps.LatLng(37.804102, -122.425089),
new google.maps.LatLng(37.804211, -122.424156),
new google.maps.LatLng(37.803861, -122.423385),
new google.maps.LatLng(37.803151, -122.423214),
new google.maps.LatLng(37.802439, -122.423077),
new google.maps.LatLng(37.80174, -122.422905),
new google.maps.LatLng(37.801069, -122.422785),
new google.maps.LatLng(37.800345, -122.422649),
new google.maps.LatLng(37.799633, -122.422603),
new google.maps.LatLng(37.79975, -122.4217),
new google.maps.LatLng(37.799885, -122.420854),
new google.maps.LatLng(37.799209, -122.420607),
new google.maps.LatLng(37.795656, -122.400395),
new google.maps.LatLng(37.795203, -122.400304),
new google.maps.LatLng(37.778738, -122.415584),
new google.maps.LatLng(37.778812, -122.415189),
new google.maps.LatLng(37.778824, -122.415092),
new google.maps.LatLng(37.778833, -122.414932),
new google.maps.LatLng(37.778834, -122.414898),
new google.maps.LatLng(37.77874, -122.414757),
new google.maps.LatLng(37.778501, -122.414433),
new google.maps.LatLng(37.778182, -122.414026),
new google.maps.LatLng(37.777851, -122.413623),
new google.maps.LatLng(37.777486, -122.413166),
new google.maps.LatLng(37.777109, -122.412674),
new google.maps.LatLng(37.776743, -122.412186),
new google.maps.LatLng(37.77644, -122.4118),
new google.maps.LatLng(37.776295, -122.411614),
new google.maps.LatLng(37.776158, -122.41144),
new google.maps.LatLng(37.775806, -122.410997),
new google.maps.LatLng(37.775422, -122.410484),
new google.maps.LatLng(37.775126, -122.410087),
new google.maps.LatLng(37.775012, -122.409854),
new google.maps.LatLng(37.775164, -122.409573),
new google.maps.LatLng(37.775498, -122.40918),
new google.maps.LatLng(37.775868, -122.40873),
new google.maps.LatLng(37.776256, -122.40824),
new google.maps.LatLng(37.776519, -122.407928),
new google.maps.LatLng(37.776539, -122.407904),
new google.maps.LatLng(37.776595, -122.407854),
new google.maps.LatLng(37.776853, -122.407547),
new google.maps.LatLng(37.777234, -122.407087),
new google.maps.LatLng(37.777644, -122.406558),
new google.maps.LatLng(37.778066, -122.406017),
new google.maps.LatLng(37.778468, -122.405499),
new google.maps.LatLng(37.778866, -122.404995),
new google.maps.LatLng(37.779295, -122.404455),
new google.maps.LatLng(37.779695, -122.40395),
new google.maps.LatLng(37.779982, -122.403584),
new google.maps.LatLng(37.780295, -122.403223),
new google.maps.LatLng(37.780664, -122.402766),
new google.maps.LatLng(37.781043, -122.402288),
new google.maps.LatLng(37.781399, -122.401823),
new google.maps.LatLng(37.781727, -122.401407),
new google.maps.LatLng(37.781853, -122.401247),
new google.maps.LatLng(37.781894, -122.401195),
new google.maps.LatLng(37.782076, -122.400977),
new google.maps.LatLng(37.782338, -122.400603),
new google.maps.LatLng(37.782666, -122.400133),
new google.maps.LatLng(37.783048, -122.399634),
new google.maps.LatLng(37.78345, -122.399198),
new google.maps.LatLng(37.783791, -122.398998),
new google.maps.LatLng(37.784177, -122.398959),
new google.maps.LatLng(37.784388, -122.398971),
new google.maps.LatLng(37.784404, -122.399128),
new google.maps.LatLng(37.784586, -122.399524),
new google.maps.LatLng(37.784835, -122.399927),
new google.maps.LatLng(37.785116, -122.400307),
new google.maps.LatLng(37.785282, -122.400539),
new google.maps.LatLng(37.785346, -122.400692),
new google.maps.LatLng(37.765769, -122.407201),
new google.maps.LatLng(37.76579, -122.407414),
new google.maps.LatLng(37.765802, -122.407755),
new google.maps.LatLng(37.765791, -122.408219),
new google.maps.LatLng(37.765763, -122.408759),
new google.maps.LatLng(37.765726, -122.409348),
new google.maps.LatLng(37.765716, -122.409882),
new google.maps.LatLng(37.765708, -122.410202),
new google.maps.LatLng(37.765705, -122.410253),
new google.maps.LatLng(37.765707, -122.410369),
new google.maps.LatLng(37.765692, -122.41072),
new google.maps.LatLng(37.765699, -122.411215),
new google.maps.LatLng(37.765687, -122.411789),
new google.maps.LatLng(37.765666, -122.412373),
new google.maps.LatLng(37.765598, -122.412883),
new google.maps.LatLng(37.765543, -122.413039),
new google.maps.LatLng(37.765532, -122.413125),
new google.maps.LatLng(37.7655, -122.413553),
new google.maps.LatLng(37.765448, -122.414053),
new google.maps.LatLng(37.765388, -122.414645),
new google.maps.LatLng(37.765323, -122.41525),
new google.maps.LatLng(37.765303, -122.415847),
new google.maps.LatLng(37.765251, -122.416439),
new google.maps.LatLng(37.765204, -122.41702),
new google.maps.LatLng(37.765172, -122.417556),
new google.maps.LatLng(37.765164, -122.418075),
new google.maps.LatLng(37.765153, -122.418618),
new google.maps.LatLng(37.765136, -122.419112),
new google.maps.LatLng(37.765129, -122.419378),
new google.maps.LatLng(37.765119, -122.419481),
new google.maps.LatLng(37.7651, -122.419852),
new google.maps.LatLng(37.765083, -122.420349),
new google.maps.LatLng(37.765045, -122.42093),
new google.maps.LatLng(37.764992, -122.421481),
new google.maps.LatLng(37.76498, -122.421695),
new google.maps.LatLng(37.764993, -122.421843),
new google.maps.LatLng(37.764986, -122.422255),
new google.maps.LatLng(37.764975, -122.422823),
new google.maps.LatLng(37.764939, -122.423411),
new google.maps.LatLng(37.764902, -122.424014),
new google.maps.LatLng(37.764853, -122.424576),
new google.maps.LatLng(37.764826, -122.424922),
new google.maps.LatLng(37.764796, -122.425375),
new google.maps.LatLng(37.764782, -122.425869),
new google.maps.LatLng(37.764768, -122.426089),
new google.maps.LatLng(37.764766, -122.426117),
new google.maps.LatLng(37.764723, -122.426276),
new google.maps.LatLng(37.764681, -122.426649),
new google.maps.LatLng(37.782012, -122.4042),
new google.maps.LatLng(37.781574, -122.404911),
new google.maps.LatLng(37.781055, -122.405597),
new google.maps.LatLng(37.780479, -122.406341),
new google.maps.LatLng(37.779996, -122.406939),
new google.maps.LatLng(37.779459, -122.407613),
new google.maps.LatLng(37.778953, -122.408228),
new google.maps.LatLng(37.778409, -122.408839),
new google.maps.LatLng(37.777842, -122.409501),
new google.maps.LatLng(37.777334, -122.410181),
new google.maps.LatLng(37.776809, -122.410836),
new google.maps.LatLng(37.77624, -122.411514),
new google.maps.LatLng(37.775725, -122.412145),
new google.maps.LatLng(37.77519, -122.412805),
new google.maps.LatLng(37.774672, -122.413464),
new google.maps.LatLng(37.774084, -122.414186),
new google.maps.LatLng(37.773533, -122.413636),
new google.maps.LatLng(37.773021, -122.413009),
new google.maps.LatLng(37.772501, -122.412371),
new google.maps.LatLng(37.771964, -122.411681),
new google.maps.LatLng(37.771479, -122.411078),
new google.maps.LatLng(37.770992, -122.410477),
new google.maps.LatLng(37.770467, -122.409801),
new google.maps.LatLng(37.77009, -122.408904),
new google.maps.LatLng(37.769657, -122.408103),
new google.maps.LatLng(37.769132, -122.407276),
new google.maps.LatLng(37.768564, -122.406469),
new google.maps.LatLng(37.76798, -122.405745),
new google.maps.LatLng(37.76738, -122.405299),
new google.maps.LatLng(37.766604, -122.405297),
new google.maps.LatLng(37.765838, -122.4052),
new google.maps.LatLng(37.765139, -122.405139),
new google.maps.LatLng(37.764457, -122.405094),
new google.maps.LatLng(37.763716, -122.405142),
new google.maps.LatLng(37.762932, -122.405398),
new google.maps.LatLng(37.762126, -122.405813),
new google.maps.LatLng(37.761344, -122.406215),
new google.maps.LatLng(37.760556, -122.406495),
new google.maps.LatLng(37.759732, -122.406484),
new google.maps.LatLng(37.75891, -122.406228),
new google.maps.LatLng(37.758182, -122.405695),
new google.maps.LatLng(37.757676, -122.405118),
new google.maps.LatLng(37.757039, -122.404346),
new google.maps.LatLng(37.756335, -122.403719),
new google.maps.LatLng(37.755503, -122.403406),
new google.maps.LatLng(37.754665, -122.403242),
new google.maps.LatLng(37.753837, -122.403172),
new google.maps.LatLng(37.752986, -122.403112),
new google.maps.LatLng(37.751266, -122.403355),
];
}
//# sourceMappingURL=heatmap.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,561 +0,0 @@
import { Loader, LoaderOptions } from 'google-maps';
let map: google.maps.Map, heatmap: google.maps.visualization.HeatmapLayer;
function initMap(): void {
const options: LoaderOptions = {/* todo */ };
const loader = new Loader('AIzaSyCZ51VFfxqZ2GkCmVrcNZdUKsM0fuBQUCY', options);
loader.load().then(function (google) {
map = new google.maps.Map(document.getElementById("map") as HTMLElement, {
zoom: 13,
center: { lat: 37.775, lng: -122.434 },
mapTypeId: "satellite",
});
heatmap = new google.maps.visualization.HeatmapLayer({
data: getPoints(),
map: map,
});
});
}
function toggleHeatmap() {
heatmap.setMap(heatmap.getMap() ? null : map);
}
function changeGradient() {
const gradient = [
"rgba(0, 255, 255, 0)",
"rgba(0, 255, 255, 1)",
"rgba(0, 191, 255, 1)",
"rgba(0, 127, 255, 1)",
"rgba(0, 63, 255, 1)",
"rgba(0, 0, 255, 1)",
"rgba(0, 0, 223, 1)",
"rgba(0, 0, 191, 1)",
"rgba(0, 0, 159, 1)",
"rgba(0, 0, 127, 1)",
"rgba(63, 0, 91, 1)",
"rgba(127, 0, 63, 1)",
"rgba(191, 0, 31, 1)",
"rgba(255, 0, 0, 1)",
];
heatmap.set("gradient", heatmap.get("gradient") ? null : gradient);
}
function changeRadius() {
heatmap.set("radius", heatmap.get("radius") ? null : 20);
}
function changeOpacity() {
heatmap.set("opacity", heatmap.get("opacity") ? null : 0.2);
}
// Heatmap data: 500 Points
function getPoints() {
return [
new google.maps.LatLng(37.782551, -122.445368),
new google.maps.LatLng(37.782745, -122.444586),
new google.maps.LatLng(37.782842, -122.443688),
new google.maps.LatLng(37.782919, -122.442815),
new google.maps.LatLng(37.782992, -122.442112),
new google.maps.LatLng(37.7831, -122.441461),
new google.maps.LatLng(37.783206, -122.440829),
new google.maps.LatLng(37.783273, -122.440324),
new google.maps.LatLng(37.783316, -122.440023),
new google.maps.LatLng(37.783357, -122.439794),
new google.maps.LatLng(37.783371, -122.439687),
new google.maps.LatLng(37.783368, -122.439666),
new google.maps.LatLng(37.783383, -122.439594),
new google.maps.LatLng(37.783508, -122.439525),
new google.maps.LatLng(37.783842, -122.439591),
new google.maps.LatLng(37.784147, -122.439668),
new google.maps.LatLng(37.784206, -122.439686),
new google.maps.LatLng(37.784386, -122.43979),
new google.maps.LatLng(37.784701, -122.439902),
new google.maps.LatLng(37.784965, -122.439938),
new google.maps.LatLng(37.78501, -122.439947),
new google.maps.LatLng(37.78536, -122.439952),
new google.maps.LatLng(37.785715, -122.44003),
new google.maps.LatLng(37.786117, -122.440119),
new google.maps.LatLng(37.786564, -122.440209),
new google.maps.LatLng(37.786905, -122.44027),
new google.maps.LatLng(37.786956, -122.440279),
new google.maps.LatLng(37.800224, -122.43352),
new google.maps.LatLng(37.800155, -122.434101),
new google.maps.LatLng(37.80016, -122.43443),
new google.maps.LatLng(37.800378, -122.434527),
new google.maps.LatLng(37.800738, -122.434598),
new google.maps.LatLng(37.800938, -122.43465),
new google.maps.LatLng(37.801024, -122.434889),
new google.maps.LatLng(37.800955, -122.435392),
new google.maps.LatLng(37.800886, -122.435959),
new google.maps.LatLng(37.800811, -122.436275),
new google.maps.LatLng(37.800788, -122.436299),
new google.maps.LatLng(37.800719, -122.436302),
new google.maps.LatLng(37.800702, -122.436298),
new google.maps.LatLng(37.800661, -122.436273),
new google.maps.LatLng(37.800395, -122.436172),
new google.maps.LatLng(37.800228, -122.436116),
new google.maps.LatLng(37.800169, -122.43613),
new google.maps.LatLng(37.800066, -122.436167),
new google.maps.LatLng(37.784345, -122.422922),
new google.maps.LatLng(37.784389, -122.422926),
new google.maps.LatLng(37.784437, -122.422924),
new google.maps.LatLng(37.784746, -122.422818),
new google.maps.LatLng(37.785436, -122.422959),
new google.maps.LatLng(37.78612, -122.423112),
new google.maps.LatLng(37.786433, -122.423029),
new google.maps.LatLng(37.786631, -122.421213),
new google.maps.LatLng(37.78666, -122.421033),
new google.maps.LatLng(37.786801, -122.420141),
new google.maps.LatLng(37.786823, -122.420034),
new google.maps.LatLng(37.786831, -122.419916),
new google.maps.LatLng(37.787034, -122.418208),
new google.maps.LatLng(37.787056, -122.418034),
new google.maps.LatLng(37.787169, -122.417145),
new google.maps.LatLng(37.787217, -122.416715),
new google.maps.LatLng(37.786144, -122.416403),
new google.maps.LatLng(37.785292, -122.416257),
new google.maps.LatLng(37.780666, -122.390374),
new google.maps.LatLng(37.780501, -122.391281),
new google.maps.LatLng(37.780148, -122.392052),
new google.maps.LatLng(37.780173, -122.391148),
new google.maps.LatLng(37.780693, -122.390592),
new google.maps.LatLng(37.781261, -122.391142),
new google.maps.LatLng(37.781808, -122.39173),
new google.maps.LatLng(37.78234, -122.392341),
new google.maps.LatLng(37.782812, -122.393022),
new google.maps.LatLng(37.7833, -122.393672),
new google.maps.LatLng(37.783809, -122.394275),
new google.maps.LatLng(37.784246, -122.394979),
new google.maps.LatLng(37.784791, -122.395958),
new google.maps.LatLng(37.785675, -122.396746),
new google.maps.LatLng(37.786262, -122.39578),
new google.maps.LatLng(37.786776, -122.395093),
new google.maps.LatLng(37.787282, -122.394426),
new google.maps.LatLng(37.787783, -122.393767),
new google.maps.LatLng(37.788343, -122.393184),
new google.maps.LatLng(37.788895, -122.392506),
new google.maps.LatLng(37.789371, -122.391701),
new google.maps.LatLng(37.789722, -122.390952),
new google.maps.LatLng(37.790315, -122.390305),
new google.maps.LatLng(37.790738, -122.389616),
new google.maps.LatLng(37.779448, -122.438702),
new google.maps.LatLng(37.779023, -122.438585),
new google.maps.LatLng(37.778542, -122.438492),
new google.maps.LatLng(37.7781, -122.438411),
new google.maps.LatLng(37.777986, -122.438376),
new google.maps.LatLng(37.77768, -122.438313),
new google.maps.LatLng(37.777316, -122.438273),
new google.maps.LatLng(37.777135, -122.438254),
new google.maps.LatLng(37.776987, -122.438303),
new google.maps.LatLng(37.776946, -122.438404),
new google.maps.LatLng(37.776944, -122.438467),
new google.maps.LatLng(37.776892, -122.438459),
new google.maps.LatLng(37.776842, -122.438442),
new google.maps.LatLng(37.776822, -122.438391),
new google.maps.LatLng(37.776814, -122.438412),
new google.maps.LatLng(37.776787, -122.438628),
new google.maps.LatLng(37.776729, -122.43865),
new google.maps.LatLng(37.776759, -122.438677),
new google.maps.LatLng(37.776772, -122.438498),
new google.maps.LatLng(37.776787, -122.438389),
new google.maps.LatLng(37.776848, -122.438283),
new google.maps.LatLng(37.77687, -122.438239),
new google.maps.LatLng(37.777015, -122.438198),
new google.maps.LatLng(37.777333, -122.438256),
new google.maps.LatLng(37.777595, -122.438308),
new google.maps.LatLng(37.777797, -122.438344),
new google.maps.LatLng(37.77816, -122.438442),
new google.maps.LatLng(37.778414, -122.438508),
new google.maps.LatLng(37.778445, -122.438516),
new google.maps.LatLng(37.778503, -122.438529),
new google.maps.LatLng(37.778607, -122.438549),
new google.maps.LatLng(37.77867, -122.438644),
new google.maps.LatLng(37.778847, -122.438706),
new google.maps.LatLng(37.77924, -122.438744),
new google.maps.LatLng(37.779738, -122.438822),
new google.maps.LatLng(37.780201, -122.438882),
new google.maps.LatLng(37.7804, -122.438905),
new google.maps.LatLng(37.780501, -122.438921),
new google.maps.LatLng(37.780892, -122.438986),
new google.maps.LatLng(37.781446, -122.439087),
new google.maps.LatLng(37.781985, -122.439199),
new google.maps.LatLng(37.782239, -122.439249),
new google.maps.LatLng(37.782286, -122.439266),
new google.maps.LatLng(37.797847, -122.429388),
new google.maps.LatLng(37.797874, -122.42918),
new google.maps.LatLng(37.797885, -122.429069),
new google.maps.LatLng(37.797887, -122.42905),
new google.maps.LatLng(37.797933, -122.428954),
new google.maps.LatLng(37.798242, -122.42899),
new google.maps.LatLng(37.798617, -122.429075),
new google.maps.LatLng(37.798719, -122.429092),
new google.maps.LatLng(37.798944, -122.429145),
new google.maps.LatLng(37.79932, -122.429251),
new google.maps.LatLng(37.79959, -122.429309),
new google.maps.LatLng(37.799677, -122.429324),
new google.maps.LatLng(37.799966, -122.42936),
new google.maps.LatLng(37.800288, -122.42943),
new google.maps.LatLng(37.800443, -122.429461),
new google.maps.LatLng(37.800465, -122.429474),
new google.maps.LatLng(37.800644, -122.42954),
new google.maps.LatLng(37.800948, -122.42962),
new google.maps.LatLng(37.801242, -122.429685),
new google.maps.LatLng(37.801375, -122.429702),
new google.maps.LatLng(37.8014, -122.429703),
new google.maps.LatLng(37.801453, -122.429707),
new google.maps.LatLng(37.801473, -122.429709),
new google.maps.LatLng(37.801532, -122.429707),
new google.maps.LatLng(37.801852, -122.429729),
new google.maps.LatLng(37.802173, -122.429789),
new google.maps.LatLng(37.802459, -122.429847),
new google.maps.LatLng(37.802554, -122.429825),
new google.maps.LatLng(37.802647, -122.429549),
new google.maps.LatLng(37.802693, -122.429179),
new google.maps.LatLng(37.802729, -122.428751),
new google.maps.LatLng(37.766104, -122.409291),
new google.maps.LatLng(37.766103, -122.409268),
new google.maps.LatLng(37.766138, -122.409229),
new google.maps.LatLng(37.766183, -122.409231),
new google.maps.LatLng(37.766153, -122.409276),
new google.maps.LatLng(37.766005, -122.409365),
new google.maps.LatLng(37.765897, -122.40957),
new google.maps.LatLng(37.765767, -122.409739),
new google.maps.LatLng(37.765693, -122.410389),
new google.maps.LatLng(37.765615, -122.411201),
new google.maps.LatLng(37.765533, -122.412121),
new google.maps.LatLng(37.765467, -122.412939),
new google.maps.LatLng(37.765444, -122.414821),
new google.maps.LatLng(37.765444, -122.414964),
new google.maps.LatLng(37.765318, -122.415424),
new google.maps.LatLng(37.763961, -122.415296),
new google.maps.LatLng(37.763115, -122.415196),
new google.maps.LatLng(37.762967, -122.415183),
new google.maps.LatLng(37.762278, -122.415127),
new google.maps.LatLng(37.761675, -122.415055),
new google.maps.LatLng(37.760932, -122.414988),
new google.maps.LatLng(37.759337, -122.414862),
new google.maps.LatLng(37.773187, -122.421922),
new google.maps.LatLng(37.773043, -122.422118),
new google.maps.LatLng(37.773007, -122.422165),
new google.maps.LatLng(37.772979, -122.422219),
new google.maps.LatLng(37.772865, -122.422394),
new google.maps.LatLng(37.772779, -122.422503),
new google.maps.LatLng(37.772676, -122.422701),
new google.maps.LatLng(37.772606, -122.422806),
new google.maps.LatLng(37.772566, -122.42284),
new google.maps.LatLng(37.772508, -122.422852),
new google.maps.LatLng(37.772387, -122.423011),
new google.maps.LatLng(37.772099, -122.423328),
new google.maps.LatLng(37.771704, -122.423783),
new google.maps.LatLng(37.771481, -122.424081),
new google.maps.LatLng(37.7714, -122.424179),
new google.maps.LatLng(37.771352, -122.42422),
new google.maps.LatLng(37.771248, -122.424327),
new google.maps.LatLng(37.770904, -122.424781),
new google.maps.LatLng(37.77052, -122.425283),
new google.maps.LatLng(37.770337, -122.425553),
new google.maps.LatLng(37.770128, -122.425832),
new google.maps.LatLng(37.769756, -122.426331),
new google.maps.LatLng(37.7693, -122.426902),
new google.maps.LatLng(37.769132, -122.427065),
new google.maps.LatLng(37.769092, -122.427103),
new google.maps.LatLng(37.768979, -122.427172),
new google.maps.LatLng(37.768595, -122.427634),
new google.maps.LatLng(37.768372, -122.427913),
new google.maps.LatLng(37.768337, -122.427961),
new google.maps.LatLng(37.768244, -122.428138),
new google.maps.LatLng(37.767942, -122.428581),
new google.maps.LatLng(37.767482, -122.429094),
new google.maps.LatLng(37.767031, -122.429606),
new google.maps.LatLng(37.766732, -122.429986),
new google.maps.LatLng(37.76668, -122.430058),
new google.maps.LatLng(37.766633, -122.430109),
new google.maps.LatLng(37.76658, -122.430211),
new google.maps.LatLng(37.766367, -122.430594),
new google.maps.LatLng(37.76591, -122.431137),
new google.maps.LatLng(37.765353, -122.431806),
new google.maps.LatLng(37.764962, -122.432298),
new google.maps.LatLng(37.764868, -122.432486),
new google.maps.LatLng(37.764518, -122.432913),
new google.maps.LatLng(37.763435, -122.434173),
new google.maps.LatLng(37.762847, -122.434953),
new google.maps.LatLng(37.762291, -122.435935),
new google.maps.LatLng(37.762224, -122.436074),
new google.maps.LatLng(37.761957, -122.436892),
new google.maps.LatLng(37.761652, -122.438886),
new google.maps.LatLng(37.761284, -122.439955),
new google.maps.LatLng(37.76121, -122.440068),
new google.maps.LatLng(37.761064, -122.44072),
new google.maps.LatLng(37.76104, -122.441411),
new google.maps.LatLng(37.761048, -122.442324),
new google.maps.LatLng(37.760851, -122.443118),
new google.maps.LatLng(37.759977, -122.444591),
new google.maps.LatLng(37.759913, -122.444698),
new google.maps.LatLng(37.759623, -122.445065),
new google.maps.LatLng(37.758902, -122.445158),
new google.maps.LatLng(37.758428, -122.44457),
new google.maps.LatLng(37.757687, -122.44334),
new google.maps.LatLng(37.757583, -122.44324),
new google.maps.LatLng(37.757019, -122.442787),
new google.maps.LatLng(37.756603, -122.442322),
new google.maps.LatLng(37.75638, -122.441602),
new google.maps.LatLng(37.75579, -122.441382),
new google.maps.LatLng(37.754493, -122.442133),
new google.maps.LatLng(37.754361, -122.442206),
new google.maps.LatLng(37.753719, -122.44265),
new google.maps.LatLng(37.753096, -122.442915),
new google.maps.LatLng(37.751617, -122.443211),
new google.maps.LatLng(37.751496, -122.443246),
new google.maps.LatLng(37.750733, -122.443428),
new google.maps.LatLng(37.750126, -122.443536),
new google.maps.LatLng(37.750103, -122.443784),
new google.maps.LatLng(37.75039, -122.44401),
new google.maps.LatLng(37.750448, -122.444013),
new google.maps.LatLng(37.750536, -122.44404),
new google.maps.LatLng(37.750493, -122.444141),
new google.maps.LatLng(37.790859, -122.402808),
new google.maps.LatLng(37.790864, -122.402768),
new google.maps.LatLng(37.790995, -122.402539),
new google.maps.LatLng(37.791148, -122.402172),
new google.maps.LatLng(37.791385, -122.401312),
new google.maps.LatLng(37.791405, -122.400776),
new google.maps.LatLng(37.791288, -122.400528),
new google.maps.LatLng(37.791113, -122.400441),
new google.maps.LatLng(37.791027, -122.400395),
new google.maps.LatLng(37.791094, -122.400311),
new google.maps.LatLng(37.791211, -122.400183),
new google.maps.LatLng(37.79106, -122.399334),
new google.maps.LatLng(37.790538, -122.398718),
new google.maps.LatLng(37.790095, -122.398086),
new google.maps.LatLng(37.789644, -122.39736),
new google.maps.LatLng(37.789254, -122.396844),
new google.maps.LatLng(37.788855, -122.396397),
new google.maps.LatLng(37.788483, -122.395963),
new google.maps.LatLng(37.788015, -122.395365),
new google.maps.LatLng(37.787558, -122.394735),
new google.maps.LatLng(37.787472, -122.394323),
new google.maps.LatLng(37.78763, -122.394025),
new google.maps.LatLng(37.787767, -122.393987),
new google.maps.LatLng(37.787486, -122.394452),
new google.maps.LatLng(37.786977, -122.395043),
new google.maps.LatLng(37.786583, -122.395552),
new google.maps.LatLng(37.78654, -122.39561),
new google.maps.LatLng(37.786516, -122.395659),
new google.maps.LatLng(37.786378, -122.395707),
new google.maps.LatLng(37.786044, -122.395362),
new google.maps.LatLng(37.785598, -122.394715),
new google.maps.LatLng(37.785321, -122.394361),
new google.maps.LatLng(37.785207, -122.394236),
new google.maps.LatLng(37.785751, -122.394062),
new google.maps.LatLng(37.785996, -122.393881),
new google.maps.LatLng(37.786092, -122.39383),
new google.maps.LatLng(37.785998, -122.393899),
new google.maps.LatLng(37.785114, -122.394365),
new google.maps.LatLng(37.785022, -122.394441),
new google.maps.LatLng(37.784823, -122.394635),
new google.maps.LatLng(37.784719, -122.394629),
new google.maps.LatLng(37.785069, -122.394176),
new google.maps.LatLng(37.7855, -122.39365),
new google.maps.LatLng(37.78577, -122.393291),
new google.maps.LatLng(37.785839, -122.393159),
new google.maps.LatLng(37.782651, -122.400628),
new google.maps.LatLng(37.782616, -122.400599),
new google.maps.LatLng(37.782702, -122.40047),
new google.maps.LatLng(37.782915, -122.400192),
new google.maps.LatLng(37.783137, -122.399887),
new google.maps.LatLng(37.783414, -122.399519),
new google.maps.LatLng(37.783629, -122.399237),
new google.maps.LatLng(37.783688, -122.399157),
new google.maps.LatLng(37.783716, -122.399106),
new google.maps.LatLng(37.783798, -122.399072),
new google.maps.LatLng(37.783997, -122.399186),
new google.maps.LatLng(37.784271, -122.399538),
new google.maps.LatLng(37.784577, -122.399948),
new google.maps.LatLng(37.784828, -122.40026),
new google.maps.LatLng(37.784999, -122.400477),
new google.maps.LatLng(37.785113, -122.400651),
new google.maps.LatLng(37.785155, -122.400703),
new google.maps.LatLng(37.785192, -122.400749),
new google.maps.LatLng(37.785278, -122.400839),
new google.maps.LatLng(37.785387, -122.400857),
new google.maps.LatLng(37.785478, -122.40089),
new google.maps.LatLng(37.785526, -122.401022),
new google.maps.LatLng(37.785598, -122.401148),
new google.maps.LatLng(37.785631, -122.401202),
new google.maps.LatLng(37.78566, -122.401267),
new google.maps.LatLng(37.803986, -122.426035),
new google.maps.LatLng(37.804102, -122.425089),
new google.maps.LatLng(37.804211, -122.424156),
new google.maps.LatLng(37.803861, -122.423385),
new google.maps.LatLng(37.803151, -122.423214),
new google.maps.LatLng(37.802439, -122.423077),
new google.maps.LatLng(37.80174, -122.422905),
new google.maps.LatLng(37.801069, -122.422785),
new google.maps.LatLng(37.800345, -122.422649),
new google.maps.LatLng(37.799633, -122.422603),
new google.maps.LatLng(37.79975, -122.4217),
new google.maps.LatLng(37.799885, -122.420854),
new google.maps.LatLng(37.799209, -122.420607),
new google.maps.LatLng(37.795656, -122.400395),
new google.maps.LatLng(37.795203, -122.400304),
new google.maps.LatLng(37.778738, -122.415584),
new google.maps.LatLng(37.778812, -122.415189),
new google.maps.LatLng(37.778824, -122.415092),
new google.maps.LatLng(37.778833, -122.414932),
new google.maps.LatLng(37.778834, -122.414898),
new google.maps.LatLng(37.77874, -122.414757),
new google.maps.LatLng(37.778501, -122.414433),
new google.maps.LatLng(37.778182, -122.414026),
new google.maps.LatLng(37.777851, -122.413623),
new google.maps.LatLng(37.777486, -122.413166),
new google.maps.LatLng(37.777109, -122.412674),
new google.maps.LatLng(37.776743, -122.412186),
new google.maps.LatLng(37.77644, -122.4118),
new google.maps.LatLng(37.776295, -122.411614),
new google.maps.LatLng(37.776158, -122.41144),
new google.maps.LatLng(37.775806, -122.410997),
new google.maps.LatLng(37.775422, -122.410484),
new google.maps.LatLng(37.775126, -122.410087),
new google.maps.LatLng(37.775012, -122.409854),
new google.maps.LatLng(37.775164, -122.409573),
new google.maps.LatLng(37.775498, -122.40918),
new google.maps.LatLng(37.775868, -122.40873),
new google.maps.LatLng(37.776256, -122.40824),
new google.maps.LatLng(37.776519, -122.407928),
new google.maps.LatLng(37.776539, -122.407904),
new google.maps.LatLng(37.776595, -122.407854),
new google.maps.LatLng(37.776853, -122.407547),
new google.maps.LatLng(37.777234, -122.407087),
new google.maps.LatLng(37.777644, -122.406558),
new google.maps.LatLng(37.778066, -122.406017),
new google.maps.LatLng(37.778468, -122.405499),
new google.maps.LatLng(37.778866, -122.404995),
new google.maps.LatLng(37.779295, -122.404455),
new google.maps.LatLng(37.779695, -122.40395),
new google.maps.LatLng(37.779982, -122.403584),
new google.maps.LatLng(37.780295, -122.403223),
new google.maps.LatLng(37.780664, -122.402766),
new google.maps.LatLng(37.781043, -122.402288),
new google.maps.LatLng(37.781399, -122.401823),
new google.maps.LatLng(37.781727, -122.401407),
new google.maps.LatLng(37.781853, -122.401247),
new google.maps.LatLng(37.781894, -122.401195),
new google.maps.LatLng(37.782076, -122.400977),
new google.maps.LatLng(37.782338, -122.400603),
new google.maps.LatLng(37.782666, -122.400133),
new google.maps.LatLng(37.783048, -122.399634),
new google.maps.LatLng(37.78345, -122.399198),
new google.maps.LatLng(37.783791, -122.398998),
new google.maps.LatLng(37.784177, -122.398959),
new google.maps.LatLng(37.784388, -122.398971),
new google.maps.LatLng(37.784404, -122.399128),
new google.maps.LatLng(37.784586, -122.399524),
new google.maps.LatLng(37.784835, -122.399927),
new google.maps.LatLng(37.785116, -122.400307),
new google.maps.LatLng(37.785282, -122.400539),
new google.maps.LatLng(37.785346, -122.400692),
new google.maps.LatLng(37.765769, -122.407201),
new google.maps.LatLng(37.76579, -122.407414),
new google.maps.LatLng(37.765802, -122.407755),
new google.maps.LatLng(37.765791, -122.408219),
new google.maps.LatLng(37.765763, -122.408759),
new google.maps.LatLng(37.765726, -122.409348),
new google.maps.LatLng(37.765716, -122.409882),
new google.maps.LatLng(37.765708, -122.410202),
new google.maps.LatLng(37.765705, -122.410253),
new google.maps.LatLng(37.765707, -122.410369),
new google.maps.LatLng(37.765692, -122.41072),
new google.maps.LatLng(37.765699, -122.411215),
new google.maps.LatLng(37.765687, -122.411789),
new google.maps.LatLng(37.765666, -122.412373),
new google.maps.LatLng(37.765598, -122.412883),
new google.maps.LatLng(37.765543, -122.413039),
new google.maps.LatLng(37.765532, -122.413125),
new google.maps.LatLng(37.7655, -122.413553),
new google.maps.LatLng(37.765448, -122.414053),
new google.maps.LatLng(37.765388, -122.414645),
new google.maps.LatLng(37.765323, -122.41525),
new google.maps.LatLng(37.765303, -122.415847),
new google.maps.LatLng(37.765251, -122.416439),
new google.maps.LatLng(37.765204, -122.41702),
new google.maps.LatLng(37.765172, -122.417556),
new google.maps.LatLng(37.765164, -122.418075),
new google.maps.LatLng(37.765153, -122.418618),
new google.maps.LatLng(37.765136, -122.419112),
new google.maps.LatLng(37.765129, -122.419378),
new google.maps.LatLng(37.765119, -122.419481),
new google.maps.LatLng(37.7651, -122.419852),
new google.maps.LatLng(37.765083, -122.420349),
new google.maps.LatLng(37.765045, -122.42093),
new google.maps.LatLng(37.764992, -122.421481),
new google.maps.LatLng(37.76498, -122.421695),
new google.maps.LatLng(37.764993, -122.421843),
new google.maps.LatLng(37.764986, -122.422255),
new google.maps.LatLng(37.764975, -122.422823),
new google.maps.LatLng(37.764939, -122.423411),
new google.maps.LatLng(37.764902, -122.424014),
new google.maps.LatLng(37.764853, -122.424576),
new google.maps.LatLng(37.764826, -122.424922),
new google.maps.LatLng(37.764796, -122.425375),
new google.maps.LatLng(37.764782, -122.425869),
new google.maps.LatLng(37.764768, -122.426089),
new google.maps.LatLng(37.764766, -122.426117),
new google.maps.LatLng(37.764723, -122.426276),
new google.maps.LatLng(37.764681, -122.426649),
new google.maps.LatLng(37.782012, -122.4042),
new google.maps.LatLng(37.781574, -122.404911),
new google.maps.LatLng(37.781055, -122.405597),
new google.maps.LatLng(37.780479, -122.406341),
new google.maps.LatLng(37.779996, -122.406939),
new google.maps.LatLng(37.779459, -122.407613),
new google.maps.LatLng(37.778953, -122.408228),
new google.maps.LatLng(37.778409, -122.408839),
new google.maps.LatLng(37.777842, -122.409501),
new google.maps.LatLng(37.777334, -122.410181),
new google.maps.LatLng(37.776809, -122.410836),
new google.maps.LatLng(37.77624, -122.411514),
new google.maps.LatLng(37.775725, -122.412145),
new google.maps.LatLng(37.77519, -122.412805),
new google.maps.LatLng(37.774672, -122.413464),
new google.maps.LatLng(37.774084, -122.414186),
new google.maps.LatLng(37.773533, -122.413636),
new google.maps.LatLng(37.773021, -122.413009),
new google.maps.LatLng(37.772501, -122.412371),
new google.maps.LatLng(37.771964, -122.411681),
new google.maps.LatLng(37.771479, -122.411078),
new google.maps.LatLng(37.770992, -122.410477),
new google.maps.LatLng(37.770467, -122.409801),
new google.maps.LatLng(37.77009, -122.408904),
new google.maps.LatLng(37.769657, -122.408103),
new google.maps.LatLng(37.769132, -122.407276),
new google.maps.LatLng(37.768564, -122.406469),
new google.maps.LatLng(37.76798, -122.405745),
new google.maps.LatLng(37.76738, -122.405299),
new google.maps.LatLng(37.766604, -122.405297),
new google.maps.LatLng(37.765838, -122.4052),
new google.maps.LatLng(37.765139, -122.405139),
new google.maps.LatLng(37.764457, -122.405094),
new google.maps.LatLng(37.763716, -122.405142),
new google.maps.LatLng(37.762932, -122.405398),
new google.maps.LatLng(37.762126, -122.405813),
new google.maps.LatLng(37.761344, -122.406215),
new google.maps.LatLng(37.760556, -122.406495),
new google.maps.LatLng(37.759732, -122.406484),
new google.maps.LatLng(37.75891, -122.406228),
new google.maps.LatLng(37.758182, -122.405695),
new google.maps.LatLng(37.757676, -122.405118),
new google.maps.LatLng(37.757039, -122.404346),
new google.maps.LatLng(37.756335, -122.403719),
new google.maps.LatLng(37.755503, -122.403406),
new google.maps.LatLng(37.754665, -122.403242),
new google.maps.LatLng(37.753837, -122.403172),
new google.maps.LatLng(37.752986, -122.403112),
new google.maps.LatLng(37.751266, -122.403355),
];
}
// [END maps_layer_heatmap]
export { initMap };

View File

@ -1,21 +0,0 @@
import { Map, GoogleApiWrapper } from 'google-maps-react';
export function Heatmap() {
const mapStyles = {
width: '100%',
height: '100%',
};
return (
<Map
google={this.props.google}
zoom={8}
style={mapStyles}
initialCenter={{ lat: 47.444, lng: -122.176 }}
/>
);
}
export default GoogleApiWrapper({
apiKey: 'AIzaSyCZ51VFfxqZ2GkCmVrcNZdUKsM0fuBQUCY'
});

View File

@ -0,0 +1,208 @@
"use strict";
/* tslint:disable */
/* eslint-disable */
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiException = exports.HttpStatusCode = void 0;
var LogService = /** @class */ (function () {
function LogService(baseUrl, http) {
this.jsonParseReviver = undefined;
this.http = http ? http : window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "api/logs";
}
LogService.prototype.getAll = function () {
var _this = this;
var url_ = this.baseUrl + "/all";
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processGetAll(_response);
});
};
LogService.prototype.processGetAll = function (response) {
var _this = this;
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 200) {
return response.text().then(function (_responseText) {
var result200 = null;
var resultData200 = _responseText === "" ? null : JSON.parse(_responseText, _this.jsonParseReviver);
if (Array.isArray(resultData200)) {
result200 = [];
for (var _i = 0, resultData200_1 = resultData200; _i < resultData200_1.length; _i++) {
var item = resultData200_1[_i];
result200.push(item);
}
}
return result200;
});
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
LogService.prototype.getFiles = function (filenames) {
var _this = this;
var url_ = this.baseUrl + "?";
if (filenames !== undefined && filenames !== null)
filenames && filenames.forEach(function (item) { url_ += "filenames=" + encodeURIComponent("" + item) + "&"; });
url_ = url_.replace(/[?&]$/, "");
var options_ = {
method: "GET",
headers: {
"Accept": "application/octet-stream",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then(function (_response) {
return _this.processGetFiles(_response);
});
};
LogService.prototype.processGetFiles = function (response) {
var status = response.status;
var _headers = {};
if (response.headers && response.headers.forEach) {
response.headers.forEach(function (v, k) { return _headers[k] = v; });
}
;
if (status === 200 || status === 206) {
var contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
var fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
var fileName_1 = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
return response.blob().then(function (blob) { return { fileName: fileName_1, data: blob, status: status, headers: _headers }; });
}
else if (status !== 200 && status !== 204) {
return response.text().then(function (_responseText) {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve(null);
};
return LogService;
}());
exports.default = LogService;
var HttpStatusCode;
(function (HttpStatusCode) {
HttpStatusCode["Continue"] = "Continue";
HttpStatusCode["SwitchingProtocols"] = "SwitchingProtocols";
HttpStatusCode["Processing"] = "Processing";
HttpStatusCode["EarlyHints"] = "EarlyHints";
HttpStatusCode["OK"] = "OK";
HttpStatusCode["Created"] = "Created";
HttpStatusCode["Accepted"] = "Accepted";
HttpStatusCode["NonAuthoritativeInformation"] = "NonAuthoritativeInformation";
HttpStatusCode["NoContent"] = "NoContent";
HttpStatusCode["ResetContent"] = "ResetContent";
HttpStatusCode["PartialContent"] = "PartialContent";
HttpStatusCode["MultiStatus"] = "MultiStatus";
HttpStatusCode["AlreadyReported"] = "AlreadyReported";
HttpStatusCode["IMUsed"] = "IMUsed";
HttpStatusCode["MultipleChoices"] = "Ambiguous";
HttpStatusCode["Ambiguous"] = "Ambiguous";
HttpStatusCode["MovedPermanently"] = "Moved";
HttpStatusCode["Moved"] = "Moved";
HttpStatusCode["Found"] = "Redirect";
HttpStatusCode["Redirect"] = "Redirect";
HttpStatusCode["SeeOther"] = "RedirectMethod";
HttpStatusCode["RedirectMethod"] = "RedirectMethod";
HttpStatusCode["NotModified"] = "NotModified";
HttpStatusCode["UseProxy"] = "UseProxy";
HttpStatusCode["Unused"] = "Unused";
HttpStatusCode["TemporaryRedirect"] = "TemporaryRedirect";
HttpStatusCode["RedirectKeepVerb"] = "TemporaryRedirect";
HttpStatusCode["PermanentRedirect"] = "PermanentRedirect";
HttpStatusCode["BadRequest"] = "BadRequest";
HttpStatusCode["Unauthorized"] = "Unauthorized";
HttpStatusCode["PaymentRequired"] = "PaymentRequired";
HttpStatusCode["Forbidden"] = "Forbidden";
HttpStatusCode["NotFound"] = "NotFound";
HttpStatusCode["MethodNotAllowed"] = "MethodNotAllowed";
HttpStatusCode["NotAcceptable"] = "NotAcceptable";
HttpStatusCode["ProxyAuthenticationRequired"] = "ProxyAuthenticationRequired";
HttpStatusCode["RequestTimeout"] = "RequestTimeout";
HttpStatusCode["Conflict"] = "Conflict";
HttpStatusCode["Gone"] = "Gone";
HttpStatusCode["LengthRequired"] = "LengthRequired";
HttpStatusCode["PreconditionFailed"] = "PreconditionFailed";
HttpStatusCode["RequestEntityTooLarge"] = "RequestEntityTooLarge";
HttpStatusCode["RequestUriTooLong"] = "RequestUriTooLong";
HttpStatusCode["UnsupportedMediaType"] = "UnsupportedMediaType";
HttpStatusCode["RequestedRangeNotSatisfiable"] = "RequestedRangeNotSatisfiable";
HttpStatusCode["ExpectationFailed"] = "ExpectationFailed";
HttpStatusCode["MisdirectedRequest"] = "MisdirectedRequest";
HttpStatusCode["UnprocessableEntity"] = "UnprocessableEntity";
HttpStatusCode["Locked"] = "Locked";
HttpStatusCode["FailedDependency"] = "FailedDependency";
HttpStatusCode["UpgradeRequired"] = "UpgradeRequired";
HttpStatusCode["PreconditionRequired"] = "PreconditionRequired";
HttpStatusCode["TooManyRequests"] = "TooManyRequests";
HttpStatusCode["RequestHeaderFieldsTooLarge"] = "RequestHeaderFieldsTooLarge";
HttpStatusCode["UnavailableForLegalReasons"] = "UnavailableForLegalReasons";
HttpStatusCode["InternalServerError"] = "InternalServerError";
HttpStatusCode["NotImplemented"] = "NotImplemented";
HttpStatusCode["BadGateway"] = "BadGateway";
HttpStatusCode["ServiceUnavailable"] = "ServiceUnavailable";
HttpStatusCode["GatewayTimeout"] = "GatewayTimeout";
HttpStatusCode["HttpVersionNotSupported"] = "HttpVersionNotSupported";
HttpStatusCode["VariantAlsoNegotiates"] = "VariantAlsoNegotiates";
HttpStatusCode["InsufficientStorage"] = "InsufficientStorage";
HttpStatusCode["LoopDetected"] = "LoopDetected";
HttpStatusCode["NotExtended"] = "NotExtended";
HttpStatusCode["NetworkAuthenticationRequired"] = "NetworkAuthenticationRequired";
})(HttpStatusCode = exports.HttpStatusCode || (exports.HttpStatusCode = {}));
var ApiException = /** @class */ (function (_super) {
__extends(ApiException, _super);
function ApiException(message, status, response, headers, result) {
var _this = _super.call(this) || this;
_this.isApiException = true;
_this.message = message;
_this.status = status;
_this.response = response;
_this.headers = headers;
_this.result = result;
return _this;
}
ApiException.isApiException = function (obj) {
return obj.isApiException === true;
};
return ApiException;
}(Error));
exports.ApiException = ApiException;
function throwException(message, status, response, headers, result) {
if (result !== null && result !== undefined)
throw result;
else
throw new ApiException(message, status, response, headers, null);
}
//# sourceMappingURL=LogService.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,201 @@
/* tslint:disable */
/* eslint-disable */
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming
export default class LogService {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
this.http = http ? http : <any>window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "api/logs";
}
getAll(): Promise<string[]> {
let url_ = this.baseUrl + "/all";
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/json",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processGetAll(_response);
});
}
protected processGetAll(response: Response): Promise<string[]> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
if (Array.isArray(resultData200)) {
result200 = [] as any;
for (let item of resultData200)
result200!.push(item);
}
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<string[]>(<any>null);
}
getFiles(filenames: string[] | null | undefined): Promise<FileResponse | null> {
let url_ = this.baseUrl + "?";
if (filenames !== undefined && filenames !== null)
filenames && filenames.forEach(item => { url_ += "filenames=" + encodeURIComponent("" + item) + "&"; });
url_ = url_.replace(/[?&]$/, "");
let options_ = <RequestInit>{
method: "GET",
headers: {
"Accept": "application/octet-stream",
'Authorization': sessionStorage.getItem('user')
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processGetFiles(_response);
});
}
protected processGetFiles(response: Response): Promise<FileResponse | null> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) {
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
const fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
const fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<FileResponse | null>(<any>null);
}
}
export interface FileResponse {
data: Blob;
status: number;
fileName?: string;
headers?: { [name: string]: any };
}
export enum HttpStatusCode {
Continue = "Continue",
SwitchingProtocols = "SwitchingProtocols",
Processing = "Processing",
EarlyHints = "EarlyHints",
OK = "OK",
Created = "Created",
Accepted = "Accepted",
NonAuthoritativeInformation = "NonAuthoritativeInformation",
NoContent = "NoContent",
ResetContent = "ResetContent",
PartialContent = "PartialContent",
MultiStatus = "MultiStatus",
AlreadyReported = "AlreadyReported",
IMUsed = "IMUsed",
MultipleChoices = "Ambiguous",
Ambiguous = "Ambiguous",
MovedPermanently = "Moved",
Moved = "Moved",
Found = "Redirect",
Redirect = "Redirect",
SeeOther = "RedirectMethod",
RedirectMethod = "RedirectMethod",
NotModified = "NotModified",
UseProxy = "UseProxy",
Unused = "Unused",
TemporaryRedirect = "TemporaryRedirect",
RedirectKeepVerb = "TemporaryRedirect",
PermanentRedirect = "PermanentRedirect",
BadRequest = "BadRequest",
Unauthorized = "Unauthorized",
PaymentRequired = "PaymentRequired",
Forbidden = "Forbidden",
NotFound = "NotFound",
MethodNotAllowed = "MethodNotAllowed",
NotAcceptable = "NotAcceptable",
ProxyAuthenticationRequired = "ProxyAuthenticationRequired",
RequestTimeout = "RequestTimeout",
Conflict = "Conflict",
Gone = "Gone",
LengthRequired = "LengthRequired",
PreconditionFailed = "PreconditionFailed",
RequestEntityTooLarge = "RequestEntityTooLarge",
RequestUriTooLong = "RequestUriTooLong",
UnsupportedMediaType = "UnsupportedMediaType",
RequestedRangeNotSatisfiable = "RequestedRangeNotSatisfiable",
ExpectationFailed = "ExpectationFailed",
MisdirectedRequest = "MisdirectedRequest",
UnprocessableEntity = "UnprocessableEntity",
Locked = "Locked",
FailedDependency = "FailedDependency",
UpgradeRequired = "UpgradeRequired",
PreconditionRequired = "PreconditionRequired",
TooManyRequests = "TooManyRequests",
RequestHeaderFieldsTooLarge = "RequestHeaderFieldsTooLarge",
UnavailableForLegalReasons = "UnavailableForLegalReasons",
InternalServerError = "InternalServerError",
NotImplemented = "NotImplemented",
BadGateway = "BadGateway",
ServiceUnavailable = "ServiceUnavailable",
GatewayTimeout = "GatewayTimeout",
HttpVersionNotSupported = "HttpVersionNotSupported",
VariantAlsoNegotiates = "VariantAlsoNegotiates",
InsufficientStorage = "InsufficientStorage",
LoopDetected = "LoopDetected",
NotExtended = "NotExtended",
NetworkAuthenticationRequired = "NetworkAuthenticationRequired",
}
export class ApiException extends Error {
message: string;
status: number;
response: string;
headers: { [key: string]: any; };
result: any;
constructor(message: string, status: number, response: string, headers: { [key: string]: any; }, result: any) {
super();
this.message = message;
this.status = status;
this.response = response;
this.headers = headers;
this.result = result;
}
protected isApiException = true;
static isApiException(obj: any): obj is ApiException {
return obj.isApiException === true;
}
}
function throwException(message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any {
if (result !== null && result !== undefined)
throw result;
else
throw new ApiException(message, status, response, headers, null);
}

View File

@ -0,0 +1,128 @@
import { Box, Button, Checkbox, List, ListItem, ListItemIcon, ListItemText, Paper, withStyles } from '@material-ui/core';
import { blueGrey } from '@material-ui/core/colors';
import React, { Component } from 'react';
import LogService from './LogService';
const styles = theme => ({
root: {
padding: '64px',
backgroundColor: theme.palette.primary.dark,
},
paper: {
backgroundColor: blueGrey[50],
},
});
class Logs extends Component {
constructor(props) {
super(props)
this.state = {
service: null,
files: [],
checked: [],
selectAllChecked: false,
}
}
componentDidMount() {
var service = new LogService();
this.setState({service: service});
service.getAll().then(result => {
this.setState({files: result});
}).catch(ex => console.log(ex));
}
handleToggle = (value) => {
const currentIndex = this.state.checked.indexOf(value);
const newChecked = [...this.state.checked];
if (currentIndex === -1) {
newChecked.push(value);
} else {
newChecked.splice(currentIndex, 1);
}
this.setState({checked: newChecked});
}
handleSelectAllToggle = () => {
this.setState({selectAllChecked: !this.state.selectAllChecked});
if (this.state.selectAllChecked) {
this.setState({checked: []});
} else {
const newChecked = [...this.state.files];
this.setState({checked: newChecked});
}
}
onDownload = () => {
this.state.service.getFiles(this.state.checked)
.then(result => {
const filename = `Logs-${new Date().toISOString()}.zip`;
const textUrl = URL.createObjectURL(result.data);
const element = document.createElement('a');
element.setAttribute('href', textUrl);
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
this.setState({checked: []});
this.setState({selectAllChecked: false});
})
.catch(ex => console.log(ex));
}
render() {
const { classes } = this.props;
const Files = this.state.files.map((value) => {
const labelId = `checkbox-list-label-${value}`;
return (
<ListItem key={value} role={undefined} dense button onClick={() => this.handleToggle(value)}>
<ListItemIcon>
<Checkbox
edge="start"
checked={this.state.checked.indexOf(value) !== -1}
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': labelId }}
/>
</ListItemIcon>
<ListItemText id={labelId} primary={`${value}`} />
</ListItem>
);
})
return (
<Box className={classes.root}>
<Paper className={classes.paper}>
<List className={classes.paper}>
<ListItem key="Select-all" role={undefined} dense button onClick={this.handleSelectAllToggle}>
<ListItemIcon>
<Checkbox
edge="start"
checked={this.state.selectAllChecked}
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': "Select-all" }}
/>
</ListItemIcon>
<ListItemText id="checkbox-list-label-Select-all" primary={(this.state.selectAllChecked ? "Uns" : "S") + "elect all"} />
</ListItem>
{Files}
</List>
<Button onClick={this.onDownload}>
Download
</Button>
</Paper>
</Box>
)
}
}
export default withStyles(styles)(Logs);

View File

@ -0,0 +1,12 @@
import React from 'react';
export default React.createContext({
devices: [],
heatmapPoints: [],
addHandler: (_, __) => { },
removeHandler: (_, __) => { },
updateDevice: () => { },
updateAllDevices: () => { },
});

View File

@ -0,0 +1,152 @@
import Context from './DevicesContext'
import { HubConnectionBuilder } from '@microsoft/signalr';
import DeviceService from '../common/DeviceService';
import C from '../common/Constants'
import React, { Component } from 'react'
const hub_url = '/hubs/devices';
export default class DevicesContextProvider extends Component {
constructor(props) {
super(props);
const handlers = {};
for (var property in C) {
handlers[C[property]] = [];
};
this.state = {
hubConnection: null,
devices: [],
heatmapPoints: [],
handlers: handlers,
};
}
addHandler = (methodName, handler) => {
const updatedHandlers = this.state.handlers;
updatedHandlers[methodName].push(handler);
//console.log("Added '" + methodName + "' handler.");
this.setState({ handlers: updatedHandlers });
}
removeHandler = (methodName, handler) => {
const updatedHandlers = this.state.handlers;
var index = updatedHandlers[methodName].findIndex((h => h === handler));
if (index > -1) {
updatedHandlers[methodName].splice(index, 1);
//console.log("Removed '" + methodName + "' handler.");
}
this.setState({ handlers: updatedHandlers });
}
updateDevice = (id) => {
this.updateDeviceInternal(id);
}
updateAllDevices = () => {
this.updateAllDevicesInternal();
}
invokeHandlers(methodName, context) {
this.state.handlers[methodName].forEach(function (handler) {
handler(context);
});
}
updateAllDevicesInternal(service = null) {
if (service === null) {
service = new DeviceService();
}
service.getall().then(result => {
this.setState({ devices: result });
this.invokeHandlers(C.update_all_method_name, null);
}).catch(ex => {
console.log(ex);
});
}
updateDeviceInternal(id, service = null) {
if (service === null) {
service = new DeviceService();
}
service.getdevice(id).then(result => {
const updatedDevices = [...this.state.devices];
var index = updatedDevices.findIndex((d => d.id == id));
if (index > -1) {
updatedDevices[index] = result;
}
else {
updatedDevices.push(result);
}
this.setState({ devices: updatedDevices });
this.invokeHandlers(C.update_method_name, result);
}).catch(ex => console.log("Device update failed.", ex));
}
componentDidMount() {
const service = new DeviceService();
this.updateAllDevicesInternal(service);
const newConnection = new HubConnectionBuilder()
.withUrl(hub_url)
.withAutomaticReconnect()
.build();
this.setState({ hubConnection: newConnection });
newConnection.start()
.then(_ => {
console.log('Devices hub Connected!');
newConnection.on(C.probability_method_name, (messages) => {
//console.log(method_name + " recieved: [id: " + id + ", date: " + date + ", prob: " + prob + "]");
const newPoints = [];
for (var message of messages) {
var device = this.state.devices.filter(function (x) { return x.id === message.deviceId })[0]
var newPoint = { deviceId: device.id, lat: device.coordinates.latitude, lng: device.coordinates.longitude, prob: message.probability, date: new Date(message.date) };
newPoints.push(newPoint);
}
this.setState({
heatmapPoints: this.state.heatmapPoints.concat(newPoints)
});
this.invokeHandlers(C.probability_method_name, newPoints);
});
newConnection.on(C.update_all_method_name, () => {
this.updateAllDevicesInternal(service);
});
newConnection.on(C.update_method_name, (id) => this.updateDeviceInternal(id, service));
}).catch(e => console.log('Devices hub Connection failed: ', e));
}
componentWillUnmount() {
if (this.state.hubConnection != null) {
this.state.hubConnection.off(C.probability_method_name);
this.state.hubConnection.off(C.update_all_method_name);
this.state.hubConnection.off(C.update_method_name);
console.log('Devices hub Disconnected!');
}
}
render() {
return (
<Context.Provider
value={{
devices: this.state.devices,
heatmapPoints: this.state.heatmapPoints,
addHandler: this.addHandler,
removeHandler: this.removeHandler,
updateDevice: this.updateDevice,
updateAllDevices: this.updateAllDevices,
}}
>
{this.props.children}
</Context.Provider>
);
};
}

View File

@ -60,7 +60,7 @@ namespace Birdmap.Controllers
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
var response = _mapper.Map<AuthenticateResponse>(user);
AuthenticateResponse response = _mapper.Map<AuthenticateResponse>(user);
response.AccessToken = tokenString;
response.TokenType = "Bearer";
response.ExpiresIn = expiresInSeconds;

View File

@ -1,26 +1,30 @@
using Birdmap.BLL.Interfaces;
using Birdmap.BLL.Services.CommunicationServices.Hubs;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks;
namespace Birdmap.API.Controllers
{
[Authorize]
[Authorize(Roles = "User, Admin")]
[ApiController]
[Route("api/[controller]")]
public class DevicesController : ControllerBase
{
private readonly IDeviceService _service;
private readonly IHubContext<DevicesHub, IDevicesHubClient> _hubContext;
private readonly ILogger<ServicesController> _logger;
public DevicesController(IDeviceService service, ILogger<ServicesController> logger)
public DevicesController(IDeviceService service, IHubContext<DevicesHub, IDevicesHubClient> hubContext, ILogger<ServicesController> logger)
{
_service = service;
_hubContext = hubContext;
_logger = logger;
}
@ -36,24 +40,28 @@ namespace Birdmap.API.Controllers
/// <summary>Shut down all devices</summary>
/// <returns>Message sent</returns>
[Authorize(Roles = "Admin")]
[HttpPost, Route("offline")]
public async Task<IActionResult> Offlineall()
{
_logger.LogInformation("Turning off all devices and sensors...");
await _service.OfflineallAsync();
await _hubContext.Clients.All.NotifyAllUpdatedAsync();
return Ok();
}
/// <summary>Bring all devices online</summary>
/// <returns>Message sent</returns>
[Authorize(Roles = "Admin")]
[HttpPost, Route("online")]
public async Task<IActionResult> Onlineall()
{
_logger.LogInformation("Turning on all devices and sensors...");
await _service.OnlineallAsync();
await _hubContext.Clients.All.NotifyAllUpdatedAsync();
return Ok();
}
@ -72,12 +80,14 @@ namespace Birdmap.API.Controllers
/// <summary>Shut down device</summary>
/// <param name="deviceID">ID of device to shut down</param>
/// <returns>Message sent</returns>
[Authorize(Roles = "Admin")]
[HttpPost, Route("{deviceID}/offline")]
public async Task<IActionResult> Offlinedevice([BindRequired] Guid deviceID)
{
_logger.LogInformation($"Turning off device [{deviceID}]...");
await _service.OfflinedeviceAsync(deviceID);
await _hubContext.Clients.All.NotifyDeviceUpdatedAsync(deviceID);
return Ok();
}
@ -85,12 +95,14 @@ namespace Birdmap.API.Controllers
/// <summary>Bring device online</summary>
/// <param name="deviceID">ID of device to bring online</param>
/// <returns>Message sent</returns>
[Authorize(Roles = "Admin")]
[HttpPost, Route("{deviceID}/online")]
public async Task<IActionResult> Onlinedevice([BindRequired] Guid deviceID)
{
_logger.LogInformation($"Turning on device [{deviceID}]...");
await _service.OnlinedeviceAsync(deviceID);
await _hubContext.Clients.All.NotifyDeviceUpdatedAsync(deviceID);
return Ok();
}
@ -111,12 +123,14 @@ namespace Birdmap.API.Controllers
/// <param name="deviceID">ID of device to query</param>
/// <param name="sensorID">ID of sensor to query</param>
/// <returns>Message sent</returns>
[Authorize(Roles = "Admin")]
[HttpPost, Route("{deviceID}/{sensorID}/offline")]
public async Task<IActionResult> Offlinesensor([BindRequired] Guid deviceID, [BindRequired] Guid sensorID)
{
_logger.LogInformation($"Turning off sensor [{sensorID}] of device [{deviceID}]...");
await _service.OfflinesensorAsync(deviceID, sensorID);
await _hubContext.Clients.All.NotifyDeviceUpdatedAsync(deviceID);
return Ok();
}
@ -125,12 +139,14 @@ namespace Birdmap.API.Controllers
/// <param name="deviceID">ID of device to query</param>
/// <param name="sensorID">ID of sensor to query</param>
/// <returns>Message sent</returns>
[Authorize(Roles = "Admin")]
[HttpPost, Route("{deviceID}/{sensorID}/online")]
public async Task<IActionResult> Onlinesensor([BindRequired] Guid deviceID, [BindRequired] Guid sensorID)
{
_logger.LogInformation($"Turning on sensor [{sensorID}] of device [{deviceID}]...");
await _service.OnlinesensorAsync(deviceID, sensorID);
await _hubContext.Clients.All.NotifyDeviceUpdatedAsync(deviceID);
return Ok();
}

View File

@ -0,0 +1,66 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Birdmap.API.Controllers
{
[Authorize(Roles = "Admin")]
[ApiController]
[Route("api/[controller]")]
public class LogsController : ControllerBase
{
private readonly ILogger<LogsController> _logger;
private readonly string _logFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Logs");
public LogsController(ILogger<LogsController> logger)
{
_logger = logger;
}
[HttpGet("all")]
public ActionResult<List<string>> GetAll()
{
_logger.LogInformation($"Getting all log filenames from folder: '{_logFolderPath}'...");
return Directory.EnumerateFiles(_logFolderPath, "*.log")
.Select(f => Path.GetFileName(f))
.ToList();
}
[HttpGet]
public async Task<IActionResult> GetFiles([FromQuery] params string[] filenames)
{
if (!filenames.Any())
return null;
return await Task.Run(() =>
{
var zipStream = new MemoryStream();
using (var zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
foreach (var file in Directory.GetFiles(_logFolderPath, "*.log"))
{
var filename = Path.GetFileName(file);
if (filenames.Contains(filename))
{
zip.CreateEntryFromFile(file, filename);
}
}
}
zipStream.Position = 0;
return File(zipStream, "application/octet-stream");
});
}
}
}

View File

@ -1,45 +1,64 @@
using AutoMapper;
using Birdmap.API.DTOs;
using Birdmap.BLL.Interfaces;
using Birdmap.BLL.Services.CommunicationServices.Hubs;
using Birdmap.DAL.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace Birdmap.API.Controllers
{
[Authorize]
[Authorize(Roles = "User, Admin")]
[ApiController]
[Route("api/[controller]")]
public class ServicesController : ControllerBase
{
private readonly IServiceService _service;
private readonly IMapper _mapper;
private readonly ICommunicationService _communicationService;
private readonly IHubContext<ServicesHub, IServicesHubClient> _hubContext;
private readonly ILogger<ServicesController> _logger;
public ServicesController(IServiceService service, IMapper mapper, ILogger<ServicesController> logger)
public ServicesController(IServiceService service, IMapper mapper, ICommunicationServiceProvider communicationServiceProvider,
IHubContext<ServicesHub, IServicesHubClient> hubContext, ILogger<ServicesController> logger)
{
_service = service;
_mapper = mapper;
_communicationService = communicationServiceProvider.Service;
_hubContext = hubContext;
_logger = logger;
}
[HttpGet("count"), ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<int>> GetCountAsync()
{
_logger.LogInformation($"Getting service count from db...");
return await _service.GetServiceCountAsync() + 1;
}
[HttpGet, ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<List<ServiceInfo>>> GetAsync()
{
_logger.LogInformation($"Getting all services from db...");
var serviceInfos = (await _service.GetAllServicesAsync())
.Select(s => new ServiceInfo { Service = _mapper.Map<ServiceRequest>(s) });
.Select(s => new ServiceInfo { Service = _mapper.Map<ServiceRequest>(s) }).ToList();
var client = new HttpClient();
var tasks = new List<Task>();
foreach (var si in serviceInfos)
{
tasks.Add(Task.Run(async () =>
{
var client = new HttpClient();
try
{
_logger.LogInformation($"Sending a request to service [{si.Service.Name}] with url [{si.Service.Uri}]...");
@ -50,14 +69,30 @@ namespace Birdmap.API.Controllers
catch (Exception ex)
{
_logger.LogWarning($"Requesting service [{si.Service.Name}] faulted.");
si.StatusCode = System.Net.HttpStatusCode.ServiceUnavailable;
si.StatusCode = HttpStatusCode.ServiceUnavailable;
si.Response = ex.ToString();
}
}));
}
await Task.WhenAll(tasks);
serviceInfos.Add(new()
{
Service = new()
{
Id = 0,
Name = "Message Queue Service",
Uri = "localhost",
},
Response = $"IsConnected: {_communicationService.IsConnected}",
StatusCode = _communicationService.IsConnected ? HttpStatusCode.OK : HttpStatusCode.ServiceUnavailable,
});
return serviceInfos.ToList();
}
[Authorize(Roles = "Admin")]
[HttpPost, ProducesResponseType(StatusCodes.Status201Created)]
public async Task<ActionResult<ServiceRequest>> PostAsync(ServiceRequest request)
{
@ -66,12 +101,14 @@ namespace Birdmap.API.Controllers
_mapper.Map<Service>(request));
_logger.LogInformation($"Created service [{created.Id}].");
await _hubContext.Clients.All.NotifyUpdatedAsync();
return CreatedAtAction(
nameof(GetAsync),
_mapper.Map<ServiceRequest>(created));
}
[Authorize(Roles = "Admin")]
[HttpPut, ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> PutAsync(ServiceRequest request)
{
@ -80,16 +117,19 @@ namespace Birdmap.API.Controllers
service.IsFromConfig = false;
await _service.UpdateServiceAsync(service);
await _hubContext.Clients.All.NotifyUpdatedAsync();
return NoContent();
}
[Authorize(Roles = "Admin")]
[HttpDelete("{id}"), ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> DeleteAsync(int id)
{
_logger.LogInformation($"Deleting service [{id}]...");
await _service.DeleteServiceAsync(id);
await _hubContext.Clients.All.NotifyUpdatedAsync();
return NoContent();
}

View File

@ -2,7 +2,7 @@
namespace Birdmap.Models
{
public class AuthenticateRequest
public record AuthenticateRequest
{
[Required(AllowEmptyStrings = false, ErrorMessage = "Username is required.")]
public string Username { get; set; }

View File

@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Birdmap.API.DTOs
{
public class AuthenticateResponse
public record AuthenticateResponse
{
[JsonProperty("user_name")]
public string Username { get; set; }

View File

@ -2,7 +2,7 @@
namespace Birdmap.API.DTOs
{
public class RegisterRequest
public record RegisterRequest
{
[Required(AllowEmptyStrings = false, ErrorMessage = "Username is required.")]
public string Username { get; set; }

View File

@ -2,7 +2,7 @@
namespace Birdmap.API.DTOs
{
public class ServiceInfo
public record ServiceInfo
{
public ServiceRequest Service { get; set; }
public HttpStatusCode StatusCode { get; set; }

View File

@ -1,6 +1,6 @@
namespace Birdmap.API.DTOs
{
public class ServiceRequest
public record ServiceRequest
{
public int Id { get; set; }
public string Name { get; set; }

View File

@ -1,6 +1,6 @@
using Birdmap.API;
using Birdmap.DAL;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@ -8,7 +8,7 @@ using NLog;
using NLog.Web;
using System;
namespace Birdmap
namespace Birdmap.API
{
public class Program
{
@ -40,6 +40,10 @@ namespace Birdmap
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddEnvironmentVariables(prefix: "Birdmap_");
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
@ -54,8 +58,8 @@ namespace Birdmap
private static void SeedDatabase(IHost host)
{
using var scope = host.Services.CreateScope();
var dbInitializer = scope.ServiceProvider.GetRequiredService<DbInitializer>();
var dbInitializer = scope.ServiceProvider.GetRequiredService<DbInitializer>();
dbInitializer.Initialize();
}
}

View File

@ -1,7 +1,11 @@
{
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iis": {
"applicationUrl": "http://localhost/Birdmap.API",
"sslPort": 0
},
"iisExpress": {
"applicationUrl": "http://localhost:63288",
"sslPort": 44331
@ -12,16 +16,24 @@
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"Birdmap_LocalDbConnectionString": "Data Source=DESKTOP-3600;Initial Catalog=birdmap2;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Birdmap": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"publishAllPorts": true,
"useSSL": true
}
}
}

View File

@ -1,7 +1,7 @@
using AutoMapper;
using Birdmap.API;
using Birdmap.API.Middlewares;
using Birdmap.BLL;
using Birdmap.BLL.Services.CommunicationServices.Hubs;
using Birdmap.DAL;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
@ -11,9 +11,11 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using NSwag.Generation.Processors.Security;
using System;
using System.Text;
namespace Birdmap
namespace Birdmap.API
{
public class Startup
{
@ -48,7 +50,7 @@ namespace Birdmap
})
.AddJwtBearer(opt =>
{
// opt.RequireHttpsMetadata = false;
//opt.RequireHttpsMetadata = false;
opt.SaveToken = true;
opt.IncludeErrorDetails = true;
opt.TokenValidationParameters = new TokenValidationParameters
@ -65,6 +67,20 @@ namespace Birdmap
{
configuration.RootPath = "ClientApp/build";
});
services.AddSwaggerDocument(opt =>
{
opt.Title = "Birdmap";
opt.OperationProcessors.Add(new OperationSecurityScopeProcessor("Jwt Token"));
opt.AddSecurity("Jwt Token", Array.Empty<string>(),
new NSwag.OpenApiSecurityScheme
{
Type = NSwag.OpenApiSecuritySchemeType.ApiKey,
Name = "Authorization",
In = NSwag.OpenApiSecurityApiKeyLocation.Header,
Description = "Bearer {token}",
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -74,24 +90,25 @@ namespace Birdmap
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseMiddleware<ExceptionHandlerMiddleware>();
}
app.UseHttpsRedirection();
app.UseMiddleware<ExceptionHandlerMiddleware>();
app.UseOpenApi();
app.UseSwaggerUi3();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health");
endpoints.MapControllers();
endpoints.MapHub<DevicesHub>("/hubs/devices");
endpoints.MapHub<ServicesHub>("/hubs/services");
});
app.UseSpa(spa =>

View File

@ -5,5 +5,51 @@
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Kestrel": {
"Certificates": {
"Default": {
"Password": "certpass123",
"Path": "C:\\Users\\Ricsi\\AppData\\Roaming\\ASP.NET\\Https\\aspnetapp.pfx"
}
}
},
"AllowedHosts": "*",
"Secret": "7vj.3KW.hYE!}4u6",
// "LocalDbConnectionString": "Data Source=DESKTOP-3600\\SQLEXPRESS;Initial Catalog=birdmap;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"LocalDbConnectionString": "Data Source=DESKTOP-3600;Initial Catalog=birdmap2;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"Defaults": {
"Services": {
"Local Database": "https://localhost:44331/health",
"KMLabz Services": "https://birb.k8s.kmlabz.com/devices"
},
"Users": [
{
"Name": "admin",
"Password": "pass",
"Role": "Admin"
},
{
"Name": "user",
"Password": "pass",
"Role": "User"
}
]
},
"UseDummyServices": true,
"ServicesBaseUrl": "https://birb.k8s.kmlabz.com/",
"UseRabbitMq": false,
"Mqtt": {
"BrokerHostSettings": {
"Host": "localhost",
"Port": 1883
},
"ClientSettings": {
"Id": "ASP.NET Core client",
"Username": "username",
"Password": "password",
"Topic": "devices/output"
}
}
}

View File

@ -6,27 +6,51 @@
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Kestrel": {
"Certificates": {
"Default": {
"Password": "",
"Path": ""
}
}
},
"AllowedHosts": "*",
"Secret": "7vj.3KW.hYE!}4u6",
// "LocalDbConnectionString": "Data Source=DESKTOP-3600\\SQLEXPRESS;Initial Catalog=birdmap;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"LocalDbConnectionString": "Data Source=DESKTOP-3600;Initial Catalog=birdmap;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
"Secret": "",
"LocalDbConnectionString": "",
"Defaults": {
"Services": {
"Local Database": "https://localhost:44331/health",
"KMLabz Services": "https://birb.k8s.kmlabz.com/devices"
},
"Users": [
{
"Name": "admin",
"Password": "pass",
"Role": "Admin"
"Users": []
},
{
"Name": "user",
"Password": "pass",
"Role": "User"
"UseDummyServices": false,
"ServicesBaseUrl": "https://birb.k8s.kmlabz.com/",
"UseRabbitMq": false,
"Mqtt": {
"BrokerHostSettings": {
"VirtualHost": "",
"Host": "",
"Port": 1883
},
"ExchangeSettings": {
"Name": "",
"Type": "",
"Durable": false,
"AutoDelete": false
},
"QueueSettings": {
"Name": "",
"Durable": false,
"Exclusive": false,
"AutoDelete": false
},
"ClientSettings": {
"Id": "ASP.NET Core client",
"Username": "",
"Password": "",
"Topic": ""
}
}
]
},
"UseDummyServices": true
}

5
Birdmap.API/libman.json Normal file
View File

@ -0,0 +1,5 @@
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}

View File

@ -3,7 +3,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Info"
internalLogFile="${basedir}Log/internal-nlog.txt"
internalLogFile="${basedir}Logs/internal-nlog.txt"
throwConfigExceptions="true">
<!-- enable asp.net core layout renderers -->
@ -14,22 +14,36 @@
<!-- the targets to write to -->
<targets async="true">
<default-target-parameters xsi:type="File" keepFileOpen="false" maxArchiveFiles="10" archiveAboveSize="1048576"/>
<target xsi:type="File" name="allfile" fileName="${basedir}Log/birdmap-all-${shortdate}.log"
layout="${longdate} [${event-properties:item=EventId_Id}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
<target xsi:type="File" name="allFile" fileName="${basedir}Logs/birdmap-all-${shortdate}.log"
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
<target xsi:type="File" name="mqttFile" fileName="${basedir}Logs/birdmap-mqtt-${shortdate}.log"
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
<target xsi:type="File" name="hubsFile" fileName="${basedir}Logs/birdmap-hubs-${shortdate}.log"
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
<!-- another file log, only own logs. Uses some ASP.NET core renderers -->
<target xsi:type="File" name="ownFile" fileName="${basedir}Log/birdmap-own-${shortdate}.log"
layout="${longdate} [${event-properties:item=EventId_Id}] ${uppercase:${level}} ${callsite} - ${message} ${exception:format=tostring} (url: ${aspnet-request-url})(action: ${aspnet-mvc-action})" />
<target xsi:type="File" name="ownFile" fileName="${basedir}Logs/birdmap-own-${shortdate}.log"
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${callsite} - ${message} ${exception:format=tostring} (url: ${aspnet-request-url})(action: ${aspnet-mvc-action})" />
</targets>
<!-- rules to map from logger name to target +-->
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="allfile" />
<logger name="*" minlevel="Trace" writeTo="allFile" />
<!--Skip non-critical Microsoft logs and so log only own logs-->
<!--Skip non-critical Mqtt logs-->
<logger name="*.*Mqtt*.*" minlevel="Trace" maxlevel="Warning" writeTo="mqttFile" final="true"/>
<logger name="*.*RabbitMq*.*" minlevel="Trace" maxlevel="Warning" writeTo="mqttFile" final="true"/>
<logger name="*.*CommunicationServiceBase*.*" minlevel="Trace" maxlevel="Warning" writeTo="mqttFile" final="true"/>
<!--Skip non-critical Hub logs-->
<logger name="*.*Hubs*.*" minlevel="Trace" maxlevel="Warning" writeTo="hubsFile" final="true"/>
<!--Skip non-critical Microsoft logs-->
<logger name="Microsoft.*" maxlevel="Info" final="true" />
<!-- BlackHole without writeTo -->
<logger name="*" minlevel="Trace" writeTo="ownFile" />
</rules>
</nlog>

View File

@ -1,9 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0" />
<PackageReference Include="MQTTnet" Version="3.0.13" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="RabbitMQ.Client" Version="6.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Birdmap.Common\Birdmap.Common.csproj" />
<ProjectReference Include="..\Birdmap.DAL\Birdmap.DAL.csproj" />

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Birdmap.BLL.Helpers
{
public static class IEnumerableExtensions
{
public static TSource RandomElementAt<TSource>(this IEnumerable<TSource> source, Random random = null)
{
random ??= new Random();
return source.ElementAt(random.Next(source.Count()));
}
}
}

View File

@ -0,0 +1,9 @@
using Microsoft.Extensions.Hosting;
namespace Birdmap.BLL.Interfaces
{
public interface ICommunicationService : IHostedService
{
public bool IsConnected { get; }
}
}

View File

@ -0,0 +1,7 @@
namespace Birdmap.BLL.Interfaces
{
public interface ICommunicationServiceProvider
{
public ICommunicationService Service { get; }
}
}

View File

@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Birdmap.BLL.Interfaces
{
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0))")]
public partial interface IInputService
{
/// <summary>Get input object by ID</summary>
/// <param name="tagID">ID of input object file</param>
/// <returns>input object</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
System.Threading.Tasks.Task<InputSingeResponse> GetInputAsync(System.Guid tagID);
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <summary>Get input object by ID</summary>
/// <param name="tagID">ID of input object file</param>
/// <returns>input object</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
System.Threading.Tasks.Task<InputSingeResponse> GetInputAsync(System.Guid tagID, System.Threading.CancellationToken cancellationToken);
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.2.1.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class InputSingeResponse
{
[Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string Status { get; set; }
[Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required]
public InputObject Message { get; set; } = new InputObject();
private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>();
[Newtonsoft.Json.JsonExtensionData]
public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
{
get { return _additionalProperties; }
set { _additionalProperties = value; }
}
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.2.1.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class InputResponse : System.Collections.ObjectModel.Collection<InputObject>
{
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.2.1.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class InputObject
{
[Newtonsoft.Json.JsonProperty("tag", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public System.Guid Tag { get; set; }
[Newtonsoft.Json.JsonProperty("date", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
[Newtonsoft.Json.JsonConverter(typeof(DateFormatConverter))]
public System.DateTimeOffset Date { get; set; }
[Newtonsoft.Json.JsonProperty("device_id", Required = Newtonsoft.Json.Required.Always)]
public Guid Device_id { get; set; }
private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>();
[Newtonsoft.Json.JsonExtensionData]
public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
{
get { return _additionalProperties; }
set { _additionalProperties = value; }
}
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.2.1.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class ApiResponse
{
[Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string Status { get; set; }
[Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string Message { get; set; }
private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>();
[Newtonsoft.Json.JsonExtensionData]
public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
{
get { return _additionalProperties; }
set { _additionalProperties = value; }
}
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.2.1.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class Description
{
[Newtonsoft.Json.JsonProperty("deviceid", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Deviceid { get; set; }
[Newtonsoft.Json.JsonProperty("date", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
[Newtonsoft.Json.JsonConverter(typeof(DateFormatConverter))]
public System.DateTimeOffset Date { get; set; }
private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>();
[Newtonsoft.Json.JsonExtensionData]
public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
{
get { return _additionalProperties; }
set { _additionalProperties = value; }
}
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.2.1.0 (Newtonsoft.Json v12.0.0.0)")]
internal class DateFormatConverter : Newtonsoft.Json.Converters.IsoDateTimeConverter
{
public DateFormatConverter()
{
DateTimeFormat = "yyyy-MM-dd";
}
}
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0))")]
public partial class FileParameter
{
public FileParameter(System.IO.Stream data)
: this(data, null, null)
{
}
public FileParameter(System.IO.Stream data, string fileName)
: this(data, fileName, null)
{
}
public FileParameter(System.IO.Stream data, string fileName, string contentType)
{
Data = data;
FileName = fileName;
ContentType = contentType;
}
public System.IO.Stream Data { get; private set; }
public string FileName { get; private set; }
public string ContentType { get; private set; }
}
}

View File

@ -0,0 +1,13 @@
using Microsoft.Extensions.Hosting;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Receiving;
namespace Birdmap.BLL.Interfaces
{
public interface IMqttClientService : IMqttClientConnectedHandler,
IMqttClientDisconnectedHandler,
IMqttApplicationMessageReceivedHandler
{
}
}

View File

@ -6,6 +6,7 @@ namespace Birdmap.BLL.Interfaces
{
public interface IServiceService
{
Task<int> GetServiceCountAsync();
Task<List<Service>> GetAllServicesAsync();
Task<Service> GetServiceAsync(int id);
Task<Service> CreateServiceAsync(Service service);

View File

@ -0,0 +1,22 @@
using MQTTnet.Client.Options;
using System;
namespace Birdmap.BLL.Options
{
public class MqttClientOptions : MqttClientOptionsBuilder
{
public IServiceProvider ServiceProvider { get; }
public MqttClientOptions(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public MqttClientOptions WithTopic(string topic)
{
WithUserProperty("Topic", topic);
return this;
}
}
}

View File

@ -0,0 +1,11 @@
namespace Birdmap.BLL.Options
{
public record RabbitMqClientOptions(
string Hostname, int Port, string VirtualHost,
string Username, string Password,
string ExchangeName, string ExchangeType,
bool ExchangeDurable, bool ExchangeAutoDelete,
string QueueName,
bool QueueDurable, bool QueueAutoDelete, bool QueueExclusive,
string Topic);
}

View File

@ -0,0 +1,92 @@
using Birdmap.BLL.Interfaces;
using Birdmap.BLL.Services.CommunicationServices.Hubs;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Timer = System.Timers.Timer;
namespace Birdmap.BLL.Services.CommunationServices
{
internal class Payload
{
[JsonProperty("tag")]
public Guid TagID { get; set; }
[JsonProperty("probability")]
public double Probability { get; set; }
}
internal abstract class CommunicationServiceBase : ICommunicationService
{
protected readonly ILogger _logger;
protected readonly IInputService _inputService;
protected readonly IHubContext<DevicesHub, IDevicesHubClient> _hubContext;
private readonly Timer _hubTimer;
private readonly List<Message> _messages = new();
private readonly object _messageLock = new();
public abstract bool IsConnected { get; }
public CommunicationServiceBase(ILogger logger, IInputService inputService, IHubContext<DevicesHub, IDevicesHubClient> hubContext)
{
_logger = logger;
_inputService = inputService;
_hubContext = hubContext;
_hubTimer = new Timer()
{
AutoReset = true,
Interval = 1000,
};
_hubTimer.Elapsed += SendMqttMessagesWithSignalR;
}
protected async Task ProcessJsonMessageAsync(string json)
{
try
{
var payload = JsonConvert.DeserializeObject<Payload>(json);
var inputResponse = await _inputService.GetInputAsync(payload.TagID);
lock (_messageLock)
{
_messages.Add(new Message(inputResponse.Message.Device_id, inputResponse.Message.Date.UtcDateTime, payload.Probability));
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Could not handle application message.");
}
}
protected void StartMessageTimer()
{
_hubTimer.Start();
}
protected void StopMessageTimer()
{
_hubTimer.Stop();
}
private void SendMqttMessagesWithSignalR(object sender, System.Timers.ElapsedEventArgs e)
{
lock (_messageLock)
{
if (_messages.Any())
{
_logger.LogInformation($"Sending ({_messages.Count}) messages: {string.Join(" | ", _messages)}");
_hubContext.Clients.All.NotifyMessagesAsync(_messages);
_messages.Clear();
}
}
}
public abstract Task StartAsync(CancellationToken cancellationToken);
public abstract Task StopAsync(CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,14 @@
using Birdmap.BLL.Interfaces;
namespace Birdmap.BLL.Services.CommunicationServices
{
internal class CommunicationServiceProvider : ICommunicationServiceProvider
{
public ICommunicationService Service { get; }
public CommunicationServiceProvider(ICommunicationService service)
{
Service = service;
}
}
}

View File

@ -0,0 +1,31 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace Birdmap.BLL.Services.CommunicationServices.Hubs
{
public class DevicesHub : Hub<IDevicesHubClient>
{
private readonly ILogger<DevicesHub> _logger;
public DevicesHub(ILogger<DevicesHub> logger)
{
_logger = logger;
}
public override Task OnConnectedAsync()
{
_logger.LogInformation("Devices Hub Client connected.");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
_logger.LogInformation("Devices Hub Client disconnected.");
return base.OnDisconnectedAsync(exception);
}
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Birdmap.BLL.Services.CommunicationServices.Hubs
{
public record Message(Guid DeviceId, DateTime Date, double Probability);
public interface IDevicesHubClient
{
Task NotifyMessagesAsync(IEnumerable<Message> messages);
Task NotifyDeviceUpdatedAsync(Guid deviceId);
Task NotifyAllUpdatedAsync();
}
}

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Birdmap.BLL.Services.CommunicationServices.Hubs
{
public interface IServicesHubClient
{
Task NotifyUpdatedAsync();
}
}

View File

@ -0,0 +1,31 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace Birdmap.BLL.Services.CommunicationServices.Hubs
{
public class ServicesHub : Hub<IServicesHubClient>
{
private readonly ILogger<ServicesHub> _logger;
public ServicesHub(ILogger<ServicesHub> logger)
{
_logger = logger;
}
public override Task OnConnectedAsync()
{
_logger.LogInformation("Services Hub Client connected.");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
_logger.LogInformation("Services Hub Client disconnected.");
return base.OnDisconnectedAsync(exception);
}
}
}

View File

@ -0,0 +1,121 @@
using Birdmap.BLL.Interfaces;
using Birdmap.BLL.Services.CommunationServices;
using Birdmap.BLL.Services.CommunicationServices.Hubs;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Birdmap.BLL.Services.CommunicationServices.Mqtt
{
internal class MqttClientService : CommunicationServiceBase, IMqttClientService
{
private readonly IMqttClient _mqttClient;
private readonly IMqttClientOptions _options;
public override bool IsConnected => _mqttClient.IsConnected;
public MqttClientService(IMqttClientOptions options, ILogger<MqttClientService> logger, IInputService inputService, IHubContext<DevicesHub, IDevicesHubClient> hubContext)
: base(logger, inputService, hubContext)
{
_options = options;
_mqttClient = new MqttFactory().CreateMqttClient();
ConfigureMqttClient();
}
private void ConfigureMqttClient()
{
_mqttClient.ConnectedHandler = this;
_mqttClient.DisconnectedHandler = this;
_mqttClient.ApplicationMessageReceivedHandler = this;
}
public Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{
var message = eventArgs.ApplicationMessage.ConvertPayloadToString();
_logger.LogDebug($"Recieved [{eventArgs.ClientId}] " +
$"Topic: {eventArgs.ApplicationMessage.Topic} | Payload: {message} | QoS: {eventArgs.ApplicationMessage.QualityOfServiceLevel} | Retain: {eventArgs.ApplicationMessage.Retain}");
return ProcessJsonMessageAsync(message);
}
public async Task HandleConnectedAsync(MqttClientConnectedEventArgs eventArgs)
{
try
{
var topic = _options.UserProperties.SingleOrDefault(up => up.Name == "Topic")?.Value;
_logger.LogInformation($"Connected. Auth result: {eventArgs.AuthenticateResult}. Subscribing to topic: {topic}");
await _mqttClient.SubscribeAsync(topic);
StartMessageTimer();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Cannot subscribe...");
}
}
public async Task HandleDisconnectedAsync(MqttClientDisconnectedEventArgs eventArgs)
{
_logger.LogDebug(eventArgs.Exception, $"Disconnected. Reason {eventArgs.ReasonCode}. Auth result: {eventArgs.AuthenticateResult}. Reconnecting...");
await Task.Delay(TimeSpan.FromSeconds(5));
try
{
StopMessageTimer();
await _mqttClient.ConnectAsync(_options, CancellationToken.None);
}
catch (Exception ex)
{
_logger.LogDebug(ex, $"Reconnect failed...");
}
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
try
{
await _mqttClient.ConnectAsync(_options);
if (!_mqttClient.IsConnected)
{
await _mqttClient.ReconnectAsync();
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Cannot connect...");
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
try
{
if (cancellationToken.IsCancellationRequested)
{
var disconnectOption = new MqttClientDisconnectOptions
{
ReasonCode = MqttClientDisconnectReason.NormalDisconnection,
ReasonString = "NormalDiconnection"
};
await _mqttClient.DisconnectAsync(disconnectOption, cancellationToken);
}
await _mqttClient.DisconnectAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Cannot disconnect...");
}
}
}
}

View File

@ -0,0 +1,114 @@
using Birdmap.BLL.Interfaces;
using Birdmap.BLL.Options;
using Birdmap.BLL.Services.CommunicationServices.Hubs;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Birdmap.BLL.Services.CommunationServices.RabbitMq
{
internal class RabbitMqClientService : CommunicationServiceBase
{
private IConnection _connection;
private IModel _channel;
private readonly IConnectionFactory _factory;
private readonly RabbitMqClientOptions _options;
public override bool IsConnected => _connection.IsOpen;
public RabbitMqClientService(RabbitMqClientOptions options, ILogger<RabbitMqClientService> logger, IInputService inputService, IHubContext<DevicesHub, IDevicesHubClient> hubContext)
: base(logger, inputService, hubContext)
{
_options = options;
_factory = new ConnectionFactory()
{
HostName = options.Hostname,
Port = options.Port,
UserName = options.Username,
Password = options.Password,
AutomaticRecoveryEnabled = true,
};
}
private Task OnRecieved(object sender, BasicDeliverEventArgs eventArgs)
{
var props = eventArgs.BasicProperties;
var body = Encoding.UTF8.GetString(eventArgs.Body.ToArray());
_logger.LogDebug($"Recieved [{props.UserId}] " +
$"ConsumerTag: {eventArgs.ConsumerTag} | DeliveryTag: {eventArgs.DeliveryTag} | Payload: {body} | Priority: {props.Priority}");
return ProcessJsonMessageAsync(body);
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
try
{
Connect();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Cannot connect. Reconnecting...");
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
await StartAsync(cancellationToken);
}
}
public override Task StopAsync(CancellationToken cancellationToken)
{
try
{
StopMessageTimer();
_channel?.Close();
_connection?.Close();
return Task.CompletedTask;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Cannot disconnect...");
return Task.FromException(ex);
}
}
private void Connect()
{
_connection = _factory.CreateConnection();
_channel = _connection.CreateModel();
_channel.ExchangeDeclare(
exchange: _options.ExchangeName,
type: _options.ExchangeType,
durable: _options.ExchangeDurable,
autoDelete: _options.ExchangeAutoDelete);
_channel.QueueDeclare(
queue: _options.QueueName,
durable: _options.QueueDurable,
exclusive: _options.QueueExclusive,
autoDelete: _options.QueueAutoDelete);
_channel.QueueBind(queue: _options.QueueName,
exchange: _options.ExchangeName,
routingKey: _options.Topic);
var consumer = new AsyncEventingBasicConsumer(_channel);
consumer.Received += OnRecieved;
_channel.BasicConsume(queue: _options.QueueName,
autoAck: true,
consumer: consumer);
StartMessageTimer();
}
}
}

View File

@ -6,8 +6,12 @@ using System.Threading.Tasks;
namespace Birdmap.BLL.Services
{
public abstract class DeviceServiceBase : IDeviceService
public abstract class DeviceAndInputServiceBase : IDeviceService, IInputService
{
public virtual Task<InputSingeResponse> GetInputAsync(Guid tagID)
=> GetInputAsync(tagID, CancellationToken.None);
public abstract Task<InputSingeResponse> GetInputAsync(Guid tagID, CancellationToken cancellationToken);
public virtual Task<ICollection<Device>> GetallAsync()
=> GetallAsync(CancellationToken.None);
public abstract Task<ICollection<Device>> GetallAsync(CancellationToken cancellationToken);

View File

@ -1,4 +1,5 @@
using Birdmap.BLL.Interfaces;
using Birdmap.BLL.Helpers;
using Birdmap.BLL.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
@ -7,48 +8,55 @@ using System.Threading.Tasks;
namespace Birdmap.BLL.Services
{
public class DummyDeviceService : DeviceServiceBase
public class DummyDeviceAndInputService : DeviceAndInputServiceBase
{
private const int numberOfDevices = 15;
private const double centerLong = 21.469640;
private const double centerLat = 48.275939;
private const double radius = 0.000200;
private const double radius = 0.001;
private static readonly Random Rand = new();
private static readonly Lazy<ICollection<Device>> Devices = new(GenerateDevices);
private static readonly Dictionary<Guid, InputSingeResponse> TagToInput = new();
private static readonly object InputLock = new();
private readonly Lazy<ICollection<Device>> _devices = new Lazy<ICollection<Device>>(GenerateDevices);
private static ListOfDevices GenerateDevices()
{
var devices = new ListOfDevices();
var rand = new Random();
T GetRandomEnum<T>()
{
var values = Enum.GetValues(typeof(T));
return (T)values.GetValue(rand.Next(values.Length));
return (T)values.GetValue(Rand.Next(values.Length));
}
double GetPlusMinus(double center, double radius)
{
return center - radius + rand.NextDouble() * radius * 2;
return center - radius + Rand.NextDouble() * radius * 2;
}
for (int d = 0; d < 15; d++)
for (int d = 0; d < numberOfDevices; d++)
{
var sensors = new ArrayofSensors();
for (int s = 0; s < rand.Next(1, 5); s++)
for (int s = 0; s < Rand.Next(1, 6); s++)
{
sensors.Add(new Sensor
sensors.Add(new()
{
Id = Guid.NewGuid(),
Status = GetRandomEnum<SensorStatus>(),
});
}
devices.Add(new Device
devices.Add(new()
{
Id = Guid.NewGuid(),
Sensors = sensors,
Status = GetRandomEnum<DeviceStatus>(),
Url = "dummyservice.device.url",
Coordinates = new Coordinates
Coordinates = new()
{
Latitude = GetPlusMinus(centerLat, radius),
Longitude = GetPlusMinus(centerLong, radius),
@ -61,17 +69,17 @@ namespace Birdmap.BLL.Services
public override Task<ICollection<Device>> GetallAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_devices.Value);
return Task.FromResult(Devices.Value);
}
public override Task<Device> GetdeviceAsync(Guid deviceID, CancellationToken cancellationToken)
{
return Task.FromResult(_devices.Value.SingleOrDefault(d => d.Id == deviceID));
return Task.FromResult(Devices.Value.SingleOrDefault(d => d.Id == deviceID));
}
public override Task<Sensor> GetsensorAsync(Guid deviceID, Guid sensorID, CancellationToken cancellationToken)
{
return Task.FromResult(_devices.Value.SingleOrDefault(d => d.Id == deviceID)?.Sensors.SingleOrDefault(s => s.Id == sensorID));
return Task.FromResult(Devices.Value.SingleOrDefault(d => d.Id == deviceID)?.Sensors.SingleOrDefault(s => s.Id == sensorID));
}
public override Task OfflineallAsync(CancellationToken cancellationToken)
@ -114,7 +122,7 @@ namespace Birdmap.BLL.Services
private void SetStatus(DeviceStatus deviceStatus, SensorStatus sensorStatus)
{
foreach (var device in _devices.Value)
foreach (var device in Devices.Value)
{
device.Status = deviceStatus;
foreach (var sensor in device.Sensors)
@ -135,5 +143,29 @@ namespace Birdmap.BLL.Services
var sensor = GetsensorAsync(deviceId, sensorID).Result;
sensor.Status = status;
}
public override Task<InputSingeResponse> GetInputAsync(Guid tagID, CancellationToken cancellationToken)
{
lock (InputLock)
{
if (!TagToInput.TryGetValue(tagID, out var value))
{
value = new()
{
Status = "Dummy_OK",
Message = new()
{
Tag = tagID,
Date = DateTime.Now,
Device_id = Devices.Value.Where(d => d.Status == DeviceStatus.Online).RandomElementAt(Rand).Id,
}
};
TagToInput.TryAdd(tagID, value);
}
return Task.FromResult(value);
}
}
}
}

View File

@ -9,6 +9,7 @@
#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type '...' is never equal to 'null' of type '...?'"
namespace Birdmap.BLL.Services
{
@ -16,14 +17,15 @@ namespace Birdmap.BLL.Services
using System = global::System;
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0))")]
public partial class LiveDummyService : IDeviceService
public partial class LiveDeviceService : IDeviceService
{
private string _baseUrl = "https://birb.k8s.kmlabz.com";
private System.Net.Http.HttpClient _httpClient;
private System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings;
public LiveDummyService(System.Net.Http.HttpClient httpClient)
public LiveDeviceService(string baseUrl, System.Net.Http.HttpClient httpClient)
{
_baseUrl = baseUrl;
_httpClient = httpClient;
_settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
}
@ -895,6 +897,7 @@ namespace Birdmap.BLL.Services
}
}
#pragma warning restore 8703
#pragma warning restore 1591
#pragma warning restore 1573
#pragma warning restore 472

View File

@ -0,0 +1,480 @@
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type '...' is never equal to 'null' of type '...?'"
namespace Birdmap.BLL.Services
{
using Birdmap.BLL.Interfaces;
using System = global::System;
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.8.2.0 (NJsonSchema v10.2.1.0 (Newtonsoft.Json v12.0.0.0))")]
public partial class LiveInputService : IInputService
{
private string _baseUrl = "https://birb.k8s.kmlabz.com";
private System.Net.Http.HttpClient _httpClient;
private System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings;
public LiveInputService(string baseUrl, System.Net.Http.HttpClient httpClient)
{
_baseUrl = baseUrl;
_httpClient = httpClient;
_settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
}
private Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings()
{
var settings = new Newtonsoft.Json.JsonSerializerSettings();
UpdateJsonSerializerSettings(settings);
return settings;
}
public string BaseUrl
{
get { return _baseUrl; }
set { _baseUrl = value; }
}
protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } }
partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings);
partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url);
partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder);
partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
/// <summary>Get all stored input queries</summary>
/// <returns>Array of input objects</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public System.Threading.Tasks.Task<System.Collections.Generic.ICollection<InputObject>> GetallAsync()
{
return GetallAsync(System.Threading.CancellationToken.None);
}
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <summary>Get all stored input queries</summary>
/// <returns>Array of input objects</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public async System.Threading.Tasks.Task<System.Collections.Generic.ICollection<InputObject>> GetallAsync(System.Threading.CancellationToken cancellationToken)
{
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/sample");
var client_ = _httpClient;
var disposeClient_ = false;
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
request_.Method = new System.Net.Http.HttpMethod("GET");
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var disposeResponse_ = true;
try
{
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
var objectResponse_ = await ReadObjectResponseAsync<System.Collections.Generic.ICollection<InputObject>>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
return objectResponse_.Object;
}
else
if (status_ == 404)
{
var objectResponse_ = await ReadObjectResponseAsync<ApiResponse>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
throw new ApiException<ApiResponse>("No object matching filter", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
}
}
finally
{
if (disposeResponse_)
response_.Dispose();
}
}
}
finally
{
if (disposeClient_)
client_.Dispose();
}
}
/// <summary>uploads a sample into the system</summary>
/// <returns>successful operation</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public System.Threading.Tasks.Task<ApiResponse> UploadFileAsync(Description description, FileParameter file)
{
return UploadFileAsync(description, file, System.Threading.CancellationToken.None);
}
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <summary>uploads a sample into the system</summary>
/// <returns>successful operation</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public async System.Threading.Tasks.Task<ApiResponse> UploadFileAsync(Description description, FileParameter file, System.Threading.CancellationToken cancellationToken)
{
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/sample");
var client_ = _httpClient;
var disposeClient_ = false;
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
var boundary_ = System.Guid.NewGuid().ToString();
var content_ = new System.Net.Http.MultipartFormDataContent(boundary_);
content_.Headers.Remove("Content-Type");
content_.Headers.TryAddWithoutValidation("Content-Type", "multipart/form-data; boundary=" + boundary_);
if (description == null)
throw new System.ArgumentNullException("description");
else
{
content_.Add(new System.Net.Http.StringContent(ConvertToString(description, System.Globalization.CultureInfo.InvariantCulture)), "description");
}
if (file == null)
throw new System.ArgumentNullException("file");
else
{
var content_file_ = new System.Net.Http.StreamContent(file.Data);
if (!string.IsNullOrEmpty(file.ContentType))
content_file_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(file.ContentType);
content_.Add(content_file_, "file", file.FileName ?? "file");
}
request_.Content = content_;
request_.Method = new System.Net.Http.HttpMethod("POST");
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var disposeResponse_ = true;
try
{
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
var objectResponse_ = await ReadObjectResponseAsync<ApiResponse>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
return objectResponse_.Object;
}
else
if (status_ == 400)
{
var objectResponse_ = await ReadObjectResponseAsync<ApiResponse>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
throw new ApiException<ApiResponse>("JSON parse error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
if (status_ == 415)
{
var objectResponse_ = await ReadObjectResponseAsync<ApiResponse>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
throw new ApiException<ApiResponse>("Media type error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
if (status_ == 417)
{
var objectResponse_ = await ReadObjectResponseAsync<ApiResponse>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
throw new ApiException<ApiResponse>("JSON invalid schema", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
if (status_ == 469)
{
var objectResponse_ = await ReadObjectResponseAsync<ApiResponse>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
throw new ApiException<ApiResponse>("No file found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
if (status_ == 470)
{
var objectResponse_ = await ReadObjectResponseAsync<ApiResponse>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
throw new ApiException<ApiResponse>("Description missing", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
}
}
finally
{
if (disposeResponse_)
response_.Dispose();
}
}
}
finally
{
if (disposeClient_)
client_.Dispose();
}
}
/// <summary>Get input object by ID</summary>
/// <param name="tagID">ID of input object file</param>
/// <returns>input object</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public System.Threading.Tasks.Task<InputSingeResponse> GetInputAsync(System.Guid tagID)
{
return GetInputAsync(tagID, System.Threading.CancellationToken.None);
}
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <summary>Get input object by ID</summary>
/// <param name="tagID">ID of input object file</param>
/// <returns>input object</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public async System.Threading.Tasks.Task<InputSingeResponse> GetInputAsync(System.Guid tagID, System.Threading.CancellationToken cancellationToken)
{
if (tagID == null)
throw new System.ArgumentNullException("tagID");
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/sample/{tagID}");
urlBuilder_.Replace("{tagID}", System.Uri.EscapeDataString(ConvertToString(tagID, System.Globalization.CultureInfo.InvariantCulture)));
var client_ = _httpClient;
var disposeClient_ = false;
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
request_.Method = new System.Net.Http.HttpMethod("GET");
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var disposeResponse_ = true;
try
{
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
var objectResponse_ = await ReadObjectResponseAsync<InputSingeResponse>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
return objectResponse_.Object;
}
else
if (status_ == 404)
{
var objectResponse_ = await ReadObjectResponseAsync<ApiResponse>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
throw new ApiException<ApiResponse>("Tag not found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
}
}
finally
{
if (disposeResponse_)
response_.Dispose();
}
}
}
finally
{
if (disposeClient_)
client_.Dispose();
}
}
protected struct ObjectResponseResult<T>
{
public ObjectResponseResult(T responseObject, string responseText)
{
this.Object = responseObject;
this.Text = responseText;
}
public T Object { get; }
public string Text { get; }
}
public bool ReadResponseAsString { get; set; }
protected virtual async System.Threading.Tasks.Task<ObjectResponseResult<T>> ReadObjectResponseAsync<T>(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers)
{
if (response == null || response.Content == null)
{
return new ObjectResponseResult<T>(default(T), string.Empty);
}
if (ReadResponseAsString)
{
var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(responseText, JsonSerializerSettings);
return new ObjectResponseResult<T>(typedBody, responseText);
}
catch (Newtonsoft.Json.JsonException exception)
{
var message = "Could not deserialize the response body string as " + typeof(T).FullName + ".";
throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception);
}
}
else
{
try
{
using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (var streamReader = new System.IO.StreamReader(responseStream))
using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader))
{
var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings);
var typedBody = serializer.Deserialize<T>(jsonTextReader);
return new ObjectResponseResult<T>(typedBody, string.Empty);
}
}
catch (Newtonsoft.Json.JsonException exception)
{
var message = "Could not deserialize the response body stream as " + typeof(T).FullName + ".";
throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception);
}
}
}
private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value == null)
{
return null;
}
if (value is System.Enum)
{
var name = System.Enum.GetName(value.GetType(), value);
if (name != null)
{
var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
if (field != null)
{
var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute))
as System.Runtime.Serialization.EnumMemberAttribute;
if (attribute != null)
{
return attribute.Value != null ? attribute.Value : name;
}
}
return System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo));
}
}
else if (value is bool)
{
return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant();
}
else if (value is byte[])
{
return System.Convert.ToBase64String((byte[])value);
}
else if (value.GetType().IsArray)
{
var array = System.Linq.Enumerable.OfType<object>((System.Array)value);
return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo)));
}
var result = System.Convert.ToString(value, cultureInfo);
return (result is null) ? string.Empty : result;
}
}
}
#pragma warning restore 8703
#pragma warning restore 1591
#pragma warning restore 1573
#pragma warning restore 472
#pragma warning restore 114
#pragma warning restore 108

View File

@ -17,6 +17,11 @@ namespace Birdmap.BLL.Services
_context = context;
}
public Task<int> GetServiceCountAsync()
{
return _context.Services.CountAsync();
}
public async Task<Service> CreateServiceAsync(Service service)
{
_context.Services.Add(service);

View File

@ -1,7 +1,14 @@
using Birdmap.BLL.Interfaces;
using Birdmap.BLL.Options;
using Birdmap.BLL.Services;
using Birdmap.BLL.Services.CommunationServices.RabbitMq;
using Birdmap.BLL.Services.CommunicationServices;
using Birdmap.BLL.Services.CommunicationServices.Mqtt;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Net.Http;
namespace Birdmap.BLL
{
@ -14,10 +21,134 @@ namespace Birdmap.BLL
services.AddTransient<IServiceService, ServiceService>();
if (configuration.GetValue<bool>("UseDummyServices"))
services.AddTransient<IDeviceService, DummyDeviceService>();
{
services.AddTransient<IInputService, DummyDeviceAndInputService>();
services.AddTransient<IDeviceService, DummyDeviceAndInputService>();
}
else
services.AddTransient<IDeviceService, LiveDummyService>();
{
var baseUrl = configuration.GetValue<string>("ServicesBaseUrl");
services.AddTransient<IInputService, LiveInputService>(serviceProvider =>
{
var httpClient = serviceProvider.GetService<HttpClient>();
var service = new LiveInputService(baseUrl, httpClient);
return service;
});
services.AddTransient<IDeviceService, LiveDeviceService>(serviceProvider =>
{
var httpClient = serviceProvider.GetService<HttpClient>();
var service = new LiveDeviceService(baseUrl, httpClient);
return service;
});
}
services.AddSignalR();
var mqtt = configuration.GetSection("Mqtt");
var client = mqtt.GetSection("ClientSettings");
var clientSettings = new
{
Id = client.GetValue<string>("Id"),
Username = client.GetValue<string>("Username"),
Password = client.GetValue<string>("Password"),
Topic = client.GetValue<string>("Topic"),
};
var brokerHost = mqtt.GetSection("BrokerHostSettings");
var brokerHostSettings = new
{
Host = brokerHost.GetValue<string>("Host"),
Port = brokerHost.GetValue<int>("Port"),
VirtualHost = brokerHost.GetValue<string>("VirtualHost"),
};
var exchange = mqtt.GetSection("ExchangeSettings");
var exchangeSettings = new
{
Name = exchange.GetValue<string>("Name"),
Type = exchange.GetValue<string>("Type"),
Durable = exchange.GetValue<bool>("Durable"),
AutoDelete = exchange.GetValue<bool>("AutoDelete"),
};
var queue = mqtt.GetSection("QueueSettings");
var queueSettings = new
{
Name = queue.GetValue<string>("Name"),
Durable = exchange.GetValue<bool>("Durable"),
Exclusive = exchange.GetValue<bool>("Exclusive"),
AutoDelete = exchange.GetValue<bool>("AutoDelete"),
};
if (configuration.GetValue<bool>("UseRabbitMq"))
{
services.AddRabbitMqClientServiceWithConfig(new RabbitMqClientOptions(
Hostname: brokerHostSettings.Host,
Port: brokerHostSettings.Port,
VirtualHost: brokerHostSettings.VirtualHost,
Username: clientSettings.Username,
Password: clientSettings.Password,
ExchangeName: exchangeSettings.Name,
ExchangeType: exchangeSettings.Type,
ExchangeDurable: exchangeSettings.Durable,
ExchangeAutoDelete: exchangeSettings.AutoDelete,
QueueName: queueSettings.Name,
QueueDurable: queueSettings.Durable,
QueueExclusive: queueSettings.Exclusive,
QueueAutoDelete: queueSettings.AutoDelete,
Topic: clientSettings.Topic));
}
else
{
services.AddMqttClientServiceWithConfig(opt =>
{
opt
.WithTopic(clientSettings.Topic)
.WithCredentials(clientSettings.Username, clientSettings.Password)
.WithClientId(clientSettings.Id)
.WithTcpServer(brokerHostSettings.Host, brokerHostSettings.Port);
});
}
return services;
}
private static IServiceCollection AddMqttClientServiceWithConfig(this IServiceCollection services, Action<MqttClientOptions> configureOptions)
{
services.AddSingleton(serviceProvider =>
{
var optionBuilder = new MqttClientOptions(serviceProvider);
configureOptions(optionBuilder);
return optionBuilder.Build();
});
services.AddClientServiceWithProvider<MqttClientService>();
return services;
}
private static IServiceCollection AddRabbitMqClientServiceWithConfig(this IServiceCollection services, RabbitMqClientOptions options)
{
services.AddSingleton(options);
services.AddClientServiceWithProvider<RabbitMqClientService>();
return services;
}
private static IServiceCollection AddClientServiceWithProvider<T>(this IServiceCollection services) where T : class, ICommunicationService
{
services.AddSingleton<T>();
services.AddSingleton<IHostedService>(serviceProvider =>
{
return serviceProvider.GetService<T>();
});
services.AddSingleton<ICommunicationServiceProvider>(serviceProvider =>
{
var clientService = serviceProvider.GetService<T>();
var clientServiceProvider = new CommunicationServiceProvider(clientService);
return clientServiceProvider;
});
return services;
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -1,17 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.9">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.0" />
</ItemGroup>
<ItemGroup>

View File

@ -22,10 +22,17 @@ namespace Birdmap.DAL
public void Initialize()
{
EnsureCreated();
AddDefaultUsers();
AddDefaultServices();
}
private void EnsureCreated()
{
_logger.LogInformation("Ensuring database is created...");
_context.Database.EnsureCreated();
}
private void AddDefaultServices()
{
_logger.LogInformation("Removing previously added default services...");

View File

@ -2,7 +2,7 @@
namespace Birdmap.DAL.Entities
{
public class Service
public record Service
{
public int Id { get; set; }
public string Name { get; set; }

View File

@ -6,7 +6,7 @@
Admin,
}
public class User
public record User
{
public int Id { get; set; }
public string Name { get; set; }

View File

@ -9,7 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Birdmap.BLL", "Birdmap.BLL\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Birdmap.DAL", "Birdmap.DAL\Birdmap.DAL.csproj", "{543FAB06-B960-41A9-8865-1624A2ED2170}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Birdmap.Common", "Birdmap.Common\Birdmap.Common.csproj", "{CE96BAFA-A0FD-4010-8EF2-700451091F71}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Birdmap.Common", "Birdmap.Common\Birdmap.Common.csproj", "{CE96BAFA-A0FD-4010-8EF2-700451091F71}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.WinForm", "MQTTnet.TestApp.WinForm\MQTTnet.TestApp.WinForm.csproj", "{E1707FE7-4A65-42AC-B71C-6CC1A55FC42A}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{9443433B-1D13-41F0-B345-B36ACD15EF81}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -33,6 +37,14 @@ Global
{CE96BAFA-A0FD-4010-8EF2-700451091F71}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CE96BAFA-A0FD-4010-8EF2-700451091F71}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE96BAFA-A0FD-4010-8EF2-700451091F71}.Release|Any CPU.Build.0 = Release|Any CPU
{E1707FE7-4A65-42AC-B71C-6CC1A55FC42A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1707FE7-4A65-42AC-B71C-6CC1A55FC42A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1707FE7-4A65-42AC-B71C-6CC1A55FC42A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1707FE7-4A65-42AC-B71C-6CC1A55FC42A}.Release|Any CPU.Build.0 = Release|Any CPU
{9443433B-1D13-41F0-B345-B36ACD15EF81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9443433B-1D13-41F0-B345-B36ACD15EF81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9443433B-1D13-41F0-B345-B36ACD15EF81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9443433B-1D13-41F0-B345-B36ACD15EF81}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

27
Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
RUN apt-get update && apt-get install -y curl
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get update && apt-get install -y nodejs
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
RUN apt-get update && apt-get install -y curl
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get update && apt-get install -y nodejs
WORKDIR /src
COPY ["Birdmap.API/Birdmap.API.csproj", "Birdmap.API/"]
COPY ["Birdmap.BLL/Birdmap.BLL.csproj", "Birdmap.BLL/"]
COPY ["Birdmap.Common/Birdmap.Common.csproj", "Birdmap.Common/"]
COPY ["Birdmap.DAL/Birdmap.DAL.csproj", "Birdmap.DAL/"]
RUN dotnet restore "Birdmap.API/Birdmap.API.csproj"
COPY . .
WORKDIR "/src/Birdmap.API"
RUN dotnet build "Birdmap.API.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Birdmap.API.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Birdmap.API.dll"]

Some files were not shown because too many files have changed in this diff Show More