initialisation

develop
Jimmy Pellier 4 years ago
parent 2397bf550a
commit 1a8b9302d9
  1. 1
      .env
  2. 3
      .env.development
  3. 1
      .env.production
  4. 21
      .gitignore
  5. 29
      README.md
  6. 5
      babel.config.js
  7. 12499
      package-lock.json
  8. 62
      package.json
  9. BIN
      public/favicon.ico
  10. 17
      public/index.html
  11. 10
      public/keycloak.json
  12. 59
      src/App.vue
  13. BIN
      src/assets/_logo.png
  14. BIN
      src/assets/coviiiiiid.jpg
  15. BIN
      src/assets/fever_ko.gif
  16. BIN
      src/assets/fever_ok.jpg
  17. BIN
      src/assets/logo.png
  18. BIN
      src/assets/out_of_range.jpg
  19. BIN
      src/assets/waiting.jpg
  20. 91
      src/components/Capture/CapturePhoto.vue
  21. 154
      src/components/Menu.vue
  22. 220
      src/components/Temperature.vue
  23. 17
      src/components/TestChart.js
  24. 284
      src/components/User/ListeUser.vue
  25. 325
      src/components/User/User.vue
  26. 38
      src/lang/messages.js
  27. 79
      src/main.js
  28. 37
      src/router/index.js
  29. 51
      src/services/UserService.js
  30. 20
      src/services/axios_interceptor.js
  31. 34
      src/services/temperature-hub.js

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

@ -0,0 +1,3 @@
VUE_APP_BACK_URL=http://localhost:5001
VUE_APP_BACK_USER=http://localhost/user
VUE_APP_IMAGE_STORE=

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

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?

@ -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,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

12499
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,62 @@
{
"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",
"core-js": "^3.4.3",
"es6-promise": "^4.2.8",
"keycloak-js": "^10.0.2",
"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",
"commander": "^4.0.1",
"nimble": "0.0.2",
"node-datetime": "^2.1.2",
"request-digest": "^1.0.13",
"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" 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,154 @@
<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="/temperature" >
{{$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 ?
'http://localhost/image/' + 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 () {
window.console.log("dologin");
sessionStorage.setItem("react-token", UserService.getToken());
sessionStorage.setItem("react-refresh-token", UserService.getRefreshToken());
let axiosConfig = {
withCredentials: true,
headers: {
'Authorization': 'Bearer ' + sessionStorage.getItem("react-token"),
}
};
window.console.log(process.env.VUE_APP_BACK_USER);
axios.get(process.env.VUE_APP_BACK_USER + "/api/users/bykeycloakid?guid=" + UserService.getUsername().sub,
axiosConfig
).then(result => {
that.$store.commit("updateUser", result.data);
});
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", []);
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 () {
window.console.log("dologin");
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 => {
that.$store.commit("updateUser", result.data);
});
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,220 @@
<template>
<b-container fluid id="temperature">
<b-row style="padding-top: 10px">
<b-col>
<b-avatar
v-b-tooltip.hover :title="$t('sub_title')"
variant="info" :src="image_avatar"
size="10rem" style="margin-right: 5px"
>
</b-avatar>
</b-col>
</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">
Emissivité : {{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">
Coviiiiid
<font-awesome-icon icon="question" />
<b-spinner v-if="acquirementInprogress" type="border" label="chargement en cours..."/>
</b-button>
</b-col>
</b-row>
</b-container>
</template>
<script>
import axios from "axios";
import TestChart from "./TestChart";
import Vue from "vue";
Vue.use(TestChart);
export default {
name: 'Temperature',
components: {
TestChart
},
props: {
msg: String
},
data:function(){
return {
datacollection: {},
received : {},
image: require('@/assets/coviiiiiid.jpg'),
acquirementInprogress:false,
result: null,
x:'',
options: {
responsive: true,
maintainAspectRatio: false,
},
roles:[],
}
},
computed: {
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 require('@/assets/coviiiiiid.jpg');
}
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 require('@/assets/coviiiiiid.jpg');
}
},
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 () {
// Listen to score changes coming from SignalR events
this.$questionHub.$on('received-temperature', this.onChanged)
},
mounted() {
this.fillData ();
},
methods : {
fillData () {
this.datacollection = {
labels: [],
datasets: [
{
label: 'Température objet',
backgroundColor: '#f87979',
data: []
}]
}
},
onChanged: function(msg) {
this.received = msg;
let datacollection = {
labels: [],
datasets: [
{
label: 'Température objet',
backgroundColor: '#f87979',
data: []
}]
}
if(this.datacollection.labels.length > 30)
{
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;
this.result = null;
let result = await axios.get(process.env.VUE_APP_BACK_URL + "/api/temperature");
this.result = result.data;
}
finally {
this.acquirementInprogress = false;
}
}
},
beforeDestroy () {
// Make sure to cleanup SignalR event handlers when removing the component
this.$questionHub.$off('received-temperature', this.onChanged)
}
}
</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,284 @@
<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" 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="3" v-for="user in users" v-bind:key="user.id"
style="padding-right: 5px; padding-left: 5px; padding-top: 5px; padding-bottom: 5px">
<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=" 'http://localhost/image/' + user.image" 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>
</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';
export default {
name: "ListeUser",
props:['value'],
components: {
InfiniteLoading,
},
data:function(){
return {
page: 1,
pageSize:4,
search:'',
infiniteId: + new Date(),
users: [
],
isLoaded: false
}
},
methods :
{
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,325 @@
<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="3" md="2">
<b-row align-v="center">
<b-col >
<img v-if="hasImage" loading="lazy" v-bind:src="img" style="width:128px;height:128px"/>
</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="9">
<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 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>
<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>
</b-col>
<b-col>
<photo v-bind:snapshot.sync="photo"></photo>
</b-col>
<b-col>
<b-button @click="save" v-bind:disabled="!isValid"><font-awesome-icon icon="save" /> {{$t('save')}}</b-button>
</b-col>
<b-col>
<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: {
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 = 'http://localhost/image/' + 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,38 @@
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",
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",
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,79 @@
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 QuestionHub from './services/temperature-hub'
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(QuestionHub);
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 () {
window.console.log("main");
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,37 @@
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: '/temperature',
name: 'Temperature',
component: Temperature
},
{
path: '/user/:id',
name: 'User',
component: User
},
{
path: '/users',
name: 'ListeUser',
component: ListeUser
},
]
})

@ -0,0 +1,51 @@
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) {
window.console.info("Philiiiippe!");
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,20 @@
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
console.log(response);
return response;
}, (error) => {
console.log(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,34 @@
import { HubConnectionBuilder, LogLevel } from '@aspnet/signalr'
export default {
install(Vue) {
const connection = new HubConnectionBuilder()
.withUrl('http://localhost:5000/batchExecutionHub')
.configureLogging(LogLevel.Information)
.build()
let startedPromise = null
function start() {
startedPromise = connection.start().catch(() => {
// window.console.error('Failed to connect with hub', err)
return new Promise((resolve, reject) =>
setTimeout(() => start().then(resolve).catch(reject), 5000))
})
return startedPromise
}
connection.onclose(() => start());
start();
// use new Vue instance as an event bus
const questionHub = new Vue()
// every component will use this.$questionHub to access the event bus
Vue.prototype.$questionHub = questionHub
// Forward server side SignalR events through $questionHub, where components will listen to them
connection.on('temperature', (msg) => {
questionHub.$emit('received-temperature', JSON.parse(msg) )
})
}
}
Loading…
Cancel
Save