mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-02-10 18:05:07 +08:00
145 lines
4.3 KiB
JavaScript
145 lines
4.3 KiB
JavaScript
import { useState, useEffect, useCallback } from 'react';
|
|
import Ajv from 'ajv';
|
|
import addFormats from 'ajv-formats';
|
|
import { fetchConfigurationSchema } from '../api';
|
|
|
|
// Cron expression with 6 space-separated fields (server-compatible)
|
|
const CRON6_REGEX = /^\s*\S+(?:\s+\S+){5}\s*$/;
|
|
|
|
/**
|
|
* Hook for field validation using backend schema
|
|
* @returns {Object} { validateField, getFieldError, isLoading, error }
|
|
*/
|
|
export const useFieldValidation = () => {
|
|
const [validator, setValidator] = useState(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState(null);
|
|
const [fieldErrors, setFieldErrors] = useState({});
|
|
|
|
// Initialize validator with schema from backend
|
|
useEffect(() => {
|
|
const initializeValidator = async () => {
|
|
try {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
const schema = await fetchConfigurationSchema();
|
|
|
|
// Build AJV validator with custom and standard formats
|
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
addFormats(ajv); // Add standard formats including email
|
|
ajv.addFormat('cron6', CRON6_REGEX); // Add custom cron6 format
|
|
|
|
const validateFn = ajv.compile(schema);
|
|
setValidator(() => validateFn);
|
|
} catch (err) {
|
|
console.error('Failed to initialize field validator:', err);
|
|
setError(err.message);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
initializeValidator();
|
|
}, []);
|
|
|
|
/**
|
|
* Validates a single field value against the schema
|
|
* @param {string} fieldPath - Dot-notation path to the field (e.g., 'FileConverter.converter.maxDownloadBytes')
|
|
* @param {*} value - Value to validate
|
|
* @returns {string|null} Error message or null if valid
|
|
*/
|
|
const validateField = useCallback((fieldPath, value) => {
|
|
if (!validator) {
|
|
return null; // No validator available yet
|
|
}
|
|
|
|
// Create a minimal object with just the field we want to validate
|
|
const testObject = createNestedObject(fieldPath, value);
|
|
|
|
// Validate the test object
|
|
const isValid = validator(testObject);
|
|
|
|
if (!isValid && validator.errors) {
|
|
// Find errors that match our field path
|
|
const relevantErrors = validator.errors.filter(err => {
|
|
const errorPath = (err.instancePath || '').replace(/^\/|\/$/g, '').replace(/\//g, '.');
|
|
return errorPath === fieldPath || errorPath === '';
|
|
});
|
|
|
|
if (relevantErrors.length > 0) {
|
|
const errorMessage = relevantErrors[0].message || 'Invalid value';
|
|
setFieldErrors(prev => ({ ...prev, [fieldPath]: errorMessage }));
|
|
return errorMessage;
|
|
}
|
|
}
|
|
|
|
// Clear any existing error for this field
|
|
setFieldErrors(prev => {
|
|
const newErrors = { ...prev };
|
|
delete newErrors[fieldPath];
|
|
return newErrors;
|
|
});
|
|
|
|
return null;
|
|
}, [validator]);
|
|
|
|
/**
|
|
* Gets the current error message for a field
|
|
* @param {string} fieldPath - Dot-notation path to the field
|
|
* @returns {string|null} Error message or null
|
|
*/
|
|
const getFieldError = useCallback((fieldPath) => {
|
|
return fieldErrors[fieldPath] || null;
|
|
}, [fieldErrors]);
|
|
|
|
/**
|
|
* Clears error for a specific field
|
|
* @param {string} fieldPath - Dot-notation path to the field
|
|
*/
|
|
const clearFieldError = useCallback((fieldPath) => {
|
|
setFieldErrors(prev => {
|
|
const newErrors = { ...prev };
|
|
delete newErrors[fieldPath];
|
|
return newErrors;
|
|
});
|
|
}, []);
|
|
|
|
/**
|
|
* Checks if there are any validation errors
|
|
* @returns {boolean} True if there are validation errors
|
|
*/
|
|
const hasValidationErrors = useCallback(() => {
|
|
return Object.keys(fieldErrors).length > 0;
|
|
}, [fieldErrors]);
|
|
|
|
return {
|
|
validateField,
|
|
getFieldError,
|
|
clearFieldError,
|
|
hasValidationErrors,
|
|
isLoading,
|
|
error
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Creates a nested object from a dot-notation path and value
|
|
* @param {string} path - Dot-notation path (e.g., 'FileConverter.converter.maxDownloadBytes')
|
|
* @param {*} value - Value to set
|
|
* @returns {Object} Nested object
|
|
*/
|
|
export function createNestedObject(path, value) {
|
|
const parts = path.split('.');
|
|
const result = {};
|
|
let current = result;
|
|
|
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
current[parts[i]] = {};
|
|
current = current[parts[i]];
|
|
}
|
|
|
|
current[parts[parts.length - 1]] = value;
|
|
return result;
|
|
}
|