mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 21:24:10 +00:00
fix: copy clients modal not opening
This commit is contained in:
parent
55d0929cfe
commit
eac7296a5d
5 changed files with 61 additions and 8 deletions
|
|
@ -58,6 +58,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|||
type CopyInboundClientsRequest struct {
|
||||
SourceInboundID int `form:"sourceInboundId" json:"sourceInboundId"`
|
||||
ClientEmails []string `form:"clientEmails" json:"clientEmails"`
|
||||
Flow string `form:"flow" json:"flow"`
|
||||
}
|
||||
|
||||
// getInbounds retrieves the list of inbounds for the logged-in user.
|
||||
|
|
@ -285,7 +286,7 @@ func (a *InboundController) copyInboundClients(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
result, needRestart, err := a.inboundService.CopyInboundClients(targetID, req.SourceInboundID, req.ClientEmails)
|
||||
result, needRestart, err := a.inboundService.CopyInboundClients(targetID, req.SourceInboundID, req.ClientEmails, req.Flow)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -826,6 +826,20 @@
|
|||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<div v-if="copyClientsModal.showFlow">
|
||||
<div style="margin-bottom: 6px;">{{ i18n "pages.client.copyFlowLabel" }}</div>
|
||||
<a-select v-model="copyClientsModal.flow"
|
||||
style="width: 100%;"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
allow-clear>
|
||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option value="xtls-rprx-vision">xtls-rprx-vision</a-select-option>
|
||||
<a-select-option value="xtls-rprx-vision-udp443">xtls-rprx-vision-udp443</a-select-option>
|
||||
</a-select>
|
||||
<div style="margin-top: 4px; font-size: 12px; opacity: 0.7;">
|
||||
{{ i18n "pages.client.copyFlowHint" }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="copyClientsModal.selectedEmails.length > 0">
|
||||
<div style="margin-bottom: 4px;">{{ i18n "pages.client.copyEmailPreview" }}</div>
|
||||
<div style="max-height: 120px; overflow-y: auto;">
|
||||
|
|
@ -849,6 +863,9 @@
|
|||
title: '',
|
||||
targetInboundId: 0,
|
||||
targetInboundRemark: '',
|
||||
targetProtocol: '',
|
||||
showFlow: false,
|
||||
flow: '',
|
||||
sourceInboundId: undefined,
|
||||
sources: [],
|
||||
sourceClients: [],
|
||||
|
|
@ -861,8 +878,18 @@
|
|||
const clients = app.getInboundClients(row) || [];
|
||||
return { id: row.id, label: `${row.remark} (${row.protocol}, ${clients.length})` };
|
||||
});
|
||||
let showFlow = false;
|
||||
try {
|
||||
const targetInbound = targetDbInbound.toInbound();
|
||||
showFlow = !!(targetInbound && typeof targetInbound.canEnableTlsFlow === 'function' && targetInbound.canEnableTlsFlow());
|
||||
} catch (e) {
|
||||
showFlow = false;
|
||||
}
|
||||
copyClientsModal.targetInboundId = targetDbInbound.id;
|
||||
copyClientsModal.targetInboundRemark = targetDbInbound.remark;
|
||||
copyClientsModal.targetProtocol = targetDbInbound.protocol;
|
||||
copyClientsModal.showFlow = showFlow;
|
||||
copyClientsModal.flow = '';
|
||||
copyClientsModal.title = `{{ i18n "pages.client.copyToInbound" }} ${targetDbInbound.remark}`;
|
||||
copyClientsModal.sources = sources;
|
||||
copyClientsModal.sourceInboundId = undefined;
|
||||
|
|
@ -925,14 +952,23 @@
|
|||
sourceInboundId: copyClientsModal.sourceInboundId,
|
||||
clientEmails: copyClientsModal.selectedEmails,
|
||||
};
|
||||
if (copyClientsModal.showFlow && copyClientsModal.flow) {
|
||||
payload.flow = copyClientsModal.flow;
|
||||
}
|
||||
try {
|
||||
const msg = await HttpUtil.post(`/panel/api/inbounds/${copyClientsModal.targetInboundId}/copyClients`, payload);
|
||||
if (!msg || !msg.success) return;
|
||||
const obj = msg.obj || {};
|
||||
const addedCount = (obj.added || []).length;
|
||||
const skippedCount = (obj.skipped || []).length;
|
||||
const errorCount = (obj.errors || []).length;
|
||||
app.$message.success(`{{ i18n "pages.client.copyResult" }}: +${addedCount}, ~${skippedCount}, !${errorCount}`);
|
||||
const errorList = obj.errors || [];
|
||||
if (addedCount > 0) {
|
||||
app.$message.success(`{{ i18n "pages.client.copyResultSuccess" }}: ${addedCount}`);
|
||||
} else {
|
||||
app.$message.warning('{{ i18n "pages.client.copyResultNone" }}');
|
||||
}
|
||||
if (errorList.length > 0) {
|
||||
app.$message.error(`{{ i18n "pages.client.copyResultErrors" }}: ${errorList.join('; ')}`);
|
||||
}
|
||||
copyClientsModal.close();
|
||||
await app.getDBInbounds();
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -801,7 +801,7 @@ func (s *InboundService) generateRandomCredential(targetProtocol model.Protocol)
|
|||
}
|
||||
}
|
||||
|
||||
func (s *InboundService) buildTargetClientFromSource(source model.Client, targetProtocol model.Protocol, email string) (model.Client, error) {
|
||||
func (s *InboundService) buildTargetClientFromSource(source model.Client, targetProtocol model.Protocol, email string, flow string) (model.Client, error) {
|
||||
nowTs := time.Now().UnixMilli()
|
||||
target := source
|
||||
target.Email = email
|
||||
|
|
@ -811,10 +811,16 @@ func (s *InboundService) buildTargetClientFromSource(source model.Client, target
|
|||
target.ID = ""
|
||||
target.Password = ""
|
||||
target.Auth = ""
|
||||
target.Flow = ""
|
||||
|
||||
switch targetProtocol {
|
||||
case model.VMESS, model.VLESS:
|
||||
case model.VMESS:
|
||||
target.ID = s.generateRandomCredential(targetProtocol)
|
||||
case model.VLESS:
|
||||
target.ID = s.generateRandomCredential(targetProtocol)
|
||||
if flow == "xtls-rprx-vision" || flow == "xtls-rprx-vision-udp443" {
|
||||
target.Flow = flow
|
||||
}
|
||||
case model.Trojan, model.Shadowsocks:
|
||||
target.Password = s.generateRandomCredential(targetProtocol)
|
||||
case model.Hysteria:
|
||||
|
|
@ -840,7 +846,7 @@ func (s *InboundService) nextAvailableCopiedEmail(originalEmail string, targetID
|
|||
}
|
||||
}
|
||||
|
||||
func (s *InboundService) CopyInboundClients(targetInboundID int, sourceInboundID int, clientEmails []string) (*CopyClientsResult, bool, error) {
|
||||
func (s *InboundService) CopyInboundClients(targetInboundID int, sourceInboundID int, clientEmails []string, flow string) (*CopyClientsResult, bool, error) {
|
||||
result := &CopyClientsResult{
|
||||
Added: []string{},
|
||||
Skipped: []string{},
|
||||
|
|
@ -913,7 +919,7 @@ func (s *InboundService) CopyInboundClients(targetInboundID int, sourceInboundID
|
|||
}
|
||||
|
||||
targetEmail := s.nextAvailableCopiedEmail(originalEmail, targetInboundID, occupiedEmails)
|
||||
targetClient, buildErr := s.buildTargetClientFromSource(sourceClient, targetInbound.Protocol, targetEmail)
|
||||
targetClient, buildErr := s.buildTargetClientFromSource(sourceClient, targetInbound.Protocol, targetEmail, flow)
|
||||
if buildErr != nil {
|
||||
result.Errors = append(result.Errors, fmt.Sprintf("%s: %v", originalEmail, buildErr))
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -305,6 +305,11 @@
|
|||
"copyEmailPreview" = "Resulting email preview"
|
||||
"copySelectSourceFirst" = "Please select a source inbound first."
|
||||
"copyResult" = "Copy result"
|
||||
"copyResultSuccess" = "Copied successfully"
|
||||
"copyResultNone" = "Nothing to copy: no clients selected or source is empty"
|
||||
"copyResultErrors" = "Copy errors"
|
||||
"copyFlowLabel" = "Flow for new clients (VLESS)"
|
||||
"copyFlowHint" = "Applied to all copied clients. Leave empty to skip."
|
||||
"selectAll" = "Select all"
|
||||
"clearAll" = "Clear all"
|
||||
"method" = "Method"
|
||||
|
|
|
|||
|
|
@ -305,6 +305,11 @@
|
|||
"copyEmailPreview" = "Предпросмотр итоговых email"
|
||||
"copySelectSourceFirst" = "Сначала выберите источник."
|
||||
"copyResult" = "Результат копирования"
|
||||
"copyResultSuccess" = "Успешно скопировано"
|
||||
"copyResultNone" = "Нечего копировать: ни одного клиента не выбрано или список источника пуст"
|
||||
"copyResultErrors" = "Ошибки при копировании"
|
||||
"copyFlowLabel" = "Flow для новых клиентов (VLESS)"
|
||||
"copyFlowHint" = "Применится ко всем копируемым клиентам. Оставьте пустым, чтобы не задавать."
|
||||
"selectAll" = "Выбрать всех"
|
||||
"clearAll" = "Снять всё"
|
||||
"method" = "Метод"
|
||||
|
|
|
|||
Loading…
Reference in a new issue