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

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