@axa-fr/slimfaas-planet-saver 0.30.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @axa-fr/slimfaas-planet-saver
2
+
3
+ [![npm version](https://badge.fury.io/js/%40axa-fr%2Fslimfaas-planet-saver.svg)](https://badge.fury.io/js/%40axa-fr%2Fslimfaas-planet-saver)
4
+
5
+ ![SlimFaas.png](https://github.com/AxaFrance/SlimFaas/blob/main/documentation/SlimFaas.png)
6
+
7
+ A Vanilla JS project to save the planet. SlimFaas (https://github.com/AxaFrance/slimfaas) is the slimest and simplest Function As A Service on Kubernetes.
8
+ It works as a proxy that you can be deployed in your namespace.
9
+
10
+ SlimFaas API can give to the frontend information about the infrastructure state. **It is a mind changer !**
11
+
12
+ **Why?**
13
+
14
+ Because in production instead of setting up 2 replicas of your API backend, you can set up 0 replicas and use an UX that will show the user that the backend is down instead !
15
+ **@axa-fr/slimfaas-planet-saver** is here to for doing that easily.
16
+
17
+ ![SlimFaasPlanetSaver.gif](https://github.com/AxaFrance/SlimFaas/blob/main/documentation/SlimfaasPlanetSaver.gif)
18
+
19
+ ## Getting Started
20
+
21
+ ```javascript
22
+ npm install @axa-fr/slimfaas-planet-saver
23
+ ```
24
+
25
+ Example usage with react :
26
+ ```javascript
27
+ import React, { useState, useEffect } from 'react';
28
+ import SlimFaasPlanetSaver from "@axa-fr/slimfaas-planet-saver";
29
+
30
+ const PlanetSaver = ({ children, baseUrl, fetch }) => {
31
+ const [isFirstStart, setIsFirstStart] = useState(true); // State for first start
32
+
33
+ useEffect(() => {
34
+ if (!baseUrl) return;
35
+
36
+ const environmentStarter = new SlimFaasPlanetSaver(baseUrl, {
37
+ interval: 2000,
38
+ fetch,
39
+ updateCallback: (data) => {
40
+ const allReady = data.every((item) => item.NumberReady >= 1);
41
+ if (allReady && isFirstStart) {
42
+ setIsFirstStart(false);
43
+ }
44
+ },
45
+ errorCallback: (error) => {
46
+ console.error('Error detected :', error);
47
+ },
48
+ overlayStartingMessage: 'Starting the environment...',
49
+ overlayNoActivityMessage: 'Waiting activity to start environment...',
50
+ overlayErrorMessage: 'An error occured when starting environment. Please contact an administrator.',
51
+ });
52
+
53
+ // Start polling
54
+ environmentStarter.startPolling();
55
+
56
+ // Cleanup
57
+ return () => environmentStarter.cleanup();
58
+ }, [baseUrl, isFirstStart]);
59
+
60
+ // During the first start, display a loading message
61
+ if (isFirstStart) {
62
+ return null;
63
+ }
64
+
65
+ // Once the environment is started, display the children
66
+ return <>{children}</>;
67
+
68
+ };
69
+
70
+ export default PlanetSaver;
71
+
72
+ ```
73
+
74
+ ## Run the demo
75
+
76
+ ```javascript
77
+ git clone https://github.com/AxaFrance/slimfaas.git
78
+ cd slimfaas/src/SlimFaasPlanetSaver
79
+ npm i
80
+ npm run dev
81
+ ```
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@axa-fr/slimfaas-planet-saver",
3
+ "private": false,
4
+ "version": "0.30.4",
5
+ "type": "module",
6
+ "module": "./src/SlimFaasPlanetSaver.js",
7
+ "description": "Pure vanilla javascript which call SlimFaas API to convert infrastructures resilience to an UX resilience and help to save the planet",
8
+ "files": [
9
+ "src/SlimFaasPlanetSaver.js",
10
+ "README.md",
11
+ "package.json",
12
+ "package-lock.json"
13
+ ],
14
+ "keywords": [
15
+ "Faas",
16
+ "SlimFaas",
17
+ "Function As A Service",
18
+ "vanilla",
19
+ "GreenIT"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/AxaFrance/slimfaas.git"
24
+ },
25
+ "scripts": {
26
+ "dev": "vite",
27
+ "build": "vite build",
28
+ "lint": "eslint .",
29
+ "preview": "vite preview"
30
+ },
31
+ "dependencies": {},
32
+ "devDependencies": {
33
+ "react": "^18.3.1",
34
+ "react-dom": "^18.3.1",
35
+ "@chromatic-com/storybook": "3.2.2",
36
+ "@eslint/js": "^9.15.0",
37
+ "@types/react": "^18.3.12",
38
+ "@types/react-dom": "^18.3.1",
39
+ "@vitejs/plugin-react": "^4.3.4",
40
+ "eslint": "^9.15.0",
41
+ "eslint-plugin-react": "^7.37.2",
42
+ "eslint-plugin-react-hooks": "^5.0.0",
43
+ "eslint-plugin-react-refresh": "^0.4.14",
44
+ "eslint-plugin-storybook": "0.11.1",
45
+ "globals": "^15.12.0",
46
+ "prop-types": "15.8.1",
47
+ "vite": "^6.0.1"
48
+ },
49
+ "license": "MIT",
50
+ "publishConfig": {
51
+ "access": "public",
52
+ "registry": "https://registry.npmjs.org/"
53
+ }
54
+ }
@@ -0,0 +1,200 @@
1
+ const normalizeBaseUrl = (url) => {
2
+ let tempUrl = url;
3
+ if (tempUrl.endsWith('/')) tempUrl = tempUrl.slice(0, -1);
4
+ return tempUrl;
5
+ }
6
+
7
+ export default class SlimFaasPlanetSaver {
8
+ constructor(baseUrl, options = {}) {
9
+ this.baseUrl = normalizeBaseUrl(baseUrl);
10
+ this.updateCallback = options.updateCallback || (() => {});
11
+ this.errorCallback = options.errorCallback || (() => {});
12
+ this.interval = options.interval || 5000;
13
+ this.overlayStartingMessage = options.overlayStartingMessage || 'Starting in progress...';
14
+ this.overlayNoActivityMessage = options.overlayNoActivityMessage || 'Waiting activity to start environment...';
15
+ this.overlayErrorMessage = options.overlayErrorMessage || 'An error occured when starting environment. Please contact an administrator.';
16
+ this.fetch = options.fetch || fetch;
17
+ this.intervalId = null;
18
+ this.isDocumentVisible = !document.hidden;
19
+ this.overlayElement = null;
20
+ this.styleElement = null;
21
+ this.isReady = false;
22
+
23
+ // Initialize lastMouseMoveTime and bind the handler
24
+ this.lastMouseMoveTime = Date.now();
25
+ this.handleMouseMove = this.handleMouseMove.bind(this);
26
+ this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
27
+
28
+ document.addEventListener('visibilitychange', this.handleVisibilityChange);
29
+ document.addEventListener('mousemove', this.handleMouseMove);
30
+
31
+ this.createOverlay();
32
+ this.injectStyles();
33
+
34
+ this.events = document.createElement('div');
35
+ }
36
+
37
+ handleMouseMove() {
38
+ this.lastMouseMoveTime = Date.now();
39
+ }
40
+
41
+ handleVisibilityChange() {
42
+ this.isDocumentVisible = !document.hidden;
43
+ if (this.isDocumentVisible) {
44
+ this.startPolling();
45
+ } else {
46
+ this.stopPolling();
47
+ }
48
+ }
49
+
50
+ async wakeUpPods(data) {
51
+ const wakePromises = data
52
+ .filter((item) => item.NumberReady === 0)
53
+ .map((item) =>
54
+ this.fetch(`${this.baseUrl}/wake-function/${item.Name}`, {
55
+ method: 'POST',
56
+ })
57
+ );
58
+
59
+ try {
60
+ await Promise.all(wakePromises);
61
+ } catch (error) {
62
+ console.error("Error waking up pods:", error);
63
+ }
64
+ }
65
+
66
+ async fetchStatus() {
67
+ try {
68
+ const response = await this.fetch(`${this.baseUrl}/status-functions`);
69
+ if (!response.ok) {
70
+ throw new Error(`HTTP error! status: ${response.status}`);
71
+ }
72
+ const data = await response.json();
73
+
74
+ const allReady = data.every((item) => item.NumberReady >= 1);
75
+ this.setReadyState(allReady);
76
+
77
+ this.updateCallback(data);
78
+
79
+ const now = Date.now();
80
+ const mouseMovedRecently = now - this.lastMouseMoveTime <= 60000; // 1 minute in milliseconds
81
+
82
+ if (!allReady && this.isDocumentVisible && !mouseMovedRecently) {
83
+ this.updateOverlayMessage(this.overlayNoActivityMessage);
84
+ }
85
+
86
+ if (!this.isDocumentVisible || mouseMovedRecently) {
87
+ this.updateOverlayMessage(this.overlayStartingMessage);
88
+ await this.wakeUpPods(data);
89
+ }
90
+ } catch (error) {
91
+ const errorMessage = error.message;
92
+ this.updateOverlayMessage(this.overlayErrorMessage);
93
+ this.errorCallback(errorMessage);
94
+ this.triggerEvent('error', { message: errorMessage });
95
+ console.error('Error getting slimfaas data:', errorMessage);
96
+ } finally {
97
+ this.intervalId = setTimeout(() => {
98
+ this.fetchStatus();
99
+ }, this.interval);
100
+ }
101
+ }
102
+
103
+ setReadyState(isReady) {
104
+ this.isReady = isReady;
105
+ if (isReady) {
106
+ this.hideOverlay();
107
+ } else {
108
+ this.showOverlay();
109
+ }
110
+ }
111
+
112
+ startPolling() {
113
+ if (this.intervalId || !this.baseUrl) return;
114
+
115
+ this.fetchStatus();
116
+
117
+ this.intervalId = setTimeout(() => {
118
+ this.fetchStatus();
119
+ }, this.interval);
120
+ }
121
+
122
+ stopPolling() {
123
+ if (this.intervalId) {
124
+ clearTimeout(this.intervalId);
125
+ this.intervalId = null;
126
+ }
127
+ }
128
+
129
+ injectStyles() {
130
+ const cssString = `
131
+ .environment-overlay {
132
+ position: fixed;
133
+ top: 0;
134
+ left: 0;
135
+ width: 100%;
136
+ cursor: not-allowed;
137
+ height: 100%;
138
+ background-color: rgba(0, 0, 0, 0.5);
139
+ display: flex;
140
+ justify-content: center;
141
+ align-items: center;
142
+ color: white;
143
+ font-size: 1.5rem;
144
+ font-weight: bold;
145
+ z-index: 1000;
146
+ visibility: hidden;
147
+ }
148
+
149
+ .environment-overlay.visible {
150
+ visibility: visible;
151
+ }
152
+ `;
153
+
154
+ this.styleElement = document.createElement('style');
155
+ this.styleElement.textContent = cssString;
156
+ document.head.appendChild(this.styleElement);
157
+ }
158
+
159
+ createOverlay() {
160
+ this.overlayElement = document.createElement('div');
161
+ this.overlayElement.className = 'environment-overlay';
162
+ this.overlayElement.innerText = this.overlayMessage;
163
+ document.body.appendChild(this.overlayElement);
164
+ }
165
+
166
+ showOverlay() {
167
+ if (this.overlayElement) {
168
+ this.overlayElement.classList.add('visible');
169
+ }
170
+ }
171
+
172
+ hideOverlay() {
173
+ if (this.overlayElement) {
174
+ this.overlayElement.classList.remove('visible');
175
+ }
176
+ }
177
+
178
+ updateOverlayMessage(newMessage) {
179
+ if (this.overlayElement) {
180
+ this.overlayElement.innerText = newMessage;
181
+ }
182
+ }
183
+
184
+ triggerEvent(eventName, detail) {
185
+ const event = new CustomEvent(eventName, { detail });
186
+ this.events.dispatchEvent(event);
187
+ }
188
+
189
+ cleanup() {
190
+ this.stopPolling();
191
+ document.removeEventListener('visibilitychange', this.handleVisibilityChange);
192
+ document.removeEventListener('mousemove', this.handleMouseMove); // Remove mousemove listener
193
+ if (this.overlayElement) {
194
+ document.body.removeChild(this.overlayElement);
195
+ }
196
+ if (this.styleElement) {
197
+ document.head.removeChild(this.styleElement);
198
+ }
199
+ }
200
+ }