diff --git a/frontend/src/components/NotificationCenter.tsx b/frontend/src/components/NotificationCenter.tsx index 195f5592..f5852809 100644 --- a/frontend/src/components/NotificationCenter.tsx +++ b/frontend/src/components/NotificationCenter.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { Bell, X, Info, AlertTriangle, AlertCircle, CheckCircle } from 'lucide-react'; -import { getNotifications, markNotificationRead, markAllNotificationsRead } from '../api/system'; +import { Bell, X, Info, AlertTriangle, AlertCircle, CheckCircle, ExternalLink } from 'lucide-react'; +import { getNotifications, markNotificationRead, markAllNotificationsRead, checkUpdates } from '../api/system'; const NotificationCenter: React.FC = () => { const [isOpen, setIsOpen] = useState(false); @@ -13,6 +13,12 @@ const NotificationCenter: React.FC = () => { refetchInterval: 30000, // Poll every 30s }); + const { data: updateInfo } = useQuery({ + queryKey: ['system-updates'], + queryFn: checkUpdates, + staleTime: 1000 * 60 * 60, // 1 hour + }); + const markReadMutation = useMutation({ mutationFn: markNotificationRead, onSuccess: () => { @@ -27,7 +33,15 @@ const NotificationCenter: React.FC = () => { }, }); - const unreadCount = notifications.length; + const unreadCount = notifications.length + (updateInfo?.available ? 1 : 0); + const hasCritical = notifications.some(n => n.type === 'error'); + const hasWarning = notifications.some(n => n.type === 'warning') || updateInfo?.available; + + const getBellColor = () => { + if (hasCritical) return 'text-red-500 hover:text-red-600'; + if (hasWarning) return 'text-yellow-500 hover:text-yellow-600'; + return 'text-green-500 hover:text-green-600'; + }; const getIcon = (type: string) => { switch (type) { @@ -42,12 +56,12 @@ const NotificationCenter: React.FC = () => {
- {notifications.length === 0 ? ( + {/* Update Notification */} + {updateInfo?.available && ( +
+
+ +
+
+

+ Update Available: {updateInfo.latest_version} +

+ + View Changelog + +
+
+ )} + + {notifications.length === 0 && !updateInfo?.available ? (
No new notifications
diff --git a/frontend/src/components/ProxyHostForm.tsx b/frontend/src/components/ProxyHostForm.tsx index 013857aa..7633f021 100644 --- a/frontend/src/components/ProxyHostForm.tsx +++ b/frontend/src/components/ProxyHostForm.tsx @@ -1,4 +1,5 @@ import { useState } from 'react' +import { CircleHelp } from 'lucide-react' import type { ProxyHost } from '../api/proxyHosts' import { useRemoteServers } from '../hooks/useRemoteServers' import { useDocker } from '../hooks/useDocker' @@ -20,15 +21,31 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor hsts_enabled: host?.hsts_enabled ?? false, hsts_subdomains: host?.hsts_subdomains ?? false, block_exploits: host?.block_exploits ?? true, - websocket_support: host?.websocket_support ?? false, + websocket_support: host?.websocket_support ?? true, advanced_config: host?.advanced_config || '', enabled: host?.enabled ?? true, }) const { servers: remoteServers } = useRemoteServers() - const [dockerHost, setDockerHost] = useState('') - const [showDockerHost, setShowDockerHost] = useState(false) - const { containers: dockerContainers, isLoading: dockerLoading, error: dockerError } = useDocker(dockerHost) + const [connectionSource, setConnectionSource] = useState<'local' | string>('local') + + // Fetch containers based on selected source + // If 'local', host is undefined (which defaults to local socket in backend) + // If remote UUID, we need to find the server and get its host address? + // Actually, the backend ListContainers takes a 'host' query param. + // If it's a remote server, we should probably pass the UUID or the host address. + // Looking at backend/internal/services/docker_service.go, it takes a 'host' string. + // If it's a remote server, we need to pass the TCP address (e.g. tcp://1.2.3.4:2375). + + const getDockerHostString = () => { + if (connectionSource === 'local') return undefined; + const server = remoteServers.find(s => s.uuid === connectionSource); + if (!server) return undefined; + // Construct the Docker host string + return `tcp://${server.host}:${server.port}`; + } + + const { containers: dockerContainers, isLoading: dockerLoading, error: dockerError } = useDocker(getDockerHostString()) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) @@ -130,28 +147,31 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor {/* Docker Container Quick Select */}
-
- - -
+ + +
- {showDockerHost && ( - setDockerHost(e.target.value)} - className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white text-sm mb-2 focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - )} +
+ HTTP/2 Support +
+ +
setFormData({ ...formData, username: e.target.value })} - className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500" - /> -
+ {formData.provider !== 'docker' && ( +
+ + setFormData({ ...formData, username: e.target.value })} + className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ )}