Skip to content

Commit e7d2087

Browse files
Validate Compose before wizard review
1 parent e823323 commit e7d2087

3 files changed

Lines changed: 66 additions & 14 deletions

File tree

‎cmd/stackhost/applications.go‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ type sourceResult struct {
5050
Summary composevalidator.Summary `json:"summary"`
5151
}
5252

53+
func (a *app) validateSourcePreview(w http.ResponseWriter, r *http.Request) {
54+
if r.Method != http.MethodPost {
55+
jsonError(w, 405, "method_not_allowed", "Método não permitido.")
56+
return
57+
}
58+
var input struct {
59+
SourceType string `json:"source_type"`
60+
Source sourceInput `json:"source"`
61+
}
62+
r.Body = http.MaxBytesReader(w, r.Body, 1024*1024+64*1024)
63+
if json.NewDecoder(r.Body).Decode(&input) != nil || !sourceTypeValid(input.SourceType) {
64+
jsonValidation(w, map[string]string{"source_type": "Selecione uma origem válida."})
65+
return
66+
}
67+
result := validateSource(input.SourceType, input.Source)
68+
json.NewEncoder(w).Encode(result)
69+
}
70+
5371
func sourceTypeValid(source string) bool {
5472
return map[string]bool{"catalog": true, "compose": true, "image": true, "git": true}[source]
5573
}

‎cmd/stackhost/main.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ func (a *app) routes() http.Handler {
152152
mux.HandleFunc("/api/v1/projects/", a.auth(a.projectRoute))
153153
mux.HandleFunc("/api/v1/applications/", a.auth(a.applicationRouteV2))
154154
mux.HandleFunc("/api/v1/catalog", a.auth(a.catalog))
155+
mux.HandleFunc("/api/v1/source/validate", a.auth(a.validateSourcePreview))
155156
mux.HandleFunc("/api/v1/infrastructure", a.auth(a.infrastructure))
156157
mux.HandleFunc("/api/v1/infrastructure/nodes", a.auth(a.infrastructureNodes))
157158
mux.HandleFunc("/api/v1/infrastructure/services", a.auth(a.infrastructureServices))

‎web/src/views/ProjectDetail.vue‎

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ const appModal = ref(false);
2121
const wizardDiscardOpen = ref(false);
2222
const deleteApp = ref<Application | null>(null);
2323
const 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);
2431
const fieldErrors = ref<Record<string, string>>({});
2532
const editProject = ref({ name: "", description: "" });
2633
const 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

Comments
 (0)