diff --git a/apps/web/src/components/blocks/profile-1/components/setting-row.tsx b/apps/web/src/components/blocks/profile-1/components/setting-row.tsx
index 7428c25..d9ff8d9 100644
--- a/apps/web/src/components/blocks/profile-1/components/setting-row.tsx
+++ b/apps/web/src/components/blocks/profile-1/components/setting-row.tsx
@@ -1,90 +1,3 @@
"use client"
-import { type ReactNode } from "react"
-
-import { cn } from "@cfdm/ui/lib/utils"
-import {
- Field,
- FieldContent,
- FieldDescription,
- FieldLabel,
- FieldSeparator,
- FieldTitle,
-} from "@cfdm/ui/components/field"
-
-interface SettingRowProps {
- title: string
- description?: ReactNode
- children: ReactNode
- last?: boolean
- compact?: boolean
- stacked?: boolean
- labelFor?: string
- contentClassName?: string
- titleAddon?: ReactNode
-}
-
-export function SettingRow({
- title,
- description,
- children,
- last,
- compact,
- stacked,
- labelFor,
- contentClassName,
- titleAddon,
-}: SettingRowProps) {
- return (
- <>
-
-
-
- {labelFor ? (
-
- {title}
-
- ) : (
-
- {title}
-
- )}
- {titleAddon}
-
-
- {description ? (
-
- {description}
-
- ) : null}
-
-
-
-
- {children}
-
-
-
-
- {!last ? : null}
- >
- )
-}
\ No newline at end of file
+export { SettingRow, type SettingRowProps } from "@/components/setting-row"
diff --git a/apps/web/src/components/counted-line-tabs.tsx b/apps/web/src/components/counted-line-tabs.tsx
index 4d4788e..f6cfe20 100644
--- a/apps/web/src/components/counted-line-tabs.tsx
+++ b/apps/web/src/components/counted-line-tabs.tsx
@@ -1,3 +1,4 @@
+import type { ReactNode } from 'react'
import { Tabs, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs'
import { cn } from '@cfdm/ui/lib/utils'
@@ -13,6 +14,7 @@ interface CountedLineTabsProps {
onValueChange: (value: string) => void
className?: string
listClassName?: string
+ children?: ReactNode
}
/** Line tabs with count pills (c-tabs-2 / data-grid-filtering-2). */
@@ -22,6 +24,7 @@ export function CountedLineTabs({
onValueChange,
className,
listClassName,
+ children,
}: CountedLineTabsProps) {
return (
@@ -41,6 +44,7 @@ export function CountedLineTabs({
))}
+ {children}
)
}
diff --git a/apps/web/src/components/health-check-config-fields.tsx b/apps/web/src/components/health-check-config-fields.tsx
index 4b504ea..c2378e1 100644
--- a/apps/web/src/components/health-check-config-fields.tsx
+++ b/apps/web/src/components/health-check-config-fields.tsx
@@ -1,6 +1,14 @@
-import { AppFieldGroup } from '@/components/app-field'
import { FormFieldSimple } from '@/components/form-field'
import { AppInput } from '@/components/app-input'
+import { SettingRow } from '@/components/setting-row'
+import { Badge } from '@/components/reui/badge'
+import {
+ NumberField,
+ NumberFieldDecrement,
+ NumberFieldGroup,
+ NumberFieldIncrement,
+ NumberFieldInput,
+} from '@/components/reui/number-field'
import {
Select,
SelectContent,
@@ -9,7 +17,7 @@ import {
SelectValue,
} from '@cfdm/ui/components/select'
import { Switch } from '@cfdm/ui/components/switch'
-import { Label } from '@cfdm/ui/components/label'
+import { FieldGroup } from '@cfdm/ui/components/field'
import { cn } from '@cfdm/ui/lib/utils'
export type LbMode = 'round_robin' | 'failover' | 'weighted'
@@ -50,6 +58,39 @@ interface HealthCheckConfigFieldsProps {
className?: string
}
+function CompactNumberField({
+ id,
+ value,
+ onValueChange,
+ min,
+ max,
+ placeholder,
+}: {
+ id: string
+ value: number | null
+ onValueChange: (next: number | null) => void
+ min?: number
+ max?: number
+ placeholder?: string
+}) {
+ return (
+ onValueChange(next ?? null)}
+ >
+
+
+
+
+
+
+ )
+}
+
export function HealthCheckConfigFields({
value,
onChange,
@@ -64,11 +105,18 @@ export function HealthCheckConfigFields({
}
const isHttp = value.type === 'http'
+ const rowClass = 'gap-3 px-0 py-3'
return (
-
- {showLbMode && (
-
+
+ {showLbMode ? (
+
-
- )}
+
+ ) : null}
-
-
- patch({ enabled: checked })}
- />
-
-
-
+
+ {value.enabled ? 'Вкл' : 'Выкл'}
+
+ }
+ >
+ patch({ enabled: checked })}
+ />
+
- {value.enabled && (
-
-
- Параметры проверки
-
-
-
+ {value.enabled ? (
+
+
+
-
- patch({ port: e.target.value === '' ? null : Number(e.target.value) })
- }
+ onValueChange={(port) => patch({ port })}
/>
+
- {isHttp && (
+ {isHttp ? (
+
- )}
- {isHttp && (
-
-
+
- patch({
- expected_status:
- e.target.value === '' ? null : Number(e.target.value),
- })
- }
- />
-
- )}
-
-
-
-
- patch({
- interval_sec:
- e.target.value === '' ? 30 : Number(e.target.value),
- })
- }
- />
-
-
-
- patch({
- timeout_ms:
- e.target.value === '' ? 3000 : Number(e.target.value),
- })
- }
+ onValueChange={(expected_status) => patch({ expected_status })}
/>
-
+ ) : null}
+
+
+
+
+ patch({ interval_sec: next ?? 30 })
+ }
+ />
+
+
+
+ patch({ timeout_ms: next ?? 3000 })
+ }
+ />
+
+
- )}
-
+ ) : null}
+
)
}
diff --git a/apps/web/src/components/service-edit-sheet.tsx b/apps/web/src/components/service-edit-sheet.tsx
index 4493798..d533f3f 100644
--- a/apps/web/src/components/service-edit-sheet.tsx
+++ b/apps/web/src/components/service-edit-sheet.tsx
@@ -1,6 +1,7 @@
import { useEffect, useMemo, useState } from 'react'
import { Link2Icon, PlusIcon, Trash2Icon } from 'lucide-react'
import { ConfirmDialog } from '@/components/confirm-dialog'
+import { CountedLineTabs } from '@/components/counted-line-tabs'
import { EmptyState } from '@/components/empty-state'
import { TaggedInput, isValidIpv4 } from '@/components/tagged-input'
import { ServiceBindingIpInput } from '@/components/service-binding-ip-input'
@@ -27,7 +28,6 @@ import {
} from '@/components/app-field'
import {
AppItem,
- AppItemActions,
AppItemContent,
AppItemGroup,
} from '@/components/app-item'
@@ -41,18 +41,7 @@ import {
SheetTitle,
} from '@cfdm/ui/components/sheet'
import { LoadingButton } from '@/components/loading-button'
-import {
- Tabs,
- TabsContent,
- TabsList,
- TabsTrigger,
-} from '@cfdm/ui/components/tabs'
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from '@cfdm/ui/components/accordion'
+import { TabsContent } from '@cfdm/ui/components/tabs'
import {
Select,
SelectContent,
@@ -188,6 +177,7 @@ export function ServiceEditSheet({
const [bindings, setBindings] = useState([])
const [lbWeight, setLbWeight] = useState(1)
const [lbPriority, setLbPriority] = useState(1)
+ const [activeTab, setActiveTab] = useState('general')
const groupItems = useMemo(
() => [
@@ -206,6 +196,7 @@ export function ServiceEditSheet({
useEffect(() => {
if (!open) return
+ setActiveTab('general')
if (mode === 'edit' && service) {
setName(service.name)
setSlug(service.slug)
@@ -399,25 +390,20 @@ export function ServiceEditSheet({
-
0 ? bindings.length : undefined,
+ },
+ ]}
+ value={activeTab}
+ onValueChange={setActiveTab}
className="flex w-full flex-col gap-4"
+ listClassName="mb-0 w-full"
>
-
-
- Основное
-
-
- Привязки
- {bindings.length > 0 ? (
-
- {bindings.length}
-
- ) : null}
-
-
-
@@ -546,20 +532,32 @@ export function ServiceEditSheet({
binding.target_ips.length > 1 &&
binding.lb_mode !== 'round_robin'
return (
-
-
-
- FQDN
- handleFqdnChange(index, tags)}
- placeholder={
- zoneHints[0] ? `newdom.${zoneHints[0]}` : 'newdom.ivx.su'
- }
- maxItems={1}
- />
-
+
+
+
+
+ FQDN
+ handleFqdnChange(index, tags)}
+ placeholder={
+ zoneHints[0] ? `newdom.${zoneHints[0]}` : 'newdom.ivx.su'
+ }
+ maxItems={1}
+ />
+
+
handleRemoveBinding(index)}
+ >
+
+
+
Тип записи
-
- handleRemoveBinding(index)}
- >
-
-
-
)
})}
)}
-
+
diff --git a/apps/web/src/components/service-group-edit-sheet.tsx b/apps/web/src/components/service-group-edit-sheet.tsx
index 1fed143..934c477 100644
--- a/apps/web/src/components/service-group-edit-sheet.tsx
+++ b/apps/web/src/components/service-group-edit-sheet.tsx
@@ -23,12 +23,6 @@ import {
SelectTrigger,
SelectValue,
} from '@cfdm/ui/components/select'
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from '@cfdm/ui/components/accordion'
const groupTypes = [
{ value: 'vpn', label: 'VPN' },
@@ -199,21 +193,14 @@ export function ServiceGroupEditSheet({
- {hasDomain && (
-
-
- Балансировка и Health-check
-
-
-
-
-
- )}
+ {hasDomain ? (
+
+ ) : null}
)
}
diff --git a/apps/web/src/components/setting-row.tsx b/apps/web/src/components/setting-row.tsx
new file mode 100644
index 0000000..522e7fe
--- /dev/null
+++ b/apps/web/src/components/setting-row.tsx
@@ -0,0 +1,85 @@
+import { type ReactNode } from 'react'
+
+import { cn } from '@cfdm/ui/lib/utils'
+import {
+ Field,
+ FieldContent,
+ FieldDescription,
+ FieldLabel,
+ FieldSeparator,
+ FieldTitle,
+} from '@cfdm/ui/components/field'
+
+export interface SettingRowProps {
+ title: string
+ description?: ReactNode
+ children: ReactNode
+ last?: boolean
+ compact?: boolean
+ stacked?: boolean
+ labelFor?: string
+ contentClassName?: string
+ className?: string
+ titleAddon?: ReactNode
+}
+
+/** Compact settings row (REUI PRO profile-1 / settings-9 pattern). */
+export function SettingRow({
+ title,
+ description,
+ children,
+ last,
+ compact,
+ stacked,
+ labelFor,
+ contentClassName,
+ className,
+ titleAddon,
+}: SettingRowProps) {
+ return (
+ <>
+
+
+
+ {labelFor ? (
+ {title}
+ ) : (
+ {title}
+ )}
+ {titleAddon}
+
+
+ {description ? (
+
{description}
+ ) : null}
+
+
+
+
+ {children}
+
+
+
+
+ {!last ?
: null}
+ >
+ )
+}