修复IPv6插件的逻辑错误
Some checks failed
build / build (api, amd64, linux) (push) Failing after -50s
build / build (api, arm64, linux) (push) Failing after -51s
build / build (api.exe, amd64, windows) (push) Failing after -51s

This commit is contained in:
CN-JS-HuiBai
2026-04-18 11:18:36 +08:00
parent 98379b21f4
commit def8352bbb
4 changed files with 218 additions and 147 deletions

View File

@@ -292937,7 +292937,11 @@ function c5t() {
},
[v, l],
),
E = () => {
E = (e) => {
if (e) {
o(!0);
return;
}
(o(!1), a(null), v.reset(i));
};
return Q.jsxs(Jet, {
@@ -294416,7 +294420,8 @@ function T5t({ node: e, refetch: t, t: n }) {
children: [
Q.jsx(Rot, {
className: "cursor-pointer",
onClick: () => {
onSelect: (t) => {
t.preventDefault();
(o(e.type), r(e), i(!0));
},
children: Q.jsxs("div", {
@@ -305860,7 +305865,7 @@ function codexNativeIPv6Table() {
[r, o] = H.useState({}),
[s, a] = H.useState({}),
[l, c] = H.useState({ pageIndex: 0, pageSize: 20 }),
[d, u] = H.useState("0"),
[d, u] = H.useState({ open: !1, user: null, planId: "0" }),
[h, g] = H.useState(!1),
{
refetch: p,
@@ -305873,16 +305878,30 @@ function codexNativeIPv6Table() {
params: { page: l.pageIndex + 1, per_page: l.pageSize, keyword: n },
}),
}),
{ data: _, refetch: v } = gC({
{ data: _ } = gC({
queryKey: ["codexNativeIPv6Config"],
queryFn: () => TL(`${RL()}/user-add-ipv6-subscription/config`),
}),
{ data: b } = gC({
{ data: v } = gC({
queryKey: ["codexNativeIPv6Plans"],
queryFn: () => DD(),
}),
y = H.useMemo(() => (Array.isArray(b?.data) ? b.data : []), [b]),
x = H.useMemo(
b = H.useMemo(() => (Array.isArray(v?.data) ? v.data : []), [v]),
y = H.useMemo(() => {
const e = _?.data?.ipv6_plan_id;
return e && e > 0 ? String(e) : "0";
}, [_]),
x = H.useCallback(
(e) => {
const t = e?.plan_id && e.plan_id > 0 ? String(e.plan_id) : y;
u({ open: !0, user: e, planId: t });
},
[y],
),
w = H.useCallback(() => {
u({ open: !1, user: null, planId: y });
}, [y]),
C = H.useMemo(
() => [
JKt.display({
id: "relation",
@@ -305896,16 +305915,10 @@ function codexNativeIPv6Table() {
cell: ({ row: e }) =>
Q.jsx("div", { className: "max-w-[240px] truncate", children: e.getValue("email") || "-" }),
},
{
accessorKey: "ipv6_email",
header: () => "IPv6 account",
cell: ({ row: e }) =>
Q.jsx("div", { className: "font-mono text-xs", children: e.getValue("ipv6_email") || "-" }),
},
{
accessorKey: "plan_name",
header: () => "IPv6 plan",
cell: ({ row: e }) => e.getValue("plan_name") || "-",
cell: ({ row: e }) => e.getValue("plan_name") || "",
},
JKt.display({
id: "status",
@@ -305915,20 +305928,32 @@ function codexNativeIPv6Table() {
JKt.display({
id: "actions",
header: () => "Actions",
cell: ({ row: e }) =>
Q.jsxs("div", {
cell: ({ row: e }) => {
const t = e.original.is_active || "active" === e.original.status,
n = "disabled" === e.original.status,
i = t ? "Set plan" : n ? "Re-enable" : "Enable";
return Q.jsxs("div", {
className: "flex flex-wrap items-center gap-2",
children: [
Q.jsx(Nm, {
size: "sm",
className: "h-8",
onClick: () => x(e.original),
children: i,
}),
t &&
Q.jsx(Nm, {
size: "sm",
variant: "outline",
className: "h-8",
onClick: async () => {
await IL(`${RL()}/user-add-ipv6-subscription/enable/${e.original.id}`, {});
hN.success("IPv6 account enabled and synced");
await IL(`${RL()}/user-add-ipv6-subscription/disable/${e.original.id}`, {});
hN.success("IPv6 account disabled");
p();
},
children: "Enable and sync",
children: "Disable",
}),
t &&
Q.jsx(Nm, {
size: "sm",
variant: "outline",
@@ -305941,14 +305966,15 @@ function codexNativeIPv6Table() {
children: "Sync password",
}),
],
}),
});
},
}),
],
[p],
[x, p],
),
w = PKt({
S = PKt({
data: f?.data?.list ?? [],
columns: x,
columns: C,
state: { columnVisibility: r, rowSelection: s, pagination: l },
getRowId: (e) => String(e.id),
rowCount: f?.data?.pagination?.total ?? 0,
@@ -305960,12 +305986,7 @@ function codexNativeIPv6Table() {
getCoreRowModel: LKt(),
getPaginationRowModel: OKt(),
});
return (
H.useEffect(() => {
const e = _?.data?.ipv6_plan_id;
u(e && e > 0 ? String(e) : "0");
}, [_]),
Q.jsxs("div", {
return Q.jsxs("div", {
className: "space-y-4",
children: [
Q.jsx(codexNativeSearchToolbar, {
@@ -305981,61 +306002,92 @@ function codexNativeIPv6Table() {
c((e) => ({ ...e, pageIndex: 0 }));
},
refetch: p,
actions: Q.jsxs("div", {
className: "flex flex-wrap items-center gap-2",
}),
Q.jsx(QKt, {
table: S,
isLoading: m,
showPagination: !0,
mobilePrimaryField: "email",
mobileGridFields: ["plan_name", "status"],
}),
Q.jsx(Jet, {
open: d.open,
onOpenChange: (e) => {
e ? u((e) => ({ ...e, open: !0 })) : w();
},
children: Q.jsxs(ttt, {
className: "sm:max-w-md",
children: [
Q.jsx("span", { className: "text-sm text-muted-foreground", children: "IPv6Only plan" }),
Q.jsxs(ntt, {
children: [
Q.jsx(rtt, { children: "Set IPv6 plan" }),
Q.jsx(ott, {
children: d.user ? `User: ${d.user.email || d.user.id}` : "Choose the plan for this IPv6 account.",
}),
],
}),
Q.jsxs("div", {
className: "space-y-4 p-6",
children: [
Q.jsxs("div", {
className: "space-y-2",
children: [
Q.jsx("label", { className: "text-sm font-medium text-foreground", children: "IPv6 plan" }),
Q.jsxs("div", {
className: "relative",
children: [
Q.jsxs("select", {
className:
"flex h-8 min-w-[180px] appearance-none rounded-md border border-input bg-transparent px-3 pr-8 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
value: d,
onChange: (e) => u(e.target.value),
"flex h-10 w-full appearance-none rounded-md border border-input bg-transparent px-3 pr-8 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
value: d.planId,
onChange: (e) => u((t) => ({ ...t, planId: e.target.value })),
children: [
Q.jsx("option", { value: "0", children: "Not set" }),
y.map((e) =>
Q.jsx("option", { value: "0", children: "Use default plan" }),
b.map((e) =>
Q.jsx("option", { value: String(e.id), children: e.name || `Plan ${e.id}` }, e.id),
),
],
}),
Q.jsx(m7e, {
className:
"pointer-events-none absolute right-2 top-2 h-4 w-4 text-muted-foreground/70",
"pointer-events-none absolute right-3 top-3.5 h-4 w-4 text-muted-foreground/70",
}),
],
}),
],
}),
Q.jsxs("div", {
className: "flex justify-end gap-2",
children: [
Q.jsx(Nm, { type: "button", variant: "outline", onClick: w, children: "Cancel" }),
Q.jsx(Nm, {
className: "h-8",
type: "button",
loading: h,
onClick: async () => {
if (!d.user) return;
g(!0);
try {
await IL(`${RL()}/user-add-ipv6-subscription/config`, {
ipv6_plan_id: Number(d) || 0,
await IL(`${RL()}/user-add-ipv6-subscription/enable/${d.user.id}`, {
plan_id: Number(d.planId) || 0,
});
hN.success("IPv6Only plan saved");
await Promise.all([v(), p()]);
hN.success("IPv6 account updated");
w();
await p();
} finally {
g(!1);
}
},
children: "Save plan",
children: "Save",
}),
],
}),
],
}),
],
}),
}),
Q.jsx(QKt, {
table: w,
isLoading: m,
showPagination: !0,
mobilePrimaryField: "email",
mobileGridFields: ["ipv6_email", "plan_name", "status"],
}),
],
})
);
});
}
function codexNativeIPv6Page() {
return codexNativePageLayout(

View File

@@ -1069,12 +1069,17 @@
saveToken(payload.auth_data);
state.authToken = getStoredToken();
// Try IPv6 login/register if email/password available
if (data.email && data.password) {
var ipv6Email = data.email.replace("@", "-ipv6@");
var ipv6Action = formType === "register" ? "/api/v1/passport/auth/register" : "/api/v1/passport/auth/login";
showMessage(formType === "register" ? "Account created" : "Signed in", "success");
return loadDashboard(true).then(function () {
if (formType !== "login" || !data.email || !data.password || state.ipv6AuthToken) {
return null;
}
if (!(state.ipv6Eligibility && state.ipv6Eligibility.is_active)) {
return null;
}
fetchJson(ipv6Action, {
var ipv6Email = data.email.replace("@", "-ipv6@");
return fetchJson("/api/v1/passport/auth/login", {
method: "POST",
auth: false,
body: Object.assign({}, data, { email: ipv6Email })
@@ -1085,14 +1090,11 @@
state.ipv6AuthToken = getStoredIpv6Token();
}
}).catch(function () {
// Ignore IPv6 errors as it might not be enabled yet or fail
}).finally(function() {
loadDashboard(true).then(render);
});
}
showMessage(formType === "register" ? "Account created" : "Signed in", "success");
return null;
}).then(function () {
return loadDashboard(true);
});
});
}).then(render).catch(function (error) {
showMessage(error.message || "Unable to continue", "error");
render();

View File

@@ -183,29 +183,39 @@ func AdminIPv6SubscriptionUsers(c *gin.Context) {
}
subscriptionByUserID := make(map[int]model.UserIPv6Subscription, len(userIDs))
shadowUserIDs := make([]int, 0, len(userIDs))
if len(userIDs) > 0 {
var rows []model.UserIPv6Subscription
if err := database.DB.Where("user_id IN ?", userIDs).Find(&rows).Error; err == nil {
for _, row := range rows {
subscriptionByUserID[row.UserID] = row
if row.ShadowUserID != nil && *row.ShadowUserID > 0 {
shadowUserIDs = append(shadowUserIDs, *row.ShadowUserID)
}
}
}
}
shadowByParentID := make(map[int]model.User, len(userIDs))
shadowByID := make(map[int]model.User, len(shadowUserIDs))
shadowByEmail := make(map[string]model.User, len(userIDs))
if len(userIDs) > 0 {
var shadowUsers []model.User
if err := database.DB.Preload("Plan").Where("parent_id IN ?", userIDs).Find(&shadowUsers).Error; err == nil {
query := database.DB.Preload("Plan")
if len(shadowUserIDs) > 0 {
query = query.Where("parent_id IN ? OR id IN ?", userIDs, shadowUserIDs)
} else {
query = query.Where("parent_id IN ?", userIDs)
}
if err := query.Find(&shadowUsers).Error; err == nil {
for _, shadow := range shadowUsers {
shadowByID[shadow.ID] = shadow
shadowByEmail[strings.ToLower(shadow.Email)] = shadow
if shadow.ParentID != nil {
shadowByParentID[*shadow.ParentID] = shadow
}
}
}
}
shadowPlanID := parsePositiveInt(
service.GetPluginConfigString(service.PluginUserAddIPv6, "ipv6_plan_id", "0"),
0,
)
planNames := make(map[int]string)
var plans []model.Plan
if err := database.DB.Select("id", "name").Find(&plans).Error; err == nil {
@@ -218,6 +228,12 @@ func AdminIPv6SubscriptionUsers(c *gin.Context) {
for _, user := range users {
subscription, hasSubscription := subscriptionByUserID[user.ID]
shadowUser, hasShadowUser := shadowByParentID[user.ID]
if !hasShadowUser && hasSubscription && subscription.ShadowUserID != nil {
shadowUser, hasShadowUser = shadowByID[*subscription.ShadowUserID]
}
if !hasShadowUser {
shadowUser, hasShadowUser = shadowByEmail[strings.ToLower(service.IPv6ShadowEmail(user.Email))]
}
if !hasSubscription && hasShadowUser {
subscription = model.UserIPv6Subscription{
UserID: user.ID,
@@ -229,10 +245,9 @@ func AdminIPv6SubscriptionUsers(c *gin.Context) {
}
hasSubscription = true
}
allowed := service.PluginUserAllowed(&user, user.Plan)
status := "not_allowed"
planID := shadowPlanID
planNameValue := planNames[shadowPlanID]
status := "eligible"
planID := 0
planNameValue := ""
if hasShadowUser {
planID = intFromPointer(shadowUser.PlanID)
planNameValue = firstString(planName(shadowUser.Plan), planNames[planID])
@@ -246,14 +261,14 @@ func AdminIPv6SubscriptionUsers(c *gin.Context) {
}
shadowUpdatedAt = subscription.UpdatedAt
}
effectiveAllowed := allowed || hasSubscription && subscription.Allowed
effectiveAllowed := true
status, statusLabel, _ := ipv6StatusPresentation(status, effectiveAllowed)
list = append(list, gin.H{
"id": user.ID,
"email": user.Email,
"plan_id": planID,
"plan_name": firstString(planNameValue, "-"),
"plan_name": planNameValue,
"allowed": effectiveAllowed,
"is_active": hasSubscription && subscription.Status == "active",
"status": status,
@@ -463,22 +478,13 @@ func PluginUserAddIPv6Check(c *gin.Context) {
return
}
var plan *model.Plan
if user.PlanID != nil {
var loadedPlan model.Plan
if err := database.DB.First(&loadedPlan, *user.PlanID).Error; err == nil {
plan = &loadedPlan
}
}
var subscription model.UserIPv6Subscription
hasSubscription := database.DB.Where("user_id = ?", user.ID).First(&subscription).Error == nil
allowed := service.PluginUserAllowed(user, plan)
status := "not_allowed"
status := "eligible"
if hasSubscription {
status = firstString(subscription.Status, "active")
}
effectiveAllowed := allowed || hasSubscription && subscription.Allowed
effectiveAllowed := true
status, statusLabel, reason := ipv6StatusPresentation(status, effectiveAllowed)
Success(c, gin.H{

View File

@@ -105,6 +105,7 @@ func SyncIPv6ShadowAccountWithPlan(user *model.User, overridePlanID int) bool {
ipv6User.Email = ipv6Email
ipv6User.Password = user.Password
ipv6User.ParentID = &user.ID
ipv6User.U = 0
ipv6User.D = 0
ipv6User.T = 0
@@ -116,6 +117,8 @@ func SyncIPv6ShadowAccountWithPlan(user *model.User, overridePlanID int) bool {
ipv6User.PlanID = &overridePlanID
} else if planID := parsePluginPositiveInt(GetPluginConfigString(PluginUserAddIPv6, "ipv6_plan_id", "0"), 0); planID > 0 {
ipv6User.PlanID = &planID
} else if ipv6User.PlanID == nil && user.PlanID != nil {
ipv6User.PlanID = user.PlanID
}
if groupID := parsePluginPositiveInt(GetPluginConfigString(PluginUserAddIPv6, "ipv6_group_id", "0"), 0); groupID > 0 {
ipv6User.GroupID = &groupID
@@ -142,7 +145,15 @@ func DisableIPv6ShadowAccount(user *model.User) bool {
ipv6Email := IPv6ShadowEmail(user.Email)
var ipv6User model.User
if err := database.DB.Where("email = ? AND parent_id = ?", ipv6Email, user.ID).First(&ipv6User).Error; err != nil {
var subscription model.UserIPv6Subscription
if err := database.DB.Where("user_id = ?", user.ID).First(&subscription).Error; err == nil && subscription.ShadowUserID != nil {
_ = database.DB.First(&ipv6User, *subscription.ShadowUserID).Error
}
if ipv6User.ID == 0 {
_ = database.DB.Where("email = ?", ipv6Email).First(&ipv6User).Error
}
if ipv6User.ID == 0 {
// No shadow user found, just update subscription record
syncIPv6SubscriptionRecord(user, nil, false, "disabled")
return true