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 { 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>
) )
} }
+18 -18
View File
@@ -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 -20
View File
@@ -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"
/> />
+26 -26
View File
@@ -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>
) )
} }
+7 -7
View File
@@ -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>
+81 -81
View File
@@ -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}
+57 -57
View File
@@ -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"
+5 -5
View File
@@ -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 />
+3 -3
View File
@@ -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}
+17 -17
View File
@@ -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} />}
+12 -12
View File
@@ -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>
) )
+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 { 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>
} }
/> />
) : ( ) : (
+11 -11
View File
@@ -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>
))} ))}
+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 { 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>
} }
/> />