2026-06-11 09:53:30 +01:00
|
|
|
import { useRef, useState } from 'react';
|
|
|
|
|
import { SearchOutlined } from '@ant-design/icons';
|
|
|
|
|
import type { InputRef, TableColumnsType, TableColumnType } from 'antd';
|
|
|
|
|
import { Button, Input, Space, Table } from 'antd';
|
|
|
|
|
import type { FilterDropdownProps } from 'antd/es/table/interface';
|
|
|
|
|
import Highlighter from 'react-highlight-words';
|
2026-06-22 10:50:37 +01:00
|
|
|
import { QRCode } from 'antd';
|
2026-06-11 09:53:30 +01:00
|
|
|
interface BridgeDataType {
|
|
|
|
|
fingerprint: string;
|
|
|
|
|
bridgeline: string;
|
|
|
|
|
qrCodeText: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Bridges = () => {
|
|
|
|
|
|
|
|
|
|
const bridgeLines: BridgeDataType[] = [
|
|
|
|
|
{fingerprint: '0C6DDE3F9FC377A5E69DC044E81C857277148D71', bridgeline: "obfs4 62.224.107.157:443 0C6DDE3F9FC377A5E69DC044E81C857277148D71 cert=6vp+cyZmJOp+QFtytv9Ca+Z+ASF4l7r12MeEevvufl4OcDimBhjQlxctjfgdCmnT7iu7Lg iat-mode=0", qrCodeText: "" },
|
|
|
|
|
{fingerprint:'8DFC222D2295A909B34B3AAAA584648EE6FAF14D', bridgeline: "obfs4 144.31.125.189:2063 8DFC222D2295A909B34B3AAAA584648EE6FAF14D cert=3BPE8q1dHF1AWoqsQEDGZHrRXPw/AaTwyM7YLGMOfFlxY6qRAlW1Jq0LlzKbe4Gh9+SacA iat-mode=0", qrCodeText: "" },
|
|
|
|
|
{fingerprint: '0C6DDE3F9FC377A5E69DC044E81C857277148D71', bridgeline: "obfs4 62.224.107.157:443 0C6DDE3F9FC377A5E69DC044E81C857277148D71 cert=6vp+cyZmJOp+QFtytv9Ca+Z+ASF4l7r12MeEevvufl4OcDimBhjQlxctjfgdCmnT7iu7Lg iat-mode=0", qrCodeText: ""},
|
|
|
|
|
{fingerprint:'8DFC222D2295A909B34B3AAAA584648EE6FAF14D', bridgeline: "obfs4 144.31.125.189:2063 8DFC222D2295A909B34B3AAAA584648EE6FAF14D cert=3BPE8q1dHF1AWoqsQEDGZHrRXPw/AaTwyM7YLGMOfFlxY6qRAlW1Jq0LlzKbe4Gh9+SacA iat-mode=0", qrCodeText: ""},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function makeQRCodeText(bridgeline: string) {
|
|
|
|
|
return '["' + bridgeline + '"]';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bridgeLines.forEach((line) => {line.qrCodeText = makeQRCodeText(line.bridgeline)});
|
|
|
|
|
console.log(bridgeLines);
|
|
|
|
|
|
|
|
|
|
const [searchText, setSearchText] = useState('');
|
|
|
|
|
const [searchedColumn, setSearchedColumn] = useState('');
|
|
|
|
|
const searchInput = useRef<InputRef>(null);
|
|
|
|
|
|
|
|
|
|
const handleSearch = (
|
|
|
|
|
selectedKeys: string[],
|
|
|
|
|
confirm: FilterDropdownProps['confirm'],
|
|
|
|
|
dataIndex: DataIndex,
|
|
|
|
|
) => {
|
|
|
|
|
confirm();
|
|
|
|
|
setSearchText(selectedKeys[0]);
|
|
|
|
|
setSearchedColumn(dataIndex);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleReset = (clearFilters: () => void) => {
|
|
|
|
|
clearFilters();
|
|
|
|
|
setSearchText('');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type DataIndex = keyof BridgeDataType;
|
|
|
|
|
const getColumnSearchProps = (dataIndex: DataIndex): TableColumnType<BridgeDataType> => ({
|
|
|
|
|
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => (
|
|
|
|
|
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
|
|
|
|
|
<Input
|
2026-06-22 10:50:37 +01:00
|
|
|
//ref={searchInput}
|
2026-06-11 09:53:30 +01:00
|
|
|
placeholder={`Search ${dataIndex}`}
|
|
|
|
|
value={selectedKeys[0]}
|
|
|
|
|
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
|
|
|
|
onPressEnter={() => handleSearch(selectedKeys as string[], confirm, dataIndex)}
|
|
|
|
|
style={{ marginBottom: 8, display: 'block' }}
|
|
|
|
|
/>
|
|
|
|
|
<Space>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
onClick={() => handleSearch(selectedKeys as string[], confirm, dataIndex)}
|
|
|
|
|
icon={<SearchOutlined />}
|
|
|
|
|
size="small"
|
|
|
|
|
style={{ width: 90 }}
|
|
|
|
|
>
|
|
|
|
|
Search
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
onClick={() => clearFilters && handleReset(clearFilters)}
|
|
|
|
|
size="small"
|
|
|
|
|
style={{ width: 90 }}
|
|
|
|
|
>
|
|
|
|
|
Reset
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
type="link"
|
|
|
|
|
size="small"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
confirm({ closeDropdown: false });
|
|
|
|
|
setSearchText((selectedKeys as string[])[0]);
|
|
|
|
|
setSearchedColumn(dataIndex);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Filter
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
type="link"
|
|
|
|
|
size="small"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
close();
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
close
|
|
|
|
|
</Button>
|
|
|
|
|
</Space>
|
|
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
filterIcon: (filtered: boolean) => (
|
|
|
|
|
<SearchOutlined style={{ color: filtered ? '#1677ff' : undefined }} />
|
|
|
|
|
),
|
|
|
|
|
onFilter: (value, record) =>
|
|
|
|
|
record[dataIndex]
|
|
|
|
|
.toString()
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
.includes((value as string).toLowerCase()),
|
|
|
|
|
filterDropdownProps: {
|
|
|
|
|
onOpenChange(open) {
|
|
|
|
|
if (open) {
|
|
|
|
|
setTimeout(() => searchInput.current?.select(), 100);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
render: (text) =>
|
|
|
|
|
searchedColumn === dataIndex ? (
|
|
|
|
|
<Highlighter
|
|
|
|
|
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
|
|
|
|
|
searchWords={[searchText]}
|
|
|
|
|
autoEscape
|
|
|
|
|
textToHighlight={text ? text.toString() : ''}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
text
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const bridgesColumns: TableColumnsType<BridgeDataType> = [
|
|
|
|
|
{title: 'Fingerprint',
|
|
|
|
|
dataIndex: 'fingerprint',
|
|
|
|
|
key: 'fingerprint',
|
|
|
|
|
width: '30%',
|
|
|
|
|
...getColumnSearchProps('fingerprint')},
|
|
|
|
|
|
|
|
|
|
{title: 'Bridgeline',
|
|
|
|
|
dataIndex: 'bridgeline',
|
|
|
|
|
key: 'bridgeline',
|
|
|
|
|
width: '40%',
|
|
|
|
|
...getColumnSearchProps('bridgeline')},
|
|
|
|
|
|
|
|
|
|
{title: 'QR Code',
|
|
|
|
|
width: '40%',
|
|
|
|
|
key: 'qrCodeText',
|
|
|
|
|
dataIndex: 'qrCodeText',
|
2026-06-22 10:50:37 +01:00
|
|
|
render: (_text, record) => {return (<QRCode value={record.qrCodeText} />)}
|
2026-06-11 09:53:30 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return(
|
|
|
|
|
<>
|
|
|
|
|
|
|
|
|
|
<Table<BridgeDataType> columns={bridgesColumns} dataSource={bridgeLines} />;
|
|
|
|
|
</>);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Bridges;
|