mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
PUT /services/{id}/variables replaced every row, and GET masks secrets as
******** — echoing the fetched list back would have overwritten real
secrets with asterisks. The backend now keeps the stored value when a
submitted secret still carries the mask.
Service detail gains a Variables section: add/edit/remove rows, secret
toggle, bulk .env paste (comments, export prefix, quoted values), and
inline validation for empty/invalid/duplicate keys. Parser and validator
live in variable-utils.ts with unit coverage.
Generated OpenAPI types drive the new updateServiceVariables client call.
ROADMAP: Variables UX marked done; per-environment values are already
structural — each environment owns its service instances.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
export type VariableDraft = { key: string; value: string; isSecret: boolean };
|
|
|
|
export const VARIABLE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
|
// parseDotenv handles KEY=VAL lines, '#' comments, 'export ' prefixes and
|
|
// single/double-quoted values — enough for standard .env pastes.
|
|
export function parseDotenv(text: string): VariableDraft[] {
|
|
const rows: VariableDraft[] = [];
|
|
for (const rawLine of text.split('\n')) {
|
|
const line = rawLine.trim();
|
|
if (!line || line.startsWith('#')) {
|
|
continue;
|
|
}
|
|
const stripped = line.startsWith('export ') ? line.slice(7).trim() : line;
|
|
const eq = stripped.indexOf('=');
|
|
if (eq <= 0) {
|
|
continue;
|
|
}
|
|
const key = stripped.slice(0, eq).trim();
|
|
let value = stripped.slice(eq + 1).trim();
|
|
if (
|
|
value.length >= 2 &&
|
|
((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
rows.push({ key, value, isSecret: false });
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
export function validateVariableRows(rows: VariableDraft[]): Record<number, string> {
|
|
const errors: Record<number, string> = {};
|
|
const seen = new Set<string>();
|
|
rows.forEach((row, i) => {
|
|
const key = row.key.trim();
|
|
if (!key) {
|
|
errors[i] = 'Key is required';
|
|
} else if (!VARIABLE_KEY_RE.test(key)) {
|
|
errors[i] = 'Letters, digits, underscore; must not start with a digit';
|
|
} else if (seen.has(key)) {
|
|
errors[i] = 'Duplicate key';
|
|
} else {
|
|
seen.add(key);
|
|
}
|
|
});
|
|
return errors;
|
|
}
|