mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-29 07:56:37 +08:00
fix(forward): show all users by default in non-compact mode
Restore 2.1.8-beta9 admin default filtering when compact mode is off; keep compact mode focused on self.
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
---
|
||||
name: security-scan
|
||||
description: Scan your Claude Code configuration (.claude/ directory) for security vulnerabilities, misconfigurations, and injection risks using AgentShield. Checks CLAUDE.md, settings.json, MCP servers, hooks, and agent definitions.
|
||||
origin: ECC
|
||||
---
|
||||
|
||||
# Security Scan Skill
|
||||
|
||||
Audit your Claude Code configuration for security issues using [AgentShield](https://github.com/affaan-m/agentshield).
|
||||
|
||||
## When to Activate
|
||||
|
||||
- Setting up a new Claude Code project
|
||||
- After modifying `.claude/settings.json`, `CLAUDE.md`, or MCP configs
|
||||
- Before committing configuration changes
|
||||
- When onboarding to a new repository with existing Claude Code configs
|
||||
- Periodic security hygiene checks
|
||||
|
||||
## What It Scans
|
||||
|
||||
| File | Checks |
|
||||
|------|--------|
|
||||
| `CLAUDE.md` | Hardcoded secrets, auto-run instructions, prompt injection patterns |
|
||||
| `settings.json` | Overly permissive allow lists, missing deny lists, dangerous bypass flags |
|
||||
| `mcp.json` | Risky MCP servers, hardcoded env secrets, npx supply chain risks |
|
||||
| `hooks/` | Command injection via interpolation, data exfiltration, silent error suppression |
|
||||
| `agents/*.md` | Unrestricted tool access, prompt injection surface, missing model specs |
|
||||
|
||||
## Prerequisites
|
||||
|
||||
AgentShield must be installed. Check and install if needed:
|
||||
|
||||
```bash
|
||||
# Check if installed
|
||||
npx ecc-agentshield --version
|
||||
|
||||
# Install globally (recommended)
|
||||
npm install -g ecc-agentshield
|
||||
|
||||
# Or run directly via npx (no install needed)
|
||||
npx ecc-agentshield scan .
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Scan
|
||||
|
||||
Run against the current project's `.claude/` directory:
|
||||
|
||||
```bash
|
||||
# Scan current project
|
||||
npx ecc-agentshield scan
|
||||
|
||||
# Scan a specific path
|
||||
npx ecc-agentshield scan --path /path/to/.claude
|
||||
|
||||
# Scan with minimum severity filter
|
||||
npx ecc-agentshield scan --min-severity medium
|
||||
```
|
||||
|
||||
### Output Formats
|
||||
|
||||
```bash
|
||||
# Terminal output (default) — colored report with grade
|
||||
npx ecc-agentshield scan
|
||||
|
||||
# JSON — for CI/CD integration
|
||||
npx ecc-agentshield scan --format json
|
||||
|
||||
# Markdown — for documentation
|
||||
npx ecc-agentshield scan --format markdown
|
||||
|
||||
# HTML — self-contained dark-theme report
|
||||
npx ecc-agentshield scan --format html > security-report.html
|
||||
```
|
||||
|
||||
### Auto-Fix
|
||||
|
||||
Apply safe fixes automatically (only fixes marked as auto-fixable):
|
||||
|
||||
```bash
|
||||
npx ecc-agentshield scan --fix
|
||||
```
|
||||
|
||||
This will:
|
||||
- Replace hardcoded secrets with environment variable references
|
||||
- Tighten wildcard permissions to scoped alternatives
|
||||
- Never modify manual-only suggestions
|
||||
|
||||
### Opus 4.6 Deep Analysis
|
||||
|
||||
Run the adversarial three-agent pipeline for deeper analysis:
|
||||
|
||||
```bash
|
||||
# Requires ANTHROPIC_API_KEY
|
||||
export ANTHROPIC_API_KEY=your-key
|
||||
npx ecc-agentshield scan --opus --stream
|
||||
```
|
||||
|
||||
This runs:
|
||||
1. **Attacker (Red Team)** — finds attack vectors
|
||||
2. **Defender (Blue Team)** — recommends hardening
|
||||
3. **Auditor (Final Verdict)** — synthesizes both perspectives
|
||||
|
||||
### Initialize Secure Config
|
||||
|
||||
Scaffold a new secure `.claude/` configuration from scratch:
|
||||
|
||||
```bash
|
||||
npx ecc-agentshield init
|
||||
```
|
||||
|
||||
Creates:
|
||||
- `settings.json` with scoped permissions and deny list
|
||||
- `CLAUDE.md` with security best practices
|
||||
- `mcp.json` placeholder
|
||||
|
||||
### GitHub Action
|
||||
|
||||
Add to your CI pipeline:
|
||||
|
||||
```yaml
|
||||
- uses: affaan-m/agentshield@v1
|
||||
with:
|
||||
path: '.'
|
||||
min-severity: 'medium'
|
||||
fail-on-findings: true
|
||||
```
|
||||
|
||||
## Severity Levels
|
||||
|
||||
| Grade | Score | Meaning |
|
||||
|-------|-------|---------|
|
||||
| A | 90-100 | Secure configuration |
|
||||
| B | 75-89 | Minor issues |
|
||||
| C | 60-74 | Needs attention |
|
||||
| D | 40-59 | Significant risks |
|
||||
| F | 0-39 | Critical vulnerabilities |
|
||||
|
||||
## Interpreting Results
|
||||
|
||||
### Critical Findings (fix immediately)
|
||||
- Hardcoded API keys or tokens in config files
|
||||
- `Bash(*)` in the allow list (unrestricted shell access)
|
||||
- Command injection in hooks via `${file}` interpolation
|
||||
- Shell-running MCP servers
|
||||
|
||||
### High Findings (fix before production)
|
||||
- Auto-run instructions in CLAUDE.md (prompt injection vector)
|
||||
- Missing deny lists in permissions
|
||||
- Agents with unnecessary Bash access
|
||||
|
||||
### Medium Findings (recommended)
|
||||
- Silent error suppression in hooks (`2>/dev/null`, `|| true`)
|
||||
- Missing PreToolUse security hooks
|
||||
- `npx -y` auto-install in MCP server configs
|
||||
|
||||
### Info Findings (awareness)
|
||||
- Missing descriptions on MCP servers
|
||||
- Prohibitive instructions correctly flagged as good practice
|
||||
|
||||
## Links
|
||||
|
||||
- **GitHub**: [github.com/affaan-m/agentshield](https://github.com/affaan-m/agentshield)
|
||||
- **npm**: [npmjs.com/package/ecc-agentshield](https://www.npmjs.com/package/ecc-agentshield)
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../.agents/skills/security-scan
|
||||
@@ -0,0 +1,62 @@
|
||||
# 功能请求:在规则页面显示隧道倍率
|
||||
|
||||
## 问题描述
|
||||
|
||||
当前规则(Forward)页面在列表中显示隧道名称,但**不显示隧道的流量倍率(trafficRatio)**。管理员在管理规则时无法快速查看该规则所使用的隧道倍率信息,需要跳转到隧道页面才能查看。
|
||||
|
||||
## 期望行为
|
||||
|
||||
在规则列表页面中,在隧道名称旁边或单独列显示该隧道的流量倍率(例如:`1x`, `0.5x`, `2x`)。
|
||||
|
||||
## 建议实现位置
|
||||
|
||||
### 前端修改
|
||||
|
||||
1. **`vite-frontend/src/pages/forward.tsx`**
|
||||
- 在 `Forward` interface 中添加 `tunnelTrafficRatio?: number` 字段
|
||||
- 在表格列中添加倍率显示(可以在隧道名称 Chip 旁边或单独一列)
|
||||
- 从 `userTunnel` 或 `getTunnelList` API 获取隧道倍率信息
|
||||
|
||||
2. **显示格式建议**
|
||||
```tsx
|
||||
<Chip className="...">
|
||||
{forward.tunnelName} ({forward.tunnelTrafficRatio}x)
|
||||
</Chip>
|
||||
```
|
||||
或者单独一列:
|
||||
```tsx
|
||||
<TableCell>
|
||||
{forward.tunnelTrafficRatio}x
|
||||
</TableCell>
|
||||
```
|
||||
|
||||
### 后端修改
|
||||
|
||||
1. **`go-backend/internal/http/handler/handler.go`**
|
||||
- 在 `forwardList` 接口返回中添加隧道的 `trafficRatio` 字段
|
||||
- 需要在查询 Forward 时 JOIN Tunnel 表获取倍率信息
|
||||
|
||||
2. **或者在前端加载规则后,批量获取隧道信息**
|
||||
- 调用 `getTunnelList` 获取所有隧道信息
|
||||
- 根据 `tunnelId` 匹配倍率
|
||||
|
||||
## 相关文件
|
||||
|
||||
- 前端:`vite-frontend/src/pages/forward.tsx`
|
||||
- 前端类型:`vite-frontend/src/api/types.ts`
|
||||
- 后端:`go-backend/internal/http/handler/handler.go`
|
||||
- 隧道类型定义:`vite-frontend/src/api/types.ts` (TunnelApiItem)
|
||||
|
||||
## 优先级
|
||||
|
||||
中等 - 不影响核心功能,但能提升管理效率
|
||||
|
||||
## 截图参考
|
||||
|
||||
隧道页面已显示倍率:
|
||||
- 位置:隧道卡片统计信息区域
|
||||
- 显示格式:`流量倍率 {trafficRatio}x`
|
||||
|
||||
---
|
||||
|
||||
**Labels**: `enhancement`, `frontend`, `backend`, `ui/ux`
|
||||
@@ -0,0 +1,178 @@
|
||||
# GOST UDP vs Realm UDP Analysis
|
||||
|
||||
**Goal:** Compare GOST UDP forwarding through tunnels with Realm's UDP implementation to understand why users report GOST UDP forwarding has problems while Realm works correctly.
|
||||
|
||||
## Task Checklist
|
||||
|
||||
- [x] Analyze GOST UDP tunnel architecture
|
||||
- [x] Analyze Realm UDP relay architecture
|
||||
- [x] Identify architectural differences
|
||||
- [x] Identify potential issues in GOST implementation
|
||||
- [ ] Document findings and recommendations
|
||||
|
||||
---
|
||||
|
||||
## GOST UDP Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **UDP Relay** (`x/internal/net/udp/relay.go`)
|
||||
- Simple bidirectional packet copying between two `net.PacketConn` interfaces
|
||||
- Uses two goroutines: one for each direction
|
||||
- Sequential packet processing (no batching)
|
||||
- No idle timeout or association tracking
|
||||
|
||||
2. **UDP over Tunnel** (`x/handler/relay/bind.go`, `x/handler/socks/v5/udp_tun.go`)
|
||||
- Wraps UDP data with SOCKS5-style framing via `UDPTunServerConn()`
|
||||
- Adds address headers to each packet
|
||||
- Uses smux multiplexing for tunnel connections
|
||||
|
||||
3. **SOCKS5 UDP Framing** (`x/internal/util/socks/conn.go`, `x/internal/util/relay/conn.go`)
|
||||
- `udpTunConn.ReadFrom()`: Parses SOCKS5 UDP header to extract target address
|
||||
- `udpTunConn.WriteTo()`: Wraps data with SOCKS5 UDP header including:
|
||||
- RSV (data length, 2 bytes)
|
||||
- Frag (0xff for tunnel relay, 1 byte)
|
||||
- Address (4/16/1+n bytes for IPv4/IPv6/domain)
|
||||
|
||||
4. **Multiplexing** (`x/internal/util/mux/mux.go`)
|
||||
- Uses smux v1.5.31 for connection multiplexing
|
||||
- Adds stream framing and flow control overhead
|
||||
|
||||
### Data Flow (UDP over Tunnel)
|
||||
|
||||
```
|
||||
Client UDP packet
|
||||
↓
|
||||
[Local GOST] SOCKS5 UDP framing (add ~10-26 bytes)
|
||||
↓
|
||||
smux stream (add framing, flow control)
|
||||
↓
|
||||
TCP tunnel to remote
|
||||
↓
|
||||
[Remote GOST] smux demux
|
||||
↓
|
||||
SOCKS5 UDP deframing
|
||||
↓
|
||||
Forward to target
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Realm UDP Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **Association Model** (`realm_core/src/udp/middle.rs`)
|
||||
- Per-client socket associations stored in `SockMap`
|
||||
- Creates dedicated remote socket per client address
|
||||
- Spawns `send_back` task for return path
|
||||
- Association timeout for cleanup
|
||||
|
||||
2. **Batched I/O** (`realm_core/src/udp/batched.rs`)
|
||||
- Uses `recvmmsg/sendmmsg` on Linux
|
||||
- Up to 128 packets per batch
|
||||
- Significantly higher throughput for high-PPS traffic
|
||||
|
||||
3. **No Protocol Overhead**
|
||||
- Plain UDP forwarding without adding protocol headers
|
||||
- No SOCKS5 framing or mux layer
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
Client UDP packet
|
||||
↓
|
||||
[Realm] Direct relay via association socket
|
||||
↓
|
||||
Target server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Architectural Differences
|
||||
|
||||
| Aspect | GOST | Realm |
|
||||
|--------|------|-------|
|
||||
| **UDP over Tunnel** | SOCKS5 framing + smux multiplexing | N/A (direct relay only) |
|
||||
| **Protocol Overhead** | Extra ~10-26 bytes per packet | None |
|
||||
| **I/O Model** | Standard Go net.PacketConn | Batched I/O (recvmmsg/sendmmsg) |
|
||||
| **Connection Tracking** | Via mux session | SockMap with timeout |
|
||||
| **Association Model** | None (bidirectional copy only) | Per-client socket association |
|
||||
| **Idle Timeout** | None on UDP relay | Association timeout |
|
||||
| **Throughput** | Single packet per syscall | Up to 128 packets per syscall |
|
||||
|
||||
---
|
||||
|
||||
## Identified Potential Issues in GOST
|
||||
|
||||
### 1. No Batch I/O Support
|
||||
- **Impact**: High PPS (packets per second) traffic incurs syscall overhead
|
||||
- **Realm advantage**: `recvmmsg/sendmmsg` batches up to 128 packets
|
||||
- **Evidence**: Realm's `batched.rs` implements this; GOST uses standard `ReadFrom/WriteTo`
|
||||
|
||||
### 2. Protocol Overhead Per Packet
|
||||
- **Impact**: Bandwidth waste, extra processing for framing/deframing
|
||||
- **Overhead**: SOCKS5 UDP header adds ~10 bytes (IPv4) to ~26 bytes (IPv6) per packet
|
||||
- **Evidence**: `x/internal/util/socks/conn.go:63-78` shows framing overhead
|
||||
|
||||
### 3. Mux Layer Overhead
|
||||
- **Impact**: Latency and throughput degradation
|
||||
- **smux adds**: Stream framing, flow control, potential backpressure
|
||||
- **Evidence**: `x/internal/util/mux/mux.go` wraps all tunnel connections
|
||||
|
||||
### 4. No Association Tracking
|
||||
- **Impact**: Cannot properly handle NAT translation for return traffic
|
||||
- **Realm approach**: `SockMap` tracks client→remote socket mapping
|
||||
- **Evidence**: GOST's `udp.Relay` just copies packets bidirectionally
|
||||
|
||||
### 5. No Idle Timeout on UDP Relay
|
||||
- **Impact**: Stale connections may persist indefinitely
|
||||
- **Evidence**: `udp.Relay.Run()` blocks until error or context cancel
|
||||
- **Contrast**: Realm has association timeout for cleanup
|
||||
|
||||
### 6. Sequential Packet Processing
|
||||
- **Impact**: Cannot pipeline multiple packets
|
||||
- **Evidence**: `relay.go:44-77` shows sequential `ReadFrom` → `WriteTo` loop
|
||||
- **Contrast**: Realm's batched I/O handles multiple packets concurrently
|
||||
|
||||
### 7. Read Deadline on Underlying TCP
|
||||
- **Impact**: Could cause unexpected connection termination
|
||||
- **Default**: 15-second read timeout set on connections
|
||||
- **Evidence**: `x/handler/relay/metadata.go:readTimeout` defaults to 15s
|
||||
- **Issue**: UDP relay may not handle deadline properly
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. **Consider Direct UDP Mode**: For scenarios where tunnel is not required, use direct UDP relay (no SOCKS5 framing)
|
||||
|
||||
2. **Add Association Tracking**: Implement client→remote socket mapping with timeout
|
||||
|
||||
3. **Investigate Batch I/O**: Consider using `recvmmsg/sendmmsg` equivalent in Go (via `x/net` or raw syscalls)
|
||||
|
||||
4. **Add Idle Timeout**: Implement timeout-based cleanup for UDP associations
|
||||
|
||||
5. **Reduce Framing Overhead**: Consider more compact framing for tunnel UDP
|
||||
|
||||
---
|
||||
|
||||
## Files Analyzed
|
||||
|
||||
### GOST Files
|
||||
- `go-gost/x/internal/net/udp/relay.go` - Core UDP relay logic
|
||||
- `go-gost/x/internal/util/mux/mux.go` - smux multiplexing
|
||||
- `go-gost/x/internal/util/socks/conn.go` - SOCKS5 UDP framing
|
||||
- `go-gost/x/internal/util/relay/conn.go` - Relay UDP framing
|
||||
- `go-gost/x/handler/relay/bind.go` - Relay BIND handler
|
||||
- `go-gost/x/handler/socks/v5/udp_tun.go` - SOCKS5 UDP tunnel handler
|
||||
- `go-gost/x/handler/tunnel/bind.go` - Tunnel BIND handler
|
||||
- `go-gost/x/connector/tunnel/bind.go` - Tunnel connector BIND
|
||||
- `go-gost/x/connector/tunnel/conn.go` - Tunnel connection types
|
||||
- `go-gost/x/connector/tunnel/listener.go` - Tunnel bind listener
|
||||
|
||||
### Realm Files (fetched from GitHub)
|
||||
- `realm_core/src/udp/mod.rs` - UDP relay entry point
|
||||
- `realm_core/src/udp/middle.rs` - Association and relay logic with SockMap
|
||||
- `realm_core/src/udp/socket.rs` - UDP socket binding and association
|
||||
- `realm_core/src/udp/batched.rs` - Batched I/O implementation
|
||||
@@ -0,0 +1,18 @@
|
||||
# Node Card Info Popover
|
||||
|
||||
## Goal
|
||||
|
||||
Restore node card's remark and renewal info display to the 2.1.8-beta9 style: an info button (ℹ️) in the CardHeader that shows a hover popover with the info, instead of the current inline display in the CardBody.
|
||||
|
||||
## Status: Completed
|
||||
|
||||
PR #327 merged
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] Add `infoPopoverPlacement` state and `updateInfoPopoverPlacement` callback
|
||||
- [x] Add info button with popover to CardHeader
|
||||
- [x] Restore drag handle with touch support
|
||||
- [x] Remove inline info display from CardBody
|
||||
- [x] Fix build errors (JSX structure and unused variables)
|
||||
- [x] Create PR and merge
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": 1,
|
||||
"skills": {
|
||||
"security-scan": {
|
||||
"source": "affaan-m/everything-claude-code",
|
||||
"sourceType": "github",
|
||||
"computedHash": "92cdcaddc554e318402f066ccc073c2e3dbcfda8c2730ec62ec373f805c41a57"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -784,9 +784,9 @@ const SortableTableRow = ({
|
||||
className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.inbound} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all hover:bg-default-200 hover:shadow-sm cursor-pointer"
|
||||
title={formatInAddress(forward.inIp, forward.inPort)}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
showAddressModal(forward.inIp, forward.inPort, "入口端口")
|
||||
}
|
||||
@@ -798,12 +798,10 @@ const SortableTableRow = ({
|
||||
className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.target} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all hover:bg-default-200 hover:shadow-sm cursor-pointer"
|
||||
title={formatRemoteAddress(forward.remoteAddr)}
|
||||
onClick={() =>
|
||||
showAddressModal(forward.remoteAddr, null, "目标地址")
|
||||
}
|
||||
type="button"
|
||||
onClick={() => showAddressModal(forward.remoteAddr, null, "目标地址")}
|
||||
>
|
||||
{formatRemoteAddress(forward.remoteAddr)}
|
||||
</button>
|
||||
@@ -1010,7 +1008,9 @@ const SortableCompactTableRow = ({
|
||||
}`}
|
||||
title={formatInAddress(forward.inIp, forward.inPort)}
|
||||
type="button"
|
||||
onClick={() => showAddressModal(forward.inIp, forward.inPort, "入口端口")}
|
||||
onClick={() =>
|
||||
showAddressModal(forward.inIp, forward.inPort, "入口端口")
|
||||
}
|
||||
>
|
||||
{formatInAddress(forward.inIp, forward.inPort)}
|
||||
</button>
|
||||
@@ -1143,23 +1143,80 @@ export default function ForwardPage() {
|
||||
const tokenRoleId = JwtUtil.getRoleIdFromToken();
|
||||
const isAdmin = tokenRoleId === 0;
|
||||
|
||||
const [compactMode, setCompactMode] = useState(false);
|
||||
|
||||
// 在非精简模式下(compactMode=false),管理员默认展示全部用户规则(与 2.1.8-beta9 行为一致)。
|
||||
// 精简模式下默认仅展示管理员本人规则,避免在表格里混看不清所属用户。
|
||||
const defaultSearchUserId = useMemo(() => {
|
||||
if (isAdmin) {
|
||||
if (!compactMode) {
|
||||
return "all";
|
||||
}
|
||||
|
||||
return tokenUserId ? tokenUserId.toString() : "all";
|
||||
}
|
||||
|
||||
return tokenUserId ? tokenUserId.toString() : "all";
|
||||
}, [compactMode, isAdmin, tokenUserId]);
|
||||
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
name: "",
|
||||
userId: tokenUserId ? tokenUserId.toString() : "all",
|
||||
userId: defaultSearchUserId,
|
||||
tunnelId: "all",
|
||||
inPort: "",
|
||||
remoteAddr: "",
|
||||
});
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
|
||||
|
||||
const resetSearchParams = useCallback(() => {
|
||||
setSearchParams({
|
||||
name: "",
|
||||
userId: defaultSearchUserId,
|
||||
tunnelId: "all",
|
||||
inPort: "",
|
||||
remoteAddr: "",
|
||||
});
|
||||
}, [defaultSearchUserId]);
|
||||
|
||||
const lastDefaultSearchUserIdRef = useRef(defaultSearchUserId);
|
||||
|
||||
useEffect(() => {
|
||||
const previousDefault = lastDefaultSearchUserIdRef.current;
|
||||
|
||||
lastDefaultSearchUserIdRef.current = defaultSearchUserId;
|
||||
|
||||
if (!isAdmin || previousDefault === defaultSearchUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSearchParams((prev) => {
|
||||
const hasOtherFilters =
|
||||
Boolean(prev.name.trim()) ||
|
||||
prev.tunnelId !== "all" ||
|
||||
Boolean(prev.inPort.trim()) ||
|
||||
Boolean(prev.remoteAddr.trim());
|
||||
|
||||
if (hasOtherFilters) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
if (prev.userId !== previousDefault) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
userId: defaultSearchUserId,
|
||||
};
|
||||
});
|
||||
}, [defaultSearchUserId, isAdmin]);
|
||||
|
||||
const activeFilterCount =
|
||||
(searchParams.name ? 1 : 0) +
|
||||
(searchParams.userId !== "all" &&
|
||||
searchParams.userId !== (tokenUserId ? tokenUserId.toString() : "all")
|
||||
? 1
|
||||
: 0) +
|
||||
(searchParams.name.trim() ? 1 : 0) +
|
||||
(searchParams.userId !== defaultSearchUserId ? 1 : 0) +
|
||||
(searchParams.tunnelId !== "all" ? 1 : 0) +
|
||||
(searchParams.inPort ? 1 : 0) +
|
||||
(searchParams.remoteAddr ? 1 : 0);
|
||||
(searchParams.inPort.trim() ? 1 : 0) +
|
||||
(searchParams.remoteAddr.trim() ? 1 : 0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [forwards, setForwards] = useState<Forward[]>([]);
|
||||
const [tunnels, setTunnels] = useState<Tunnel[]>([]);
|
||||
@@ -1169,7 +1226,6 @@ export default function ForwardPage() {
|
||||
// const isMobile = useMobileBreakpoint();
|
||||
// searchKeyword removed
|
||||
// isSearchVisible removed
|
||||
const [compactMode, setCompactMode] = useState(false);
|
||||
|
||||
// 显示模式状态 - 从localStorage读取,默认为平铺显示
|
||||
const [viewMode, setViewMode] = useState<"grouped" | "direct">(() => {
|
||||
@@ -3930,15 +3986,7 @@ export default function ForwardPage() {
|
||||
color="danger"
|
||||
size="sm"
|
||||
variant="light"
|
||||
onPress={() =>
|
||||
setSearchParams({
|
||||
name: "",
|
||||
userId: tokenUserId ? tokenUserId.toString() : "all",
|
||||
tunnelId: "all",
|
||||
inPort: "",
|
||||
remoteAddr: "",
|
||||
})
|
||||
}
|
||||
onPress={resetSearchParams}
|
||||
>
|
||||
清空条件
|
||||
</Button>
|
||||
@@ -6061,15 +6109,7 @@ export default function ForwardPage() {
|
||||
<Button
|
||||
color="primary"
|
||||
variant="flat"
|
||||
onPress={() => {
|
||||
setSearchParams({
|
||||
name: "",
|
||||
userId: tokenUserId ? tokenUserId.toString() : "all",
|
||||
tunnelId: "all",
|
||||
inPort: "",
|
||||
remoteAddr: "",
|
||||
});
|
||||
}}
|
||||
onPress={resetSearchParams}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user