fix(alerts): сделать выбор объектов доступным с клавиатуры
This commit is contained in:
1 parent
1deccf862c
commit
f3ec6b31ce
5 files changed
+53
-71
No files matched your search
+33
-69
@@ -866,8 +866,6 @@ function ServerObjectCardPicker({
|
||||
selected,
|
||||
onToggle,
|
||||
hideLabel,
|
||||
/** Родитель задаёт высоту и скролл (блок в шите) */
|
||||
listUnconstrained,
|
||||
}: {
|
||||
label: string
|
||||
rows: AlertsServerRow[]
|
||||
@@ -875,7 +873,6 @@ function ServerObjectCardPicker({
|
||||
onToggle: (serverTitle: string) => void
|
||||
/** Один общий заголовок снаружи (блок «шагов») */
|
||||
hideLabel?: boolean
|
||||
listUnconstrained?: boolean
|
||||
}) {
|
||||
const [query, setQuery] = useState("")
|
||||
|
||||
@@ -927,13 +924,11 @@ function ServerObjectCardPicker({
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
role="listbox"
|
||||
role="group"
|
||||
aria-label={label}
|
||||
className={cn(
|
||||
"flex flex-col gap-1 overscroll-contain pr-1 -mr-1",
|
||||
listUnconstrained
|
||||
? "overflow-visible"
|
||||
: "max-h-[min(40vh,16rem)] overflow-y-auto",
|
||||
"overflow-visible",
|
||||
)}
|
||||
>
|
||||
{filteredRows.length === 0 ? (
|
||||
@@ -948,8 +943,7 @@ function ServerObjectCardPicker({
|
||||
<button
|
||||
key={`${title}\0${s.host}`}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={active}
|
||||
aria-pressed={active}
|
||||
onClick={() => onToggle(title)}
|
||||
className={cn(
|
||||
"rounded-md border px-2.5 py-1.5 text-left transition-all w-full min-w-0 leading-tight",
|
||||
@@ -998,14 +992,12 @@ function GreTunnelObjectCardPicker({
|
||||
selected,
|
||||
onToggle,
|
||||
hideLabel,
|
||||
listUnconstrained,
|
||||
}: {
|
||||
label: string
|
||||
rows: AlertsGreTunnelRow[]
|
||||
selected: string[]
|
||||
onToggle: (targetLabel: string) => void
|
||||
hideLabel?: boolean
|
||||
listUnconstrained?: boolean
|
||||
}) {
|
||||
const [query, setQuery] = useState("")
|
||||
|
||||
@@ -1059,11 +1051,11 @@ function GreTunnelObjectCardPicker({
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
role="listbox"
|
||||
role="group"
|
||||
aria-label={label}
|
||||
className={cn(
|
||||
"flex flex-col gap-1 overscroll-contain pr-1 -mr-1",
|
||||
listUnconstrained ? "overflow-visible" : "max-h-[min(40vh,16rem)] overflow-y-auto",
|
||||
"overflow-visible",
|
||||
)}
|
||||
>
|
||||
{filteredRows.length === 0 ? (
|
||||
@@ -1083,8 +1075,7 @@ function GreTunnelObjectCardPicker({
|
||||
<button
|
||||
key={`${r.targetLabel}\0${r.remoteAddress}`}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={active}
|
||||
aria-pressed={active}
|
||||
onClick={() => onToggle(r.targetLabel)}
|
||||
className={cn(
|
||||
"rounded-md border px-2.5 py-1.5 text-left transition-all w-full min-w-0 leading-tight",
|
||||
@@ -1150,7 +1141,6 @@ function StringOptionCardPicker({
|
||||
selected,
|
||||
onToggle,
|
||||
hideLabel,
|
||||
listUnconstrained,
|
||||
ruleType,
|
||||
}: {
|
||||
label: string
|
||||
@@ -1158,7 +1148,6 @@ function StringOptionCardPicker({
|
||||
selected: string[]
|
||||
onToggle: (opt: string) => void
|
||||
hideLabel?: boolean
|
||||
listUnconstrained?: boolean
|
||||
ruleType: AlertType
|
||||
}) {
|
||||
const [query, setQuery] = useState("")
|
||||
@@ -1200,11 +1189,11 @@ function StringOptionCardPicker({
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
role="listbox"
|
||||
role="group"
|
||||
aria-label={label}
|
||||
className={cn(
|
||||
"flex flex-col gap-1 overscroll-contain pr-1 -mr-1",
|
||||
listUnconstrained ? "overflow-visible" : "max-h-[min(40vh,16rem)] overflow-y-auto",
|
||||
"overflow-visible",
|
||||
)}
|
||||
>
|
||||
{filtered.length === 0 ? (
|
||||
@@ -1217,8 +1206,7 @@ function StringOptionCardPicker({
|
||||
<button
|
||||
key={opt}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={active}
|
||||
aria-pressed={active}
|
||||
onClick={() => onToggle(opt)}
|
||||
className={cn(
|
||||
"rounded-md border px-2.5 py-1.5 text-left transition-all w-full min-w-0 leading-tight",
|
||||
@@ -1258,7 +1246,6 @@ function ConditionOptionCardPicker({
|
||||
selected,
|
||||
onToggle,
|
||||
hideLabel,
|
||||
listUnconstrained,
|
||||
ruleType,
|
||||
}: {
|
||||
label: string
|
||||
@@ -1266,7 +1253,6 @@ function ConditionOptionCardPicker({
|
||||
selected: string[]
|
||||
onToggle: (v: string) => void
|
||||
hideLabel?: boolean
|
||||
listUnconstrained?: boolean
|
||||
ruleType: AlertType
|
||||
}) {
|
||||
const [query, setQuery] = useState("")
|
||||
@@ -1307,12 +1293,11 @@ function ConditionOptionCardPicker({
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
role="listbox"
|
||||
role="group"
|
||||
aria-label={label}
|
||||
aria-multiselectable
|
||||
className={cn(
|
||||
"flex flex-col gap-1 overscroll-contain pr-1 -mr-1",
|
||||
listUnconstrained ? "overflow-visible" : "max-h-[min(36vh,14rem)] overflow-y-auto",
|
||||
"overflow-visible",
|
||||
)}
|
||||
>
|
||||
{filtered.length === 0 ? (
|
||||
@@ -1325,8 +1310,7 @@ function ConditionOptionCardPicker({
|
||||
<button
|
||||
key={opt}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={active}
|
||||
aria-pressed={active}
|
||||
onClick={() => onToggle(opt)}
|
||||
className={cn(
|
||||
"rounded-md border px-2.5 py-1.5 text-left transition-all w-full min-w-0 leading-tight",
|
||||
@@ -1561,13 +1545,6 @@ function AddRuleSheet({
|
||||
...(initialForm ?? {}),
|
||||
}))
|
||||
const nameTouchedRef = useRef(Boolean(editRuleId))
|
||||
/** Список объектов не показываем сразу — после фокуса/клика по другому полю шита. Редактирование с выбранными объектами — сразу. */
|
||||
const [objectPickerRevealed, setObjectPickerRevealed] = useState(
|
||||
() => Boolean(editRuleId && (initialForm?.targets?.length ?? 0) > 0),
|
||||
)
|
||||
const revealObjectPicker = useCallback(() => {
|
||||
setObjectPickerRevealed(true)
|
||||
}, [])
|
||||
/** Для списка объектов: после выбора сворачиваем список, ниже показываем условия. */
|
||||
const [objectExpanded, setObjectExpanded] = useState(
|
||||
() => !(initialForm?.targets?.length),
|
||||
@@ -1743,7 +1720,7 @@ function AddRuleSheet({
|
||||
</SheetHeader>
|
||||
|
||||
{/* ── scrollable body ── */}
|
||||
<fieldset disabled={saving} className="min-h-0 min-w-0 flex-1 overflow-y-auto px-4 sm:px-6 py-5 flex flex-col gap-5">
|
||||
<fieldset disabled={saving} className="min-h-0 min-w-0 flex-1 overflow-y-auto px-4 sm:px-6 py-5 flex flex-col gap-5 [&>div]:shrink-0">
|
||||
|
||||
{/* name */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
@@ -1755,7 +1732,7 @@ function AddRuleSheet({
|
||||
size="sm"
|
||||
className="h-7 text-[11px] px-2 text-muted-foreground shrink-0"
|
||||
onClick={() => {
|
||||
revealObjectPicker()
|
||||
|
||||
applySuggestedName()
|
||||
}}
|
||||
>
|
||||
@@ -1765,7 +1742,7 @@ function AddRuleSheet({
|
||||
<Input
|
||||
aria-label="Название правила" placeholder="Подставится при выборе объекта или условия и будет обновляться при каждом шаге"
|
||||
value={form.name}
|
||||
onFocus={revealObjectPicker}
|
||||
|
||||
onChange={e => {
|
||||
nameTouchedRef.current = true
|
||||
setForm((f) => ({ ...f, name: e.target.value }))
|
||||
@@ -1790,10 +1767,11 @@ function AddRuleSheet({
|
||||
<button
|
||||
key={t}
|
||||
type="button"
|
||||
onFocus={revealObjectPicker}
|
||||
aria-pressed={active}
|
||||
|
||||
onClick={() => {
|
||||
nameTouchedRef.current = false
|
||||
setObjectPickerRevealed(true)
|
||||
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
type: t,
|
||||
@@ -1838,14 +1816,9 @@ function AddRuleSheet({
|
||||
{allowManualTarget ? (
|
||||
<div className="flex flex-col gap-1.5 min-w-0">
|
||||
<FieldLabel>Объект</FieldLabel>
|
||||
{!objectPickerRevealed ? (
|
||||
<p className="text-[10px] text-muted-foreground leading-snug">
|
||||
Поле ввода появится после клика или фокуса на названии правила, типе события, серьёзности,
|
||||
пороге, группе или Chat ID.
|
||||
</p>
|
||||
) : (
|
||||
{(
|
||||
<>
|
||||
<Input
|
||||
<Input aria-label="Объект правила"
|
||||
value={form.targets[0] ?? ""}
|
||||
onChange={e => {
|
||||
nameTouchedRef.current = false
|
||||
@@ -1874,12 +1847,7 @@ function AddRuleSheet({
|
||||
<p className="text-[10px] text-muted-foreground leading-snug">
|
||||
Можно выбрать несколько карточек — сработает при событии на любом из них (OR).
|
||||
</p>
|
||||
{!objectPickerRevealed ? (
|
||||
<p className="text-[10px] text-muted-foreground leading-snug">
|
||||
Список объектов появится после клика или фокуса на названии правила, типе события, серьёзности,
|
||||
пороге, группе или Chat ID.
|
||||
</p>
|
||||
) : (
|
||||
{(
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-xl border border-border/80 bg-muted/10 overflow-hidden",
|
||||
@@ -1893,11 +1861,10 @@ function AddRuleSheet({
|
||||
)}
|
||||
>
|
||||
<div className="min-h-0 overflow-hidden">
|
||||
<div className="p-3 max-h-[min(42vh,17rem)] overflow-y-auto overscroll-contain">
|
||||
<div className="p-3">
|
||||
{useGreTunnelObjectCards ? (
|
||||
<GreTunnelObjectCardPicker
|
||||
hideLabel
|
||||
listUnconstrained
|
||||
label="Объект"
|
||||
rows={objectGreCardRows}
|
||||
selected={form.targets}
|
||||
@@ -1906,7 +1873,6 @@ function AddRuleSheet({
|
||||
) : useServerObjectCards ? (
|
||||
<ServerObjectCardPicker
|
||||
hideLabel
|
||||
listUnconstrained
|
||||
label="Объект"
|
||||
rows={objectServerCardRows}
|
||||
selected={form.targets}
|
||||
@@ -1915,7 +1881,6 @@ function AddRuleSheet({
|
||||
) : (
|
||||
<StringOptionCardPicker
|
||||
hideLabel
|
||||
listUnconstrained
|
||||
label="Объект"
|
||||
ruleType={form.type}
|
||||
options={targets}
|
||||
@@ -1940,7 +1905,7 @@ function AddRuleSheet({
|
||||
server={objectSummaryGre ? undefined : objectSummaryServer}
|
||||
greTunnel={objectSummaryGre}
|
||||
onExpand={() => {
|
||||
revealObjectPicker()
|
||||
|
||||
setObjectExpanded(true)
|
||||
}}
|
||||
/>
|
||||
@@ -1972,10 +1937,9 @@ function AddRuleSheet({
|
||||
"opacity-100 translate-y-0 motion-reduce:transition-none",
|
||||
)}
|
||||
>
|
||||
<div className="p-3 max-h-[min(40vh,16rem)] overflow-y-auto overscroll-contain">
|
||||
<div className="p-3">
|
||||
<ConditionOptionCardPicker
|
||||
hideLabel
|
||||
listUnconstrained
|
||||
label="Условие"
|
||||
ruleType={form.type}
|
||||
conditions={conditions}
|
||||
@@ -1997,7 +1961,7 @@ function AddRuleSheet({
|
||||
<Input
|
||||
type="number" min="0"
|
||||
value={form.threshold}
|
||||
onFocus={revealObjectPicker}
|
||||
|
||||
onChange={e => {
|
||||
nameTouchedRef.current = false
|
||||
setForm((f) => ({ ...f, threshold: e.target.value }))
|
||||
@@ -2023,9 +1987,9 @@ function AddRuleSheet({
|
||||
<button
|
||||
key={s}
|
||||
type="button"
|
||||
onFocus={revealObjectPicker}
|
||||
|
||||
onClick={() => {
|
||||
revealObjectPicker()
|
||||
|
||||
setForm((f) => ({ ...f, severity: s }))
|
||||
}}
|
||||
className={cn(
|
||||
@@ -2056,9 +2020,9 @@ function AddRuleSheet({
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
onFocus={revealObjectPicker}
|
||||
|
||||
onClick={() => {
|
||||
revealObjectPicker()
|
||||
|
||||
setForm((f) => ({ ...f, cooldown: c }))
|
||||
}}
|
||||
className={cn(
|
||||
@@ -2085,9 +2049,9 @@ function AddRuleSheet({
|
||||
<button
|
||||
key={o.label}
|
||||
type="button"
|
||||
onFocus={revealObjectPicker}
|
||||
|
||||
onClick={() => {
|
||||
revealObjectPicker()
|
||||
|
||||
setForm((f) => ({ ...f, confirmStabilitySec: o.value }))
|
||||
}}
|
||||
className={cn(
|
||||
@@ -2116,7 +2080,7 @@ function AddRuleSheet({
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
)}
|
||||
value={form.groupId ?? ""}
|
||||
onFocus={revealObjectPicker}
|
||||
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
setForm((f) => ({ ...f, groupId: v ? v : null }))
|
||||
@@ -2139,7 +2103,7 @@ function AddRuleSheet({
|
||||
<FieldLabel>Chat ID — переопределить</FieldLabel>
|
||||
<Input
|
||||
value={form.chatId}
|
||||
onFocus={revealObjectPicker}
|
||||
|
||||
onChange={e => setForm(f => ({ ...f, chatId: e.target.value }))}
|
||||
className="font-mono text-sm"
|
||||
placeholder="-100xxxxxxxxxx"
|
||||
|
||||
Reference in new issue
Block a user