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 " }}
-
+
@@ -410,13 +193,9 @@ export default {
-
+
-
{{ index + 1 }}.
-
{{ response.SEL }}
@@ -446,6 +225,7 @@ 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 {
- Refine
+ Refine
Response
@@ -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 @@