Compare commits

...

6 Commits

Author SHA1 Message Date
Jimmy Pellier 045d1a54a3 Création DockerFile 5 years ago
Jimmy Pellier 085c544587 Ajout dockerfile 5 years ago
Jimmy Pellier 4cb28e49de Fermeture websocket 5 years ago
Jimmy Pellier 04c5230b6d Gestion des images 5 years ago
Jimmy Pellier be9a12a9d8 Ajout variable d'environnement 5 years ago
Jimmy Pellier 1a8b9302d9 initialisation 5 years ago
  1. 1
      .env
  2. 8
      .env.development
  3. 8
      .env.production
  4. 21
      .gitignore
  5. 13
      DockerFile
  6. 29
      README.md
  7. 6
      babel.config.js
  8. 34
      css/global.css
  9. 296
      css/styles.css
  10. 1
      obj/Docker/CanonicalServiceNames.cache
  11. 12237
      package-lock.json
  12. 64
      package.json
  13. BIN
      public/favicon.ico
  14. 17
      public/index.html
  15. 10
      public/keycloak.json
  16. 59
      src/App.vue
  17. BIN
      src/assets/_logo.png
  18. BIN
      src/assets/coviiiiiid.jpg
  19. BIN
      src/assets/fever_ko.gif
  20. BIN
      src/assets/fever_ok.jpg
  21. BIN
      src/assets/logo.png
  22. BIN
      src/assets/out_of_range.jpg
  23. BIN
      src/assets/waiting.jpg
  24. 91
      src/components/Capture/CapturePhoto.vue
  25. 160
      src/components/Menu.vue
  26. 271
      src/components/Temperature.vue
  27. 17
      src/components/TestChart.js
  28. 183
      src/components/User/CardUser.vue
  29. 228
      src/components/User/ListeUser.vue
  30. 328
      src/components/User/User.vue
  31. 41
      src/lang/messages.js
  32. 78
      src/main.js
  33. 33
      src/router/index.js
  34. 52
      src/services/TemperatureHub.js
  35. 50
      src/services/UserService.js
  36. 18
      src/services/axios_interceptor.js
  37. 6
      vue.config.js

@ -0,0 +1 @@
VUE_APP_BACK_URL=http://localhost:5000

@ -0,0 +1,8 @@
VUE_APP_BACK_URL=http://localhost/covid
VUE_APP_BACK_USER=http://localhost/user
VUE_APP_IMAGE_STORE=http://localhost/apache/public/uploads/
VUE_APP_TEMPERATURE_HUB=http://localhost/covid/batchExecutionHub
VUE_APP_TIMEOUT_TEMPERATURE_HUB=5000
VUE_APP_MSG_TEMPERATURE_HUB=temperature
VUE_APP_EMIT_MSG_TEMPERATURE=received-temperature
VUE_APP_MAX_CHART=30

@ -0,0 +1,8 @@
VUE_APP_BACK_URL=http://localhost/covid
VUE_APP_BACK_USER=http://localhost/user
VUE_APP_IMAGE_STORE=http://localhost/apache/public/uploads/
VUE_APP_TEMPERATURE_HUB=http://localhost/covid/batchExecutionHub
VUE_APP_TIMEOUT_TEMPERATURE_HUB=5000
VUE_APP_MSG_TEMPERATURE_HUB=temperature
VUE_APP_EMIT_MSG_TEMPERATURE=received-temperature
VUE_APP_MAX_CHART=30

21
.gitignore vendored

@ -0,0 +1,21 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,13 @@
# étape de build
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# étape de production
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

@ -1,2 +1,29 @@
# TemperatureCheckerFront # clientapp
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Run your tests
```
npm run test
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

@ -0,0 +1,6 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
}

@ -0,0 +1,34 @@
.corps {
height: 80vh;
display: block;
position: relative;
}
.bg-passageOk
{
background-color: seagreen;
}
.corps::after {
content: "";
background-repeat:no-repeat;
background-position:center center;
background-attachment:fixed;
background-size: 50vh auto;
background-image: url("../src/assets/logo.png");
opacity: 0.5;
top: 0;
left: 0;
bottom: 0;
right: 0;
position: absolute;
z-index: -1;
}
.fade-enter-active{
transition: opacity .5s;
}
.fade-enter /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}

@ -0,0 +1,296 @@
@charset "UTF-8";
@import url("https://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");
body {
background-color: #FFF;
margin: 0px;
font-family: Ubuntu, sans-serif;
color: #1e1e1e;
font-weight: normal;
padding-top: 0;
}
h1, h2, h3, h4 {
font-family: "Cabin Condensed", sans-serif;
}
header {
padding: 1em;
}
header .headline {
max-width: 640px;
margin: 0 auto;
}
header .headline h1 {
font-size: 3em;
margin-bottom: 0;
}
header .headline h2 {
margin-top: 0.2em;
}
footer {
padding: 1em 2em 2em;
}
#container {
width: 640px;
margin: 20px auto;
padding: 10px;
}
#interactive.viewport {
width: 640px;
height: 480px;
}
#interactive.viewport canvas, video {
float: left;
width: 640px;
height: 480px;
}
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -640px;
}
.controls fieldset {
border: none;
margin: 0;
padding: 5px;
}
.controls .input-group {
float: left;
}
.controls .input-group input, .controls .input-group button {
display: block;
}
.controls .reader-config-group {
float: right;
}
.controls .reader-config-group label {
display: block;
}
.controls .reader-config-group label span {
width: 9rem;
display: inline-block;
text-align: right;
}
.controls:after {
content: '';
display: block;
clear: both;
}
#result_strip {
margin: 10px 0;
border-top: 1px solid #EEE;
border-bottom: 1px solid #EEE;
padding: 10px 0;
}
#result_strip > ul {
padding: 0;
margin: 0;
list-style-type: none;
width: auto;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
}
#result_strip > ul > li {
display: inline-block;
vertical-align: middle;
width: 160px;
}
#result_strip > ul > li .thumbnail {
padding: 5px;
margin: 4px;
border: 1px dashed #CCC;
}
#result_strip > ul > li .thumbnail img {
max-width: 140px;
}
#result_strip > ul > li .thumbnail .caption {
white-space: normal;
}
#result_strip > ul > li .thumbnail .caption h4 {
text-align: center;
word-wrap: break-word;
height: 40px;
margin: 0px;
}
#result_strip > ul:after {
content: "";
display: table;
clear: both;
}
.scanner-overlay {
display: none;
width: 640px;
height: 510px;
position: absolute;
padding: 20px;
top: 50%;
margin-top: -275px;
left: 50%;
margin-left: -340px;
background-color: #FFF;
-moz-box-shadow: #333333 0px 4px 10px;
-webkit-box-shadow: #333333 0px 4px 10px;
box-shadow: #333333 0px 4px 10px;
}
.scanner-overlay > .header {
position: relative;
margin-bottom: 14px;
}
.scanner-overlay > .header h4, .scanner-overlay > .header .close {
line-height: 16px;
}
.scanner-overlay > .header h4 {
margin: 0px;
padding: 0px;
}
.scanner-overlay > .header .close {
position: absolute;
right: 0px;
top: 0px;
height: 16px;
width: 16px;
text-align: center;
font-weight: bold;
font-size: 14px;
cursor: pointer;
}
i.icon-24-scan {
width: 24px;
height: 24px;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzFFMjMzNTBFNjcwMTFFMkIzMERGOUMzMzEzM0E1QUMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzFFMjMzNTFFNjcwMTFFMkIzMERGOUMzMzEzM0E1QUMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDMUUyMzM0RUU2NzAxMUUyQjMwREY5QzMzMTMzQTVBQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDMUUyMzM0RkU2NzAxMUUyQjMwREY5QzMzMTMzQTVBQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtQr90wAAAUuSURBVHjanFVLbFRVGP7ua97T9DGPthbamAYYBNSMVbBpjCliWWGIEBMWsnDJxkh8RDeEDW5MDGticMmGBWnSlRSCwgLFNkqmmrRIqzjTznTazkxn5s7c6/efzm0G0Jhwkj/nP+d/nv91tIWFBTQaDQWapkGW67p4ltUub5qmAi0UCqF/a/U2m81tpmddotwwDGSz2dzi4uKSaOucnJycGhsbe1XXdQiIIcdxEAgEtgXq9brySHCht79UXi/8QheawN27d385fPjwuEl6XyKR6LdtW7t06RLK5TKOHj2K/fv3Q87Dw8OYn5/HiRMnMDs7i5mZGQwODiqlPp8PuVwO6XRaOXb16lXl1OnTp5FMJvtosF8M+MWLarWqGJaWlpBKpRRcu3YN4+PjmJ6exsTEhDJw5coVjI6OKgPhcBiZTAbxeBx+vx+XL19Gd3c3Tp48Ka9zqDYgBlTQxYNgMIhIJKLCILkQb+TZsgvdsiyFi+feWRR7oRNZyanQtvW2V4DEUUBiK2eJpeDirSyhCe7F2QPh8fiEp72i9PbsC5G52DbiKZA771yr1dTuGfJ4PQNPFoAyQNR1aNEmsS5eyB3PgjeooMZd2AWvNmzYci/Gea7TeFOcI93jV/K67noGmi4vdRI9gPSDeMLSdKUBZZczlWm1rTtHjLZ24d+WER2tc8N1m+Y+ID74wx0zGYvhg9UNrJdtHJyZRdQfwPsrq9g99xsGlgsYmr6BNzO/IVwsYfjBQ6XYz6JI/72MV366B5/lw0elOkJWGUM3bmKtWjXSLuLaBWhnPnnp0FfoiFi4+TMfVAb2poBkDLjO845uYLEAjL4ALGWBP5YAOsP4AJYBFDaB1HOSVWD2PuV95H2RdV93Lv74/cf6p6Zxq/h6OofeOPJBC39JtONdwOAAViOs4p4OFGTf0Uc8iiyrr9YdQrUnDLsngrVOC0jQib44HlF2RafRZBz1Qy+vfhgK3NJZBlrm+LEm9qWwzFgLU7Ozg0JxZP06jQSRpQ7EerAWDSt6PuhHPmChEAog56fCLvJT5hHTm3OZkz3DyLx7XNWTGEA1GkV14gjWgwbW0ESVjYRwCOuai03L5E7OUBAV4kXSS4auoGIaKOma4m8EA5R1sMEGLh95C+XuLph0WJWpxepYYLtfT0RRgY1KgNODY6BoaChRuEhDCIZQYseuki5KN6hcQHiq7OZNv4/Zq2O6P4Lfkwn46vZjjaYZrIpvWbpzjLErrc4xUGE4avRedpYJalRcIl5hQius/SrPm9xrNOQYJhao6BvNUeWqtY8KaWuNjHOFAr7mM9f4NA4UbKysoUJ8PV9UzVOx6wxDDWUOxnK1pmCD07fOMAvtIsM3l89Dl3HRGhVma9AZMqjOnz2LQqWCxs6dqr3T7x1DTzKJaG8SekcHhg4cgI/56uKdlKnBV/WndqN3YAB/7tyBd3oT6GBIOzs7kc/nDfFdDFT5bS73cp06dQoaPa/Rw/rtO/resTHxxE2m9rCrbSR27UJCcMf1BpiA5rAAGgdfc868fUR1sMwj0cm9Iu9IctweisViB3hhKTHDcHc5jv/LspbyaZrR1OD82/fIlOkuB9LnEWRmDX2TsddUPg3D5gvuc0je0rZaD5EW6G3yjS+A3eeBEWq3XW/Abw1HhUspXADufQb86oW7tZytkYCN//3hHwBvDALPi8EnSOYK8DAOfCc2h4aGcO7cuafkzampqf9UripH12/DtOZbx8ciVGzYy5OO40o25ascGRl5Ssc/AgwAjW3JwqIUjSYAAAAASUVORK5CYII=");
display: inline-block;
background-repeat: no-repeat;
line-height: 24px;
margin-top: 1px;
vertical-align: text-top;
}
@media (max-width: 603px) {
#container {
width: 300px;
margin: 10px auto;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
#container form.voucher-form input.voucher-code {
width: 180px;
}
}
@media (max-width: 603px) {
.reader-config-group {
width: 100%;
}
.reader-config-group label > span {
width: 50%;
}
.reader-config-group label > select, .reader-config-group label > input {
max-width: calc(50% - 2px);
}
#interactive.viewport {
width: 300px;
height: 300px;
overflow: hidden;
}
#interactive.viewport canvas, video {
margin-top: -50px;
width: 300px;
height: 400px;
}
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -300px;
}
#result_strip {
margin-top: 5px;
padding-top: 5px;
}
#result_strip ul.thumbnails > li {
width: 150px;
}
#result_strip ul.thumbnails > li .thumbnail .imgWrapper {
width: 130px;
height: 130px;
overflow: hidden;
}
#result_strip ul.thumbnails > li .thumbnail .imgWrapper img {
margin-top: -25px;
width: 130px;
height: 180px;
}
}
@media (max-width: 603px) {
.overlay.scanner {
width: 640px;
height: 510px;
padding: 20px;
margin-top: -275px;
margin-left: -340px;
background-color: #FFF;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
.overlay.scanner > .header {
margin-bottom: 14px;
}
.overlay.scanner > .header h4, .overlay.scanner > .header .close {
line-height: 16px;
}
.overlay.scanner > .header .close {
height: 16px;
width: 16px;
}
}

12237
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,64 @@
{
"name": "coviiiiid",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "node_modules/.bin/vue-cli-service serve",
"build": "node_modules/.bin/vue-cli-service build",
"lint": "node_modules/.bin/vue-cli-service lint"
},
"dependencies": {
"@aspnet/signalr": "^1.1.4",
"@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/vue-fontawesome": "^0.1.9",
"axios": "^0.19.2",
"bootstrap-vue": "^2.15.0",
"chart.js": "^2.9.3",
"commander": "^4.0.1",
"core-js": "^3.4.3",
"debounce": "^1.2.0",
"es6-promise": "^4.2.8",
"jquery": "^1.9.1",
"keycloak-js": "^10.0.2",
"nimble": "0.0.2",
"node-datetime": "^2.1.2",
"request-digest": "^1.0.13",
"socket.io": "^2.3.0",
"vue": "^2.6.10",
"vue-chartjs": "^3.5.0",
"vue-i18n": "^8.18.1",
"vue-infinite-loading": "^2.4.5",
"vue-js-toggle-button": "^1.3.3",
"vue-router": "^3.3.2",
"vuex": "^3.4.0",
"xml2js": "^0.4.22"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-service": "^4.1.0",
"babel-eslint": "^10.0.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.6.10"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>covid finder</title>
</head>
<body>
<noscript>
<strong>We're sorry but clientapp doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

@ -0,0 +1,10 @@
{
"realm": "Test",
"auth-server-url": "https://oauth.apsidetop-devel.net/auth",
"ssl-required": "external",
"resource": "test",
"credentials": {
"secret": "5cf010f0-b4a9-4db9-a9f2-102bc703c448"
},
"confidential-port": 0
}

@ -0,0 +1,59 @@
<template>
<b-container fluid id="app">
<Menu class="header" />
<div style="margin-top:60px">
<router-view > </router-view>
</div>
</b-container>
</template>
<script>
import Menu from './components/Menu.vue'
import Vue from 'vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faUserSecret } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
library.add(faUserSecret)
Vue.component('font-awesome-icon', FontAwesomeIcon)
// Install BootstrapVue
Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)
export default {
name: 'app',
components: {
Menu
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 0px;
padding-left: 0;
padding-right: 0
}
.header {
position:fixed; /* fixing the position takes it out of html flow - knows
nothing about where to locate itself except by browser
coordinates */
left:0; /* top left corner should start at leftmost spot */
top:0; /* top left corner should start at topmost spot */
width:100vw; /* take up the full browser width */
z-index:100; /* high z index so other content scrolls underneath */
height:50px; /* define height for content */
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@ -0,0 +1,91 @@
<template>
<div id="videoapp">
<b-button v-b-modal.modal-2 >
<font-awesome-icon icon="camera" />
{{$t('user_detail_photo')}}
</b-button>
<b-modal static size="xl" id="modal-2" :hide-footer="true" >
<template v-slot:modal-header>
Title
<b-button size="sm" variant="danger" @click="close()">
Cancel
</b-button>
</template>
<template v-slot:default>
<div>
<div><video ref="video" id="video" width="640" height="480" autoplay ></video></div>
<div><button id="snap" v-on:click="capture()">Snap Photo</button></div>
<canvas ref="canvas" id="canvas" width="640" height="480"></canvas>
<ul>
<li v-for="c in captures" v-bind:key="c.id">
<img v-bind:src="c.photo" alt="Photo" height="50" />
</li>
</ul>
</div>
</template>
</b-modal>
</div>
</template>
<script>
export default {
name: "CapturePhoto",
mounted() {
this.video = this.$refs.video;
let video = document.querySelector('video');
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
video.srcObject = stream
});
}
},
data () {
return {
video: {},
canvas: {},
compteur:0,
captures: [],
snapshot:{}
}
},
methods: {
close()
{
this.snapshot= null;
this.$bvModal.hide("modal-2");
},
capture() {
this.canvas = this.$refs.canvas;
this.canvas.getContext("2d").drawImage(this.video, 0, 0, 640, 480);
this.compteur++;
this.snapshot = {photo:this.canvas.toDataURL("image/png"), id:this.compteur++}
// this.captures.push(this.snapshot);
this.$emit('update:snapshot', this.snapshot);
this.$bvModal.hide("modal-2");
}
}
}
</script>
<style>
body: {
background-color: #F0F0F0;
}
/* #app {
text-align: center;
color: #2c3e50;
margin-top: 60px;
}*/
#video {
background-color: #000000;
}
#canvas {
display: none;
}
li {
display: inline;
padding: 5px;
}
</style>

@ -0,0 +1,160 @@
<template>
<div class="MenuNavBar">
<b-navbar toggleable="lg" type="dark" variant="info" style="min-height:60px; padding: 0px 0px 0px 0px">
<b-navbar-brand class="nav-link d-flex align-items-center justify-content-center" @click="doLogin">
<b-avatar variant="info" v-bind:src="img" size="3rem" >
</b-avatar>
</b-navbar-brand>
<b-navbar-brand class="nav-link d-flex align-items-center justify-content-center" to="/" >
{{$t('title')}}
</b-navbar-brand>
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
<b-collapse id="nav-collapse" is-nav>
<b-navbar-nav>
<b-nav-item v-if="isAdmin" active-class="active" class="nav-link d-flex align-items-center justify-content-center" to="/users" >{{$t('user_title')}}</b-nav-item>
<b-nav-item active-class="active" class="nav-link d-flex align-items-center justify-content-center" @click="logout" >{{$t('logout')}}</b-nav-item>
</b-navbar-nav>
</b-collapse>
</b-navbar>
</div>
</template>
<script>
import { BNavbar, BNavbarBrand, BNavbarToggle } from 'bootstrap-vue'
import { BAvatar } from 'bootstrap-vue'
import UserService from "../services/UserService";
import Vue from "vue";
import axios from "axios";
Vue.component('b-avatar', BAvatar)
export default {
name: "Menu",
components :
{
BNavbar,BNavbarBrand, BNavbarToggle, BAvatar
},
data:function(){
return {
}
},
computed :
{
user(){
return this.$store.state.user;
},
img()
{
return this.$store.state.user != null && this.$store.state.user.image != null ?
process.env.VUE_APP_IMAGE_STORE + this.$store.state.user.image :
'';
},
isAdmin() {
return this.$store.state.roles.find(l => l.name == 'Administrators' && l.isActived) != null;
},
isClient() {
return this.$store.state.roles.find(l => l.name == 'customer' && l.isActived) != null;
},
isLivreur() {
return this.$store.state.roles.find(l => l.name == 'delivery' && l.isActived) != null;
},
isManager() {
return this.$store.state.roles.find(l => l.name == 'manager' && l.isActived) != null;
},
},
mounted() {
let that = this;
UserService.initKeycloak( function () {
sessionStorage.setItem("react-token", UserService.getToken());
sessionStorage.setItem("react-refresh-token", UserService.getRefreshToken());
let axiosConfig = {
withCredentials: true,
headers: {
'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
};
axios.get(process.env.VUE_APP_BACK_USER + "/api/users/bykeycloakid?guid=" + UserService.getUsername().sub,
axiosConfig
).then(result => {
if( result.data.keycloakId !== null)
that.$store.commit("updateUser", result.data);
else
UserService.doLogout();
});
axios.get(process.env.VUE_APP_BACK_USER + "/api/roles/bykeycloakid?keycloakid=" + UserService.getUsername().sub,
axiosConfig
).then(result => {
that.$store.commit("updateRole", result.data);
});
},
function(){
that.$store.commit("updateUser", null);
that.$store.commit("updateRole", []);
if(that.$router.currentRoute.path !== "/")
that.$router.push({path:"/"});
});
},
methods : {
logout: function ()
{
UserService.doLogout();
sessionStorage.setItem("react-token", "");
sessionStorage.setItem("react-refresh-token", "");
},
doLogin : function()
{
let that = this;
UserService.initKeycloak( function () {
sessionStorage.setItem("react-token", UserService.getToken());
sessionStorage.setItem("react-refresh-token", UserService.getRefreshToken());
let axiosConfig = {
withCredentials: true,
headers: {
'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
};
axios.get(process.env.VUE_APP_BACK_USER + "/api/users/bykeycloakid?guid=" + UserService.getUsername().sub,
axiosConfig
).then(result => {
if( result.data.keycloakId !== null)
that.$store.commit("updateUser", result.data);
else
UserService.doLogout();
});
axios.get(process.env.VUE_APP_BACK_USER + "/api/roles/bykeycloakid?keycloakid=" + UserService.getUsername().sub,
axiosConfig
).then(result => {
that.$store.commit("updateRole", result.data);
});
},
UserService.doLogin())
}
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,271 @@
<template>
<b-container fluid id="temperature">
<b-row style="padding-top: 10px">
<b-col>
<b-avatar
v-if="!acquirementInprogress"
v-b-tooltip.hover :title="$t('sub_title')"
variant="info" :src="image_avatar"
size="10rem" style="margin-right: 5px"
>
</b-avatar>
<div><video
v-if="acquirementInprogress"
ref="video"
id="video" width="160" height="160" style="border-radius: 80px;"
:srcObject.prop="stream"
autoplay />
</div>
</b-col>
</b-row>
<b-row>
</b-row>
<b-row>
<b-col>
<label style="font-size: 10px;font-style: italic">{{message}}</label>
</b-col>
</b-row>
<b-row v-if="result != null">
<b-col>
{{result.temperature | formatFloat}} {{$t('temperature_unit')}}
</b-col>
</b-row>
<b-row style="padding-top: 10px">
<b-col cols="6" class="d-flex justify-content-start">
{{$t('object_temperature')}} : {{received.Element | formatFloat}} {{$t('temperature_unit')}}
</b-col>
<b-col cols="6" class="d-flex justify-content-start">
{{$t('ambient_temperature')}} : {{received.Ambient | formatFloat}} {{$t('temperature_unit')}}
</b-col>
<b-col cols="12" class="d-flex justify-content-start" v-if="isAdmin">
{{$t('emissivity')}} : {{received.Emissivity | formatFloat}}
</b-col>
</b-row>
<b-row v-if="isAuth">
<test-chart
:options="options"
:styles="myStyles"
:chart-data="datacollection"></test-chart>
</b-row>
<b-row>
<b-col>
<b-button @click="acquireTemp" variant="danger" :disabled="acquirementInprogress" v-if="isAuth">
{{$t('temperature_acq')}}
<font-awesome-icon icon="question" />
<b-spinner v-if="acquirementInprogress" type="border" :label="$t('inprogress')"/>
</b-button>
</b-col>
</b-row>
</b-container>
</template>
<script>
import axios from "axios";
import TestChart from "./TestChart";
import Vue from "vue";
import '../services/TemperatureHub.js'
import TemperatureHub from "../services/TemperatureHub";
Vue.use(TestChart);
export default {
name: 'Temperature',
components: {
TestChart
},
props: {
msg: String
},
data:function(){
return {
stream:{},
datacollection: {},
received : {},
acquirementInprogress:false,
result: null,
x:'',
options: {
responsive: true,
maintainAspectRatio: false,
},
roles:[],
temperatureHub : null
}
},
computed: {
image()
{
return this.$store.state.user != null && this.$store.state.user.image != null ?
process.env.VUE_APP_IMAGE_STORE + this.$store.state.user.image :
require('@/assets/coviiiiiid.jpg');
},
myStyles() {
return {
height: `300px`,
width:'100%',
position: 'relative'
}
},
message()
{
if(this.result == null)
return "";
else
return this.result.message;
},
image_avatar()
{
if(this.result == null)
{
if(this.acquirementInprogress)
return require('@/assets/waiting.jpg');
else
return this.image;
}
else
{
if(this.result.fever === null)
return require('@/assets/out_of_range.jpg');
else if(this.result.fever)
return require('@/assets/fever_ok.jpg');
else
return this.image;
}
},
isAdmin() {
return this.$store.state.roles.find(l => l.name == 'Administrators' && l.isActived) != null;
},
isAuth() {
return this.$store.state.roles !== undefined && this.$store.state.roles.find(l => l.isActived) != null;
},
},
filters: {
formatFloat : function(value) {
if (value) {
return parseFloat(value).toFixed(2);
}
}
},
created () {
this.temperatureHub = new TemperatureHub();
Vue.prototype.$questionHub = this.temperatureHub.startHub();
this.$questionHub.$on(process.env.VUE_APP_EMIT_MSG_TEMPERATURE, this.onChanged);
// Listen to score changes coming from SignalR events
},
mounted() {
this.fillData ();
},
methods : {
fillData () {
this.datacollection = {
labels: [],
datasets: [
{
label: this.$t('temperature_chart_label'),
backgroundColor: '#f87979',
data: []
}]
}
},
onChanged: function(msg) {
this.received = msg;
let datacollection = {
labels: [],
datasets: [
{
label: this.$t('temperature_chart_label'),
backgroundColor: '#f87979',
data: []
}]
}
if(this.datacollection.labels.length > process.env.VUE_APP_MAX_CHART)
{
this.datacollection.labels.shift();
this.datacollection.datasets[0].data.shift();
}
datacollection.labels.push(...this.datacollection.labels);
datacollection.datasets[0].data.push(...this.datacollection.datasets[0].data);
datacollection.labels.push(this.x);
datacollection.datasets[0].data.push(this.received.Element);
this.datacollection = datacollection;
},
acquireTemp : async function()
{
try {
this.acquirementInprogress = true;
let that = this;
this.video = this.$refs.video;
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
that.stream = stream
});
}
this.result = null;
let axiosConfig = {
withCredentials: true,
headers:{'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
};
let result = await axios.get(process.env.VUE_APP_BACK_URL + "/api/temperature", axiosConfig);
window.console.log(result);
this.result = result.data;
}
finally {
this.acquirementInprogress = false;
this.stream = null;
}
}
},
beforeDestroy : async function() {
// Make sure to cleanup SignalR event handlers when removing the component
this.$questionHub.$off(process.env.VUE_APP_EMIT_MSG_TEMPERATURE, this.onChanged);
await this.temperatureHub.stopHub();
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

@ -0,0 +1,17 @@
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Line,
mixins: [reactiveProp],
props: ['options'],
options: {
responsive: true,
maintainAspectRatio: false,
},
mounted () {
// this.chartData is created in the mixin.
// If you want to pass options please create a local options object
this.renderChart(this.chartData, this.options)
}
}

@ -0,0 +1,183 @@
<template>
<div>
<b-card v-if="user.isNew == undefined" no-body class="overflow-hidden cardUser"
>
<b-row no-gutters style="height: 200px" >
<b-col md="8" cols="7" class="border-right" style=";background-color: #EEEEEE">
<b-row align-h="center" v-if="user.image !== null">
<router-link
:to="{name: 'User', params: {id: user.id } }"
v-slot="{ href, route, navigate}"
v-if="user.image !== '' && user.image !== null"
>
<div class="mb-2 imageUser">
<b-avatar :href="href" @click="navigate"
:title="user.nom + ' ' + user.prenom"
variant="light"
v-bind:src="img" size="6rem"
>
</b-avatar>
</div>
</router-link>
</b-row>
<b-row align-h="center">
<b-col>
<h4> {{user.prenom}} {{user.nom}}</h4>
</b-col>
</b-row>
<b-row v-for="role in user.roles" v-bind:key="role.id">
<b-col class="d-flex justify-content-center align-items-center">
<h5> {{role.libelle}}</h5>
</b-col>
</b-row>
</b-col>
<b-col md="4" cols="5" class="d-flex flex-column justify-content-between">
<b-row >
<b-col style="padding-bottom: 15px; padding-top: 15px">
<router-link
:to="{name: 'User', params: {id: user.id } }"
v-slot="{ href, route, navigate}"
>
<b-button :href="href" @click="navigate" class="actionButton" >
<font-awesome-icon icon="edit" />
{{$t('edit')}}</b-button>
</router-link>
</b-col>
</b-row>
<b-row>
<b-col style="padding-bottom: 15px; padding-top: 15px">
<b-button @click="deleteUser(user)" class="actionButton" >
<font-awesome-icon icon="trash" />
{{$t('delete')}}
</b-button>
</b-col>
</b-row>
</b-col>
</b-row>
</b-card>
<b-card v-if="user.isNew" no-body
class="overflow-hidden rounded Desktopbutton cardUser">
<router-link
:to="{name: 'User', params: {id: 0 } }"
v-slot="{ href, route, navigate}">
<b-button :href="href" @click="navigate"
style="width:100%; height: 100%"
class="d-flex justify-content-center align-items-center">
<font-awesome-icon icon="plus-circle" style="width: 64px; height:64px" />
</b-button>
</router-link>
</b-card>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "CardUser",
props:
{
method: {type: Function},
user: {
type: Object
}
},
mounted()
{
},
computed :
{
img()
{
return this.user != null && this.user.image!= null ?
process.env.VUE_APP_IMAGE_STORE + this.user.image :
'';
},
},
methods:
{
deleteUser : function(user)
{
let axiosConfig = {
withCredentials: true,
headers:{'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
};
this.$bvModal.msgBoxConfirm('Vous allez supprimer l\'utilisateur \'' + user.login + '\'. Voulez-vous continuer ?',
{
title: 'Suppression Utilisateur',
size: 'sm',
buttonSize: 'sm',
okVariant: 'success',
okTitle: 'Oui',
cancelVariant: 'danger',
cancelTitle: 'Non',
footerClass: 'p-2',
hideHeaderClose: false,
centered: true
})
.then(value => {
if(value) {
axios.delete(process.env.VUE_APP_BACK_USER+ "/api/users/" + user.id,
axiosConfig
).then(() => {
this.$emit('send-message');
});
}
})
.catch(err => {
window.console.error(err.messages);
})
}
}
}
</script>
<style scoped>
.card-body
{
padding-right: 0px;
padding-left: 0px;
padding-top: 0px;
}
.cardUser {
max-width: 540px;
height: 175px
}
.actionButton{
width:125px
}
.imageUser {
width:96px;
height:96px
}
.headerUser {
position:fixed; /* fixing the position takes it out of html flow - knows
nothing about where to locate itself except by browser
coordinates */
left:0; /* top left corner should start at leftmost spot */
/*top:0; /* top left corner should start at topmost spot */
width:100vw; /* take up the full browser width */
z-index:50; /* high z index so other content scrolls underneath */
height: 100px;
background-color: white;
}
@media (min-width: 50em) {
.Desktopbutton { display: block; }
.Mobilebutton { display: none; }
}
@media (max-width: 50em) {
.Desktopbutton { display: none; }
.Mobilebutton { display: block; }
.MobileEntete {margin-top: 35px;}
}
</style>

@ -0,0 +1,228 @@
<template>
<b-container fluid id="ListeUser" >
<div class="headerUser" variant="dark">
<b-row align-h="center"
style="padding-top: 15px;"
>
<h2>{{$t('user_title')}} </h2>
</b-row>
<b-row align-v="center">
<b-col cols="8" lg="3" >
<b-form-input v-model="search"
@input="debounceInput"
v-bind:placeholder="$t('search')" style="max-width: 400px"></b-form-input>
</b-col>
<b-col cols="1" lg="1">
<b-button @click="display">
<font-awesome-icon icon="search" />
</b-button>
</b-col>
</b-row>
<b-row>
<router-link
:to="{name: 'User', params: {id: 0 } }"
v-slot="{ href, route, navigate}">
<b-button :href="href" @click="navigate" class="Mobilebutton"
style="width:100%">
<font-awesome-icon icon="plus-circle" />
</b-button>
</router-link>
</b-row>
</div>
<div style="padding-top: 135px" class="MobileEntete">
<b-row v-if="!isLoaded" class="text-center d-flex justify-content-center">
<b-spinner type="border" label="chargement en cours..."></b-spinner>
</b-row>
<b-row v-if="isLoaded && users.length > 0" >
<b-col cols="12" md="4" lg="4" v-for="user in users" v-bind:key="user.id"
style="padding-right: 5px; padding-left: 5px; padding-top: 5px; padding-bottom: 5px">
<div>
<card-user :user="user" @send-message="handleSendMessage"/>
</div>
</b-col>
<infinite-loading @infinite="infiniteHandler" :identifier="infiniteId">
<div slot="no-more"></div>
<div slot="no-results"></div>
</infinite-loading>
</b-row>
<b-row class="mb-4" v-else>
<p v-if="isLoaded">{{$t('user_list_empty')}} </p>
</b-row>
</div>
</b-container>
</template>
<script>
import axios from 'axios';
import InfiniteLoading from 'vue-infinite-loading';
import CardUser from "./CardUser";
import { debounce } from "debounce";
export default {
name: "ListeUser",
props:['value'],
components: {
CardUser,
InfiniteLoading,
},
data:function(){
return {
page: 1,
pageSize:4,
search:'',
infiniteId: + new Date(),
users: [
],
isLoaded: false
}
},
methods :
{
handleSendMessage() {
this.display();
},
debounceInput: debounce(function () {
this.display();
}, 500,false),
infiniteHandler : function($state)
{
let that = this;
let axiosConfig = {
withCredentials: true,
headers:{'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
};
axios.get(process.env.VUE_APP_BACK_USER + "/api/users?page=" + that.page
+ "&pageSize=" + that.pageSize
+ "&search=" + that.search,
axiosConfig
).then(result => {
if (result.data.length) {
this.page += 1;
this.users.push(...result.data);
$state.loaded();
} else {
$state.complete();
}
});
},
display : async function()
{
this.isLoaded = false;
let axiosConfig = {
withCredentials: true,
headers:{'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
};
this.page = 1;
let result = await axios.get(process.env.VUE_APP_BACK_USER + "/api/users?page=" + this.page
+ "&pageSize=" + this.pageSize
+ "&search=" + this.search,
axiosConfig
);
this.isLoaded = true;
this.page += 1;
this.users = [ {isNew:true}];
this.users.push(...result.data);
this.infiniteId += 1;
},
deleteUser : function(user)
{
let that = this;
let axiosConfig = {
withCredentials: true,
headers:{'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
};
this.$bvModal.msgBoxConfirm('Vous allez supprimer l\'utilisateur \'' + user.nom + '\'. Voulez-vous continuer ?',
{
title: 'Suppression Utilisateur',
size: 'sm',
buttonSize: 'sm',
okVariant: 'success',
okTitle: 'Oui',
cancelVariant: 'danger',
cancelTitle: 'Non',
footerClass: 'p-2',
hideHeaderClose: false,
centered: true
})
.then(value => {
if(value) {
axios.delete(process.env.VUE_APP_BACK_USER+ "/api/users/" + user.id,
axiosConfig
).then(() => {
that.display();
});
}
})
.catch(err => {
window.console.error(err.messages);
})
}
},
mounted() {
this.display();
}
}
</script>
<style scoped>
.card-body
{
padding-right: 0px;
padding-left: 0px;
padding-top: 0px;
}
.cardUser {
max-width: 540px;
height: 175px
}
.actionButton{
width:125px
}
.imageUser {
width:96px;
height:96px
}
.headerUser {
position:fixed; /* fixing the position takes it out of html flow - knows
nothing about where to locate itself except by browser
coordinates */
left:0; /* top left corner should start at leftmost spot */
/*top:0; /* top left corner should start at topmost spot */
width:100vw; /* take up the full browser width */
z-index:50; /* high z index so other content scrolls underneath */
height: 100px;
background-color: white;
}
@media (min-width: 50em) {
.Desktopbutton { display: block; }
.Mobilebutton { display: none; }
}
@media (max-width: 50em) {
.Desktopbutton { display: none; }
.Mobilebutton { display: block; }
.MobileEntete {margin-top: 35px;}
}
</style>

@ -0,0 +1,328 @@
<template>
<b-container fluid id="User" >
<!-- <b-row class="d-flex justify-content-center" style="padding-top: 15px;">
<h2>{{titre}} </h2>
</b-row>-->
<b-row v-if="!isLoaded" class="text-center d-flex justify-content-center">
<b-spinner type="border" label="chargement en cours..."></b-spinner>
</b-row>
<b-row v-if="isLoaded && user !== null">
<b-col cols="12" md="2">
<b-row align-v="center">
<b-col >
<b-avatar
variant="light"
v-bind:src="img" size="10rem"
>
</b-avatar>
</b-col>
</b-row>
<b-row align-v="center">
<b-col class="d-flex justify-content-center align-items-center">
<b-button v-if="hasImage" variant="danger" class="Desktop" @click="clearImage">{{$t('clear')}}</b-button>
</b-col>
</b-row>
</b-col>
<b-col cols="12" md="10">
<b-row align-v="center" style="padding-top: 5px">
<b-col md="1" cols="4">
<label>{{$t('user_detail_prenom')}}</label>
</b-col>
<b-col md="5" cols="8">
<b-form-input v-model="user.prenom"
:state="validFirstName"
v-bind:placeholder="$t('user_detail_prenom')"></b-form-input>
</b-col>
<b-col md="1" cols="4">
<label>{{$t('user_detail_nom')}}</label>
</b-col>
<b-col md="5" cols="8">
<b-form-input number v-model="user.nom"
:state="validName"
v-bind:placeholder="$t('user_detail_nom')"></b-form-input>
</b-col>
</b-row>
<b-row>
<b-col md="1" cols="4">
<label>{{$t('user_detail_username')}}</label>
</b-col>
<b-col md="5" cols="8">
<b-form-input v-model="user.userName"
:state="validUserName"
v-bind:placeholder="$t('user_detail_username')"></b-form-input>
</b-col>
<!-- <b-col md="1" cols="4">
<label>{{$t('user_detail_mail')}}</label>
</b-col>
<b-col md="5" cols="8">
<b-form-input mail v-model="user.email" v-bind:placeholder="$t('user_detail_mail')"></b-form-input>
</b-col>-->
</b-row>
<b-row align-v="center" style="padding-top: 5px">
<b-col md="1" cols="4">
<label> {{$t('user_detail_id_keycloak')}}</label>
</b-col>
<b-col md="5" cols="8">
<b-form-input :disabled="!isAdmin" v-model="user.keycloakId" v-bind:placeholder="$t('user_detail_id_keycloak')"></b-form-input>
</b-col>
<b-col md="1" class="Desktop">
<label>{{$t('user_detail_image')}}</label>
</b-col>
<b-col md="5" class="Desktop">
<b-form-file
v-model="file"
plain
></b-form-file>
</b-col>
</b-row>
<b-row class="d-flex justify-content-center align-items-center" style="padding-top: 10px">
<h5>{{$t('user_detail_roles')}}</h5>
</b-row>
<b-row>
<b-col>
<b-row v-for="role in roles" v-bind:key="role.id">
<b-col>
{{role.name}}
</b-col>
<b-col>
<toggle-button
v-model="role.isActived"
:value="false"
:font-size="16"
:sync="true"
/>
</b-col>
</b-row>
</b-col>
</b-row>
</b-col>
</b-row>
<b-row v-if="isLoaded" >
<b-col class="d-flex justify-content-between align-items-center">
<router-link
:to="{name: 'ListeUser' }"
v-slot="{ href, route, navigate}"
>
<b-button :href="href" @click="navigate" variant="danger"><font-awesome-icon icon="arrow-alt-circle-left" /> {{$t('back')}}</b-button>
</router-link>
<photo v-bind:snapshot.sync="photo"></photo>
<b-button @click="saveAndQuit" variant="success" v-bind:disabled="!isValid"><font-awesome-icon icon="check" /> {{$t('valid')}}</b-button>
</b-col>
</b-row>
</b-container>
</template>
<script>
import axios from 'axios';
import photo from '../Capture/CapturePhoto';
import Vue from "vue";
Vue.use(photo);
const base64Encode = data =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(data);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
export default {
name: "User",
components: { photo},
data:function(){
return {
user: null,
id: null,
componentKey:0,
file : null,
photo: null,
img:null,
roles: null,
isLoaded : false
}
},
computed: {
isAdmin() {
return this.$store.state.roles.find(l => l.name == 'Administrators' && l.isActived) != null;
},
isValid()
{
return this.validFirstName && this.validName && this.validUserName;
},
validFirstName(){
return this.user != null && this.user.prenom !== '' && this.user.prenom !== null;
},
validName(){
return this.user != null && this.user.nom !== '' && this.user.nom !== null;
},
validUserName(){
return this.user != null && this.user.userName !== '' && this.user.userName !== null && this.user.userName !== undefined;
},
hasImage() {
return !!this.img;
},
titre(){
return (this.user != null) ? this.user.prenom + ' ' + this.user.nom : this.$i18n.t('user_detail_title');
}
},
watch :
{
file : function (newValue, oldValue) {
if (newValue !== oldValue) {
if (newValue) {
base64Encode(newValue)
.then(value => {
this.user.image= newValue.name;
this.img = value;
})
.catch(() => {
this.img = null;
});
} else {
this.img = null;
}
}
},
photo: function(val)
{
this.img =val.photo;
},
},
methods :
{
clearImage() {
this.img = null;
this.file = null;
this.photo = null;
this.user.image="user_unknown.png";
},
saveAndQuit : async function()
{
await this.save();
await this.$router.push({name: 'ListeUser'});
},
save : async function()
{
let that = this;
let axiosConfig = {
withCredentials: true,
headers:{'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
};
let result = await axios.put(process.env.VUE_APP_BACK_USER + "/api/users/" + this.id, this.user, axiosConfig);
that.user = result.data;
let data = new FormData();
if(that.file !== null) {
data.set('file', that.file)
await axios.post(process.env.VUE_APP_BACK_USER + '/api/users/' + that.id + '/loadimage', data, {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
});
}
else if(that.photo != null)
{
data.set('base64', that.img.split(",")[1]);
await axios.post(process.env.VUE_APP_BACK_USER + "/api/users/" + that.id + '/loadimage64',data,
{
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
}
);
that.componentKey += 1;
}
await axios.put(process.env.VUE_APP_BACK_USER + "/api/roles/" + this.user.keycloakId, this.roles, axiosConfig);
}
},
mounted() {
this.id = this.$route.params.id;
let axiosConfig = {
withCredentials: true,
headers: {
'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
};
this.isLoaded = false;
if(this.id !== 0) {
axios.get(process.env.VUE_APP_BACK_USER + "/api/users/" + this.id,
axiosConfig
).then(result => {
this.user = result.data;
this.img = process.env.VUE_APP_IMAGE_STORE + this.user.image;
axios.get(process.env.VUE_APP_BACK_USER + "/api/roles/bykeycloakid?keycloakid=" + this.user.keycloakId,
axiosConfig
).then(result => {
this.isLoaded = true;
this.roles = result.data;
});
});
}
else
{
this.user = {id:0, nom:'', prenom:'', keycloakId:''}
axios.get(process.env.VUE_APP_BACK_USER + "/api/roles",
axiosConfig
).then(result => {
this.isLoaded = true;
this.roles = result.data;
});
}
}
}
</script>
<style scoped>
.row
{
padding-top: 5px
}
.card-body
{
padding-right: 0px;
padding-left: 0px;
padding-top: 0px;
padding-bottom: 0px;
}
.collapsed > .when-opened,
:not(.collapsed) > .when-closed {
display: none;
}
@media (min-width: 50em) {
.Desktop { display: block; }
.Mobile { display: none; }
}
@media (max-width: 50em) {
.Desktop { display: none; }
.Mobile { display: block; }
}
</style>

@ -0,0 +1,41 @@
export default {
en: {
},
'fr-FR': {
edit:"Editer",
delete:"Supprimer",
add:"ajouter",
logout:"Logout",
login:"Login",
save:"Sauvegarder",
back:"Retour",
valid:"Valider",
unload:"Décharge",
search:"Recherche",
clear:"Effacer",
inprogress:"chargement en cours...",
title:"Coviiiid",
sub_title:"Coviiiid !!!!! Je sais où tu t'caches ! Viens là que j'te bute !",
object_temperature : "T° objet",
ambient_temperature : "T° ambiante",
emissivity:"Emissivité",
temperature_unit : "°C",
temperature_acq:"Coviiiid",
temperature_chart_label:"Température",
user_title : "Utilisateurs",
user_list_empty: "Aucun utilisateur",
user_detail_title: "Utilisateur",
user_detail_nom: "Nom",
user_detail_prenom: "Prénom",
user_detail_image:"Image",
user_detail_id_keycloak:'idKeycloak',
user_detail_username : 'login',
user_detail_mail : 'Email',
user_detail_roles : 'Rôles',
user_detail_photo:"photo",
}
}

@ -0,0 +1,78 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router/index'
import VueI18n from 'vue-i18n'
import messages from './lang/messages'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import axios from "axios";
import UserService from "./services/UserService";
import ToggleButton from 'vue-js-toggle-button'
import Vuex from 'vuex'
library.add(fas)
Vue.component('font-awesome-icon', FontAwesomeIcon)
Vue.config.productionTip = false;
Vue.use(VueI18n)
Vue.use(ToggleButton);
let locale = navigator.language;
const i18n = new VueI18n({
fallbackLocale: 'fr',
locale: locale,
messages
})
Vue.use(Vuex)
const store = new Vuex.Store({
state : {
roles: [],
user:null,
},
mutations: {
updateRole (state, roles) {
this.state.roles = roles;
},
updateUser (state, user) {
this.state.user = user;
}
}
})
new Vue({
router,
i18n,
store,
mounted(){
axios.interceptors.response.use( (response) => {
// Return a successful response back to the calling service
return response;
}, (error) => {
// Return any error which is not due to authentication back to the calling service
if (error.response.status !== 401) {
return new Promise((resolve, reject) => {
reject(error);
});
}
else if (error.response.status === 401) {
return new Promise((resolve, reject) => {
UserService.doLogout();
UserService.initKeycloak( function () {
sessionStorage.setItem("react-token", UserService.getToken());
sessionStorage.setItem("react-refresh-token", UserService.getRefreshToken());
}, UserService.doLogin);
reject(error);
});
}
})
},
render: h => h(App),
}).$mount('#app')

@ -0,0 +1,33 @@
import Vue from 'vue'
import Router from 'vue-router'
import Temperature from "../components/Temperature";
import User from "../components/User/User";
import ListeUser from "../components/User/ListeUser";
Vue.use(Router)
export default new Router({
routes: [
{
path: '*',
name: 'Temperature',
component: Temperature
},
{
path: '/',
name: 'Temperature',
component: Temperature
},
{
path: '/user/:id',
name: 'User',
component: User
},
{
path: '/users',
name: 'ListeUser',
component: ListeUser
},
]
})

@ -0,0 +1,52 @@
import {HubConnectionBuilder, LogLevel} from "@aspnet/signalr";
import Vue from "vue";
export default class TemperatureHub
{
hubConnection;
constructor() {
this.hubConnection = new HubConnectionBuilder()
.withUrl(process.env.VUE_APP_TEMPERATURE_HUB)
.configureLogging(LogLevel.Information)
.build();
}
start() {
let startedPromise = null;
let that = this;
startedPromise = this.hubConnection.start().catch(() => {
return new Promise((resolve, reject) =>
setTimeout(() => that.start().then(resolve).catch(reject), process.env.VUE_APP_TIMEOUT_TEMPERATURE_HUB))
})
return startedPromise;
}
startHub()
{
let that = this;
this.hubConnection.onclose(() =>
{
that.start();
}/**/);
this.start();
// use new Vue instance as an event bus
let questionHub = new Vue()
// every component will use this.$questionHub to access the event bus
// Forward server side SignalR events through $questionHub, where components will listen to them
this.hubConnection.on(process.env.VUE_APP_MSG_TEMPERATURE_HUB, (msg) => {
questionHub.$emit(process.env.VUE_APP_EMIT_MSG_TEMPERATURE, JSON.parse(msg) )
})
return questionHub;
}
async stopHub()
{
await this.hubConnection.stop();
}
}

@ -0,0 +1,50 @@
import Keycloak from "keycloak-js";
const _kc = new Keycloak('/keycloak.json');
/**
* Initializes Keycloak instance and calls the provided callback function if successfully authenticated.
*
* @param onAuthenticatedCallback
*/
const initKeycloak = (onAuthenticatedCallback, onUnAuthentificatedCallback) => {
_kc.init({
onLoad: 'check-sso',
flow: 'implicit',
promiseType: 'native',
})
.then((authenticated) => {
if (authenticated) {
onAuthenticatedCallback();
} else {
window.console.warn("not authenticated!");
onUnAuthentificatedCallback();
}
})
};
const doLogin = _kc.login;
const doLogout = _kc.logout;
const getToken = () => _kc.token;
const getRefreshToken = () => _kc.refreshToken;
const updateToken = (successCallback) => {
return _kc.updateToken(5)
.then(successCallback)
.catch(doLogin)
};
const getUsername = () => _kc.tokenParsed;
export default {
initKeycloak,
doLogin,
doLogout,
getToken,
getRefreshToken,
updateToken,
getUsername,
}

@ -0,0 +1,18 @@
import axios from 'axios';
import { Promise } from "es6-promise";
export default () => {
axios.interceptors.response.use( (response) => {
// Return a successful response back to the calling service
return response;
}, (error) => {
// Return any error which is not due to authentication back to the calling service
if (error.response.status !== 401) {
return new Promise((resolve, reject) => {
reject(error);
});
}
})
}

@ -0,0 +1,6 @@
module.exports = {
devServer: {
https: false,
port:8080
}
}
Loading…
Cancel
Save