refactor: Replace legacy UI components with new App components across various files for improved consistency and maintainability
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m55s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 2m14s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 7s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m55s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 2m14s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 7s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from '@cfdm/ui/components/accordion'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppAccordionProps = ComponentProps<typeof Accordion>
|
||||||
|
export type AppAccordionItemProps = ComponentProps<typeof AccordionItem>
|
||||||
|
export type AppAccordionTriggerProps = ComponentProps<typeof AccordionTrigger>
|
||||||
|
export type AppAccordionContentProps = ComponentProps<typeof AccordionContent>
|
||||||
|
|
||||||
|
export function AppAccordion({ className, ...props }: AppAccordionProps) {
|
||||||
|
return <Accordion className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAccordionItem({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppAccordionItemProps) {
|
||||||
|
return <AccordionItem className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAccordionTrigger({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppAccordionTriggerProps) {
|
||||||
|
return <AccordionTrigger className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAccordionContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppAccordionContentProps) {
|
||||||
|
return <AccordionContent className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import { Alert, AlertAction, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppAlertProps = ComponentProps<typeof Alert>
|
||||||
|
export type AppAlertTitleProps = ComponentProps<typeof AlertTitle>
|
||||||
|
export type AppAlertDescriptionProps = ComponentProps<typeof AlertDescription>
|
||||||
|
export type AppAlertActionProps = ComponentProps<typeof AlertAction>
|
||||||
|
|
||||||
|
export function AppAlert({ className, ...props }: AppAlertProps) {
|
||||||
|
return <Alert className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAlertTitle({ className, ...props }: AppAlertTitleProps) {
|
||||||
|
return <AlertTitle className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAlertDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppAlertDescriptionProps) {
|
||||||
|
return <AlertDescription className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAlertAction({ className, ...props }: AppAlertActionProps) {
|
||||||
|
return <AlertAction className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
AvatarBadge,
|
||||||
|
AvatarFallback,
|
||||||
|
AvatarGroup,
|
||||||
|
AvatarGroupCount,
|
||||||
|
AvatarImage,
|
||||||
|
} from '@cfdm/ui/components/avatar'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppAvatarProps = ComponentProps<typeof Avatar>
|
||||||
|
export type AppAvatarImageProps = ComponentProps<typeof AvatarImage>
|
||||||
|
export type AppAvatarFallbackProps = ComponentProps<typeof AvatarFallback>
|
||||||
|
export type AppAvatarBadgeProps = ComponentProps<typeof AvatarBadge>
|
||||||
|
export type AppAvatarGroupProps = ComponentProps<typeof AvatarGroup>
|
||||||
|
export type AppAvatarGroupCountProps = ComponentProps<typeof AvatarGroupCount>
|
||||||
|
|
||||||
|
export function AppAvatar({ className, ...props }: AppAvatarProps) {
|
||||||
|
return <Avatar className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAvatarImage({ className, ...props }: AppAvatarImageProps) {
|
||||||
|
return <AvatarImage className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAvatarFallback({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppAvatarFallbackProps) {
|
||||||
|
return <AvatarFallback className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAvatarBadge({ className, ...props }: AppAvatarBadgeProps) {
|
||||||
|
return <AvatarBadge className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAvatarGroup({ className, ...props }: AppAvatarGroupProps) {
|
||||||
|
return <AvatarGroup className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppAvatarGroupCount({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppAvatarGroupCountProps) {
|
||||||
|
return <AvatarGroupCount className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import { Badge } from '@cfdm/ui/components/badge'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppBadgeProps = ComponentProps<typeof Badge>
|
||||||
|
|
||||||
|
export function AppBadge({ className, ...props }: AppBadgeProps) {
|
||||||
|
return <Badge className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import { Button } from '@cfdm/ui/components/button'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppButtonProps = ComponentProps<typeof Button>
|
||||||
|
|
||||||
|
export function AppButton({ className, ...props }: AppButtonProps) {
|
||||||
|
return <Button className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardAction,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@cfdm/ui/components/card'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppCardProps = ComponentProps<typeof Card>
|
||||||
|
export type AppCardHeaderProps = ComponentProps<typeof CardHeader>
|
||||||
|
export type AppCardTitleProps = ComponentProps<typeof CardTitle>
|
||||||
|
export type AppCardDescriptionProps = ComponentProps<typeof CardDescription>
|
||||||
|
export type AppCardActionProps = ComponentProps<typeof CardAction>
|
||||||
|
export type AppCardContentProps = ComponentProps<typeof CardContent>
|
||||||
|
export type AppCardFooterProps = ComponentProps<typeof CardFooter>
|
||||||
|
|
||||||
|
export function AppCard({ className, ...props }: AppCardProps) {
|
||||||
|
return <Card className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppCardHeader({ className, ...props }: AppCardHeaderProps) {
|
||||||
|
return <CardHeader className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppCardTitle({ className, ...props }: AppCardTitleProps) {
|
||||||
|
return <CardTitle className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppCardDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppCardDescriptionProps) {
|
||||||
|
return <CardDescription className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppCardAction({ className, ...props }: AppCardActionProps) {
|
||||||
|
return <CardAction className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppCardContent({ className, ...props }: AppCardContentProps) {
|
||||||
|
return <CardContent className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppCardFooter({ className, ...props }: AppCardFooterProps) {
|
||||||
|
return <CardFooter className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import { Checkbox } from '@cfdm/ui/components/checkbox'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppCheckboxProps = ComponentProps<typeof Checkbox>
|
||||||
|
|
||||||
|
export function AppCheckbox({ className, ...props }: AppCheckboxProps) {
|
||||||
|
return <Checkbox className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import {
|
||||||
|
Field,
|
||||||
|
FieldContent,
|
||||||
|
FieldDescription,
|
||||||
|
FieldError,
|
||||||
|
FieldGroup,
|
||||||
|
FieldLabel,
|
||||||
|
FieldLegend,
|
||||||
|
FieldSeparator,
|
||||||
|
FieldSet,
|
||||||
|
FieldTitle,
|
||||||
|
} from '@cfdm/ui/components/field'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppFieldProps = ComponentProps<typeof Field>
|
||||||
|
export type AppFieldGroupProps = ComponentProps<typeof FieldGroup>
|
||||||
|
export type AppFieldLabelProps = ComponentProps<typeof FieldLabel>
|
||||||
|
export type AppFieldDescriptionProps = ComponentProps<typeof FieldDescription>
|
||||||
|
export type AppFieldErrorProps = ComponentProps<typeof FieldError>
|
||||||
|
export type AppFieldContentProps = ComponentProps<typeof FieldContent>
|
||||||
|
export type AppFieldTitleProps = ComponentProps<typeof FieldTitle>
|
||||||
|
export type AppFieldLegendProps = ComponentProps<typeof FieldLegend>
|
||||||
|
export type AppFieldSeparatorProps = ComponentProps<typeof FieldSeparator>
|
||||||
|
export type AppFieldSetProps = ComponentProps<typeof FieldSet>
|
||||||
|
|
||||||
|
export function AppField({ className, ...props }: AppFieldProps) {
|
||||||
|
return <Field className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppFieldGroup({ className, ...props }: AppFieldGroupProps) {
|
||||||
|
return <FieldGroup className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppFieldLabel({ className, ...props }: AppFieldLabelProps) {
|
||||||
|
return <FieldLabel className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppFieldDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppFieldDescriptionProps) {
|
||||||
|
return <FieldDescription className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppFieldError({ className, ...props }: AppFieldErrorProps) {
|
||||||
|
return <FieldError className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppFieldContent({ className, ...props }: AppFieldContentProps) {
|
||||||
|
return <FieldContent className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppFieldTitle({ className, ...props }: AppFieldTitleProps) {
|
||||||
|
return <FieldTitle className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppFieldLegend({ className, ...props }: AppFieldLegendProps) {
|
||||||
|
return <FieldLegend className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppFieldSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppFieldSeparatorProps) {
|
||||||
|
return <FieldSeparator className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppFieldSet({ className, ...props }: AppFieldSetProps) {
|
||||||
|
return <FieldSet className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import { Input } from '@cfdm/ui/components/input'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppInputProps = ComponentProps<typeof Input>
|
||||||
|
|
||||||
|
export function AppInput({ className, ...props }: AppInputProps) {
|
||||||
|
return <Input className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import {
|
||||||
|
Item,
|
||||||
|
ItemActions,
|
||||||
|
ItemContent,
|
||||||
|
ItemDescription,
|
||||||
|
ItemFooter,
|
||||||
|
ItemGroup,
|
||||||
|
ItemHeader,
|
||||||
|
ItemMedia,
|
||||||
|
ItemSeparator,
|
||||||
|
ItemTitle,
|
||||||
|
} from '@cfdm/ui/components/item'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppItemProps = ComponentProps<typeof Item>
|
||||||
|
export type AppItemGroupProps = ComponentProps<typeof ItemGroup>
|
||||||
|
export type AppItemSeparatorProps = ComponentProps<typeof ItemSeparator>
|
||||||
|
export type AppItemMediaProps = ComponentProps<typeof ItemMedia>
|
||||||
|
export type AppItemContentProps = ComponentProps<typeof ItemContent>
|
||||||
|
export type AppItemTitleProps = ComponentProps<typeof ItemTitle>
|
||||||
|
export type AppItemDescriptionProps = ComponentProps<typeof ItemDescription>
|
||||||
|
export type AppItemActionsProps = ComponentProps<typeof ItemActions>
|
||||||
|
export type AppItemHeaderProps = ComponentProps<typeof ItemHeader>
|
||||||
|
export type AppItemFooterProps = ComponentProps<typeof ItemFooter>
|
||||||
|
|
||||||
|
export function AppItem({ className, ...props }: AppItemProps) {
|
||||||
|
return <Item className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppItemGroup({ className, ...props }: AppItemGroupProps) {
|
||||||
|
return <ItemGroup className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppItemSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppItemSeparatorProps) {
|
||||||
|
return <ItemSeparator className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppItemMedia({ className, ...props }: AppItemMediaProps) {
|
||||||
|
return <ItemMedia className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppItemContent({ className, ...props }: AppItemContentProps) {
|
||||||
|
return <ItemContent className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppItemTitle({ className, ...props }: AppItemTitleProps) {
|
||||||
|
return <ItemTitle className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppItemDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: AppItemDescriptionProps) {
|
||||||
|
return <ItemDescription className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppItemActions({ className, ...props }: AppItemActionsProps) {
|
||||||
|
return <ItemActions className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppItemHeader({ className, ...props }: AppItemHeaderProps) {
|
||||||
|
return <ItemHeader className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppItemFooter({ className, ...props }: AppItemFooterProps) {
|
||||||
|
return <ItemFooter className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import { Separator } from '@cfdm/ui/components/separator'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppSeparatorProps = ComponentProps<typeof Separator>
|
||||||
|
|
||||||
|
export function AppSeparator({ className, ...props }: AppSeparatorProps) {
|
||||||
|
return <Separator className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import { Spinner } from '@cfdm/ui/components/spinner'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppSpinnerProps = ComponentProps<typeof Spinner>
|
||||||
|
|
||||||
|
export function AppSpinner({ className, ...props }: AppSpinnerProps) {
|
||||||
|
return <Spinner className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import { Switch } from '@cfdm/ui/components/switch'
|
||||||
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
|
export type AppSwitchProps = ComponentProps<typeof Switch>
|
||||||
|
|
||||||
|
export function AppSwitch({ className, ...props }: AppSwitchProps) {
|
||||||
|
return <Switch className={cn(className)} {...props} />
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@ import { ConfirmDialog } from '@/components/confirm-dialog'
|
|||||||
import { TableCard } from '@/components/table-card'
|
import { TableCard } from '@/components/table-card'
|
||||||
import { groupDnsRecords, type DnsRecordGroup } from '@/lib/dns-grouping'
|
import { groupDnsRecords, type DnsRecordGroup } from '@/lib/dns-grouping'
|
||||||
import type { DnsRecord } from '@/lib/schemas'
|
import type { DnsRecord } from '@/lib/schemas'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -40,9 +40,9 @@ function DnsRecordCells({
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
trigger={
|
trigger={
|
||||||
<Button variant="destructive" size="sm" disabled={isDeleting}>
|
<AppButton variant="destructive" size="sm" disabled={isDeleting}>
|
||||||
Удалить
|
Удалить
|
||||||
</Button>
|
</AppButton>
|
||||||
}
|
}
|
||||||
title="Удалить DNS-запись?"
|
title="Удалить DNS-запись?"
|
||||||
description={`Запись ${record.name} (${record.record_type}) будет удалена из зоны.`}
|
description={`Запись ${record.name} (${record.record_type}) будет удалена из зоны.`}
|
||||||
@@ -95,14 +95,14 @@ function MultiValueGroupRows({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<TableCell rowSpan={group.records.length} className="align-top">
|
<TableCell rowSpan={group.records.length} className="align-top">
|
||||||
<Badge variant="outline">{group.recordType}</Badge>
|
<AppBadge variant="outline">{group.recordType}</AppBadge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell rowSpan={group.records.length} className="align-top font-medium">
|
<TableCell rowSpan={group.records.length} className="align-top font-medium">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span>{group.name}</span>
|
<span>{group.name}</span>
|
||||||
<Badge variant="secondary" className="w-fit">
|
<AppBadge variant="secondary" className="w-fit">
|
||||||
{group.records.length} адресов
|
{group.records.length} адресов
|
||||||
</Badge>
|
</AppBadge>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<DnsRecordCells record={first} onDelete={onDelete} isDeleting={isDeleting} />
|
<DnsRecordCells record={first} onDelete={onDelete} isDeleting={isDeleting} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PlusIcon } from 'lucide-react'
|
import { PlusIcon } from 'lucide-react'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
|
|
||||||
interface DomainActionsBarProps {
|
interface DomainActionsBarProps {
|
||||||
onCreateSubdomain: () => void
|
onCreateSubdomain: () => void
|
||||||
@@ -8,10 +8,10 @@ interface DomainActionsBarProps {
|
|||||||
export function DomainActionsBar({ onCreateSubdomain }: DomainActionsBarProps) {
|
export function DomainActionsBar({ onCreateSubdomain }: DomainActionsBarProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Button onClick={onCreateSubdomain}>
|
<AppButton onClick={onCreateSubdomain}>
|
||||||
<PlusIcon data-icon="inline-start" />
|
<PlusIcon data-icon="inline-start" />
|
||||||
Создать поддомен
|
Создать поддомен
|
||||||
</Button>
|
</AppButton>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,24 @@ import { EmptyState } from '@/components/empty-state'
|
|||||||
import { StatusBadge } from '@/components/status-badge'
|
import { StatusBadge } from '@/components/status-badge'
|
||||||
import { groupBindingsByHostname } from '@/lib/domain-ips'
|
import { groupBindingsByHostname } from '@/lib/domain-ips'
|
||||||
import type { ServiceBinding } from '@/lib/schemas'
|
import type { ServiceBinding } from '@/lib/schemas'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
Card,
|
AppCard,
|
||||||
CardContent,
|
AppCardContent,
|
||||||
CardDescription,
|
AppCardDescription,
|
||||||
CardFooter,
|
AppCardFooter,
|
||||||
CardHeader,
|
AppCardHeader,
|
||||||
CardTitle,
|
AppCardTitle,
|
||||||
} from '@cfdm/ui/components/card'
|
} from '@/components/app-card'
|
||||||
import {
|
import {
|
||||||
Item,
|
AppItem,
|
||||||
ItemActions,
|
AppItemActions,
|
||||||
ItemContent,
|
AppItemContent,
|
||||||
ItemGroup,
|
AppItemGroup,
|
||||||
ItemSeparator,
|
AppItemSeparator,
|
||||||
ItemTitle,
|
AppItemTitle,
|
||||||
} from '@cfdm/ui/components/item'
|
} from '@/components/app-item'
|
||||||
|
|
||||||
interface DomainBindingsCardProps {
|
interface DomainBindingsCardProps {
|
||||||
bindings: ServiceBinding[]
|
bindings: ServiceBinding[]
|
||||||
@@ -36,12 +36,12 @@ export function DomainBindingsCard({ bindings }: DomainBindingsCardProps) {
|
|||||||
const entries = [...byHostname.entries()]
|
const entries = [...byHostname.entries()]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<AppCard>
|
||||||
<CardHeader>
|
<AppCardHeader>
|
||||||
<CardTitle>Привязки сервисов</CardTitle>
|
<AppCardTitle>Привязки сервисов</AppCardTitle>
|
||||||
<CardDescription>Сервисы, назначенные hostname в этой зоне</CardDescription>
|
<AppCardDescription>Сервисы, назначенные hostname в этой зоне</AppCardDescription>
|
||||||
</CardHeader>
|
</AppCardHeader>
|
||||||
<CardContent>
|
<AppCardContent>
|
||||||
{entries.length === 0 ? (
|
{entries.length === 0 ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
icon={Link2Icon}
|
icon={Link2Icon}
|
||||||
@@ -50,18 +50,18 @@ export function DomainBindingsCard({ bindings }: DomainBindingsCardProps) {
|
|||||||
className="border border-dashed p-4"
|
className="border border-dashed p-4"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ItemGroup className="gap-0">
|
<AppItemGroup className="gap-0">
|
||||||
{entries.map(([hostname, hostnameBindings], index) => {
|
{entries.map(([hostname, hostnameBindings], index) => {
|
||||||
const services = uniqueServices(hostnameBindings)
|
const services = uniqueServices(hostnameBindings)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={hostname}>
|
<div key={hostname}>
|
||||||
<Item variant="outline">
|
<AppItem variant="outline">
|
||||||
<ItemContent className="gap-2">
|
<AppItemContent className="gap-2">
|
||||||
<ItemTitle className="font-mono">{hostname}</ItemTitle>
|
<AppItemTitle className="font-mono">{hostname}</AppItemTitle>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{services.map((name) => (
|
{services.map((name) => (
|
||||||
<Badge key={name}>{name}</Badge>
|
<AppBadge key={name}>{name}</AppBadge>
|
||||||
))}
|
))}
|
||||||
{[
|
{[
|
||||||
...new Set(
|
...new Set(
|
||||||
@@ -73,32 +73,32 @@ export function DomainBindingsCard({ bindings }: DomainBindingsCardProps) {
|
|||||||
<StatusBadge key={status} status={status} />
|
<StatusBadge key={status} status={status} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</ItemContent>
|
</AppItemContent>
|
||||||
<ItemActions>
|
<AppItemActions>
|
||||||
<Button
|
<AppButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
render={<Link to="/services" search={{ domainId: undefined }} />}
|
render={<Link to="/services" search={{ domainId: undefined }} />}
|
||||||
>
|
>
|
||||||
Сервисы
|
Сервисы
|
||||||
</Button>
|
</AppButton>
|
||||||
</ItemActions>
|
</AppItemActions>
|
||||||
</Item>
|
</AppItem>
|
||||||
{index < entries.length - 1 && <ItemSeparator />}
|
{index < entries.length - 1 && <AppItemSeparator />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ItemGroup>
|
</AppItemGroup>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</AppCardContent>
|
||||||
{entries.length > 0 && (
|
{entries.length > 0 && (
|
||||||
<CardFooter>
|
<AppCardFooter>
|
||||||
<Button variant="link" className="h-auto p-0" nativeButton={false} render={<Link to="/services" search={{ domainId: undefined }} />}>
|
<AppButton variant="link" className="h-auto p-0" nativeButton={false} render={<Link to="/services" search={{ domainId: undefined }} />}>
|
||||||
Управление привязками
|
Управление привязками
|
||||||
</Button>
|
</AppButton>
|
||||||
</CardFooter>
|
</AppCardFooter>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</AppCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { createGroupSchema, type CreateGroupInput, type Group } from '@/lib/sche
|
|||||||
import { FormSheet } from '@/components/form-sheet'
|
import { FormSheet } from '@/components/form-sheet'
|
||||||
import { FormFieldSimple } from '@/components/form-field'
|
import { FormFieldSimple } from '@/components/form-field'
|
||||||
import { LoadingButton } from '@/components/loading-button'
|
import { LoadingButton } from '@/components/loading-button'
|
||||||
import { FieldGroup } from '@cfdm/ui/components/field'
|
import { AppFieldGroup } from '@/components/app-field'
|
||||||
import { Input } from '@cfdm/ui/components/input'
|
import { AppInput } from '@/components/app-input'
|
||||||
|
|
||||||
interface DomainGroupEditSheetProps {
|
interface DomainGroupEditSheetProps {
|
||||||
mode: 'create' | 'edit'
|
mode: 'create' | 'edit'
|
||||||
@@ -69,13 +69,13 @@ export function DomainGroupEditSheet({
|
|||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FieldGroup>
|
<AppFieldGroup>
|
||||||
<FormFieldSimple
|
<FormFieldSimple
|
||||||
label="Название"
|
label="Название"
|
||||||
htmlFor="domain-group-name"
|
htmlFor="domain-group-name"
|
||||||
error={form.formState.errors.name}
|
error={form.formState.errors.name}
|
||||||
>
|
>
|
||||||
<Input
|
<AppInput
|
||||||
id="domain-group-name"
|
id="domain-group-name"
|
||||||
placeholder="Production"
|
placeholder="Production"
|
||||||
{...form.register('name')}
|
{...form.register('name')}
|
||||||
@@ -87,14 +87,14 @@ export function DomainGroupEditSheet({
|
|||||||
htmlFor="domain-group-slug"
|
htmlFor="domain-group-slug"
|
||||||
error={form.formState.errors.slug}
|
error={form.formState.errors.slug}
|
||||||
>
|
>
|
||||||
<Input
|
<AppInput
|
||||||
id="domain-group-slug"
|
id="domain-group-slug"
|
||||||
placeholder="production"
|
placeholder="production"
|
||||||
{...form.register('slug')}
|
{...form.register('slug')}
|
||||||
aria-invalid={!!form.formState.errors.slug}
|
aria-invalid={!!form.formState.errors.slug}
|
||||||
/>
|
/>
|
||||||
</FormFieldSimple>
|
</FormFieldSimple>
|
||||||
</FieldGroup>
|
</AppFieldGroup>
|
||||||
</FormSheet>
|
</FormSheet>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import {
|
import {
|
||||||
Item,
|
AppItem,
|
||||||
ItemContent,
|
AppItemContent,
|
||||||
ItemGroup,
|
AppItemGroup,
|
||||||
ItemSeparator,
|
AppItemSeparator,
|
||||||
ItemTitle,
|
AppItemTitle,
|
||||||
} from '@cfdm/ui/components/item'
|
} from '@/components/app-item'
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -19,9 +19,9 @@ interface DomainIpBadgesProps {
|
|||||||
|
|
||||||
function IpBadge({ ip }: { ip: string }) {
|
function IpBadge({ ip }: { ip: string }) {
|
||||||
return (
|
return (
|
||||||
<Badge variant="secondary" className="font-mono">
|
<AppBadge variant="secondary" className="font-mono">
|
||||||
{ip}
|
{ip}
|
||||||
</Badge>
|
</AppBadge>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,24 +43,24 @@ export function DomainIpBadges({ ips }: DomainIpBadgesProps) {
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger
|
<TooltipTrigger
|
||||||
render={
|
render={
|
||||||
<Badge variant="outline" className="cursor-default font-mono tabular-nums" />
|
<AppBadge variant="outline" className="cursor-default font-mono tabular-nums" />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
+{rest.length}
|
+{rest.length}
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top" className="max-w-xs p-2">
|
<TooltipContent side="top" className="max-w-xs p-2">
|
||||||
<ItemGroup className="gap-0">
|
<AppItemGroup className="gap-0">
|
||||||
{ips.map((ip, index) => (
|
{ips.map((ip, index) => (
|
||||||
<div key={ip}>
|
<div key={ip}>
|
||||||
<Item size="xs" variant="muted" className="border-0 px-2 py-1">
|
<AppItem size="xs" variant="muted" className="border-0 px-2 py-1">
|
||||||
<ItemContent>
|
<AppItemContent>
|
||||||
<ItemTitle className="font-mono font-normal">{ip}</ItemTitle>
|
<AppItemTitle className="font-mono font-normal">{ip}</AppItemTitle>
|
||||||
</ItemContent>
|
</AppItemContent>
|
||||||
</Item>
|
</AppItem>
|
||||||
{index < ips.length - 1 && <ItemSeparator className="my-0" />}
|
{index < ips.length - 1 && <AppItemSeparator className="my-0" />}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</ItemGroup>
|
</AppItemGroup>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import {
|
|||||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||||
import { StatusBadge } from '@/components/status-badge'
|
import { StatusBadge } from '@/components/status-badge'
|
||||||
import type { DomainListItem } from '@/lib/schemas'
|
import type { DomainListItem } from '@/lib/schemas'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -88,17 +88,17 @@ export function DomainsDataTable({
|
|||||||
{
|
{
|
||||||
accessorKey: 'zone_name',
|
accessorKey: 'zone_name',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<Button
|
<AppButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="-ml-3 h-8"
|
className="-ml-3 h-8"
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
>
|
>
|
||||||
Зона
|
Зона
|
||||||
<ArrowUpDownIcon />
|
<ArrowUpDownIcon />
|
||||||
</Button>
|
</AppButton>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Button
|
<AppButton
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0 font-medium"
|
className="h-auto p-0 font-medium"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
@@ -110,7 +110,7 @@ export function DomainsDataTable({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{row.original.zone_name}
|
{row.original.zone_name}
|
||||||
</Button>
|
</AppButton>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -121,7 +121,7 @@ export function DomainsDataTable({
|
|||||||
const domain = row.original
|
const domain = row.original
|
||||||
if (domain.group_id && domain.group_name) {
|
if (domain.group_id && domain.group_name) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<AppButton
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0"
|
className="h-auto p-0"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
@@ -133,17 +133,17 @@ export function DomainsDataTable({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{domain.group_name}
|
{domain.group_name}
|
||||||
</Button>
|
</AppButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return <Badge variant="outline">Без группы</Badge>
|
return <AppBadge variant="outline">Без группы</AppBadge>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'service_count',
|
accessorKey: 'service_count',
|
||||||
header: 'Сервисы',
|
header: 'Сервисы',
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Button
|
<AppButton
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0 tabular-nums"
|
className="h-auto p-0 tabular-nums"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
@@ -155,7 +155,7 @@ export function DomainsDataTable({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{row.original.service_count}
|
{row.original.service_count}
|
||||||
</Button>
|
</AppButton>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -179,11 +179,11 @@ export function DomainsDataTable({
|
|||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
render={
|
render={
|
||||||
<Button variant="outline" size="icon" className="size-8" />
|
<AppButton variant="outline" size="icon" className="size-8" />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon />
|
<MoreHorizontalIcon />
|
||||||
<span className="sr-only">Действия</span>
|
<span className="sr-only">Действия</span>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -341,22 +341,22 @@ export function DomainsDataTable({
|
|||||||
<span className="text-sm text-muted-foreground tabular-nums">
|
<span className="text-sm text-muted-foreground tabular-nums">
|
||||||
Стр. {table.getState().pagination.pageIndex + 1} из {table.getPageCount()}
|
Стр. {table.getState().pagination.pageIndex + 1} из {table.getPageCount()}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<AppButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => table.previousPage()}
|
onClick={() => table.previousPage()}
|
||||||
disabled={!table.getCanPreviousPage()}
|
disabled={!table.getCanPreviousPage()}
|
||||||
>
|
>
|
||||||
Назад
|
Назад
|
||||||
</Button>
|
</AppButton>
|
||||||
<Button
|
<AppButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => table.nextPage()}
|
onClick={() => table.nextPage()}
|
||||||
disabled={!table.getCanNextPage()}
|
disabled={!table.getCanNextPage()}
|
||||||
>
|
>
|
||||||
Вперёд
|
Вперёд
|
||||||
</Button>
|
</AppButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { FolderTreeIcon, MoreHorizontalIcon, PencilIcon, Trash2Icon } from 'lucide-react'
|
import { FolderTreeIcon, MoreHorizontalIcon, PencilIcon, Trash2Icon } from 'lucide-react'
|
||||||
import type { BoardColumn } from '@/components/groups-board/types'
|
import type { BoardColumn } from '@/components/groups-board/types'
|
||||||
import type { Group } from '@/lib/schemas'
|
import type { Group } from '@/lib/schemas'
|
||||||
import { AccordionTrigger } from '@cfdm/ui/components/accordion'
|
import { AppAccordionTrigger } from '@/components/app-accordion'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -31,7 +31,7 @@ export function DomainGroupHeader({
|
|||||||
}: DomainGroupHeaderProps) {
|
}: DomainGroupHeaderProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1 px-1">
|
<div className="flex items-center gap-1 px-1">
|
||||||
<AccordionTrigger className="min-h-10 flex-1 items-center gap-2 rounded-md py-2 hover:bg-muted/40 hover:no-underline">
|
<AppAccordionTrigger className="min-h-10 flex-1 items-center gap-2 rounded-md py-2 hover:bg-muted/40 hover:no-underline">
|
||||||
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">
|
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">
|
||||||
{column.groupId !== null ? (
|
{column.groupId !== null ? (
|
||||||
<span className="flex size-7 shrink-0 items-center justify-center rounded-md bg-background/80 text-muted-foreground">
|
<span className="flex size-7 shrink-0 items-center justify-center rounded-md bg-background/80 text-muted-foreground">
|
||||||
@@ -39,19 +39,19 @@ export function DomainGroupHeader({
|
|||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="font-medium">{column.title}</span>
|
<span className="font-medium">{column.title}</span>
|
||||||
<Badge variant="secondary">{column.items.length}</Badge>
|
<AppBadge variant="secondary">{column.items.length}</AppBadge>
|
||||||
{column.slug ? (
|
{column.slug ? (
|
||||||
<Badge variant="outline" className="font-mono">
|
<AppBadge variant="outline" className="font-mono">
|
||||||
{column.slug}
|
{column.slug}
|
||||||
</Badge>
|
</AppBadge>
|
||||||
) : null}
|
) : null}
|
||||||
{!isOpen && isDragging && !dragDisabled ? (
|
{!isOpen && isDragging && !dragDisabled ? (
|
||||||
<Badge variant="default" className="font-normal">
|
<AppBadge variant="default" className="font-normal">
|
||||||
Отпустите для переноса
|
Отпустите для переноса
|
||||||
</Badge>
|
</AppBadge>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AppAccordionTrigger>
|
||||||
|
|
||||||
{column.groupId !== null && column.group ? (
|
{column.groupId !== null && column.group ? (
|
||||||
<div
|
<div
|
||||||
@@ -62,7 +62,7 @@ export function DomainGroupHeader({
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
render={
|
render={
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import { DomainRow } from '@/components/groups-board/domain-row'
|
|||||||
import type { BoardColumn } from '@/components/groups-board/types'
|
import type { BoardColumn } from '@/components/groups-board/types'
|
||||||
import type { Group } from '@/lib/schemas'
|
import type { Group } from '@/lib/schemas'
|
||||||
import {
|
import {
|
||||||
AccordionContent,
|
AppAccordionContent,
|
||||||
AccordionItem,
|
AppAccordionItem,
|
||||||
} from '@cfdm/ui/components/accordion'
|
} from '@/components/app-accordion'
|
||||||
import {
|
import {
|
||||||
Empty,
|
Empty,
|
||||||
EmptyDescription,
|
EmptyDescription,
|
||||||
EmptyHeader,
|
EmptyHeader,
|
||||||
EmptyTitle,
|
EmptyTitle,
|
||||||
} from '@cfdm/ui/components/empty'
|
} from '@cfdm/ui/components/empty'
|
||||||
import { ItemGroup, ItemSeparator } from '@cfdm/ui/components/item'
|
import { AppItemGroup, AppItemSeparator } from '@/components/app-item'
|
||||||
import { cn } from '@cfdm/ui/lib/utils'
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
interface DomainGroupItemProps {
|
interface DomainGroupItemProps {
|
||||||
@@ -50,7 +50,7 @@ export function DomainGroupItem({
|
|||||||
}, [isOver, isDragging, dragDisabled, column.id, onExpandColumn])
|
}, [isOver, isDragging, dragDisabled, column.id, onExpandColumn])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccordionItem
|
<AppAccordionItem
|
||||||
value={column.id}
|
value={column.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
'not-last:border-b-0 overflow-hidden rounded-lg border border-border transition-colors',
|
'not-last:border-b-0 overflow-hidden rounded-lg border border-border transition-colors',
|
||||||
@@ -68,13 +68,13 @@ export function DomainGroupItem({
|
|||||||
onDeleteGroup={onDeleteGroup}
|
onDeleteGroup={onDeleteGroup}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AccordionContent className="px-1 pb-2">
|
<AppAccordionContent className="px-1 pb-2">
|
||||||
<div ref={setNodeRef}>
|
<div ref={setNodeRef}>
|
||||||
{column.items.length > 0 ? (
|
{column.items.length > 0 ? (
|
||||||
<ItemGroup className="gap-0 py-1">
|
<AppItemGroup className="gap-0 py-1">
|
||||||
{column.items.map((domain, index) => (
|
{column.items.map((domain, index) => (
|
||||||
<div key={domain.id}>
|
<div key={domain.id}>
|
||||||
{index > 0 ? <ItemSeparator className="my-0" /> : null}
|
{index > 0 ? <AppItemSeparator className="my-0" /> : null}
|
||||||
<DomainRow
|
<DomainRow
|
||||||
domain={domain}
|
domain={domain}
|
||||||
serviceLabels={serviceLabelsByDomain?.get(domain.id)}
|
serviceLabels={serviceLabelsByDomain?.get(domain.id)}
|
||||||
@@ -82,7 +82,7 @@ export function DomainGroupItem({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</ItemGroup>
|
</AppItemGroup>
|
||||||
) : (
|
) : (
|
||||||
<Empty
|
<Empty
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -99,7 +99,7 @@ export function DomainGroupItem({
|
|||||||
</Empty>
|
</Empty>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AccordionContent>
|
</AppAccordionContent>
|
||||||
</AccordionItem>
|
</AppAccordionItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import {
|
|||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { StatusBadge } from '@/components/status-badge'
|
import { StatusBadge } from '@/components/status-badge'
|
||||||
import type { DomainListItem } from '@/lib/schemas'
|
import type { DomainListItem } from '@/lib/schemas'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import { Card, CardContent } from '@cfdm/ui/components/card'
|
import { AppCard, AppCardContent } from '@/components/app-card'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -45,15 +45,15 @@ function DomainServiceList({
|
|||||||
|
|
||||||
if (labels.length === 1) {
|
if (labels.length === 1) {
|
||||||
return (
|
return (
|
||||||
<Badge variant="secondary" className="max-w-full truncate font-normal">
|
<AppBadge variant="secondary" className="max-w-full truncate font-normal">
|
||||||
{labels[0]}
|
{labels[0]}
|
||||||
</Badge>
|
</AppBadge>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card size="sm" className="w-full shadow-none">
|
<AppCard size="sm" className="w-full shadow-none">
|
||||||
<CardContent className="flex flex-col gap-1 py-0">
|
<AppCardContent className="flex flex-col gap-1 py-0">
|
||||||
{labels.map((label) => (
|
{labels.map((label) => (
|
||||||
<div
|
<div
|
||||||
key={label}
|
key={label}
|
||||||
@@ -63,8 +63,8 @@ function DomainServiceList({
|
|||||||
<span className="truncate">{label}</span>
|
<span className="truncate">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</CardContent>
|
</AppCardContent>
|
||||||
</Card>
|
</AppCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ export const DomainRow = memo(function DomainRow({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!dragDisabled && !overlay ? (
|
{!dragDisabled && !overlay ? (
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
@@ -110,7 +110,7 @@ export const DomainRow = memo(function DomainRow({
|
|||||||
{...attributes}
|
{...attributes}
|
||||||
>
|
>
|
||||||
<GripVerticalIcon />
|
<GripVerticalIcon />
|
||||||
</Button>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -138,15 +138,15 @@ export const DomainRow = memo(function DomainRow({
|
|||||||
<StatusBadge status={domain.status} />
|
<StatusBadge status={domain.status} />
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
render={
|
render={
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
aria-label={`Действия для ${domain.zone_name}`}
|
aria-label={`Действия для ${domain.zone_name}`}
|
||||||
onPointerDown={(event) => event.stopPropagation()}
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon />
|
<MoreHorizontalIcon />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { DomainGroupItem } from '@/components/groups-board/domain-group-item'
|
|||||||
import { DomainRow } from '@/components/groups-board/domain-row'
|
import { DomainRow } from '@/components/groups-board/domain-row'
|
||||||
import type { BoardState } from '@/components/groups-board/types'
|
import type { BoardState } from '@/components/groups-board/types'
|
||||||
import type { DomainListItem, Group } from '@/lib/schemas'
|
import type { DomainListItem, Group } from '@/lib/schemas'
|
||||||
import { Accordion } from '@cfdm/ui/components/accordion'
|
import { AppAccordion } from '@/components/app-accordion'
|
||||||
|
|
||||||
interface GroupsBoardProps {
|
interface GroupsBoardProps {
|
||||||
board: BoardState
|
board: BoardState
|
||||||
@@ -70,7 +70,7 @@ export function GroupsBoard({
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Accordion
|
<AppAccordion
|
||||||
multiple
|
multiple
|
||||||
value={openColumns}
|
value={openColumns}
|
||||||
onValueChange={setOpenColumns}
|
onValueChange={setOpenColumns}
|
||||||
@@ -89,7 +89,7 @@ export function GroupsBoard({
|
|||||||
serviceLabelsByDomain={serviceLabelsByDomain}
|
serviceLabelsByDomain={serviceLabelsByDomain}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Accordion>
|
</AppAccordion>
|
||||||
</DragContextProvider>
|
</DragContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from '@cfdm/ui/components/breadcrumb'
|
} from '@cfdm/ui/components/breadcrumb'
|
||||||
import { Separator } from '@cfdm/ui/components/separator'
|
import { AppSeparator } from '@/components/app-separator'
|
||||||
import { SidebarTrigger } from '@cfdm/ui/components/sidebar'
|
import { SidebarTrigger } from '@cfdm/ui/components/sidebar'
|
||||||
|
|
||||||
export interface RouteBreadcrumbLoaderData {
|
export interface RouteBreadcrumbLoaderData {
|
||||||
@@ -86,7 +86,7 @@ export function SiteHeader() {
|
|||||||
<header className="flex h-16 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
<header className="flex h-16 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
||||||
<div className="flex items-center gap-2 px-4">
|
<div className="flex items-center gap-2 px-4">
|
||||||
<SidebarTrigger className="-ml-1" />
|
<SidebarTrigger className="-ml-1" />
|
||||||
<Separator
|
<AppSeparator
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
className="mr-2 data-[orientation=vertical]:h-4"
|
className="mr-2 data-[orientation=vertical]:h-4"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ import { loginSchema, type LoginInput } from '@/lib/schemas'
|
|||||||
import { FormFieldSimple } from '@/components/form-field'
|
import { FormFieldSimple } from '@/components/form-field'
|
||||||
import { LoadingButton } from '@/components/loading-button'
|
import { LoadingButton } from '@/components/loading-button'
|
||||||
import {
|
import {
|
||||||
Card,
|
AppCard,
|
||||||
CardContent,
|
AppCardContent,
|
||||||
CardDescription,
|
AppCardDescription,
|
||||||
CardHeader,
|
AppCardHeader,
|
||||||
CardTitle,
|
AppCardTitle,
|
||||||
} from '@cfdm/ui/components/card'
|
} from '@/components/app-card'
|
||||||
import { FieldGroup } from '@cfdm/ui/components/field'
|
import { AppFieldGroup } from '@/components/app-field'
|
||||||
import { Input } from '@cfdm/ui/components/input'
|
import { AppInput } from '@/components/app-input'
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert'
|
import { AppAlert, AppAlertDescription, AppAlertTitle } from '@/components/app-alert'
|
||||||
|
|
||||||
type LoginFormProps = React.ComponentProps<'div'>
|
type LoginFormProps = React.ComponentProps<'div'>
|
||||||
|
|
||||||
@@ -45,22 +45,22 @@ export function LoginForm({ className, ...props }: LoginFormProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
||||||
<Card>
|
<AppCard>
|
||||||
<CardHeader className="text-center">
|
<AppCardHeader className="text-center">
|
||||||
<CardTitle className="text-xl">Вход в систему</CardTitle>
|
<AppCardTitle className="text-xl">Вход в систему</AppCardTitle>
|
||||||
<CardDescription>
|
<AppCardDescription>
|
||||||
Введите учётные данные для доступа к панели управления
|
Введите учётные данные для доступа к панели управления
|
||||||
</CardDescription>
|
</AppCardDescription>
|
||||||
</CardHeader>
|
</AppCardHeader>
|
||||||
<CardContent>
|
<AppCardContent>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<FieldGroup>
|
<AppFieldGroup>
|
||||||
<FormFieldSimple
|
<FormFieldSimple
|
||||||
label="Имя пользователя"
|
label="Имя пользователя"
|
||||||
htmlFor="username"
|
htmlFor="username"
|
||||||
error={form.formState.errors.username}
|
error={form.formState.errors.username}
|
||||||
>
|
>
|
||||||
<Input
|
<AppInput
|
||||||
id="username"
|
id="username"
|
||||||
placeholder="admin"
|
placeholder="admin"
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
@@ -73,7 +73,7 @@ export function LoginForm({ className, ...props }: LoginFormProps) {
|
|||||||
htmlFor="password"
|
htmlFor="password"
|
||||||
error={form.formState.errors.password}
|
error={form.formState.errors.password}
|
||||||
>
|
>
|
||||||
<Input
|
<AppInput
|
||||||
id="password"
|
id="password"
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
@@ -82,11 +82,11 @@ export function LoginForm({ className, ...props }: LoginFormProps) {
|
|||||||
/>
|
/>
|
||||||
</FormFieldSimple>
|
</FormFieldSimple>
|
||||||
{rootError && (
|
{rootError && (
|
||||||
<Alert variant="destructive">
|
<AppAlert variant="destructive">
|
||||||
<CircleAlertIcon />
|
<CircleAlertIcon />
|
||||||
<AlertTitle>Ошибка входа</AlertTitle>
|
<AppAlertTitle>Ошибка входа</AppAlertTitle>
|
||||||
<AlertDescription>{rootError}</AlertDescription>
|
<AppAlertDescription>{rootError}</AppAlertDescription>
|
||||||
</Alert>
|
</AppAlert>
|
||||||
)}
|
)}
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -96,10 +96,10 @@ export function LoginForm({ className, ...props }: LoginFormProps) {
|
|||||||
>
|
>
|
||||||
Войти
|
Войти
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</FieldGroup>
|
</AppFieldGroup>
|
||||||
</form>
|
</form>
|
||||||
</CardContent>
|
</AppCardContent>
|
||||||
</Card>
|
</AppCard>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useNavigate } from '@tanstack/react-router'
|
import { useNavigate } from '@tanstack/react-router'
|
||||||
import { Avatar, AvatarFallback } from '@cfdm/ui/components/avatar'
|
import { AppAvatar, AppAvatarFallback } from '@/components/app-avatar'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -36,9 +36,9 @@ export function NavUser() {
|
|||||||
<SidebarMenuButton size="lg" className="aria-expanded:bg-muted" />
|
<SidebarMenuButton size="lg" className="aria-expanded:bg-muted" />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Avatar>
|
<AppAvatar>
|
||||||
<AvatarFallback>АД</AvatarFallback>
|
<AppAvatarFallback>АД</AppAvatarFallback>
|
||||||
</Avatar>
|
</AppAvatar>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-medium">Администратор</span>
|
<span className="truncate font-medium">Администратор</span>
|
||||||
<span className="truncate text-xs">admin</span>
|
<span className="truncate text-xs">admin</span>
|
||||||
@@ -54,9 +54,9 @@ export function NavUser() {
|
|||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuLabel className="p-0 font-normal">
|
<DropdownMenuLabel className="p-0 font-normal">
|
||||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||||
<Avatar>
|
<AppAvatar>
|
||||||
<AvatarFallback>АД</AvatarFallback>
|
<AppAvatarFallback>АД</AppAvatarFallback>
|
||||||
</Avatar>
|
</AppAvatar>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-medium">Администратор</span>
|
<span className="truncate font-medium">Администратор</span>
|
||||||
<span className="truncate text-xs">admin</span>
|
<span className="truncate text-xs">admin</span>
|
||||||
|
|||||||
@@ -6,22 +6,22 @@ import {
|
|||||||
ShieldCheckIcon,
|
ShieldCheckIcon,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { StatusBadge } from '@/components/status-badge'
|
import { StatusBadge } from '@/components/status-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
Card,
|
AppCard,
|
||||||
CardAction,
|
AppCardAction,
|
||||||
CardContent,
|
AppCardContent,
|
||||||
CardDescription,
|
AppCardDescription,
|
||||||
CardFooter,
|
AppCardFooter,
|
||||||
CardHeader,
|
AppCardHeader,
|
||||||
CardTitle,
|
AppCardTitle,
|
||||||
} from '@cfdm/ui/components/card'
|
} from '@/components/app-card'
|
||||||
import {
|
import {
|
||||||
Item,
|
AppItem,
|
||||||
ItemContent,
|
AppItemContent,
|
||||||
ItemGroup,
|
AppItemGroup,
|
||||||
ItemTitle,
|
AppItemTitle,
|
||||||
} from '@cfdm/ui/components/item'
|
} from '@/components/app-item'
|
||||||
|
|
||||||
interface SectionCardsProps {
|
interface SectionCardsProps {
|
||||||
domainCount: number
|
domainCount: number
|
||||||
@@ -41,106 +41,106 @@ export function SectionCards({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||||
<Card>
|
<AppCard>
|
||||||
<CardHeader>
|
<AppCardHeader>
|
||||||
<CardDescription>Домены</CardDescription>
|
<AppCardDescription>Домены</AppCardDescription>
|
||||||
<CardTitle className="text-3xl font-semibold tabular-nums">
|
<AppCardTitle className="text-3xl font-semibold tabular-nums">
|
||||||
{domainCount}
|
{domainCount}
|
||||||
</CardTitle>
|
</AppCardTitle>
|
||||||
<CardAction>
|
<AppCardAction>
|
||||||
<GlobeIcon className="size-4 text-muted-foreground" />
|
<GlobeIcon className="size-4 text-muted-foreground" />
|
||||||
</CardAction>
|
</AppCardAction>
|
||||||
</CardHeader>
|
</AppCardHeader>
|
||||||
<CardFooter>
|
<AppCardFooter>
|
||||||
<Button
|
<AppButton
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0"
|
className="h-auto p-0"
|
||||||
render={<Link to="/domains" />}
|
render={<Link to="/domains" />}
|
||||||
>
|
>
|
||||||
Управление доменами
|
Управление доменами
|
||||||
</Button>
|
</AppButton>
|
||||||
</CardFooter>
|
</AppCardFooter>
|
||||||
</Card>
|
</AppCard>
|
||||||
<Card>
|
<AppCard>
|
||||||
<CardHeader>
|
<AppCardHeader>
|
||||||
<CardDescription>Группы доменов</CardDescription>
|
<AppCardDescription>Группы доменов</AppCardDescription>
|
||||||
<CardTitle className="text-3xl font-semibold tabular-nums">
|
<AppCardTitle className="text-3xl font-semibold tabular-nums">
|
||||||
{groupCount}
|
{groupCount}
|
||||||
</CardTitle>
|
</AppCardTitle>
|
||||||
<CardAction>
|
<AppCardAction>
|
||||||
<FolderTreeIcon className="size-4 text-muted-foreground" />
|
<FolderTreeIcon className="size-4 text-muted-foreground" />
|
||||||
</CardAction>
|
</AppCardAction>
|
||||||
</CardHeader>
|
</AppCardHeader>
|
||||||
<CardFooter>
|
<AppCardFooter>
|
||||||
<Button
|
<AppButton
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0"
|
className="h-auto p-0"
|
||||||
render={<Link to="/groups" />}
|
render={<Link to="/groups" />}
|
||||||
>
|
>
|
||||||
Канбан групп
|
Канбан групп
|
||||||
</Button>
|
</AppButton>
|
||||||
</CardFooter>
|
</AppCardFooter>
|
||||||
</Card>
|
</AppCard>
|
||||||
<Card>
|
<AppCard>
|
||||||
<CardHeader>
|
<AppCardHeader>
|
||||||
<CardDescription>Сервисы</CardDescription>
|
<AppCardDescription>Сервисы</AppCardDescription>
|
||||||
<CardTitle className="text-3xl font-semibold tabular-nums">
|
<AppCardTitle className="text-3xl font-semibold tabular-nums">
|
||||||
{serviceCount}
|
{serviceCount}
|
||||||
</CardTitle>
|
</AppCardTitle>
|
||||||
<CardAction>
|
<AppCardAction>
|
||||||
<ServerIcon className="size-4 text-muted-foreground" />
|
<ServerIcon className="size-4 text-muted-foreground" />
|
||||||
</CardAction>
|
</AppCardAction>
|
||||||
</CardHeader>
|
</AppCardHeader>
|
||||||
<CardFooter>
|
<AppCardFooter>
|
||||||
<Button
|
<AppButton
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0"
|
className="h-auto p-0"
|
||||||
render={<Link to="/services" search={{ domainId: undefined }} />}
|
render={<Link to="/services" search={{ domainId: undefined }} />}
|
||||||
>
|
>
|
||||||
Управление сервисами
|
Управление сервисами
|
||||||
</Button>
|
</AppButton>
|
||||||
</CardFooter>
|
</AppCardFooter>
|
||||||
</Card>
|
</AppCard>
|
||||||
<Card>
|
<AppCard>
|
||||||
<CardHeader>
|
<AppCardHeader>
|
||||||
<CardDescription>Сертификаты</CardDescription>
|
<AppCardDescription>Сертификаты</AppCardDescription>
|
||||||
<CardTitle className="text-3xl font-semibold tabular-nums">
|
<AppCardTitle className="text-3xl font-semibold tabular-nums">
|
||||||
{certCount}
|
{certCount}
|
||||||
</CardTitle>
|
</AppCardTitle>
|
||||||
<CardAction>
|
<AppCardAction>
|
||||||
<ShieldCheckIcon className="size-4 text-muted-foreground" />
|
<ShieldCheckIcon className="size-4 text-muted-foreground" />
|
||||||
</CardAction>
|
</AppCardAction>
|
||||||
</CardHeader>
|
</AppCardHeader>
|
||||||
<CardFooter>
|
<AppCardFooter>
|
||||||
<Button
|
<AppButton
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0"
|
className="h-auto p-0"
|
||||||
render={<Link to="/certificates" />}
|
render={<Link to="/certificates" />}
|
||||||
>
|
>
|
||||||
Мониторинг сертификатов
|
Мониторинг сертификатов
|
||||||
</Button>
|
</AppButton>
|
||||||
</CardFooter>
|
</AppCardFooter>
|
||||||
</Card>
|
</AppCard>
|
||||||
</div>
|
</div>
|
||||||
{certSummary && certSummary.length > 0 ? (
|
{certSummary && certSummary.length > 0 ? (
|
||||||
<Card>
|
<AppCard>
|
||||||
<CardHeader>
|
<AppCardHeader>
|
||||||
<CardTitle className="text-base">Статусы сертификатов</CardTitle>
|
<AppCardTitle className="text-base">Статусы сертификатов</AppCardTitle>
|
||||||
<CardDescription>Сводка по последней проверке</CardDescription>
|
<AppCardDescription>Сводка по последней проверке</AppCardDescription>
|
||||||
</CardHeader>
|
</AppCardHeader>
|
||||||
<CardContent>
|
<AppCardContent>
|
||||||
<ItemGroup className="gap-2">
|
<AppItemGroup className="gap-2">
|
||||||
{certSummary.map(([status, count]) => (
|
{certSummary.map(([status, count]) => (
|
||||||
<Item key={status} variant="outline" size="sm">
|
<AppItem key={status} variant="outline" size="sm">
|
||||||
<ItemContent className="flex flex-row items-center justify-between gap-2">
|
<AppItemContent className="flex flex-row items-center justify-between gap-2">
|
||||||
<StatusBadge status={status} />
|
<StatusBadge status={status} />
|
||||||
<ItemTitle className="font-medium tabular-nums">{count}</ItemTitle>
|
<AppItemTitle className="font-medium tabular-nums">{count}</AppItemTitle>
|
||||||
</ItemContent>
|
</AppItemContent>
|
||||||
</Item>
|
</AppItem>
|
||||||
))}
|
))}
|
||||||
</ItemGroup>
|
</AppItemGroup>
|
||||||
</CardContent>
|
</AppCardContent>
|
||||||
</Card>
|
</AppCard>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,22 +3,22 @@ import { CSS } from '@dnd-kit/utilities'
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import { StatusBadge } from '@/components/status-badge'
|
import { StatusBadge } from '@/components/status-badge'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
Card,
|
AppCard,
|
||||||
CardAction,
|
AppCardAction,
|
||||||
CardContent,
|
AppCardContent,
|
||||||
CardFooter,
|
AppCardFooter,
|
||||||
CardHeader,
|
AppCardHeader,
|
||||||
CardTitle,
|
AppCardTitle,
|
||||||
} from '@cfdm/ui/components/card'
|
} from '@/components/app-card'
|
||||||
import {
|
import {
|
||||||
Field,
|
AppField,
|
||||||
FieldGroup,
|
AppFieldGroup,
|
||||||
FieldLabel,
|
AppFieldLabel,
|
||||||
} from '@cfdm/ui/components/field'
|
} from '@/components/app-field'
|
||||||
import { Input } from '@cfdm/ui/components/input'
|
import { AppInput } from '@/components/app-input'
|
||||||
import { cn } from '@cfdm/ui/lib/utils'
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
import type { ServiceBinding } from '@/lib/schemas'
|
import type { ServiceBinding } from '@/lib/schemas'
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ export function ServiceBindingCard({
|
|||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<AppCard
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
size="sm"
|
size="sm"
|
||||||
style={style}
|
style={style}
|
||||||
@@ -60,21 +60,21 @@ export function ServiceBindingCard({
|
|||||||
{...listeners}
|
{...listeners}
|
||||||
{...attributes}
|
{...attributes}
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<AppCardHeader>
|
||||||
<CardTitle>{binding.zone_name}</CardTitle>
|
<AppCardTitle>{binding.zone_name}</AppCardTitle>
|
||||||
<CardAction>
|
<AppCardAction>
|
||||||
{binding.group_name ? (
|
{binding.group_name ? (
|
||||||
<Badge variant="secondary">{binding.group_name}</Badge>
|
<AppBadge variant="secondary">{binding.group_name}</AppBadge>
|
||||||
) : (
|
) : (
|
||||||
<Badge variant="outline">Без группы</Badge>
|
<AppBadge variant="outline">Без группы</AppBadge>
|
||||||
)}
|
)}
|
||||||
</CardAction>
|
</AppCardAction>
|
||||||
</CardHeader>
|
</AppCardHeader>
|
||||||
<CardContent>
|
<AppCardContent>
|
||||||
<FieldGroup className="flex flex-col gap-3">
|
<AppFieldGroup className="flex flex-col gap-3">
|
||||||
<Field>
|
<AppField>
|
||||||
<FieldLabel htmlFor={`hostname-${binding.id}`}>Hostname</FieldLabel>
|
<AppFieldLabel htmlFor={`hostname-${binding.id}`}>Hostname</AppFieldLabel>
|
||||||
<Input
|
<AppInput
|
||||||
id={`hostname-${binding.id}`}
|
id={`hostname-${binding.id}`}
|
||||||
value={hostname}
|
value={hostname}
|
||||||
placeholder="@"
|
placeholder="@"
|
||||||
@@ -86,10 +86,10 @@ export function ServiceBindingCard({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</AppField>
|
||||||
<Field>
|
<AppField>
|
||||||
<FieldLabel htmlFor={`ip-${binding.id}`}>IPv4</FieldLabel>
|
<AppFieldLabel htmlFor={`ip-${binding.id}`}>IPv4</AppFieldLabel>
|
||||||
<Input
|
<AppInput
|
||||||
id={`ip-${binding.id}`}
|
id={`ip-${binding.id}`}
|
||||||
value={ip}
|
value={ip}
|
||||||
placeholder="192.168.1.1"
|
placeholder="192.168.1.1"
|
||||||
@@ -101,15 +101,15 @@ export function ServiceBindingCard({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</AppField>
|
||||||
</FieldGroup>
|
</AppFieldGroup>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Badge>{binding.service_name}</Badge>
|
<AppBadge>{binding.service_name}</AppBadge>
|
||||||
{binding.sync_status && <StatusBadge status={binding.sync_status} />}
|
{binding.sync_status && <StatusBadge status={binding.sync_status} />}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</AppCardContent>
|
||||||
<CardFooter>
|
<AppCardFooter>
|
||||||
<Button
|
<AppButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
render={
|
render={
|
||||||
@@ -120,8 +120,8 @@ export function ServiceBindingCard({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
Домен
|
Домен
|
||||||
</Button>
|
</AppButton>
|
||||||
</CardFooter>
|
</AppCardFooter>
|
||||||
</Card>
|
</AppCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TaggedInput, isValidIpv4 } from '@/components/tagged-input'
|
import { TaggedInput, isValidIpv4 } from '@/components/tagged-input'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
|
|
||||||
interface ServiceBindingIpInputProps {
|
interface ServiceBindingIpInputProps {
|
||||||
id?: string
|
id?: string
|
||||||
@@ -34,7 +34,7 @@ export function ServiceBindingIpInput({
|
|||||||
{available.length > 0 ? (
|
{available.length > 0 ? (
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{available.map((ip) => (
|
{available.map((ip) => (
|
||||||
<Button
|
<AppButton
|
||||||
key={ip}
|
key={ip}
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -43,7 +43,7 @@ export function ServiceBindingIpInput({
|
|||||||
onClick={() => onChange([...value, ip])}
|
onClick={() => onChange([...value, ip])}
|
||||||
>
|
>
|
||||||
+ {ip}
|
+ {ip}
|
||||||
</Button>
|
</AppButton>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -12,20 +12,20 @@ import type {
|
|||||||
UpdateServiceConfigInput,
|
UpdateServiceConfigInput,
|
||||||
} from '@/lib/schemas'
|
} from '@/lib/schemas'
|
||||||
import { bindingToFqdn } from '@/lib/parse-fqdn'
|
import { bindingToFqdn } from '@/lib/parse-fqdn'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import { Input } from '@cfdm/ui/components/input'
|
import { AppInput } from '@/components/app-input'
|
||||||
import {
|
import {
|
||||||
Field,
|
AppField,
|
||||||
FieldGroup,
|
AppFieldGroup,
|
||||||
FieldLabel,
|
AppFieldLabel,
|
||||||
} from '@cfdm/ui/components/field'
|
} from '@/components/app-field'
|
||||||
import {
|
import {
|
||||||
Item,
|
AppItem,
|
||||||
ItemActions,
|
AppItemActions,
|
||||||
ItemContent,
|
AppItemContent,
|
||||||
ItemGroup,
|
AppItemGroup,
|
||||||
} from '@cfdm/ui/components/item'
|
} from '@/components/app-item'
|
||||||
import { Separator } from '@cfdm/ui/components/separator'
|
import { AppSeparator } from '@/components/app-separator'
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
@@ -271,27 +271,27 @@ export function ServiceEditSheet({
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="general" className="flex flex-col gap-4">
|
<TabsContent value="general" className="flex flex-col gap-4">
|
||||||
<FieldGroup className="flex flex-col gap-4">
|
<AppFieldGroup className="flex flex-col gap-4">
|
||||||
<Field>
|
<AppField>
|
||||||
<FieldLabel htmlFor="edit-service-name">Название</FieldLabel>
|
<AppFieldLabel htmlFor="edit-service-name">Название</AppFieldLabel>
|
||||||
<Input
|
<AppInput
|
||||||
id="edit-service-name"
|
id="edit-service-name"
|
||||||
value={name}
|
value={name}
|
||||||
placeholder={isCreate ? 'VPN Panel' : undefined}
|
placeholder={isCreate ? 'VPN Panel' : undefined}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</AppField>
|
||||||
<Field>
|
<AppField>
|
||||||
<FieldLabel htmlFor="edit-service-slug">Slug</FieldLabel>
|
<AppFieldLabel htmlFor="edit-service-slug">Slug</AppFieldLabel>
|
||||||
<Input
|
<AppInput
|
||||||
id="edit-service-slug"
|
id="edit-service-slug"
|
||||||
value={slug}
|
value={slug}
|
||||||
placeholder={isCreate ? 'vpn-panel' : undefined}
|
placeholder={isCreate ? 'vpn-panel' : undefined}
|
||||||
onChange={(e) => setSlug(e.target.value)}
|
onChange={(e) => setSlug(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</AppField>
|
||||||
<Field>
|
<AppField>
|
||||||
<FieldLabel htmlFor="edit-service-group">Группа сервисов</FieldLabel>
|
<AppFieldLabel htmlFor="edit-service-group">Группа сервисов</AppFieldLabel>
|
||||||
<Select
|
<Select
|
||||||
items={groupItems}
|
items={groupItems}
|
||||||
value={serviceGroupId}
|
value={serviceGroupId}
|
||||||
@@ -308,9 +308,9 @@ export function ServiceEditSheet({
|
|||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</Field>
|
</AppField>
|
||||||
<Field>
|
<AppField>
|
||||||
<FieldLabel htmlFor="edit-service-ips">IP-адреса сервиса</FieldLabel>
|
<AppFieldLabel htmlFor="edit-service-ips">IP-адреса сервиса</AppFieldLabel>
|
||||||
<TaggedInput
|
<TaggedInput
|
||||||
id="edit-service-ips"
|
id="edit-service-ips"
|
||||||
value={ips}
|
value={ips}
|
||||||
@@ -318,8 +318,8 @@ export function ServiceEditSheet({
|
|||||||
placeholder="192.168.1.1"
|
placeholder="192.168.1.1"
|
||||||
validate={isValidIpv4}
|
validate={isValidIpv4}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</AppField>
|
||||||
</FieldGroup>
|
</AppFieldGroup>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="bindings" className="flex flex-col gap-4">
|
<TabsContent value="bindings" className="flex flex-col gap-4">
|
||||||
@@ -327,10 +327,10 @@ export function ServiceEditSheet({
|
|||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
FQDN → IP или CNAME для DNS-записей Cloudflare
|
FQDN → IP или CNAME для DNS-записей Cloudflare
|
||||||
</p>
|
</p>
|
||||||
<Button type="button" variant="outline" size="sm" onClick={handleAddBinding}>
|
<AppButton type="button" variant="outline" size="sm" onClick={handleAddBinding}>
|
||||||
<PlusIcon data-icon="inline-start" />
|
<PlusIcon data-icon="inline-start" />
|
||||||
Добавить
|
Добавить
|
||||||
</Button>
|
</AppButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{bindings.length === 0 ? (
|
{bindings.length === 0 ? (
|
||||||
@@ -339,19 +339,19 @@ export function ServiceEditSheet({
|
|||||||
title="Нет привязок"
|
title="Нет привязок"
|
||||||
description="Необязательно. Пример: newdom.ivx.su — зона ivx.su определится автоматически."
|
description="Необязательно. Пример: newdom.ivx.su — зона ivx.su определится автоматически."
|
||||||
action={
|
action={
|
||||||
<Button type="button" variant="outline" size="sm" onClick={handleAddBinding}>
|
<AppButton type="button" variant="outline" size="sm" onClick={handleAddBinding}>
|
||||||
<PlusIcon data-icon="inline-start" />
|
<PlusIcon data-icon="inline-start" />
|
||||||
Добавить привязку
|
Добавить привязку
|
||||||
</Button>
|
</AppButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ItemGroup className="gap-2">
|
<AppItemGroup className="gap-2">
|
||||||
{bindings.map((binding, index) => (
|
{bindings.map((binding, index) => (
|
||||||
<Item key={`binding-${index}`} variant="outline">
|
<AppItem key={`binding-${index}`} variant="outline">
|
||||||
<ItemContent className="flex flex-col gap-3">
|
<AppItemContent className="flex flex-col gap-3">
|
||||||
<Field>
|
<AppField>
|
||||||
<FieldLabel htmlFor={`binding-fqdn-${index}`}>FQDN</FieldLabel>
|
<AppFieldLabel htmlFor={`binding-fqdn-${index}`}>FQDN</AppFieldLabel>
|
||||||
<TaggedInput
|
<TaggedInput
|
||||||
id={`binding-fqdn-${index}`}
|
id={`binding-fqdn-${index}`}
|
||||||
value={binding.fqdn ? [binding.fqdn] : []}
|
value={binding.fqdn ? [binding.fqdn] : []}
|
||||||
@@ -361,9 +361,9 @@ export function ServiceEditSheet({
|
|||||||
}
|
}
|
||||||
maxItems={1}
|
maxItems={1}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</AppField>
|
||||||
<Field>
|
<AppField>
|
||||||
<FieldLabel htmlFor={`binding-type-${index}`}>Тип записи</FieldLabel>
|
<AppFieldLabel htmlFor={`binding-type-${index}`}>Тип записи</AppFieldLabel>
|
||||||
<Select
|
<Select
|
||||||
items={[
|
items={[
|
||||||
{ label: 'A (IP)', value: 'A' },
|
{ label: 'A (IP)', value: 'A' },
|
||||||
@@ -382,33 +382,33 @@ export function ServiceEditSheet({
|
|||||||
<SelectItem value="CNAME">CNAME</SelectItem>
|
<SelectItem value="CNAME">CNAME</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</Field>
|
</AppField>
|
||||||
{binding.record_type === 'CNAME' ? (
|
{binding.record_type === 'CNAME' ? (
|
||||||
<Field>
|
<AppField>
|
||||||
<FieldLabel htmlFor={`binding-cname-${index}`}>
|
<AppFieldLabel htmlFor={`binding-cname-${index}`}>
|
||||||
CNAME-цель
|
CNAME-цель
|
||||||
</FieldLabel>
|
</AppFieldLabel>
|
||||||
<Input
|
<AppInput
|
||||||
id={`binding-cname-${index}`}
|
id={`binding-cname-${index}`}
|
||||||
value={binding.target_cname}
|
value={binding.target_cname}
|
||||||
placeholder="mmsk.rkns.top"
|
placeholder="mmsk.rkns.top"
|
||||||
onChange={(event) => handleCnameChange(index, event.target.value)}
|
onChange={(event) => handleCnameChange(index, event.target.value)}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</AppField>
|
||||||
) : (
|
) : (
|
||||||
<Field>
|
<AppField>
|
||||||
<FieldLabel htmlFor={`binding-ip-${index}`}>IP</FieldLabel>
|
<AppFieldLabel htmlFor={`binding-ip-${index}`}>IP</AppFieldLabel>
|
||||||
<ServiceBindingIpInput
|
<ServiceBindingIpInput
|
||||||
id={`binding-ip-${index}`}
|
id={`binding-ip-${index}`}
|
||||||
value={binding.target_ips}
|
value={binding.target_ips}
|
||||||
pool={ips}
|
pool={ips}
|
||||||
onChange={(targetIps) => handleIpsChange(index, targetIps)}
|
onChange={(targetIps) => handleIpsChange(index, targetIps)}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</AppField>
|
||||||
)}
|
)}
|
||||||
</ItemContent>
|
</AppItemContent>
|
||||||
<ItemActions>
|
<AppItemActions>
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
@@ -416,17 +416,17 @@ export function ServiceEditSheet({
|
|||||||
onClick={() => handleRemoveBinding(index)}
|
onClick={() => handleRemoveBinding(index)}
|
||||||
>
|
>
|
||||||
<Trash2Icon />
|
<Trash2Icon />
|
||||||
</Button>
|
</AppButton>
|
||||||
</ItemActions>
|
</AppItemActions>
|
||||||
</Item>
|
</AppItem>
|
||||||
))}
|
))}
|
||||||
</ItemGroup>
|
</AppItemGroup>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<AppSeparator />
|
||||||
|
|
||||||
<SheetFooter className="flex flex-row flex-wrap gap-2 border-t-0 pt-4">
|
<SheetFooter className="flex flex-row flex-wrap gap-2 border-t-0 pt-4">
|
||||||
{!isCreate ? (
|
{!isCreate ? (
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import type { z } from 'zod'
|
|||||||
import { FormSheet } from '@/components/form-sheet'
|
import { FormSheet } from '@/components/form-sheet'
|
||||||
import { FormFieldSimple } from '@/components/form-field'
|
import { FormFieldSimple } from '@/components/form-field'
|
||||||
import { LoadingButton } from '@/components/loading-button'
|
import { LoadingButton } from '@/components/loading-button'
|
||||||
import { FieldGroup } from '@cfdm/ui/components/field'
|
import { AppFieldGroup } from '@/components/app-field'
|
||||||
import { Input } from '@cfdm/ui/components/input'
|
import { AppInput } from '@/components/app-input'
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -101,13 +101,13 @@ export function ServiceGroupEditSheet({
|
|||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FieldGroup>
|
<AppFieldGroup>
|
||||||
<FormFieldSimple
|
<FormFieldSimple
|
||||||
label="Название"
|
label="Название"
|
||||||
htmlFor="group-name"
|
htmlFor="group-name"
|
||||||
error={form.formState.errors.name}
|
error={form.formState.errors.name}
|
||||||
>
|
>
|
||||||
<Input
|
<AppInput
|
||||||
id="group-name"
|
id="group-name"
|
||||||
placeholder="VPN"
|
placeholder="VPN"
|
||||||
{...form.register('name')}
|
{...form.register('name')}
|
||||||
@@ -143,13 +143,13 @@ export function ServiceGroupEditSheet({
|
|||||||
label="Домен группы (FQDN, необязательно)"
|
label="Домен группы (FQDN, необязательно)"
|
||||||
htmlFor="group-domain"
|
htmlFor="group-domain"
|
||||||
>
|
>
|
||||||
<Input
|
<AppInput
|
||||||
id="group-domain"
|
id="group-domain"
|
||||||
placeholder="domain.new.ivx.su"
|
placeholder="domain.new.ivx.su"
|
||||||
{...form.register('domain')}
|
{...form.register('domain')}
|
||||||
/>
|
/>
|
||||||
</FormFieldSimple>
|
</FormFieldSimple>
|
||||||
</FieldGroup>
|
</AppFieldGroup>
|
||||||
</FormSheet>
|
</FormSheet>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { MoreHorizontalIcon, PencilIcon, Trash2Icon } from 'lucide-react'
|
|||||||
import { ServiceGroupIcon } from '@/components/service-group-icon'
|
import { ServiceGroupIcon } from '@/components/service-group-icon'
|
||||||
import type { BoardColumn } from '@/components/services-board/types'
|
import type { BoardColumn } from '@/components/services-board/types'
|
||||||
import type { ServiceGroupView } from '@/lib/schemas'
|
import type { ServiceGroupView } from '@/lib/schemas'
|
||||||
import { AccordionTrigger } from '@cfdm/ui/components/accordion'
|
import { AppAccordionTrigger } from '@/components/app-accordion'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import { Checkbox } from '@cfdm/ui/components/checkbox'
|
import { AppCheckbox } from '@/components/app-checkbox'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@cfdm/ui/components/dropdown-menu'
|
} from '@cfdm/ui/components/dropdown-menu'
|
||||||
import { Spinner } from '@cfdm/ui/components/spinner'
|
import { AppSpinner } from '@/components/app-spinner'
|
||||||
import { Switch } from '@cfdm/ui/components/switch'
|
import { AppSwitch } from '@/components/app-switch'
|
||||||
import { cn } from '@cfdm/ui/lib/utils'
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
interface ServiceGroupHeaderProps {
|
interface ServiceGroupHeaderProps {
|
||||||
@@ -53,7 +53,7 @@ export function ServiceGroupHeader({
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1 px-1">
|
<div className="flex items-center gap-1 px-1">
|
||||||
{showCheckbox && !dragDisabled && column.items.length > 0 ? (
|
{showCheckbox && !dragDisabled && column.items.length > 0 ? (
|
||||||
<Checkbox
|
<AppCheckbox
|
||||||
checked={groupAllSelected}
|
checked={groupAllSelected}
|
||||||
onCheckedChange={() => {
|
onCheckedChange={() => {
|
||||||
if (groupAllSelected || groupSomeSelected) {
|
if (groupAllSelected || groupSomeSelected) {
|
||||||
@@ -71,7 +71,7 @@ export function ServiceGroupHeader({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<AccordionTrigger className="min-h-10 flex-1 items-center gap-2 rounded-md py-2 hover:bg-muted/40 hover:no-underline">
|
<AppAccordionTrigger className="min-h-10 flex-1 items-center gap-2 rounded-md py-2 hover:bg-muted/40 hover:no-underline">
|
||||||
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">
|
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">
|
||||||
{column.groupId !== null ? (
|
{column.groupId !== null ? (
|
||||||
<span className="flex size-7 shrink-0 items-center justify-center rounded-md bg-background/80 text-muted-foreground">
|
<span className="flex size-7 shrink-0 items-center justify-center rounded-md bg-background/80 text-muted-foreground">
|
||||||
@@ -79,17 +79,17 @@ export function ServiceGroupHeader({
|
|||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="font-medium">{column.title}</span>
|
<span className="font-medium">{column.title}</span>
|
||||||
<Badge variant="secondary">{column.items.length}</Badge>
|
<AppBadge variant="secondary">{column.items.length}</AppBadge>
|
||||||
{column.domain ? (
|
{column.domain ? (
|
||||||
<Badge variant="outline">{column.domain}</Badge>
|
<AppBadge variant="outline">{column.domain}</AppBadge>
|
||||||
) : null}
|
) : null}
|
||||||
{!isOpen && isDragging && !dragDisabled ? (
|
{!isOpen && isDragging && !dragDisabled ? (
|
||||||
<Badge variant="default" className="font-normal">
|
<AppBadge variant="default" className="font-normal">
|
||||||
Отпустите для переноса
|
Отпустите для переноса
|
||||||
</Badge>
|
</AppBadge>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AppAccordionTrigger>
|
||||||
|
|
||||||
{column.groupId !== null ? (
|
{column.groupId !== null ? (
|
||||||
<div
|
<div
|
||||||
@@ -100,7 +100,7 @@ export function ServiceGroupHeader({
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
render={
|
render={
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
@@ -133,9 +133,9 @@ export function ServiceGroupHeader({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
{column.domain && onGroupToggle ? (
|
{column.domain && onGroupToggle ? (
|
||||||
isGroupToggling ? (
|
isGroupToggling ? (
|
||||||
<Spinner className="size-4" />
|
<AppSpinner className="size-4" />
|
||||||
) : (
|
) : (
|
||||||
<Switch
|
<AppSwitch
|
||||||
checked={column.enabled}
|
checked={column.enabled}
|
||||||
disabled={isGroupToggling}
|
disabled={isGroupToggling}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import { ServiceRow } from '@/components/services-board/service-row'
|
|||||||
import type { BoardColumn } from '@/components/services-board/types'
|
import type { BoardColumn } from '@/components/services-board/types'
|
||||||
import type { ServiceGroupView, ServiceView } from '@/lib/schemas'
|
import type { ServiceGroupView, ServiceView } from '@/lib/schemas'
|
||||||
import {
|
import {
|
||||||
AccordionContent,
|
AppAccordionContent,
|
||||||
AccordionItem,
|
AppAccordionItem,
|
||||||
} from '@cfdm/ui/components/accordion'
|
} from '@/components/app-accordion'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
Empty,
|
Empty,
|
||||||
EmptyContent,
|
EmptyContent,
|
||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
EmptyHeader,
|
EmptyHeader,
|
||||||
EmptyTitle,
|
EmptyTitle,
|
||||||
} from '@cfdm/ui/components/empty'
|
} from '@cfdm/ui/components/empty'
|
||||||
import { ItemGroup, ItemSeparator } from '@cfdm/ui/components/item'
|
import { AppItemGroup, AppItemSeparator } from '@/components/app-item'
|
||||||
import { cn } from '@cfdm/ui/lib/utils'
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
|
||||||
interface ServiceGroupItemProps {
|
interface ServiceGroupItemProps {
|
||||||
@@ -88,7 +88,7 @@ export function ServiceGroupItem({
|
|||||||
}, [isOver, isDragging, dragDisabled, column.id, onExpandColumn])
|
}, [isOver, isDragging, dragDisabled, column.id, onExpandColumn])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccordionItem
|
<AppAccordionItem
|
||||||
value={column.id}
|
value={column.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
'not-last:border-b-0 overflow-hidden rounded-lg border border-border transition-colors',
|
'not-last:border-b-0 overflow-hidden rounded-lg border border-border transition-colors',
|
||||||
@@ -114,17 +114,17 @@ export function ServiceGroupItem({
|
|||||||
onDeleteGroup={onDeleteGroup}
|
onDeleteGroup={onDeleteGroup}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AccordionContent className="px-1 pb-2">
|
<AppAccordionContent className="px-1 pb-2">
|
||||||
<div ref={setNodeRef}>
|
<div ref={setNodeRef}>
|
||||||
<SortableContext
|
<SortableContext
|
||||||
items={column.items.map((service) => String(service.id))}
|
items={column.items.map((service) => String(service.id))}
|
||||||
strategy={verticalListSortingStrategy}
|
strategy={verticalListSortingStrategy}
|
||||||
>
|
>
|
||||||
{column.items.length > 0 ? (
|
{column.items.length > 0 ? (
|
||||||
<ItemGroup className="gap-0 py-1">
|
<AppItemGroup className="gap-0 py-1">
|
||||||
{column.items.map((service, index) => (
|
{column.items.map((service, index) => (
|
||||||
<div key={service.id}>
|
<div key={service.id}>
|
||||||
{index > 0 ? <ItemSeparator className="my-0" /> : null}
|
{index > 0 ? <AppItemSeparator className="my-0" /> : null}
|
||||||
<ServiceRow
|
<ServiceRow
|
||||||
service={service}
|
service={service}
|
||||||
onToggle={onServiceToggle}
|
onToggle={onServiceToggle}
|
||||||
@@ -141,7 +141,7 @@ export function ServiceGroupItem({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</ItemGroup>
|
</AppItemGroup>
|
||||||
) : (
|
) : (
|
||||||
<Empty
|
<Empty
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -159,7 +159,7 @@ export function ServiceGroupItem({
|
|||||||
</EmptyHeader>
|
</EmptyHeader>
|
||||||
{onAddService ? (
|
{onAddService ? (
|
||||||
<EmptyContent>
|
<EmptyContent>
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="link"
|
variant="link"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -167,7 +167,7 @@ export function ServiceGroupItem({
|
|||||||
>
|
>
|
||||||
<PlusIcon data-icon="inline-start" />
|
<PlusIcon data-icon="inline-start" />
|
||||||
Добавить сервис
|
Добавить сервис
|
||||||
</Button>
|
</AppButton>
|
||||||
</EmptyContent>
|
</EmptyContent>
|
||||||
) : null}
|
) : null}
|
||||||
</Empty>
|
</Empty>
|
||||||
@@ -175,7 +175,7 @@ export function ServiceGroupItem({
|
|||||||
</SortableContext>
|
</SortableContext>
|
||||||
|
|
||||||
{column.items.length > 0 && onAddService ? (
|
{column.items.length > 0 && onAddService ? (
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -184,10 +184,10 @@ export function ServiceGroupItem({
|
|||||||
>
|
>
|
||||||
<PlusIcon data-icon="inline-start" />
|
<PlusIcon data-icon="inline-start" />
|
||||||
Добавить сервис
|
Добавить сервис
|
||||||
</Button>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</AccordionContent>
|
</AppAccordionContent>
|
||||||
</AccordionItem>
|
</AppAccordionItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import {
|
|||||||
serviceDisplayFqdn,
|
serviceDisplayFqdn,
|
||||||
} from '@/lib/service-utils'
|
} from '@/lib/service-utils'
|
||||||
import type { ServiceView } from '@/lib/schemas'
|
import type { ServiceView } from '@/lib/schemas'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import { Checkbox } from '@cfdm/ui/components/checkbox'
|
import { AppCheckbox } from '@/components/app-checkbox'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -19,8 +19,8 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@cfdm/ui/components/dropdown-menu'
|
} from '@cfdm/ui/components/dropdown-menu'
|
||||||
import { Spinner } from '@cfdm/ui/components/spinner'
|
import { AppSpinner } from '@/components/app-spinner'
|
||||||
import { Switch } from '@cfdm/ui/components/switch'
|
import { AppSwitch } from '@/components/app-switch'
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -112,7 +112,7 @@ export const ServiceRow = memo(function ServiceRow({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{showCheckbox && !dragDisabled && !overlay ? (
|
{showCheckbox && !dragDisabled && !overlay ? (
|
||||||
<Checkbox
|
<AppCheckbox
|
||||||
checked={selected}
|
checked={selected}
|
||||||
onCheckedChange={(checked) => onSelectedChange?.(checked === true)}
|
onCheckedChange={(checked) => onSelectedChange?.(checked === true)}
|
||||||
onPointerDown={(event) => event.stopPropagation()}
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
@@ -121,7 +121,7 @@ export const ServiceRow = memo(function ServiceRow({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!dragDisabled && !overlay ? (
|
{!dragDisabled && !overlay ? (
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
@@ -131,7 +131,7 @@ export const ServiceRow = memo(function ServiceRow({
|
|||||||
{...attributes}
|
{...attributes}
|
||||||
>
|
>
|
||||||
<GripVerticalIcon />
|
<GripVerticalIcon />
|
||||||
</Button>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className="flex min-w-0 shrink-0 items-center gap-2">
|
<div className="flex min-w-0 shrink-0 items-center gap-2">
|
||||||
@@ -143,9 +143,9 @@ export const ServiceRow = memo(function ServiceRow({
|
|||||||
>
|
>
|
||||||
{service.name}
|
{service.name}
|
||||||
</span>
|
</span>
|
||||||
<Badge variant="secondary" className="shrink-0 font-mono tabular-nums">
|
<AppBadge variant="secondary" className="shrink-0 font-mono tabular-nums">
|
||||||
{service.slug}
|
{service.slug}
|
||||||
</Badge>
|
</AppBadge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="min-w-0 flex-1 truncate text-sm text-muted-foreground">
|
<div className="min-w-0 flex-1 truncate text-sm text-muted-foreground">
|
||||||
@@ -155,9 +155,9 @@ export const ServiceRow = memo(function ServiceRow({
|
|||||||
<div className="flex shrink-0 items-center gap-2">
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
{syncStatus ? <StatusBadge status={syncStatus} /> : null}
|
{syncStatus ? <StatusBadge status={syncStatus} /> : null}
|
||||||
{isToggling ? (
|
{isToggling ? (
|
||||||
<Spinner className="size-4" />
|
<AppSpinner className="size-4" />
|
||||||
) : (
|
) : (
|
||||||
<Switch
|
<AppSwitch
|
||||||
checked={service.enabled}
|
checked={service.enabled}
|
||||||
disabled={disabled || isToggling}
|
disabled={disabled || isToggling}
|
||||||
onPointerDown={(event) => event.stopPropagation()}
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
@@ -167,15 +167,15 @@ export const ServiceRow = memo(function ServiceRow({
|
|||||||
)}
|
)}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
render={
|
render={
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
aria-label={`Действия для ${service.name}`}
|
aria-label={`Действия для ${service.name}`}
|
||||||
onPointerDown={(event) => event.stopPropagation()}
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon />
|
<MoreHorizontalIcon />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ServiceGroupItem } from '@/components/services-board/service-group-item
|
|||||||
import { ServiceRow } from '@/components/services-board/service-row'
|
import { ServiceRow } from '@/components/services-board/service-row'
|
||||||
import type { BoardState } from '@/components/services-board/types'
|
import type { BoardState } from '@/components/services-board/types'
|
||||||
import type { ServiceGroupView, ServiceView } from '@/lib/schemas'
|
import type { ServiceGroupView, ServiceView } from '@/lib/schemas'
|
||||||
import { Accordion } from '@cfdm/ui/components/accordion'
|
import { AppAccordion } from '@/components/app-accordion'
|
||||||
|
|
||||||
interface ServicesBoardProps {
|
interface ServicesBoardProps {
|
||||||
board: BoardState
|
board: BoardState
|
||||||
@@ -98,7 +98,7 @@ export function ServicesBoard({
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Accordion
|
<AppAccordion
|
||||||
multiple
|
multiple
|
||||||
value={openColumns}
|
value={openColumns}
|
||||||
onValueChange={setOpenColumns}
|
onValueChange={setOpenColumns}
|
||||||
@@ -130,7 +130,7 @@ export function ServicesBoard({
|
|||||||
onDeselectAllInGroup={onDeselectAllInGroup}
|
onDeselectAllInGroup={onDeselectAllInGroup}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Accordion>
|
</AppAccordion>
|
||||||
</DragContextProvider>
|
</DragContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
|
|
||||||
interface ServicesBulkToolbarProps {
|
interface ServicesBulkToolbarProps {
|
||||||
count: number
|
count: number
|
||||||
@@ -22,7 +22,7 @@ export function ServicesBulkToolbar({
|
|||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
Выбрано: {count}
|
Выбрано: {count}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -30,8 +30,8 @@ export function ServicesBulkToolbar({
|
|||||||
onClick={onEnable}
|
onClick={onEnable}
|
||||||
>
|
>
|
||||||
Включить
|
Включить
|
||||||
</Button>
|
</AppButton>
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -39,8 +39,8 @@ export function ServicesBulkToolbar({
|
|||||||
onClick={onDisable}
|
onClick={onDisable}
|
||||||
>
|
>
|
||||||
Выключить
|
Выключить
|
||||||
</Button>
|
</AppButton>
|
||||||
<Button
|
<AppButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -48,7 +48,7 @@ export function ServicesBulkToolbar({
|
|||||||
onClick={onClear}
|
onClick={onClear}
|
||||||
>
|
>
|
||||||
Снять выделение
|
Снять выделение
|
||||||
</Button>
|
</AppButton>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { certMonitoringOptions } from '@/lib/cert-monitoring'
|
|||||||
import { formatServiceGroupLabel } from '@/lib/service-utils'
|
import { formatServiceGroupLabel } from '@/lib/service-utils'
|
||||||
import { FormFieldSimple } from '@/components/form-field'
|
import { FormFieldSimple } from '@/components/form-field'
|
||||||
import { LoadingButton } from '@/components/loading-button'
|
import { LoadingButton } from '@/components/loading-button'
|
||||||
import { Input } from '@cfdm/ui/components/input'
|
import { AppInput } from '@/components/app-input'
|
||||||
import { FieldDescription, FieldGroup } from '@cfdm/ui/components/field'
|
import { AppFieldDescription, AppFieldGroup } from '@/components/app-field'
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -115,9 +115,9 @@ export function SubdomainEditSheet({
|
|||||||
</SheetDescription>
|
</SheetDescription>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4 px-4">
|
<form onSubmit={handleSubmit} className="flex flex-col gap-4 px-4">
|
||||||
<FieldGroup>
|
<AppFieldGroup>
|
||||||
<FormFieldSimple label="Имя" htmlFor="subdomain_name">
|
<FormFieldSimple label="Имя" htmlFor="subdomain_name">
|
||||||
<Input
|
<AppInput
|
||||||
id="subdomain_name"
|
id="subdomain_name"
|
||||||
placeholder="www"
|
placeholder="www"
|
||||||
value={name}
|
value={name}
|
||||||
@@ -168,16 +168,16 @@ export function SubdomainEditSheet({
|
|||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FieldDescription>
|
<AppFieldDescription>
|
||||||
{
|
{
|
||||||
certMonitoringOptions.find((o) => o.value === certMonitoring)
|
certMonitoringOptions.find((o) => o.value === certMonitoring)
|
||||||
?.description
|
?.description
|
||||||
}
|
}
|
||||||
</FieldDescription>
|
</AppFieldDescription>
|
||||||
</FormFieldSimple>
|
</FormFieldSimple>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</FieldGroup>
|
</AppFieldGroup>
|
||||||
<SheetFooter>
|
<SheetFooter>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { TableCard } from '@/components/table-card'
|
|||||||
import type { SubdomainTableRow } from '@/hooks/use-domain-page'
|
import type { SubdomainTableRow } from '@/hooks/use-domain-page'
|
||||||
import { formatSubdomainServiceLinks } from '@/hooks/use-domain-page'
|
import { formatSubdomainServiceLinks } from '@/hooks/use-domain-page'
|
||||||
import { formatDate } from '@/lib/format'
|
import { formatDate } from '@/lib/format'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -65,9 +65,9 @@ export function SubdomainsTable({
|
|||||||
<TableRow key={row.subdomain.id} className="h-10 text-sm">
|
<TableRow key={row.subdomain.id} className="h-10 text-sm">
|
||||||
<TableCell className="font-mono">{row.subdomain.fqdn}</TableCell>
|
<TableCell className="font-mono">{row.subdomain.fqdn}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge variant={row.subdomain.enabled ? 'default' : 'outline'}>
|
<AppBadge variant={row.subdomain.enabled ? 'default' : 'outline'}>
|
||||||
{row.subdomain.enabled ? 'Активен' : 'Неактивен'}
|
{row.subdomain.enabled ? 'Активен' : 'Неактивен'}
|
||||||
</Badge>
|
</AppBadge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{formatSubdomainServiceLinks(row.serviceLinks)}</TableCell>
|
<TableCell>{formatSubdomainServiceLinks(row.serviceLinks)}</TableCell>
|
||||||
<TableCell>{formatDate(row.subdomain.created_at)}</TableCell>
|
<TableCell>{formatDate(row.subdomain.created_at)}</TableCell>
|
||||||
@@ -75,7 +75,7 @@ export function SubdomainsTable({
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
render={
|
render={
|
||||||
<Button variant="outline" size="icon" className="size-8" />
|
<AppButton variant="outline" size="icon" className="size-8" />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon />
|
<MoreHorizontalIcon />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { XIcon } from 'lucide-react'
|
import { XIcon } from 'lucide-react'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import {
|
import {
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputGroupButton,
|
InputGroupButton,
|
||||||
@@ -78,7 +78,7 @@ export function TaggedInput({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{value.map((tag) => (
|
{value.map((tag) => (
|
||||||
<Badge key={tag} variant="secondary" className="gap-1 pr-1">
|
<AppBadge key={tag} variant="secondary" className="gap-1 pr-1">
|
||||||
{tag}
|
{tag}
|
||||||
<InputGroupButton
|
<InputGroupButton
|
||||||
type="button"
|
type="button"
|
||||||
@@ -90,7 +90,7 @@ export function TaggedInput({
|
|||||||
>
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
</InputGroupButton>
|
</InputGroupButton>
|
||||||
</Badge>
|
</AppBadge>
|
||||||
))}
|
))}
|
||||||
<InputGroupInput
|
<InputGroupInput
|
||||||
id={id}
|
id={id}
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ import { EmptyState } from '@/components/empty-state'
|
|||||||
import { StatusBadge } from '@/components/status-badge'
|
import { StatusBadge } from '@/components/status-badge'
|
||||||
import { DataTableCard } from '@/components/data-table-card'
|
import { DataTableCard } from '@/components/data-table-card'
|
||||||
import { TableToolbar } from '@/components/table-toolbar'
|
import { TableToolbar } from '@/components/table-toolbar'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
Card,
|
AppCard,
|
||||||
CardContent,
|
AppCardContent,
|
||||||
CardDescription,
|
AppCardDescription,
|
||||||
CardHeader,
|
AppCardHeader,
|
||||||
CardTitle,
|
AppCardTitle,
|
||||||
} from '@cfdm/ui/components/card'
|
} from '@/components/app-card'
|
||||||
import {
|
import {
|
||||||
ChartContainer,
|
ChartContainer,
|
||||||
ChartTooltip,
|
ChartTooltip,
|
||||||
@@ -116,12 +116,12 @@ function CertificatesPage() {
|
|||||||
onRetry={refetch}
|
onRetry={refetch}
|
||||||
skeleton={<TableSkeleton rows={5} columns={4} />}
|
skeleton={<TableSkeleton rows={5} columns={4} />}
|
||||||
>
|
>
|
||||||
<Card>
|
<AppCard>
|
||||||
<CardHeader>
|
<AppCardHeader>
|
||||||
<CardTitle>Обзор статусов</CardTitle>
|
<AppCardTitle>Обзор статусов</AppCardTitle>
|
||||||
<CardDescription>Распределение сертификатов по статусам</CardDescription>
|
<AppCardDescription>Распределение сертификатов по статусам</AppCardDescription>
|
||||||
</CardHeader>
|
</AppCardHeader>
|
||||||
<CardContent>
|
<AppCardContent>
|
||||||
{chartData.length > 0 ? (
|
{chartData.length > 0 ? (
|
||||||
<ChartContainer config={chartConfig} className="aspect-auto h-64 w-full">
|
<ChartContainer config={chartConfig} className="aspect-auto h-64 w-full">
|
||||||
<BarChart data={chartData}>
|
<BarChart data={chartData}>
|
||||||
@@ -143,8 +143,8 @@ function CertificatesPage() {
|
|||||||
description="Запустите проверку сертификатов"
|
description="Запустите проверку сертификатов"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</AppCardContent>
|
||||||
</Card>
|
</AppCard>
|
||||||
<DataTableCard
|
<DataTableCard
|
||||||
title="Сертификаты"
|
title="Сертификаты"
|
||||||
description="Хосты с активными сервисами или ручным мониторингом"
|
description="Хосты с активными сервисами или ручным мониторингом"
|
||||||
@@ -159,9 +159,9 @@ function CertificatesPage() {
|
|||||||
}
|
}
|
||||||
emptyAction={
|
emptyAction={
|
||||||
isFilteredEmpty ? (
|
isFilteredEmpty ? (
|
||||||
<Button variant="outline" onClick={() => setSearchQuery('')}>
|
<AppButton variant="outline" onClick={() => setSearchQuery('')}>
|
||||||
Сбросить фильтр
|
Сбросить фильтр
|
||||||
</Button>
|
</AppButton>
|
||||||
) : (
|
) : (
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
onClick={() => checkMutation.mutate()}
|
onClick={() => checkMutation.mutate()}
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import { LoadingButton } from '@/components/loading-button'
|
|||||||
import { TableSkeleton } from '@/components/table-skeleton'
|
import { TableSkeleton } from '@/components/table-skeleton'
|
||||||
import { TableToolbar } from '@/components/table-toolbar'
|
import { TableToolbar } from '@/components/table-toolbar'
|
||||||
import { DnsRecordsTable } from '@/components/dns-records-table'
|
import { DnsRecordsTable } from '@/components/dns-records-table'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import { Input } from '@cfdm/ui/components/input'
|
import { AppInput } from '@/components/app-input'
|
||||||
import { Field, FieldGroup, FieldLabel } from '@cfdm/ui/components/field'
|
import { AppField, AppFieldGroup, AppFieldLabel } from '@/components/app-field'
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@cfdm/ui/components/select'
|
} from '@cfdm/ui/components/select'
|
||||||
import { Switch } from '@cfdm/ui/components/switch'
|
import { AppSwitch } from '@/components/app-switch'
|
||||||
|
|
||||||
const DNS_TYPES = ['A', 'AAAA', 'CNAME', 'TXT', 'MX'] as const
|
const DNS_TYPES = ['A', 'AAAA', 'CNAME', 'TXT', 'MX'] as const
|
||||||
|
|
||||||
@@ -158,9 +158,9 @@ function DnsPage() {
|
|||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<Button variant="outline" onClick={() => setSheetOpen(true)}>
|
<AppButton variant="outline" onClick={() => setSheetOpen(true)}>
|
||||||
Новая запись
|
Новая запись
|
||||||
</Button>
|
</AppButton>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
onClick={() => syncMutation.mutate()}
|
onClick={() => syncMutation.mutate()}
|
||||||
isLoading={syncMutation.isPending}
|
isLoading={syncMutation.isPending}
|
||||||
@@ -197,11 +197,11 @@ function DnsPage() {
|
|||||||
isEmpty={!hasRecords}
|
isEmpty={!hasRecords}
|
||||||
emptyAction={
|
emptyAction={
|
||||||
isFilteredEmpty ? (
|
isFilteredEmpty ? (
|
||||||
<Button variant="outline" onClick={() => setSearchQuery('')}>
|
<AppButton variant="outline" onClick={() => setSearchQuery('')}>
|
||||||
Сбросить фильтр
|
Сбросить фильтр
|
||||||
</Button>
|
</AppButton>
|
||||||
) : (
|
) : (
|
||||||
<Button onClick={() => setSheetOpen(true)}>Новая запись</Button>
|
<AppButton onClick={() => setSheetOpen(true)}>Новая запись</AppButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
toolbar={
|
toolbar={
|
||||||
@@ -244,7 +244,7 @@ function DnsPage() {
|
|||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FieldGroup>
|
<AppFieldGroup>
|
||||||
<FormFieldSimple label="Тип" htmlFor="record_type">
|
<FormFieldSimple label="Тип" htmlFor="record_type">
|
||||||
<Controller
|
<Controller
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -274,7 +274,7 @@ function DnsPage() {
|
|||||||
htmlFor="name"
|
htmlFor="name"
|
||||||
error={form.formState.errors.name}
|
error={form.formState.errors.name}
|
||||||
>
|
>
|
||||||
<Input
|
<AppInput
|
||||||
id="name"
|
id="name"
|
||||||
placeholder="@"
|
placeholder="@"
|
||||||
{...form.register('name')}
|
{...form.register('name')}
|
||||||
@@ -286,7 +286,7 @@ function DnsPage() {
|
|||||||
htmlFor="content"
|
htmlFor="content"
|
||||||
error={form.formState.errors.content}
|
error={form.formState.errors.content}
|
||||||
>
|
>
|
||||||
<Input
|
<AppInput
|
||||||
id="content"
|
id="content"
|
||||||
placeholder="192.168.1.1"
|
placeholder="192.168.1.1"
|
||||||
{...form.register('content')}
|
{...form.register('content')}
|
||||||
@@ -298,28 +298,28 @@ function DnsPage() {
|
|||||||
htmlFor="ttl"
|
htmlFor="ttl"
|
||||||
error={form.formState.errors.ttl}
|
error={form.formState.errors.ttl}
|
||||||
>
|
>
|
||||||
<Input
|
<AppInput
|
||||||
id="ttl"
|
id="ttl"
|
||||||
type="number"
|
type="number"
|
||||||
{...form.register('ttl', { valueAsNumber: true })}
|
{...form.register('ttl', { valueAsNumber: true })}
|
||||||
aria-invalid={!!form.formState.errors.ttl}
|
aria-invalid={!!form.formState.errors.ttl}
|
||||||
/>
|
/>
|
||||||
</FormFieldSimple>
|
</FormFieldSimple>
|
||||||
<Field className="flex flex-row items-center justify-between gap-4">
|
<AppField className="flex flex-row items-center justify-between gap-4">
|
||||||
<FieldLabel htmlFor="proxied">Прокси Cloudflare</FieldLabel>
|
<AppFieldLabel htmlFor="proxied">Прокси Cloudflare</AppFieldLabel>
|
||||||
<Controller
|
<Controller
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="proxied"
|
name="proxied"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Switch
|
<AppSwitch
|
||||||
id="proxied"
|
id="proxied"
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</AppField>
|
||||||
</FieldGroup>
|
</AppFieldGroup>
|
||||||
</FormSheet>
|
</FormSheet>
|
||||||
</PageShell>
|
</PageShell>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ import {
|
|||||||
} from '@/components/subdomain-edit-sheet'
|
} from '@/components/subdomain-edit-sheet'
|
||||||
import { StatusBadge } from '@/components/status-badge'
|
import { StatusBadge } from '@/components/status-badge'
|
||||||
import { formatDate } from '@/lib/format'
|
import { formatDate } from '@/lib/format'
|
||||||
import { Badge } from '@cfdm/ui/components/badge'
|
import { AppBadge } from '@/components/app-badge'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
Card,
|
AppCard,
|
||||||
CardContent,
|
AppCardContent,
|
||||||
CardDescription,
|
AppCardDescription,
|
||||||
CardHeader,
|
AppCardHeader,
|
||||||
CardTitle,
|
AppCardTitle,
|
||||||
} from '@cfdm/ui/components/card'
|
} from '@/components/app-card'
|
||||||
import { LoadingButton } from '@/components/loading-button'
|
import { LoadingButton } from '@/components/loading-button'
|
||||||
import { TableSkeleton } from '@/components/table-skeleton'
|
import { TableSkeleton } from '@/components/table-skeleton'
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ function DomainOverviewPage() {
|
|||||||
>
|
>
|
||||||
Синхронизировать
|
Синхронизировать
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
<Button
|
<AppButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
render={
|
render={
|
||||||
@@ -186,7 +186,7 @@ function DomainOverviewPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
DNS-записи
|
DNS-записи
|
||||||
</Button>
|
</AppButton>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -209,12 +209,12 @@ function DomainOverviewPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<Card>
|
<AppCard>
|
||||||
<CardHeader>
|
<AppCardHeader>
|
||||||
<CardTitle>Сводка</CardTitle>
|
<AppCardTitle>Сводка</AppCardTitle>
|
||||||
<CardDescription>Основные параметры зоны</CardDescription>
|
<AppCardDescription>Основные параметры зоны</AppCardDescription>
|
||||||
</CardHeader>
|
</AppCardHeader>
|
||||||
<CardContent className="flex flex-col gap-3 text-sm">
|
<AppCardContent className="flex flex-col gap-3 text-sm">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<span className="text-muted-foreground">Статус</span>
|
<span className="text-muted-foreground">Статус</span>
|
||||||
<StatusBadge status={domain.status} />
|
<StatusBadge status={domain.status} />
|
||||||
@@ -222,7 +222,7 @@ function DomainOverviewPage() {
|
|||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<span className="text-muted-foreground">Группа</span>
|
<span className="text-muted-foreground">Группа</span>
|
||||||
{domain.group_id ? (
|
{domain.group_id ? (
|
||||||
<Button
|
<AppButton
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0"
|
className="h-auto p-0"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
@@ -234,9 +234,9 @@ function DomainOverviewPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
Открыть группу
|
Открыть группу
|
||||||
</Button>
|
</AppButton>
|
||||||
) : (
|
) : (
|
||||||
<Badge variant="outline">Без группы</Badge>
|
<AppBadge variant="outline">Без группы</AppBadge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
@@ -247,8 +247,8 @@ function DomainOverviewPage() {
|
|||||||
{formatDate(domain.last_synced_at)}
|
{formatDate(domain.last_synced_at)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</AppCardContent>
|
||||||
</Card>
|
</AppCard>
|
||||||
<DomainBindingsCard bindings={bindings} />
|
<DomainBindingsCard bindings={bindings} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -261,14 +261,14 @@ function DomainOverviewPage() {
|
|||||||
emptyIcon={GlobeIcon}
|
emptyIcon={GlobeIcon}
|
||||||
emptyAction={
|
emptyAction={
|
||||||
<div className="flex flex-wrap justify-center gap-2">
|
<div className="flex flex-wrap justify-center gap-2">
|
||||||
<Button onClick={openCreateSheet}>Создать поддомен</Button>
|
<AppButton onClick={openCreateSheet}>Создать поддомен</AppButton>
|
||||||
<Button
|
<AppButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => syncMutation.mutate()}
|
onClick={() => syncMutation.mutate()}
|
||||||
disabled={syncMutation.isPending}
|
disabled={syncMutation.isPending}
|
||||||
>
|
>
|
||||||
Синхронизировать
|
Синхронизировать
|
||||||
</Button>
|
</AppButton>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
toolbar={<DomainActionsBar onCreateSubdomain={openCreateSheet} />}
|
toolbar={<DomainActionsBar onCreateSubdomain={openCreateSheet} />}
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ import { FormSheet } from '@/components/form-sheet'
|
|||||||
import { FormFieldSimple } from '@/components/form-field'
|
import { FormFieldSimple } from '@/components/form-field'
|
||||||
import { LoadingButton } from '@/components/loading-button'
|
import { LoadingButton } from '@/components/loading-button'
|
||||||
import { TableSkeleton } from '@/components/table-skeleton'
|
import { TableSkeleton } from '@/components/table-skeleton'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import { Input } from '@cfdm/ui/components/input'
|
import { AppInput } from '@/components/app-input'
|
||||||
import { FieldGroup } from '@cfdm/ui/components/field'
|
import { AppFieldGroup } from '@/components/app-field'
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -145,10 +145,10 @@ function DomainsPage() {
|
|||||||
description={`${tableData.length} зон · ${tableData.reduce((sum, d) => sum + d.service_count, 0)} привязок сервисов`}
|
description={`${tableData.length} зон · ${tableData.reduce((sum, d) => sum + d.service_count, 0)} привязок сервисов`}
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<Button variant="outline" nativeButton={false} render={<Link to="/groups" />}>
|
<AppButton variant="outline" nativeButton={false} render={<Link to="/groups" />}>
|
||||||
Канбан групп
|
Канбан групп
|
||||||
</Button>
|
</AppButton>
|
||||||
<Button onClick={() => setSheetOpen(true)}>Импортировать домен</Button>
|
<AppButton onClick={() => setSheetOpen(true)}>Импортировать домен</AppButton>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -175,11 +175,11 @@ function DomainsPage() {
|
|||||||
emptyIcon={GlobeIcon}
|
emptyIcon={GlobeIcon}
|
||||||
emptyAction={
|
emptyAction={
|
||||||
isZeroResults ? (
|
isZeroResults ? (
|
||||||
<Button variant="outline" onClick={() => setFilterGroupId('')}>
|
<AppButton variant="outline" onClick={() => setFilterGroupId('')}>
|
||||||
Сбросить фильтр
|
Сбросить фильтр
|
||||||
</Button>
|
</AppButton>
|
||||||
) : (
|
) : (
|
||||||
<Button onClick={() => setSheetOpen(true)}>Импортировать домен</Button>
|
<AppButton onClick={() => setSheetOpen(true)}>Импортировать домен</AppButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -214,13 +214,13 @@ function DomainsPage() {
|
|||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FieldGroup>
|
<AppFieldGroup>
|
||||||
<FormFieldSimple
|
<FormFieldSimple
|
||||||
label="Имя зоны"
|
label="Имя зоны"
|
||||||
htmlFor="zone_name"
|
htmlFor="zone_name"
|
||||||
error={form.formState.errors.zone_name}
|
error={form.formState.errors.zone_name}
|
||||||
>
|
>
|
||||||
<Input
|
<AppInput
|
||||||
id="zone_name"
|
id="zone_name"
|
||||||
placeholder="example.com"
|
placeholder="example.com"
|
||||||
{...form.register('zone_name')}
|
{...form.register('zone_name')}
|
||||||
@@ -245,7 +245,7 @@ function DomainsPage() {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</FormFieldSimple>
|
</FormFieldSimple>
|
||||||
</FieldGroup>
|
</AppFieldGroup>
|
||||||
</FormSheet>
|
</FormSheet>
|
||||||
</PageShell>
|
</PageShell>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { DomainGroupEditSheet } from '@/components/domain-group-edit-sheet'
|
|||||||
import { GroupsBoard } from '@/components/groups-board/groups-board'
|
import { GroupsBoard } from '@/components/groups-board/groups-board'
|
||||||
import { GroupsBoardSkeleton } from '@/components/groups-board/groups-board-skeleton'
|
import { GroupsBoardSkeleton } from '@/components/groups-board/groups-board-skeleton'
|
||||||
import { useGroupsBoard } from '@/hooks/use-groups-board'
|
import { useGroupsBoard } from '@/hooks/use-groups-board'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_auth/groups')({
|
export const Route = createFileRoute('/_auth/groups')({
|
||||||
loader: ({ context: { queryClient } }) =>
|
loader: ({ context: { queryClient } }) =>
|
||||||
@@ -128,9 +128,9 @@ function GroupsPage() {
|
|||||||
title="Группы доменов"
|
title="Группы доменов"
|
||||||
description="Перетащите домен в группу или создайте новую группу для организации зон"
|
description="Перетащите домен в группу или создайте новую группу для организации зон"
|
||||||
actions={
|
actions={
|
||||||
<Button onClick={() => setCreateSheetOpen(true)}>
|
<AppButton onClick={() => setCreateSheetOpen(true)}>
|
||||||
Добавить группу
|
Добавить группу
|
||||||
</Button>
|
</AppButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -147,9 +147,9 @@ function GroupsPage() {
|
|||||||
title="Группы не найдены"
|
title="Группы не найдены"
|
||||||
description="Создайте группу и назначьте домены при импорте или перетаскиванием на доске."
|
description="Создайте группу и назначьте домены при импорте или перетаскиванием на доске."
|
||||||
action={
|
action={
|
||||||
<Button onClick={() => setCreateSheetOpen(true)}>
|
<AppButton onClick={() => setCreateSheetOpen(true)}>
|
||||||
Добавить группу
|
Добавить группу
|
||||||
</Button>
|
</AppButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { StatusBadge } from '@/components/status-badge'
|
|||||||
import { DataTableCard } from '@/components/data-table-card'
|
import { DataTableCard } from '@/components/data-table-card'
|
||||||
import { TableSkeleton } from '@/components/table-skeleton'
|
import { TableSkeleton } from '@/components/table-skeleton'
|
||||||
import { TableToolbar } from '@/components/table-toolbar'
|
import { TableToolbar } from '@/components/table-toolbar'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -76,13 +76,13 @@ function GroupDetailPage() {
|
|||||||
: 'Домены в группе'
|
: 'Домены в группе'
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
<Button
|
<AppButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
render={<Link to="/groups" />}
|
render={<Link to="/groups" />}
|
||||||
>
|
>
|
||||||
К группам
|
К группам
|
||||||
</Button>
|
</AppButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<QueryState
|
<QueryState
|
||||||
@@ -110,17 +110,17 @@ function GroupDetailPage() {
|
|||||||
isEmpty={!hasRecords}
|
isEmpty={!hasRecords}
|
||||||
emptyAction={
|
emptyAction={
|
||||||
isFilteredEmpty ? (
|
isFilteredEmpty ? (
|
||||||
<Button variant="outline" onClick={() => setSearchQuery('')}>
|
<AppButton variant="outline" onClick={() => setSearchQuery('')}>
|
||||||
Сбросить фильтр
|
Сбросить фильтр
|
||||||
</Button>
|
</AppButton>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<AppButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
render={<Link to="/groups" />}
|
render={<Link to="/groups" />}
|
||||||
>
|
>
|
||||||
Открыть канбан групп
|
Открыть канбан групп
|
||||||
</Button>
|
</AppButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
toolbar={
|
toolbar={
|
||||||
@@ -149,7 +149,7 @@ function GroupDetailPage() {
|
|||||||
<StatusBadge status={domain.status} />
|
<StatusBadge status={domain.status} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Button
|
<AppButton
|
||||||
variant="link"
|
variant="link"
|
||||||
className="h-auto p-0 tabular-nums"
|
className="h-auto p-0 tabular-nums"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
@@ -161,10 +161,10 @@ function GroupDetailPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{domain.service_count}
|
{domain.service_count}
|
||||||
</Button>
|
</AppButton>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<Button
|
<AppButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
nativeButton={false}
|
nativeButton={false}
|
||||||
@@ -176,7 +176,7 @@ function GroupDetailPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
Обзор
|
Обзор
|
||||||
</Button>
|
</AppButton>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { ServicesBoardSkeleton } from '@/components/services-board/services-boar
|
|||||||
import { ServicesBulkToolbar } from '@/components/services-board/services-bulk-toolbar'
|
import { ServicesBulkToolbar } from '@/components/services-board/services-bulk-toolbar'
|
||||||
import { useServicesBoard } from '@/hooks/use-services-board'
|
import { useServicesBoard } from '@/hooks/use-services-board'
|
||||||
import { useServicesSelection } from '@/hooks/use-services-selection'
|
import { useServicesSelection } from '@/hooks/use-services-selection'
|
||||||
import { Button } from '@cfdm/ui/components/button'
|
import { AppButton } from '@/components/app-button'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_auth/services')({
|
export const Route = createFileRoute('/_auth/services')({
|
||||||
validateSearch: (search: Record<string, unknown>) => ({
|
validateSearch: (search: Record<string, unknown>) => ({
|
||||||
@@ -416,10 +416,10 @@ function ServicesPage() {
|
|||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Button variant="outline" onClick={() => setCreateGroupSheetOpen(true)}>
|
<AppButton variant="outline" onClick={() => setCreateGroupSheetOpen(true)}>
|
||||||
Добавить группу
|
Добавить группу
|
||||||
</Button>
|
</AppButton>
|
||||||
<Button onClick={() => handleOpenCreateService()}>Добавить сервис</Button>
|
<AppButton onClick={() => handleOpenCreateService()}>Добавить сервис</AppButton>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -450,10 +450,10 @@ function ServicesPage() {
|
|||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<div className="flex flex-wrap justify-center gap-2">
|
<div className="flex flex-wrap justify-center gap-2">
|
||||||
<Button variant="outline" onClick={() => setCreateGroupSheetOpen(true)}>
|
<AppButton variant="outline" onClick={() => setCreateGroupSheetOpen(true)}>
|
||||||
Добавить группу
|
Добавить группу
|
||||||
</Button>
|
</AppButton>
|
||||||
<Button onClick={() => handleOpenCreateService()}>Добавить сервис</Button>
|
<AppButton onClick={() => handleOpenCreateService()}>Добавить сервис</AppButton>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user