feat(ui): validate API form and improve error UX
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initSourceType();
|
||||
initApiSource();
|
||||
initUpload();
|
||||
initTabs();
|
||||
initFilters();
|
||||
@@ -9,6 +10,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
let sourceType = 'archive';
|
||||
let apiConnectPayload = null;
|
||||
|
||||
function initSourceType() {
|
||||
const sourceButtons = document.querySelectorAll('.source-switch-btn');
|
||||
@@ -29,9 +31,214 @@ function setSourceType(nextType) {
|
||||
});
|
||||
|
||||
const archiveContent = document.getElementById('archive-source-content');
|
||||
const apiPlaceholder = document.getElementById('api-source-placeholder');
|
||||
const apiSourceContent = document.getElementById('api-source-content');
|
||||
archiveContent.classList.toggle('hidden', sourceType !== 'archive');
|
||||
apiPlaceholder.classList.toggle('hidden', sourceType !== 'api');
|
||||
apiSourceContent.classList.toggle('hidden', sourceType !== 'api');
|
||||
}
|
||||
|
||||
function initApiSource() {
|
||||
const apiForm = document.getElementById('api-connect-form');
|
||||
if (!apiForm) {
|
||||
return;
|
||||
}
|
||||
|
||||
const authTypeField = document.getElementById('api-auth-type');
|
||||
const fieldNames = ['host', 'protocol', 'port', 'username', 'authType', 'password', 'token'];
|
||||
|
||||
apiForm.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
const { isValid, payload, errors } = validateCollectForm();
|
||||
renderFormErrors(errors);
|
||||
renderApiConnectStatus(isValid, payload);
|
||||
|
||||
if (!isValid) {
|
||||
apiConnectPayload = null;
|
||||
return;
|
||||
}
|
||||
|
||||
apiConnectPayload = payload;
|
||||
console.log('API payload prepared:', apiConnectPayload);
|
||||
});
|
||||
|
||||
fieldNames.forEach((fieldName) => {
|
||||
const field = apiForm.elements.namedItem(fieldName);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventName = field.tagName.toLowerCase() === 'select' ? 'change' : 'input';
|
||||
field.addEventListener(eventName, () => {
|
||||
if (fieldName === 'authType') {
|
||||
toggleApiAuthFields(authTypeField.value);
|
||||
}
|
||||
|
||||
const { errors } = validateCollectForm();
|
||||
renderFormErrors(errors);
|
||||
clearApiConnectStatus();
|
||||
});
|
||||
});
|
||||
|
||||
toggleApiAuthFields(authTypeField.value);
|
||||
}
|
||||
|
||||
function validateCollectForm() {
|
||||
const host = getApiValue('host');
|
||||
const protocol = getApiValue('protocol');
|
||||
const portRaw = getApiValue('port');
|
||||
const username = getApiValue('username');
|
||||
const authType = getApiValue('authType');
|
||||
const password = getApiValue('password');
|
||||
const token = getApiValue('token');
|
||||
|
||||
const errors = {};
|
||||
|
||||
if (!host) {
|
||||
errors.host = 'Укажите host.';
|
||||
}
|
||||
|
||||
if (!['redfish', 'ipmi'].includes(protocol)) {
|
||||
errors.protocol = 'Выберите протокол.';
|
||||
}
|
||||
|
||||
const port = Number(portRaw);
|
||||
const isPortInteger = Number.isInteger(port);
|
||||
if (!portRaw) {
|
||||
errors.port = 'Укажите порт.';
|
||||
} else if (!isPortInteger || port < 1 || port > 65535) {
|
||||
errors.port = 'Порт должен быть от 1 до 65535.';
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
errors.username = 'Укажите username.';
|
||||
}
|
||||
|
||||
if (!['password', 'token'].includes(authType)) {
|
||||
errors.authType = 'Выберите тип авторизации.';
|
||||
}
|
||||
|
||||
if (authType === 'password' && !password) {
|
||||
errors.password = 'Введите пароль.';
|
||||
}
|
||||
|
||||
if (authType === 'token' && !token) {
|
||||
errors.token = 'Введите токен.';
|
||||
}
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
return { isValid: false, errors, payload: null };
|
||||
}
|
||||
|
||||
const payload = {
|
||||
sourceType: 'api',
|
||||
connection: {
|
||||
host,
|
||||
protocol,
|
||||
port,
|
||||
username,
|
||||
authType
|
||||
}
|
||||
};
|
||||
|
||||
if (authType === 'password') {
|
||||
payload.connection.password = password;
|
||||
} else {
|
||||
payload.connection.token = token;
|
||||
}
|
||||
|
||||
return { isValid: true, errors: {}, payload };
|
||||
}
|
||||
|
||||
function renderFormErrors(errors) {
|
||||
const apiForm = document.getElementById('api-connect-form');
|
||||
const summary = document.getElementById('api-form-errors');
|
||||
if (!apiForm || !summary) {
|
||||
return;
|
||||
}
|
||||
|
||||
const errorFields = ['host', 'protocol', 'port', 'username', 'authType', 'password', 'token'];
|
||||
errorFields.forEach((fieldName) => {
|
||||
const errorNode = apiForm.querySelector(`[data-error-for="${fieldName}"]`);
|
||||
if (!errorNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldWrapper = errorNode.closest('.api-form-field');
|
||||
const message = errors[fieldName] || '';
|
||||
errorNode.textContent = message;
|
||||
if (fieldWrapper) {
|
||||
fieldWrapper.classList.toggle('has-error', Boolean(message));
|
||||
}
|
||||
});
|
||||
|
||||
const messages = Object.values(errors);
|
||||
if (messages.length === 0) {
|
||||
summary.innerHTML = '';
|
||||
summary.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
summary.classList.remove('hidden');
|
||||
summary.innerHTML = `<strong>Исправьте ошибки в форме:</strong><ul>${messages.map(msg => `<li>${escapeHtml(msg)}</li>`).join('')}</ul>`;
|
||||
}
|
||||
|
||||
function renderApiConnectStatus(isValid, payload) {
|
||||
const status = document.getElementById('api-connect-status');
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
status.textContent = 'Форма не отправлена: есть ошибки.';
|
||||
status.className = 'api-connect-status error';
|
||||
return;
|
||||
}
|
||||
|
||||
const payloadPreview = { ...payload, connection: { ...payload.connection } };
|
||||
if (payloadPreview.connection.password) {
|
||||
payloadPreview.connection.password = '***';
|
||||
}
|
||||
if (payloadPreview.connection.token) {
|
||||
payloadPreview.connection.token = '***';
|
||||
}
|
||||
|
||||
status.textContent = `Payload сформирован: ${JSON.stringify(payloadPreview)}`;
|
||||
status.className = 'api-connect-status success';
|
||||
}
|
||||
|
||||
function clearApiConnectStatus() {
|
||||
const status = document.getElementById('api-connect-status');
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
status.textContent = '';
|
||||
status.className = 'api-connect-status';
|
||||
}
|
||||
|
||||
function toggleApiAuthFields(authType) {
|
||||
const passwordField = document.getElementById('api-password-field');
|
||||
const tokenField = document.getElementById('api-token-field');
|
||||
|
||||
if (!passwordField || !tokenField) {
|
||||
return;
|
||||
}
|
||||
|
||||
const useToken = authType === 'token';
|
||||
passwordField.classList.toggle('hidden', useToken);
|
||||
tokenField.classList.toggle('hidden', !useToken);
|
||||
}
|
||||
|
||||
function getApiValue(fieldName) {
|
||||
const apiForm = document.getElementById('api-connect-form');
|
||||
if (!apiForm) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const field = apiForm.elements.namedItem(fieldName);
|
||||
if (!field || typeof field.value !== 'string') {
|
||||
return '';
|
||||
}
|
||||
return field.value.trim();
|
||||
}
|
||||
|
||||
// Load and display available parsers
|
||||
|
||||
Reference in New Issue
Block a user