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
- 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.
208 lines
6.3 KiB
TypeScript
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>
|
|
)
|
|
}
|