FallbacksCard.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import { useTranslation } from 'react-i18next';
  2. import { Button, Card, Empty, Input, InputNumber, Select, Space } from 'antd';
  3. import { ArrowDownOutlined, ArrowUpOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
  4. import { InputAddon } from '@/components/ui';
  5. import type { FallbackRow } from '@/schemas/forms/inbound-form';
  6. interface FallbacksCardProps {
  7. fallbacks: FallbackRow[];
  8. fallbackChildOptions: { label: string; value: number }[];
  9. addFallback: () => void;
  10. updateFallback: (rowKey: string, patch: Partial<FallbackRow>) => void;
  11. removeFallback: (idx: number) => void;
  12. moveFallback: (idx: number, direction: -1 | 1) => void;
  13. addAllFallbacks: () => void;
  14. }
  15. export default function FallbacksCard({
  16. fallbacks,
  17. fallbackChildOptions,
  18. addFallback,
  19. updateFallback,
  20. removeFallback,
  21. moveFallback,
  22. addAllFallbacks,
  23. }: FallbacksCardProps) {
  24. const { t } = useTranslation();
  25. return (
  26. <Card size="small" className="mt-12" title={t('pages.inbounds.fallbacks.title') || 'Fallbacks'}>
  27. {fallbacks.length === 0 && (
  28. <Empty
  29. description={t('pages.inbounds.fallbacks.empty') || 'No fallbacks yet'}
  30. styles={{ image: { height: 40 } }}
  31. style={{ margin: '8px 0 12px' }}
  32. />
  33. )}
  34. {fallbacks.map((record, idx) => (
  35. <div
  36. key={record.rowKey}
  37. style={{ border: '1px solid var(--app-border-tertiary)', borderRadius: 6, padding: '10px 12px', marginBottom: 8 }}
  38. >
  39. <Space.Compact block style={{ marginBottom: 6 }}>
  40. <Select
  41. value={record.childId}
  42. options={fallbackChildOptions}
  43. placeholder={t('pages.inbounds.fallbacks.pickInbound') || 'Pick an inbound'}
  44. allowClear
  45. showSearch={{
  46. filterOption: (input, option) =>
  47. ((option?.label as string) || '').toLowerCase().includes(input.toLowerCase()),
  48. }}
  49. style={{ width: '100%' }}
  50. onChange={(v) => updateFallback(record.rowKey, { childId: v ?? null })}
  51. />
  52. <Button
  53. disabled={idx === 0}
  54. onClick={() => moveFallback(idx, -1)}
  55. title={t('pages.inbounds.form.moveUp')}
  56. >
  57. <ArrowUpOutlined />
  58. </Button>
  59. <Button
  60. disabled={idx === fallbacks.length - 1}
  61. onClick={() => moveFallback(idx, 1)}
  62. title={t('pages.inbounds.form.moveDown')}
  63. >
  64. <ArrowDownOutlined />
  65. </Button>
  66. <Button danger onClick={() => removeFallback(idx)}>
  67. <DeleteOutlined />
  68. </Button>
  69. </Space.Compact>
  70. <Space.Compact block>
  71. <InputAddon>SNI</InputAddon>
  72. <Input
  73. placeholder={t('pages.inbounds.fallbacks.matchAny') || 'any'}
  74. value={record.name}
  75. onChange={(e) => updateFallback(record.rowKey, { name: e.target.value })}
  76. />
  77. <InputAddon>ALPN</InputAddon>
  78. <Input
  79. placeholder={t('pages.inbounds.fallbacks.matchAny') || 'any'}
  80. value={record.alpn}
  81. onChange={(e) => updateFallback(record.rowKey, { alpn: e.target.value })}
  82. />
  83. <InputAddon>Path</InputAddon>
  84. <Input
  85. placeholder="/"
  86. value={record.path}
  87. onChange={(e) => updateFallback(record.rowKey, { path: e.target.value })}
  88. />
  89. <InputAddon>Dest</InputAddon>
  90. <Input
  91. placeholder={t('pages.inbounds.fallbacks.destPlaceholder') || 'auto'}
  92. value={record.dest}
  93. onChange={(e) => updateFallback(record.rowKey, { dest: e.target.value })}
  94. />
  95. <InputAddon>xver</InputAddon>
  96. <InputNumber
  97. min={0}
  98. max={2}
  99. value={record.xver}
  100. onChange={(v) => updateFallback(record.rowKey, { xver: Number(v) || 0 })}
  101. />
  102. </Space.Compact>
  103. </div>
  104. ))}
  105. <Space>
  106. <Button size="small" onClick={addFallback}>
  107. <PlusOutlined /> {t('pages.inbounds.fallbacks.add') || 'Add fallback'}
  108. </Button>
  109. <Button
  110. size="small"
  111. onClick={addAllFallbacks}
  112. disabled={fallbackChildOptions.length === 0
  113. || fallbacks.length >= fallbackChildOptions.length}
  114. title={t('pages.inbounds.form.addAllFallbackTooltip')}
  115. >
  116. {t('pages.inbounds.form.addAll')}
  117. </Button>
  118. </Space>
  119. </Card>
  120. );
  121. }