Files
cloudflare-domain-manager/apps/web/src/components/subdomain-edit-sheet.tsx
T
Denozordec a458465153
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 8s
quality / changes (push) Successful in 10s
quality / docker-check (push) Skipped
quality / web (push) Successful in 1m8s
quality / api (push) Successful in 1m9s
CD / quality (push) Successful in 2m31s
CD / publish (push) Successful in 1m35s
feat(certificates): enhance service certificate management and monitoring
- Added new endpoints for listing and checking service certificates, improving visibility into SSL status.
- Integrated certificate monitoring options into service binding updates, allowing for flexible SSL management.
- Updated the service detail grid to include SSL monitoring controls, enhancing user interaction with certificate settings.
- Refactored related components and schemas to support the new certificate features, ensuring consistency across the application.
- Improved test coverage for certificate functionalities, validating the new features and ensuring reliability.
2026-08-20 00:32:43 +07:00

208 lines
6.3 KiB
TypeScript

import { Link } from '@tanstack/react-router'
import { useEffect, useMemo } from 'react'
import { useForm, Controller } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import type { ServiceView, SubdomainRecord } from '@/lib/schemas'
import type { SubdomainServiceLink } from '@/hooks/use-domain-page'
import { formatServiceGroupLabel } from '@/lib/service-utils'
import { FormSheet } from '@/components/form-sheet'
import { FormFieldSimple } from '@/components/form-field'
import { LoadingButton } from '@/components/loading-button'
import { Badge } from '@/components/reui/badge'
import {
Alert,
AlertDescription,
AlertTitle,
} from '@/components/reui/alert'
import { FieldGroup } from '@cfdm/ui/components/field'
import { Input } from '@cfdm/ui/components/input'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@cfdm/ui/components/select'
const subdomainEditSchema = z.object({
name: z.string().min(1, 'Укажите имя'),
serviceId: z.string(),
})
export type SubdomainEditValues = z.infer<typeof subdomainEditSchema>
interface SubdomainEditSheetProps {
mode: 'create' | 'edit'
subdomain: SubdomainRecord | null
zoneName: string
services: ServiceView[]
serviceGroupById: Map<number, string | null>
currentServiceId: string
serviceLinks?: SubdomainServiceLink[]
open: boolean
isSaving: boolean
onOpenChange: (open: boolean) => void
onSubmit: (values: SubdomainEditValues) => void
}
export function SubdomainEditSheet({
mode,
subdomain,
zoneName,
services,
serviceGroupById,
currentServiceId,
serviceLinks = [],
open,
isSaving,
onOpenChange,
onSubmit,
}: SubdomainEditSheetProps) {
const form = useForm<SubdomainEditValues>({
resolver: zodResolver(subdomainEditSchema),
defaultValues: {
name: '',
serviceId: 'none',
},
})
const serviceItems = useMemo(
() => [
{ label: 'Без сервиса', value: 'none' },
...services.map((service) => ({
label: formatServiceGroupLabel(
serviceGroupById.get(service.id),
service.name,
),
value: String(service.id),
})),
],
[services, serviceGroupById],
)
useEffect(() => {
if (!open) return
if (mode === 'edit' && subdomain) {
form.reset({
name: subdomain.name,
serviceId: currentServiceId || 'none',
})
return
}
form.reset({
name: '',
serviceId: 'none',
})
}, [open, mode, subdomain, currentServiceId, form])
const hasMultipleServices = mode === 'edit' && serviceLinks.length > 1
function handleSubmit(values: SubdomainEditValues) {
onSubmit({
name: values.name.trim(),
serviceId: values.serviceId,
})
}
return (
<FormSheet<SubdomainEditValues>
open={open}
onOpenChange={onOpenChange}
title={
mode === 'create' ? 'Создать поддомен' : 'Редактировать поддомен'
}
description={
mode === 'create'
? `Имя записи в зоне ${zoneName} (например, www или api)`
: `Изменение поддомена в зоне ${zoneName}`
}
form={form}
onSubmit={handleSubmit}
footer={
<LoadingButton
type="submit"
className="w-full"
disabled={!form.watch('name')?.trim()}
isLoading={isSaving}
loadingLabel="Сохранение…"
>
{mode === 'create' ? 'Создать' : 'Сохранить'}
</LoadingButton>
}
>
<FieldGroup>
<FormFieldSimple
label="Имя"
htmlFor="subdomain_name"
error={form.formState.errors.name}
>
<Input
id="subdomain_name"
placeholder="www"
className="font-mono"
aria-invalid={!!form.formState.errors.name}
{...form.register('name')}
/>
</FormFieldSimple>
{mode === 'edit' ? (
<>
{hasMultipleServices ? (
<Alert variant="warning">
<AlertTitle>Несколько сервисов на hostname</AlertTitle>
<AlertDescription className="flex flex-col gap-2">
<p>
Здесь редактируется основной сервис. Остальные привязки
управляются в карточке сервиса.
</p>
<div className="flex flex-wrap items-center gap-1.5">
{serviceLinks.map((link) => (
<Link
key={link.serviceId}
to="/services"
search={{ serviceId: link.serviceId }}
className="inline-flex"
>
<Badge variant="outline" size="xs">
{formatServiceGroupLabel(
link.groupName,
link.serviceName,
)}
</Badge>
</Link>
))}
</div>
</AlertDescription>
</Alert>
) : null}
<FormFieldSimple label="Сервис" htmlFor="subdomain_service">
<Controller
control={form.control}
name="serviceId"
render={({ field }) => (
<Select
items={serviceItems}
value={field.value}
onValueChange={(value) => field.onChange(value ?? 'none')}
>
<SelectTrigger id="subdomain_service" className="w-full">
<SelectValue placeholder="Без сервиса" />
</SelectTrigger>
<SelectContent>
{serviceItems.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
</FormFieldSimple>
</>
) : null}
</FieldGroup>
</FormSheet>
)
}