Requisito Toscano
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
import { UserPrefStore } from '../stores/UserPrefStore.js';
|
||||
import AppMenuItem from './AppMenuItem.vue';
|
||||
|
||||
const userPrefStore = UserPrefStore();
|
||||
const route = useRouter()
|
||||
|
||||
|
||||
const model = ref([
|
||||
@@ -13,14 +15,16 @@ const model = ref([
|
||||
label: 'Scenarios',
|
||||
items: [
|
||||
{ label: 'Available Scenarios', icon: 'pi pi-fw pi-id-card', to: '/' },
|
||||
{ label: 'Executions List', icon: 'pi pi-fw pi-id-card', to: '/executions' },
|
||||
{ label: 'Execution List', icon: 'pi pi-fw pi-list', command: () => {
|
||||
route.push({path: '/executions/all'});
|
||||
} },
|
||||
] },
|
||||
{
|
||||
label: '',
|
||||
items: [] } ,
|
||||
{
|
||||
label: 'Canvas',
|
||||
items: [{ label: 'New Canvas', icon: 'pi pi-fw pi-id-card', to: '/mdcanvas' }]
|
||||
items: [{ label: 'New Canvas', icon: 'pi pi-fw pi-pencil', to: '/mdcanvas' }]
|
||||
}
|
||||
|
||||
]);
|
||||
@@ -30,22 +34,62 @@ function updateApplicationsMenu() {
|
||||
const selectedApp = userPrefStore.getSelApp;
|
||||
console.log("selectedApp", selectedApp);
|
||||
|
||||
if (selectedApp!=null) {
|
||||
if (selectedApp != null) {
|
||||
// Se selectedApp non è nullo, aggiorna gli item
|
||||
model.value[1].label = selectedApp.fe_name;
|
||||
|
||||
// Aggiorna gli item dell'app selezionata
|
||||
// Aggiorna gli item dell'app selezionata
|
||||
model.value[1].items = [
|
||||
{ label: 'Rev Eng Code', icon: 'pi pi-fw pi-id-card', to: '/app-browser' }
|
||||
{
|
||||
label: 'Rev Eng Code',
|
||||
icon: 'pi pi-fw pi-wrench',
|
||||
command: () => {
|
||||
route.push({path: '/app-browser'});
|
||||
},
|
||||
items: [
|
||||
{
|
||||
label: 'Execution List', // Sottovoce per Rev Eng Code
|
||||
icon: 'pi pi-fw pi-list', // Icona per la sottovoce
|
||||
to: '/executions', // URL per la sottovoce
|
||||
command: () => {
|
||||
// Salva il nome dello scenario nello store
|
||||
userPrefStore.setSelectedScenario('Rev Eng Code');
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
if( selectedApp.available_scenarios.length > 0) {
|
||||
// Se ci sono scenari disponibili, aggiungili come sottovoci
|
||||
if (selectedApp.available_scenarios.length > 0) {
|
||||
selectedApp.available_scenarios.forEach(app => {
|
||||
model.value[1].items.push({ label: app.label, icon: 'pi pi-fw pi-id-card', to: `/scenario/exec/${app.scenario_id}` });
|
||||
let scenarioItem = {
|
||||
label: app.label,
|
||||
icon: 'pi pi-fw pi-wrench',
|
||||
|
||||
items: [] , // Sub-items per ogni scenario
|
||||
command: () => {
|
||||
route.push({path: `/scenario/exec/${app.scenario_id}`});
|
||||
}
|
||||
};
|
||||
|
||||
// Aggiungi la sottovoce "Execution List"
|
||||
scenarioItem.items.push({
|
||||
label: 'Execution List', // Etichetta della sottovoce
|
||||
icon: 'pi pi-fw pi-list', // Icona per la sottovoce
|
||||
to: '/executions/filter', // URL per la sottovoce
|
||||
command: () => {
|
||||
// Salva il nome dello scenario nello store
|
||||
userPrefStore.setSelectedScenario(app.label);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Aggiungi lo scenario alla lista principale
|
||||
model.value[1].items.push(scenarioItem);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
// Se selectedApp è nullo, svuota gli item
|
||||
model.value[1].label = '';
|
||||
@@ -53,6 +97,8 @@ function updateApplicationsMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Monitora i cambiamenti in selectedApp dallo store
|
||||
watch(() => userPrefStore.getSelApp, updateApplicationsMenu, { immediate: true });
|
||||
|
||||
|
||||
@@ -36,10 +36,11 @@ const router = createRouter({
|
||||
component: () => import('@/views/pages/OldScenarioExec.vue')
|
||||
},
|
||||
{
|
||||
path: '/executions',
|
||||
path: '/executions/:name',
|
||||
name: 'executions',
|
||||
component: () => import('@/views/pages/ScenarioExecList.vue')
|
||||
},
|
||||
component: () => import('@/views/pages/ScenarioExecList.vue'),
|
||||
props: true // Facoltativo: consente di passare il parametro come prop al componente
|
||||
},
|
||||
{
|
||||
path: '/canvas',
|
||||
name: 'canvas',
|
||||
|
||||
@@ -9,6 +9,7 @@ export const LoadingStore = defineStore('loading_store', () => {
|
||||
const user_loading = ref(false)
|
||||
const another_loading = ref(false)
|
||||
const exectuion_loading = ref(false)
|
||||
const id_exec_loading = ref("")
|
||||
const re_loading = ref(false)
|
||||
|
||||
const isLoading = computed(() => {
|
||||
@@ -24,12 +25,22 @@ export const LoadingStore = defineStore('loading_store', () => {
|
||||
return 'none'
|
||||
})
|
||||
|
||||
async function setIdExecLoading(id){
|
||||
id_exec_loading.value = id
|
||||
}
|
||||
|
||||
const getExecIdLoading = computed(() => {
|
||||
return id_exec_loading.value
|
||||
});
|
||||
|
||||
return {isLoading,
|
||||
user_loading,
|
||||
scenario_loading,
|
||||
another_loading,
|
||||
exectuion_loading,
|
||||
re_loading,
|
||||
loadingType
|
||||
loadingType,
|
||||
setIdExecLoading,
|
||||
getExecIdLoading
|
||||
}
|
||||
})
|
||||
@@ -11,7 +11,7 @@ export const UserPrefStore = defineStore('userpref_store', () => {
|
||||
const selectedApp = ref(null)
|
||||
const loadingStore = LoadingStore()
|
||||
const selectedFileRE = ref(null)
|
||||
|
||||
const selectedScenario = ref(null)
|
||||
|
||||
async function fetchUserData(){
|
||||
|
||||
@@ -66,6 +66,10 @@ export const UserPrefStore = defineStore('userpref_store', () => {
|
||||
selectedFileRE.value = file;
|
||||
}
|
||||
|
||||
async function setSelectedScenario(scenario){
|
||||
selectedScenario.value = scenario;
|
||||
}
|
||||
|
||||
const getSelProj = computed(() => {
|
||||
return selectedProject.value
|
||||
})
|
||||
@@ -77,8 +81,15 @@ export const UserPrefStore = defineStore('userpref_store', () => {
|
||||
const getSelFile = computed(() => {
|
||||
return selectedFileRE.value
|
||||
})
|
||||
|
||||
|
||||
|
||||
return {getSelFile, user,selectedFileRE,fetchUserData,userLoaded,selectedProject,availableApp,getSelApp,setSelectedApp,selectedApp, updateSelectedProject,getSelProj, setSelectedFile }
|
||||
const getSelScenario = computed(() => {
|
||||
return selectedScenario.value
|
||||
})
|
||||
|
||||
const getUser = computed(() => {
|
||||
return user.value
|
||||
})
|
||||
|
||||
|
||||
return {getSelFile, user,selectedFileRE,fetchUserData,getUser,userLoaded,selectedProject,availableApp,getSelApp,setSelectedApp,selectedApp, setSelectedScenario, updateSelectedProject,getSelProj, getSelScenario, setSelectedFile }
|
||||
})
|
||||
@@ -33,13 +33,15 @@
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-bold">Execution Input for ID {{execution_id}}</span>
|
||||
<div v-if="rating!=null" class="flex justify-end">
|
||||
</div>
|
||||
</template>
|
||||
<template #icons>
|
||||
<div v-if="updateLoading" class="flex justify-end">
|
||||
<Rating
|
||||
|
||||
:modelValue="rating"
|
||||
:stars="5"
|
||||
|
||||
:readonly="true"
|
||||
@change="updateRating($event)"
|
||||
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="flex justify-end">
|
||||
@@ -47,23 +49,50 @@
|
||||
|
||||
:modelValue="rating"
|
||||
:stars="5"
|
||||
|
||||
:readonly="true"
|
||||
@change="updateRating($event)"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="box p-4 border rounded-md shadow-sm" style="background-color: white;">
|
||||
<div v-for="(input, index) in inputs" :key="index" class="input-container">
|
||||
<div class="input-wrapper">
|
||||
<span class="font-bold">{{ index.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase()) }}:</span>
|
||||
<span>{{ input }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table-auto w-full border-collapse border border-gray-300">
|
||||
<tbody>
|
||||
<tr v-for="(input, index) in inputs" :key="index">
|
||||
<th v-if="index === 'MultiFileUpload'" class="border border-gray-300 px-4 py-2">
|
||||
Files Uploaded
|
||||
</th>
|
||||
<th v-else-if="index === 'SingleFileUpload'" class="border border-gray-300 px-4 py-2 bg-gray-500 text-white">
|
||||
Parameter
|
||||
</th>
|
||||
<th v-else class="border border-gray-300 px-4 py-2">
|
||||
{{ index.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase()) }}
|
||||
</th>
|
||||
<td class="border border-gray-300 px-4 py-2">
|
||||
<div v-if="index === 'MultiFileUpload'">
|
||||
<div v-if="fileNames.length">
|
||||
<ul>
|
||||
<li v-for="(file, idx) in fileNames" :key="idx" class="file-item">
|
||||
{{ file.substring(file.lastIndexOf('/') + 1) }}
|
||||
<Button
|
||||
icon="pi pi-download"
|
||||
class="p-button-text p-button-sm"
|
||||
label="Download"
|
||||
@click="downloadFileInput(file)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>No files found in the zip.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="index !== 'SingleFileUpload'">{{ input }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<Panel class="mt-6">
|
||||
@@ -84,8 +113,34 @@
|
||||
<div v-if="scenario.outputType == 'ciaOutput'">
|
||||
<ChangeImpactOutputViewer :scenario_output="scenario_output" />
|
||||
</div>
|
||||
<div v-else-if="loadingStore.exectuion_loading && loadingStore.getExecIdLoading===execution_id">
|
||||
<div class="flex justify-center mt-4">
|
||||
<jellyfish-loader :loading="loadingStore.exectuion_loading" scale="1" color="#A100FF" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="fileType == 'FILE'">
|
||||
<ul>
|
||||
<li class="file-item">
|
||||
sf_document-{{execution_id}}
|
||||
<Button
|
||||
icon="pi pi-download"
|
||||
class="p-button-text p-button-sm"
|
||||
label="Download"
|
||||
@click="downloadFile(scenario_output)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else-if="fileType == 'MARKDOWN'">
|
||||
<div v-html="fileContent" class="markdown-content"></div>
|
||||
</div>
|
||||
<div v-else-if="fileType == 'JSON'">
|
||||
<pre>{{ fileContent }}</pre>
|
||||
</div>
|
||||
<div v-else>
|
||||
<MdPreview class="editor" v-model="scenario_output" language="en-US" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
@@ -107,17 +162,23 @@
|
||||
<script setup>
|
||||
|
||||
import ChangeImpactOutputViewer from '@/components/ChangeImpactOutputViewer.vue';
|
||||
import { LoadingStore } from '@/stores/LoadingStore.js';
|
||||
import axios from 'axios';
|
||||
import JsonEditorVue from 'json-editor-vue';
|
||||
import JSZip from 'jszip';
|
||||
import { marked } from 'marked';
|
||||
import { MdPreview } from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import ProgressSpinner from 'primevue/progressspinner';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { JellyfishLoader } from "vue3-spinner";
|
||||
import { ScenarioService } from '../../service/ScenarioService.js';
|
||||
import { ScenarioExecutionStore } from '../../stores/ScenarioExecutionStore.js';
|
||||
import { UserPrefStore } from '../../stores/UserPrefStore.js';
|
||||
|
||||
const loadingStore = LoadingStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const value = ref('');
|
||||
@@ -135,7 +196,16 @@ const scenario_execution_store = ScenarioExecutionStore();
|
||||
const execution = scenario_execution_store.getSelectedExecScenario;
|
||||
const execution_id = ref(null)
|
||||
const inputs = ref(null);
|
||||
const steps = ref(null);
|
||||
const toast = useToast();
|
||||
const fileNames = ref([]); // Memorizza i nomi dei file nello zip
|
||||
const fileNamesOutput = ref([]); // Memorizza i nomi dei file nello zip
|
||||
const zipInput = ref(null); // Contenitore per il file zip
|
||||
const zipOutput = ref(null); // Contenitore per il file zip
|
||||
const userPrefStore = UserPrefStore();
|
||||
const updateLoading = ref(false);
|
||||
const fileContent = ref('');
|
||||
const fileType = ref('');
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
@@ -149,6 +219,7 @@ onMounted(() => {
|
||||
console.log(execution_id.value)
|
||||
}
|
||||
retrieveScenarioExec(execution_id.value)
|
||||
|
||||
});
|
||||
|
||||
const retrieveScenarioExec = (id) => {
|
||||
@@ -165,9 +236,266 @@ const retrieveScenarioExec = (id) => {
|
||||
scenario_output.value = response.data.execSharedMap.scenario_output;
|
||||
exec_id.value = response.data.scenarioExecution_id
|
||||
inputs.value = response.data.scenarioExecutionInput.inputs
|
||||
steps.value = response.data.scenario.steps
|
||||
if(inputs.value['MultiFileUpload']){
|
||||
extractFiles(inputs.value['MultiFileUpload'], 'input', zipInput)
|
||||
if(steps.value[0].attributes['codegenie_output_type']){
|
||||
if(steps.value[0].attributes['codegenie_output_type'] == 'FILE'){
|
||||
//console.log('base64 ', scenario_output.value)
|
||||
//extractFiles(scenario_output.value, 'output', zipOutput)
|
||||
fileType.value = 'FILE'
|
||||
|
||||
}
|
||||
else if(steps.value[0].attributes['codegenie_output_type'] == 'MARKDOWN'){
|
||||
fileType.value = 'MARKDOWN'
|
||||
showFileContent(scenario_output.value, 'MARKDOWN')
|
||||
}
|
||||
else if(steps.value[0].attributes['codegenie_output_type'] == 'JSON'){
|
||||
fileType.value = 'JSON'
|
||||
showFileContent(scenario_output.value, 'JSON')
|
||||
}
|
||||
}
|
||||
}
|
||||
if(exec_scenario.value.executedByUsername===userPrefStore.getUser.username){
|
||||
updateLoading.value = true
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const extractFiles = async (base64String, type, zip) => {
|
||||
try {
|
||||
// Decodifica la base64 in un array di byte
|
||||
const byteCharacters = atob(base64String);
|
||||
const byteNumbers = Array.from(byteCharacters, char => char.charCodeAt(0));
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
|
||||
// Carica il file zip con JSZip
|
||||
const zipData = await JSZip.loadAsync(byteArray);
|
||||
zip.value = zipData;
|
||||
|
||||
// Ottieni tutti i file (compresi quelli nelle sottocartelle)
|
||||
if(type == 'input'){
|
||||
fileNames.value = getFileNamesInput(zipData);
|
||||
}else{
|
||||
fileNamesOutput.value = getFileNames(zipData);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error extracting zip:', error);
|
||||
if(type == 'input'){
|
||||
fileNames.value = [];
|
||||
}else{
|
||||
fileNamesOutput.value = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const showFileContent = (base64String, type) => {
|
||||
try {
|
||||
// Decodifica la stringa Base64
|
||||
const binaryString = atob(base64String);
|
||||
const binaryLength = binaryString.length;
|
||||
const bytes = new Uint8Array(binaryLength);
|
||||
|
||||
for (let i = 0; i < binaryLength; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
// Converti i byte in una stringa leggibile
|
||||
const textContent = new TextDecoder().decode(bytes);
|
||||
|
||||
// Gestione del tipo di file
|
||||
if (type === 'MARKDOWN') {
|
||||
//fileType.value = 'markdown';
|
||||
fileContent.value = marked(textContent); // Converte Markdown in HTML
|
||||
} else if (type === 'JSON') {
|
||||
//fileType.value = 'json';
|
||||
const jsonObject = JSON.parse(textContent); // Parse JSON
|
||||
fileContent.value = JSON.stringify(jsonObject, null, 2); // Formatta JSON
|
||||
} else {
|
||||
fileContent.value = 'Tipo di file non supportato.';
|
||||
}
|
||||
} catch (error) {
|
||||
fileContent.value = 'Errore durante la decodifica o il parsing del file.';
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Funzione ricorsiva per ottenere tutti i file (anche quelli dentro le cartelle)
|
||||
const getFileNames = (zipData) => {
|
||||
const files = [];
|
||||
|
||||
// Esplora tutti i file nel file zip, considerando anche le sottocartelle
|
||||
zipData.forEach((relativePath, file) => {
|
||||
if (!file.dir) { // Escludiamo le cartelle
|
||||
const fileName = relativePath.split('/').pop(); // Estrai solo il nome del file
|
||||
files.push(fileName); // Aggiungiamo solo il nome del file
|
||||
}
|
||||
});
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
const getFileNamesInput = (zipData) => {
|
||||
const files = [];
|
||||
|
||||
// Esplora tutti i file nel file zip, considerando anche le sottocartelle
|
||||
zipData.forEach((relativePath, file) => {
|
||||
if (!file.dir) { // Escludiamo le cartelle
|
||||
files.push(relativePath); // Aggiungiamo il percorso relativo del file
|
||||
}
|
||||
});
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
// Funzione per scaricare il file
|
||||
const downloadFileInput = async (fileName) => {
|
||||
if (!zipInput.value) return;
|
||||
|
||||
try {
|
||||
// Estrai il file dallo zip
|
||||
const fileContent = await zipInput.value.file(fileName).async('blob');
|
||||
const url = URL.createObjectURL(fileContent);
|
||||
|
||||
// Crea un link per scaricare il file
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error(`Error downloading file "${fileName}":`, error);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadFileOutput = async (fileName) => {
|
||||
if (!zipOutput.value) return;
|
||||
|
||||
try {
|
||||
// Estrai il file dallo zip
|
||||
const fileContent = await zipOutput.value.file(fileName).async('blob');
|
||||
const url = URL.createObjectURL(fileContent);
|
||||
|
||||
// Crea un link per scaricare il file
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error(`Error downloading file "${fileName}":`, error);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadFile = (base64String) => {
|
||||
// Decodifica la stringa Base64
|
||||
const binaryString = atob(base64String);
|
||||
const binaryLength = binaryString.length;
|
||||
const bytes = new Uint8Array(binaryLength);
|
||||
|
||||
for (let i = 0; i < binaryLength; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
// Creazione di un Blob dal file binario
|
||||
const blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
|
||||
|
||||
// Creazione di un URL per il Blob
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Creazione di un elemento anchor per il download
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'sf_document-'+execution_id.value+'.docx';
|
||||
|
||||
// Simulazione di un click per scaricare il file
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// Pulizia del DOM
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const downloadFolderFromBase64 = async (base64String) => {
|
||||
try {
|
||||
// Decodifica la stringa base64 in un array di byte
|
||||
const binaryString = atob(base64String); // Decodifica base64 in una stringa binaria
|
||||
const byteArray = new Uint8Array(binaryString.length); // Crea un array di byte
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
byteArray[i] = binaryString.charCodeAt(i); // Popola l'array di byte
|
||||
}
|
||||
|
||||
// Crea un oggetto JSZip per lavorare con il contenuto ZIP
|
||||
const zip = await JSZip.loadAsync(byteArray); // Carica direttamente l'array di byte
|
||||
|
||||
// Estrai il primo nome della cartella presente nello ZIP
|
||||
let folderName = Object.keys(zip.files).find(file => zip.files[file].dir); // Trova il primo file che è una cartella
|
||||
|
||||
// Se non è stata trovata alcuna cartella, creiamo una cartella chiamata "folderToDownload"
|
||||
if (!folderName) {
|
||||
folderName = 'docOutput-' + execution_id.value;
|
||||
console.log(`Non è stata trovata alcuna cartella nello ZIP. Verrà creata una cartella chiamata "${folderName}".`);
|
||||
}
|
||||
|
||||
// Crea un nuovo archivio ZIP in cui mettere i file della cartella
|
||||
const newZip = new JSZip();
|
||||
|
||||
// Se una cartella esiste nello ZIP, estrai i file da essa, altrimenti crea una nuova cartella
|
||||
const folder = zip.folder(folderName);
|
||||
if (folder) {
|
||||
// Aggiungi ogni file della cartella al nuovo archivio ZIP
|
||||
const files = folder.files;
|
||||
for (const fileName in files) {
|
||||
const file = files[fileName];
|
||||
|
||||
// Controlla se il file è valido (non nullo)
|
||||
if (file && file.async) {
|
||||
try {
|
||||
const fileContent = await file.async('blob'); // Estrai il contenuto del file
|
||||
newZip.file(fileName, fileContent);
|
||||
} catch (fileError) {
|
||||
console.error(`Errore durante l'estrazione del file "${fileName}":`, fileError);
|
||||
}
|
||||
} else {
|
||||
console.warn(`Il file "${fileName}" non è valido o non può essere elaborato.`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Se la cartella non esiste, crea una cartella vuota con nome "folderToDownload"
|
||||
newZip.folder(folderName);
|
||||
}
|
||||
|
||||
// Crea il nuovo file ZIP da scaricare
|
||||
const newZipContent = await newZip.generateAsync({ type: 'blob' });
|
||||
const url = URL.createObjectURL(newZipContent);
|
||||
|
||||
// Crea un link per scaricare il file ZIP
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${folderName}.zip`; // Nome del file ZIP che contiene la cartella
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('Errore durante il download della cartella:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
async function updateRating(newRating) {
|
||||
|
||||
loading_data.value = true;
|
||||
@@ -179,7 +507,7 @@ async function updateRating(newRating) {
|
||||
console.log('Rating aggiornato con successo:', response.data);
|
||||
toast.add({
|
||||
severity: 'success', // Tipo di notifica (successo)
|
||||
summary: 'Successo', // Titolo della notifica
|
||||
summary: 'Success', // Titolo della notifica
|
||||
detail: 'Rating updated with success.', // Messaggio dettagliato
|
||||
life: 3000 // Durata della notifica in millisecondi
|
||||
});
|
||||
@@ -187,7 +515,7 @@ async function updateRating(newRating) {
|
||||
console.error('Errore nell\'aggiornamento del rating', response.data);
|
||||
toast.add({
|
||||
severity: 'error', // Tipo di notifica (errore)
|
||||
summary: 'Errore', // Titolo della notifica
|
||||
summary: 'Error', // Titolo della notifica
|
||||
detail: 'Error updating rating. Try later.', // Messaggio dettagliato
|
||||
life: 3000 // Durata della notifica in millisecondi
|
||||
});
|
||||
@@ -237,5 +565,33 @@ const openDebug = () => {
|
||||
.editor ul {
|
||||
list-style-type: disc !important;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap; /* Fa andare a capo il contenuto automaticamente */
|
||||
word-wrap: break-word; /* Interrompe le parole troppo lunghe */
|
||||
overflow-wrap: break-word; /* Per compatibilità con più browser */
|
||||
max-width: 100%; /* Imposta una larghezza massima pari al contenitore genitore */
|
||||
overflow-x: auto; /* Aggiunge uno scorrimento orizzontale solo se necessario */
|
||||
background-color: #f5f5f5; /* Colore di sfondo opzionale per migliorare leggibilità */
|
||||
padding: 10px; /* Spaziatura interna */
|
||||
border-radius: 5px; /* Bordo arrotondato opzionale */
|
||||
font-family: monospace; /* Font specifico per codice */
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Ombra per migliorare estetica */
|
||||
}
|
||||
|
||||
.markdown-content {
|
||||
white-space: pre-wrap; /* Gestisce correttamente gli spazi e i ritorni a capo */
|
||||
word-wrap: break-word; /* Spezza le parole lunghe */
|
||||
overflow-wrap: break-word; /* Per compatibilità con più browser */
|
||||
max-width: 100%; /* Adatta il contenuto alla larghezza del contenitore */
|
||||
overflow-x: auto; /* Aggiunge scorrimento orizzontale solo se necessario */
|
||||
background-color: #f5f5f5; /* Sfondo per distinguere il contenuto */
|
||||
padding: 10px; /* Margini interni */
|
||||
border-radius: 5px; /* Bordo arrotondato */
|
||||
font-family: Arial, sans-serif; /* Puoi scegliere un font leggibile */
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Effetto estetico di ombra */
|
||||
line-height: 1.5; /* Aumenta la leggibilità */
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -4,34 +4,175 @@
|
||||
<h1>
|
||||
{{ scenario.name }}
|
||||
</h1>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="flex mt-6">
|
||||
<div class="card flex flex-col gap-4 w-full">
|
||||
<template v-if="scenario.inputs && scenario.inputs.length === 1">
|
||||
<div class="input-container flex items-center">
|
||||
<div class="flex-grow">
|
||||
<label :for="scenario.inputs[0].name"><h2>{{ scenario.inputs[0].label }}</h2></label>
|
||||
<div class="input-wrapper">
|
||||
<component
|
||||
:is="getInputComponent(scenario.inputs[0].type)"
|
||||
:id="scenario.inputs[0].name"
|
||||
v-model="formData[scenario.inputs[0].name]"
|
||||
:options="scenario.inputs[0].options"
|
||||
class="full-width-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<Button :disabled="loadingStore.exectuion_loading || !isInputFilled" label="Execute" @click="execScenario" size="large" iconPos="right" icon="pi pi-cog"></Button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h3>
|
||||
{{ scenario.description }}
|
||||
</h3>
|
||||
<template v-if="scenario.inputs">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="input in scenario.inputs" :key="input.name" class="input-container">
|
||||
<label :for="input.name"><b>{{ input.label }}</b></label>
|
||||
<div class="input-wrapper">
|
||||
<div v-for="input in scenario.inputs" :key="input.name"
|
||||
:class="['input-container', input.type === 'textarea' ? 'col-span-12' : '']">
|
||||
|
||||
<div v-if="input.type === 'singlefile'">
|
||||
<label :for="input.name">
|
||||
<b>{{ input.label }}</b>
|
||||
<i
|
||||
class="pi pi-info-circle text-violet-600 cursor-pointer"
|
||||
v-tooltip="'Upload one PR document of .docx type. Mandatory if you want to execute scenario.'"
|
||||
></i>
|
||||
</label>
|
||||
<div>
|
||||
|
||||
<FileUpload
|
||||
:name="'MultiFileUpload'"
|
||||
:customUpload="false"
|
||||
:url="uploadUrlPR"
|
||||
@upload="(event) => onUpload(event, 'SingleFileUpload')"
|
||||
:multiple="false"
|
||||
accept=".docx"
|
||||
auto
|
||||
:showUploadButton="false"
|
||||
:showCancelButton="false"
|
||||
:maxFileSize="10000000"
|
||||
v-model:files="uploadedFiles"
|
||||
|
||||
|
||||
>
|
||||
<template #content="{ files, uploadedFiles, removeUploadedFileCallback, removeFileCallback }">
|
||||
<div class="pt-4">
|
||||
<!-- Tabella per file in caricamento -->
|
||||
<div v-if="uploadedFiles.length > 0">
|
||||
|
||||
<table class="table-auto w-full border-collapse border border-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-gray-300 p-2">Name</th>
|
||||
<th class="border border-gray-300 p-2">Dimension</th>
|
||||
<th class="border border-gray-300 p-2">Status</th>
|
||||
<th class="border border-gray-300 p-2">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(file, index) in uploadedFiles"
|
||||
:key="file.name + file.size"
|
||||
class="hover:bg-gray-50"
|
||||
>
|
||||
<td class="border border-gray-300 p-2">{{ file.name }}</td>
|
||||
<td class="border border-gray-300 p-2">{{ formatSize(file.size) }}</td>
|
||||
<td class="border border-gray-300 p-2">
|
||||
<Badge value="UPLOADED" severity="success" />
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2">
|
||||
<Button
|
||||
label="Remove"
|
||||
@click="onRemove({ file, index }, removeUploadedFileCallback, 'SingleFileUpload')"
|
||||
|
||||
|
||||
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
<div class="flex items-center justify-center flex-col">
|
||||
<i class="pi pi-cloud-upload !border-2 !rounded-full !p-8 !text-4xl !text-muted-color" />
|
||||
<p class="mt-6 mb-0">Drag and drop files to here to upload.</p>
|
||||
</div>
|
||||
</template>
|
||||
</FileUpload>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="input.type === 'multifile'">
|
||||
<label :for="input.name">
|
||||
<b>{{ input.label }}
|
||||
</b>
|
||||
<i
|
||||
class="pi pi-info-circle text-violet-600 cursor-pointer"
|
||||
v-tooltip="'Upload others documents of .docx, .msg, .text type. Optional.'"
|
||||
></i>
|
||||
</label>
|
||||
<div>
|
||||
|
||||
<FileUpload
|
||||
:name="'MultiFileUpload'"
|
||||
:customUpload="false"
|
||||
:url="uploadUrlOther"
|
||||
@upload="(event) => onUpload(event, 'MultiFileUpload')"
|
||||
:multiple="true"
|
||||
accept=".msg,.txt,.docx"
|
||||
auto
|
||||
:showUploadButton="false"
|
||||
:showCancelButton="false"
|
||||
:maxFileSize="10000000"
|
||||
v-model:files="uploadedFiles"
|
||||
|
||||
|
||||
>
|
||||
<template #content="{ files, uploadedFiles, removeUploadedFileCallback, removeFileCallback }">
|
||||
<div class="pt-4">
|
||||
<!-- Tabella per file in caricamento -->
|
||||
<div v-if="uploadedFiles.length > 0">
|
||||
|
||||
<table class="table-auto w-full border-collapse border border-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-gray-300 p-2">Name</th>
|
||||
<th class="border border-gray-300 p-2">Dimension</th>
|
||||
<th class="border border-gray-300 p-2">Status</th>
|
||||
<th class="border border-gray-300 p-2">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(file, index) in uploadedFiles"
|
||||
:key="file.name + file.size"
|
||||
class="hover:bg-gray-50"
|
||||
>
|
||||
<td class="border border-gray-300 p-2">{{ file.name }}</td>
|
||||
<td class="border border-gray-300 p-2">{{ formatSize(file.size) }}</td>
|
||||
<td class="border border-gray-300 p-2">
|
||||
<Badge value="UPLOADED" severity="success" />
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2">
|
||||
<Button
|
||||
label="Remove"
|
||||
@click="onRemove({ file, index }, removeUploadedFileCallback, 'MultiFileUpload')"
|
||||
|
||||
|
||||
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
<div class="flex items-center justify-center flex-col">
|
||||
<i class="pi pi-cloud-upload !border-2 !rounded-full !p-8 !text-4xl !text-muted-color" />
|
||||
<p class="mt-6 mb-0">Drag and drop files to here to upload.</p>
|
||||
</div>
|
||||
</template>
|
||||
</FileUpload>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<label :for="input.name"><b>{{ input.label }}</b></label>
|
||||
<div class="input-wrapper">
|
||||
<component
|
||||
:is="getInputComponent(input.type)"
|
||||
:id="input.name"
|
||||
@@ -40,13 +181,18 @@
|
||||
class="full-width-input"
|
||||
:disabled="loadingStore.exectuion_loading"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<Button :disabled="loadingStore.exectuion_loading || !isInputFilled " label="Execute" @click="execScenario" size="large" iconPos="right" icon="pi pi-cog"></Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,31 +217,76 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-bold">Workflow Response</span>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
</template>
|
||||
|
||||
<template #icons>
|
||||
<div class="flex justify-end">
|
||||
<div class="flex">
|
||||
<Rating
|
||||
|
||||
:modelValue="rating"
|
||||
:stars="5"
|
||||
@change="updateRating($event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #icons>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<div>
|
||||
<Button severity="secondary" rounded @click="openDebug" v-tooltip.left="'View code'">
|
||||
<i class="pi pi-code"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="card flex flex-col gap-4 w-full">
|
||||
<div v-if="scenario.outputType == 'ciaOutput'">
|
||||
<ChangeImpactOutputViewer :scenario_output="scenario_output" />
|
||||
</div>
|
||||
<div v-if="scenario.outputType == 'file'">
|
||||
<Button
|
||||
icon="pi pi-download"
|
||||
label="Download File"
|
||||
class="p-button-primary"
|
||||
@click="downloadFile"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- <div v-if="fileNamesOutput.length">
|
||||
<ul>
|
||||
<li v-for="(file, idx) in fileNamesOutput" :key="idx" class="file-item">
|
||||
{{ file }}
|
||||
<Button
|
||||
icon="pi pi-download"
|
||||
class="p-button-text p-button-sm"
|
||||
label="Download"
|
||||
@click="downloadZipFile(file)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div> -->
|
||||
<div v-if="fileType == 'FILE'">
|
||||
<ul>
|
||||
<li class="file-item">
|
||||
sf_document-{{exec_id}}
|
||||
<Button
|
||||
icon="pi pi-download"
|
||||
class="p-button-text p-button-sm"
|
||||
label="Download"
|
||||
@click="downloadFile(scenario_output)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else-if="fileType == 'MARKDOWN'">
|
||||
<div v-html="fileContent" class="markdown-content"></div>
|
||||
</div>
|
||||
<div v-else-if="fileType == 'JSON'">
|
||||
<pre>{{ fileContent }}</pre>
|
||||
</div>
|
||||
<div v-else>
|
||||
<MdPreview class="editor" v-model="scenario_output" language="en-US" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
@@ -119,12 +310,16 @@ import ChangeImpactOutputViewer from '@/components/ChangeImpactOutputViewer.vue'
|
||||
import { LoadingStore } from '@/stores/LoadingStore';
|
||||
import axios from 'axios';
|
||||
import JsonEditorVue from 'json-editor-vue';
|
||||
import JSZip from 'jszip';
|
||||
import { marked } from 'marked';
|
||||
import { MdPreview } from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import moment from 'moment';
|
||||
import { usePrimeVue } from 'primevue/config';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import Select from 'primevue/select';
|
||||
import Textarea from 'primevue/textarea';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
@@ -134,7 +329,7 @@ import { ScenarioService } from '../../service/ScenarioService';
|
||||
|
||||
const loadingStore = LoadingStore();
|
||||
const toast = useToast();
|
||||
|
||||
const zip = ref(null);
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const value = ref('');
|
||||
@@ -151,6 +346,27 @@ const exec_id = ref(null);
|
||||
const exec_scenario = ref({});
|
||||
const debug_modal = ref(false);
|
||||
let pollingInterval = null;
|
||||
const folderName = ref("");
|
||||
const fileNamesOutput = ref([]);
|
||||
// URL di upload
|
||||
const uploadUrlBase = import.meta.env.VITE_BACKEND_URL;
|
||||
const uploadUrl = ref("");
|
||||
const uploadUrlPR = ref("");
|
||||
const uploadUrlOther = ref("");
|
||||
// File che l'utente ha selezionato
|
||||
const uploadedFiles = ref([]);
|
||||
const numberPrFiles = ref(0);
|
||||
// :url="`http://localhost:8081/uploadListFiles/${folderName}`"
|
||||
|
||||
// Stato per l'ID univoco della cartella
|
||||
const uniqueFolderId = ref(generateUniqueId());
|
||||
const confirm = useConfirm();
|
||||
|
||||
const $primevue = usePrimeVue();
|
||||
const files = ref([]);
|
||||
const fileContent = ref('');
|
||||
const fileType = ref('');
|
||||
const reqMultiFile = ref(false);
|
||||
|
||||
let startTime = ref(null);
|
||||
let timerInterval = ref(null);
|
||||
@@ -184,6 +400,13 @@ const isInputFilled = computed(() => {
|
||||
|
||||
onMounted(() => {
|
||||
fetchScenario(route.params.id);
|
||||
const timestamp = Date.now(); // Ottiene il timestamp corrente
|
||||
const randomNumber = Math.floor(Math.random() * 1000);
|
||||
folderName.value = `${timestamp}_${randomNumber}`
|
||||
uploadUrl.value = uploadUrlBase + '/uploadListFiles/' + folderName.value;
|
||||
uploadUrlPR.value = uploadUrl.value + '/PR';
|
||||
uploadUrlOther.value = uploadUrl.value + '/OTHER';
|
||||
console.log("Upload URL:", uploadUrl);
|
||||
});
|
||||
|
||||
// Ricarica i dati quando cambia il parametro `id`
|
||||
@@ -198,6 +421,11 @@ watch(() => route.params.id, fetchScenario);
|
||||
axios.get(`/scenarios/${id}`)
|
||||
.then(response => {
|
||||
scenario.value = response.data;
|
||||
console.log("Scenario fetched:", scenario.value);
|
||||
|
||||
if (scenario.value.inputs.some((input) => input.name === 'MultiFileUpload' || input.name === 'SingleFileUpload')) {
|
||||
reqMultiFile.value = true;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error fetching scenario:", error);
|
||||
@@ -221,8 +449,19 @@ watch(() => route.params.id, fetchScenario);
|
||||
};
|
||||
|
||||
const execScenario = () => {
|
||||
|
||||
if(numberPrFiles.value!==1 && reqMultiFile.value){
|
||||
toast.add({
|
||||
severity: 'warn', // Tipo di notifica (errore)
|
||||
summary: 'Attention', // Titolo della notifica
|
||||
detail: 'You can upload only 1 PR file. Please remove others.' // Messaggio dettagliato
|
||||
});
|
||||
}else{
|
||||
|
||||
|
||||
loading_data.value = true;
|
||||
data_loaded.value = false;
|
||||
rating.value = 0;
|
||||
startTimer();
|
||||
|
||||
loadingStore.exectuion_loading = true;
|
||||
@@ -234,11 +473,12 @@ watch(() => route.params.id, fetchScenario);
|
||||
|
||||
axios.post('/scenarios/execute-async', data)
|
||||
.then(response => {
|
||||
console.log("Response data exec 1:", response.data);
|
||||
scenario_response.value = response.data;
|
||||
scenario_response_message.value = response.data.message;
|
||||
scenario_output.value = response.data.stringOutput;
|
||||
exec_id.value = response.data.scenarioExecution_id
|
||||
|
||||
loadingStore.setIdExecLoading(exec_id.value);
|
||||
// Start polling
|
||||
startPolling();
|
||||
})
|
||||
@@ -246,12 +486,9 @@ watch(() => route.params.id, fetchScenario);
|
||||
console.error('Error executing scenario:', error);
|
||||
loadingStore.exectuion_loading = false;
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const back = () => {
|
||||
router.push({ name: 'scenario-list'});
|
||||
}
|
||||
|
||||
const openDebug = () => {
|
||||
|
||||
axios.get('/scenarios/execute/'+ exec_id.value).then(resp =>{
|
||||
@@ -261,6 +498,7 @@ watch(() => route.params.id, fetchScenario);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const pollBackendAPI = () => {
|
||||
|
||||
axios.get('/scenarios/getExecutionProgress/'+exec_id.value).then(response => {
|
||||
@@ -272,8 +510,42 @@ watch(() => route.params.id, fetchScenario);
|
||||
loading_data.value = false;
|
||||
data_loaded.value = true;
|
||||
scenario_output.value = response.data.stringOutput;
|
||||
console.log("Response data exec 2:", response.data);
|
||||
exec_id.value = response.data.scenarioExecution_id
|
||||
scenario_response_message.value = null //if != null, next scenario starts with old message
|
||||
console.log("Scenario 3:", scenario.value);
|
||||
|
||||
// Controlla se l'array `inputs` contiene un elemento con `name = 'MultiFileUpload'`
|
||||
if (scenario.value.inputs.some((input) => input.name === 'MultiFileUpload')) {
|
||||
console.log('Im in');
|
||||
|
||||
// Accedi al primo step e controlla se esiste l'attributo `codegenie_output_type`
|
||||
const firstStep = scenario.value.steps[0];
|
||||
if (firstStep?.attributes?.['codegenie_output_type']) {
|
||||
// Controlla se `codegenie_output_type` è uguale a 'FILE'
|
||||
// if (firstStep.attributes['codegenie_output_type'] === 'FILE') {
|
||||
// console.log('base64 ', scenario_output.value);
|
||||
|
||||
// Chiama la funzione `extractFiles` con il valore di `scenario_output.value`
|
||||
//extractFiles(scenario_output.value);
|
||||
//}
|
||||
if(firstStep.attributes['codegenie_output_type'] == 'FILE'){
|
||||
//console.log('base64 ', scenario_output.value)
|
||||
//extractFiles(scenario_output.value, 'output', zipOutput)
|
||||
fileType.value = 'FILE'
|
||||
|
||||
}
|
||||
else if(firstStep.attributes['codegenie_output_type'] == 'MARKDOWN'){
|
||||
fileType.value = 'MARKDOWN'
|
||||
showFileContent(scenario_output.value, 'MARKDOWN')
|
||||
}
|
||||
else if(firstStep.attributes['codegenie_output_type'] == 'JSON'){
|
||||
fileType.value = 'JSON'
|
||||
showFileContent(scenario_output.value, 'JSON')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
console.log("Condition not met, polling continues.");
|
||||
@@ -283,6 +555,37 @@ watch(() => route.params.id, fetchScenario);
|
||||
});
|
||||
}
|
||||
|
||||
const showFileContent = (base64String, type) => {
|
||||
try {
|
||||
// Decodifica la stringa Base64
|
||||
const binaryString = atob(base64String);
|
||||
const binaryLength = binaryString.length;
|
||||
const bytes = new Uint8Array(binaryLength);
|
||||
|
||||
for (let i = 0; i < binaryLength; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
// Converti i byte in una stringa leggibile
|
||||
const textContent = new TextDecoder().decode(bytes);
|
||||
|
||||
// Gestione del tipo di file
|
||||
if (type === 'MARKDOWN') {
|
||||
//fileType.value = 'markdown';
|
||||
fileContent.value = marked(textContent); // Converte Markdown in HTML
|
||||
} else if (type === 'JSON') {
|
||||
//fileType.value = 'json';
|
||||
const jsonObject = JSON.parse(textContent); // Parse JSON
|
||||
fileContent.value = JSON.stringify(jsonObject, null, 2); // Formatta JSON
|
||||
} else {
|
||||
fileContent.value = 'Tipo di file non supportato.';
|
||||
}
|
||||
} catch (error) {
|
||||
fileContent.value = 'Errore durante la decodifica o il parsing del file.';
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to start polling
|
||||
function startPolling() {
|
||||
// Set polling interval (every 2.5 seconds in this case)
|
||||
@@ -294,11 +597,77 @@ watch(() => route.params.id, fetchScenario);
|
||||
function stopPolling() {
|
||||
clearInterval(pollingInterval);
|
||||
loadingStore.exectuion_loading = false;
|
||||
|
||||
loadingStore.setIdExecLoading("");
|
||||
|
||||
console.log("Polling stopped.");
|
||||
}
|
||||
|
||||
const extractFiles = async (base64String) => {
|
||||
try {
|
||||
// Decodifica la base64 in un array di byte
|
||||
const byteCharacters = atob(base64String);
|
||||
const byteNumbers = Array.from(byteCharacters, char => char.charCodeAt(0));
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
|
||||
// Carica il file zip con JSZip
|
||||
const zipData = await JSZip.loadAsync(byteArray);
|
||||
zip.value = zipData;
|
||||
|
||||
// Ottieni tutti i file (compresi quelli nelle sottocartelle)
|
||||
|
||||
fileNamesOutput.value = getFileNames(zipData);
|
||||
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error extracting zip:', error);
|
||||
|
||||
fileNamesOutput.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// Funzione ricorsiva per ottenere tutti i file (anche quelli dentro le cartelle)
|
||||
const getFileNames = (zipData) => {
|
||||
const files = [];
|
||||
|
||||
// Esplora tutti i file nel file zip, considerando anche le sottocartelle
|
||||
zipData.forEach((relativePath, file) => {
|
||||
if (!file.dir) { // Escludiamo le cartelle
|
||||
files.push(relativePath); // Aggiungiamo il percorso relativo del file
|
||||
}
|
||||
});
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
// const uploadSingleFile = async (file) => {
|
||||
// // Logica per caricare un singolo file utilizzando axios
|
||||
// try {
|
||||
// const formData = new FormData();
|
||||
// formData.append('MultiFileUpload', file);
|
||||
|
||||
// const response = await axios.post(uploadUrl.value, formData, {
|
||||
// headers: {
|
||||
// 'Content-Type': 'multipart/form-data',
|
||||
// },
|
||||
// });
|
||||
|
||||
// if (response.status === 200) {
|
||||
// toast.add({ severity: 'success', summary: 'Success', detail: `File "${file.name}" uploaded successfully.`, life: 3000 });
|
||||
// } else {
|
||||
// toast.add({ severity: 'error', summary: 'Error', detail: `Failed to upload file "${file.name}".`, life: 3000 });
|
||||
// }
|
||||
// } catch (error) {
|
||||
// toast.add({ severity: 'error', summary: 'Error', detail: `Error uploading file "${file.name}": ${error.message}`, life: 3000 });
|
||||
// console.error('Error uploading file:', error);
|
||||
// }
|
||||
// };
|
||||
|
||||
// const removeFile = (file, index, callback) => {
|
||||
// // Logica per rimuovere un file dalla lista dei "pending"
|
||||
// console.log('Removing pending file:', file);
|
||||
// callback(file, index);
|
||||
// };
|
||||
async function updateRating(newRating) {
|
||||
|
||||
ScenarioService.updateScenarioExecRating(exec_id.value,newRating.value).then((response) => {
|
||||
@@ -309,7 +678,7 @@ watch(() => route.params.id, fetchScenario);
|
||||
console.log('Rating aggiornato con successo:', response.data);
|
||||
toast.add({
|
||||
severity: 'success', // Tipo di notifica (successo)
|
||||
summary: 'Successo', // Titolo della notifica
|
||||
summary: 'Success', // Titolo della notifica
|
||||
detail: 'Rating updated with success.', // Messaggio dettagliato
|
||||
life: 3000 // Durata della notifica in millisecondi
|
||||
});
|
||||
@@ -317,7 +686,7 @@ watch(() => route.params.id, fetchScenario);
|
||||
console.error('Errore nell\'aggiornamento del rating', response.data);
|
||||
toast.add({
|
||||
severity: 'error', // Tipo di notifica (errore)
|
||||
summary: 'Errore', // Titolo della notifica
|
||||
summary: 'Error', // Titolo della notifica
|
||||
detail: 'Error updating rating. Try later.', // Messaggio dettagliato
|
||||
life: 3000 // Durata della notifica in millisecondi
|
||||
});
|
||||
@@ -325,10 +694,220 @@ watch(() => route.params.id, fetchScenario);
|
||||
}).catch((error) => {
|
||||
console.error('Errore durante la chiamata al backend:', error);
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Funzione per generare un ID univoco
|
||||
function generateUniqueId() {
|
||||
return Date.now(); // Puoi usare anche UUID.randomUUID() o una libreria simile
|
||||
}
|
||||
|
||||
const onRemove = (event, removeUploadedFileCallback, type) => {
|
||||
const { file, index } = event;
|
||||
console.log('Removing file:', folderName.value);
|
||||
|
||||
try {
|
||||
axios.post(
|
||||
`/deleteFile`,
|
||||
{ fileName: file.name, folderName: folderName.value }, // Invio nome del file come payload
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
).then(response => {
|
||||
if (response.status === 200) {
|
||||
console.log('File removed successfully:', response.data);
|
||||
|
||||
// Mostra notifica di successo
|
||||
toast.add({
|
||||
severity: "success",
|
||||
summary: "Success",
|
||||
detail: "File removed successfully!",
|
||||
life: 3000,
|
||||
});
|
||||
|
||||
if(type === 'SingleFileUpload'){
|
||||
numberPrFiles.value -= 1;
|
||||
console.log("Number of PR files: ", numberPrFiles.value);
|
||||
}
|
||||
|
||||
// Aggiorna lista dei file caricati
|
||||
removeUploadedFileCallback(index);
|
||||
} else {
|
||||
console.error('Failed to remove file:', response.statusText);
|
||||
|
||||
// Mostra notifica di errore
|
||||
toast.add({
|
||||
severity: "error",
|
||||
summary: "Error",
|
||||
detail: `Failed to remove file. Status: ${response.statusText}`,
|
||||
life: 3000,
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error while removing file:', error);
|
||||
|
||||
// Mostra notifica di errore
|
||||
toast.add({
|
||||
severity: "error",
|
||||
summary: "Error",
|
||||
detail: `Error while removing file: ${error.message}`,
|
||||
life: 3000,
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error while removing file:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// const onRemove = (event) => {
|
||||
// // Metodo per gestire la rimozione dei file
|
||||
// console.log('Removing file:', folderName.value);
|
||||
|
||||
|
||||
// try {
|
||||
// axios.post(
|
||||
// `http://localhost:8081/deleteFile`,
|
||||
// { fileName: event.file.name, folderName: folderName.value}, // Invio nome del file come payload
|
||||
// {
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// }
|
||||
// ).then(response => {
|
||||
// if (response.status === 200) {
|
||||
// console.log('File removed successfully:', response.data);
|
||||
|
||||
// toast.add({
|
||||
// severity: "success",
|
||||
// summary: "Successo",
|
||||
// detail: "File removed successfully!",
|
||||
// life: 3000,
|
||||
// });
|
||||
// } else {
|
||||
// console.error('Failed to remove file:', response.statusText);
|
||||
|
||||
// toast.add({
|
||||
// severity: "error",
|
||||
// summary: "Errore",
|
||||
// detail: `Failed to remove file. Status: ${xhr.status}`,
|
||||
// life: 3000,
|
||||
// });
|
||||
// }
|
||||
|
||||
// })
|
||||
|
||||
// } catch (error) {
|
||||
// console.error('Error while removing file:', error);
|
||||
// }
|
||||
// }
|
||||
|
||||
const onUpload = (event, uploadType) => {
|
||||
console.log("response upload ",event.xhr.response);
|
||||
|
||||
const { xhr } = event; // Estraggo l'oggetto XMLHttpRequest
|
||||
|
||||
if (xhr.status === 200) {
|
||||
// Risposta OK
|
||||
|
||||
if(uploadType==='SingleFileUpload'){
|
||||
formData.value['SingleFileUpload'] = "OK";
|
||||
numberPrFiles.value += 1;
|
||||
console.log("Number of PR files: ", numberPrFiles.value);
|
||||
}
|
||||
formData.value['MultiFileUpload'] = xhr.response;
|
||||
|
||||
console.log("Form value upload ",formData.value['MultiFileUpload']);
|
||||
|
||||
console.log("Upload completato con successo. Risposta:", xhr.response);
|
||||
|
||||
toast.add({
|
||||
severity: "success",
|
||||
summary: "Success",
|
||||
detail: "File uploaded successfully!",
|
||||
life: 3000,
|
||||
});
|
||||
|
||||
} else {
|
||||
// Errore durante l'upload
|
||||
console.error("Errore durante l'upload. Stato:", xhr.status, "Risposta:", xhr.response);
|
||||
|
||||
toast.add({
|
||||
severity: "error",
|
||||
summary: "Error",
|
||||
detail: `Failed to upload file. Stato: ${xhr.status}`,
|
||||
life: 3000,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Funzione per scaricare il file
|
||||
const downloadZipFile = async (fileName) => {
|
||||
if (!zip.value) return;
|
||||
|
||||
try {
|
||||
// Estrai il file dallo zip
|
||||
const fileContent = await zip.value.file(fileName).async('blob');
|
||||
const url = URL.createObjectURL(fileContent);
|
||||
|
||||
// Crea un link per scaricare il file
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error(`Error downloading file "${fileName}":`, error);
|
||||
}
|
||||
};
|
||||
|
||||
function downloadFile() {
|
||||
try {
|
||||
// Converti la stringa base64 in un blob
|
||||
const base64String = this.scenario_output;
|
||||
const byteCharacters = atob(base64String);
|
||||
const byteNumbers = Array.from(byteCharacters, char => char.charCodeAt(0));
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray]);
|
||||
|
||||
// Crea un link temporaneo per il download
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'sf_document-'+exec_id.value+'.docx';// Specifica il nome del file
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
// Rimuovi il link temporaneo
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('Errore durante il download del file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const formatSize = (bytes) => {
|
||||
const k = 1024;
|
||||
const sizes = $primevue.config.locale.fileSizeTypes;
|
||||
|
||||
if (bytes === 0) {
|
||||
return `0 ${sizes[0]}`;
|
||||
}
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
const truncatedSize = Math.trunc(bytes / Math.pow(k, i)); // Troncamento del valore
|
||||
|
||||
return `${truncatedSize} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -355,5 +934,33 @@ watch(() => route.params.id, fetchScenario);
|
||||
.editor ul {
|
||||
list-style-type: disc !important;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap; /* Fa andare a capo il contenuto automaticamente */
|
||||
word-wrap: break-word; /* Interrompe le parole troppo lunghe */
|
||||
overflow-wrap: break-word; /* Per compatibilità con più browser */
|
||||
max-width: 100%; /* Imposta una larghezza massima pari al contenitore genitore */
|
||||
overflow-x: auto; /* Aggiunge uno scorrimento orizzontale solo se necessario */
|
||||
background-color: #f5f5f5; /* Colore di sfondo opzionale per migliorare leggibilità */
|
||||
padding: 10px; /* Spaziatura interna */
|
||||
border-radius: 5px; /* Bordo arrotondato opzionale */
|
||||
font-family: monospace; /* Font specifico per codice */
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Ombra per migliorare estetica */
|
||||
}
|
||||
|
||||
.markdown-content {
|
||||
white-space: pre-wrap; /* Gestisce correttamente gli spazi e i ritorni a capo */
|
||||
word-wrap: break-word; /* Spezza le parole lunghe */
|
||||
overflow-wrap: break-word; /* Per compatibilità con più browser */
|
||||
max-width: 100%; /* Adatta il contenuto alla larghezza del contenitore */
|
||||
overflow-x: auto; /* Aggiunge scorrimento orizzontale solo se necessario */
|
||||
background-color: #f5f5f5; /* Sfondo per distinguere il contenuto */
|
||||
padding: 10px; /* Margini interni */
|
||||
border-radius: 5px; /* Bordo arrotondato */
|
||||
font-family: Arial, sans-serif; /* Puoi scegliere un font leggibile */
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Effetto estetico di ombra */
|
||||
line-height: 1.5; /* Aumenta la leggibilità */
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -137,7 +137,18 @@
|
||||
placeholder="Search by File" />
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="executedByUsername" header="Executed By" sortable
|
||||
style="min-width: 12rem">
|
||||
<template #body="slotProps">
|
||||
<div class="flex items-center gap-2">
|
||||
{{ slotProps.data.executedByUsername || 'N/A' }}
|
||||
</div>
|
||||
</template>
|
||||
<template #filter="{ filterModel, filterCallback }">
|
||||
<InputText v-model="filterModel.value" type="text" @input="filterCallback()"
|
||||
placeholder="Search by File" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="rating" header="Rating">
|
||||
<template #body="slotProps">
|
||||
<Rating :modelValue="slotProps.data.rating" :stars="5" :readonly="true" />
|
||||
@@ -171,10 +182,11 @@ import 'md-editor-v3/lib/style.css';
|
||||
import moment from 'moment';
|
||||
import ProgressSpinner from 'primevue/progressspinner';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ScenarioService } from '../../service/ScenarioService.js';
|
||||
import { ScenarioExecutionStore } from '../../stores/ScenarioExecutionStore.js';
|
||||
import { UserPrefStore } from '../../stores/UserPrefStore.js';
|
||||
|
||||
const first = ref(0);
|
||||
const router = useRouter();
|
||||
@@ -193,6 +205,7 @@ const execution_id = ref("");
|
||||
const listScenarios = ref([]);
|
||||
const scenario_execution_store = ScenarioExecutionStore();
|
||||
const toast = useToast();
|
||||
const userPrefStore = UserPrefStore();
|
||||
|
||||
|
||||
|
||||
@@ -225,6 +238,11 @@ const filters = ref({
|
||||
operator: FilterOperator.AND,
|
||||
constraints: [{ value: null, matchMode: FilterMatchMode.CONTAINS }]
|
||||
},
|
||||
|
||||
'executedByUsername': {
|
||||
operator: FilterOperator.AND,
|
||||
constraints: [{ value: null, matchMode: FilterMatchMode.CONTAINS }]
|
||||
},
|
||||
|
||||
'startDate': {
|
||||
operator: FilterOperator.AND,
|
||||
@@ -235,7 +253,20 @@ const filters = ref({
|
||||
|
||||
onMounted(() => {
|
||||
scenario_execution_store.fetchScenariosExecution()
|
||||
updateFilters();
|
||||
});
|
||||
watch(() => route.params.name, updateFilters);
|
||||
|
||||
function updateFilters() {
|
||||
const selectedScenario = userPrefStore.getSelScenario;
|
||||
|
||||
if (selectedScenario && route.params.name!=='all') {
|
||||
console.log('selectedScenario: im in');
|
||||
filters.value['scenario.name'].constraints[0].value = selectedScenario;
|
||||
}else{
|
||||
filters.value['scenario.name'].constraints[0].value = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateRating(rowData, newRating) {
|
||||
|
||||
@@ -287,7 +318,10 @@ function onPage(event) {
|
||||
first.value = event.first;
|
||||
}
|
||||
|
||||
|
||||
// onBeforeRouteLeave((to, from, next) => {
|
||||
// userPrefStore.setSelectedScenario(null);
|
||||
// console.log('selectedScenario: im out');
|
||||
// });
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -102,6 +102,9 @@ const scenarioTypeOp = ref([
|
||||
|
||||
scenario_store.fetchScenarios();
|
||||
scenario_store.fetchScenariosCross();
|
||||
if(userPrefStore.getSelApp != null){
|
||||
scenario_store.fetchApplicationScenarios();
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user