chore: remove cashed

This commit is contained in:
Wikid82
2025-11-24 18:22:01 +00:00
parent 9c842e7eab
commit 6feff3e8ce
289 changed files with 39795 additions and 0 deletions

View File

@@ -0,0 +1,241 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { renderHook, waitFor, act } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import React from 'react'
import { useImport } from '../useImport'
import * as api from '../../api/import'
// Mock the API
vi.mock('../../api/import', () => ({
uploadCaddyfile: vi.fn(),
getImportPreview: vi.fn(),
commitImport: vi.fn(),
cancelImport: vi.fn(),
getImportStatus: vi.fn(),
}))
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
})
return ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
}
describe('useImport', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: false })
})
afterEach(() => {
vi.clearAllMocks()
})
it('starts with no active session', async () => {
const { result } = renderHook(() => useImport(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
expect(result.current.session).toBeNull()
})
expect(result.current.error).toBeNull()
})
it('uploads content and creates session', async () => {
const mockSession = {
id: 'session-1',
state: 'reviewing' as const,
created_at: '2025-01-18T10:00:00Z',
updated_at: '2025-01-18T10:00:00Z',
}
const mockPreviewData = {
hosts: [{ domain_names: 'test.com' }],
conflicts: [],
errors: [],
}
const mockResponse = {
session: mockSession,
preview: mockPreviewData,
}
vi.mocked(api.uploadCaddyfile).mockResolvedValue(mockResponse)
vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: true, session: mockSession })
vi.mocked(api.getImportPreview).mockResolvedValue(mockResponse)
const { result } = renderHook(() => useImport(), { wrapper: createWrapper() })
await act(async () => {
await result.current.upload('example.com { reverse_proxy localhost:8080 }')
})
await waitFor(() => {
expect(result.current.session).toEqual(mockSession)
})
expect(api.uploadCaddyfile).toHaveBeenCalledWith('example.com { reverse_proxy localhost:8080 }')
expect(result.current.loading).toBe(false)
})
it('handles upload errors', async () => {
const mockError = new Error('Upload failed')
vi.mocked(api.uploadCaddyfile).mockRejectedValue(mockError)
const { result } = renderHook(() => useImport(), { wrapper: createWrapper() })
let threw = false
await act(async () => {
try {
await result.current.upload('invalid')
} catch {
threw = true
}
})
expect(threw).toBe(true)
await waitFor(() => {
expect(result.current.error).toBe('Upload failed')
})
})
it('commits import with resolutions', async () => {
const mockSession = {
id: 'session-2',
state: 'reviewing' as const,
created_at: '2025-01-18T10:00:00Z',
updated_at: '2025-01-18T10:00:00Z',
}
const mockResponse = {
session: mockSession,
preview: { hosts: [], conflicts: [], errors: [] },
}
let isCommitted = false
vi.mocked(api.uploadCaddyfile).mockResolvedValue(mockResponse)
vi.mocked(api.getImportStatus).mockImplementation(async () => {
if (isCommitted) return { has_pending: false }
return { has_pending: true, session: mockSession }
})
vi.mocked(api.getImportPreview).mockResolvedValue(mockResponse)
vi.mocked(api.commitImport).mockImplementation(async () => {
isCommitted = true
})
const { result } = renderHook(() => useImport(), { wrapper: createWrapper() })
await act(async () => {
await result.current.upload('test')
})
await waitFor(() => {
expect(result.current.session).toEqual(mockSession)
})
await act(async () => {
await result.current.commit({ 'test.com': 'skip' })
})
expect(api.commitImport).toHaveBeenCalledWith('session-2', { 'test.com': 'skip' })
await waitFor(() => {
expect(result.current.session).toBeNull()
})
})
it('cancels active import session', async () => {
const mockSession = {
id: 'session-3',
state: 'reviewing' as const,
created_at: '2025-01-18T10:00:00Z',
updated_at: '2025-01-18T10:00:00Z',
}
const mockResponse = {
session: mockSession,
preview: { hosts: [], conflicts: [], errors: [] },
}
let isCancelled = false
vi.mocked(api.uploadCaddyfile).mockResolvedValue(mockResponse)
vi.mocked(api.getImportStatus).mockImplementation(async () => {
if (isCancelled) return { has_pending: false }
return { has_pending: true, session: mockSession }
})
vi.mocked(api.getImportPreview).mockResolvedValue(mockResponse)
vi.mocked(api.cancelImport).mockImplementation(async () => {
isCancelled = true
})
const { result } = renderHook(() => useImport(), { wrapper: createWrapper() })
await act(async () => {
await result.current.upload('test')
})
await waitFor(() => {
expect(result.current.session).toEqual(mockSession)
})
await act(async () => {
await result.current.cancel()
})
expect(api.cancelImport).toHaveBeenCalled()
await waitFor(() => {
expect(result.current.session).toBeNull()
})
})
it('handles commit errors', async () => {
const mockSession = {
id: 'session-4',
state: 'reviewing' as const,
created_at: '2025-01-18T10:00:00Z',
updated_at: '2025-01-18T10:00:00Z',
}
const mockResponse = {
session: mockSession,
preview: { hosts: [], conflicts: [], errors: [] },
}
vi.mocked(api.uploadCaddyfile).mockResolvedValue(mockResponse)
vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: true, session: mockSession })
vi.mocked(api.getImportPreview).mockResolvedValue(mockResponse)
const mockError = new Error('Commit failed')
vi.mocked(api.commitImport).mockRejectedValue(mockError)
const { result } = renderHook(() => useImport(), { wrapper: createWrapper() })
await act(async () => {
await result.current.upload('test')
})
await waitFor(() => {
expect(result.current.session).toEqual(mockSession)
})
let threw = false
await act(async () => {
try {
await result.current.commit({})
} catch {
threw = true
}
})
expect(threw).toBe(true)
await waitFor(() => {
expect(result.current.error).toBe('Commit failed')
})
})
})

View File

@@ -0,0 +1,216 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { renderHook, waitFor, act } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import React from 'react'
import { useProxyHosts } from '../useProxyHosts'
import * as api from '../../api/proxyHosts'
// Mock the API
vi.mock('../../api/proxyHosts', () => ({
getProxyHosts: vi.fn(),
createProxyHost: vi.fn(),
updateProxyHost: vi.fn(),
deleteProxyHost: vi.fn(),
}))
const createMockHost = (overrides: Partial<api.ProxyHost> = {}): api.ProxyHost => ({
uuid: '1',
domain_names: 'test.com',
forward_scheme: 'http',
forward_host: 'localhost',
forward_port: 8080,
ssl_forced: false,
http2_support: false,
hsts_enabled: false,
hsts_subdomains: false,
block_exploits: false,
websocket_support: false,
locations: [],
enabled: true,
created_at: '2025-01-01T00:00:00Z',
updated_at: '2025-01-01T00:00:00Z',
...overrides,
})
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
})
return ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
}
describe('useProxyHosts', () => {
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
vi.clearAllMocks()
})
it('loads proxy hosts on mount', async () => {
const mockHosts = [
createMockHost({ uuid: '1', domain_names: 'test.com', enabled: true, forward_host: 'localhost', forward_port: 8080 }),
createMockHost({ uuid: '2', domain_names: 'app.com', enabled: true, forward_host: 'localhost', forward_port: 3000 }),
]
vi.mocked(api.getProxyHosts).mockResolvedValue(mockHosts)
const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() })
expect(result.current.loading).toBe(true)
expect(result.current.hosts).toEqual([])
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
expect(result.current.hosts).toEqual(mockHosts)
expect(result.current.error).toBeNull()
expect(api.getProxyHosts).toHaveBeenCalledOnce()
})
it('handles loading errors', async () => {
const mockError = new Error('Failed to fetch')
vi.mocked(api.getProxyHosts).mockRejectedValue(mockError)
const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
expect(result.current.error).toBe('Failed to fetch')
expect(result.current.hosts).toEqual([])
})
it('creates a new proxy host', async () => {
vi.mocked(api.getProxyHosts).mockResolvedValue([])
const newHost = { domain_names: 'new.com', forward_host: 'localhost', forward_port: 9000 }
const createdHost = createMockHost({ uuid: '3', ...newHost, enabled: true })
vi.mocked(api.createProxyHost).mockImplementation(async () => {
vi.mocked(api.getProxyHosts).mockResolvedValue([createdHost])
return createdHost
})
const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await act(async () => {
await result.current.createHost(newHost)
})
expect(api.createProxyHost).toHaveBeenCalledWith(newHost)
await waitFor(() => {
expect(result.current.hosts).toContainEqual(createdHost)
})
})
it('updates an existing proxy host', async () => {
const existingHost = createMockHost({ uuid: '1', domain_names: 'test.com', enabled: true, forward_host: 'localhost', forward_port: 8080 })
let hosts = [existingHost]
vi.mocked(api.getProxyHosts).mockImplementation(() => Promise.resolve(hosts))
vi.mocked(api.updateProxyHost).mockImplementation(async (_, data) => {
hosts = [{ ...existingHost, ...data }]
return hosts[0]
})
const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await act(async () => {
await result.current.updateHost('1', { domain_names: 'updated.com' })
})
expect(api.updateProxyHost).toHaveBeenCalledWith('1', { domain_names: 'updated.com' })
await waitFor(() => {
expect(result.current.hosts[0].domain_names).toBe('updated.com')
})
})
it('deletes a proxy host', async () => {
const hosts = [
createMockHost({ uuid: '1', domain_names: 'test.com', enabled: true, forward_host: 'localhost', forward_port: 8080 }),
createMockHost({ uuid: '2', domain_names: 'app.com', enabled: true, forward_host: 'localhost', forward_port: 3000 }),
]
vi.mocked(api.getProxyHosts).mockResolvedValue(hosts)
vi.mocked(api.deleteProxyHost).mockImplementation(async (uuid) => {
const remaining = hosts.filter(h => h.uuid !== uuid)
vi.mocked(api.getProxyHosts).mockResolvedValue(remaining)
})
const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await act(async () => {
await result.current.deleteHost('1')
})
expect(api.deleteProxyHost).toHaveBeenCalledWith('1')
await waitFor(() => {
expect(result.current.hosts).toHaveLength(1)
expect(result.current.hosts[0].uuid).toBe('2')
})
})
it('handles create errors', async () => {
vi.mocked(api.getProxyHosts).mockResolvedValue([])
const mockError = new Error('Failed to create')
vi.mocked(api.createProxyHost).mockRejectedValue(mockError)
const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await expect(result.current.createHost({ domain_names: 'test.com', forward_host: 'localhost', forward_port: 8080 })).rejects.toThrow('Failed to create')
})
it('handles update errors', async () => {
const host = createMockHost({ uuid: '1', domain_names: 'test.com', enabled: true, forward_host: 'localhost', forward_port: 8080 })
vi.mocked(api.getProxyHosts).mockResolvedValue([host])
const mockError = new Error('Failed to update')
vi.mocked(api.updateProxyHost).mockRejectedValue(mockError)
const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await expect(result.current.updateHost('1', { domain_names: 'updated.com' })).rejects.toThrow('Failed to update')
})
it('handles delete errors', async () => {
const host = createMockHost({ uuid: '1', domain_names: 'test.com', enabled: true, forward_host: 'localhost', forward_port: 8080 })
vi.mocked(api.getProxyHosts).mockResolvedValue([host])
const mockError = new Error('Failed to delete')
vi.mocked(api.deleteProxyHost).mockRejectedValue(mockError)
const { result } = renderHook(() => useProxyHosts(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await expect(result.current.deleteHost('1')).rejects.toThrow('Failed to delete')
})
})

View File

@@ -0,0 +1,242 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { renderHook, waitFor, act } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import React from 'react'
import { useRemoteServers } from '../useRemoteServers'
import * as api from '../../api/remoteServers'
// Mock the API
vi.mock('../../api/remoteServers', () => ({
getRemoteServers: vi.fn(),
createRemoteServer: vi.fn(),
updateRemoteServer: vi.fn(),
deleteRemoteServer: vi.fn(),
testRemoteServerConnection: vi.fn(),
}))
const createMockServer = (overrides: Partial<api.RemoteServer> = {}): api.RemoteServer => ({
uuid: '1',
name: 'Server 1',
provider: 'generic',
host: 'localhost',
port: 8080,
enabled: true,
reachable: true,
created_at: '2025-01-01T00:00:00Z',
updated_at: '2025-01-01T00:00:00Z',
...overrides,
})
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
})
return ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
}
describe('useRemoteServers', () => {
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
vi.clearAllMocks()
})
it('loads all remote servers on mount', async () => {
const mockServers = [
createMockServer({ uuid: '1', name: 'Server 1', host: 'localhost', port: 8080, enabled: true }),
createMockServer({ uuid: '2', name: 'Server 2', host: '192.168.1.100', port: 3000, enabled: false }),
]
vi.mocked(api.getRemoteServers).mockResolvedValue(mockServers)
const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() })
expect(result.current.loading).toBe(true)
expect(result.current.servers).toEqual([])
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
expect(result.current.servers).toEqual(mockServers)
expect(result.current.error).toBeNull()
expect(api.getRemoteServers).toHaveBeenCalledOnce()
})
it('handles loading errors', async () => {
const mockError = new Error('Network error')
vi.mocked(api.getRemoteServers).mockRejectedValue(mockError)
const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
expect(result.current.error).toBe('Network error')
expect(result.current.servers).toEqual([])
})
it('creates a new remote server', async () => {
vi.mocked(api.getRemoteServers).mockResolvedValue([])
const newServer = { name: 'New Server', host: 'new.local', port: 5000, provider: 'generic' }
const createdServer = createMockServer({ uuid: '4', ...newServer, enabled: true })
vi.mocked(api.createRemoteServer).mockImplementation(async () => {
vi.mocked(api.getRemoteServers).mockResolvedValue([createdServer])
return createdServer
})
const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await act(async () => {
await result.current.createServer(newServer)
})
expect(api.createRemoteServer).toHaveBeenCalledWith(newServer)
await waitFor(() => {
expect(result.current.servers).toContainEqual(createdServer)
})
})
it('updates an existing remote server', async () => {
const existingServer = createMockServer({ uuid: '1', name: 'Server 1', host: 'localhost', port: 8080, enabled: true })
let servers = [existingServer]
vi.mocked(api.getRemoteServers).mockImplementation(() => Promise.resolve(servers))
vi.mocked(api.updateRemoteServer).mockImplementation(async (_, data) => {
servers = [{ ...existingServer, ...data }]
return servers[0]
})
const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await act(async () => {
await result.current.updateServer('1', { name: 'Updated Server' })
})
expect(api.updateRemoteServer).toHaveBeenCalledWith('1', { name: 'Updated Server' })
await waitFor(() => {
expect(result.current.servers[0].name).toBe('Updated Server')
})
})
it('deletes a remote server', async () => {
const servers = [
createMockServer({ uuid: '1', name: 'Server 1', host: 'localhost', port: 8080, enabled: true }),
createMockServer({ uuid: '2', name: 'Server 2', host: '192.168.1.100', port: 3000, enabled: false }),
]
vi.mocked(api.getRemoteServers).mockResolvedValue(servers)
vi.mocked(api.deleteRemoteServer).mockImplementation(async (uuid) => {
const remaining = servers.filter(s => s.uuid !== uuid)
vi.mocked(api.getRemoteServers).mockResolvedValue(remaining)
})
const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await act(async () => {
await result.current.deleteServer('1')
})
expect(api.deleteRemoteServer).toHaveBeenCalledWith('1')
await waitFor(() => {
expect(result.current.servers).toHaveLength(1)
expect(result.current.servers[0].uuid).toBe('2')
})
})
it('tests server connection', async () => {
vi.mocked(api.getRemoteServers).mockResolvedValue([])
const testResult = { reachable: true, address: 'localhost:8080' }
vi.mocked(api.testRemoteServerConnection).mockResolvedValue(testResult)
const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
const response = await result.current.testConnection('1')
expect(api.testRemoteServerConnection).toHaveBeenCalledWith('1')
expect(response).toEqual(testResult)
})
it('handles create errors', async () => {
vi.mocked(api.getRemoteServers).mockResolvedValue([])
const mockError = new Error('Failed to create')
vi.mocked(api.createRemoteServer).mockRejectedValue(mockError)
const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await expect(result.current.createServer({ name: 'Test', host: 'localhost', port: 8080 })).rejects.toThrow('Failed to create')
})
it('handles update errors', async () => {
const server = createMockServer({ uuid: '1', name: 'Server 1', host: 'localhost', port: 8080, enabled: true })
vi.mocked(api.getRemoteServers).mockResolvedValue([server])
const mockError = new Error('Failed to update')
vi.mocked(api.updateRemoteServer).mockRejectedValue(mockError)
const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await expect(result.current.updateServer('1', { name: 'Updated Server' })).rejects.toThrow('Failed to update')
})
it('handles delete errors', async () => {
const server = createMockServer({ uuid: '1', name: 'Server 1', host: 'localhost', port: 8080, enabled: true })
vi.mocked(api.getRemoteServers).mockResolvedValue([server])
const mockError = new Error('Failed to delete')
vi.mocked(api.deleteRemoteServer).mockRejectedValue(mockError)
const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await expect(result.current.deleteServer('1')).rejects.toThrow('Failed to delete')
})
it('handles connection test errors', async () => {
vi.mocked(api.getRemoteServers).mockResolvedValue([])
const mockError = new Error('Connection failed')
vi.mocked(api.testRemoteServerConnection).mockRejectedValue(mockError)
const { result } = renderHook(() => useRemoteServers(), { wrapper: createWrapper() })
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
await expect(result.current.testConnection('1')).rejects.toThrow('Connection failed')
})
})

View File

@@ -0,0 +1,17 @@
import { describe, it, expect } from 'vitest'
import { renderHook } from '@testing-library/react'
import { useTheme } from '../useTheme'
describe('useTheme', () => {
it('throws error when used outside ThemeProvider', () => {
// Suppress console.error for this test as React logs the error
const consoleSpy = vi.spyOn(console, 'error')
consoleSpy.mockImplementation(() => {})
expect(() => {
renderHook(() => useTheme())
}).toThrow('useTheme must be used within a ThemeProvider')
consoleSpy.mockRestore()
})
})

View File

@@ -0,0 +1,10 @@
import { useContext } from 'react';
import { AuthContext } from '../context/AuthContextValue';
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};

View File

@@ -0,0 +1,15 @@
import { useQuery } from '@tanstack/react-query'
import { getCertificates } from '../api/certificates'
export function useCertificates() {
const { data, isLoading, error } = useQuery({
queryKey: ['certificates'],
queryFn: getCertificates,
})
return {
certificates: data || [],
isLoading,
error,
}
}

View File

@@ -0,0 +1,23 @@
import { useQuery } from '@tanstack/react-query'
import { dockerApi } from '../api/docker'
export function useDocker(host?: string | null) {
const {
data: containers = [],
isLoading,
error,
refetch,
} = useQuery({
queryKey: ['docker-containers', host],
queryFn: () => dockerApi.listContainers(host || undefined),
enabled: host !== null, // Disable if host is explicitly null
retry: 1, // Don't retry too much if docker is not available
})
return {
containers,
isLoading,
error,
refetch,
}
}

View File

@@ -0,0 +1,34 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import * as api from '../api/domains'
export function useDomains() {
const queryClient = useQueryClient()
const { data: domains = [], isLoading, isFetching, error } = useQuery({
queryKey: ['domains'],
queryFn: api.getDomains,
})
const createMutation = useMutation({
mutationFn: api.createDomain,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['domains'] })
},
})
const deleteMutation = useMutation({
mutationFn: api.deleteDomain,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['domains'] })
},
})
return {
domains,
isLoading,
isFetching,
error,
createDomain: createMutation.mutateAsync,
deleteDomain: deleteMutation.mutateAsync,
}
}

View File

@@ -0,0 +1,80 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
uploadCaddyfile,
getImportPreview,
commitImport,
cancelImport,
getImportStatus,
ImportSession,
ImportPreview
} from '../api/import';
export const QUERY_KEY = ['import-session'];
export function useImport() {
const queryClient = useQueryClient();
// Poll for status if we think there's an active session
const statusQuery = useQuery({
queryKey: QUERY_KEY,
queryFn: getImportStatus,
refetchInterval: (query) => {
const data = query.state.data;
// Poll if we have a pending session in reviewing state
if (data?.has_pending && data?.session?.state === 'reviewing') {
return 3000;
}
return false;
},
});
const previewQuery = useQuery({
queryKey: ['import-preview'],
queryFn: getImportPreview,
enabled: !!statusQuery.data?.has_pending && (statusQuery.data?.session?.state === 'reviewing' || statusQuery.data?.session?.state === 'pending'),
});
const uploadMutation = useMutation({
mutationFn: (content: string) => uploadCaddyfile(content),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
queryClient.invalidateQueries({ queryKey: ['import-preview'] });
},
});
const commitMutation = useMutation({
mutationFn: (resolutions: Record<string, string>) => {
const sessionId = statusQuery.data?.session?.id;
if (!sessionId) throw new Error("No active session");
return commitImport(sessionId, resolutions);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
queryClient.invalidateQueries({ queryKey: ['import-preview'] });
// Also invalidate proxy hosts as they might have changed
queryClient.invalidateQueries({ queryKey: ['proxy-hosts'] });
},
});
const cancelMutation = useMutation({
mutationFn: () => cancelImport(),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
queryClient.invalidateQueries({ queryKey: ['import-preview'] });
},
});
return {
session: statusQuery.data?.session || null,
preview: previewQuery.data || null,
loading: statusQuery.isLoading || uploadMutation.isPending || commitMutation.isPending || cancelMutation.isPending,
error: (statusQuery.error || previewQuery.error || uploadMutation.error || commitMutation.error || cancelMutation.error)
? ((statusQuery.error || previewQuery.error || uploadMutation.error || commitMutation.error || cancelMutation.error) as Error).message
: null,
upload: uploadMutation.mutateAsync,
commit: commitMutation.mutateAsync,
cancel: cancelMutation.mutateAsync,
};
}
export type { ImportSession, ImportPreview };

View File

@@ -0,0 +1,56 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
getProxyHosts,
createProxyHost,
updateProxyHost,
deleteProxyHost,
ProxyHost
} from '../api/proxyHosts';
export const QUERY_KEY = ['proxy-hosts'];
export function useProxyHosts() {
const queryClient = useQueryClient();
const query = useQuery({
queryKey: QUERY_KEY,
queryFn: getProxyHosts,
});
const createMutation = useMutation({
mutationFn: (host: Partial<ProxyHost>) => createProxyHost(host),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
},
});
const updateMutation = useMutation({
mutationFn: ({ uuid, data }: { uuid: string; data: Partial<ProxyHost> }) =>
updateProxyHost(uuid, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
},
});
const deleteMutation = useMutation({
mutationFn: (uuid: string) => deleteProxyHost(uuid),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
},
});
return {
hosts: query.data || [],
loading: query.isLoading,
isFetching: query.isFetching,
error: query.error ? (query.error as Error).message : null,
createHost: createMutation.mutateAsync,
updateHost: (uuid: string, data: Partial<ProxyHost>) => updateMutation.mutateAsync({ uuid, data }),
deleteHost: deleteMutation.mutateAsync,
isCreating: createMutation.isPending,
isUpdating: updateMutation.isPending,
isDeleting: deleteMutation.isPending,
};
}
export type { ProxyHost };

View File

@@ -0,0 +1,63 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
getRemoteServers,
createRemoteServer,
updateRemoteServer,
deleteRemoteServer,
testRemoteServerConnection,
RemoteServer
} from '../api/remoteServers';
export const QUERY_KEY = ['remote-servers'];
export function useRemoteServers(enabledOnly = false) {
const queryClient = useQueryClient();
const query = useQuery({
queryKey: [...QUERY_KEY, { enabled: enabledOnly }],
queryFn: () => getRemoteServers(enabledOnly),
});
const createMutation = useMutation({
mutationFn: (server: Partial<RemoteServer>) => createRemoteServer(server),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
},
});
const updateMutation = useMutation({
mutationFn: ({ uuid, data }: { uuid: string; data: Partial<RemoteServer> }) =>
updateRemoteServer(uuid, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
},
});
const deleteMutation = useMutation({
mutationFn: (uuid: string) => deleteRemoteServer(uuid),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
},
});
const testConnectionMutation = useMutation({
mutationFn: (uuid: string) => testRemoteServerConnection(uuid),
});
return {
servers: query.data || [],
loading: query.isLoading,
isFetching: query.isFetching,
error: query.error ? (query.error as Error).message : null,
createServer: createMutation.mutateAsync,
updateServer: (uuid: string, data: Partial<RemoteServer>) => updateMutation.mutateAsync({ uuid, data }),
deleteServer: deleteMutation.mutateAsync,
testConnection: testConnectionMutation.mutateAsync,
isCreating: createMutation.isPending,
isUpdating: updateMutation.isPending,
isDeleting: deleteMutation.isPending,
isTestingConnection: testConnectionMutation.isPending,
};
}
export type { RemoteServer };

View File

@@ -0,0 +1,10 @@
import { useContext } from 'react'
import { ThemeContext } from '../context/ThemeContextValue'
export function useTheme() {
const context = useContext(ThemeContext)
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider')
}
return context
}