ObservatorySettingsTab.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { useMemo } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { Alert, Empty, Input, InputNumber, Select, Space, Switch, Tag } from 'antd';
  4. import { onNumber } from '@/utils/onNumber';
  5. import { SettingListItem } from '@/components/ui';
  6. import {
  7. BurstObservatorySchema,
  8. ObservatoryHttpMethodSchema,
  9. ObservatorySchema,
  10. type BurstObservatoryObject,
  11. type ObservatoryHttpMethod,
  12. type ObservatoryObject,
  13. type PingConfigObject,
  14. } from '@/schemas/observatory';
  15. import type { XraySettingsValue } from '@/hooks/useXraySetting';
  16. import { settingsRequireBurstObservatory } from './balancer-helpers';
  17. interface ObservatorySettingsTabProps {
  18. templateSettings: XraySettingsValue | null;
  19. mutate: (mutator: (next: XraySettingsValue) => void) => void;
  20. }
  21. const OBSERVATORY_DEFAULTS = ObservatorySchema.parse({});
  22. const BURST_DEFAULTS = BurstObservatorySchema.parse({});
  23. function asObject(value: unknown): Record<string, unknown> {
  24. return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
  25. }
  26. function SelectorTags({ tags }: { tags: string[] }) {
  27. if (!tags || tags.length === 0) return <Tag>—</Tag>;
  28. return (
  29. <>
  30. {tags.map((sel) => (
  31. <Tag key={sel} className="info-large-tag" style={{ margin: 0, marginRight: 4, marginBottom: 4 }}>
  32. {sel}
  33. </Tag>
  34. ))}
  35. </>
  36. );
  37. }
  38. export default function ObservatorySettingsTab({
  39. templateSettings,
  40. mutate,
  41. }: ObservatorySettingsTabProps) {
  42. const { t } = useTranslation();
  43. const observatory = useMemo<ObservatoryObject | null>(() => {
  44. const raw = templateSettings?.observatory;
  45. if (raw == null) return null;
  46. return { ...OBSERVATORY_DEFAULTS, ...asObject(raw) } as ObservatoryObject;
  47. }, [templateSettings?.observatory]);
  48. const burst = useMemo<BurstObservatoryObject | null>(() => {
  49. const raw = templateSettings?.burstObservatory;
  50. if (raw == null) return null;
  51. const merged = { ...BURST_DEFAULTS, ...asObject(raw) } as BurstObservatoryObject;
  52. merged.pingConfig = { ...BURST_DEFAULTS.pingConfig, ...asObject(merged.pingConfig) } as PingConfigObject;
  53. return merged;
  54. }, [templateSettings?.burstObservatory]);
  55. const hasObservatory = observatory != null;
  56. const hasBurst = burst != null;
  57. const hasMixedObservers = hasObservatory && hasBurst;
  58. const activeView = hasBurst && (!hasObservatory || settingsRequireBurstObservatory(templateSettings))
  59. ? 'burstObservatory'
  60. : 'observatory';
  61. function patchObservatory(patch: Partial<ObservatoryObject>) {
  62. mutate((tt) => {
  63. tt.observatory = { ...OBSERVATORY_DEFAULTS, ...asObject(tt.observatory), ...patch };
  64. });
  65. }
  66. function patchPingConfig(patch: Partial<PingConfigObject>) {
  67. mutate((tt) => {
  68. const current = asObject(tt.burstObservatory);
  69. const currentPing = asObject(current.pingConfig);
  70. tt.burstObservatory = {
  71. ...BURST_DEFAULTS,
  72. ...current,
  73. pingConfig: { ...BURST_DEFAULTS.pingConfig, ...currentPing, ...patch },
  74. };
  75. });
  76. }
  77. if (!hasObservatory && !hasBurst) {
  78. return <Empty description={t('pages.xray.observatory.emptyHint')} />;
  79. }
  80. const observatorySection = observatory && (
  81. <>
  82. <SettingListItem
  83. paddings="small"
  84. title={t('pages.xray.observatory.subjectSelector')}
  85. description={t('pages.xray.observatory.subjectSelectorDesc')}
  86. >
  87. <SelectorTags tags={observatory.subjectSelector} />
  88. </SettingListItem>
  89. <SettingListItem
  90. paddings="small"
  91. title={t('pages.xray.observatory.probeURL')}
  92. description={t('pages.xray.observatory.probeURLDesc')}
  93. >
  94. <Input
  95. value={observatory.probeURL}
  96. onChange={(e) => patchObservatory({ probeURL: e.target.value })}
  97. placeholder="https://www.google.com/generate_204"
  98. />
  99. </SettingListItem>
  100. <SettingListItem
  101. paddings="small"
  102. title={t('pages.xray.observatory.probeInterval')}
  103. description={t('pages.xray.observatory.probeIntervalDesc')}
  104. >
  105. <Input
  106. value={observatory.probeInterval}
  107. onChange={(e) => patchObservatory({ probeInterval: e.target.value })}
  108. placeholder="1m"
  109. />
  110. </SettingListItem>
  111. <SettingListItem
  112. paddings="small"
  113. title={t('pages.xray.observatory.enableConcurrency')}
  114. description={t('pages.xray.observatory.enableConcurrencyDesc')}
  115. >
  116. <Switch
  117. checked={observatory.enableConcurrency}
  118. onChange={(v) => patchObservatory({ enableConcurrency: v })}
  119. />
  120. </SettingListItem>
  121. </>
  122. );
  123. const burstSection = burst && (
  124. <>
  125. <SettingListItem
  126. paddings="small"
  127. title={t('pages.xray.observatory.subjectSelector')}
  128. description={t('pages.xray.observatory.subjectSelectorDesc')}
  129. >
  130. <SelectorTags tags={burst.subjectSelector} />
  131. </SettingListItem>
  132. <SettingListItem
  133. paddings="small"
  134. title={t('pages.xray.observatory.destination')}
  135. description={t('pages.xray.observatory.destinationDesc')}
  136. >
  137. <Input
  138. value={burst.pingConfig.destination}
  139. onChange={(e) => patchPingConfig({ destination: e.target.value })}
  140. placeholder="https://www.google.com/generate_204"
  141. />
  142. </SettingListItem>
  143. <SettingListItem
  144. paddings="small"
  145. title={t('pages.xray.observatory.connectivity')}
  146. description={t('pages.xray.observatory.connectivityDesc')}
  147. >
  148. <Input
  149. value={burst.pingConfig.connectivity}
  150. allowClear
  151. onChange={(e) => patchPingConfig({ connectivity: e.target.value })}
  152. placeholder="http://connectivitycheck.platform.hicloud.com/generate_204"
  153. />
  154. </SettingListItem>
  155. <SettingListItem
  156. paddings="small"
  157. title={t('pages.xray.observatory.interval')}
  158. description={t('pages.xray.observatory.intervalDesc')}
  159. >
  160. <Input
  161. value={burst.pingConfig.interval}
  162. onChange={(e) => patchPingConfig({ interval: e.target.value })}
  163. placeholder="1m"
  164. />
  165. </SettingListItem>
  166. <SettingListItem
  167. paddings="small"
  168. title={t('pages.xray.observatory.timeout')}
  169. description={t('pages.xray.observatory.timeoutDesc')}
  170. >
  171. <Input
  172. value={burst.pingConfig.timeout}
  173. onChange={(e) => patchPingConfig({ timeout: e.target.value })}
  174. placeholder="5s"
  175. />
  176. </SettingListItem>
  177. <SettingListItem
  178. paddings="small"
  179. title={t('pages.xray.observatory.sampling')}
  180. description={t('pages.xray.observatory.samplingDesc')}
  181. >
  182. <InputNumber
  183. min={1}
  184. value={burst.pingConfig.sampling}
  185. onChange={onNumber((v) => patchPingConfig({ sampling: v }))}
  186. style={{ width: '100%' }}
  187. />
  188. </SettingListItem>
  189. <SettingListItem
  190. paddings="small"
  191. title={t('pages.xray.observatory.httpMethod')}
  192. description={t('pages.xray.observatory.httpMethodDesc')}
  193. >
  194. <Select<ObservatoryHttpMethod>
  195. value={burst.pingConfig.httpMethod}
  196. onChange={(v) => patchPingConfig({ httpMethod: v })}
  197. options={ObservatoryHttpMethodSchema.options.map((m) => ({ value: m, label: m }))}
  198. style={{ width: '100%' }}
  199. />
  200. </SettingListItem>
  201. </>
  202. );
  203. return (
  204. <Space orientation="vertical" size="middle" style={{ width: '100%' }}>
  205. <Alert type="info" showIcon title={t('pages.xray.observatory.autoManaged')} />
  206. {hasMixedObservers && (
  207. <Alert
  208. type="warning"
  209. showIcon
  210. title={t('pages.xray.observatory.mixedLegacy')}
  211. />
  212. )}
  213. <div>{activeView === 'observatory' ? observatorySection : burstSection}</div>
  214. </Space>
  215. );
  216. }