mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-08 14:14:19 +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 {
|
type CopyInboundClientsRequest struct {
|
||||||
SourceInboundID int `form:"sourceInboundId" json:"sourceInboundId"`
|
SourceInboundID int `form:"sourceInboundId" json:"sourceInboundId"`
|
||||||
ClientEmails []string `form:"clientEmails" json:"clientEmails"`
|
ClientEmails []string `form:"clientEmails" json:"clientEmails"`
|
||||||
|
Flow string `form:"flow" json:"flow"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInbounds retrieves the list of inbounds for the logged-in user.
|
// getInbounds retrieves the list of inbounds for the logged-in user.
|
||||||
|
|
@ -285,7 +286,7 @@ func (a *InboundController) copyInboundClients(c *gin.Context) {
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -826,6 +826,20 @@
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</div>
|
</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 v-if="copyClientsModal.selectedEmails.length > 0">
|
||||||
<div style="margin-bottom: 4px;">{{ i18n "pages.client.copyEmailPreview" }}</div>
|
<div style="margin-bottom: 4px;">{{ i18n "pages.client.copyEmailPreview" }}</div>
|
||||||
<div style="max-height: 120px; overflow-y: auto;">
|
<div style="max-height: 120px; overflow-y: auto;">
|
||||||
|
|
@ -849,6 +863,9 @@
|
||||||
title: '',
|
title: '',
|
||||||
targetInboundId: 0,
|
targetInboundId: 0,
|
||||||
targetInboundRemark: '',
|
targetInboundRemark: '',
|
||||||
|
targetProtocol: '',
|
||||||
|
showFlow: false,
|
||||||
|
flow: '',
|
||||||
sourceInboundId: undefined,
|
sourceInboundId: undefined,
|
||||||
sources: [],
|
sources: [],
|
||||||
sourceClients: [],
|
sourceClients: [],
|
||||||
|
|
@ -861,8 +878,18 @@
|
||||||
const clients = app.getInboundClients(row) || [];
|
const clients = app.getInboundClients(row) || [];
|
||||||
return { id: row.id, label: `${row.remark} (${row.protocol}, ${clients.length})` };
|
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.targetInboundId = targetDbInbound.id;
|
||||||
copyClientsModal.targetInboundRemark = targetDbInbound.remark;
|
copyClientsModal.targetInboundRemark = targetDbInbound.remark;
|
||||||
|
copyClientsModal.targetProtocol = targetDbInbound.protocol;
|
||||||
|
copyClientsModal.showFlow = showFlow;
|
||||||
|
copyClientsModal.flow = '';
|
||||||
copyClientsModal.title = `{{ i18n "pages.client.copyToInbound" }} ${targetDbInbound.remark}`;
|
copyClientsModal.title = `{{ i18n "pages.client.copyToInbound" }} ${targetDbInbound.remark}`;
|
||||||
copyClientsModal.sources = sources;
|
copyClientsModal.sources = sources;
|
||||||
copyClientsModal.sourceInboundId = undefined;
|
copyClientsModal.sourceInboundId = undefined;
|
||||||
|
|
@ -925,14 +952,23 @@
|
||||||
sourceInboundId: copyClientsModal.sourceInboundId,
|
sourceInboundId: copyClientsModal.sourceInboundId,
|
||||||
clientEmails: copyClientsModal.selectedEmails,
|
clientEmails: copyClientsModal.selectedEmails,
|
||||||
};
|
};
|
||||||
|
if (copyClientsModal.showFlow && copyClientsModal.flow) {
|
||||||
|
payload.flow = copyClientsModal.flow;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post(`/panel/api/inbounds/${copyClientsModal.targetInboundId}/copyClients`, payload);
|
const msg = await HttpUtil.post(`/panel/api/inbounds/${copyClientsModal.targetInboundId}/copyClients`, payload);
|
||||||
if (!msg || !msg.success) return;
|
if (!msg || !msg.success) return;
|
||||||
const obj = msg.obj || {};
|
const obj = msg.obj || {};
|
||||||
const addedCount = (obj.added || []).length;
|
const addedCount = (obj.added || []).length;
|
||||||
const skippedCount = (obj.skipped || []).length;
|
const errorList = obj.errors || [];
|
||||||
const errorCount = (obj.errors || []).length;
|
if (addedCount > 0) {
|
||||||
app.$message.success(`{{ i18n "pages.client.copyResult" }}: +${addedCount}, ~${skippedCount}, !${errorCount}`);
|
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();
|
copyClientsModal.close();
|
||||||
await app.getDBInbounds();
|
await app.getDBInbounds();
|
||||||
} finally {
|
} 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()
|
nowTs := time.Now().UnixMilli()
|
||||||
target := source
|
target := source
|
||||||
target.Email = email
|
target.Email = email
|
||||||
|
|
@ -811,10 +811,16 @@ func (s *InboundService) buildTargetClientFromSource(source model.Client, target
|
||||||
target.ID = ""
|
target.ID = ""
|
||||||
target.Password = ""
|
target.Password = ""
|
||||||
target.Auth = ""
|
target.Auth = ""
|
||||||
|
target.Flow = ""
|
||||||
|
|
||||||
switch targetProtocol {
|
switch targetProtocol {
|
||||||
case model.VMESS, model.VLESS:
|
case model.VMESS:
|
||||||
target.ID = s.generateRandomCredential(targetProtocol)
|
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:
|
case model.Trojan, model.Shadowsocks:
|
||||||
target.Password = s.generateRandomCredential(targetProtocol)
|
target.Password = s.generateRandomCredential(targetProtocol)
|
||||||
case model.Hysteria:
|
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{
|
result := &CopyClientsResult{
|
||||||
Added: []string{},
|
Added: []string{},
|
||||||
Skipped: []string{},
|
Skipped: []string{},
|
||||||
|
|
@ -913,7 +919,7 @@ func (s *InboundService) CopyInboundClients(targetInboundID int, sourceInboundID
|
||||||
}
|
}
|
||||||
|
|
||||||
targetEmail := s.nextAvailableCopiedEmail(originalEmail, targetInboundID, occupiedEmails)
|
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 {
|
if buildErr != nil {
|
||||||
result.Errors = append(result.Errors, fmt.Sprintf("%s: %v", originalEmail, buildErr))
|
result.Errors = append(result.Errors, fmt.Sprintf("%s: %v", originalEmail, buildErr))
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,11 @@
|
||||||
"copyEmailPreview" = "Resulting email preview"
|
"copyEmailPreview" = "Resulting email preview"
|
||||||
"copySelectSourceFirst" = "Please select a source inbound first."
|
"copySelectSourceFirst" = "Please select a source inbound first."
|
||||||
"copyResult" = "Copy result"
|
"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"
|
"selectAll" = "Select all"
|
||||||
"clearAll" = "Clear all"
|
"clearAll" = "Clear all"
|
||||||
"method" = "Method"
|
"method" = "Method"
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,11 @@
|
||||||
"copyEmailPreview" = "Предпросмотр итоговых email"
|
"copyEmailPreview" = "Предпросмотр итоговых email"
|
||||||
"copySelectSourceFirst" = "Сначала выберите источник."
|
"copySelectSourceFirst" = "Сначала выберите источник."
|
||||||
"copyResult" = "Результат копирования"
|
"copyResult" = "Результат копирования"
|
||||||
|
"copyResultSuccess" = "Успешно скопировано"
|
||||||
|
"copyResultNone" = "Нечего копировать: ни одного клиента не выбрано или список источника пуст"
|
||||||
|
"copyResultErrors" = "Ошибки при копировании"
|
||||||
|
"copyFlowLabel" = "Flow для новых клиентов (VLESS)"
|
||||||
|
"copyFlowHint" = "Применится ко всем копируемым клиентам. Оставьте пустым, чтобы не задавать."
|
||||||
"selectAll" = "Выбрать всех"
|
"selectAll" = "Выбрать всех"
|
||||||
"clearAll" = "Снять всё"
|
"clearAll" = "Снять всё"
|
||||||
"method" = "Метод"
|
"method" = "Метод"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue