Feat: Add YAML validation for compose content in deployment flows by austin047 · Pull Request #46 · flatrun/ui · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
18 changes: 17 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"primevue": "^3.50.0",
"vue": "^3.4.21",
"vue-codemirror": "^6.1.1",
"vue-router": "^4.3.0"
"vue-router": "^4.3.0",
"yaml": "^2.8.2"
},
"devDependencies": {
"@pinia/testing": "^0.1.6",
Expand Down
47 changes: 35 additions & 12 deletions src/components/NewDeploymentModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -754,24 +754,38 @@
Password
<span v-if="form.database.mode === 'create'" class="required">*</span>
</label>
<input
id="dbPassword"
v-model="form.database.dbPassword"
type="password"
placeholder="••••••••"
/>
<div class="input-wrapper">
<input
id="dbPassword"
v-model="form.database.dbPassword"
:type="showDbPassword ? 'text' : 'password'"
placeholder="••••••••"
/>
<button type="button" class="input-icon-btn" @click="showDbPassword = !showDbPassword">
<i :class="showDbPassword ? 'pi pi-eye-slash' : 'pi pi-eye'" />
</button>
</div>
</div>
</div>

<Transition name="expand">
<div v-if="form.database.mode === 'create'" class="form-field">
<label for="dbRootPassword">Root Password</label>
<input
id="dbRootPassword"
v-model="form.database.dbRootPassword"
type="password"
placeholder="Leave empty to use same as password"
/>
<div class="input-wrapper">
<input
id="dbRootPassword"
v-model="form.database.dbRootPassword"
:type="showDbRootPassword ? 'text' : 'password'"
placeholder="Leave empty to use same as password"
/>
<button
type="button"
class="input-icon-btn"
@click="showDbRootPassword = !showDbRootPassword"
>
<i :class="showDbRootPassword ? 'pi pi-eye-slash' : 'pi pi-eye'" />
</button>
</div>
<span class="field-hint">Admin password for new database</span>
</div>
</Transition>
Expand Down Expand Up @@ -1345,6 +1359,7 @@ import BaseModal from "@/components/base/BaseModal.vue";
import { deploymentsApi, templatesApi, settingsApi, containersApi, composeApi, credentialsApi } from "@/services/api";
import type { RegistryCredential } from "@/types";
import { useNotificationsStore } from "@/stores/notifications";
import { validateComposeYaml } from "@/utils/yaml";

interface TemplateMount {
id: string;
Expand Down Expand Up @@ -1407,6 +1422,8 @@ const extensions = shallowRef([yaml(), oneDark]);

const deploymentMode = ref<"" | "easy" | "compose" | "image">("");
const showRegistryPassword = ref(false);
const showDbPassword = ref(false);
const showDbRootPassword = ref(false);
const existingCredentials = ref<RegistryCredential[]>([]);
const loadingCredentials = ref(false);

Expand Down Expand Up @@ -2331,6 +2348,12 @@ const validate = () => {
if (!form.composeContent.trim()) {
errors.composeContent = "Compose configuration is required";
valid = false;
} else {
const yamlResult = validateComposeYaml(form.composeContent);
if (!yamlResult.valid) {
errors.composeContent = yamlResult.error;
valid = false;
}
}

return valid;
Expand Down
21 changes: 21 additions & 0 deletions src/utils/yaml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { parse } from "yaml";

export type ComposeYamlValidation = { valid: true } | { valid: false; error: string };

/**
* This function validates that a string is valid YAML. It is used before sending compose content
* to the API (e.g.during a deployment update or create).
*/
export function validateComposeYaml(content: string): ComposeYamlValidation {
const trimmed = content.trim();
if (!trimmed) {
return { valid: false, error: "Compose content is empty" };
}
try {
parse(trimmed);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, the yaml package's parse function might only throw for critical syntax errors. For stricter validation (e.g., catching duplicate keys which are common in Compose files), it is better to check for warnings or use a stricter parsing configuration if available in the version installed.

Suggested change
parse(trimmed);
parse(trimmed, { uniqueKeys: true });

return { valid: true };
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "Invalid YAML syntax";
return { valid: false, error: message };
}
}
10 changes: 8 additions & 2 deletions src/views/DeploymentDetailView.vue