diff --git a/go-gost/gost b/go-gost/gost index e2bec7d..43ac09c 100755 Binary files a/go-gost/gost and b/go-gost/gost differ diff --git a/panel_install.sh b/panel_install.sh index 96ebf88..a1d5e68 100755 --- a/panel_install.sh +++ b/panel_install.sh @@ -172,6 +172,8 @@ get_config_params() { echo "🔧 请输入配置参数:" echo "📡 节点端服务器和面板通信的地址,需要能正常访问(IPv6不需要加[])" + echo "不要套CDN,不支持HTTPS,直接输入面板服务器的ip" + echo "用于节点和面板通讯的地址,节点和面板通讯内容有加密!!!!!" while true; do read -p "当前面板服务器地址: " SERVER_HOST if [ -n "$SERVER_HOST" ]; then diff --git a/springboot-backend/src/main/java/com/admin/common/dto/ResetFlowDto.java b/springboot-backend/src/main/java/com/admin/common/dto/ResetFlowDto.java new file mode 100644 index 0000000..8854cfc --- /dev/null +++ b/springboot-backend/src/main/java/com/admin/common/dto/ResetFlowDto.java @@ -0,0 +1,16 @@ +package com.admin.common.dto; + + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Data +public class ResetFlowDto { + + @NotNull(message = "重置账号id不能为空") + private Integer id; + + @NotNull(message = "重置类型不能为空") + private Integer type; +} diff --git a/springboot-backend/src/main/java/com/admin/controller/UserController.java b/springboot-backend/src/main/java/com/admin/controller/UserController.java index 20966d7..8ee8573 100644 --- a/springboot-backend/src/main/java/com/admin/controller/UserController.java +++ b/springboot-backend/src/main/java/com/admin/controller/UserController.java @@ -3,11 +3,7 @@ package com.admin.controller; import com.admin.common.aop.LogAnnotation; import com.admin.common.annotation.RequireRole; -import com.admin.common.dto.ChangePasswordDto; -import com.admin.common.dto.LoginDto; -import com.admin.common.dto.PageDto; -import com.admin.common.dto.UserDto; -import com.admin.common.dto.UserUpdateDto; +import com.admin.common.dto.*; import com.admin.common.lang.R; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -78,4 +74,14 @@ public class UserController extends BaseController { public R updatePassword(@Validated @RequestBody ChangePasswordDto changePasswordDto) { return userService.updatePassword(changePasswordDto); } + + @LogAnnotation + @RequireRole + @PostMapping("/reset") + public R reset(@Validated @RequestBody ResetFlowDto resetFlowDto) { + return userService.reset(resetFlowDto); + } + + + } diff --git a/springboot-backend/src/main/java/com/admin/service/UserService.java b/springboot-backend/src/main/java/com/admin/service/UserService.java index 76fa03c..70e16d2 100644 --- a/springboot-backend/src/main/java/com/admin/service/UserService.java +++ b/springboot-backend/src/main/java/com/admin/service/UserService.java @@ -1,10 +1,6 @@ package com.admin.service; -import com.admin.common.dto.ChangePasswordDto; -import com.admin.common.dto.LoginDto; -import com.admin.common.dto.PageDto; -import com.admin.common.dto.UserDto; -import com.admin.common.dto.UserUpdateDto; +import com.admin.common.dto.*; import com.admin.common.lang.R; import com.admin.entity.User; import com.baomidou.mybatisplus.extension.service.IService; @@ -32,4 +28,6 @@ public interface UserService extends IService { R getUserPackageInfo(); R updatePassword(ChangePasswordDto changePasswordDto); + + R reset(ResetFlowDto resetFlowDto); } diff --git a/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java index 536edb0..1af9a20 100644 --- a/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java +++ b/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java @@ -2,13 +2,7 @@ package com.admin.service.impl; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; -import com.admin.common.dto.ChangePasswordDto; -import com.admin.common.dto.LoginDto; -import com.admin.common.dto.PageDto; -import com.admin.common.dto.UserDto; -import com.admin.common.dto.UserUpdateDto; -import com.admin.common.dto.UserPackageDto; -import com.admin.common.dto.GostDto; +import com.admin.common.dto.*; import com.admin.common.lang.R; import com.admin.common.task.DelayQueueManager; import com.admin.common.task.DelayTask; @@ -351,6 +345,24 @@ public class UserServiceImpl extends ServiceImpl implements Us } } + @Override + public R reset(ResetFlowDto resetFlowDto) { + if (resetFlowDto.getType() == 1){ // 清零账号流量 + User user = this.getById(resetFlowDto.getId()); + if (user == null) return R.err(ERROR_USER_NOT_FOUND); + user.setInFlow(0L); + user.setOutFlow(0L); + this.updateById(user); + }else { // 清零隧道流量 + UserTunnel tunnel = userTunnelService.getById(resetFlowDto.getId()); + if (tunnel == null) return R.err("隧道不存在"); + tunnel.setInFlow(0L); + tunnel.setOutFlow(0L); + userTunnelService.updateById(tunnel); + } + return R.ok(); + } + // ========== 私有辅助方法 ========== /** @@ -691,7 +703,7 @@ public class UserServiceImpl extends ServiceImpl implements Us UserPackageDto.UserInfoDto userInfo = buildUserInfoDto(user); // 2. 获取隧道权限详情 - List tunnelPermissions = getTunnelPermissions(user.getId(), roleId); + List tunnelPermissions = getTunnelPermissions(user.getId()); // 3. 获取转发详情 List forwards = userMapper.getUserForwardDetails(user.getId().intValue()); @@ -731,15 +743,10 @@ public class UserServiceImpl extends ServiceImpl implements Us * 获取隧道权限详情 * * @param userId 用户ID - * @param roleId 角色ID * @return 隧道权限详情列表 */ - private List getTunnelPermissions(Long userId, Integer roleId) { - if (roleId != null && roleId == ADMIN_ROLE_ID) { - return userMapper.getAllTunnelsForAdmin(); - } else { - return userMapper.getUserTunnelDetails(userId.intValue()); - } + private List getTunnelPermissions(Long userId) { + return userMapper.getUserTunnelDetails(userId.intValue()); } /** diff --git a/vite-frontend/src/api/index.ts b/vite-frontend/src/api/index.ts index f939ccf..7a2886e 100644 --- a/vite-frontend/src/api/index.ts +++ b/vite-frontend/src/api/index.ts @@ -72,6 +72,9 @@ export const deleteSpeedLimit = (id: number) => Network.post("/speed-limit/delet // 修改密码接口 export const updatePassword = (data: any) => Network.post("/user/updatePassword", data); +// 重置流量接口 +export const resetUserFlow = (data: { id: number; type: number }) => Network.post("/user/reset", data); + // 网站配置相关接口 export const getConfigs = () => Network.post("/config/list"); export const getConfigByName = (name: string) => Network.post("/config/get", { name }); diff --git a/vite-frontend/src/config/site.ts b/vite-frontend/src/config/site.ts index 5681291..63bc8ff 100644 --- a/vite-frontend/src/config/site.ts +++ b/vite-frontend/src/config/site.ts @@ -4,7 +4,7 @@ export type SiteConfig = typeof siteConfig; // 缓存相关常量 const CACHE_PREFIX = 'vite_config_'; -const VERSION = "1.2.0"; +const VERSION = "1.2.1"; // 缓存策略: // - 长期有效,不设置过期时间 diff --git a/vite-frontend/src/pages/dashboard.tsx b/vite-frontend/src/pages/dashboard.tsx index 6965320..5d617e4 100644 --- a/vite-frontend/src/pages/dashboard.tsx +++ b/vite-frontend/src/pages/dashboard.tsx @@ -774,11 +774,11 @@ export default function DashboardPage() {
-
入站
+
上传
{formatFlow(forward.inFlow || 0)}
-
出站
+
下载
{formatFlow(forward.outFlow || 0)}
diff --git a/vite-frontend/src/pages/forward.tsx b/vite-frontend/src/pages/forward.tsx index 6cd953e..975cfae 100644 --- a/vite-frontend/src/pages/forward.tsx +++ b/vite-frontend/src/pages/forward.tsx @@ -760,14 +760,15 @@ export default function ForwardPage() { {strategyDisplay.text} - - {formatFlow((forward.inFlow || 0) + (forward.outFlow || 0))} - - {forward.userName && ( - - {forward.userName} +
+ + ↑{formatFlow(forward.inFlow || 0)} + + +
+ + ↓{formatFlow(forward.outFlow || 0)} - )}
diff --git a/vite-frontend/src/pages/node.tsx b/vite-frontend/src/pages/node.tsx index 7ee4e8e..cf13789 100644 --- a/vite-frontend/src/pages/node.tsx +++ b/vite-frontend/src/pages/node.tsx @@ -284,6 +284,34 @@ export default function NodePage() { return parseFloat((bytesPerSecond / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; + // 格式化开机时间 + const formatUptime = (seconds: number): string => { + if (seconds === 0) return '-'; + + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (days > 0) { + return `${days}天${hours}小时`; + } else if (hours > 0) { + return `${hours}小时${minutes}分钟`; + } else { + return `${minutes}分钟`; + } + }; + + // 格式化流量 + const formatTraffic = (bytes: number): string => { + if (bytes === 0) return '0 B'; + + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }; + // 获取进度条颜色 const getProgressColor = (value: number, offline = false): "default" | "primary" | "secondary" | "success" | "warning" | "danger" => { if (offline) return "default"; @@ -633,6 +661,15 @@ export default function NodePage() { 版本 {node.version || '未知'}
+
+ 开机时间 + + {node.connectionStatus === 'online' && node.systemInfo + ? formatUptime(node.systemInfo.uptime) + : '-' + } + +
{/* 系统监控 */} @@ -700,6 +737,28 @@ export default function NodePage() { + + {/* 流量统计 */} +
+
+
↑ 上行流量
+
+ {node.connectionStatus === 'online' && node.systemInfo + ? formatTraffic(node.systemInfo.uploadTraffic) + : '-' + } +
+
+
+
↓ 下行流量
+
+ {node.connectionStatus === 'online' && node.systemInfo + ? formatTraffic(node.systemInfo.downloadTraffic) + : '-' + } +
+
+
{/* 操作按钮 */} diff --git a/vite-frontend/src/pages/tunnel.tsx b/vite-frontend/src/pages/tunnel.tsx index ae81eab..26a5914 100644 --- a/vite-frontend/src/pages/tunnel.tsx +++ b/vite-frontend/src/pages/tunnel.tsx @@ -518,23 +518,17 @@ export default function TunnelPage() { {/* 配置信息 */}
-
流量计算
{getFlowDisplay(tunnel.flow)}
-
倍率
{tunnel.trafficRatio}x
- {/* 时间信息 */} -
- 创建时间: {formatTime(tunnel.createdTime)} -
diff --git a/vite-frontend/src/pages/user.tsx b/vite-frontend/src/pages/user.tsx index 8987511..1ede2a1 100644 --- a/vite-frontend/src/pages/user.tsx +++ b/vite-frontend/src/pages/user.tsx @@ -46,7 +46,8 @@ import { getUserTunnelList, removeUserTunnel, updateUserTunnel, - getSpeedLimitList + getSpeedLimitList, + resetUserFlow } from '@/api'; import { SearchIcon, PlusIcon, EditIcon, DeleteIcon, UserIcon, SettingsIcon } from '@/components/icons'; import { parseDate } from "@internationalized/date"; @@ -157,6 +158,16 @@ export default function UserPage() { const { isOpen: isDeleteTunnelModalOpen, onOpen: onDeleteTunnelModalOpen, onClose: onDeleteTunnelModalClose } = useDisclosure(); const [tunnelToDelete, setTunnelToDelete] = useState(null); + // 重置流量确认相关状态 + const { isOpen: isResetFlowModalOpen, onOpen: onResetFlowModalOpen, onClose: onResetFlowModalClose } = useDisclosure(); + const [userToReset, setUserToReset] = useState(null); + const [resetFlowLoading, setResetFlowLoading] = useState(false); + + // 重置隧道流量确认相关状态 + const { isOpen: isResetTunnelFlowModalOpen, onOpen: onResetTunnelFlowModalOpen, onClose: onResetTunnelFlowModalClose } = useDisclosure(); + const [tunnelToReset, setTunnelToReset] = useState(null); + const [resetTunnelFlowLoading, setResetTunnelFlowLoading] = useState(false); + // 其他数据 const [tunnels, setTunnels] = useState([]); const [speedLimits, setSpeedLimits] = useState([]); @@ -444,6 +455,70 @@ export default function UserPage() { } }; + // 重置流量相关函数 + const handleResetFlow = (user: User) => { + setUserToReset(user); + onResetFlowModalOpen(); + }; + + const handleConfirmResetFlow = async () => { + if (!userToReset) return; + + setResetFlowLoading(true); + try { + const response = await resetUserFlow({ + id: userToReset.id, + type: 1 // 1表示重置用户流量 + }); + + if (response.code === 0) { + toast.success('流量重置成功'); + onResetFlowModalClose(); + setUserToReset(null); + loadUsers(); // 重新加载用户列表 + } else { + toast.error(response.msg || '重置失败'); + } + } catch (error) { + toast.error('重置失败'); + } finally { + setResetFlowLoading(false); + } + }; + + // 隧道流量重置相关函数 + const handleResetTunnelFlow = (userTunnel: UserTunnel) => { + setTunnelToReset(userTunnel); + onResetTunnelFlowModalOpen(); + }; + + const handleConfirmResetTunnelFlow = async () => { + if (!tunnelToReset) return; + + setResetTunnelFlowLoading(true); + try { + const response = await resetUserFlow({ + id: tunnelToReset.id, + type: 2 // 2表示重置隧道流量 + }); + + if (response.code === 0) { + toast.success('隧道流量重置成功'); + onResetTunnelFlowModalClose(); + setTunnelToReset(null); + if (currentUser) { + loadUserTunnels(currentUser.id); // 重新加载隧道权限列表 + } + } else { + toast.error(response.msg || '重置失败'); + } + } catch (error) { + toast.error('重置失败'); + } finally { + setResetTunnelFlowLoading(false); + } + }; + // 过滤数据 const availableTunnels = tunnels.filter( tunnel => !userTunnels.some(ut => ut.tunnelId === tunnel.id) @@ -595,15 +670,16 @@ export default function UserPage() {
过期时间
-
{formatDate(user.expTime)}
- {expStatus && ( + {expStatus && expStatus.color === 'success' ? ( +
{formatDate(user.expTime)}
+ ) : ( - {expStatus.text} + {expStatus?.text || '未知状态'} )}
@@ -623,6 +699,20 @@ export default function UserPage() { > 编辑 + + + + + + + + {/* 重置隧道流量确认对话框 */} + + + + 确认重置隧道流量 + + +
+
+ + + +
+
+

+ 确定要重置用户 {currentUser?.user} 对隧道 "{tunnelToReset?.tunnelName}" 的流量吗? +

+

+ 该操作只会重置隧道权限流量不会重置账号流量,重置后该隧道权限的上下行流量将归零,此操作不可撤销。 +

+
+
+ 当前流量使用情况: +
+
+
+ 上行流量: + {tunnelToReset ? formatFlow(tunnelToReset.inFlow || 0) : '-'} +
+
+ 下行流量: + {tunnelToReset ? formatFlow(tunnelToReset.outFlow || 0) : '-'} +
+
+ 总计: + + {tunnelToReset ? formatFlow(calculateTunnelUsedFlow(tunnelToReset)) : '-'} + +
+
+
+
+
+
+ + + + +
+
);