mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-31 10:14:15 +00:00
feat(inbounds): row action to delete all clients of an inbound
Adds POST /panel/api/inbounds/:id/delAllClients that collects every client email from settings.clients[] and runs ClientService.BulkDelete in one pass. Row action lives in the More menu as a danger item, only shown for multi-user inbounds that currently have at least one client; confirmation modal displays the live client count.
This commit is contained in:
parent
93eda06878
commit
e23599cb18
17 changed files with 127 additions and 5 deletions
|
|
@ -28,6 +28,7 @@ import {
|
||||||
BlockOutlined,
|
BlockOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
InfoCircleOutlined,
|
InfoCircleOutlined,
|
||||||
|
UsergroupDeleteOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
import { HttpUtil, SizeFormatter, IntlUtil, ColorUtils } from '@/utils';
|
import { HttpUtil, SizeFormatter, IntlUtil, ColorUtils } from '@/utils';
|
||||||
|
|
@ -167,6 +168,7 @@ export type RowAction =
|
||||||
| 'clipboard'
|
| 'clipboard'
|
||||||
| 'delete'
|
| 'delete'
|
||||||
| 'resetTraffic'
|
| 'resetTraffic'
|
||||||
|
| 'delAllClients'
|
||||||
| 'clone';
|
| 'clone';
|
||||||
|
|
||||||
export type GeneralAction = 'import' | 'export' | 'subs' | 'resetInbounds';
|
export type GeneralAction = 'import' | 'export' | 'subs' | 'resetInbounds';
|
||||||
|
|
@ -228,11 +230,12 @@ function showQrCodeMenu(dbInbound: DBInboundRecord): boolean {
|
||||||
interface RowActionsMenuProps {
|
interface RowActionsMenuProps {
|
||||||
record: DBInboundRecord;
|
record: DBInboundRecord;
|
||||||
subEnable: boolean;
|
subEnable: boolean;
|
||||||
|
hasClients: boolean;
|
||||||
onClick: (key: RowAction) => void;
|
onClick: (key: RowAction) => void;
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRowActionsMenu({ record, subEnable, t, isMobile }: { record: DBInboundRecord; subEnable: boolean; t: (k: string) => string; isMobile?: boolean }): MenuProps['items'] {
|
function buildRowActionsMenu({ record, subEnable, t, isMobile, hasClients }: { record: DBInboundRecord; subEnable: boolean; t: (k: string) => string; isMobile?: boolean; hasClients?: boolean }): MenuProps['items'] {
|
||||||
const items: MenuProps['items'] = [];
|
const items: MenuProps['items'] = [];
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
items.push({ key: 'edit', icon: <EditOutlined />, label: t('edit') });
|
items.push({ key: 'edit', icon: <EditOutlined />, label: t('edit') });
|
||||||
|
|
@ -255,11 +258,14 @@ function buildRowActionsMenu({ record, subEnable, t, isMobile }: { record: DBInb
|
||||||
items.push({ key: 'clipboard', icon: <CopyOutlined />, label: t('pages.inbounds.exportInbound') });
|
items.push({ key: 'clipboard', icon: <CopyOutlined />, label: t('pages.inbounds.exportInbound') });
|
||||||
items.push({ key: 'resetTraffic', icon: <RetweetOutlined />, label: t('pages.inbounds.resetTraffic') });
|
items.push({ key: 'resetTraffic', icon: <RetweetOutlined />, label: t('pages.inbounds.resetTraffic') });
|
||||||
items.push({ key: 'clone', icon: <BlockOutlined />, label: t('pages.inbounds.clone') });
|
items.push({ key: 'clone', icon: <BlockOutlined />, label: t('pages.inbounds.clone') });
|
||||||
|
if (isInboundMultiUser(record) && hasClients) {
|
||||||
|
items.push({ key: 'delAllClients', icon: <UsergroupDeleteOutlined />, danger: true, label: t('pages.inbounds.delAllClients') });
|
||||||
|
}
|
||||||
items.push({ key: 'delete', icon: <DeleteOutlined />, danger: true, label: t('delete') });
|
items.push({ key: 'delete', icon: <DeleteOutlined />, danger: true, label: t('delete') });
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RowActionsCell({ record, subEnable, onClick }: RowActionsMenuProps) {
|
function RowActionsCell({ record, subEnable, hasClients, onClick }: RowActionsMenuProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="action-buttons">
|
<div className="action-buttons">
|
||||||
|
|
@ -267,7 +273,7 @@ function RowActionsCell({ record, subEnable, onClick }: RowActionsMenuProps) {
|
||||||
<Dropdown
|
<Dropdown
|
||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
menu={{
|
menu={{
|
||||||
items: buildRowActionsMenu({ record, subEnable, t }),
|
items: buildRowActionsMenu({ record, subEnable, t, hasClients }),
|
||||||
onClick: ({ key }) => onClick(key as RowAction),
|
onClick: ({ key }) => onClick(key as RowAction),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -350,6 +356,7 @@ export default function InboundList({
|
||||||
<RowActionsCell
|
<RowActionsCell
|
||||||
record={record}
|
record={record}
|
||||||
subEnable={subEnable}
|
subEnable={subEnable}
|
||||||
|
hasClients={(clientCount[record.id]?.clients || 0) > 0}
|
||||||
onClick={(key) => onRowAction({ key, dbInbound: record })}
|
onClick={(key) => onRowAction({ key, dbInbound: record })}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|
@ -623,7 +630,7 @@ export default function InboundList({
|
||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
placement="bottomRight"
|
placement="bottomRight"
|
||||||
menu={{
|
menu={{
|
||||||
items: buildRowActionsMenu({ record, subEnable, t, isMobile: true }),
|
items: buildRowActionsMenu({ record, subEnable, t, isMobile: true, hasClients: (clientCount[record.id]?.clients || 0) > 0 }),
|
||||||
onClick: ({ key }) => onRowAction({ key: key as RowAction, dbInbound: record }),
|
onClick: ({ key }) => onRowAction({ key: key as RowAction, dbInbound: record }),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ type RowAction =
|
||||||
| 'clipboard'
|
| 'clipboard'
|
||||||
| 'delete'
|
| 'delete'
|
||||||
| 'resetTraffic'
|
| 'resetTraffic'
|
||||||
|
| 'delAllClients'
|
||||||
| 'clone';
|
| 'clone';
|
||||||
|
|
||||||
type GeneralAction = 'import' | 'export' | 'subs' | 'resetInbounds';
|
type GeneralAction = 'import' | 'export' | 'subs' | 'resetInbounds';
|
||||||
|
|
@ -355,6 +356,21 @@ export default function InboundsPage() {
|
||||||
});
|
});
|
||||||
}, [modal, refresh, t]);
|
}, [modal, refresh, t]);
|
||||||
|
|
||||||
|
const confirmDelAllClients = useCallback((dbInbound: DBInbound) => {
|
||||||
|
const count = clientCount[dbInbound.id]?.clients || 0;
|
||||||
|
modal.confirm({
|
||||||
|
title: t('pages.inbounds.delAllClientsConfirmTitle', { remark: dbInbound.remark, count }),
|
||||||
|
content: t('pages.inbounds.delAllClientsConfirmContent'),
|
||||||
|
okText: t('delete'),
|
||||||
|
okType: 'danger',
|
||||||
|
cancelText: t('cancel'),
|
||||||
|
onOk: async () => {
|
||||||
|
const msg = await HttpUtil.post(`/panel/api/inbounds/${dbInbound.id}/delAllClients`);
|
||||||
|
if (msg?.success) await refresh();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [modal, refresh, t, clientCount]);
|
||||||
|
|
||||||
const confirmClone = useCallback((dbInbound: DBInbound) => {
|
const confirmClone = useCallback((dbInbound: DBInbound) => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: t('pages.inbounds.cloneConfirmTitle', { remark: dbInbound.remark }),
|
title: t('pages.inbounds.cloneConfirmTitle', { remark: dbInbound.remark }),
|
||||||
|
|
@ -456,13 +472,16 @@ export default function InboundsPage() {
|
||||||
case 'resetTraffic':
|
case 'resetTraffic':
|
||||||
confirmResetTraffic(target);
|
confirmResetTraffic(target);
|
||||||
break;
|
break;
|
||||||
|
case 'delAllClients':
|
||||||
|
confirmDelAllClients(target);
|
||||||
|
break;
|
||||||
case 'clone':
|
case 'clone':
|
||||||
confirmClone(target);
|
confirmClone(target);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
messageApi.info(`Action "${key}" — coming in a later 5f subphase`);
|
messageApi.info(`Action "${key}" — coming in a later 5f subphase`);
|
||||||
}
|
}
|
||||||
}, [hydrateInbound, openEdit, checkFallback, findClientIndex, exportInboundLinks, exportInboundSubs, exportInboundClipboard, confirmDelete, confirmResetTraffic, confirmClone, messageApi]);
|
}, [hydrateInbound, openEdit, checkFallback, findClientIndex, exportInboundLinks, exportInboundSubs, exportInboundClipboard, confirmDelete, confirmResetTraffic, confirmDelAllClients, confirmClone, messageApi]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider theme={antdThemeConfig}>
|
<ConfigProvider theme={antdThemeConfig}>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
// InboundController handles HTTP requests related to Xray inbounds management.
|
// InboundController handles HTTP requests related to Xray inbounds management.
|
||||||
type InboundController struct {
|
type InboundController struct {
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
|
clientService service.ClientService
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
fallbackService service.FallbackService
|
fallbackService service.FallbackService
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +73,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/update/:id", a.updateInbound)
|
g.POST("/update/:id", a.updateInbound)
|
||||||
g.POST("/setEnable/:id", a.setInboundEnable)
|
g.POST("/setEnable/:id", a.setInboundEnable)
|
||||||
g.POST("/:id/resetTraffic", a.resetInboundTraffic)
|
g.POST("/:id/resetTraffic", a.resetInboundTraffic)
|
||||||
|
g.POST("/:id/delAllClients", a.delAllInboundClients)
|
||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/import", a.importInbound)
|
g.POST("/import", a.importInbound)
|
||||||
g.POST("/:id/fallbacks", a.setFallbacks)
|
g.POST("/:id/fallbacks", a.setFallbacks)
|
||||||
|
|
@ -276,6 +278,40 @@ func (a *InboundController) resetInboundTraffic(c *gin.Context) {
|
||||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetInboundTrafficSuccess"), nil)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetInboundTrafficSuccess"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delAllInboundClients removes every client attached to a specific inbound
|
||||||
|
// while keeping the inbound itself. Internally collects the current email
|
||||||
|
// list from settings.clients[] and feeds it into ClientService.BulkDelete,
|
||||||
|
// which handles per-inbound JSON rewriting, runtime user removal, traffic
|
||||||
|
// row cleanup, and the SyncInbound mapping pass in one optimized cycle.
|
||||||
|
func (a *InboundController) delAllInboundClients(c *gin.Context) {
|
||||||
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emails, err := a.inboundService.EmailsByInbound(id)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(emails) == 0 {
|
||||||
|
jsonObj(c, service.BulkDeleteResult{}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, needRestart, err := a.clientService.BulkDelete(&a.inboundService, emails, false)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, result, nil)
|
||||||
|
if needRestart {
|
||||||
|
a.xrayService.SetToNeedRestart()
|
||||||
|
}
|
||||||
|
user := session.GetLoginUser(c)
|
||||||
|
a.broadcastInboundsUpdate(user.Id)
|
||||||
|
notifyClientsChanged()
|
||||||
|
}
|
||||||
|
|
||||||
// resetAllTraffics resets all traffic counters across all inbounds.
|
// resetAllTraffics resets all traffic counters across all inbounds.
|
||||||
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||||
err := a.inboundService.ResetAllTraffics()
|
err := a.inboundService.ResetAllTraffics()
|
||||||
|
|
|
||||||
|
|
@ -2415,6 +2415,27 @@ func (s *InboundService) ResetInboundTraffic(id int) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EmailsByInbound returns the list of client emails currently configured on
|
||||||
|
// an inbound's settings.clients[]. Used by the "delete all clients" flow on
|
||||||
|
// the inbounds page, which then feeds the list into ClientService.BulkDelete.
|
||||||
|
func (s *InboundService) EmailsByInbound(inboundId int) ([]string, error) {
|
||||||
|
inbound, err := s.GetInbound(inboundId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clients, err := s.GetClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
emails := make([]string, 0, len(clients))
|
||||||
|
for _, c := range clients {
|
||||||
|
if e := strings.TrimSpace(c.Email); e != "" {
|
||||||
|
emails = append(emails, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emails, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) DelDepletedClients(id int) (err error) {
|
func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "يعيد عدادات الإرسال/الاستقبال لهذا الإدخال إلى 0.",
|
"resetConfirmContent": "يعيد عدادات الإرسال/الاستقبال لهذا الإدخال إلى 0.",
|
||||||
"cloneConfirmTitle": "نسخ الإدخال \"{remark}\"؟",
|
"cloneConfirmTitle": "نسخ الإدخال \"{remark}\"؟",
|
||||||
"cloneConfirmContent": "ينشئ نسخة بمنفذ جديد وقائمة عملاء فارغة.",
|
"cloneConfirmContent": "ينشئ نسخة بمنفذ جديد وقائمة عملاء فارغة.",
|
||||||
|
"delAllClients": "حذف جميع العملاء",
|
||||||
|
"delAllClientsConfirmTitle": "حذف جميع العملاء البالغ عددهم {count} من \"{remark}\"؟",
|
||||||
|
"delAllClientsConfirmContent": "يزيل كل عميل من هذا الإدخال ويحذف سجلات حركة المرور الخاصة بهم. يتم الاحتفاظ بالإدخال نفسه. لا يمكن التراجع عن هذا.",
|
||||||
"exportLinksTitle": "تصدير روابط الإدخال",
|
"exportLinksTitle": "تصدير روابط الإدخال",
|
||||||
"exportSubsTitle": "تصدير روابط الاشتراك",
|
"exportSubsTitle": "تصدير روابط الاشتراك",
|
||||||
"exportAllLinksTitle": "تصدير كل روابط الإدخالات",
|
"exportAllLinksTitle": "تصدير كل روابط الإدخالات",
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,9 @@
|
||||||
"resetConfirmContent": "Resets up/down counters to 0 for this inbound.",
|
"resetConfirmContent": "Resets up/down counters to 0 for this inbound.",
|
||||||
"cloneConfirmTitle": "Clone inbound \"{remark}\"?",
|
"cloneConfirmTitle": "Clone inbound \"{remark}\"?",
|
||||||
"cloneConfirmContent": "Creates a copy with a new port and an empty client list.",
|
"cloneConfirmContent": "Creates a copy with a new port and an empty client list.",
|
||||||
|
"delAllClients": "Delete All Clients",
|
||||||
|
"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.",
|
||||||
"exportLinksTitle": "Export inbound links",
|
"exportLinksTitle": "Export inbound links",
|
||||||
"exportSubsTitle": "Export subscription links",
|
"exportSubsTitle": "Export subscription links",
|
||||||
"exportAllLinksTitle": "Export all inbound links",
|
"exportAllLinksTitle": "Export all inbound links",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "Restablece los contadores de subida/bajada a 0 para este inbound.",
|
"resetConfirmContent": "Restablece los contadores de subida/bajada a 0 para este inbound.",
|
||||||
"cloneConfirmTitle": "¿Clonar el inbound \"{remark}\"?",
|
"cloneConfirmTitle": "¿Clonar el inbound \"{remark}\"?",
|
||||||
"cloneConfirmContent": "Crea una copia con un puerto nuevo y una lista de clientes vacía.",
|
"cloneConfirmContent": "Crea una copia con un puerto nuevo y una lista de clientes vacía.",
|
||||||
|
"delAllClients": "Eliminar todos los clientes",
|
||||||
|
"delAllClientsConfirmTitle": "¿Eliminar los {count} clientes de \"{remark}\"?",
|
||||||
|
"delAllClientsConfirmContent": "Elimina todos los clientes de este inbound y sus registros de tráfico. El inbound se mantiene. Esto no se puede deshacer.",
|
||||||
"exportLinksTitle": "Exportar enlaces del inbound",
|
"exportLinksTitle": "Exportar enlaces del inbound",
|
||||||
"exportSubsTitle": "Exportar enlaces de suscripción",
|
"exportSubsTitle": "Exportar enlaces de suscripción",
|
||||||
"exportAllLinksTitle": "Exportar todos los enlaces de inbound",
|
"exportAllLinksTitle": "Exportar todos los enlaces de inbound",
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,9 @@
|
||||||
"resetConfirmContent": "شمارندههای ارسال/دریافت این اینباند به صفر برمیگردد.",
|
"resetConfirmContent": "شمارندههای ارسال/دریافت این اینباند به صفر برمیگردد.",
|
||||||
"cloneConfirmTitle": "اینباند «{remark}» کپی شود؟",
|
"cloneConfirmTitle": "اینباند «{remark}» کپی شود؟",
|
||||||
"cloneConfirmContent": "یک نسخه با پورت جدید و لیست کلاینت خالی ساخته میشود.",
|
"cloneConfirmContent": "یک نسخه با پورت جدید و لیست کلاینت خالی ساخته میشود.",
|
||||||
|
"delAllClients": "حذف همه کلاینتها",
|
||||||
|
"delAllClientsConfirmTitle": "حذف هر {count} کلاینت اینباند «{remark}»؟",
|
||||||
|
"delAllClientsConfirmContent": "تمام کلاینتهای این اینباند به همراه رکوردهای ترافیکشان حذف میشوند. خود اینباند باقی میماند. این عمل غیرقابل بازگشت است.",
|
||||||
"exportLinksTitle": "خروجی لینکهای اینباند",
|
"exportLinksTitle": "خروجی لینکهای اینباند",
|
||||||
"exportSubsTitle": "خروجی لینکهای ساب",
|
"exportSubsTitle": "خروجی لینکهای ساب",
|
||||||
"exportAllLinksTitle": "خروجی لینکهای همه اینباندها",
|
"exportAllLinksTitle": "خروجی لینکهای همه اینباندها",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "Mengatur ulang counter unggah/unduh ke 0 untuk inbound ini.",
|
"resetConfirmContent": "Mengatur ulang counter unggah/unduh ke 0 untuk inbound ini.",
|
||||||
"cloneConfirmTitle": "Klon inbound \"{remark}\"?",
|
"cloneConfirmTitle": "Klon inbound \"{remark}\"?",
|
||||||
"cloneConfirmContent": "Membuat salinan dengan port baru dan daftar klien kosong.",
|
"cloneConfirmContent": "Membuat salinan dengan port baru dan daftar klien kosong.",
|
||||||
|
"delAllClients": "Hapus Semua Klien",
|
||||||
|
"delAllClientsConfirmTitle": "Hapus semua {count} klien dari \"{remark}\"?",
|
||||||
|
"delAllClientsConfirmContent": "Menghapus setiap klien dari inbound ini dan menghapus catatan trafiknya. Inbound itu sendiri dipertahankan. Tindakan ini tidak dapat dibatalkan.",
|
||||||
"exportLinksTitle": "Ekspor tautan inbound",
|
"exportLinksTitle": "Ekspor tautan inbound",
|
||||||
"exportSubsTitle": "Ekspor tautan langganan",
|
"exportSubsTitle": "Ekspor tautan langganan",
|
||||||
"exportAllLinksTitle": "Ekspor semua tautan inbound",
|
"exportAllLinksTitle": "Ekspor semua tautan inbound",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "このインバウンドの送受信カウンタを 0 にリセットします。",
|
"resetConfirmContent": "このインバウンドの送受信カウンタを 0 にリセットします。",
|
||||||
"cloneConfirmTitle": "インバウンド「{remark}」を複製しますか?",
|
"cloneConfirmTitle": "インバウンド「{remark}」を複製しますか?",
|
||||||
"cloneConfirmContent": "新しいポートと空のクライアント一覧でコピーを作成します。",
|
"cloneConfirmContent": "新しいポートと空のクライアント一覧でコピーを作成します。",
|
||||||
|
"delAllClients": "すべてのクライアントを削除",
|
||||||
|
"delAllClientsConfirmTitle": "「{remark}」から {count} 件のクライアントをすべて削除しますか?",
|
||||||
|
"delAllClientsConfirmContent": "このインバウンドからすべてのクライアントを削除し、トラフィックレコードも破棄します。インバウンド自体は保持されます。この操作は取り消せません。",
|
||||||
"exportLinksTitle": "インバウンドリンクのエクスポート",
|
"exportLinksTitle": "インバウンドリンクのエクスポート",
|
||||||
"exportSubsTitle": "サブスクリプションリンクのエクスポート",
|
"exportSubsTitle": "サブスクリプションリンクのエクスポート",
|
||||||
"exportAllLinksTitle": "全インバウンドリンクのエクスポート",
|
"exportAllLinksTitle": "全インバウンドリンクのエクスポート",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "Zera os contadores de envio/recebimento para este inbound.",
|
"resetConfirmContent": "Zera os contadores de envio/recebimento para este inbound.",
|
||||||
"cloneConfirmTitle": "Clonar o inbound \"{remark}\"?",
|
"cloneConfirmTitle": "Clonar o inbound \"{remark}\"?",
|
||||||
"cloneConfirmContent": "Cria uma cópia com uma nova porta e lista de clientes vazia.",
|
"cloneConfirmContent": "Cria uma cópia com uma nova porta e lista de clientes vazia.",
|
||||||
|
"delAllClients": "Excluir todos os clientes",
|
||||||
|
"delAllClientsConfirmTitle": "Excluir todos os {count} clientes de \"{remark}\"?",
|
||||||
|
"delAllClientsConfirmContent": "Remove todos os clientes deste inbound e descarta seus registros de tráfego. O inbound em si é mantido. Esta ação não pode ser desfeita.",
|
||||||
"exportLinksTitle": "Exportar links do inbound",
|
"exportLinksTitle": "Exportar links do inbound",
|
||||||
"exportSubsTitle": "Exportar links de assinatura",
|
"exportSubsTitle": "Exportar links de assinatura",
|
||||||
"exportAllLinksTitle": "Exportar todos os links de inbound",
|
"exportAllLinksTitle": "Exportar todos os links de inbound",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "Сбрасывает счётчики отправки/получения этого подключения до 0.",
|
"resetConfirmContent": "Сбрасывает счётчики отправки/получения этого подключения до 0.",
|
||||||
"cloneConfirmTitle": "Клонировать подключение \"{remark}\"?",
|
"cloneConfirmTitle": "Клонировать подключение \"{remark}\"?",
|
||||||
"cloneConfirmContent": "Создаёт копию с новым портом и пустым списком клиентов.",
|
"cloneConfirmContent": "Создаёт копию с новым портом и пустым списком клиентов.",
|
||||||
|
"delAllClients": "Удалить всех клиентов",
|
||||||
|
"delAllClientsConfirmTitle": "Удалить всех {count} клиентов из \"{remark}\"?",
|
||||||
|
"delAllClientsConfirmContent": "Удаляет всех клиентов этого подключения и сбрасывает их записи трафика. Само подключение сохраняется. Это действие нельзя отменить.",
|
||||||
"exportLinksTitle": "Экспортировать ссылки подключения",
|
"exportLinksTitle": "Экспортировать ссылки подключения",
|
||||||
"exportSubsTitle": "Экспортировать ссылки подписки",
|
"exportSubsTitle": "Экспортировать ссылки подписки",
|
||||||
"exportAllLinksTitle": "Экспортировать все ссылки подключений",
|
"exportAllLinksTitle": "Экспортировать все ссылки подключений",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "Bu inbound için gönderme/alma sayaçlarını 0'a sıfırlar.",
|
"resetConfirmContent": "Bu inbound için gönderme/alma sayaçlarını 0'a sıfırlar.",
|
||||||
"cloneConfirmTitle": "\"{remark}\" inbound klonlansın mı?",
|
"cloneConfirmTitle": "\"{remark}\" inbound klonlansın mı?",
|
||||||
"cloneConfirmContent": "Yeni bir port ve boş istemci listesiyle bir kopya oluşturur.",
|
"cloneConfirmContent": "Yeni bir port ve boş istemci listesiyle bir kopya oluşturur.",
|
||||||
|
"delAllClients": "Tüm istemcileri sil",
|
||||||
|
"delAllClientsConfirmTitle": "\"{remark}\" içindeki {count} istemcinin tamamı silinsin mi?",
|
||||||
|
"delAllClientsConfirmContent": "Bu inbound'a ait tüm istemcileri ve trafik kayıtlarını siler. Inbound'un kendisi korunur. Bu işlem geri alınamaz.",
|
||||||
"exportLinksTitle": "Inbound bağlantılarını dışa aktar",
|
"exportLinksTitle": "Inbound bağlantılarını dışa aktar",
|
||||||
"exportSubsTitle": "Abonelik bağlantılarını dışa aktar",
|
"exportSubsTitle": "Abonelik bağlantılarını dışa aktar",
|
||||||
"exportAllLinksTitle": "Tüm inbound bağlantılarını dışa aktar",
|
"exportAllLinksTitle": "Tüm inbound bağlantılarını dışa aktar",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "Скидає лічильники відправки/отримання цього вхідного до 0.",
|
"resetConfirmContent": "Скидає лічильники відправки/отримання цього вхідного до 0.",
|
||||||
"cloneConfirmTitle": "Клонувати вхідні \"{remark}\"?",
|
"cloneConfirmTitle": "Клонувати вхідні \"{remark}\"?",
|
||||||
"cloneConfirmContent": "Створює копію з новим портом і порожнім списком клієнтів.",
|
"cloneConfirmContent": "Створює копію з новим портом і порожнім списком клієнтів.",
|
||||||
|
"delAllClients": "Видалити всіх клієнтів",
|
||||||
|
"delAllClientsConfirmTitle": "Видалити всіх {count} клієнтів із \"{remark}\"?",
|
||||||
|
"delAllClientsConfirmContent": "Видаляє всіх клієнтів цього вхідного й скидає їхні записи трафіку. Сам вхідний зберігається. Цю дію не можна скасувати.",
|
||||||
"exportLinksTitle": "Експортувати посилання вхідних",
|
"exportLinksTitle": "Експортувати посилання вхідних",
|
||||||
"exportSubsTitle": "Експортувати посилання підписок",
|
"exportSubsTitle": "Експортувати посилання підписок",
|
||||||
"exportAllLinksTitle": "Експортувати всі посилання вхідних",
|
"exportAllLinksTitle": "Експортувати всі посилання вхідних",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "Đặt lại bộ đếm lên/xuống về 0 cho inbound này.",
|
"resetConfirmContent": "Đặt lại bộ đếm lên/xuống về 0 cho inbound này.",
|
||||||
"cloneConfirmTitle": "Sao chép inbound \"{remark}\"?",
|
"cloneConfirmTitle": "Sao chép inbound \"{remark}\"?",
|
||||||
"cloneConfirmContent": "Tạo bản sao với cổng mới và danh sách khách hàng trống.",
|
"cloneConfirmContent": "Tạo bản sao với cổng mới và danh sách khách hàng trống.",
|
||||||
|
"delAllClients": "Xóa tất cả khách hàng",
|
||||||
|
"delAllClientsConfirmTitle": "Xóa toàn bộ {count} khách hàng khỏi \"{remark}\"?",
|
||||||
|
"delAllClientsConfirmContent": "Xóa mọi khách hàng khỏi inbound này và hủy bản ghi lưu lượng của họ. Bản thân inbound vẫn được giữ lại. Hành động này không thể hoàn tác.",
|
||||||
"exportLinksTitle": "Xuất liên kết inbound",
|
"exportLinksTitle": "Xuất liên kết inbound",
|
||||||
"exportSubsTitle": "Xuất liên kết đăng ký",
|
"exportSubsTitle": "Xuất liên kết đăng ký",
|
||||||
"exportAllLinksTitle": "Xuất tất cả liên kết inbound",
|
"exportAllLinksTitle": "Xuất tất cả liên kết inbound",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "将此入站的上/下行计数器清零。",
|
"resetConfirmContent": "将此入站的上/下行计数器清零。",
|
||||||
"cloneConfirmTitle": "克隆入站 \"{remark}\"?",
|
"cloneConfirmTitle": "克隆入站 \"{remark}\"?",
|
||||||
"cloneConfirmContent": "使用新端口和空客户端列表创建副本。",
|
"cloneConfirmContent": "使用新端口和空客户端列表创建副本。",
|
||||||
|
"delAllClients": "删除所有客户端",
|
||||||
|
"delAllClientsConfirmTitle": "从 \"{remark}\" 中删除全部 {count} 个客户端?",
|
||||||
|
"delAllClientsConfirmContent": "从此入站中移除每个客户端并丢弃其流量记录。入站本身将保留。此操作无法撤销。",
|
||||||
"exportLinksTitle": "导出入站链接",
|
"exportLinksTitle": "导出入站链接",
|
||||||
"exportSubsTitle": "导出订阅链接",
|
"exportSubsTitle": "导出订阅链接",
|
||||||
"exportAllLinksTitle": "导出所有入站链接",
|
"exportAllLinksTitle": "导出所有入站链接",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@
|
||||||
"resetConfirmContent": "將此入站的上/下行計數器歸零。",
|
"resetConfirmContent": "將此入站的上/下行計數器歸零。",
|
||||||
"cloneConfirmTitle": "複製入站「{remark}」?",
|
"cloneConfirmTitle": "複製入站「{remark}」?",
|
||||||
"cloneConfirmContent": "使用新連接埠和空客戶端清單建立副本。",
|
"cloneConfirmContent": "使用新連接埠和空客戶端清單建立副本。",
|
||||||
|
"delAllClients": "刪除所有客戶端",
|
||||||
|
"delAllClientsConfirmTitle": "從「{remark}」中刪除全部 {count} 個客戶端?",
|
||||||
|
"delAllClientsConfirmContent": "從此入站中移除每個客戶端並捨棄其流量記錄。入站本身將保留。此操作無法復原。",
|
||||||
"exportLinksTitle": "匯出入站連結",
|
"exportLinksTitle": "匯出入站連結",
|
||||||
"exportSubsTitle": "匯出訂閱連結",
|
"exportSubsTitle": "匯出訂閱連結",
|
||||||
"exportAllLinksTitle": "匯出所有入站連結",
|
"exportAllLinksTitle": "匯出所有入站連結",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue