import React, { useState, useEffect, useContext } from 'react'; import { Card, Table, Button, Modal, Form, Input, Space, message, Popconfirm, Tag, Tooltip, Row, Col, Typography, Alert, Badge, Progress, Tabs, Select, Switch, Spin } from 'antd'; import { getAgentSyncColor, getConfigStatusColor, getEntityStatusColor } from '../utils/colors'; import EntitySyncStatus from './EntitySyncStatus'; import { PlusOutlined, DeleteOutlined, ReloadOutlined, LockOutlined, EyeOutlined, WarningOutlined, SafetyCertificateOutlined, SearchOutlined, PlayCircleOutlined, EditOutlined } from '@ant-design/icons'; import axios from 'axios'; import { useCluster } from '../contexts/ClusterContext'; import { useProgress } from '../contexts/ProgressContext'; import { formatEntityForSync } from '../utils/agentSync'; const { Title, Text } = Typography; const { TextArea } = Input; const { TabPane } = Tabs; const SSLManagement = () => { const { selectedCluster, clusters } = useCluster(); const [certificates, setCertificates] = useState([]); const [filteredCertificates, setFilteredCertificates] = useState([]); const [searchText, setSearchText] = useState(''); const [loading, setLoading] = useState(false); const [modalVisible, setModalVisible] = useState(false); const [viewModalVisible, setViewModalVisible] = useState(false); const [selectedCertificate, setSelectedCertificate] = useState(null); const [pendingChanges, setPendingChanges] = useState(false); const [form] = Form.useForm(); // Filter states with localStorage persistence const [showGlobal, setShowGlobal] = useState(() => { const saved = localStorage.getItem('ssl_filter_global'); return saved !== null ? JSON.parse(saved) : true; }); const [showClusterSpecific, setShowClusterSpecific] = useState(() => { const saved = localStorage.getItem('ssl_filter_cluster'); return saved !== null ? JSON.parse(saved) : true; }); // Filter toggle handlers const toggleGlobalFilter = () => { const newValue = !showGlobal; setShowGlobal(newValue); localStorage.setItem('ssl_filter_global', JSON.stringify(newValue)); }; const toggleClusterFilter = () => { const newValue = !showClusterSpecific; setShowClusterSpecific(newValue); localStorage.setItem('ssl_filter_cluster', JSON.stringify(newValue)); }; // Apply filters whenever certificates or filter states change useEffect(() => { let filtered = certificates; // Apply SSL type filters if (!showGlobal || !showClusterSpecific) { filtered = filtered.filter(cert => { if (cert.ssl_type === 'Global' && !showGlobal) return false; if (cert.ssl_type === 'Cluster-specific' && !showClusterSpecific) return false; return true; }); } // Apply search filter if (searchText) { filtered = filtered.filter(cert => cert.name.toLowerCase().includes(searchText.toLowerCase()) || cert.domain.toLowerCase().includes(searchText.toLowerCase()) || (cert.issuer && cert.issuer.toLowerCase().includes(searchText.toLowerCase())) ); } setFilteredCertificates(filtered); }, [certificates, showGlobal, showClusterSpecific, searchText]); useEffect(() => { if (selectedCluster) { fetchCertificates(); checkPendingChanges(); } else { // Clear certificates when no cluster is selected setCertificates([]); setFilteredCertificates([]); setPendingChanges(false); } }, [selectedCluster]); const fetchCertificates = async () => { if (!selectedCluster) return; setLoading(true); // Clear existing certificates immediately when fetching new cluster data setCertificates([]); setFilteredCertificates([]); try { const response = await axios.get(`/api/ssl/certificates?cluster_id=${selectedCluster.id}`, { headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache' } }); // Handle different response formats const certs = response.data.certificates || response.data || []; console.log('🎯 SSL FETCH DEBUG: Response structure:', { 'response.data': Object.keys(response.data), 'certificates count': certs.length, 'sample cert': certs[0] ? { name: certs[0].name, has_pending_config: certs[0].has_pending_config, last_config_status: certs[0].last_config_status } : null }); setCertificates(certs); setFilteredCertificates(certs); } catch (error) { console.error('SSL certificates fetch error:', error); message.error('Failed to fetch SSL certificates: ' + error.message); setCertificates([]); setFilteredCertificates([]); } finally { setLoading(false); } }; // Search filter function const handleSearch = (value) => { setSearchText(value); if (!value) { setFilteredCertificates(certificates || []); } else { const filtered = certificates.filter(cert => cert.name.toLowerCase().includes(value.toLowerCase()) || cert.domain.toLowerCase().includes(value.toLowerCase()) ); setFilteredCertificates(filtered); } }; // Update filtered data when certificates change useEffect(() => { if (searchText) { handleSearch(searchText); } else { setFilteredCertificates(certificates); } }, [certificates, searchText]); // Check for pending SSL configuration changes const checkPendingChanges = async () => { if (!selectedCluster) return; try { console.log('🎯 SSL APPLY DEBUG: Checking pending changes for cluster:', selectedCluster.id); console.log('🔄 Frontend deployment check - v2.0'); const response = await axios.get(`/api/clusters/${selectedCluster.id}/config-versions`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); const versions = response.data.config_versions || response.data.versions || response.data || []; console.log('🎯 SSL APPLY DEBUG: Versions type:', typeof versions, 'Array?', Array.isArray(versions), 'Value:', versions); if (!Array.isArray(versions)) { console.error('🎯 SSL APPLY DEBUG: versions is not an array!', versions); setPendingChanges(false); return; } const pendingVersions = versions.filter(version => version.status === 'PENDING' && version.version_name.includes('ssl-') ); console.log('🎯 SSL APPLY DEBUG: Total versions:', versions.length); console.log('🎯 SSL APPLY DEBUG: SSL pending versions:', pendingVersions.length); console.log('🎯 SSL APPLY DEBUG: Pending SSL versions:', pendingVersions.map(v => v.version_name)); setPendingChanges(pendingVersions.length > 0); } catch (error) { console.error('🎯 SSL APPLY DEBUG: Failed to check pending changes:', error); setPendingChanges(false); } }; // Fetch entity agent sync status (consistent with other pages) const fetchEntityAgentSync = async (entityType, entityId) => { if (!selectedCluster) return null; try { const token = localStorage.getItem('token'); // SSL certificates use a special endpoint for agent sync let endpoint; if (entityType === 'ssl_certificates') { endpoint = `/api/clusters/${selectedCluster.id}/ssl_certificates/${entityId}/agent-sync`; } else { endpoint = `/api/clusters/${selectedCluster.id}/entity-sync/${entityType}/${entityId}`; } const response = await axios.get(endpoint, { headers: { Authorization: `Bearer ${token}` } }); return response.data; } catch (error) { console.error(`Failed to fetch entity sync for ${entityType}/${entityId}:`, error); return null; } }; const handleAdd = () => { form.resetFields(); // Set default values for new certificate form.setFieldsValue({ ssl_type: 'cluster' }); setSelectedCertificate(null); setModalVisible(true); }; const handleView = async (certificate) => { try { const response = await axios.get(`/api/ssl/certificates/${certificate.id}`); setSelectedCertificate(response.data); setViewModalVisible(true); } catch (error) { message.error('Failed to load certificate details: ' + error.message); } }; const handleEdit = async (certificate) => { try { const response = await axios.get(`/api/ssl/certificates/${certificate.id}`); const cert = response.data; // Pre-fill form with certificate data form.setFieldsValue({ name: cert.name, certificate_content: cert.certificate_content, private_key_content: cert.private_key_content, chain_content: cert.chain_content, ssl_type: cert.is_global ? 'global' : 'cluster', cluster_ids: cert.is_global ? null : cert.cluster_ids }); setSelectedCertificate(cert); setModalVisible(true); } catch (error) { message.error('Failed to load certificate for editing: ' + error.message); } }; const handleDelete = async (certificateId) => { try { const response = await axios.delete(`/api/ssl/certificates/${certificateId}`); // Handle cluster sync results const syncResults = response.data.sync_results || []; const totalNodes = syncResults.length; const successCount = syncResults.filter(result => result.success).length; if (syncResults.length > 0) { if (successCount === totalNodes) { message.success(
A certificate with the name "{form.getFieldValue('name')}" already exists.
Please choose one of the following options:
Certificate validation failed:
{errorDetail}
Please check your certificate content and try again.