@@ -21,6 +21,13 @@ const appModal = ref(false);
2121const wizardDiscardOpen = ref (false );
2222const deleteApp = ref <Application | null >(null );
2323const saving = ref (false );
24+ const validating = ref (false );
25+ const validationPreview = ref <{
26+ valid: boolean ;
27+ errors: string [];
28+ warnings: string [];
29+ summary? : { services: string []; images: string []; ports: string [] };
30+ } | null >(null );
2431const fieldErrors = ref <Record <string , string >>({});
2532const editProject = ref ({ name: " " , description: " " });
2633const editApp = ref ({ id: 0 , name: " " });
@@ -161,7 +168,7 @@ function discardWizard() {
161168 wizardDiscardOpen .value = false ;
162169 appModal .value = false ;
163170}
164- function nextAppStep() {
171+ async function nextAppStep() {
165172 if (appStep .value === 1 && ! newApp .value .name .trim ()) {
166173 fieldErrors .value = { name: " O nome é obrigatório." };
167174 return ;
@@ -176,15 +183,37 @@ function nextAppStep() {
176183 catalogTemplates .value = items ;
177184 });
178185 }
179- if (
180- appStep .value === 3 &&
181- newApp .value .source_type === " compose" &&
182- ! newSource .value .compose_yaml .includes (" services:" )
183- ) {
184- fieldErrors .value = {
185- source: " Inclua uma seção services válida no Compose." ,
186- };
187- return ;
186+ if (appStep .value === 3 && newApp .value .source_type === " compose" ) {
187+ validating .value = true ;
188+ try {
189+ validationPreview .value = await api <typeof validationPreview .value >(
190+ " /api/v1/source/validate" ,
191+ {
192+ method: " POST" ,
193+ body: JSON .stringify ({
194+ source_type: " compose" ,
195+ source: newSource .value ,
196+ }),
197+ },
198+ );
199+ if (! validationPreview .value ?.valid ) {
200+ fieldErrors .value = {
201+ source:
202+ validationPreview .value ?.errors ?.[0 ] ||
203+ " Revise o Compose antes de continuar." ,
204+ };
205+ return ;
206+ }
207+ } catch (err ) {
208+ toast .error (
209+ err instanceof Error
210+ ? err .message
211+ : " Não foi possível validar o Compose." ,
212+ );
213+ return ;
214+ } finally {
215+ validating .value = false ;
216+ }
188217 }
189218 if (
190219 appStep .value === 3 &&
@@ -616,13 +645,17 @@ onMounted(load);
616645 v-if =" !editApp.id && appStep < 4"
617646 class =" primary"
618647 type =" button"
619- :disabled =" saving || (appStep === 2 && !newApp.source_type)"
648+ :disabled ="
649+ saving || validating || (appStep === 2 && !newApp.source_type)
650+ "
620651 @click =" nextAppStep"
621652 >
622653 {{
623- appStep === 3 && newApp.source_type === "compose"
624- ? "Validar e continuar"
625- : "Continuar"
654+ validating
655+ ? "Validando…"
656+ : appStep === 3 && newApp.source_type === "compose"
657+ ? "Validar e continuar"
658+ : "Continuar"
626659 }}</button
627660 ><button
628661 v-if =" !editApp.id && appStep === 4"
0 commit comments