feat: The Other Dude v9.0.1 — full-featured email system

ci: add GitHub Pages deployment workflow for docs site

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-08 17:46:37 -05:00
commit b840047e19
511 changed files with 106948 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
import { useState } from 'react'
import { useMutation } from '@tanstack/react-query'
import { Search, AlertCircle } from 'lucide-react'
import { devicesApi, type SubnetScanResponse } from '@/lib/api'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
interface Props {
tenantId: string
onResults: (results: SubnetScanResponse) => void
}
export function SubnetScanForm({ tenantId, onResults }: Props) {
const [cidr, setCidr] = useState('')
const [error, setError] = useState<string | null>(null)
const mutation = useMutation({
mutationFn: () => devicesApi.scan(tenantId, cidr),
onSuccess: (data) => {
onResults(data)
},
onError: (err: unknown) => {
const detail =
(err as { response?: { data?: { detail?: string } } })?.response?.data?.detail
?? 'Scan failed. Check the CIDR format.'
setError(detail)
},
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!cidr.trim()) {
setError('CIDR is required (e.g. 192.168.1.0/24)')
return
}
setError(null)
mutation.mutate()
}
return (
<div className="space-y-4">
<div>
<h2 className="text-base font-semibold">Scan Subnet</h2>
<p className="text-xs text-text-muted mt-0.5">
Discover MikroTik devices on a network range (max /20 4096 IPs)
</p>
</div>
<form onSubmit={handleSubmit} className="flex items-end gap-2">
<div className="flex-1 max-w-xs space-y-1.5">
<Label htmlFor="scan-cidr">Network CIDR</Label>
<Input
id="scan-cidr"
value={cidr}
onChange={(e) => {
setCidr(e.target.value)
if (error) setError(null)
}}
placeholder="e.g., 192.168.1.0/24"
autoFocus
/>
<p className="text-[10px] text-text-muted mt-0.5">CIDR notation /24 scans 254 addresses</p>
</div>
<Button type="submit" size="sm" disabled={mutation.isPending}>
{mutation.isPending ? (
<>
<Search className="h-3.5 w-3.5 animate-pulse" />
Scanning...
</>
) : (
<>
<Search className="h-3.5 w-3.5" />
Scan
</>
)}
</Button>
</form>
{error && (
<div className="flex items-center gap-2 rounded-md bg-error/10 border border-error/50 px-3 py-2">
<AlertCircle className="h-4 w-4 text-error flex-shrink-0" />
<p className="text-xs text-error">{error}</p>
</div>
)}
{mutation.isPending && (
<div className="text-xs text-text-muted animate-pulse">
Scanning {cidr}... This may take up to 30 seconds for larger ranges.
</div>
)}
</div>
)
}