Compare commits

..

2 commits

Author SHA1 Message Date
Sami Mokaddem
4529017748 chg: [app] Replaced _.throttle by _.debounce to have the desired effect 2024-07-02 09:39:19 +02:00
Sami Mokaddem
0fe9d9183a chg: [app] Performance improvements 2024-07-02 09:23:08 +02:00
12 changed files with 889 additions and 838 deletions

778
dist/assets/index-CG7kEUoH.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View file

@ -5,8 +5,8 @@
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title> <title>Vite App</title>
<script type="module" crossorigin src="/assets/index-D76yy8_K.js"></script> <script type="module" crossorigin src="/assets/index-CG7kEUoH.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CvXX7jiP.css"> <link rel="stylesheet" crossorigin href="/assets/index-DlglK08D.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View file

@ -9,6 +9,7 @@ from urllib.parse import parse_qs
VERBOSE_MODE = False VERBOSE_MODE = False
NOTIFICATION_COUNT = 1
def set_verbose_mode(enabled: bool): def set_verbose_mode(enabled: bool):
@ -107,6 +108,9 @@ def is_api_request(data: dict) -> bool:
def get_notification_message(data: dict) -> dict: def get_notification_message(data: dict) -> dict:
global NOTIFICATION_COUNT
id = NOTIFICATION_COUNT
NOTIFICATION_COUNT += 1
user = db.USER_ID_TO_EMAIL_MAPPING.get(int(data['user_id']), '?') user = db.USER_ID_TO_EMAIL_MAPPING.get(int(data['user_id']), '?')
time = data['created'].split(' ')[1].split('.')[0] time = data['created'].split(' ')[1].split('.')[0]
url = data['url'] url = data['url']
@ -117,6 +121,7 @@ def get_notification_message(data: dict) -> dict:
http_method = 'DELETE' if (http_method == 'POST' or http_method == 'PUT') and action == 'delete' else http_method # small override for UI http_method = 'DELETE' if (http_method == 'POST' or http_method == 'PUT') and action == 'delete' else http_method # small override for UI
payload = get_request_post_body(data) payload = get_request_post_body(data)
return { return {
'id': id,
'user': user, 'user': user,
'time': time, 'time': time,
'url': url, 'url': url,

7
package-lock.json generated
View file

@ -13,6 +13,7 @@
"@fortawesome/free-regular-svg-icons": "^6.5.2", "@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/vue-fontawesome": "^3.0.8", "@fortawesome/vue-fontawesome": "^3.0.8",
"lodash.debounce": "^4.0.8",
"vue": "^3.4.29" "vue": "^3.4.29"
}, },
"devDependencies": { "devDependencies": {
@ -2347,6 +2348,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true "dev": true
}, },
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
},
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",

View file

@ -16,6 +16,7 @@
"@fortawesome/free-regular-svg-icons": "^6.5.2", "@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/vue-fontawesome": "^3.0.8", "@fortawesome/vue-fontawesome": "^3.0.8",
"lodash.debounce": "^4.0.8",
"vue": "^3.4.29" "vue": "^3.4.29"
}, },
"devDependencies": { "devDependencies": {

View file

@ -16,6 +16,7 @@ onMounted(() => {
<template> <template>
<main> <main>
<h1 class="text-2xl text-center text-slate-500 dark:text-slate-400 absolute top-1 left-1">MISP Exercise Dashboard</h1>
<div class="absolute top-1 right-1"> <div class="absolute top-1 right-1">
<div class="flex gap-2"> <div class="flex gap-2">
<TheThemeButton></TheThemeButton> <TheThemeButton></TheThemeButton>

View file

@ -19,7 +19,6 @@ onMounted(() => {
</script> </script>
<template> <template>
<h1 class="text-3xl font-bold text-center text-slate-600 dark:text-slate-300">MISP Exercise Dashboard</h1>
<TheScores></TheScores> <TheScores></TheScores>
<TheLiveLogs></TheLiveLogs> <TheLiveLogs></TheLiveLogs>
</template> </template>

View file

@ -1,6 +1,6 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { exercises, selected_exercises, diagnostic, resetAllExerciseProgress, resetLiveLoggs, changeExerciseSelection, fetchDiagnostic } from "@/socket"; import { exercises, selected_exercises, diagnostic, resetAllExerciseProgress, resetLiveLogs, changeExerciseSelection, debouncedGetDiangostic } from "@/socket";
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faScrewdriverWrench, faTrash, faSuitcaseMedical, faGraduationCap, faBan } from '@fortawesome/free-solid-svg-icons' import { faScrewdriverWrench, faTrash, faSuitcaseMedical, faGraduationCap, faBan } from '@fortawesome/free-solid-svg-icons'
@ -17,7 +17,7 @@
function showTheModal() { function showTheModal() {
admin_modal.value.showModal() admin_modal.value.showModal()
fetchDiagnostic() debouncedGetDiangostic()
} }
</script> </script>
@ -52,7 +52,7 @@
Reset All Exercises Reset All Exercises
</button> </button>
<button <button
@click="resetLiveLoggs()" @click="resetLiveLogs()"
class="h-10 min-h-10 px-2 py-1 font-semibold bg-amber-600 text-slate-200 hover:bg-amber-700 btn btn-sm" class="h-10 min-h-10 px-2 py-1 font-semibold bg-amber-600 text-slate-200 hover:bg-amber-700 btn btn-sm"
> >
<FontAwesomeIcon :icon="faBan" class=""></FontAwesomeIcon> <FontAwesomeIcon :icon="faBan" class=""></FontAwesomeIcon>

View file

@ -79,7 +79,7 @@
</td> </td>
</tr> </tr>
<template v-else> <template v-else>
<tr v-for="(notification, index) in notifications" :key="index"> <tr v-for="notification in notifications" :key="notification.id">
<td <td
class="border-b border-slate-100 dark:border-slate-700 text-slate-600 dark:text-slate-400 p-1 pl-2 w-12 whitespace-nowrap" class="border-b border-slate-100 dark:border-slate-700 text-slate-600 dark:text-slate-400 p-1 pl-2 w-12 whitespace-nowrap"
> >

View file

@ -1,5 +1,6 @@
import { reactive, computed } from "vue"; import { reactive, computed } from "vue";
import { io } from "socket.io-client"; import { io } from "socket.io-client";
import debounce from 'lodash.debounce'
// "undefined" means the URL will be computed from the `window.location` object // "undefined" means the URL will be computed from the `window.location` object
const URL = process.env.NODE_ENV === "production" ? undefined : "http://localhost:3000"; const URL = process.env.NODE_ENV === "production" ? undefined : "http://localhost:3000";
@ -18,7 +19,16 @@ const initial_state = {
const state = reactive({ ...initial_state }); const state = reactive({ ...initial_state });
const connectionState = reactive({ const connectionState = reactive({
connected: false connected: false
}) })
const socket = io(URL, {
autoConnect: true
});
/* Public */
/* ------ */
export const exercises = computed(() => state.exercises) export const exercises = computed(() => state.exercises)
export const selected_exercises = computed(() => state.selected_exercises) export const selected_exercises = computed(() => state.selected_exercises)
@ -36,25 +46,10 @@ export function resetState() {
} }
export function fullReload() { export function fullReload() {
socket.emit("get_exercises", (all_exercises) => { getExercises()
state.exercises = all_exercises getSelectedExercises()
}) getNotifications()
socket.emit("get_selected_exercises", (all_selected_exercises) => { getProgress()
state.selected_exercises = all_selected_exercises
})
socket.emit("get_notifications", (all_notifications) => {
state.notificationEvents = all_notifications
})
socket.emit("get_progress", (all_progress) => {
state.progresses = all_progress
})
}
export function fetchDiagnostic() {
state.diagnostic = {}
socket.emit("get_diagnostic", (diagnostic) => {
state.diagnostic = diagnostic
})
} }
export function setCompletedState(completed, user_id, exec_uuid, task_uuid) { export function setCompletedState(completed, user_id, exec_uuid, task_uuid) {
@ -63,28 +58,15 @@ export function setCompletedState(completed, user_id, exec_uuid, task_uuid) {
exercise_uuid: exec_uuid, exercise_uuid: exec_uuid,
task_uuid: task_uuid, task_uuid: task_uuid,
} }
const event_name = !completed ? "mark_task_completed": "mark_task_incomplete" sendCompletedState(completed, payload)
socket.emit(event_name, payload, () => {
socket.emit("get_progress", (all_progress) => {
state.progresses = all_progress
})
})
} }
export function resetAllExerciseProgress() { export function resetAllExerciseProgress() {
socket.emit("reset_all_exercise_progress", () => { sendResetAllExerciseProgress()
socket.emit("get_progress", (all_progress) => {
state.progresses = all_progress
})
})
} }
export function resetLiveLoggs() { export function resetLiveLogs() {
socket.emit("reset_notifications", () => { sendResetLiveLogs()
socket.emit("get_notifications", (all_notifications) => {
state.notificationEvents = all_notifications
})
})
} }
export function changeExerciseSelection(exec_uuid, state_enabled) { export function changeExerciseSelection(exec_uuid, state_enabled) {
@ -92,24 +74,84 @@ export function changeExerciseSelection(exec_uuid, state_enabled) {
exercise_uuid: exec_uuid, exercise_uuid: exec_uuid,
selected: state_enabled, selected: state_enabled,
} }
socket.emit("change_exercise_selection", payload, () => { sendChangeExerciseSelection(payload)
socket.emit("get_selected_exercises", (all_selected_exercises) => {
state.selected_exercises = all_selected_exercises
})
})
} }
export function toggleVerboseMode(enabled) { export function toggleVerboseMode(enabled) {
sendToggleVerboseMode(enabled)
}
export const debouncedGetProgress = debounce(getProgress, 200, {leading: true})
export const debouncedGetDiangostic = debounce(getDiangostic, 1000, {leading: true})
/* Private */
/* ------- */
function getExercises() {
socket.emit("get_exercises", (all_exercises) => {
state.exercises = all_exercises
})
}
function getSelectedExercises() {
socket.emit("get_selected_exercises", (all_selected_exercises) => {
state.selected_exercises = all_selected_exercises
})
}
function getNotifications() {
socket.emit("get_notifications", (all_notifications) => {
state.notificationEvents = all_notifications
})
}
function getProgress() {
socket.emit("get_progress", (all_progress) => {
state.progresses = all_progress
})
}
function getDiangostic() {
state.diagnostic = {}
socket.emit("get_diagnostic", (diagnostic) => {
state.diagnostic = diagnostic
})
}
function sendCompletedState(completed, payload) {
const event_name = !completed ? "mark_task_completed": "mark_task_incomplete"
socket.emit(event_name, payload, () => {
getProgress()
})
}
function sendResetAllExerciseProgress() {
socket.emit("reset_all_exercise_progress", () => {
getProgress()
})
}
function sendResetLiveLogs() {
socket.emit("reset_notifications", () => {
getNotifications()
})
}
function sendChangeExerciseSelection(payload) {
socket.emit("change_exercise_selection", payload, () => {
getSelectedExercises()
})
}
function sendToggleVerboseMode(enabled) {
const payload = { const payload = {
verbose: enabled verbose: enabled
} }
socket.emit("toggle_verbose_mode", payload, () => {}) socket.emit("toggle_verbose_mode", payload, () => {})
} }
/* Event listener */
const socket = io(URL, {
autoConnect: true
});
socket.on("connect", () => { socket.on("connect", () => {
connectionState.connected = true; connectionState.connected = true;
@ -128,15 +170,11 @@ socket.on("notification", (message) => {
}); });
socket.on("new_user", (new_user) => { socket.on("new_user", (new_user) => {
socket.emit("get_progress", (all_progress) => { debouncedGetProgress()
state.progresses = all_progress
})
}); });
socket.on("refresh_score", (new_user) => { socket.on("refresh_score", (new_user) => {
socket.emit("get_progress", (all_progress) => { debouncedGetProgress()
state.progresses = all_progress
})
}); });
function addLimited(target, message, maxCount) { function addLimited(target, message, maxCount) {