diff --git a/apps/web/src/components/services/service-fqdn-list.tsx b/apps/web/src/components/services/service-fqdn-list.tsx
index dba74f2..bd8f536 100644
--- a/apps/web/src/components/services/service-fqdn-list.tsx
+++ b/apps/web/src/components/services/service-fqdn-list.tsx
@@ -8,6 +8,13 @@ import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
import { serviceDisplayFqdns } from '@/lib/service-utils'
import type { ServiceView } from '@/lib/schemas'
import { Button } from '@cfdm/ui/components/button'
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemGroup,
+ ItemMedia,
+} from '@cfdm/ui/components/item'
import { Switch } from '@cfdm/ui/components/switch'
import {
Tooltip,
@@ -17,6 +24,9 @@ import {
} from '@cfdm/ui/components/tooltip'
import { cn } from '@cfdm/ui/lib/utils'
+/** Matches `Button size="icon-sm"` so Switch columns align with the card menu. */
+const MENU_SLOT_CLASS = 'size-7 shrink-0'
+
export function CopyFqdnButton({
value,
className,
@@ -126,6 +136,8 @@ interface ServiceIpListProps {
togglingIp?: string | null
ipToggleDisabled?: boolean
onToggleIp?: (ip: string, enabled: boolean) => void
+ /** Invisible icon-sm slot so IP Switch lines up with the card overflow menu. */
+ alignWithMenu?: boolean
className?: string
emptyLabel?: string
copyable?: boolean
@@ -139,6 +151,7 @@ export function ServiceIpList({
togglingIp = null,
ipToggleDisabled = false,
onToggleIp,
+ alignWithMenu = false,
className,
emptyLabel = 'Нет IP',
copyable = false,
@@ -155,49 +168,69 @@ export function ServiceIpList({
const healthByIp = new Map(ipHealth.map((row) => [row.ip, row]))
const visible = onToggleIp ? ips : ips.slice(0, VISIBLE_IP_LIMIT)
const extraCount = ips.length - visible.length
+ const showMenuSlot = Boolean(onToggleIp && alignWithMenu)
return (
-
+
{visible.map((ip) => {
const health = healthByIp.get(ip)
const enabled = ipEnabled[ip] !== false
return (
-
-
-
- {ip}
-
- {copyable ?
: null}
- {onToggleIp ? (
-
{
- event.stopPropagation()
- }}
- onCheckedChange={(checked) => onToggleIp(ip, Boolean(checked))}
- aria-label={
- enabled ? `Выключить IP ${ip}` : `Включить IP ${ip}`
- }
+ -
+
+
+
+
+
+
+ {ip}
+
+ {copyable ? : null}
+
+
+ {onToggleIp ? (
+
+ {
+ event.stopPropagation()
+ }}
+ onCheckedChange={(checked) =>
+ onToggleIp(ip, Boolean(checked))
+ }
+ aria-label={
+ enabled ? `Выключить IP ${ip}` : `Включить IP ${ip}`
+ }
+ />
+ {showMenuSlot ? (
+
+ ) : null}
+
) : null}
-
+
)
})}
{extraCount > 0 ? (
@@ -224,6 +257,6 @@ export function ServiceIpList({
) : null}
-
+
)
}
diff --git a/apps/web/src/components/services/service-unit-card.tsx b/apps/web/src/components/services/service-unit-card.tsx
index 68f7481..74c7911 100644
--- a/apps/web/src/components/services/service-unit-card.tsx
+++ b/apps/web/src/components/services/service-unit-card.tsx
@@ -5,7 +5,6 @@ import { Badge } from '@/components/reui/badge'
import {
Frame,
FrameDescription,
- FrameHeader,
FramePanel,
FrameTitle,
} from '@/components/reui/frame'
@@ -23,6 +22,12 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@cfdm/ui/components/dropdown-menu'
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemMedia,
+} from '@cfdm/ui/components/item'
import { Switch } from '@cfdm/ui/components/switch'
import {
Tooltip,
@@ -31,6 +36,12 @@ import {
TooltipTrigger,
} from '@cfdm/ui/components/tooltip'
+/**
+ * Compact service card — settings-8 DNA (Badge + copy + Switch + menu).
+ * Preview: https://reui.io/preview/base/settings-8
+ * Frame: https://reui.io/docs/components/base/frame
+ * IconTile: https://reui.io/docs/components/base/icon-tile
+ */
interface ServiceUnitCardProps {
service: ServiceView
togglingId: number | null
@@ -55,18 +66,20 @@ export function ServiceUnitCard({
const extraCount = Math.max(0, fqdns.length - 1)
return (
-
-
-
-
-
-
-
+
+
+ -
+
+
+
+
+
+
) : null}
-
-
-
-
- onToggleService(service.id, Boolean(checked))
- }
- aria-label={
- service.enabled ? 'Выключить сервис' : 'Включить сервис'
- }
- />
-
-
+
+
+
+ onToggleService(service.id, Boolean(checked))
}
- >
-
-
-
-
+
+
}
>
- Обзор
-
- onEditService(service)}>
- Изменить
-
- onDeleteService(service)}
- >
- Удалить
-
-
-
-
-
+
+
+
+
+ }
+ >
+ Обзор
+
+ onEditService(service)}>
+ Изменить
+
+ onDeleteService(service)}
+ >
+ Удалить
+
+
+
+
+
+
-
+