Merge pull request 'Mise en place d'un linter, de formatteur et d'actions afin de vérifier que le code du frontend compile bien' (#3) from linter into main
All checks were successful
CI / build (push) Successful in 12s
All checks were successful
CI / build (push) Successful in 12s
Reviewed-on: #3 Reviewed-by: Theo <tlelez@enseirb-matmeca.fr> Vu que vous ne voulez pas faire avancer le projet, je vais abuser de mes droits d'administrateur et le faire.
This commit is contained in:
commit
c32eea8a40
24
.gitea/workflows/front.yaml
Normal file
24
.gitea/workflows/front.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: CI
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install everything
|
||||||
|
working-directory: ./front/MyINPulse-front
|
||||||
|
run: npm i
|
||||||
|
|
||||||
|
- name: Run ESLint
|
||||||
|
working-directory: ./front/MyINPulse-front
|
||||||
|
run: npx eslint
|
||||||
|
|
||||||
|
- name: Run prettier
|
||||||
|
working-directory: ./front/MyINPulse-front
|
||||||
|
run: npx prettier src --check
|
||||||
|
|
||||||
|
- name: Build frontend
|
||||||
|
working-directory: ./front/MyINPulse-front
|
||||||
|
run: npm run build
|
2
front/MyINPulse-front/.gitignore
vendored
2
front/MyINPulse-front/.gitignore
vendored
@ -36,4 +36,4 @@ playwright-report/
|
|||||||
# Custom
|
# Custom
|
||||||
|
|
||||||
.installed
|
.installed
|
||||||
package-lock.json
|
./package-lock.json
|
7
front/MyINPulse-front/.prettierrc
Normal file
7
front/MyINPulse-front/.prettierrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"useTabs":false,
|
||||||
|
"semi":true,
|
||||||
|
"trailingComma":"es5",
|
||||||
|
"arrowParens":"always",
|
||||||
|
"tabWidth":4
|
||||||
|
}
|
29
front/MyINPulse-front/eslint.config.js
Normal file
29
front/MyINPulse-front/eslint.config.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import eslint from "@eslint/js";
|
||||||
|
import eslintConfigPrettier from "eslint-config-prettier";
|
||||||
|
import eslintPluginVue from "eslint-plugin-vue";
|
||||||
|
import globals from "globals";
|
||||||
|
import typescriptEslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default typescriptEslint.config(
|
||||||
|
{ ignores: ["*.d.ts", "**/coverage", "**/dist"] },
|
||||||
|
{
|
||||||
|
extends: [
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...typescriptEslint.configs.recommended,
|
||||||
|
...eslintPluginVue.configs["flat/recommended"],
|
||||||
|
],
|
||||||
|
files: ["**/*.{ts,vue}"],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
globals: globals.browser,
|
||||||
|
parserOptions: {
|
||||||
|
parser: typescriptEslint.parser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// your rules
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eslintConfigPrettier
|
||||||
|
);
|
1397
front/MyINPulse-front/package-lock.json
generated
1397
front/MyINPulse-front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -26,8 +26,15 @@
|
|||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
|
"eslint": "^9.20.0",
|
||||||
|
"eslint-config-prettier": "^10.0.1",
|
||||||
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
|
"globals": "^15.14.0",
|
||||||
|
"jiti": "^2.4.2",
|
||||||
"npm-run-all2": "^7.0.2",
|
"npm-run-all2": "^7.0.2",
|
||||||
|
"prettier": "3.5.0",
|
||||||
"typescript": "~5.7.3",
|
"typescript": "~5.7.3",
|
||||||
|
"typescript-eslint": "^8.23.0",
|
||||||
"vite": "^6.0.11",
|
"vite": "^6.0.11",
|
||||||
"vite-plugin-vue-devtools": "^7.7.0",
|
"vite-plugin-vue-devtools": "^7.7.0",
|
||||||
"vue-tsc": "^2.2.0"
|
"vue-tsc": "^2.2.0"
|
||||||
|
@ -1,50 +1,47 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink, RouterView } from 'vue-router'
|
import { RouterView } from "vue-router";
|
||||||
import ErrorWrapper from "@/views/errorWrapper.vue";
|
import ErrorWrapper from "@/views/errorWrapper.vue";
|
||||||
|
import ProjectComponent from "@/components/ProjectComponent.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header />
|
<HeaderComponent />
|
||||||
<error-wrapper></error-wrapper>
|
<error-wrapper></error-wrapper>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<ProjectComp
|
<ProjectComponent
|
||||||
v-for="(project, index) in projects"
|
v-for="(project, index) in projects"
|
||||||
:key="index"
|
:key="index"
|
||||||
:projectName="project.name"
|
:project-name="project.name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Header from "@/components/Header.vue";
|
import HeaderComponent from "@/components/HeaderComponent.vue";
|
||||||
import ProjectComp from "@/components/Project-comp.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: "App",
|
||||||
components: {
|
components: {
|
||||||
Header,
|
HeaderComponent,
|
||||||
ProjectComp,
|
},
|
||||||
},
|
data() {
|
||||||
data() {
|
return {
|
||||||
return {
|
projects: [
|
||||||
projects: [
|
{
|
||||||
{
|
name: "Projet Alpha",
|
||||||
name: 'Projet Alpha',
|
//link: './project-alpha.html',
|
||||||
//link: './project-alpha.html',
|
//members: ['Alice', 'Bob', 'Charlie'],
|
||||||
//members: ['Alice', 'Bob', 'Charlie'],
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "Projet Beta",
|
||||||
name: 'Projet Beta',
|
//link: './project-beta.html',
|
||||||
//link: './project-beta.html',
|
//members: ['David', 'Eve', 'Frank'],
|
||||||
//members: ['David', 'Eve', 'Frank'],
|
},
|
||||||
},
|
],
|
||||||
],
|
};
|
||||||
};
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
</script>
|
<style scoped></style>
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<header>
|
<header>
|
||||||
<img src="./icons/logo inpulse.png" alt="INPulse" />
|
<img src="./icons/logo inpulse.png" alt="INPulse" />
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
name: 'Header',
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
header img {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header{
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: "HeaderComponent",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
header img {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 2px solid #ddd;
|
border-bottom: 2px solid #ddd;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -3,16 +3,19 @@
|
|||||||
<div class="project-header">
|
<div class="project-header">
|
||||||
<h2>{{ projectName }}</h2>
|
<h2>{{ projectName }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Project',
|
name: "ProjectComponent",
|
||||||
props: {
|
props: {
|
||||||
projectName: String,
|
projectName: {
|
||||||
}
|
type: Object as PropType<string>,
|
||||||
}
|
required: true,
|
||||||
</script>
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
@ -1,17 +1,43 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps(['data']);
|
import { color } from "@/services/popupDisplayer.ts";
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
errorMessage: {
|
||||||
|
type: Object as PropType<string>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
errorColor: {
|
||||||
|
type: Object as PropType<color>,
|
||||||
|
default: color.Red,
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class='["red", "yellow", "blue", "green"][data.type]' class="error-modal">
|
<div
|
||||||
<p>{{["Erreur :(", "Warning :|", "Info :)", "Succes ;)"][data.type]}}</p>
|
:class="['red', 'yellow', 'blue', 'green'][errorColor]"
|
||||||
<p>{{data.errorMessage}}</p>
|
class="error-modal"
|
||||||
<div class="loading" :class='["red-loader", "yellow-loader", "blue-loader", "green-loader"][data.type]'></div>
|
>
|
||||||
</div>
|
<p>
|
||||||
|
{{
|
||||||
|
["Erreur :(", "Warning :|", "Info :)", "Succes ;)"][errorColor]
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<p>{{ errorMessage }}</p>
|
||||||
|
<div
|
||||||
|
class="loading"
|
||||||
|
:class="
|
||||||
|
['red-loader', 'yellow-loader', 'blue-loader', 'green-loader'][
|
||||||
|
errorColor
|
||||||
|
]
|
||||||
|
"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.error-modal{
|
.error-modal {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
@ -19,41 +45,45 @@ const props = defineProps(['data']);
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
animation: disappear 5s linear forwards;
|
animation: disappear 5s linear forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.red{
|
.red {
|
||||||
background-color: #ee6055;
|
background-color: #ee6055;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.red-loader {
|
|
||||||
|
.red-loader {
|
||||||
background-color: #fa8383;
|
background-color: #fa8383;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yellow{
|
.yellow {
|
||||||
background-color: #FF9D23;
|
background-color: #ff9d23;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.yellow-loader{
|
|
||||||
|
.yellow-loader {
|
||||||
background-color: #ffbf81;
|
background-color: #ffbf81;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blue {
|
.blue {
|
||||||
background-color: #809bce;
|
background-color: #809bce;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.blue-loader{
|
|
||||||
background-color: #95b8d1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.green {
|
.blue-loader {
|
||||||
|
background-color: #95b8d1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
background-color: green;
|
background-color: green;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.green-loader {
|
|
||||||
background-color: darkgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
.green-loader {
|
||||||
|
background-color: darkgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -62,33 +92,33 @@ const props = defineProps(['data']);
|
|||||||
height: 1em;
|
height: 1em;
|
||||||
width: 0;
|
width: 0;
|
||||||
animation: loading 4s linear forwards;
|
animation: loading 4s linear forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Animation for the loading bar */
|
/* Animation for the loading bar */
|
||||||
@keyframes loading {
|
@keyframes loading {
|
||||||
0% {
|
0% {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes disappear {
|
@keyframes disappear {
|
||||||
0% {
|
0% {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
80% {
|
80% {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
height: 0;
|
height: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,36 +1,31 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from "vue";
|
||||||
import App from './App.vue'
|
import App from "./App.vue";
|
||||||
import router from './router/router.ts'
|
import router from "./router/router.ts";
|
||||||
import {createPinia} from "pinia";
|
import { createPinia } from "pinia";
|
||||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||||
import AuthStorePlugin from './plugins/authStore';
|
import keycloakService from "./services/keycloak";
|
||||||
import keycloakService from './services/keycloak';
|
import { type AuthStore, useAuthStore } from "@/stores/authStore.ts";
|
||||||
import {useAuthStore} from "@/stores/authStore.ts";
|
|
||||||
let store: any;
|
let store: AuthStore;
|
||||||
|
|
||||||
keycloakService.CallInit(() => {
|
keycloakService.CallInit(() => {
|
||||||
try {
|
try {
|
||||||
const app = createApp(App)
|
const app = createApp(App);
|
||||||
|
|
||||||
// Setup pinia store, allowing user to keep logged in status after refresh
|
// Setup pinia store, allowing user to keep logged in status after refresh
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
pinia.use(piniaPluginPersistedstate);
|
pinia.use(piniaPluginPersistedstate);
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
app.use(AuthStorePlugin, { pinia });
|
|
||||||
store = useAuthStore();
|
store = useAuthStore();
|
||||||
app.use(router)
|
keycloakService.CallInitStore(store);
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount("#app");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error while initiating Keycloak.")
|
console.error("Error while initiating Keycloak.");
|
||||||
console.error(e)
|
console.error(e);
|
||||||
createApp(App).mount('#app');
|
createApp(App).mount("#app");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
})
|
export { store };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export {store};
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
// file: src/plugins/authStore.js
|
|
||||||
|
|
||||||
import { useAuthStore } from "@/stores/authStore.ts";
|
|
||||||
import keycloakService from '@/services/keycloak';
|
|
||||||
// Setup auth store as a plugin so it can be accessed globally in our FE
|
|
||||||
const authStorePlugin = {
|
|
||||||
install(app: any, option: any) {
|
|
||||||
const store = useAuthStore(option.pinia);
|
|
||||||
app.config.globalProperties.$store = store;
|
|
||||||
keycloakService.CallInitStore(store);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default authStorePlugin;
|
|
@ -1,17 +1,17 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/test',
|
path: "/test",
|
||||||
name: 'test',
|
name: "test",
|
||||||
// route level code-splitting
|
// route level code-splitting
|
||||||
// this generates a separate chunk (About.[hash].js) for this route
|
// this generates a separate chunk (About.[hash].js) for this route
|
||||||
// which is lazy-loaded when the route is visited.
|
// which is lazy-loaded when the route is visited.
|
||||||
component: () => import('../views/test.vue'),
|
component: () => import("../views/testComponent.vue"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
});
|
||||||
|
|
||||||
export default router
|
export default router;
|
||||||
|
@ -1,31 +1,36 @@
|
|||||||
import axios from "axios";
|
import axios, { type AxiosError, type AxiosResponse } from "axios";
|
||||||
import {store} from "@/main.ts";
|
import { store } from "@/main.ts";
|
||||||
import {addNewMessage, color} from "@/services/popupDisplayer.ts";
|
import { addNewMessage, color } from "@/services/popupDisplayer.ts";
|
||||||
|
|
||||||
const axiosInstance = axios.create({
|
const axiosInstance = axios.create({
|
||||||
baseURL: import.meta.env.VITE_BACKEND_URL,
|
baseURL: import.meta.env.VITE_BACKEND_URL,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
axiosInstance.interceptors.response.use(
|
axiosInstance.interceptors.response.use(
|
||||||
response => response, // Directly return successful responses.
|
(response) => response, // Directly return successful responses.
|
||||||
async error => {
|
async (error) => {
|
||||||
const originalRequest = error.config;
|
const originalRequest = error.config;
|
||||||
if (error.response.status === 401 && !originalRequest._retry && store.authenticated) {
|
if (
|
||||||
|
error.response.status === 401 &&
|
||||||
|
!originalRequest._retry &&
|
||||||
|
store.authenticated
|
||||||
|
) {
|
||||||
originalRequest._retry = true; // Mark the request as retried to avoid infinite loops.
|
originalRequest._retry = true; // Mark the request as retried to avoid infinite loops.
|
||||||
try {
|
try {
|
||||||
await store.refreshUserToken();
|
await store.refreshUserToken();
|
||||||
// Update the authorization header with the new access token.
|
// Update the authorization header with the new access token.
|
||||||
axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${store.user.token}`;
|
axiosInstance.defaults.headers.common["Authorization"] =
|
||||||
|
`Bearer ${store.user.token}`;
|
||||||
return axiosInstance(originalRequest); // Retry the original request with the new access token.
|
return axiosInstance(originalRequest); // Retry the original request with the new access token.
|
||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
// Handle refresh token errors by clearing stored tokens and redirecting to the login page.
|
// Handle refresh token errors by clearing stored tokens and redirecting to the login page.
|
||||||
console.error('Token refresh failed:', refreshError);
|
console.error("Token refresh failed:", refreshError);
|
||||||
localStorage.removeItem('accessToken');
|
localStorage.removeItem("accessToken");
|
||||||
localStorage.removeItem('refreshToken');
|
localStorage.removeItem("refreshToken");
|
||||||
window.location.href = '/login';
|
window.location.href = "/login";
|
||||||
return Promise.reject(refreshError);
|
return Promise.reject(refreshError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,23 +39,29 @@ axiosInstance.interceptors.response.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// TODO: spawn a error modal
|
// TODO: spawn a error modal
|
||||||
function defaultApiErrorHandler(err: string){
|
function defaultApiErrorHandler(err: AxiosError) {
|
||||||
addNewMessage(err, color.Red);
|
addNewMessage(err.message, color.Red);
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultApiSuccessHandler(response: any){
|
function defaultApiSuccessHandler(response: AxiosResponse) {
|
||||||
addNewMessage(response.data, color.green)
|
addNewMessage(response.data, color.Green);
|
||||||
}
|
|
||||||
function callApi(endpoint: string, onSuccessHandler?: any, onErrorHandler?: any): void {
|
|
||||||
axiosInstance.get(endpoint).then(
|
|
||||||
onSuccessHandler == null ? defaultApiSuccessHandler : onSuccessHandler
|
|
||||||
).catch(
|
|
||||||
(err) => {
|
|
||||||
onErrorHandler == null ? defaultApiErrorHandler(err): onErrorHandler(err);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function callApi(
|
||||||
|
endpoint: string,
|
||||||
|
onSuccessHandler?: (response: AxiosResponse) => void,
|
||||||
|
onErrorHandler?: (error: AxiosError) => void
|
||||||
|
): void {
|
||||||
|
axiosInstance
|
||||||
|
.get(endpoint)
|
||||||
|
.then(
|
||||||
|
onSuccessHandler == null
|
||||||
|
? defaultApiSuccessHandler
|
||||||
|
: onSuccessHandler
|
||||||
|
)
|
||||||
|
.catch(
|
||||||
|
onErrorHandler == null ? defaultApiErrorHandler : onErrorHandler
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export {callApi}
|
export { callApi };
|
||||||
|
@ -1,33 +1,31 @@
|
|||||||
import Keycloak from 'keycloak-js';
|
import Keycloak from "keycloak-js";
|
||||||
|
import type { AuthStore } from "@/stores/authStore.ts";
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
url: import.meta.env.VITE_KEYCLOAK_URL,
|
url: import.meta.env.VITE_KEYCLOAK_URL,
|
||||||
clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID,
|
clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID,
|
||||||
realm: import.meta.env.VITE_KEYCLOAK_REALM
|
realm: import.meta.env.VITE_KEYCLOAK_REALM,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const keycloak = new Keycloak(options);
|
const keycloak = new Keycloak(options);
|
||||||
let authenticated: boolean | undefined;
|
let authenticated: boolean | undefined;
|
||||||
let store = null;
|
let store = null;
|
||||||
|
|
||||||
async function login(){
|
async function login() {
|
||||||
try {
|
try {
|
||||||
await keycloak.login() // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
|
await keycloak.login(); // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
|
||||||
return keycloak;
|
return keycloak;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function signup(){
|
async function signup() {
|
||||||
try {
|
try {
|
||||||
await keycloak.login(
|
await keycloak.login({ action: "register" }); // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
|
||||||
{action: "register"}
|
|
||||||
) // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
|
|
||||||
return keycloak;
|
return keycloak;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,31 +40,33 @@ async function init(onInitCallback: () => void) {
|
|||||||
onLoad: "check-sso",
|
onLoad: "check-sso",
|
||||||
silentCheckSsoRedirectUri: `${location.origin}/silent-check-sso.htm`,
|
silentCheckSsoRedirectUri: `${location.origin}/silent-check-sso.htm`,
|
||||||
responseMode: "query",
|
responseMode: "query",
|
||||||
})
|
});
|
||||||
onInitCallback()
|
onInitCallback();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Keycloak init failed")
|
console.error("Keycloak init failed");
|
||||||
console.error(error)
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes store with Keycloak user data
|
* Initializes store with Keycloak user data
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function initStore(storeInstance: any) {
|
async function initStore(storeInstance: AuthStore) {
|
||||||
try {
|
try {
|
||||||
store = storeInstance
|
store = storeInstance;
|
||||||
console.log(keycloak)
|
console.log(keycloak);
|
||||||
await store.initOauth(keycloak)
|
await store.initOauth(keycloak);
|
||||||
|
|
||||||
// Show alert if user is not authenticated
|
// Show alert if user is not authenticated
|
||||||
if (!authenticated) { console.warn("not authenticated") }
|
if (!authenticated) {
|
||||||
|
console.warn("not authenticated");
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Keycloak init failed")
|
console.error("Keycloak init failed");
|
||||||
console.error(error)
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout user
|
* Logout user
|
||||||
@ -83,7 +83,7 @@ async function refreshToken() {
|
|||||||
await keycloak.updateToken(480);
|
await keycloak.updateToken(480);
|
||||||
return keycloak;
|
return keycloak;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to refresh token');
|
console.error("Failed to refresh token");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,4 +97,4 @@ const KeycloakService = {
|
|||||||
callSignup: signup,
|
callSignup: signup,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default KeycloakService;
|
export default KeycloakService;
|
||||||
|
@ -1,19 +1,43 @@
|
|||||||
import {ref} from "vue";
|
import { ref, type Ref } from "vue";
|
||||||
enum color {Red, Yellow, Blue, green}
|
|
||||||
|
|
||||||
function addNewMessage(errorMessage: string, type?: color, timeout?: number){
|
enum color {
|
||||||
if (timeout == null){
|
Red,
|
||||||
|
Yellow,
|
||||||
|
Blue,
|
||||||
|
Green,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorMessageContent = {
|
||||||
|
message: string;
|
||||||
|
color: color;
|
||||||
|
id: number;
|
||||||
|
timeout: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
let id: number = 0;
|
||||||
|
const getId = () => {
|
||||||
|
id = id + 1;
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
function addNewMessage(errorMessage: string, type?: color, timeout?: number) {
|
||||||
|
if (timeout == null) {
|
||||||
timeout = 5000;
|
timeout = 5000;
|
||||||
}
|
}
|
||||||
if (type == null){
|
if (type == null) {
|
||||||
type = color.Red;
|
type = color.Red;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {errorMessage: errorMessage, timeout: timeout, type: type, uid: Math.random()*100000};
|
const data: ErrorMessageContent = {
|
||||||
errorList.value.push(data)
|
message: errorMessage,
|
||||||
setTimeout(() => errorList.value.slice(0, 1), timeout)
|
timeout: timeout,
|
||||||
|
color: type,
|
||||||
|
id: getId(),
|
||||||
|
};
|
||||||
|
errorList.value.push(data);
|
||||||
|
setTimeout(() => errorList.value.slice(0, 1), timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorList: any= ref([])
|
const errorList: Ref<ErrorMessageContent[]> = ref([]);
|
||||||
|
|
||||||
export {addNewMessage, errorList, color}
|
export { addNewMessage, errorList, color, type ErrorMessageContent };
|
||||||
|
@ -1,54 +1,61 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import keycloakService from '@/services/keycloak';
|
import keycloakService from "@/services/keycloak";
|
||||||
import type Keycloak from "keycloak-js";
|
import type Keycloak from "keycloak-js";
|
||||||
export const useAuthStore = defineStore("storeAuth", {
|
|
||||||
|
const useAuthStore = defineStore("storeAuth", {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
testv: true,
|
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
user: {
|
user: {
|
||||||
token: "",
|
token: "",
|
||||||
refreshToken: "",
|
refreshToken: "",
|
||||||
username: "",
|
username: "",
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
persist: true,
|
persist: true,
|
||||||
getters: {},
|
getters: {},
|
||||||
actions: {
|
actions: {
|
||||||
// Initialize Keycloak OAuth
|
// Initialize Keycloak OAuth
|
||||||
async initOauth(keycloak: Keycloak, clearData = true) {
|
async initOauth(keycloak: Keycloak, clearData = true) {
|
||||||
if(clearData) { await this.clearUserData(); }
|
if (clearData) {
|
||||||
|
await this.clearUserData();
|
||||||
|
}
|
||||||
|
|
||||||
this.authenticated = !!keycloak.authenticated; // the !! removes undefined
|
this.authenticated = !!keycloak.authenticated; // the !! removes undefined
|
||||||
if (this.authenticated && keycloak.token && keycloak.idTokenParsed && keycloak.refreshToken){
|
if (
|
||||||
|
this.authenticated &&
|
||||||
|
keycloak.token &&
|
||||||
|
keycloak.idTokenParsed &&
|
||||||
|
keycloak.refreshToken
|
||||||
|
) {
|
||||||
this.user.username = keycloak.idTokenParsed.given_name;
|
this.user.username = keycloak.idTokenParsed.given_name;
|
||||||
this.user.token = keycloak.token;
|
this.user.token = keycloak.token;
|
||||||
this.user.refreshToken = keycloak.refreshToken;
|
this.user.refreshToken = keycloak.refreshToken;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async login(){
|
async login() {
|
||||||
try {
|
try {
|
||||||
const keycloak = await keycloakService.callLogin();
|
const keycloak = await keycloakService.callLogin();
|
||||||
if (keycloak)
|
if (keycloak) await this.initOauth(keycloak);
|
||||||
await this.initOauth(keycloak);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async signup() {
|
async signup() {
|
||||||
try {
|
try {
|
||||||
const keycloak = await keycloakService.callSignup();
|
const keycloak = await keycloakService.callSignup();
|
||||||
if (keycloak)
|
if (keycloak) await this.initOauth(keycloak);
|
||||||
await this.initOauth(keycloak);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Logout user
|
// Logout user
|
||||||
async logout() {
|
async logout() {
|
||||||
try {
|
try {
|
||||||
await keycloakService.CallLogout(import.meta.env.VITE_APP_URL + "/test");
|
await keycloakService.CallLogout(
|
||||||
|
import.meta.env.VITE_APP_URL + "/test"
|
||||||
|
);
|
||||||
await this.clearUserData();
|
await this.clearUserData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -58,23 +65,23 @@ export const useAuthStore = defineStore("storeAuth", {
|
|||||||
async refreshUserToken() {
|
async refreshUserToken() {
|
||||||
try {
|
try {
|
||||||
const keycloak = await keycloakService.CallTokenRefresh();
|
const keycloak = await keycloakService.CallTokenRefresh();
|
||||||
if (keycloak)
|
if (keycloak) await this.initOauth(keycloak, false);
|
||||||
await this.initOauth(keycloak, false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
test() {
|
|
||||||
this.testv = !this.testv;
|
|
||||||
},
|
|
||||||
// Clear user's store data
|
// Clear user's store data
|
||||||
clearUserData() {
|
clearUserData() {
|
||||||
this.authenticated = false;
|
this.authenticated = false;
|
||||||
this.user = {
|
this.user = {
|
||||||
token: "",
|
token: "",
|
||||||
refreshToken: "",
|
refreshToken: "",
|
||||||
username: "",
|
username: "",
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type AuthStore = ReturnType<typeof useAuthStore>;
|
||||||
|
|
||||||
|
export { useAuthStore, type AuthStore };
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.about {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,22 +1,25 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { errorList } from "@/services/popupDisplayer.ts";
|
||||||
import {errorList} from "@/services/popupDisplayer.ts";
|
|
||||||
import ErrorModal from "@/components/errorModal.vue";
|
import ErrorModal from "@/components/errorModal.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="error-wrapper">
|
<div class="error-wrapper">
|
||||||
<error-modal v-for="elm in errorList" :data=elm></error-modal>
|
<error-modal
|
||||||
</div>
|
v-for="elm in errorList"
|
||||||
|
:key="elm.id"
|
||||||
|
:error-message="elm.message"
|
||||||
|
:error-color="elm.color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.error-wrapper{
|
.error-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 70%;
|
left: 70%;
|
||||||
//background-color: blue;
|
//background-color: blue;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {store} from "../main.ts";
|
|
||||||
import {callApi} from "@/services/api.ts";
|
|
||||||
import ErrorModal from "@/components/errorModal.vue";
|
|
||||||
import {errorList} from "@/services/popupDisplayer.ts";
|
|
||||||
import TempModal from "@/components/temp-modal.vue";
|
|
||||||
import ErrorWrapper from "@/App.vue";
|
|
||||||
function addResToTable(id: any){
|
|
||||||
return (req: any) => {
|
|
||||||
console.log(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<h1>Test page</h1>
|
|
||||||
<table class="test" style="width:100%">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Is Currently Authenticated ? </td>
|
|
||||||
<td>{{store.authenticated}}</td>
|
|
||||||
<td><button @click="store.login">Login</button></td>
|
|
||||||
<td><button @click="store.logout">Logout</button></td>
|
|
||||||
<td><button @click="store.signup">Signup</button></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>current token</td>
|
|
||||||
<td>{{store.user.token}}</td>
|
|
||||||
<td><button @click="store.refreshUserToken">Refresh</button></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Current refresh token</td>
|
|
||||||
<td>{{store.user.refreshToken}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Entrepreneur API call</td>
|
|
||||||
<td><button @click="callApi('random')">call</button></td>
|
|
||||||
<td>res</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Admin API call</td>
|
|
||||||
<td><button @click="callApi('random2')">call</button></td>
|
|
||||||
<td>res</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Unauth API call</td>
|
|
||||||
<td><button @click="callApi('random3')">call</button></td>
|
|
||||||
<td>res</td>
|
|
||||||
<td id="3"></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<temp-modal></temp-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
table {
|
|
||||||
width: 100px;
|
|
||||||
table-layout: fixed
|
|
||||||
}
|
|
||||||
tr {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
border: solid 1px black;
|
|
||||||
width: 20%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
79
front/MyINPulse-front/src/views/testComponent.vue
Normal file
79
front/MyINPulse-front/src/views/testComponent.vue
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { store } from "../main.ts";
|
||||||
|
import { callApi } from "@/services/api.ts";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>Test page</h1>
|
||||||
|
<table class="test" style="width: 100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Is Currently Authenticated ?</td>
|
||||||
|
<td>{{ store.authenticated }}</td>
|
||||||
|
<td>
|
||||||
|
<button @click="store.login">Login</button>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button @click="store.logout">Logout</button>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button @click="store.signup">Signup</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>current token</td>
|
||||||
|
<td>{{ store.user.token }}</td>
|
||||||
|
<td>
|
||||||
|
<button @click="store.refreshUserToken">Refresh</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Current refresh token</td>
|
||||||
|
<td>{{ store.user.refreshToken }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entrepreneur API call</td>
|
||||||
|
<td>
|
||||||
|
<button @click="callApi('random')">call</button>
|
||||||
|
</td>
|
||||||
|
<td>res</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Admin API call</td>
|
||||||
|
<td>
|
||||||
|
<button @click="callApi('random2')">call</button>
|
||||||
|
</td>
|
||||||
|
<td>res</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Unauth API call</td>
|
||||||
|
<td>
|
||||||
|
<button @click="callApi('random3')">call</button>
|
||||||
|
</td>
|
||||||
|
<td>res</td>
|
||||||
|
<td id="3"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
table {
|
||||||
|
width: 100px;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border: solid 1px black;
|
||||||
|
width: 20%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user