mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-02 16:37:14 +00:00
refactor(clients): coherent group management — rename, split, extract
This bundles a set of group-related improvements that built up across one session and only make sense together. Terminology / API surface: - Rename "assign group" → "add to group" everywhere: i18n keys, callback names (bulkAddToGroup), component + file names (BulkAddToGroupModal, AddClientsToGroupModal), Go controller/struct names (bulkAddToGroup, AddToGroup), OpenAPI summaries. Nothing keeps the word "assign" anymore. - Move group routes under /panel/api/clients/groups/* (was /bulkAssignGroup at the clients root). - Split add and remove into two endpoints: /groups/bulkAdd now rejects empty group; new /groups/bulkRemove clears the label for the given emails. The old "submit empty to clear" UX is gone — Ungroup is its own action. UI affordances on Clients page: - Promote Group + Ungroup to visible bar buttons next to Attach + Detach. Group reuses BulkAddToGroupModal; Ungroup pops a danger confirm and calls bulkRemoveFromGroup. - Custom UngroupIcon (TagsOutlined with a diagonal strike) for the Ungroup button so the pairing reads at a glance. - Hide the Group column when no clients have a group label yet — removes a column of em-dashes on fresh installs. UI on Groups page: - New per-row Add clients… / Remove clients… actions backed by GroupAddClientsModal and GroupRemoveClientsModal: rich client picker (email / comment / current group / enable) with search and preserveSelectedRowKeys, mirroring the inbounds Attach modal UX. Controller split: - Move all /groups/* routes, handlers, and request bodies out of web/controller/client.go into a dedicated web/controller/group.go (GroupController with leaner clientService + xrayService dependencies). URLs are byte-identical because the new controller registers on the same parent gin.RouterGroup; api_docs_test.go gets a group.go → /panel/api/clients basePath entry so its route extraction keeps working. Invalidation dedup: - Removing a client from a group on the Groups page used to refetch /clients/groups and /clients/onlines three times: once from the mutation's onSuccess, once from a redundant invalidate() in the page's onSubmit, once from the WebSocket invalidate broadcast that the backend fires after every mutation. The manual invalidate() is gone, and a small invalidationTracker module lets websocketBridge skip WS-driven invalidates that arrive within 1.5s of a local invalidate — bringing the refetch count down to one. The WS path still works for changes made by another tab or user.
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
"save": "Save",
|
||||
"logout": "Log Out",
|
||||
"create": "Create",
|
||||
"add": "Add",
|
||||
"remove": "Remove",
|
||||
"update": "Update",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied",
|
||||
@@ -299,7 +301,7 @@
|
||||
"delAllClientsConfirmTitle": "Delete all {count} clients from \"{remark}\"?",
|
||||
"delAllClientsConfirmContent": "This removes every client from this inbound and drops their traffic records. The inbound itself is kept. This cannot be undone.",
|
||||
"attachClients": "Attach Clients To…",
|
||||
"assignClientsGroup": "Assign Clients To Group…",
|
||||
"addClientsToGroup": "Add Clients To Group…",
|
||||
"attachClientsTitle": "Attach clients from \"{remark}\"",
|
||||
"attachClientsDesc": "Attaches the same {count} clients (same UUID/password and shared traffic) to the selected inbound(s). They stay on this inbound too.",
|
||||
"attachClientsTargets": "Target inbounds",
|
||||
@@ -536,12 +538,15 @@
|
||||
"deleteSelected": "Delete ({count})",
|
||||
"adjustSelected": "Adjust ({count})",
|
||||
"subLinksSelected": "Sub links ({count})",
|
||||
"assignGroupSelected": "Group ({count})",
|
||||
"assignGroupTitle": "Assign group to {count} client(s)",
|
||||
"assignGroupTooltip": "Pick an existing group or type a new name. Leave blank to clear the group on the selected clients.",
|
||||
"assignGroupPlaceholder": "Group name (leave blank to clear)",
|
||||
"assignGroupAssignedToast": "Assigned {count} client(s) to {group}",
|
||||
"assignGroupClearedToast": "Cleared group from {count} client(s)",
|
||||
"addToGroupTitle": "Add {count} client(s) to a group",
|
||||
"addToGroupTooltip": "Pick an existing group or type a new name. Use the Ungroup action to remove clients from their current group.",
|
||||
"addToGroupPlaceholder": "Group name",
|
||||
"addToGroupSuccessToast": "Added {count} client(s) to {group}",
|
||||
"ungroupSuccessToast": "Cleared group from {count} client(s)",
|
||||
"ungroup": "Ungroup",
|
||||
"ungroupConfirmTitle": "Remove {count} client(s) from their group?",
|
||||
"ungroupConfirmContent": "Clears the group label on each selected client. Clients themselves are kept (use Delete to remove them entirely).",
|
||||
"addToGroup": "Add to group",
|
||||
"attach": "Attach",
|
||||
"adjust": "Adjust",
|
||||
"subLinks": "Sub links",
|
||||
@@ -629,7 +634,16 @@
|
||||
"deleteClientsConfirmTitle": "Delete all clients in {name}?",
|
||||
"deleteClientsConfirmContent": "This permanently removes {count} client(s) along with their traffic records. The group label is cleared too. This cannot be undone.",
|
||||
"deleteClientsSuccess": "Deleted {count} client(s).",
|
||||
"deleteClientsMixed": "{ok} deleted, {failed} skipped"
|
||||
"deleteClientsMixed": "{ok} deleted, {failed} skipped",
|
||||
"addToGroup": "Add clients…",
|
||||
"addToGroupTitle": "Add clients to group \"{name}\"",
|
||||
"addToGroupDesc": "Select clients to add to this group. They keep their existing inbound attachments; only the group label changes. Clients already in this group are not listed.",
|
||||
"addToGroupEmpty": "No other clients available to add.",
|
||||
"addToGroupResult": "Added {count} client(s) to {name}.",
|
||||
"removeFromGroup": "Remove clients…",
|
||||
"removeFromGroupTitle": "Remove clients from group \"{name}\"",
|
||||
"removeFromGroupDesc": "Select members to remove from this group. Clients themselves are kept (use \"Delete clients in group\" to remove them entirely).",
|
||||
"removeFromGroupResult": "Removed {count} client(s) from {name}."
|
||||
},
|
||||
"nodes": {
|
||||
"title": "Nodes",
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"save": "ذخیره",
|
||||
"logout": "خروج",
|
||||
"create": "ایجاد",
|
||||
"add": "افزودن",
|
||||
"remove": "حذف",
|
||||
"update": "بهروزرسانی",
|
||||
"copy": "کپی",
|
||||
"copied": "کپی شد",
|
||||
@@ -294,7 +296,7 @@
|
||||
"delAllClientsConfirmTitle": "حذف هر {count} کلاینت اینباند «{remark}»؟",
|
||||
"delAllClientsConfirmContent": "تمام کلاینتهای این اینباند به همراه رکوردهای ترافیکشان حذف میشوند. خود اینباند باقی میماند. این عمل غیرقابل بازگشت است.",
|
||||
"attachClients": "اتصال کلاینتها به…",
|
||||
"assignClientsGroup": "افزودن کلاینتها به گروه…",
|
||||
"addClientsToGroup": "افزودن کلاینتها به گروه…",
|
||||
"attachClientsTitle": "اتصال کلاینتهای «{remark}»",
|
||||
"attachClientsDesc": "همان {count} کلاینت (با همان UUID/پسورد و ترافیک مشترک) را به اینباند(های) انتخابشده هم متصل میکند. روی این اینباند هم باقی میمانند.",
|
||||
"attachClientsTargets": "اینباندهای مقصد",
|
||||
@@ -517,6 +519,10 @@
|
||||
"adjust": "تنظیم",
|
||||
"subLinks": "لینکهای ساب",
|
||||
"selectedCount": "{count} انتخابشده",
|
||||
"ungroup": "حذف گروه",
|
||||
"ungroupConfirmTitle": "{count} کلاینت از گروهشان حذف شود؟",
|
||||
"ungroupConfirmContent": "برچسب گروه از هر کلاینت انتخابشده پاک میشود. خود کلاینتها حفظ میشوند (برای حذف کامل، از Delete استفاده کنید).",
|
||||
"addToGroup": "افزودن به گروه",
|
||||
"attachSelected": "اتصال ({count})",
|
||||
"attachToInboundsTitle": "اتصال {count} کلاینت به اینباند(ها)",
|
||||
"attachToInboundsDesc": "{count} کلاینت انتخابشده (با همان UUID/پسورد و ترافیک مشترک) به اینباند(های) انتخابی متصل میشوند. روی اینباندهای فعلی هم باقی میمانند.",
|
||||
|
||||
Reference in New Issue
Block a user