feat: update routing for ImportCaddy and enhance navigation type safety; add test for Uptime pause button
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Suspense, lazy } from 'react'
|
||||
import { Navigate } from 'react-router-dom'
|
||||
import { BrowserRouter as Router, Routes, Route, Outlet } from 'react-router-dom'
|
||||
import Layout from './components/Layout'
|
||||
import { ToastContainer } from './components/Toast'
|
||||
@@ -54,7 +55,7 @@ export default function App() {
|
||||
<Route path="access-lists" element={<AccessLists />} />
|
||||
<Route path="uptime" element={<Uptime />} />
|
||||
<Route path="notifications" element={<Notifications />} />
|
||||
<Route path="import" element={<ImportCaddy />} />
|
||||
<Route path="import" element={<Navigate to="/tasks/import/caddyfile" replace />} />
|
||||
|
||||
{/* Settings Routes */}
|
||||
<Route path="settings" element={<Settings />}>
|
||||
|
||||
@@ -13,6 +13,13 @@ interface LayoutProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
type NavItem = {
|
||||
name: string
|
||||
path?: string
|
||||
icon?: string
|
||||
children?: NavItem[]
|
||||
}
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
const location = useLocation()
|
||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false)
|
||||
@@ -41,7 +48,7 @@ export default function Layout({ children }: LayoutProps) {
|
||||
staleTime: 1000 * 60 * 60, // 1 hour
|
||||
})
|
||||
|
||||
const navigation = [
|
||||
const navigation: NavItem[] = [
|
||||
{ name: 'Dashboard', path: '/', icon: '📊' },
|
||||
{ name: 'Proxy Hosts', path: '/proxy-hosts', icon: '🌐' },
|
||||
{ name: 'Remote Servers', path: '/remote-servers', icon: '🖥️' },
|
||||
@@ -162,9 +169,9 @@ export default function Layout({ children }: LayoutProps) {
|
||||
|
||||
{isExpanded && (
|
||||
<div className="pl-11 space-y-1">
|
||||
{item.children.map((child) => {
|
||||
{item.children.map((child: NavItem) => {
|
||||
// If this child has its own children, render a nested accordion
|
||||
if ((child as any).children) {
|
||||
if (child.children && child.children.length > 0) {
|
||||
|
||||
const nestedExpandedKey = `${item.name}:${child.name}`
|
||||
const isNestedOpen = expandedMenus.includes(nestedExpandedKey)
|
||||
@@ -190,10 +197,10 @@ export default function Layout({ children }: LayoutProps) {
|
||||
</button>
|
||||
{isNestedOpen && (
|
||||
<div className="pl-6 space-y-1">
|
||||
{(child as any).children.map((sub: any) => (
|
||||
{child.children.map((sub: NavItem) => (
|
||||
<Link
|
||||
key={sub.path}
|
||||
to={sub.path}
|
||||
to={sub.path!}
|
||||
onClick={() => setMobileSidebarOpen(false)}
|
||||
className={`block py-2 px-3 rounded-md text-sm transition-colors ${
|
||||
location.pathname === sub.path
|
||||
|
||||
@@ -100,6 +100,37 @@ describe('Uptime page', () => {
|
||||
expect(barTitles.some(el => (el.getAttribute('title') || '').includes('Status: DOWN'))).toBeTruthy()
|
||||
})
|
||||
|
||||
it('pause button is yellow and appears before delete in settings menu', async () => {
|
||||
const monitor = {
|
||||
id: 'm12', name: 'OrderTest', url: 'http://example.com', type: 'http', interval: 60, enabled: true,
|
||||
status: 'up', last_check: new Date().toISOString(), latency: 10, max_retries: 3,
|
||||
}
|
||||
vi.mocked(uptimeApi.getMonitors).mockResolvedValue([monitor])
|
||||
vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue([])
|
||||
|
||||
renderWithProviders(<Uptime />)
|
||||
await waitFor(() => expect(screen.getByText('OrderTest')).toBeInTheDocument())
|
||||
const card = screen.getByText('OrderTest').closest('div') as HTMLElement
|
||||
await userEvent.click(within(card).getByTitle('Monitor settings'))
|
||||
|
||||
const configureBtn = within(card).getByText('Configure')
|
||||
// Find the menu container by traversing up until the absolute positioned menu is found
|
||||
let menuContainer: HTMLElement | null = configureBtn.parentElement
|
||||
while (menuContainer && !menuContainer.className.includes('absolute')) {
|
||||
menuContainer = menuContainer.parentElement
|
||||
}
|
||||
expect(menuContainer).toBeTruthy()
|
||||
const buttons = Array.from(menuContainer!.querySelectorAll('button'))
|
||||
const pauseBtn = buttons.find(b => b.textContent?.trim() === 'Pause')
|
||||
const deleteBtn = buttons.find(b => b.textContent?.trim() === 'Delete')
|
||||
expect(pauseBtn).toBeTruthy()
|
||||
expect(deleteBtn).toBeTruthy()
|
||||
// Ensure Pause appears before Delete
|
||||
expect(buttons.indexOf(pauseBtn!)).toBeLessThan(buttons.indexOf(deleteBtn!))
|
||||
// Ensure Pause has yellow styling class
|
||||
expect(pauseBtn!.className).toContain('text-yellow-600')
|
||||
})
|
||||
|
||||
it('deletes monitor when delete confirmed and shows toast', async () => {
|
||||
const monitor = {
|
||||
id: 'm5', name: 'DeleteMe', url: 'http://example.com', type: 'http', interval: 60, enabled: true,
|
||||
|
||||
Reference in New Issue
Block a user