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