import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AlertCircle, CheckCircle, Save, Shield } from "lucide-react"; import { useEffect, useId, useState } from "react"; import { permissionsAPI, settingsAPI } from "../../utils/api"; const AgentUpdatesTab = () => { const updateIntervalId = useId(); const autoUpdateId = useId(); const signupEnabledId = useId(); const defaultRoleId = useId(); const ignoreSslId = useId(); const [formData, setFormData] = useState({ updateInterval: 60, autoUpdate: false, signupEnabled: false, defaultUserRole: "user", ignoreSslSelfSigned: false, }); const [errors, setErrors] = useState({}); const [isDirty, setIsDirty] = useState(false); const queryClient = useQueryClient(); // Fetch current settings const { data: settings, isLoading, error, } = useQuery({ queryKey: ["settings"], queryFn: () => settingsAPI.get().then((res) => res.data), }); // Fetch available roles for default user role dropdown const { data: roles, isLoading: rolesLoading } = useQuery({ queryKey: ["rolePermissions"], queryFn: () => permissionsAPI.getRoles().then((res) => res.data), }); // Update form data when settings are loaded useEffect(() => { if (settings) { const newFormData = { updateInterval: settings.update_interval || 60, autoUpdate: settings.auto_update || false, signupEnabled: settings.signup_enabled === true, defaultUserRole: settings.default_user_role || "user", ignoreSslSelfSigned: settings.ignore_ssl_self_signed === true, }; setFormData(newFormData); setIsDirty(false); } }, [settings]); // Update settings mutation const updateSettingsMutation = useMutation({ mutationFn: (data) => { return settingsAPI.update(data).then((res) => res.data); }, onSuccess: () => { queryClient.invalidateQueries(["settings"]); setIsDirty(false); setErrors({}); }, onError: (error) => { if (error.response?.data?.errors) { setErrors( error.response.data.errors.reduce((acc, err) => { acc[err.path] = err.msg; return acc; }, {}), ); } else { setErrors({ general: error.response?.data?.error || "Failed to update settings", }); } }, }); // Normalize update interval to safe presets const normalizeInterval = (minutes) => { let m = parseInt(minutes, 10); if (Number.isNaN(m)) return 60; if (m < 5) m = 5; if (m > 1440) m = 1440; // If less than 60 minutes, keep within 5-59 and step of 5 if (m < 60) { return Math.min(59, Math.max(5, Math.round(m / 5) * 5)); } // 60 or more: only allow exact hour multiples (60, 120, 180, 360, 720, 1440) const allowed = [60, 120, 180, 360, 720, 1440]; // Snap to nearest allowed value let nearest = allowed[0]; let bestDiff = Math.abs(m - nearest); for (const a of allowed) { const d = Math.abs(m - a); if (d < bestDiff) { bestDiff = d; nearest = a; } } return nearest; }; const handleInputChange = (field, value) => { setFormData((prev) => { const newData = { ...prev, [field]: field === "updateInterval" ? normalizeInterval(value) : value, }; return newData; }); setIsDirty(true); if (errors[field]) { setErrors((prev) => ({ ...prev, [field]: null })); } }; const validateForm = () => { const newErrors = {}; if ( !formData.updateInterval || formData.updateInterval < 5 || formData.updateInterval > 1440 ) { newErrors.updateInterval = "Update interval must be between 5 and 1440 minutes"; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSave = () => { if (validateForm()) { updateSettingsMutation.mutate(formData); } }; if (isLoading) { return (
{error.response?.data?.error || "Failed to load settings"}
{errors.general}
To completely remove PatchMon from a host:
{/* Go Agent Uninstall */}--remove-config,{" "}
--remove-logs, --remove-all,{" "}
--force
⚠️ This command will remove all PatchMon files, configuration, and crontab entries