feat: password fields with masked input, reveal toggle, confirmation
- Password deploy fields now use type=password (masked by default) - Added eye toggle button to reveal/hide password and confirm fields - Added confirmation field below each password input - Generate button fills both password and confirmation fields - Form validation checks password confirmation matches before deploy - Confirmation field only shown for new deployments (not already deployed) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -308,15 +308,25 @@
|
|||||||
</select>
|
</select>
|
||||||
{{else if eq .Type "password"}}
|
{{else if eq .Type "password"}}
|
||||||
<div class="input-with-button">
|
<div class="input-with-button">
|
||||||
<input type="text" id="field-{{.EnvVar}}" name="{{.EnvVar}}"
|
<input type="password" id="field-{{.EnvVar}}" name="{{.EnvVar}}"
|
||||||
class="form-control" value="{{.Default}}"
|
class="form-control" value="{{.Default}}"
|
||||||
placeholder="{{.Placeholder}}"
|
placeholder="{{.Placeholder}}"
|
||||||
data-field-type="password"
|
data-field-type="password"
|
||||||
required
|
required
|
||||||
{{if $.AlreadyDeployed}}disabled{{end}}>
|
{{if $.AlreadyDeployed}}disabled{{end}}>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline pw-toggle-btn"
|
||||||
|
onclick="togglePasswordField('field-{{.EnvVar}}', 'field-confirm-{{.EnvVar}}', this)"
|
||||||
|
title="Megjelenítés">👁</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline"
|
<button type="button" class="btn btn-sm btn-outline"
|
||||||
onclick="generatePassword('field-{{.EnvVar}}')">Generálás</button>
|
onclick="generatePassword('field-{{.EnvVar}}', 'field-confirm-{{.EnvVar}}')">Generálás</button>
|
||||||
</div>
|
</div>
|
||||||
|
{{if not $.AlreadyDeployed}}
|
||||||
|
<div class="input-with-button" style="margin-top:.25rem">
|
||||||
|
<input type="password" id="field-confirm-{{.EnvVar}}"
|
||||||
|
class="form-control" placeholder="Jelszó megerősítése"
|
||||||
|
data-confirm-for="field-{{.EnvVar}}">
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
{{else if eq .Type "boolean"}}
|
{{else if eq .Type "boolean"}}
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input type="checkbox" id="field-{{.EnvVar}}" name="{{.EnvVar}}" value="true"
|
<input type="checkbox" id="field-{{.EnvVar}}" name="{{.EnvVar}}" value="true"
|
||||||
@@ -549,7 +559,7 @@ function toggleAutoField(fieldId, btn) {
|
|||||||
el.type = el.type === 'password' ? 'text' : 'password';
|
el.type = el.type === 'password' ? 'text' : 'password';
|
||||||
btn.textContent = el.type === 'password' ? 'Megjelenítés' : 'Elrejtés';
|
btn.textContent = el.type === 'password' ? 'Megjelenítés' : 'Elrejtés';
|
||||||
}
|
}
|
||||||
function generatePassword(fieldId) {
|
function generatePassword(fieldId, confirmFieldId) {
|
||||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
let pass = '';
|
let pass = '';
|
||||||
const arr = new Uint8Array(16);
|
const arr = new Uint8Array(16);
|
||||||
@@ -558,6 +568,19 @@ function generatePassword(fieldId) {
|
|||||||
pass += chars[arr[i] % chars.length];
|
pass += chars[arr[i] % chars.length];
|
||||||
}
|
}
|
||||||
document.getElementById(fieldId).value = pass;
|
document.getElementById(fieldId).value = pass;
|
||||||
|
if (confirmFieldId) {
|
||||||
|
var ce = document.getElementById(confirmFieldId);
|
||||||
|
if (ce) ce.value = pass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function togglePasswordField(fieldId, confirmFieldId, btn) {
|
||||||
|
var el = document.getElementById(fieldId);
|
||||||
|
if (!el) return;
|
||||||
|
var newType = el.type === 'password' ? 'text' : 'password';
|
||||||
|
el.type = newType;
|
||||||
|
var ce = document.getElementById(confirmFieldId);
|
||||||
|
if (ce) ce.type = newType;
|
||||||
|
btn.title = newType === 'password' ? 'Megjelenítés' : 'Elrejtés';
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteStaleData(stackName, stalePath, btn) {
|
function deleteStaleData(stackName, stalePath, btn) {
|
||||||
@@ -620,6 +643,18 @@ document.getElementById('deploy-form').addEventListener('submit', async function
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client-side validation: check password confirmation matches
|
||||||
|
const confirmInputs = e.target.querySelectorAll('input[data-confirm-for]');
|
||||||
|
for (const ci of confirmInputs) {
|
||||||
|
const mainEl = document.getElementById(ci.getAttribute('data-confirm-for'));
|
||||||
|
if (mainEl && !mainEl.disabled && ci.value !== mainEl.value) {
|
||||||
|
const label = mainEl.closest('.form-group').querySelector('label').textContent.trim();
|
||||||
|
showAlert('A két jelszó nem egyezik: ' + label);
|
||||||
|
ci.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Client-side validation: check subdomain format
|
// Client-side validation: check subdomain format
|
||||||
const subdomainField = e.target.querySelector('.subdomain-input');
|
const subdomainField = e.target.querySelector('.subdomain-input');
|
||||||
if (subdomainField && !subdomainField.disabled) {
|
if (subdomainField && !subdomainField.disabled) {
|
||||||
|
|||||||
Reference in New Issue
Block a user