Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/api/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export function handleFetchError(err: unknown, ctx: FetchCtx) {

async function validateFetchResponse(res: Response, ctx: FetchCtx) {
if (res.status === 401) throw new YacdBackendUnauthorizedError('', ctx);
if (res.status === 404) {
console.warn(`Server returns 404: ${ctx.endpoint}`);
return res;
}
if (!res.ok)
throw new YacdBackendGeneralError('', {
...ctx,
Expand Down
4 changes: 4 additions & 0 deletions src/components/Connections.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@
.react-tabs__tab-panel--selected {
display: block;
}

._btn_lzu00_1 {
margin-right: 10px;
}
30 changes: 23 additions & 7 deletions src/components/Connections.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,39 @@
border-radius: 30px;
}

.inputWrapper {
margin: 0 30px;
width: 100%;
max-width: 350px;
justify-self: flex-end;
.filterWrapper {
display: flex;
flex-wrap: nowrap;
}

.input {
appearance: none;
-webkit-appearance: none;
background-color: var(--color-input-bg);
background-image: none;
border-radius: 18px;
// left half circle
border-radius: 18px 0 0 18px;
border: 1px solid var(--color-input-border);
box-sizing: border-box;
color: #c1c1c1;
display: inline;
font-size: inherit;
height: 36px;
outline: none;
padding: 0 15px;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
width: 100%;
}

.button {
background-color: var(--color-input-bg);
background-image: none;
// right half circle
border-radius: 0 18px 18px 0;
border: 1px solid var(--color-input-border);
box-sizing: border-box;
color: #c1c1c1;
display: inline-block;
display: inline;
font-size: inherit;
height: 36px;
outline: none;
Expand Down
93 changes: 70 additions & 23 deletions src/components/Connections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,22 @@ import React from 'react';
import { Pause, Play, X as IconClose } from 'react-feather';
import { useTranslation } from 'react-i18next';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { List } from 'reselect/es/types';
import { ConnectionItem } from 'src/api/connections';
import BaseModal from 'src/components/shared/BaseModal';

import { useApiConfig } from '$src/store/app';

import * as connAPI from '../api/connections';
import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
import Button from './Button';
import s from './Connections.module.scss';
import ConnectionTable from './ConnectionTable';
import { MutableConnRefCtx } from './conns/ConnCtx';
import { ContentHeader } from './ContentHeader';
import ModalCloseAllConnections from './ModalCloseAllConnections';
import { Action, Fab, position as fabPosition } from './shared/Fab';
import SourceIP from './SourceIP';
import SvgYacd from './SvgYacd';

const { useEffect, useState, useRef, useCallback } = React;
Expand Down Expand Up @@ -58,23 +62,38 @@ type FormattedConn = {
function hasSubstring(s: string, pat: string) {
return (s ?? '').toLowerCase().includes(pat.toLowerCase());
}
function filterConnIps(conns: FormattedConn[], ipStr: string) {
return conns.filter((each) => each.sourceIP === ipStr);
}

function filterConns(conns: FormattedConn[], keyword: string) {
return !keyword
? conns
: conns.filter((conn) =>
[
conn.host,
conn.sourceIP,
conn.sourcePort,
conn.destinationIP,
conn.chains,
conn.rule,
conn.type,
conn.network,
conn.processPath,
].some((field) => hasSubstring(field, keyword)),
);
function getConnIpList(conns: FormattedConn[]): List<string> {
return Array.from(new Set(conns.map((x) => x.sourceIP))).sort();
}

function filterConns(conns: FormattedConn[], keyword: string, sourceIp: string) {
let result = conns;
if (keyword !== '') {
result = conns.filter((conn) =>
[
conn.host,
conn.sourceIP,
conn.sourcePort,
conn.destinationIP,
conn.chains,
conn.rule,
conn.type,
conn.network,
conn.processPath,
].some((field) => {
return hasSubstring(field, keyword);
}),
);
}
if (sourceIp !== '') {
result = filterConnIps(result, sourceIp);
}
// result.forEach((e) => console.log(e.sourceIP));
return result;
}

function fmtConnItem(
Expand Down Expand Up @@ -127,12 +146,28 @@ function connQty({ qty }) {
export default function Conn() {
const apiConfig = useApiConfig();
const [refContainer, containerHeight] = useRemainingViewPortHeight();

const [isExtraModalOpen, setIsExtraModalOpen] = useState(false);

const [conns, setConns] = useState([]);
const [closedConns, setClosedConns] = useState([]);

const [filterKeyword, setFilterKeyword] = useState('');
const filteredConns = filterConns(conns, filterKeyword);
const filteredClosedConns = filterConns(closedConns, filterKeyword);
const [filterSourceIpStr, setFilterSourceIpStr] = useState('');

const filteredConns = filterConns(conns, filterKeyword, filterSourceIpStr);
const filteredClosedConns = filterConns(closedConns, filterKeyword, filterSourceIpStr);

const connIpSet = getConnIpList(conns);

const [isCloseAllModalOpen, setIsCloseAllModalOpen] = useState(false);
const openExtraModal = useCallback(() => {
setIsExtraModalOpen(true);
}, []);
const closeExtraModal = useCallback(() => {
setIsExtraModalOpen(false);
}, []);

const openCloseAllModal = useCallback(() => setIsCloseAllModalOpen(true), []);
const closeCloseAllModal = useCallback(() => setIsCloseAllModalOpen(false), []);
const [isRefreshPaused, setIsRefreshPaused] = useState(false);
Expand Down Expand Up @@ -176,7 +211,12 @@ export default function Conn() {

return (
<div>
<ContentHeader title={t('Connections')} />
<ContentHeader title={`${t('Connections')} : ${filterSourceIpStr}`} />
<BaseModal isOpen={isExtraModalOpen} onRequestClose={closeExtraModal}>
<span>{t('pleaseSelectSourceIP')}</span>
<hr />
<SourceIP connIPset={connIpSet} setFilterIpStr={setFilterSourceIpStr} />
</BaseModal>
<Tabs>
<div
style={{
Expand All @@ -195,15 +235,18 @@ export default function Conn() {
<span className={s.connQty}>{connQty({ qty: filteredClosedConns.length })}</span>
</Tab>
</TabList>
<div className={s.inputWrapper}>
<div className={s.filterWrapper}>
<input
type="text"
name="filter"
autoComplete="off"
className={s.input}
placeholder="Filter"
placeholder="Filter By Keyword"
onChange={(e) => setFilterKeyword(e.target.value)}
/>
<Button className={s.button} onClick={openExtraModal}>
{t('filterByIP')}
</Button>
</div>
</div>
<div ref={refContainer} style={{ padding: 30, paddingBottom, paddingTop: 0 }}>
Expand All @@ -214,7 +257,8 @@ export default function Conn() {
}}
>
<TabPanel>
<>{renderTableOrPlaceholder(filteredConns)}</>
{/* <SourceIP connIPset={connIpSet} setFilterIpStr={setFilterSourceIpStr} /> */}
{renderTableOrPlaceholder(filteredConns)}
<Fab
icon={isRefreshPaused ? <Play size={16} /> : <Pause size={16} />}
mainButtonStyles={isRefreshPaused ? { background: '#e74c3c' } : {}}
Expand All @@ -227,7 +271,10 @@ export default function Conn() {
</Action>
</Fab>
</TabPanel>
<TabPanel>{renderTableOrPlaceholder(filteredClosedConns)}</TabPanel>
<TabPanel>
{/* <SourceIP connIPset={ClosedConnIpSet} setFilterIpStr={setFilterSourceIpStr} /> */}
{renderTableOrPlaceholder(filteredClosedConns)}
</TabPanel>
</div>
</div>
<ModalCloseAllConnections
Expand Down
6 changes: 6 additions & 0 deletions src/components/SourceIP.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.src-ips{
display: flex;
flex-wrap: wrap;
max-width: 70vw;
overflow: hidden;
}
32 changes: 32 additions & 0 deletions src/components/SourceIP.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import './SourceIP.css';

import React from 'react';
import { useTranslation } from 'react-i18next';
import { List } from 'reselect/es/types';

import Button from './Button';

type Props = {
setFilterIpStr?: (string) => void;
connIPset?: List<string>;
};

function SourceIp({ setFilterIpStr: toggle, connIPset: conns }: Props) {
const { t } = useTranslation();
return (
<div className="src-ips">
<Button onClick={() => toggle('')} kind="minimal">
{t('All')}
</Button>
{conns.map((ip, index) => {
return (
<Button key={index} onClick={() => toggle(ip)} kind="minimal">
{ip}
</Button>
);
})}
</div>
);
}

export default SourceIp;
2 changes: 1 addition & 1 deletion src/components/proxies/Proxies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import Settings from 'src/components/proxies/Settings';
import BaseModal from 'src/components/shared/BaseModal';
import { connect, useStoreActions } from 'src/components/StateProvider';
import Equalizer from 'src/components/svg/Equalizer';
import { proxyFilterTextAtom } from 'src/store/proxies';
import {
fetchProxies,
getDelay,
getProxyGroupNames,
getProxyProviders,
getShowModalClosePrevConns,
proxyFilterTextAtom,
} from 'src/store/proxies';
import type { DelayMapping, DispatchFn, FormattedProxyProvider, State } from 'src/store/types';

Expand Down
3 changes: 3 additions & 0 deletions src/i18n/en.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const data = {
All: 'All',
Overview: 'Overview',
Proxies: 'Proxies',
Rules: 'Rules',
Expand Down Expand Up @@ -39,4 +40,6 @@ export const data = {
update_all_rule_provider: 'Update all rule providers',
update_all_proxy_provider: 'Update all proxy providers',
dark_mode_pure_black_toggle_label: 'Use pure black in dark mode',
pleaseSelectSourceIP: 'please select a source ip to sensor',
filterByIP: 'Filter By IP',
};
3 changes: 3 additions & 0 deletions src/i18n/zh.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const data = {
All: '所有',
Overview: '概览',
Proxies: '代理',
Rules: '规则',
Expand Down Expand Up @@ -39,4 +40,6 @@ export const data = {
update_all_rule_provider: '更新所有 rule provider',
update_all_proxy_provider: '更新所有 proxy providers',
dark_mode_pure_black_toggle_label: '在黑色主题下使用纯黑背景',
pleaseSelectSourceIP: '请选择要审查的源ip',
filterByIP: '从源IP筛选',
};