diff --git a/Dockerfile b/Dockerfile index 1c24ff9..9175f5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.2-slim as base +FROM node:22.2-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable diff --git a/assets/css/global.css b/assets/css/global.css new file mode 100644 index 0000000..22bb35d --- /dev/null +++ b/assets/css/global.css @@ -0,0 +1,21 @@ +.container-row { + display: flex; +} + +.container-row-element { + width: 100%; +} + +.container-row-element-s { + width: 50%; +} + +.container-row-element-xs { + width: 25%; +} + +.left { + text-align: left; + overflow: auto; + display: block; +} \ No newline at end of file diff --git a/components/PSPWizard.vue b/components/PSPWizard.vue index 8702c27..b927533 100644 --- a/components/PSPWizard.vue +++ b/components/PSPWizard.vue @@ -1,4 +1,5 @@ @@ -299,8 +87,8 @@ export default {
- - + +
@@ -314,7 +102,7 @@ export default {
{{ "Transform all Target Logics to " }} - @@ -335,15 +123,9 @@ export default { Stimuli: - +
  • {{ index + 1 }}. - {{ stimulus.SEL }} @@ -370,6 +152,7 @@ export default { SEL: {{ stimulus.SEL }}
+ @@ -410,13 +193,9 @@ export default { - +
@@ -463,10 +243,10 @@ export default {

Resilience Score:

- - {{ getResilienceScore() }} + {{ getResilienceScore(this.result) }} @@ -477,7 +257,7 @@ export default { - {{ getSimulationVerificationResultsPerScenario() }} + {{ getSimulationVerificationResultsPerScenario(this.result) }} @@ -488,7 +268,7 @@ export default { - {{ getSearchVerificationResultsPerScenario() }} + {{ getSearchVerificationResultsPerScenario(this.result) }} @@ -496,12 +276,12 @@ export default { + @click="startScenarioSimulation(scenario);"> Start Simulation + @click="startScenarioSearch(scenario);"> Start Search Verify Simulation @@ -540,7 +320,7 @@ export default {
  • - @@ -572,7 +352,7 @@ export default {
  • - @@ -595,55 +375,16 @@ export default { - -
    - -
    - -
    - - - -
    - Complete + Complete
    \ No newline at end of file diff --git a/composables/api.js b/composables/api.js new file mode 100644 index 0000000..de116cf --- /dev/null +++ b/composables/api.js @@ -0,0 +1,233 @@ +export async function initScenario() { + const res = await fetch("/api/initScenario", { + method: "POST" + }) + const body = await res.json() + return body.simulationID; +} + +export async function startSimulation(simulationID) { + await fetch("/api/startSimulation", { + method: "POST", + body: JSON.stringify({ + simulationID: simulationID + }) + }) +} + +export async function startSearch(simulationID) { + await fetch("/api/startSearch", { + method: "POST", + body: JSON.stringify({ + simulationID: simulationID + }) + }) +} + +export async function allScenarios() { + const response = await fetch("/api/allScenarios"); + const body = await response.json(); + return body.scenarios +} + +export async function getResult(simulationID) { + const response = await fetch("/api/getResult", { + method: "POST", + body: JSON.stringify({ + simulationID: simulationID + }) + }); + const bodyResult = await response.json(); + return bodyResult.result +} + +export async function allResults() { + return await fetch("/api/allResults"); +} + +export async function deleteScenario(simulationID) { + await fetch("/api/deleteScenario", { + method: "POST", + body: JSON.stringify({ + simulationID: simulationID + }) + }) +} + +export async function verifySimulation(scenario) { + await useFetch("/api/verifySimulation", { + method: "POST", + body: JSON.stringify({ + scenario, + }) + }); +} + +export async function verifySearch(scenario) { + await useFetch("/api/verifySearch", { + method: "POST", + body: JSON.stringify({ + scenario, + }) + }); +} + +export async function getScenario(simulationID) { + const res = await fetch("/api/getScenario", { + method: "POST", + body: JSON.stringify({ + simulationID: simulationID + }) + }) + const body = await res.json() + return body.Scenario +} + +export async function allEvents() { + const response = await fetch("/api/allEvents"); + return await response.json(); +} + +export async function allCommands() { + const response = await fetch("/api/allCommands"); + return await response.json(); +} + +export async function allListeners() { + const response = await fetch("/api/allListeners"); + return await response.json(); +} + +export async function pushScenarioField(simulationID, field, newValue) { + await fetch("/api/pushScenarioField", { + method: "POST", + body: JSON.stringify({ + simulationID: simulationID, + fieldName: field, + fieldValue: newValue + }) + }) +} + +export async function setScenarioField(simulationID, field, newValue) { + await fetch("/api/setScenarioField", { + method: "POST", + body: JSON.stringify({ + simulationID: simulationID, + fieldName: field, + fieldValue: newValue + }) + }) +} + + +export async function deleteScenarioField(simulationID, fieldName, fieldIndex) { + const body = { + simulationID: simulationID, + fieldName: fieldName, + fieldIndex: fieldIndex + } + await fetch("/api/deleteScenarioField", { + method: "POST", + body: JSON.stringify(body) + }) +} + +export async function saveEvent(params) { + await fetch("/api/saveEvent", { + method: "POST", + body: JSON.stringify(params) + }) +} + +export async function saveCommand(params) { + await fetch("/api/saveCommand", { + method: "POST", + body: JSON.stringify(params) + }) +} + +export async function saveListener(params) { + await fetch("/api/saveListener", { + method: "POST", + body: JSON.stringify(params) + }) +} + +export async function changeEvent(params) { + await fetch("/api/changeEvent", { + method: "POST", + body: JSON.stringify(params) + }) +} + +export async function changeCommand(params) { + await fetch("/api/changeCommand", { + method: "POST", + body: JSON.stringify(params) + }) +} + +export async function changeListener(params) { + await fetch("/api/changeListener", { + method: "POST", + body: JSON.stringify(params) + }) +} + +export async function deleteEvent(id) { + const body = { + _id: id + } + await fetch("/api/deleteEvent", { + method: "POST", + body: JSON.stringify(body) + }) +} + +export async function deleteCommand(id) { + const body = { + _id: id + } + await fetch("/api/deleteCommand", { + method: "POST", + body: JSON.stringify(body) + }) +} + +export async function deleteListener(id) { + const body = { + _id: id + } + await fetch("/api/deleteListener", { + method: "POST", + body: JSON.stringify(body) + }) +} + +export async function getPSPMapping(payload) { + return useFetch("/api/getPSPMapping", { + method: "POST", + body: payload + }) +} + +export async function deleteResultEntry(simulationID, index) { + await fetch("/api/deleteResultEntry", { + method: "POST", + body: JSON.stringify({ + simulationID: simulationID, + index: index + }) + }) +} + +export async function uploadAdditionalEnvironmentFile(filename, file) { + const formData = new FormData() + formData.append(filename, file) + await fetch("/api/uploadAdditionalEnvironmentFile", { + method: "POST", + body: formData + }) +} + diff --git a/composables/navigation.js b/composables/navigation.js new file mode 100644 index 0000000..9d25c9a --- /dev/null +++ b/composables/navigation.js @@ -0,0 +1,23 @@ +export function toScenarioEditor(simulationID, router = this.$router ) { + router.push('/scenarioEditorSite?simID=' + simulationID); +} + +export function toPSPWizardResponse(simulationID, router = this.$router) { + router.push('/pspwizardSite?simID=' + simulationID + '&type=response'); +} + +export function toPSPWizardStimulus(simulationID, router = this.$router) { + router.push('/pspwizardSite?simID=' + simulationID + '&type=stimulus'); +} + +export function toScenariosOverview(router = this.$router){ + router.push('/scenariosSite'); +} + +export function toRefinement(simID, responseIndex, router = this.$router) { + router.push('/tqPropRefinerSiteDynamic?sim_id=' + simID + '&response_index=' + responseIndex); +} + +export async function toScenarioDetails(simulationID, router = this.$router) { + router.push('/scenarioDetails/?simID=' + simulationID); +} \ No newline at end of file diff --git a/composables/popup.js b/composables/popup.js new file mode 100644 index 0000000..1941c07 --- /dev/null +++ b/composables/popup.js @@ -0,0 +1,23 @@ +let popUp; + +export function preparePopups(){ + popUp = useToast() +} + +export async function successMessage(title, description) { + popUp.add({ + icon: "i-heroicons-check-badge", + title: title, + color: "green", + description: description + }) +} + +export async function failureMessage(title, description) { + popUp.add({ + icon: "i-heroicons-no-symbol", + title: title, + color: "red", + description: description + }) +} \ No newline at end of file diff --git a/composables/resultActions.js b/composables/resultActions.js new file mode 100644 index 0000000..476ec72 --- /dev/null +++ b/composables/resultActions.js @@ -0,0 +1,77 @@ +export function getSimulationVerificationResultsPerResponse(result, responseIndex) { + const defaultResult = "0 / 0" + if (result === undefined) { + return defaultResult; + } + const totals = result.simulationResultsTotal; + const successes = result.simulationResultsResponseSuccesses; + if (totals === undefined || successes === undefined) { + return defaultResult + } + return successes[responseIndex] + " / " + totals +} + +export function getResilienceScore(result) { + let resilienceScore = 0; + if (result !== undefined && result.resilienceScore !== undefined) { + resilienceScore = result.resilienceScore + } + return resilienceScore +} + +export function getResilienceScoreColor(result) { + //value from 0 to 1 + const value = 0.1 * this.getResilienceScore(result) + const hue = ((value) * 12).toString(10); + return ["hsl(", hue, ",100%,50%)"].join(""); +} + +export function getSearchVerificationResultsPerResponse(result, responseIndex) { + const defaultResult = "0 / 0" + if (result === undefined) { + return defaultResult; + } + const totals = result.searchResultsTotal; + const successes = result.searchResultsResponseSuccesses; + if (totals === undefined || successes === undefined) { + return defaultResult + } + return successes[responseIndex] + " / " + totals +} + +export function getSearchVerificationResultsPerScenario(result) { + const defaultResult = "0 / 0" + if (result === undefined) { + return defaultResult; + } + const totals = result.searchResultsTotal; + const successes = result.searchResultsScenarioSuccessesTotal; + if (totals === undefined || successes === undefined) { + return defaultResult + } + return successes + " / " + totals +} + +export function getSimulationVerificationResultsPerScenario(result) { + const defaultResult = "0 / 0" + if (result === undefined) { + return defaultResult; + } + const totals = result.simulationResultsTotal; + const successes = result.simulationResultsScenarioSuccessesTotal; + if (totals === undefined || successes === undefined) { + return defaultResult + } + return successes + " / " + totals +} + +export function mapResultToColor(resultValue) { + if (resultValue === undefined) { + return 'gray' + } + if (resultValue) { + return 'green' + } else { + return 'red' + } +} \ No newline at end of file diff --git a/composables/scenarioActions.js b/composables/scenarioActions.js new file mode 100644 index 0000000..f87783a --- /dev/null +++ b/composables/scenarioActions.js @@ -0,0 +1,78 @@ +import {successMessage} from "~/composables/popup.js"; +import {getScenario, startSearch, startSimulation} from "~/composables/api.js"; +import JSZip from "jszip"; + +export async function startScenarioSimulation(scenario) { + await successMessage("Simulation started", 'SimID: ' + scenario.simulationID) + scenario.simState = 'running'; + await startSimulation(scenario.simulationID) + await successMessage("Simulation finished", 'SimID: ' + scenario.simulationID) + scenario.simState = 'done'; + return 'done' +} + +export async function startScenarioSearch(scenario) { + await successMessage("Search started", 'SimID: ' + scenario.simulationID) + scenario.mosimState = 'running'; + await startSearch(scenario.simulationID); + await successMessage("Search finished", 'SimID: ' + scenario.simulationID) + scenario.mosimState = 'done'; + return 'done' +} + +//Changes all target logics to the same one +export function changeAllTargets(scenarios, target) { + scenarios.forEach(scenario => { + scenario.responses.forEach(response => { + response.target_logic = target; + }) + scenario.stimuli.forEach(stimulus => { + stimulus.target_logic = target; + }) + }); +} + +//Download a single scenario as json +export async function downloadJSON(simID) { + const scenario = await getScenario(simID) + + const jsonStr = JSON.stringify(scenario, null, 2); + const blob = new Blob([jsonStr], {type: 'application/json'}) + const url = window.URL.createObjectURL(blob); + + const a = document.createElement('a'); + const fileName = 'Scenario_' + scenario.name + '.json'; + a.style.display = 'none'; + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(a); +} + +//Download all scenarios as zip file +export async function downloadZip() { + const zip = new JSZip(); + let c = 1 + this.scenarios.forEach((scenario, index) => { + const name = c + " - " + scenario.name; + const jsonData = this.scenarios[index]; + zip.file('Scenario_' + name + '.json', JSON.stringify(jsonData, null, 2)); + c++; + }); + + const content = await zip.generateAsync({type: 'blob'}); + const url = window.URL.createObjectURL(content); + + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = 'Scenarios.zip'; + document.body.appendChild(a); + a.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(a); +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d0449a2..50543ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "1" - name: "dashboard-project" services: @@ -38,7 +36,7 @@ services: - ./data/simulations_results:/usr/share/nginx/html/assets/simulations_results tbverifier: - image: ghcr.io/cambio-project/transient-behavior-verifier:1.0.1 + image: ghcr.io/cambio-project/transient-behavior-verifier:1.0.2 container_name: tbverifier ports: - 8083:5000 @@ -50,7 +48,7 @@ services: - ./data/search_results:/app/search_results misim: - image: ghcr.io/cambio-project/misim:v4.1.0 + image: ghcr.io/cambio-project/misim:v4.1.1 container_name: misim ports: - 8084:8080 @@ -91,9 +89,11 @@ services: ports: - 8086:8081 environment: - ME_CONFIG_MONGODB_ADMINUSERNAME: root - ME_CONFIG_MONGODB_ADMINPASSWORD: example - ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/ + ME_CONFIG_BASICAUTH_USERNAME: root + ME_CONFIG_BASICAUTH_PASSWORD: example + ME_CONFIG_MONGODB_AUTH_USERNAME: root + ME_CONFIG_MONGODB_AUTH_PASSWORD: example + ME_CONFIG_MONGODB_SERVER: mongo networks: dashboardNetwork: diff --git a/nuxt.config.ts b/nuxt.config.ts index be2c5c8..9dd5af1 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -29,5 +29,6 @@ export default defineNuxtConfig({ }, colorMode: { preference: "light" - } + }, + css: ['~/assets/css/global.css'] }) \ No newline at end of file diff --git a/pages/index.vue b/pages/index.vue index d83e6dd..ac030d7 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,7 +1,5 @@