mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-13 20:46:38 +08:00
优化显示,新增流量重置
This commit is contained in:
Binary file not shown.
@@ -172,6 +172,8 @@ get_config_params() {
|
|||||||
echo "🔧 请输入配置参数:"
|
echo "🔧 请输入配置参数:"
|
||||||
|
|
||||||
echo "📡 节点端服务器和面板通信的地址,需要能正常访问(IPv6不需要加[])"
|
echo "📡 节点端服务器和面板通信的地址,需要能正常访问(IPv6不需要加[])"
|
||||||
|
echo "不要套CDN,不支持HTTPS,直接输入面板服务器的ip"
|
||||||
|
echo "用于节点和面板通讯的地址,节点和面板通讯内容有加密!!!!!"
|
||||||
while true; do
|
while true; do
|
||||||
read -p "当前面板服务器地址: " SERVER_HOST
|
read -p "当前面板服务器地址: " SERVER_HOST
|
||||||
if [ -n "$SERVER_HOST" ]; then
|
if [ -n "$SERVER_HOST" ]; then
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -3,11 +3,7 @@ package com.admin.controller;
|
|||||||
|
|
||||||
import com.admin.common.aop.LogAnnotation;
|
import com.admin.common.aop.LogAnnotation;
|
||||||
import com.admin.common.annotation.RequireRole;
|
import com.admin.common.annotation.RequireRole;
|
||||||
import com.admin.common.dto.ChangePasswordDto;
|
import com.admin.common.dto.*;
|
||||||
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.lang.R;
|
import com.admin.common.lang.R;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -78,4 +74,14 @@ public class UserController extends BaseController {
|
|||||||
public R updatePassword(@Validated @RequestBody ChangePasswordDto changePasswordDto) {
|
public R updatePassword(@Validated @RequestBody ChangePasswordDto changePasswordDto) {
|
||||||
return userService.updatePassword(changePasswordDto);
|
return userService.updatePassword(changePasswordDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@LogAnnotation
|
||||||
|
@RequireRole
|
||||||
|
@PostMapping("/reset")
|
||||||
|
public R reset(@Validated @RequestBody ResetFlowDto resetFlowDto) {
|
||||||
|
return userService.reset(resetFlowDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package com.admin.service;
|
package com.admin.service;
|
||||||
|
|
||||||
import com.admin.common.dto.ChangePasswordDto;
|
import com.admin.common.dto.*;
|
||||||
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.lang.R;
|
import com.admin.common.lang.R;
|
||||||
import com.admin.entity.User;
|
import com.admin.entity.User;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
@@ -32,4 +28,6 @@ public interface UserService extends IService<User> {
|
|||||||
R getUserPackageInfo();
|
R getUserPackageInfo();
|
||||||
|
|
||||||
R updatePassword(ChangePasswordDto changePasswordDto);
|
R updatePassword(ChangePasswordDto changePasswordDto);
|
||||||
|
|
||||||
|
R reset(ResetFlowDto resetFlowDto);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,7 @@ package com.admin.service.impl;
|
|||||||
|
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.admin.common.dto.ChangePasswordDto;
|
import com.admin.common.dto.*;
|
||||||
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.lang.R;
|
import com.admin.common.lang.R;
|
||||||
import com.admin.common.task.DelayQueueManager;
|
import com.admin.common.task.DelayQueueManager;
|
||||||
import com.admin.common.task.DelayTask;
|
import com.admin.common.task.DelayTask;
|
||||||
@@ -351,6 +345,24 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> 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<UserMapper, User> implements Us
|
|||||||
UserPackageDto.UserInfoDto userInfo = buildUserInfoDto(user);
|
UserPackageDto.UserInfoDto userInfo = buildUserInfoDto(user);
|
||||||
|
|
||||||
// 2. 获取隧道权限详情
|
// 2. 获取隧道权限详情
|
||||||
List<UserPackageDto.UserTunnelDetailDto> tunnelPermissions = getTunnelPermissions(user.getId(), roleId);
|
List<UserPackageDto.UserTunnelDetailDto> tunnelPermissions = getTunnelPermissions(user.getId());
|
||||||
|
|
||||||
// 3. 获取转发详情
|
// 3. 获取转发详情
|
||||||
List<UserPackageDto.UserForwardDetailDto> forwards = userMapper.getUserForwardDetails(user.getId().intValue());
|
List<UserPackageDto.UserForwardDetailDto> forwards = userMapper.getUserForwardDetails(user.getId().intValue());
|
||||||
@@ -731,15 +743,10 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
* 获取隧道权限详情
|
* 获取隧道权限详情
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @param roleId 角色ID
|
|
||||||
* @return 隧道权限详情列表
|
* @return 隧道权限详情列表
|
||||||
*/
|
*/
|
||||||
private List<UserPackageDto.UserTunnelDetailDto> getTunnelPermissions(Long userId, Integer roleId) {
|
private List<UserPackageDto.UserTunnelDetailDto> getTunnelPermissions(Long userId) {
|
||||||
if (roleId != null && roleId == ADMIN_ROLE_ID) {
|
return userMapper.getUserTunnelDetails(userId.intValue());
|
||||||
return userMapper.getAllTunnelsForAdmin();
|
|
||||||
} else {
|
|
||||||
return userMapper.getUserTunnelDetails(userId.intValue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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 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 getConfigs = () => Network.post("/config/list");
|
||||||
export const getConfigByName = (name: string) => Network.post("/config/get", { name });
|
export const getConfigByName = (name: string) => Network.post("/config/get", { name });
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export type SiteConfig = typeof siteConfig;
|
|||||||
|
|
||||||
// 缓存相关常量
|
// 缓存相关常量
|
||||||
const CACHE_PREFIX = 'vite_config_';
|
const CACHE_PREFIX = 'vite_config_';
|
||||||
const VERSION = "1.2.0";
|
const VERSION = "1.2.1";
|
||||||
|
|
||||||
// 缓存策略:
|
// 缓存策略:
|
||||||
// - 长期有效,不设置过期时间
|
// - 长期有效,不设置过期时间
|
||||||
|
|||||||
@@ -774,11 +774,11 @@ export default function DashboardPage() {
|
|||||||
<div className="pt-2 border-t border-gray-200 dark:border-default-200">
|
<div className="pt-2 border-t border-gray-200 dark:border-default-200">
|
||||||
<div className="grid grid-cols-3 gap-1 text-xs">
|
<div className="grid grid-cols-3 gap-1 text-xs">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-default-500 mb-1">入站</div>
|
<div className="text-default-500 mb-1">上传</div>
|
||||||
<div className="font-medium text-green-600 dark:text-green-400 truncate">{formatFlow(forward.inFlow || 0)}</div>
|
<div className="font-medium text-green-600 dark:text-green-400 truncate">{formatFlow(forward.inFlow || 0)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-default-500 mb-1">出站</div>
|
<div className="text-default-500 mb-1">下载</div>
|
||||||
<div className="font-medium text-orange-600 dark:text-orange-400 truncate">{formatFlow(forward.outFlow || 0)}</div>
|
<div className="font-medium text-orange-600 dark:text-orange-400 truncate">{formatFlow(forward.outFlow || 0)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
|||||||
@@ -760,14 +760,15 @@ export default function ForwardPage() {
|
|||||||
<Chip color={strategyDisplay.color as any} variant="flat" size="sm" className="text-xs">
|
<Chip color={strategyDisplay.color as any} variant="flat" size="sm" className="text-xs">
|
||||||
{strategyDisplay.text}
|
{strategyDisplay.text}
|
||||||
</Chip>
|
</Chip>
|
||||||
<Chip variant="flat" size="sm" className="text-xs">
|
<div className="flex items-center gap-1">
|
||||||
{formatFlow((forward.inFlow || 0) + (forward.outFlow || 0))}
|
<Chip variant="flat" size="sm" className="text-xs" color="primary">
|
||||||
</Chip>
|
↑{formatFlow(forward.inFlow || 0)}
|
||||||
{forward.userName && (
|
</Chip>
|
||||||
<Chip variant="flat" size="sm" className="text-xs">
|
|
||||||
{forward.userName}
|
</div>
|
||||||
|
<Chip variant="flat" size="sm" className="text-xs" color="success">
|
||||||
|
↓{formatFlow(forward.outFlow || 0)}
|
||||||
</Chip>
|
</Chip>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -284,6 +284,34 @@ export default function NodePage() {
|
|||||||
return parseFloat((bytesPerSecond / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
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" => {
|
const getProgressColor = (value: number, offline = false): "default" | "primary" | "secondary" | "success" | "warning" | "danger" => {
|
||||||
if (offline) return "default";
|
if (offline) return "default";
|
||||||
@@ -633,6 +661,15 @@ export default function NodePage() {
|
|||||||
<span className="text-default-600">版本</span>
|
<span className="text-default-600">版本</span>
|
||||||
<span className="text-xs">{node.version || '未知'}</span>
|
<span className="text-xs">{node.version || '未知'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-default-600">开机时间</span>
|
||||||
|
<span className="text-xs">
|
||||||
|
{node.connectionStatus === 'online' && node.systemInfo
|
||||||
|
? formatUptime(node.systemInfo.uptime)
|
||||||
|
: '-'
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 系统监控 */}
|
{/* 系统监控 */}
|
||||||
@@ -700,6 +737,28 @@ export default function NodePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 流量统计 */}
|
||||||
|
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||||
|
<div className="text-center p-2 bg-primary-50 dark:bg-primary-100/20 rounded border border-primary-200 dark:border-primary-300/20">
|
||||||
|
<div className="text-primary-600 dark:text-primary-400 mb-0.5">↑ 上行流量</div>
|
||||||
|
<div className="font-mono text-primary-700 dark:text-primary-300">
|
||||||
|
{node.connectionStatus === 'online' && node.systemInfo
|
||||||
|
? formatTraffic(node.systemInfo.uploadTraffic)
|
||||||
|
: '-'
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center p-2 bg-success-50 dark:bg-success-100/20 rounded border border-success-200 dark:border-success-300/20">
|
||||||
|
<div className="text-success-600 dark:text-success-400 mb-0.5">↓ 下行流量</div>
|
||||||
|
<div className="font-mono text-success-700 dark:text-success-300">
|
||||||
|
{node.connectionStatus === 'online' && node.systemInfo
|
||||||
|
? formatTraffic(node.systemInfo.downloadTraffic)
|
||||||
|
: '-'
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 操作按钮 */}
|
{/* 操作按钮 */}
|
||||||
|
|||||||
@@ -518,23 +518,17 @@ export default function TunnelPage() {
|
|||||||
{/* 配置信息 */}
|
{/* 配置信息 */}
|
||||||
<div className="flex justify-between items-center pt-2 border-t border-divider">
|
<div className="flex justify-between items-center pt-2 border-t border-divider">
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-xs text-default-500">流量计算</div>
|
|
||||||
<div className="text-xs font-medium text-foreground">
|
<div className="text-xs font-medium text-foreground">
|
||||||
{getFlowDisplay(tunnel.flow)}
|
{getFlowDisplay(tunnel.flow)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-xs text-default-500">倍率</div>
|
|
||||||
<div className="text-xs font-medium text-foreground">
|
<div className="text-xs font-medium text-foreground">
|
||||||
{tunnel.trafficRatio}x
|
{tunnel.trafficRatio}x
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 时间信息 */}
|
|
||||||
<div className="text-xs text-default-500 pt-1 border-t border-divider">
|
|
||||||
创建时间: {formatTime(tunnel.createdTime)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-1.5 mt-3">
|
<div className="flex gap-1.5 mt-3">
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ import {
|
|||||||
getUserTunnelList,
|
getUserTunnelList,
|
||||||
removeUserTunnel,
|
removeUserTunnel,
|
||||||
updateUserTunnel,
|
updateUserTunnel,
|
||||||
getSpeedLimitList
|
getSpeedLimitList,
|
||||||
|
resetUserFlow
|
||||||
} from '@/api';
|
} from '@/api';
|
||||||
import { SearchIcon, PlusIcon, EditIcon, DeleteIcon, UserIcon, SettingsIcon } from '@/components/icons';
|
import { SearchIcon, PlusIcon, EditIcon, DeleteIcon, UserIcon, SettingsIcon } from '@/components/icons';
|
||||||
import { parseDate } from "@internationalized/date";
|
import { parseDate } from "@internationalized/date";
|
||||||
@@ -157,6 +158,16 @@ export default function UserPage() {
|
|||||||
const { isOpen: isDeleteTunnelModalOpen, onOpen: onDeleteTunnelModalOpen, onClose: onDeleteTunnelModalClose } = useDisclosure();
|
const { isOpen: isDeleteTunnelModalOpen, onOpen: onDeleteTunnelModalOpen, onClose: onDeleteTunnelModalClose } = useDisclosure();
|
||||||
const [tunnelToDelete, setTunnelToDelete] = useState<UserTunnel | null>(null);
|
const [tunnelToDelete, setTunnelToDelete] = useState<UserTunnel | null>(null);
|
||||||
|
|
||||||
|
// 重置流量确认相关状态
|
||||||
|
const { isOpen: isResetFlowModalOpen, onOpen: onResetFlowModalOpen, onClose: onResetFlowModalClose } = useDisclosure();
|
||||||
|
const [userToReset, setUserToReset] = useState<User | null>(null);
|
||||||
|
const [resetFlowLoading, setResetFlowLoading] = useState(false);
|
||||||
|
|
||||||
|
// 重置隧道流量确认相关状态
|
||||||
|
const { isOpen: isResetTunnelFlowModalOpen, onOpen: onResetTunnelFlowModalOpen, onClose: onResetTunnelFlowModalClose } = useDisclosure();
|
||||||
|
const [tunnelToReset, setTunnelToReset] = useState<UserTunnel | null>(null);
|
||||||
|
const [resetTunnelFlowLoading, setResetTunnelFlowLoading] = useState(false);
|
||||||
|
|
||||||
// 其他数据
|
// 其他数据
|
||||||
const [tunnels, setTunnels] = useState<Tunnel[]>([]);
|
const [tunnels, setTunnels] = useState<Tunnel[]>([]);
|
||||||
const [speedLimits, setSpeedLimits] = useState<SpeedLimit[]>([]);
|
const [speedLimits, setSpeedLimits] = useState<SpeedLimit[]>([]);
|
||||||
@@ -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(
|
const availableTunnels = tunnels.filter(
|
||||||
tunnel => !userTunnels.some(ut => ut.tunnelId === tunnel.id)
|
tunnel => !userTunnels.some(ut => ut.tunnelId === tunnel.id)
|
||||||
@@ -595,15 +670,16 @@ export default function UserPage() {
|
|||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-default-600">过期时间</span>
|
<span className="text-default-600">过期时间</span>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-xs">{formatDate(user.expTime)}</div>
|
{expStatus && expStatus.color === 'success' ? (
|
||||||
{expStatus && (
|
<div className="text-xs">{formatDate(user.expTime)}</div>
|
||||||
|
) : (
|
||||||
<Chip
|
<Chip
|
||||||
color={expStatus.color}
|
color={expStatus?.color || 'default'}
|
||||||
variant="flat"
|
variant="flat"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-xs mt-0.5"
|
className="text-xs"
|
||||||
>
|
>
|
||||||
{expStatus.text}
|
{expStatus?.text || '未知状态'}
|
||||||
</Chip>
|
</Chip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -623,6 +699,20 @@ export default function UserPage() {
|
|||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="flat"
|
||||||
|
color="warning"
|
||||||
|
onPress={() => handleResetFlow(user)}
|
||||||
|
className="flex-1 min-h-8"
|
||||||
|
startContent={
|
||||||
|
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
@@ -959,6 +1049,18 @@ export default function UserPage() {
|
|||||||
>
|
>
|
||||||
<EditIcon className="w-4 h-4" />
|
<EditIcon className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="flat"
|
||||||
|
color="warning"
|
||||||
|
isIconOnly
|
||||||
|
onClick={() => handleResetTunnelFlow(userTunnel)}
|
||||||
|
title="重置流量"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
@@ -1179,6 +1281,140 @@ export default function UserPage() {
|
|||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 重置流量确认对话框 */}
|
||||||
|
<Modal
|
||||||
|
isOpen={isResetFlowModalOpen}
|
||||||
|
onClose={onResetFlowModalClose}
|
||||||
|
size="md"
|
||||||
|
scrollBehavior="outside"
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader className="flex flex-col gap-1">
|
||||||
|
确认重置流量
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-12 h-12 bg-warning-100 rounded-full flex items-center justify-center">
|
||||||
|
<svg className="w-6 h-6 text-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-foreground">
|
||||||
|
确定要重置用户 <span className="font-semibold text-warning">"{userToReset?.user}"</span> 的流量吗?
|
||||||
|
</p>
|
||||||
|
<p className="text-small text-default-500 mt-1">
|
||||||
|
该操作只会重置账号流量不会重置隧道权限流量,重置后该用户的上下行流量将归零,此操作不可撤销。
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 p-2 bg-warning-50 dark:bg-warning-100/10 rounded text-xs">
|
||||||
|
<div className="text-warning-700 dark:text-warning-300">
|
||||||
|
当前流量使用情况:
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 space-y-1">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>上行流量:</span>
|
||||||
|
<span className="font-mono">{userToReset ? formatFlow(userToReset.inFlow || 0) : '-'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>下行流量:</span>
|
||||||
|
<span className="font-mono">{userToReset ? formatFlow(userToReset.outFlow || 0) : '-'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between font-medium">
|
||||||
|
<span>总计:</span>
|
||||||
|
<span className="font-mono text-warning-700 dark:text-warning-300">
|
||||||
|
{userToReset ? formatFlow(calculateUserTotalUsedFlow(userToReset)) : '-'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
onPress={onResetFlowModalClose}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="warning"
|
||||||
|
onPress={handleConfirmResetFlow}
|
||||||
|
isLoading={resetFlowLoading}
|
||||||
|
>
|
||||||
|
确认重置
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* 重置隧道流量确认对话框 */}
|
||||||
|
<Modal
|
||||||
|
isOpen={isResetTunnelFlowModalOpen}
|
||||||
|
onClose={onResetTunnelFlowModalClose}
|
||||||
|
size="md"
|
||||||
|
scrollBehavior="outside"
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader className="flex flex-col gap-1">
|
||||||
|
确认重置隧道流量
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-12 h-12 bg-warning-100 rounded-full flex items-center justify-center">
|
||||||
|
<svg className="w-6 h-6 text-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-foreground">
|
||||||
|
确定要重置用户 <span className="font-semibold">{currentUser?.user}</span> 对隧道 <span className="font-semibold text-warning">"{tunnelToReset?.tunnelName}"</span> 的流量吗?
|
||||||
|
</p>
|
||||||
|
<p className="text-small text-default-500 mt-1">
|
||||||
|
该操作只会重置隧道权限流量不会重置账号流量,重置后该隧道权限的上下行流量将归零,此操作不可撤销。
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 p-2 bg-warning-50 dark:bg-warning-100/10 rounded text-xs">
|
||||||
|
<div className="text-warning-700 dark:text-warning-300">
|
||||||
|
当前流量使用情况:
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 space-y-1">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>上行流量:</span>
|
||||||
|
<span className="font-mono">{tunnelToReset ? formatFlow(tunnelToReset.inFlow || 0) : '-'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>下行流量:</span>
|
||||||
|
<span className="font-mono">{tunnelToReset ? formatFlow(tunnelToReset.outFlow || 0) : '-'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between font-medium">
|
||||||
|
<span>总计:</span>
|
||||||
|
<span className="font-mono text-warning-700 dark:text-warning-300">
|
||||||
|
{tunnelToReset ? formatFlow(calculateTunnelUsedFlow(tunnelToReset)) : '-'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
onPress={onResetTunnelFlowModalClose}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="warning"
|
||||||
|
onPress={handleConfirmResetTunnelFlow}
|
||||||
|
isLoading={resetTunnelFlowLoading}
|
||||||
|
>
|
||||||
|
确认重置
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user