From 649d7f9aa4382686ee72f23670c975aba760dcce Mon Sep 17 00:00:00 2001 From: Guillaume Chervet Date: Sun, 1 Dec 2024 21:53:31 +0100 Subject: [PATCH] fix(SlimFaasPlanetSaver): better UI --- src/SlimFaasPlanetSaver/README.md | 36 ++-- src/SlimFaasPlanetSaver/src/PlanetSaver.jsx | 35 +-- .../src/SlimFaasPlanetSaver.js | 204 ++++++++++-------- src/SlimFaasPlanetSaver/src/mockFetch.js | 7 +- 4 files changed, 167 insertions(+), 115 deletions(-) diff --git a/src/SlimFaasPlanetSaver/README.md b/src/SlimFaasPlanetSaver/README.md index 249f82ed..afe95cc3 100644 --- a/src/SlimFaasPlanetSaver/README.md +++ b/src/SlimFaasPlanetSaver/README.md @@ -24,16 +24,19 @@ npm install @axa-fr/slimfaas-planet-saver Example usage with react : ```javascript -import React, { useState, useEffect } from 'react'; -import SlimFaasPlanetSaver from "@axa-fr/slimfaas-planet-saver"; +import React, { useState, useEffect, useRef } from 'react'; +import SlimFaasPlanetSaver from "./SlimFaasPlanetSaver.js"; const PlanetSaver = ({ children, baseUrl, fetch }) => { - const [isFirstStart, setIsFirstStart] = useState(true); // State for first start + const [isFirstStart, setIsFirstStart] = useState(true); + const environmentStarterRef = useRef(null); useEffect(() => { if (!baseUrl) return; - const environmentStarter = new SlimFaasPlanetSaver(baseUrl, { + if (environmentStarterRef.current) return; + + const instance = new SlimFaasPlanetSaver(baseUrl, { interval: 2000, fetch, updateCallback: (data) => { @@ -45,31 +48,36 @@ const PlanetSaver = ({ children, baseUrl, fetch }) => { errorCallback: (error) => { console.error('Error detected :', error); }, - overlayStartingMessage: '🌍 Starting the environment.... 🌳', + overlayStartingMessage: '🌳 Starting the environment.... 🌳', overlayNoActivityMessage: 'Waiting activity to start environment...', - overlayErrorMessage: 'An error occured when starting environment. Please contact an administrator.', + overlayErrorMessage: 'An error occurred when starting environment. Please contact an administrator.', overlaySecondaryMessage: 'Startup should be fast, but if no machines are available it can take several minutes.', + overlayLoadingIcon: '🌍', + overlayErrorSecondaryMessage: 'If the error persists, please contact an administrator.' }); - // Start polling - environmentStarter.startPolling(); + environmentStarterRef.current = instance; + + // Initialiser les effets de bord + instance.initialize(); + instance.startPolling(); - // Cleanup - return () => environmentStarter.cleanup(); - }, [baseUrl, isFirstStart]); + return () => { + instance.cleanup(); + environmentStarterRef.current = null; + }; + }, [baseUrl]); - // During the first start, display a loading message if (isFirstStart) { return null; } - // Once the environment is started, display the children return <>{children}; - }; export default PlanetSaver; + ``` ## Run the demo diff --git a/src/SlimFaasPlanetSaver/src/PlanetSaver.jsx b/src/SlimFaasPlanetSaver/src/PlanetSaver.jsx index a4436eec..f15aba5a 100644 --- a/src/SlimFaasPlanetSaver/src/PlanetSaver.jsx +++ b/src/SlimFaasPlanetSaver/src/PlanetSaver.jsx @@ -1,13 +1,16 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import SlimFaasPlanetSaver from "./SlimFaasPlanetSaver.js"; const PlanetSaver = ({ children, baseUrl, fetch }) => { - const [isFirstStart, setIsFirstStart] = useState(true); // State for first start + const [isFirstStart, setIsFirstStart] = useState(true); + const environmentStarterRef = useRef(null); useEffect(() => { if (!baseUrl) return; - const environmentStarter = new SlimFaasPlanetSaver(baseUrl, { + if (environmentStarterRef.current) return; + + const instance = new SlimFaasPlanetSaver(baseUrl, { interval: 2000, fetch, updateCallback: (data) => { @@ -19,27 +22,31 @@ const PlanetSaver = ({ children, baseUrl, fetch }) => { errorCallback: (error) => { console.error('Error detected :', error); }, - overlayStartingMessage: '🌍 Starting the environment.... 🌳', + overlayStartingMessage: '🌳 Starting the environment.... 🌳', overlayNoActivityMessage: 'Waiting activity to start environment...', - overlayErrorMessage: 'An error occured when starting environment. Please contact an administrator.', - overlaySecondaryMessage: 'Startup should be fast, but if no machines are available it can take several minutes.' + overlayErrorMessage: 'An error occurred when starting environment. Please contact an administrator.', + overlaySecondaryMessage: 'Startup should be fast, but if no machines are available it can take several minutes.', + overlayLoadingIcon: '🌍', + overlayErrorSecondaryMessage: 'If the error persists, please contact an administrator.' }); - // Start polling - environmentStarter.startPolling(); + environmentStarterRef.current = instance; + + // Initialiser les effets de bord + instance.initialize(); + instance.startPolling(); - // Cleanup - return () => environmentStarter.cleanup(); - }, [baseUrl, isFirstStart]); + return () => { + instance.cleanup(); + environmentStarterRef.current = null; + }; + }, [baseUrl]); - // During the first start, display a loading message if (isFirstStart) { return null; } - // Once the environment is started, display the children return <>{children}; - }; export default PlanetSaver; diff --git a/src/SlimFaasPlanetSaver/src/SlimFaasPlanetSaver.js b/src/SlimFaasPlanetSaver/src/SlimFaasPlanetSaver.js index 1ea2536d..2d44c583 100644 --- a/src/SlimFaasPlanetSaver/src/SlimFaasPlanetSaver.js +++ b/src/SlimFaasPlanetSaver/src/SlimFaasPlanetSaver.js @@ -4,25 +4,32 @@ return tempUrl; } + + export default class SlimFaasPlanetSaver { constructor(baseUrl, options = {}) { this.baseUrl = normalizeBaseUrl(baseUrl); this.updateCallback = options.updateCallback || (() => {}); this.errorCallback = options.errorCallback || (() => {}); this.interval = options.interval || 5000; - this.overlayStartingMessage = options.overlayStartingMessage || '🌍 Starting the environment.... 🌳'; + this.overlayStartingMessage = options.overlayStartingMessage || '🌳 Starting the environment.... 🌳'; this.overlayNoActivityMessage = options.overlayNoActivityMessage || 'Waiting activity to start environment...'; - this.overlayErrorMessage = options.overlayErrorMessage || 'Une erreur est survenue lors du démarrage de l\'environnement. Veuillez contacter un administrateur.'; + this.overlayErrorMessage = options.overlayErrorMessage || 'An error occurred while starting the environment.'; this.overlaySecondaryMessage = options.overlaySecondaryMessage || 'Startup should be fast, but if no machines are available it can take several minutes.'; + this.overlayErrorSecondaryMessage = options.overlayErrorSecondaryMessage || 'If the error persists, please contact an administrator.'; + this.overlayLoadingIcon = options.overlayLoadingIcon || '🌍'; this.fetch = options.fetch || fetch; this.intervalId = null; this.isDocumentVisible = !document.hidden; this.overlayElement = null; - this.spanElement = null; // Nouvel élément pour le + this.spanElement = null; this.styleElement = null; this.isReady = false; - // Initialisation du temps du dernier mouvement de souris et liaison du gestionnaire + this.events = document.createElement('div'); + } + + initialize() { this.lastMouseMoveTime = Date.now(); this.handleMouseMove = this.handleMouseMove.bind(this); this.handleVisibilityChange = this.handleVisibilityChange.bind(this); @@ -32,10 +39,7 @@ export default class SlimFaasPlanetSaver { this.createOverlay(); this.injectStyles(); - - this.events = document.createElement('div'); } - handleMouseMove() { this.lastMouseMoveTime = Date.now(); } @@ -52,24 +56,29 @@ export default class SlimFaasPlanetSaver { async wakeUpPods(data) { const wakePromises = data .filter((item) => item.NumberReady === 0) - .map((item) => - this.fetch(`${this.baseUrl}/wake-function/${item.Name}`, { + .map(async (item) => { + const response = await this.fetch(`${this.baseUrl}/wake-function/${item.Name}`, { method: 'POST', - }) - ); + }); + if (response.status >= 400) { + throw new Error(`HTTP Error! status: ${response.status} for function ${item.Name}`); + } + return response; + }); try { await Promise.all(wakePromises); } catch (error) { - console.error("Erreur lors du réveil des pods :", error); + console.error("Error waking up pods:", error); + throw error; } } async fetchStatus() { try { const response = await this.fetch(`${this.baseUrl}/status-functions`); - if (!response.ok) { - throw new Error(`Erreur HTTP ! statut : ${response.status}`); + if (response.status >= 400) { + throw new Error(`HTTP Error! status: ${response.status}`); } const data = await response.json(); @@ -79,7 +88,7 @@ export default class SlimFaasPlanetSaver { this.updateCallback(data); const now = Date.now(); - const mouseMovedRecently = now - this.lastMouseMoveTime <= 60000; // 1 minute en millisecondes + const mouseMovedRecently = now - this.lastMouseMoveTime <= 60000; // 1 minute if (!allReady && this.isDocumentVisible && !mouseMovedRecently) { this.updateOverlayMessage(this.overlayNoActivityMessage, 'waiting-action'); @@ -89,10 +98,10 @@ export default class SlimFaasPlanetSaver { } } catch (error) { const errorMessage = error.message; - this.updateOverlayMessage(this.overlayErrorMessage, 'error'); + this.updateOverlayMessage(this.overlayErrorMessage, 'error', this.overlayErrorSecondaryMessage); this.errorCallback(errorMessage); this.triggerEvent('error', { message: errorMessage }); - console.error('Erreur lors de la récupération des données slimfaas :', errorMessage); + console.error('Error fetching slimfaas data:', errorMessage); } finally { this.intervalId = setTimeout(() => { this.fetchStatus(); @@ -128,61 +137,67 @@ export default class SlimFaasPlanetSaver { injectStyles() { const cssString = ` - .environment-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - cursor: not-allowed; - height: 100%; - background-color: rgba(0, 0, 0, 0.8); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - color: white; - font-size: 2rem; - font-weight: bold; - z-index: 1000; - visibility: hidden; - text-align: center; - } - - .environment-overlay.visible { - visibility: visible; - } - - .environment-overlay .main-message { - display: flex; - align-items: center; - gap: 0.5rem; - color: white; - } - - .environment-overlay .secondary-message { - font-size: 1.2rem; - font-weight: normal; - margin-top: 1rem; - } - - .environment-overlay--waiting{ - color: white; - } - - .environment-overlay--waiting-action { - color: lightyellow; - } - .environment-overlay--waiting-action .secondary-message { - visibility: hidden; - } - - .environment-overlay--error { - color: lightred; - } - .environment-overlay--error .secondary-message { - visibility: hidden; - } + .slimfaas-environment-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + cursor: not-allowed; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 2rem; + font-weight: bold; + z-index: 1000; + text-align: center; + } + + .slimfaas-environment-overlay__icon { + font-size: 4rem; + animation: slimfaas-environment-overlay__icon-spin 0.5s linear infinite; + } + + @keyframes slimfaas-environment-overlay__icon-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + .slimfaas-environment-overlay__main-message { + display: flex; + align-items: center; + gap: 0.5rem; + } + .slimfaas-environment-overlay__secondary-message { + font-size: 1.2rem; + font-weight: normal; + margin-top: 1rem; + } + + .slimfaas-environment-overlay--waiting { + color: white; + } + + .slimfaas-environment-overlay--waiting-action { + color: lightyellow; + } + .slimfaas-environment-overlay--waiting-action .slimfaas-environment-overlay__secondary-message { + visibility: hidden; + } + .slimfaas-environment-overlay--waiting-action .slimfaas-environment-overlay__icon { + animation: none; + } + + .slimfaas-environment-overlay--error { + color: lightcoral; + } `; this.styleElement = document.createElement('style'); @@ -192,43 +207,60 @@ export default class SlimFaasPlanetSaver { createOverlay() { this.overlayElement = document.createElement('div'); - this.overlayElement.className = 'environment-overlay'; + this.overlayElement.className = 'slimfaas-environment-overlay'; - // Créer un élément pour le texte et les icônes + // Créer l'élément icône + this.iconElement = document.createElement('div'); + this.iconElement.className = 'slimfaas-environment-overlay__icon'; + this.iconElement.innerText = this.overlayLoadingIcon; + + // Créer l'élément du message principal this.spanElement = document.createElement('span'); + this.spanElement.className = 'slimfaas-environment-overlay__main-message'; this.spanElement.innerHTML = `${this.overlayStartingMessage}`; - // Créer un élément pour le second message + // Créer l'élément du message secondaire this.secondarySpanElement = document.createElement('span'); - this.secondarySpanElement.className = 'secondary-message'; + this.secondarySpanElement.className = 'slimfaas-environment-overlay__secondary-message'; this.secondarySpanElement.innerText = this.overlaySecondaryMessage; - // Ajouter le à l'overlay + // Ajouter les éléments à l'overlay + this.overlayElement.appendChild(this.iconElement); this.overlayElement.appendChild(this.spanElement); this.overlayElement.appendChild(this.secondarySpanElement); - document.body.appendChild(this.overlayElement); + // Ne pas ajouter l'overlay au DOM ici + // document.body.appendChild(this.overlayElement); } showOverlay() { - if (this.overlayElement) { - this.overlayElement.classList.add('visible'); + if (this.overlayElement && !document.body.contains(this.overlayElement)) { + document.body.appendChild(this.overlayElement); } } hideOverlay() { - if (this.overlayElement) { - this.overlayElement.classList.remove('visible'); + if (this.overlayElement && document.body.contains(this.overlayElement)) { + document.body.removeChild(this.overlayElement); } } - updateOverlayMessage(newMessage, status = 'waiting') { + updateOverlayMessage(newMessage, status = 'waiting', secondaryMessage = null) { if (this.spanElement) { this.spanElement.innerHTML = `${newMessage}`; } + if (this.secondarySpanElement && secondaryMessage !== null) { + this.secondarySpanElement.innerText = secondaryMessage; + } else { + this.secondarySpanElement.innerText = this.overlaySecondaryMessage; + } if (this.overlayElement) { - this.overlayElement.classList.remove('environment-overlay--error', 'environment--overlay-waiting', 'environment-overlay--waiting-action'); - this.overlayElement.classList.add("environment-overlay--"+status); + this.overlayElement.classList.remove( + 'slimfaas-environment-overlay--error', + 'slimfaas-environment-overlay--waiting', + 'slimfaas-environment-overlay--waiting-action' + ); + this.overlayElement.classList.add('slimfaas-environment-overlay--' + status); } } @@ -241,10 +273,10 @@ export default class SlimFaasPlanetSaver { this.stopPolling(); document.removeEventListener('visibilitychange', this.handleVisibilityChange); document.removeEventListener('mousemove', this.handleMouseMove); - if (this.overlayElement) { + if (this.overlayElement && document.body.contains(this.overlayElement)) { document.body.removeChild(this.overlayElement); } - if (this.styleElement) { + if (this.styleElement && document.head.contains(this.styleElement)) { document.head.removeChild(this.styleElement); } } diff --git a/src/SlimFaasPlanetSaver/src/mockFetch.js b/src/SlimFaasPlanetSaver/src/mockFetch.js index f8f62c2b..0e94927d 100644 --- a/src/SlimFaasPlanetSaver/src/mockFetch.js +++ b/src/SlimFaasPlanetSaver/src/mockFetch.js @@ -51,6 +51,12 @@ setTimeout(toggleStatusFunctionsBody, 8000); // Mock fetch function function mockFetch(url, options = {}) { return new Promise((resolve, reject) => { + // Lancer une exception aléatoirement 1 fois sur 20 + if (Math.floor(Math.random() * 20) === 0) { + reject(new Error("Exception aléatoire")); + return; + } + // Route: /status-functions if (url === "https://slimfaas/status-functions" && (!options.method || options.method === "GET")) { resolve({ @@ -82,5 +88,4 @@ function mockFetch(url, options = {}) { } }); } - export default mockFetch;