mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-11 03:36:37 +08:00
@@ -138,7 +138,6 @@ func (h *Handler) Register(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("/api/v1/user/update", h.userUpdate)
|
mux.HandleFunc("/api/v1/user/update", h.userUpdate)
|
||||||
mux.HandleFunc("/api/v1/user/delete", h.userDelete)
|
mux.HandleFunc("/api/v1/user/delete", h.userDelete)
|
||||||
mux.HandleFunc("/api/v1/user/reset", h.userResetFlow)
|
mux.HandleFunc("/api/v1/user/reset", h.userResetFlow)
|
||||||
mux.HandleFunc("/api/v1/user/batch-set-max-conn", h.userBatchSetMaxConn)
|
|
||||||
mux.HandleFunc("/api/v1/user/quota/reset", h.userQuotaReset)
|
mux.HandleFunc("/api/v1/user/quota/reset", h.userQuotaReset)
|
||||||
mux.HandleFunc("/api/v1/user/groups", h.userGroups)
|
mux.HandleFunc("/api/v1/user/groups", h.userGroups)
|
||||||
mux.HandleFunc("/api/v1/config/get", h.getConfigByName)
|
mux.HandleFunc("/api/v1/config/get", h.getConfigByName)
|
||||||
|
|||||||
@@ -214,26 +214,6 @@ func (h *Handler) userUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
response.WriteJSON(w, response.OKEmpty())
|
response.WriteJSON(w, response.OKEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) userBatchSetMaxConn(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var req struct {
|
|
||||||
UserIDs []int64 `json:"userIds"`
|
|
||||||
MaxConn int `json:"maxConn"`
|
|
||||||
}
|
|
||||||
if err := decodeJSON(r.Body, &req); err != nil || len(req.UserIDs) == 0 {
|
|
||||||
response.WriteJSON(w, response.ErrDefault("请求参数错误"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s, f := h.repo.BatchUpdateUserMaxConn(req.UserIDs, req.MaxConn)
|
|
||||||
response.WriteJSON(w, response.OK(map[string]interface{}{
|
|
||||||
"success": s,
|
|
||||||
"failed": f,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) userGroups(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) userGroups(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ func requiresAdmin(path string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch path {
|
switch path {
|
||||||
case "/api/v1/user/create", "/api/v1/user/list", "/api/v1/user/update", "/api/v1/user/delete", "/api/v1/user/reset", "/api/v1/user/batch-set-max-conn":
|
case "/api/v1/user/create", "/api/v1/user/list", "/api/v1/user/update", "/api/v1/user/delete", "/api/v1/user/reset":
|
||||||
return true
|
return true
|
||||||
case "/api/v1/config/update", "/api/v1/config/update-single":
|
case "/api/v1/config/update", "/api/v1/config/update-single":
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1320,23 +1320,6 @@ func (r *Repository) BatchUpdateForwardStatus(ids []int64, status int) (int, int
|
|||||||
return s, f
|
return s, f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) BatchUpdateUserMaxConn(ids []int64, maxConn int) (int, int) {
|
|
||||||
if r == nil || r.db == nil {
|
|
||||||
return 0, len(ids)
|
|
||||||
}
|
|
||||||
s := 0
|
|
||||||
f := 0
|
|
||||||
now := time.Now().UnixMilli()
|
|
||||||
for _, id := range ids {
|
|
||||||
if err := r.db.Model(&model.User{}).Where("id = ?", id).Updates(map[string]interface{}{"max_conn": maxConn, "updated_time": now}).Error; err != nil {
|
|
||||||
f++
|
|
||||||
} else {
|
|
||||||
s++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s, f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repository) CreateTunnelTx(tx *gorm.DB, name string, trafficRatio float64, typeVal int, flow int64, now int64, status int, inIP interface{}, inx int, ipPreference string) (int64, error) {
|
func (r *Repository) CreateTunnelTx(tx *gorm.DB, name string, trafficRatio float64, typeVal int, flow int64, now int64, status int, inIP interface{}, inx int, ipPreference string) (int64, error) {
|
||||||
inIPVal := nullStringFromInterface(inIP)
|
inIPVal := nullStringFromInterface(inIP)
|
||||||
tunnel := model.Tunnel{
|
tunnel := model.Tunnel{
|
||||||
|
|||||||
@@ -240,8 +240,6 @@ export const resetUserFlow = (data: { id: number; type: number }) =>
|
|||||||
Network.post("/user/reset", data);
|
Network.post("/user/reset", data);
|
||||||
export const resetUserQuota = (data: UserQuotaResetPayload) =>
|
export const resetUserQuota = (data: UserQuotaResetPayload) =>
|
||||||
Network.post("/user/quota/reset", data);
|
Network.post("/user/quota/reset", data);
|
||||||
export const batchSetUserMaxConn = (data: { userIds: number[]; maxConn: number }) =>
|
|
||||||
Network.post("/user/batch-set-max-conn", data);
|
|
||||||
|
|
||||||
export const getUserGroups = (id: number) =>
|
export const getUserGroups = (id: number) =>
|
||||||
Network.post<number[]>("/user/groups", { id });
|
Network.post<number[]>("/user/groups", { id });
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ import {
|
|||||||
getSpeedLimitList,
|
getSpeedLimitList,
|
||||||
resetUserFlow,
|
resetUserFlow,
|
||||||
resetUserQuota,
|
resetUserQuota,
|
||||||
batchSetUserMaxConn,
|
|
||||||
getUserGroupList,
|
getUserGroupList,
|
||||||
getUserGroups,
|
getUserGroups,
|
||||||
getMonitorPermissionList,
|
getMonitorPermissionList,
|
||||||
@@ -202,10 +201,6 @@ export default function UserPage() {
|
|||||||
size: 10,
|
size: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const [selectedUserIds, setSelectedUserIds] = useState<Set<number>>(new Set());
|
|
||||||
const [isBatchMaxConnModalOpen, setIsBatchMaxConnModalOpen] = useState(false);
|
|
||||||
const [batchMaxConnValue, setBatchMaxConnValue] = useState(0);
|
|
||||||
const [batchMaxConnLoading, setBatchMaxConnLoading] = useState(false);
|
|
||||||
const searchDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const searchDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
// 用户表单相关状态
|
// 用户表单相关状态
|
||||||
@@ -909,60 +904,6 @@ export default function UserPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBatchSetMaxConn = async () => {
|
|
||||||
if (selectedUserIds.size === 0) {
|
|
||||||
toast.error("请先选择用户");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setBatchMaxConnLoading(true);
|
|
||||||
try {
|
|
||||||
const response = await batchSetUserMaxConn({
|
|
||||||
userIds: Array.from(selectedUserIds),
|
|
||||||
maxConn: batchMaxConnValue,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.code === 0) {
|
|
||||||
const data = response.data as { success: number; failed: number } | null;
|
|
||||||
const { success = 0, failed = 0 } = data || {};
|
|
||||||
if (failed === 0) {
|
|
||||||
toast.success(`成功设置 ${success} 个用户的连接数限制`);
|
|
||||||
} else {
|
|
||||||
toast.success(`成功 ${success} 个,失败 ${failed} 个`);
|
|
||||||
}
|
|
||||||
setIsBatchMaxConnModalOpen(false);
|
|
||||||
setSelectedUserIds(new Set());
|
|
||||||
await loadUsers(searchKeyword);
|
|
||||||
} else {
|
|
||||||
toast.error(response.msg || "批量设置失败");
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
toast.error("批量设置失败");
|
|
||||||
} finally {
|
|
||||||
setBatchMaxConnLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleSelectUser = (userId: number) => {
|
|
||||||
setSelectedUserIds((prev) => {
|
|
||||||
const next = new Set(prev);
|
|
||||||
if (next.has(userId)) {
|
|
||||||
next.delete(userId);
|
|
||||||
} else {
|
|
||||||
next.add(userId);
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleSelectAll = () => {
|
|
||||||
if (selectedUserIds.size === users.length) {
|
|
||||||
setSelectedUserIds(new Set());
|
|
||||||
} else {
|
|
||||||
setSelectedUserIds(new Set(users.map((u) => u.id)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 隧道流量重置相关函数
|
// 隧道流量重置相关函数
|
||||||
const handleResetTunnelFlow = (userTunnel: UserTunnel) => {
|
const handleResetTunnelFlow = (userTunnel: UserTunnel) => {
|
||||||
setTunnelToReset(userTunnel);
|
setTunnelToReset(userTunnel);
|
||||||
@@ -1105,19 +1046,6 @@ export default function UserPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{selectedUserIds.size > 0 && (
|
|
||||||
<Button
|
|
||||||
color="warning"
|
|
||||||
size="sm"
|
|
||||||
variant="flat"
|
|
||||||
onPress={() => {
|
|
||||||
setBatchMaxConnValue(0);
|
|
||||||
setIsBatchMaxConnModalOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
批量设置连接数 ({selectedUserIds.size})
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -1164,12 +1092,6 @@ export default function UserPage() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableColumn>
|
|
||||||
<Checkbox
|
|
||||||
isSelected={selectedUserIds.size === users.length && users.length > 0}
|
|
||||||
onValueChange={toggleSelectAll}
|
|
||||||
/>
|
|
||||||
</TableColumn>
|
|
||||||
<TableColumn>用户名</TableColumn>
|
<TableColumn>用户名</TableColumn>
|
||||||
<TableColumn>流量统计</TableColumn>
|
<TableColumn>流量统计</TableColumn>
|
||||||
<TableColumn>配额限制</TableColumn>
|
<TableColumn>配额限制</TableColumn>
|
||||||
@@ -1187,12 +1109,6 @@ export default function UserPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={user.id}>
|
<TableRow key={user.id}>
|
||||||
<TableCell>
|
|
||||||
<Checkbox
|
|
||||||
isSelected={selectedUserIds.has(user.id)}
|
|
||||||
onValueChange={() => toggleSelectUser(user.id)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center justify-center shrink-0 w-10 h-10 rounded-full bg-primary text-white font-bold text-sm relative">
|
<div className="flex items-center justify-center shrink-0 w-10 h-10 rounded-full bg-primary text-white font-bold text-sm relative">
|
||||||
@@ -1354,11 +1270,6 @@ export default function UserPage() {
|
|||||||
<Card className="overflow-hidden h-full">
|
<Card className="overflow-hidden h-full">
|
||||||
<CardHeader className="pb-2 md:pb-2">
|
<CardHeader className="pb-2 md:pb-2">
|
||||||
<div className="flex justify-between items-start w-full">
|
<div className="flex justify-between items-start w-full">
|
||||||
<Checkbox
|
|
||||||
className="absolute top-2 right-2 z-10"
|
|
||||||
isSelected={selectedUserIds.has(user.id)}
|
|
||||||
onValueChange={() => toggleSelectUser(user.id)}
|
|
||||||
/>
|
|
||||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||||
<div className="flex items-center justify-center shrink-0 w-10 h-10 rounded-full bg-primary text-white font-bold text-sm relative">
|
<div className="flex items-center justify-center shrink-0 w-10 h-10 rounded-full bg-primary text-white font-bold text-sm relative">
|
||||||
{(user.name || user.user).slice(0, 2).toUpperCase()}
|
{(user.name || user.user).slice(0, 2).toUpperCase()}
|
||||||
@@ -2613,51 +2524,6 @@ export default function UserPage() {
|
|||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{/* 批量设置最大连接数对话框 */}
|
|
||||||
<Modal
|
|
||||||
backdrop="blur"
|
|
||||||
classNames={{
|
|
||||||
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl",
|
|
||||||
}}
|
|
||||||
isOpen={isBatchMaxConnModalOpen}
|
|
||||||
placement="center"
|
|
||||||
scrollBehavior="inside"
|
|
||||||
size="md"
|
|
||||||
onClose={() => setIsBatchMaxConnModalOpen(false)}
|
|
||||||
>
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>批量设置最大连接数</ModalHeader>
|
|
||||||
<ModalBody>
|
|
||||||
<p className="text-sm text-default-500">
|
|
||||||
已选择 <span className="font-medium text-foreground">{selectedUserIds.size}</span> 个用户
|
|
||||||
</p>
|
|
||||||
<Input
|
|
||||||
label="最大连接数"
|
|
||||||
placeholder="0 或空表示不限制"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
value={batchMaxConnValue === 0 ? "" : String(batchMaxConnValue)}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = Math.max(Number(e.target.value) || 0, 0);
|
|
||||||
setBatchMaxConnValue(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter>
|
|
||||||
<Button variant="light" onPress={() => setIsBatchMaxConnModalOpen(false)}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="warning"
|
|
||||||
isLoading={batchMaxConnLoading}
|
|
||||||
onPress={handleBatchSetMaxConn}
|
|
||||||
>
|
|
||||||
确认设置
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
</AnimatedPage>
|
</AnimatedPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user