vesta-web 1.1.0__py3-none-any.whl → 1.1.2__py3-none-any.whl
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.
- vesta/db/db_service.py +10 -3
- vesta/emptyProject/.gitlab-ci.yml +0 -1
- vesta/emptyProject/requirements.txt +1 -0
- vesta/emptyProject/static/framework/global.mjs +11 -0
- vesta/emptyProject/static/framework/navigation.mjs +295 -0
- vesta/emptyProject/static/framework/templating.mjs +110 -0
- vesta/emptyProject/static/framework/vesta.mjs +173 -0
- vesta/emptyProject/static/framework/websockets.mjs +41 -0
- vesta/emptyProject/static/markdown/markdown.mjs +463 -0
- vesta/emptyProject/static/markdown/utils.mjs +21 -0
- vesta/http/baseServer.py +1 -1
- vesta/http/response.py +15 -5
- vesta/mailing/mailing_service.py +10 -9
- vesta/scripts/initDB.py +11 -9
- vesta/scripts/testsRun.py +6 -6
- {vesta_web-1.1.0.dist-info → vesta_web-1.1.2.dist-info}/METADATA +1 -1
- {vesta_web-1.1.0.dist-info → vesta_web-1.1.2.dist-info}/RECORD +21 -14
- vesta_web-1.1.2.dist-info/entry_points.txt +2 -0
- vesta_web-1.1.0.dist-info/entry_points.txt +0 -2
- /vesta/scripts/{vesta.py → cli.py} +0 -0
- {vesta_web-1.1.0.dist-info → vesta_web-1.1.2.dist-info}/WHEEL +0 -0
- {vesta_web-1.1.0.dist-info → vesta_web-1.1.2.dist-info}/licenses/LICENSE.md +0 -0
vesta/db/db_service.py
CHANGED
|
@@ -152,7 +152,7 @@ class DB:
|
|
|
152
152
|
else:
|
|
153
153
|
return []
|
|
154
154
|
except Exception as e:
|
|
155
|
-
print(f"[VESTA] An error occurred with the db: {e}")
|
|
155
|
+
print(f"[VESTA] An error occurred with the db in getFilters: {e}")
|
|
156
156
|
self.conn.rollback()
|
|
157
157
|
|
|
158
158
|
def insertDict(self, table, dict, getId=False):
|
|
@@ -194,8 +194,15 @@ class DB:
|
|
|
194
194
|
self.conn.commit()
|
|
195
195
|
|
|
196
196
|
def resetTable(self, table):
|
|
197
|
-
|
|
198
|
-
self.cur.execute(
|
|
197
|
+
# Use SQL identifiers to prevent SQL injection
|
|
198
|
+
self.cur.execute(
|
|
199
|
+
sql.SQL("DELETE FROM {} CASCADE").format(sql.Identifier(table))
|
|
200
|
+
)
|
|
201
|
+
self.cur.execute(
|
|
202
|
+
sql.SQL("ALTER SEQUENCE {} RESTART WITH 1").format(
|
|
203
|
+
sql.Identifier(table + "_id_seq")
|
|
204
|
+
)
|
|
205
|
+
)
|
|
199
206
|
self.conn.commit()
|
|
200
207
|
|
|
201
208
|
def edit(self, table, id, element, value, selector='id'):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vesta-web
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import global from "./global.mjs";
|
|
2
|
+
import {xhr} from "./templating.mjs";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Opens a menu by its DOM id. If the menu element exists, it is displayed. Otherwise, loads the menu template asynchronously.
|
|
7
|
+
* @param {string} id - The DOM id of the menu to open.
|
|
8
|
+
* @param {boolean} [async=true] - Whether to load the template asynchronously if not found.
|
|
9
|
+
*/
|
|
10
|
+
function openMenu(id, async=true){
|
|
11
|
+
const menu = document.getElementById(id)
|
|
12
|
+
if (menu){
|
|
13
|
+
menu.style.display = "flex"
|
|
14
|
+
}else{
|
|
15
|
+
loadTemplate(id.concat(".html"), undefined, id, async)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
window.openMenu = openMenu
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Toggles the display of a menu by its DOM id. If the menu exists, toggles its visibility. Otherwise, loads the menu template.
|
|
22
|
+
* @param {string} id - The DOM id of the menu to toggle.
|
|
23
|
+
* @param {boolean} [async=true] - Whether to load the template asynchronously if not found.
|
|
24
|
+
* @param {HTMLElement} [target=undefined] - Optional target HTML element for template loading.
|
|
25
|
+
*/
|
|
26
|
+
function toggleMenu(id, async=true,target=undefined){
|
|
27
|
+
const menu = document.getElementById(id)
|
|
28
|
+
if (menu){
|
|
29
|
+
if (menu.style.display === "flex"){
|
|
30
|
+
menu.style.display = "none"
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
menu.style.display = "flex"
|
|
34
|
+
}else{
|
|
35
|
+
loadTemplate(id.concat(".html"), target, id, async)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
window.toggleMenu = toggleMenu
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Navigates to a new view or section by loading a template and updating selection state.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} id - The id of the element to inject the template into.
|
|
44
|
+
* @param {string} target - The base name of the template to load (without .html).
|
|
45
|
+
* @param {Object} [selected=undefined] - Optional selection context {category | event | id} allowing to add/remove the "selected" class.
|
|
46
|
+
* @param {boolean} [async=true] - Whether to load the template asynchronously.
|
|
47
|
+
* @param {Function} [postInsert=undefined] - Optional callback to run after template insertion.
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
50
|
+
export function goTo(id, target, selected=undefined, async=true, postInsert=undefined){
|
|
51
|
+
if (postInsert){
|
|
52
|
+
loadTemplate(target.concat(".html"), id, undefined, false)
|
|
53
|
+
postInsert()
|
|
54
|
+
}else{
|
|
55
|
+
loadTemplate(target.concat(".html"), id, undefined, async)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if(selected){
|
|
59
|
+
let targetElt;
|
|
60
|
+
if(global.state[selected.category]){
|
|
61
|
+
global.state[selected.category].classList.remove("selected")
|
|
62
|
+
}else if(selected.event){
|
|
63
|
+
selected.event.currentTarget.parentElement.querySelector('.selected').classList.remove("selected")
|
|
64
|
+
}else if(selected.id){
|
|
65
|
+
targetElt = document.getElementById(selected.id)
|
|
66
|
+
targetElt.parentElement.querySelector('.selected').classList.remove("selected")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if(selected.event){
|
|
70
|
+
global.state[selected.category] = selected.event.currentTarget
|
|
71
|
+
}else if(selected.id){
|
|
72
|
+
global.state[selected.category] = targetElt
|
|
73
|
+
}
|
|
74
|
+
global.state[selected.category].classList.add("selected")
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
window.goTo = goTo
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
let openingFM = false;
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Toggles the visibility of a floating menu (FM) by its DOM id.
|
|
85
|
+
* If another FM is open, it will be hidden and the new one will be shown.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} id - The DOM id of the floating menu to toggle.
|
|
88
|
+
*/
|
|
89
|
+
function toggleFM(id){
|
|
90
|
+
const FM = document.getElementById(id)
|
|
91
|
+
if(FM !== global.state.activeFM){
|
|
92
|
+
openingFM = true
|
|
93
|
+
if(global.state.activeFM){
|
|
94
|
+
global.state.activeFM.classList.toggle("visible")
|
|
95
|
+
}
|
|
96
|
+
global.state.activeFM = FM
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
window.toggleFM = toggleFM
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Closes the currently active floating menu (FM) if one is open.
|
|
103
|
+
*/
|
|
104
|
+
export function closeFM(){
|
|
105
|
+
if(global.state.activeFM){
|
|
106
|
+
global.state.activeFM.classList.toggle("visible")
|
|
107
|
+
global.state.activeFM = undefined
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Toggles the display of a modal window at the mouse event position.
|
|
113
|
+
* If another modal is open, it will be hidden and the new one will be shown at the event's coordinates.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} id - The DOM id of the modal to toggle.
|
|
116
|
+
* @param {Event} event - The event triggering the modal (used for positioning).
|
|
117
|
+
*/
|
|
118
|
+
function toggleModale(id, event){
|
|
119
|
+
const FM = document.getElementById(id)
|
|
120
|
+
if(FM !== global.state.activeFM){
|
|
121
|
+
openingFM = true
|
|
122
|
+
global.state.modaltarget = event.currentTarget
|
|
123
|
+
if(global.state.activeFM){
|
|
124
|
+
global.state.activeFM.classList.toggle("visible")
|
|
125
|
+
}
|
|
126
|
+
console.log(event.currentTarget,global.state.modaltarget)
|
|
127
|
+
FM.style.top = event.clientY.toString().concat("px")
|
|
128
|
+
FM.style.left = event.clientX.toString().concat("px")
|
|
129
|
+
global.state.activeFM = FM
|
|
130
|
+
if (event.type === "contextmenu"){
|
|
131
|
+
event.preventDefault()
|
|
132
|
+
global.state.activeFM.classList.add("visible")
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
window.toggleModale = toggleModale
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Toggles the closed/open state of a group element (e.g., collapsible section).
|
|
140
|
+
*/
|
|
141
|
+
function toggleGroup(){
|
|
142
|
+
event.currentTarget.classList.toggle("closed")
|
|
143
|
+
}
|
|
144
|
+
window.toggleGroup = toggleGroup
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Navigates to a specific step in a multi-step menu by translating the menu horizontally.
|
|
148
|
+
*
|
|
149
|
+
* @param {number} step - The step index to navigate to.
|
|
150
|
+
* @param {string} stepsID - The DOM id of the steps container.
|
|
151
|
+
*/
|
|
152
|
+
function gotoStep(step,stepsID){
|
|
153
|
+
const menu = document.getElementById(stepsID)
|
|
154
|
+
menu.style.transform=`translateX(${-100*step/menu.childElementCount}%)`
|
|
155
|
+
}
|
|
156
|
+
window.gotoStep = gotoStep
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Removes the 'selected' class from all elements with class 'selected' inside the given container.
|
|
160
|
+
* Used for resetting selection state in multi-step menus.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} id - The DOM id of the container to reset selection in.
|
|
163
|
+
*/
|
|
164
|
+
function resetSelected(id){
|
|
165
|
+
const selected = document.getElementById(id).querySelectorAll(".selected")
|
|
166
|
+
for(let el of selected){
|
|
167
|
+
el.classList.remove("selected")
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Closes a menu by hiding it and resetting its state. Can target a specific element or use the event target.
|
|
173
|
+
*
|
|
174
|
+
* @param {string|undefined} cible - CSS selector of the element to close, or undefined to use event target.
|
|
175
|
+
* @param {string} [id='createServerSteps'] - The DOM id of the steps container to reset.
|
|
176
|
+
*
|
|
177
|
+
*/
|
|
178
|
+
function closeMenu(cible = undefined,id='createServerSteps'){
|
|
179
|
+
if (global.state.disableClose){
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if(!cible){
|
|
184
|
+
if(event.target !== event.currentTarget){
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
event.target.style.display = "none";
|
|
188
|
+
}else{
|
|
189
|
+
const cibleEl= document.querySelector(cible)
|
|
190
|
+
cibleEl.style.display = "none";
|
|
191
|
+
}
|
|
192
|
+
gotoStep(0,id)
|
|
193
|
+
resetSelected(id)
|
|
194
|
+
}
|
|
195
|
+
window.closeMenu = closeMenu
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Saves the current cursor position in a text input or textarea to global state.
|
|
199
|
+
* Used to paste emojis in a textarea from an emoji-keyboard.
|
|
200
|
+
*
|
|
201
|
+
* @param {Event} event - The input event containing the cursor position.
|
|
202
|
+
*/
|
|
203
|
+
function saveCursorPosition(event){
|
|
204
|
+
global.state.previousCursor={start:event.srcElement.selectionStart, end:event.srcElement.selectionEnd}
|
|
205
|
+
}
|
|
206
|
+
window.saveCursorPosition = saveCursorPosition
|
|
207
|
+
|
|
208
|
+
function isMobileDevice() {
|
|
209
|
+
return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function initNavigation(){
|
|
213
|
+
window.global = global
|
|
214
|
+
global.state.dom = document.querySelector("body")
|
|
215
|
+
|
|
216
|
+
const { hostname, port } = window.location;
|
|
217
|
+
global.state.location = { host:hostname, port:port, short:port ? hostname + ":" + port : hostname }
|
|
218
|
+
|
|
219
|
+
global.state.isMobile = isMobileDevice()
|
|
220
|
+
if (global.state.isMobile) {
|
|
221
|
+
global.state.dom.classList.add("mobile")
|
|
222
|
+
|
|
223
|
+
const onload = function(){
|
|
224
|
+
console.log(this.responseText)
|
|
225
|
+
}
|
|
226
|
+
xhr("/static/mobileUiManifest.mjs", onload)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
document.addEventListener('click', function (event) {
|
|
230
|
+
if (global.state.activeFM && !global.state.activeFM.contains(event.target)) {
|
|
231
|
+
global.state.activeFM.classList.toggle("visible")
|
|
232
|
+
if (!openingFM){
|
|
233
|
+
global.state.activeFM = undefined
|
|
234
|
+
}else{
|
|
235
|
+
openingFM=false
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}, false);
|
|
239
|
+
|
|
240
|
+
if (global.state.socket){
|
|
241
|
+
window.addEventListener('unload', () => {
|
|
242
|
+
if (global.state?.socket.readyState !== WebSocket.CLOSED) {
|
|
243
|
+
const message = {"type" : 'unregister', "clientID":global.state.clientID}
|
|
244
|
+
global.state.socket.send(JSON.stringify(message))
|
|
245
|
+
global.state.socket.close()
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
function print(...args){
|
|
254
|
+
const green ="#68f66e"
|
|
255
|
+
const blue ="#4284f5"
|
|
256
|
+
const yellow ="#fef972"
|
|
257
|
+
const red ="#ee6966"
|
|
258
|
+
const pink ="#fb7bfa"
|
|
259
|
+
const purple ="#6a76fa"
|
|
260
|
+
|
|
261
|
+
const colors = [green,blue,green,"white",green,blue,"white",yellow,blue,"white",yellow,"white",yellow,red,pink,purple];
|
|
262
|
+
console.log(`%c${args.join(' ')}`, ...colors.map(c => `color: ${c};`));
|
|
263
|
+
// console.log(colors.map(c => `%c${c}`).join(''), ...colors.map(c => `background: ${c};`));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
export function printWatermark(name, git){
|
|
268
|
+
print(` %c ${name}\n` +
|
|
269
|
+
"%c⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ %c -----------------------------------\n" +
|
|
270
|
+
"%c⠀⠀⠀⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ %c Credits%c: Lou ! \n" +
|
|
271
|
+
`%c⠀⠀⠀⠀⠀⠀⠀⠸⣷⣦⣀⠀⠀⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀ %c Git%c: ${git}\n` +
|
|
272
|
+
"%c⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣿⣿⣦⠀⠠⠾⠿⣿⣷⠀⠀⠀⠀⠀⣠⣤⣄⠀⠀⠀\n" +
|
|
273
|
+
"⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠟⢉⣠⣤⣶⡆⠀⣠⣈⠀⢀⣠⣴⣿⣿⠋⠀⠀⠀⠀ %c Powered by Vesta ! \n" +
|
|
274
|
+
"%c⠀⢀⡀⢀⣀⣀⣠⣤⡄⢀⣀⡘⣿⣿⣿⣷⣼⣿⣿⣷⡄⠹⣿⡿⠁⠀⠀⠀⠀⠀\n" +
|
|
275
|
+
"%c⠀⠀⠻⠿⢿⣿⣿⣿⠁⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⣁⠀⠋⠀⠀⠀⠀⠀⠀⠀ \n" +
|
|
276
|
+
"⠀⠀⠀⠀⠀⠀⠈⠻⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⢰⣄⣀⠀⠀⠀⠀⠀\n" +
|
|
277
|
+
"%c⠀⠀ ⠀⠀⠀⠀⣠⡀⠀⣴⣿⣿⣿⣿⣿⣿⣿⡿⢿⡿⠀⣾⣿⣿⣿⣿⣶⡄⠀ \n" +
|
|
278
|
+
"⠀⠀⠀⠀⠀⢀⣾⣿⣷⡀⠻⣿⣿⡿⠻⣿⣿⣿⣿⠀⠀⠈⠉⠉⠉⠀⠀⠀⠀⠀\n" +
|
|
279
|
+
"⠀⠀⠀⠀⣠⣾⡿⠟⠉⠉⠀⢀⡉⠁⠀⠛⠛⢉⣠⣴⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀\n" +
|
|
280
|
+
"%c⠀⠀⠀⠈⠉⠉⠀⠀⠀⠀⠀⢸⣿⣿⡿⠉⠀⠙⠿⣿⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀\n" +
|
|
281
|
+
"⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⠁⠀⠀⠀⠀⠀⠙⠿⣷⠀⠀⠀⠀⠀⠀⠀\n" +
|
|
282
|
+
"⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⠀⠀")
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function hideLoadingScreen() {
|
|
286
|
+
const loadingScreen = document.getElementById('loading-screen');
|
|
287
|
+
|
|
288
|
+
// Fade out the loading screen
|
|
289
|
+
loadingScreen.classList.add('fade-out');
|
|
290
|
+
|
|
291
|
+
// Optional: wait until the transition ends before removing or showing content
|
|
292
|
+
loadingScreen.addEventListener('transitionend', () => {
|
|
293
|
+
loadingScreen.style.display = 'none';
|
|
294
|
+
});
|
|
295
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
let templates = {}
|
|
2
|
+
|
|
3
|
+
export function xhr(endpoint,effect,method="GET", async=true, body=undefined){
|
|
4
|
+
let xhr= new XMLHttpRequest();
|
|
5
|
+
xhr.open(method, endpoint, async);
|
|
6
|
+
xhr.onload=effect
|
|
7
|
+
xhr.onerror = function() {
|
|
8
|
+
console.log("request failed")
|
|
9
|
+
};
|
|
10
|
+
if (body) {
|
|
11
|
+
xhr.setRequestHeader("Content-Type", "application/json");
|
|
12
|
+
xhr.send(JSON.stringify(body));
|
|
13
|
+
}else{
|
|
14
|
+
xhr.send();
|
|
15
|
+
}
|
|
16
|
+
return xhr
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
function loadTemplate(template, target=undefined, flex= undefined, async){
|
|
21
|
+
const effect = function() {
|
|
22
|
+
if (target){
|
|
23
|
+
//maybe not using eval
|
|
24
|
+
document.getElementById(target).innerHTML = eval('`' + this.responseText + '`');
|
|
25
|
+
}else{
|
|
26
|
+
global.state.dom.insertAdjacentHTML('beforeend',eval('`' + this.responseText + '`'))
|
|
27
|
+
}
|
|
28
|
+
if (flex){
|
|
29
|
+
document.getElementById(flex).style.display = "flex"
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
xhr( '/static/templates/'.concat(template), effect,"GET", async)
|
|
34
|
+
}
|
|
35
|
+
window.loadTemplate = loadTemplate
|
|
36
|
+
|
|
37
|
+
function fillWith(template, list){
|
|
38
|
+
// console.log("fillWith",template,list,typeof list)
|
|
39
|
+
|
|
40
|
+
let request
|
|
41
|
+
if(templates[template]){
|
|
42
|
+
request={"responseText":templates[template]}
|
|
43
|
+
}else{
|
|
44
|
+
request = xhr( '/static/templates/'.concat(template,".html"), undefined, "GET", false)
|
|
45
|
+
// console.log("adding to cache",templates, template)
|
|
46
|
+
templates[template] = request.responseText
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let content = ""
|
|
50
|
+
if (typeof list == 'object'){
|
|
51
|
+
for (let elementId in list){
|
|
52
|
+
const element = list[elementId]
|
|
53
|
+
content += eval('`' + request.responseText + '`')
|
|
54
|
+
}
|
|
55
|
+
}else if (!list){
|
|
56
|
+
console.warn("fillWith called with undefined list")
|
|
57
|
+
return content
|
|
58
|
+
}else{
|
|
59
|
+
for (let element of list){
|
|
60
|
+
content += eval('`' + request.responseText + '`')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return content
|
|
65
|
+
}
|
|
66
|
+
window.fillWith = fillWith
|
|
67
|
+
|
|
68
|
+
function Subscribe(element, content, className=undefined, params=undefined){
|
|
69
|
+
//subscribe content to element, content will be reevaluated on element change
|
|
70
|
+
const domElement = document.createElement('div')
|
|
71
|
+
domElement.className = element.replaceAll('.','-').replaceAll('[','🪟').replaceAll(']','🥹')
|
|
72
|
+
|
|
73
|
+
if (className){
|
|
74
|
+
const classList = className.split(" ")
|
|
75
|
+
for (let name of classList){
|
|
76
|
+
domElement.classList.add(name)
|
|
77
|
+
}
|
|
78
|
+
domElement.dataset.defaultClass = domElement.className
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (params?.target === "class"){
|
|
82
|
+
domElement.dataset.target = params.target
|
|
83
|
+
domElement.className = domElement.className + " " + content()
|
|
84
|
+
}else if(params?.target === "style"){
|
|
85
|
+
domElement.dataset.target = params.target
|
|
86
|
+
domElement.style = content()
|
|
87
|
+
} else{
|
|
88
|
+
domElement.innerHTML = content()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if (params?.element){
|
|
94
|
+
domElement.dataset.element = params.element
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if(params?.repaint){
|
|
98
|
+
domElement.dataset.repaint = params.repaint
|
|
99
|
+
}else{
|
|
100
|
+
domElement.dataset.content = content
|
|
101
|
+
}
|
|
102
|
+
return domElement.outerHTML
|
|
103
|
+
}
|
|
104
|
+
window.Subscribe = Subscribe
|
|
105
|
+
|
|
106
|
+
function getTemplate(name){
|
|
107
|
+
const request = xhr( '/static/templates/'.concat(name,".html"), ()=>{}, "GET", false)
|
|
108
|
+
return eval('`' + request.responseText + '`')
|
|
109
|
+
}
|
|
110
|
+
window.getTemplate = getTemplate
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
function logout() {
|
|
2
|
+
const url = "/logout";
|
|
3
|
+
fetch(url, {
|
|
4
|
+
method: 'POST',
|
|
5
|
+
redirect: 'manual'
|
|
6
|
+
})
|
|
7
|
+
.then(response => {
|
|
8
|
+
const location = response.headers.get('location');
|
|
9
|
+
if (response.status === 302 && location) {
|
|
10
|
+
window.location.href = location;
|
|
11
|
+
} else {
|
|
12
|
+
// fallback: reload page or handle error
|
|
13
|
+
window.location.reload();
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
.catch(() => {
|
|
17
|
+
console.log("request failed");
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
window.logout = logout
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export function updateElement(selector,value=undefined){
|
|
25
|
+
const subscriptions = document.querySelectorAll(`[class^="${selector.replaceAll('.','-').replaceAll('[','🪟').replaceAll(']','🥹')}"]`)
|
|
26
|
+
for (let sub of subscriptions){
|
|
27
|
+
if(sub.dataset.repaint){
|
|
28
|
+
const fn = eval(sub.dataset.repaint)
|
|
29
|
+
fn()
|
|
30
|
+
}else if(sub.dataset.target === "class"){
|
|
31
|
+
|
|
32
|
+
const content = eval(sub.dataset.content)
|
|
33
|
+
if(sub.dataset.element){
|
|
34
|
+
sub.className = sub.dataset.defaultClass.concat(' ',content(eval(sub.dataset.element)))
|
|
35
|
+
}else{
|
|
36
|
+
sub.className = sub.dataset.defaultClass.concat(' ',content())
|
|
37
|
+
}
|
|
38
|
+
}else if(sub.dataset.target === "style"){
|
|
39
|
+
const content = eval(sub.dataset.content)
|
|
40
|
+
if(sub.dataset.element){
|
|
41
|
+
sub.style = content(eval(sub.dataset.element))
|
|
42
|
+
}else{
|
|
43
|
+
sub.style = content()
|
|
44
|
+
}
|
|
45
|
+
}else{
|
|
46
|
+
|
|
47
|
+
const content = eval(sub.dataset.content)
|
|
48
|
+
if(sub.dataset.element){
|
|
49
|
+
sub.innerHTML = content(eval(sub.dataset.element))
|
|
50
|
+
}else{
|
|
51
|
+
sub.innerHTML = content()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function setElement(element, value){
|
|
58
|
+
// console.log(element,'has been updated to:', value);
|
|
59
|
+
eval(`${element} = value`);
|
|
60
|
+
updateElement(element,value)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function pushElement(element, value){
|
|
64
|
+
// console.log(element,'has been added:', value);
|
|
65
|
+
eval(`${element}.push(value)`);
|
|
66
|
+
updateElement(element,value)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function addElement(element, value){
|
|
70
|
+
// console.log(element,'has been added:', value);
|
|
71
|
+
eval(`${element}[value.id] = value`);
|
|
72
|
+
updateElement(element,value)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// this deletes an element from a list BY ID
|
|
76
|
+
export function deleteElement(element, id){
|
|
77
|
+
// console.log(element,'has been removed:', element[id]);
|
|
78
|
+
eval(`${element}.splice(id,1)`);
|
|
79
|
+
updateElement(element)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// this deletes an element from a dict BY ID
|
|
83
|
+
export function deleteElementDict(element, id){
|
|
84
|
+
// console.log(element,'has been removed:', element[id]);
|
|
85
|
+
eval(`delete ${element}[id]`);
|
|
86
|
+
updateElement(element)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// this deletes an element from a list BY VALUE
|
|
90
|
+
export function deleteVal(element, val){
|
|
91
|
+
// console.log(element,'has been removed:', element[id]);
|
|
92
|
+
eval(`${element} = ${element}.filter(item => item !== val)`);
|
|
93
|
+
updateElement(element)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function checkEnter(event, effect){
|
|
97
|
+
if (event.key === "Enter"){
|
|
98
|
+
effect()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
window.checkEnter = checkEnter
|
|
102
|
+
|
|
103
|
+
function getTimeStr(timestamp, options = { locale: "fr-FR" }) {
|
|
104
|
+
const date = new Date(timestamp);
|
|
105
|
+
|
|
106
|
+
const defaultOptions = {
|
|
107
|
+
year: "numeric",
|
|
108
|
+
month: "numeric",
|
|
109
|
+
day: "numeric",
|
|
110
|
+
hour: "numeric",
|
|
111
|
+
minute: "numeric",
|
|
112
|
+
hour12: false,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const mergedOptions = { ...defaultOptions, ...options };
|
|
116
|
+
// console.log("TIMESTR",date.toLocaleDateString(undefined, mergedOptions))
|
|
117
|
+
return date.toLocaleDateString(undefined, mergedOptions);
|
|
118
|
+
}
|
|
119
|
+
window.getTimeStr = getTimeStr
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
function goodbye(){
|
|
123
|
+
if (confirm('Voulez-vous vraiment vous désinscrire et supprimer votre compte de tous les services Carbonlab ? (toutes vos informations seront effacées)')) {
|
|
124
|
+
const url = "/goodbye";
|
|
125
|
+
let request = new XMLHttpRequest();
|
|
126
|
+
request.open('POST', url, true);
|
|
127
|
+
request.onload = function() { // request successful
|
|
128
|
+
console.log("account deleted")
|
|
129
|
+
|
|
130
|
+
if (request.response === 302){
|
|
131
|
+
window.location.href = request.response.headers.get('location');
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
request.onerror = function() {
|
|
136
|
+
console.log("request failed")
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
request.send();
|
|
140
|
+
} else {
|
|
141
|
+
console.log("ouf 😖")
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
window.goodbye = goodbye
|
|
145
|
+
|
|
146
|
+
export function difference(arrKeys, dict) {
|
|
147
|
+
const result = [];
|
|
148
|
+
const dictKeys = new Set(Object.keys(dict)); // Convert dict keys to a set for efficient lookup
|
|
149
|
+
|
|
150
|
+
for (const key of arrKeys) {
|
|
151
|
+
if (!dictKeys.has(key.toString())) {
|
|
152
|
+
result.push(key);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return Array.from(result);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function generateUUID() {
|
|
159
|
+
let d = new Date().getTime();//Timestamp
|
|
160
|
+
let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now()*1000)) || 0;//Time in microseconds since page-load or 0 if unsupported
|
|
161
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
162
|
+
let r = Math.random() * 16;//random number between 0 and 16
|
|
163
|
+
if(d > 0){//Use timestamp until depleted
|
|
164
|
+
r = (d + r)%16 | 0;
|
|
165
|
+
d = Math.floor(d/16);
|
|
166
|
+
} else {//Use microseconds since page-load if supported
|
|
167
|
+
r = (d2 + r)%16 | 0;
|
|
168
|
+
d2 = Math.floor(d2/16);
|
|
169
|
+
}
|
|
170
|
+
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import global from "./global.mjs";
|
|
2
|
+
import {onMessage} from "../ws/onMessage.mjs";
|
|
3
|
+
import {WEBSOCKETS} from "/config";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export function initWebSockets(){
|
|
7
|
+
global.state.websocket = new WebSocket(WEBSOCKETS);
|
|
8
|
+
|
|
9
|
+
global.state.websocket.onopen = function(event) {
|
|
10
|
+
if (!global.user.id) {
|
|
11
|
+
const cookie = document.cookie.split('; ').find(row => row.startsWith('client' + '='));
|
|
12
|
+
if (cookie) {
|
|
13
|
+
global.user['id'] = cookie.split('=')[1];
|
|
14
|
+
} else {
|
|
15
|
+
console.log("ERROR : INVALID SESSION",document);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
console.log("Connection opened to Python WebSocket server!");
|
|
21
|
+
const message = {"type" : 'register', "uid": global.user.id};
|
|
22
|
+
global.state.websocket.send(JSON.stringify(message));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
global.state.websocket.onmessage = onMessage;
|
|
26
|
+
|
|
27
|
+
global.state.websocket.onclose = function(event) {
|
|
28
|
+
console.log(`[WS] Connexion closed - Code: ${event.code}, Reason: ${event.reason}`);
|
|
29
|
+
|
|
30
|
+
if (event.code !== 1000 && event.code !== 1001) {
|
|
31
|
+
console.log("[WS] Trying to reconnect in 3 secondes...");
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
initWebSockets();
|
|
34
|
+
}, 3000);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
global.state.websocket.onerror = function(error) {
|
|
39
|
+
console.error("WebSocket error:", error);
|
|
40
|
+
};
|
|
41
|
+
}
|