Compare commits

...

12 commits

Author SHA1 Message Date
nistootsin
576645af71
Merge a290704cfa into 6e5ed881f2 2025-03-18 22:19:40 +01:00
Shishkevich D.
6e5ed881f2
chore: pretty Inbounds page (#2791)
* chore: pretty 'Inbounds' page

* chore: return styles for aCustomStatistic

styles was intended to properly display a-statistic in the app, but for some unknown reason it was removed

* fix: switch style in dark mode

---------
2025-03-18 22:13:01 +01:00
Sanaei
a290704cfa
Merge branch 'main' into develop-bot/add_client_feature 2025-03-18 10:00:57 +01:00
nistootsin
2bcdc97203
Merge branch 'MHSanaei:main' into develop-bot/add_client_feature 2025-03-18 02:37:15 +03:30
nistootsin
5ab137fc6b Merge branch 'bot/add-client' 2025-03-17 14:21:35 +03:30
nistootsin
176f907043 - remove add_client_as_enable button in bot 2025-03-17 13:32:39 +03:30
nistootsin
f77fb6f2e2 - handle password input rather than id for shadow and trojan protocols 2025-03-17 13:14:59 +03:30
nistootsin
03dcb184a8 - customize the add client message and json for each protocol 2025-03-17 04:00:47 +03:30
nistootsin
27fca74d10 - Add client variables: client_method, client_sh_password, client_tr_password
- Exclude specific inbound protocols (HTTP, WireGuard, Socks, DOKODEMO) from addclient inline button
2025-03-17 02:59:54 +03:30
nistootsin
698c1be3bb feat: complete submission process for adding a client to inbounds 2025-03-16 03:49:23 +03:30
nistootsin
ae0b98c87e update the go.mod 2025-03-15 03:00:18 +03:30
nistootsin
7eb62855ac Add feature to add clients to inbound:
- Implement buttons for adding new clients
- Handle client addition process (submission remains to be completed)
- Support for multiple languages
2025-03-15 02:43:46 +03:30
17 changed files with 1238 additions and 425 deletions

1
go.mod
View file

@ -7,6 +7,7 @@ require (
github.com/gin-contrib/sessions v1.0.2 github.com/gin-contrib/sessions v1.0.2
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-json v0.10.5 github.com/goccy/go-json v0.10.5
github.com/google/uuid v1.6.0
github.com/mymmrac/telego v0.32.0 github.com/mymmrac/telego v0.32.0
github.com/nicksnyder/go-i18n/v2 v2.5.1 github.com/nicksnyder/go-i18n/v2 v2.5.1
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7

View file

@ -12,6 +12,18 @@
{{end}} {{end}}
{{define "component/aCustomStatistic"}} {{define "component/aCustomStatistic"}}
<style>
.dark .ant-statistic-content {
color: var(--dark-color-text-primary)
}
.dark .ant-statistic-title {
color: rgba(255, 255, 255, 0.55)
}
.ant-statistic-content {
font-size: 16px;
}
</style>
<script> <script>
Vue.component('a-custom-statistic', { Vue.component('a-custom-statistic', {
props: { props: {

View file

@ -43,6 +43,15 @@
margin:-10px 2px !important; margin:-10px 2px !important;
} }
} }
.dark .ant-switch-small:not(.ant-switch-checked) {
background-color: var(--dark-color-surface-100) !important;
}
.ant-custom-popover-title {
display: flex;
align-items: center;
gap: 10px;
margin: 5px 0;
}
.ant-col-sm-24 { .ant-col-sm-24 {
margin: 0.5rem -2rem 0.5rem 2rem; margin: 0.5rem -2rem 0.5rem 2rem;
} }
@ -137,406 +146,433 @@
</a-alert> </a-alert>
</transition> </transition>
<transition name="list" appear> <transition name="list" appear>
<a-card hoverable> <a-card size="small" style="padding: 16px;" hoverable>
<a-row> <a-row>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :sm="12" :md="6">
{{ i18n "pages.inbounds.totalDownUp" }}: <a-custom-statistic title='{{ i18n "pages.inbounds.totalDownUp" }}' :value="`${SizeFormatter.sizeFormat(total.up)} / ${SizeFormatter.sizeFormat(total.down)}`">
<a-tag color="green">[[ SizeFormatter.sizeFormat(total.up) ]] / [[ SizeFormatter.sizeFormat(total.down) ]]</a-tag> <template #prefix>
<a-icon type="swap"></a-icon>
</template>
</a-custom-statistic>
</a-col> </a-col>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :sm="12" :md="6">
{{ i18n "pages.inbounds.totalUsage" }}: <a-custom-statistic title='{{ i18n "pages.inbounds.totalUsage" }}' :value="SizeFormatter.sizeFormat(total.up + total.down)" :style="{ marginTop: isMobile ? '10px' : 0 }">
<a-tag color="green">[[ SizeFormatter.sizeFormat(total.up + total.down) ]]</a-tag> <template #prefix>
<a-icon type="pie-chart"></a-icon>
</template>
</a-custom-statistic>
</a-col> </a-col>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :sm="12" :md="6">
{{ i18n "pages.inbounds.inboundCount" }}: <a-custom-statistic title='{{ i18n "pages.inbounds.inboundCount" }}' :value="dbInbounds.length" :style="{ marginTop: isMobile ? '10px' : 0 }">
<a-tag color="green">[[ dbInbounds.length ]]</a-tag> <template #prefix>
<a-icon type="bars"></a-icon>
</template>
</a-custom-statistic>
</a-col> </a-col>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :sm="12" :md="6">
<template> <a-custom-statistic title='{{ i18n "clients" }}' value=" " :style="{ marginTop: isMobile ? '10px' : 0 }">
<div> <template #prefix>
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top> <a-space direction="horizontal">
{{ i18n "clients" }}: <a-icon type="team"></a-icon>
<a-tag color="green">[[ total.clients ]]</a-tag> <div>
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top>
<template slot="content"> <a-tag color="green">[[ total.clients ]]</a-tag>
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p> <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
</template> <template slot="content">
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag> <div v-for="clientEmail in total.deactive"><span>[[ clientEmail ]]</span></div>
</a-popover> </template>
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
<template slot="content"> </a-popover>
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p> <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
</template> <template slot="content">
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag> <div v-for="clientEmail in total.depleted"><span>[[ clientEmail ]]</span></div>
</a-popover> </template>
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
<template slot="content"> </a-popover>
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p> <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
</template> <template slot="content">
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag> <div v-for="clientEmail in total.expiring"><span>[[ clientEmail ]]</span></div>
</a-popover> </template>
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
<template slot="content"> </a-popover>
<p v-for="clientEmail in onlineClients">[[ clientEmail ]]</p> <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
</template> <template slot="content">
<a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag> <div v-for="clientEmail in onlineClients"><span>[[ clientEmail ]]</span></div>
</a-popover> </template>
</div> <a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
</template> </a-popover>
</div>
</a-space>
</template>
</a-custom-statistic>
</a-col> </a-col>
</a-row> </a-row>
</a-card> </a-card>
</transition> </transition>
<transition name="list" appear> <transition name="list" appear>
<a-card hoverable> <a-card hoverable>
<div slot="title"> <template #title>
<a-row> <a-space direction="horizontal">
<a-col :xs="12" :sm="12" :lg="12"> <a-button type="primary" icon="plus" @click="openAddInbound">
<a-button type="primary" icon="plus" @click="openAddInbound"> <template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
<template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template> </a-button>
</a-button>
<a-dropdown :trigger="['click']">
<a-button type="primary" icon="menu">
<template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
</a-button>
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
<a-menu-item key="import">
<a-icon type="import"></a-icon>
{{ i18n "pages.inbounds.importInbound" }}
</a-menu-item>
<a-menu-item key="export">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export" }}
</a-menu-item>
<a-menu-item key="subs" v-if="subSettings.enable">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
</a-menu-item>
<a-menu-item key="resetInbounds">
<a-icon type="reload"></a-icon>
{{ i18n "pages.inbounds.resetAllTraffic" }}
</a-menu-item>
<a-menu-item key="resetClients">
<a-icon type="file-done"></a-icon>
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
</a-menu-item>
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
<a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item>
</a-menu>
</a-dropdown>
</a-col>
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
<a-select v-model="refreshInterval"
style="width: 65px;"
v-if="isRefreshEnabled"
@change="changeRefreshInterval"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
</a-select>
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh"></a-switch>
</a-col>
</a-row>
</div>
<div :style="isMobile ? '' : 'display: flex; align-items: center; justify-content: flex-start;'">
<a-switch v-model="enableFilter"
:style="isMobile ? 'margin-bottom: .5rem; display: flex;' : 'margin-right: .5rem;'"
@change="toggleFilter">
<a-icon slot="checkedChildren" type="search"></a-icon>
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
</a-switch>
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px" :size="isMobile ? 'small' : ''"></a-input>
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
<a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
</a-radio-group>
</div>
<a-back-top></a-back-top>
<a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
:data-source="searchedInbounds"
:scroll="isMobile ? {} : { x: 1000 }"
:pagination=pagination(searchedInbounds)
:expand-icon-as-cell="false"
:expand-row-by-click="false"
:expand-icon-column-index="0"
:indent-size="0"
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
style="margin-top: 10px">
<template slot="action" slot-scope="text, dbInbound">
<a-dropdown :trigger="['click']"> <a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 20px; text-decoration: solid;"></a-icon> <a-button type="primary" icon="menu">
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme"> <template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
<a-menu-item key="edit"> </a-button>
<a-icon type="edit"></a-icon> <a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
{{ i18n "edit" }} <a-menu-item key="import">
<a-icon type="import"></a-icon>
{{ i18n "pages.inbounds.importInbound" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard"> <a-menu-item key="export">
<a-icon type="qrcode"></a-icon> <a-icon type="export"></a-icon>
{{ i18n "qrCode" }} {{ i18n "pages.inbounds.export" }}
</a-menu-item> </a-menu-item>
<template v-if="dbInbound.isMultiUser()"> <a-menu-item key="subs" v-if="subSettings.enable">
<a-menu-item key="addClient"> <a-icon type="export"></a-icon>
<a-icon type="user-add"></a-icon> {{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
{{ i18n "pages.client.add"}}
</a-menu-item>
<a-menu-item key="addBulkClient">
<a-icon type="usergroup-add"></a-icon>
{{ i18n "pages.client.bulk"}}
</a-menu-item>
<a-menu-item key="resetClients">
<a-icon type="file-done"></a-icon>
{{ i18n "pages.inbounds.resetInboundClientTraffics"}}
</a-menu-item>
<a-menu-item key="export">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}}
</a-menu-item>
<a-menu-item key="subs" v-if="subSettings.enable">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
</a-menu-item>
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
<a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item>
</template>
<template v-else>
<a-menu-item key="showInfo">
<a-icon type="info-circle"></a-icon>
{{ i18n "info"}}
</a-menu-item>
</template>
<a-menu-item key="clipboard">
<a-icon type="copy"></a-icon>
{{ i18n "pages.inbounds.exportInbound" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="resetTraffic"> <a-menu-item key="resetInbounds">
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }} <a-icon type="reload"></a-icon>
{{ i18n "pages.inbounds.resetAllTraffic" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="clone"> <a-menu-item key="resetClients">
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}} <a-icon type="file-done"></a-icon>
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="delete"> <a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
<span style="color: #FF4D4F"> <a-icon type="rest"></a-icon>
<a-icon type="delete"></a-icon> {{ i18n "delete"}} {{ i18n "pages.inbounds.delDepletedClients" }}
</span>
</a-menu-item>
<a-menu-item v-if="isMobile">
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
{{ i18n "pages.inbounds.enable" }}
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
</a-dropdown> </a-dropdown>
</template> </a-space>
<template slot="protocol" slot-scope="text, dbInbound"> </template>
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag> <template #extra>
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS"> <a-button-group>
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag> <a-button icon="sync" @click="manualRefresh" :loading="refreshing"></a-button>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag> <a-popover placement="bottomRight" trigger="click" :overlay-class-name="themeSwitcher.currentTheme">
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag> <template #title>
<div class="ant-custom-popover-title">
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh" size="small"></a-switch>
<span>{{ i18n "pages.inbounds.autoRefresh" }}</span>
</div>
</template>
<template #content>
<a-space direction="vertical">
<span>{{ i18n "pages.inbounds.autoRefreshInterval" }}</span>
<a-select v-model="refreshInterval"
:disabled="!isRefreshEnabled"
style="width: 100%;"
@change="changeRefreshInterval"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
</a-select>
</a-space>
</template>
<a-button icon="down"></a-button>
</a-popover>
</a-button-group>
</template>
<a-space direction="vertical">
<div :style="isMobile ? '' : 'display: flex; align-items: center; justify-content: flex-start;'">
<a-switch v-model="enableFilter"
:style="isMobile ? 'margin-bottom: .5rem; display: flex;' : 'margin-right: .5rem;'"
@change="toggleFilter">
<a-icon slot="checkedChildren" type="search"></a-icon>
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
</a-switch>
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px" :size="isMobile ? 'small' : ''"></a-input>
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
<a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
</a-radio-group>
</div>
<a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
:data-source="searchedInbounds"
:scroll="isMobile ? {} : { x: 1000 }"
:pagination=pagination(searchedInbounds)
:expand-icon-as-cell="false"
:expand-row-by-click="false"
:expand-icon-column-index="0"
:indent-size="0"
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
style="margin-top: 10px">
<template slot="action" slot-scope="text, dbInbound">
<a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 20px; text-decoration: solid;"></a-icon>
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
<a-menu-item key="edit">
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
<a-icon type="qrcode"></a-icon>
{{ i18n "qrCode" }}
</a-menu-item>
<template v-if="dbInbound.isMultiUser()">
<a-menu-item key="addClient">
<a-icon type="user-add"></a-icon>
{{ i18n "pages.client.add"}}
</a-menu-item>
<a-menu-item key="addBulkClient">
<a-icon type="usergroup-add"></a-icon>
{{ i18n "pages.client.bulk"}}
</a-menu-item>
<a-menu-item key="resetClients">
<a-icon type="file-done"></a-icon>
{{ i18n "pages.inbounds.resetInboundClientTraffics"}}
</a-menu-item>
<a-menu-item key="export">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}}
</a-menu-item>
<a-menu-item key="subs" v-if="subSettings.enable">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
</a-menu-item>
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
<a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item>
</template>
<template v-else>
<a-menu-item key="showInfo">
<a-icon type="info-circle"></a-icon>
{{ i18n "info"}}
</a-menu-item>
</template>
<a-menu-item key="clipboard">
<a-icon type="copy"></a-icon>
{{ i18n "pages.inbounds.exportInbound" }}
</a-menu-item>
<a-menu-item key="resetTraffic">
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
</a-menu-item>
<a-menu-item key="clone">
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
</a-menu-item>
<a-menu-item key="delete">
<span style="color: #FF4D4F">
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
</span>
</a-menu-item>
<a-menu-item v-if="isMobile">
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
{{ i18n "pages.inbounds.enable" }}
</a-menu-item>
</a-menu>
</a-dropdown>
</template> </template>
</template> <template slot="protocol" slot-scope="text, dbInbound">
<template slot="clients" slot-scope="text, dbInbound"> <a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
<template v-if="clientCount[dbInbound.id]"> <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag> <a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
</template>
</template>
<template slot="clients" slot-scope="text, dbInbound">
<template v-if="clientCount[dbInbound.id]">
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].deactive"><span>[[ clientEmail ]]</span></div>
</template>
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
</a-popover>
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].depleted"><span>[[ clientEmail ]]</span></div>
</template>
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
</a-popover>
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].expiring"><span>[[ clientEmail ]]</span></div>
</template>
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
</a-popover>
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].online"><span>[[ clientEmail ]]</span></div>
</template>
<a-tag style="margin:0; padding: 0 2px;" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
</a-popover>
</template>
</template>
<template slot="traffic" slot-scope="text, dbInbound">
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p> <table cellpadding="2" width="100%">
<tr>
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
</tr>
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
<td>{{ i18n "remained" }}</td>
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
</tr>
</table>
</template> </template>
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag> <a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
</a-popover> [[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme"> <template v-if="dbInbound.total > 0">
<template slot="content"> [[ SizeFormatter.sizeFormat(dbInbound.total) ]]
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p> </template>
</template> <template v-else>
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
</a-popover> <path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme"> </svg>
<template slot="content"> </template>
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p> </a-tag>
</template>
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
</a-popover>
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
</template>
<a-tag style="margin:0; padding: 0 2px;" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
</a-popover> </a-popover>
</template> </template>
</template> <template slot="enable" slot-scope="text, dbInbound">
<template slot="traffic" slot-scope="text, dbInbound"> <a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
<a-popover :overlay-class-name="themeSwitcher.currentTheme"> </template>
<template slot="content"> <template slot="expiryTime" slot-scope="text, dbInbound">
<table cellpadding="2" width="100%"> <a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
<tr> <template slot="content" v-if="app.datepicker === 'gregorian'">
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td> [[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
</tr>
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
<td>{{ i18n "remained" }}</td>
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
</tr>
</table>
</template>
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
<template v-if="dbInbound.total > 0">
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else> <template v-else slot="content">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> [[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</template> </template>
<a-tag style="min-width: 50px;" :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
[[ remainedDays(dbInbound._expiryTime) ]]
</a-tag>
</a-popover>
<a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</a-tag> </a-tag>
</a-popover> </template>
</template> <template slot="info" slot-scope="text, dbInbound">
<template slot="enable" slot-scope="text, dbInbound"> <a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch> <template slot="content">
</template> <table cellpadding="2">
<template slot="expiryTime" slot-scope="text, dbInbound"> <tr>
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme"> <td>{{ i18n "pages.inbounds.protocol" }}</td>
<template slot="content" v-if="app.datepicker === 'gregorian'"> <td>
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]] <a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
</template> <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<template v-else slot="content"> <a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]] <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
</template> <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
<a-tag style="min-width: 50px;" :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
[[ remainedDays(dbInbound._expiryTime) ]]
</a-tag>
</a-popover>
<a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</a-tag>
</template>
<template slot="info" slot-scope="text, dbInbound">
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
<template slot="content">
<table cellpadding="2">
<tr>
<td>{{ i18n "pages.inbounds.protocol" }}</td>
<td>
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
</template>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.port" }}</td>
<td><a-tag>[[ dbInbound.port ]]</a-tag></td>
</tr>
<tr v-if="clientCount[dbInbound.id]">
<td>{{ i18n "clients" }}</td>
<td>
<a-tag style="margin:0;" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag>
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
</template> </template>
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag> </td>
</a-popover> </tr>
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme"> <tr>
<template slot="content"> <td>{{ i18n "pages.inbounds.port" }}</td>
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p> <td><a-tag>[[ dbInbound.port ]]</a-tag></td>
</template> </tr>
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag> <tr v-if="clientCount[dbInbound.id]">
</a-popover> <td>{{ i18n "clients" }}</td>
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme"> <td>
<template slot="content"> <a-tag style="margin:0;" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag>
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p> <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
</template> <template slot="content">
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag> <p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
</a-popover> </template>
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
<template slot="content"> </a-popover>
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p> <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
</template> <template slot="content">
<a-tag style="margin:0; padding: 0 2px;" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag> <p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
</a-popover> </template>
</td> <a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
</tr> </a-popover>
<tr> <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
<td>{{ i18n "pages.inbounds.traffic" }}</td> <template slot="content">
<td> <p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
<a-popover :overlay-class-name="themeSwitcher.currentTheme"> </template>
<template slot="content"> <a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
<table cellpadding="2" width="100%"> </a-popover>
<tr> <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td> <template slot="content">
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td> <p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
</tr> </template>
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total"> <a-tag style="margin:0; padding: 0 2px;" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
<td>{{ i18n "remained" }}</td> </a-popover>
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td> </td>
</tr> </tr>
</table> <tr>
</template> <td>{{ i18n "pages.inbounds.traffic" }}</td>
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)"> <td>
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] / <a-popover :overlay-class-name="themeSwitcher.currentTheme">
<template v-if="dbInbound.total > 0"> <template slot="content">
[[ SizeFormatter.sizeFormat(dbInbound.total) ]] <table cellpadding="2" width="100%">
<tr>
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
</tr>
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
<td>{{ i18n "remained" }}</td>
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
</tr>
</table>
</template>
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
<template v-if="dbInbound.total > 0">
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template>
<template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</template> </template>
</a-tag>
</a-popover>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.expireDate" }}</td>
<td>
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0"
:color="dbInbound.isExpiry? 'red': 'blue'">
<template v-if="app.datepicker === 'gregorian'">
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
</template>
<template v-else> <template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> [[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</template> </template>
</a-tag> </a-tag>
</a-popover> <a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">
</td> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
</tr> <path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
<tr> </svg>
<td>{{ i18n "pages.inbounds.expireDate" }}</td> </a-tag>
<td> </td>
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" </tr>
:color="dbInbound.isExpiry? 'red': 'blue'"> </table>
<template v-if="app.datepicker === 'gregorian'"> </template>
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]] <a-badge>
</template> <a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
<template v-else> <a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]] <a-icon type="info"></a-icon>
</template> </a-button>
</a-tag> </a-badge>
<a-tag v-else style="text-align: center;" color="purple" class="infinite-tag"> </a-popover>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> </template>
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path> <template slot="expandedRowRender" slot-scope="record">
</svg> <a-table
</a-tag> :row-key="client => client.id"
</td> :columns="isMobile ? innerMobileColumns : innerColumns"
</tr> :data-source="getInboundClients(record)"
</table> :pagination=pagination(getInboundClients(record))
</template> :style="isMobile ? 'margin: -10px 2px -11px;' : 'margin: -10px 22px -11px;'">
<a-badge> {{template "client_table"}}
<a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon> </a-table>
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;"> </template>
<a-icon type="info"></a-icon> </a-table>
</a-button> </a-space>
</a-badge>
</a-popover>
</template>
<template slot="expandedRowRender" slot-scope="record">
<a-table
:row-key="client => client.id"
:columns="isMobile ? innerMobileColumns : innerColumns"
:data-source="getInboundClients(record)"
:pagination=pagination(getInboundClients(record))
:style="isMobile ? 'margin: -10px 2px -11px;' : 'margin: -10px 22px -11px;'">
{{template "client_table"}}
</a-table>
</template>
</a-table>
</a-card> </a-card>
</transition> </transition>
</a-spin> </a-spin>
@ -549,6 +585,7 @@
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
{{template "component/aThemeSwitch" .}} {{template "component/aThemeSwitch" .}}
{{template "component/aCustomStatistic" .}}
{{template "component/aPersianDatepicker" .}} {{template "component/aPersianDatepicker" .}}
<script> <script>
const columns = [{ const columns = [{

View file

@ -24,11 +24,9 @@
} }
.dark .ant-backup-list-item svg, .dark .ant-backup-list-item svg,
.dark .ant-badge-status-text, .dark .ant-badge-status-text,
.dark .ant-statistic-content,
.dark .ant-card-extra { .dark .ant-card-extra {
color: var(--dark-color-text-primary); color: var(--dark-color-text-primary);
} }
.dark .ant-statistic-title,
.dark .ant-card-actions>li { .dark .ant-card-actions>li {
color: rgba(255, 255, 255, 0.55); color: rgba(255, 255, 255, 0.55);
} }
@ -48,9 +46,6 @@
.ant-card-actions { .ant-card-actions {
background: transparent; background: transparent;
} }
.ant-statistic-content {
font-size: 16px;
}
.ip-hidden { .ip-hidden {
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
@ -299,16 +294,16 @@
</template> </template>
<a-row :class="showIp ? 'ip-visible' : 'ip-hidden'"> <a-row :class="showIp ? 'ip-visible' : 'ip-hidden'">
<a-col :xs="24" :xxl="12" :style="{ marginTop: isMobile ? '10px' : 0 }"> <a-col :xs="24" :xxl="12" :style="{ marginTop: isMobile ? '10px' : 0 }">
<a-custom-statistic :value="status.publicIP.ipv4"> <a-custom-statistic title="IPv4" :value="status.publicIP.ipv4">
<template #prefix> <template #prefix>
IPv4: <a-icon type="global" />
</template> </template>
</a-custom-statistic> </a-custom-statistic>
</a-col> </a-col>
<a-col :xs="24" :xxl="12" :style="{ marginTop: isMobile ? '10px' : 0 }"> <a-col :xs="24" :xxl="12" :style="{ marginTop: isMobile ? '10px' : 0 }">
<a-custom-statistic :value="status.publicIP.ipv6"> <a-custom-statistic title="IPv6" :value="status.publicIP.ipv6">
<template #prefix> <template #prefix>
IPv6: <a-icon type="global" />
</template> </template>
</a-custom-statistic> </a-custom-statistic>
</a-col> </a-col>

View file

@ -1,9 +1,12 @@
package service package service
import ( import (
"crypto/rand"
"embed" "embed"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"math/big"
"net" "net"
"net/url" "net/url"
"os" "os"
@ -20,8 +23,7 @@ import (
"x-ui/web/locale" "x-ui/web/locale"
"x-ui/xray" "x-ui/xray"
"slices" "github.com/google/uuid"
"github.com/mymmrac/telego" "github.com/mymmrac/telego"
th "github.com/mymmrac/telego/telegohandler" th "github.com/mymmrac/telego/telegohandler"
tu "github.com/mymmrac/telego/telegoutil" tu "github.com/mymmrac/telego/telegoutil"
@ -30,14 +32,38 @@ import (
) )
var ( var (
bot *telego.Bot bot *telego.Bot
botHandler *th.BotHandler botHandler *th.BotHandler
adminIds []int64 adminIds []int64
isRunning bool isRunning bool
hostname string hostname string
hashStorage *global.HashStorage hashStorage *global.HashStorage
handler *th.Handler
// clients data to adding new client
receiver_inbound_ID int
client_Id string
client_Flow string
client_Email string
client_LimitIP int
client_TotalGB int64
client_ExpiryTime int64
client_Enable bool
client_TgID string
client_SubID string
client_Comment string
client_Reset int
client_Security string
client_ShPassword string
client_TrPassword string
client_Method string
) )
var userStates = make(map[int64]string)
type LoginStatus byte type LoginStatus byte
const ( const (
@ -46,6 +72,8 @@ const (
EmptyTelegramUserID = int64(0) EmptyTelegramUserID = int64(0)
) )
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
type Tgbot struct { type Tgbot struct {
inboundService InboundService inboundService InboundService
settingService SettingService settingService SettingService
@ -54,6 +82,7 @@ type Tgbot struct {
lastStatus *Status lastStatus *Status
} }
func (t *Tgbot) NewTgbot() *Tgbot { func (t *Tgbot) NewTgbot() *Tgbot {
return new(Tgbot) return new(Tgbot)
} }
@ -223,36 +252,102 @@ func (t *Tgbot) OnReceive() {
botHandler, _ = th.NewBotHandler(bot, updates) botHandler, _ = th.NewBotHandler(bot, updates)
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
delete(userStates, message.Chat.ID)
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove()) t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove())
}, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard"))) }, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard")))
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
delete(userStates, message.Chat.ID)
t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID)) t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID))
}, th.AnyCommand()) }, th.AnyCommand())
botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) { botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) {
delete(userStates,query.Message.GetChat().ID)
t.answerCallback(&query, checkAdmin(query.From.ID)) t.answerCallback(&query, checkAdmin(query.From.ID))
}, th.AnyCallbackQueryWithMessage()) }, th.AnyCallbackQueryWithMessage())
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
if message.UsersShared != nil { if userState, exists := userStates[message.Chat.ID]; exists {
if checkAdmin(message.From.ID) { switch userState {
for _, sharedUser := range message.UsersShared.Users { case "awaiting_id":
userID := sharedUser.UserID client_Id = message.Text
needRestart, err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userID) userStates[message.Chat.ID] = "awaiting_email"
if needRestart { t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.received_id", "ClientId=="+client_Id), tu.ReplyKeyboardRemove())
t.xrayService.SetToNeedRestart() cancel_btn_markup := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("default_client_email"),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
),
)
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.email_prompt", "ClientEmail=="+client_Email), cancel_btn_markup)
case "awaiting_password_tr":
client_TrPassword = message.Text
userStates[message.Chat.ID] = "awaiting_email"
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.received_password", "ClientPass=="+client_TrPassword), tu.ReplyKeyboardRemove())
cancel_btn_markup := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("default_client_email"),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
),
)
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.email_prompt", "ClientEmail=="+client_Email), cancel_btn_markup)
case "awaiting_password_sh":
client_ShPassword = message.Text
userStates[message.Chat.ID] = "awaiting_email"
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.received_password", "ClientPass=="+client_ShPassword), tu.ReplyKeyboardRemove())
cancel_btn_markup := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("default_client_email"),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
),
)
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.email_prompt", "ClientEmail=="+client_Email), cancel_btn_markup)
case "awaiting_email":
client_Email = message.Text
userStates[message.Chat.ID] = "awaiting_comment"
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.received_email", "ClientEmail=="+client_Email), tu.ReplyKeyboardRemove())
cancel_btn_markup := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("default_client_comment"),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
),
)
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.comment_prompt", "ClientComment=="+client_Comment), cancel_btn_markup)
case "awaiting_comment":
client_Comment = message.Text
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.messages.received_comment", "ClientComment=="+client_Comment), tu.ReplyKeyboardRemove())
message_text, _ := t.BuildClientDataMessage()
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),
),
)
t.SendMsgToTgbot(message.Chat.ID, message_text, inlineKeyboard)
delete(userStates, message.Chat.ID)
}
} else {
if message.UsersShared != nil {
if checkAdmin(message.From.ID) {
for _, sharedUser := range message.UsersShared.Users {
userID := sharedUser.UserID
needRestart, err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userID)
if needRestart {
t.xrayService.SetToNeedRestart()
}
output := ""
if err != nil {
output += t.I18nBot("tgbot.messages.selectUserFailed")
} else {
output += t.I18nBot("tgbot.messages.userSaved")
}
t.SendMsgToTgbot(message.Chat.ID, output, tu.ReplyKeyboardRemove())
} }
output := "" } else {
if err != nil { t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.noResult"), tu.ReplyKeyboardRemove())
output += t.I18nBot("tgbot.messages.selectUserFailed")
} else {
output += t.I18nBot("tgbot.messages.userSaved")
}
t.SendMsgToTgbot(message.Chat.ID, output, tu.ReplyKeyboardRemove())
} }
} else {
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.noResult"), tu.ReplyKeyboardRemove())
} }
} }
}, th.AnyMessage()) }, th.AnyMessage())
@ -344,6 +439,27 @@ func (t *Tgbot) sendResponse(chatId int64, msg string, onlyMessage, isAdmin bool
} }
} }
func (t *Tgbot) randomLowerAndNum(length int) string {
bytes := make([]byte, length)
for i := range bytes {
randomIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
bytes[i] = charset[randomIndex.Int64()]
}
return string(bytes)
}
func (t *Tgbot) randomShadowSocksPassword() string {
array := make([]byte, 32)
_, err := rand.Read(array)
if err != nil {
return t.randomLowerAndNum(32)
}
return base64.StdEncoding.EncodeToString(array)
}
func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) { func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
chatId := callbackQuery.Message.GetChat().ID chatId := callbackQuery.Message.GetChat().ID
@ -838,7 +954,40 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseClient", "Inbound=="+inbound.Remark), clients) t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseClient", "Inbound=="+inbound.Remark), clients)
case "add_client_to":
// assign default values to clients variables
client_Id = uuid.New().String()
client_Flow = ""
client_Email = t.randomLowerAndNum(8)
client_LimitIP = 0
client_TotalGB = 0
client_ExpiryTime = 0
client_Enable = true
client_TgID = ""
client_SubID = t.randomLowerAndNum(16)
client_Comment = ""
client_Reset = 0
client_Security="auto"
client_ShPassword=t.randomShadowSocksPassword()
client_TrPassword=t.randomLowerAndNum(10)
client_Method=""
inboundId := dataArray[1]
inboundIdInt, err := strconv.Atoi(inboundId)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
receiver_inbound_ID = inboundIdInt
inbound, err := t.inboundService.GetInbound(inboundIdInt)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
t.addClient(chatId, message_text)
} }
return return
} else { } else {
@ -892,11 +1041,275 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
case "commands": case "commands":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands")) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands")) t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands"))
case "add_client":
// assign default values to clients variables
client_Id = uuid.New().String()
client_Flow = ""
client_Email = t.randomLowerAndNum(8)
client_LimitIP = 0
client_TotalGB = 0
client_ExpiryTime = 0
client_Enable = true
client_TgID = ""
client_SubID = t.randomLowerAndNum(16)
client_Comment = ""
client_Reset = 0
client_Security="auto"
client_ShPassword=t.randomShadowSocksPassword()
client_TrPassword=t.randomLowerAndNum(10)
client_Method=""
inbounds, err := t.getInboundsAddClient()
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.addClient"))
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), inbounds)
case "add_client_ch_default":
var prompt_state string
var prompt_message string
prompt_state ,prompt_message, _ = t.BuildClientChDefaultResponse()
cancel_btn_markup := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("default_client_id_pass"),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
),
)
t.SendMsgToTgbot(chatId, prompt_message,cancel_btn_markup)
userStates[chatId] = prompt_state
case "default_client_id_pass":
cancel_btn_markup := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("default_client_email"),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
),
)
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.email_prompt", "ClientEmail=="+client_Email),cancel_btn_markup)
userStates[chatId] = "awaiting_email"
case "default_client_email":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.use_default")).WithCallbackData("default_client_comment"),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
),
)
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.comment_prompt", "ClientComment=="+client_Comment),inlineKeyboard)
userStates[chatId] = "awaiting_comment"
case "default_client_comment":
message_text, _ := t.BuildClientDataMessage()
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
),
)
t.SendMsgToTgbot(chatId, message_text, inlineKeyboard)
delete(userStates, chatId)
case "add_client_cancel":
delete(userStates, chatId)
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.cancel"), tu.ReplyKeyboardRemove())
case "add_client_submit_disable":
client_Enable = false
_, err := t.SubmitAddClient()
if err != nil {
errorMessage := fmt.Sprintf("%v", err)
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.error_add_client", "error=="+errorMessage), tu.ReplyKeyboardRemove())
} else {
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.success_add_client"), tu.ReplyKeyboardRemove())
}
} }
} }
func (t *Tgbot) BuildClientChDefaultResponse() (string,string,error) {
inbound, err := t.inboundService.GetInbound(receiver_inbound_ID)
if err != nil {
logger.Warning("getIboundClients run failed:", err)
return "", "",errors.New(t.I18nBot("tgbot.answers.getInboundsFailed"))
}
protocol := inbound.Protocol
switch protocol {
case model.VMESS, model.VLESS:
prompt := t.I18nBot("tgbot.messages.id_prompt", "ClientId=="+client_Id)
return "awaiting_id", prompt,errors.New("unknown protocol")
case model.Trojan:
prompt := t.I18nBot("tgbot.messages.pass_prompt", "ClientPassword=="+client_TrPassword)
return "awaiting_password_tr", prompt,errors.New("unknown protocol")
case model.Shadowsocks:
prompt := t.I18nBot("tgbot.messages.pass_prompt", "ClientPassword=="+client_ShPassword)
return "awaiting_password_sh", prompt,errors.New("unknown protocol")
default:
return "","", errors.New("unknown protocol")
}
}
func (t *Tgbot) BuildInboundClientDataMessage(inbound_remark string ,protocol model.Protocol) (string, error) {
var message string
switch protocol {
case model.VMESS, model.VLESS:
message = t.I18nBot("tgbot.messages.inbound_client_data_id", "InboundRemark=="+inbound_remark,"ClientId=="+client_Id,"ClientEmail=="+client_Email,"ClientComment=="+client_Comment)
case model.Trojan:
message = t.I18nBot("tgbot.messages.inbound_client_data_pass", "InboundRemark=="+inbound_remark,"ClientPass=="+client_TrPassword,"ClientEmail=="+client_Email,"ClientComment=="+client_Comment)
case model.Shadowsocks:
message = t.I18nBot("tgbot.messages.inbound_client_data_pass", "InboundRemark=="+inbound_remark,"ClientPass=="+client_ShPassword,"ClientEmail=="+client_Email,"ClientComment=="+client_Comment)
default:
return "", errors.New("unknown protocol")
}
return message, nil
}
func (t *Tgbot) BuildClientDataMessage() (string, error) {
var message string
inbound, err := t.inboundService.GetInbound(receiver_inbound_ID)
if err != nil {
logger.Warning("getIboundClients run failed:", err)
return "", errors.New(t.I18nBot("tgbot.answers.getInboundsFailed"))
}
protocol := inbound.Protocol
switch protocol {
case model.VMESS, model.VLESS:
message = t.I18nBot("tgbot.messages.client_data_id", "ClientId=="+client_Id,"ClientEmail=="+client_Email,"ClientComment=="+client_Comment)
case model.Trojan:
message = t.I18nBot("tgbot.messages.client_data_pass", "ClientPass=="+client_TrPassword,"ClientEmail=="+client_Email,"ClientComment=="+client_Comment)
case model.Shadowsocks:
message = t.I18nBot("tgbot.messages.client_data_pass", "ClientPass=="+client_ShPassword,"ClientEmail=="+client_Email,"ClientComment=="+client_Comment)
default:
return "", errors.New("unknown protocol")
}
return message, nil
}
func (t *Tgbot) BuildJSONForProtocol(protocol model.Protocol) (string, error) {
var jsonString string
switch protocol {
case model.VMESS:
jsonString = fmt.Sprintf(`{
"clients": [{
"id": "%s",
"security": "%s",
"email": "%s",
"limitIp": %d,
"totalGB": %d,
"expiryTime": %d,
"enable": %t,
"tgId": "%s",
"subId": "%s",
"comment": "%s",
"reset": %d
}]
}`, client_Id, client_Security, client_Email, client_LimitIP, client_TotalGB, client_ExpiryTime, client_Enable, client_TgID, client_SubID, client_Comment, client_Reset)
case model.VLESS:
jsonString = fmt.Sprintf(`{
"clients": [{
"id": "%s",
"flow": "%s",
"email": "%s",
"limitIp": %d,
"totalGB": %d,
"expiryTime": %d,
"enable": %t,
"tgId": "%s",
"subId": "%s",
"comment": "%s",
"reset": %d
}]
}`, client_Id, client_Flow, client_Email, client_LimitIP, client_TotalGB, client_ExpiryTime, client_Enable, client_TgID, client_SubID, client_Comment, client_Reset)
case model.Trojan:
jsonString = fmt.Sprintf(`{
"clients": [{
"password": "%s",
"email": "%s",
"limitIp": %d,
"totalGB": %d,
"expiryTime": %d,
"enable": %t,
"tgId": "%s",
"subId": "%s",
"comment": "%s",
"reset": %d
}]
}`, client_TrPassword, client_Email, client_LimitIP, client_TotalGB, client_ExpiryTime, client_Enable, client_TgID, client_SubID, client_Comment, client_Reset)
case model.Shadowsocks:
jsonString = fmt.Sprintf(`{
"clients": [{
"method": "%s",
"password": "%s",
"email": "%s",
"limitIp": %d,
"totalGB": %d,
"expiryTime": %d,
"enable": %t,
"tgId": "%s",
"subId": "%s",
"comment": "%s",
"reset": %d
}]
}`, client_Method, client_ShPassword, client_Email, client_LimitIP, client_TotalGB, client_ExpiryTime, client_Enable, client_TgID, client_SubID, client_Comment, client_Reset)
default:
return "", errors.New("unknown protocol")
}
return jsonString, nil
}
func (t *Tgbot) SubmitAddClient() (bool, error) {
inbound, err := t.inboundService.GetInbound(receiver_inbound_ID)
if err != nil {
logger.Warning("getIboundClients run failed:", err)
return false, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed"))
}
jsonString, err := t.BuildJSONForProtocol(inbound.Protocol)
newInbound := &model.Inbound{
Id: receiver_inbound_ID,
Settings: jsonString,
}
return t.inboundService.AddInboundClient(newInbound)
}
func checkAdmin(tgId int64) bool { func checkAdmin(tgId int64) bool {
return slices.Contains(adminIds, tgId) for _, adminId := range adminIds {
if adminId == tgId {
return true
}
}
return false
} }
func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) { func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
@ -915,7 +1328,10 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
tu.InlineKeyboardRow( tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("commands")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("commands")),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.onlines")).WithCallbackData(t.encodeQuery("onlines")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.onlines")).WithCallbackData(t.encodeQuery("onlines")),
),
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.allClients")).WithCallbackData(t.encodeQuery("get_inbounds")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.allClients")).WithCallbackData(t.encodeQuery("get_inbounds")),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.addClient")).WithCallbackData(t.encodeQuery("add_client")),
), ),
// TODOOOOOOOOOOOOOO: Add restart button here. // TODOOOOOOOOOOOOOO: Add restart button here.
) )
@ -1161,35 +1577,75 @@ func (t *Tgbot) getInboundUsages() string {
} }
return info return info
} }
func (t *Tgbot) getInbounds() (*telego.InlineKeyboardMarkup, error) { func (t *Tgbot) getInbounds() (*telego.InlineKeyboardMarkup, error) {
inbounds, err := t.inboundService.GetAllInbounds() inbounds, err := t.inboundService.GetAllInbounds()
var buttons []telego.InlineKeyboardButton
if err != nil { if err != nil {
logger.Warning("GetAllInbounds run failed:", err) logger.Warning("GetAllInbounds run failed:", err)
return nil, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed")) return nil, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed"))
} else {
if len(inbounds) > 0 {
for _, inbound := range inbounds {
status := "❌"
if inbound.Enable {
status = "✅"
}
buttons = append(buttons, tu.InlineKeyboardButton(fmt.Sprintf("%v - %v", inbound.Remark, status)).WithCallbackData(t.encodeQuery("get_clients "+strconv.Itoa(inbound.Id))))
}
} else {
logger.Warning("GetAllInbounds run failed:", err)
return nil, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed"))
}
} }
cols := 0
if len(buttons) < 6 { if len(inbounds) == 0 {
cols = 3 logger.Warning("No inbounds found")
} else { return nil, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed"))
}
var buttons []telego.InlineKeyboardButton
for _, inbound := range inbounds {
status := "❌"
if inbound.Enable {
status = "✅"
}
callbackData := t.encodeQuery(fmt.Sprintf("%s %d","get_clients", inbound.Id))
buttons = append(buttons, tu.InlineKeyboardButton(fmt.Sprintf("%v - %v", inbound.Remark, status)).WithCallbackData(callbackData))
}
cols := 1
if len(buttons) >= 6 {
cols = 2 cols = 2
} }
keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...))
return keyboard, nil
}
func (t *Tgbot) getInboundsAddClient() (*telego.InlineKeyboardMarkup, error) {
inbounds, err := t.inboundService.GetAllInbounds()
if err != nil {
logger.Warning("GetAllInbounds run failed:", err)
return nil, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed"))
}
if len(inbounds) == 0 {
logger.Warning("No inbounds found")
return nil, errors.New(t.I18nBot("tgbot.answers.getInboundsFailed"))
}
excludedProtocols := map[model.Protocol]bool{
model.DOKODEMO: true,
model.Socks: true,
model.WireGuard: true,
model.HTTP: true,
}
var buttons []telego.InlineKeyboardButton
for _, inbound := range inbounds {
if excludedProtocols[inbound.Protocol] {
continue
}
status := "❌"
if inbound.Enable {
status = "✅"
}
callbackData := t.encodeQuery(fmt.Sprintf("%s %d","add_client_to", inbound.Id))
buttons = append(buttons, tu.InlineKeyboardButton(fmt.Sprintf("%v - %v", inbound.Remark, status)).WithCallbackData(callbackData))
}
cols := 1
if len(buttons) >= 6 {
cols = 2
}
keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...)) keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...))
return keyboard, nil return keyboard, nil
} }
@ -1484,6 +1940,25 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
} }
} }
func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_default")).WithCallbackData("add_client_ch_default"),
),
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData("add_client_cancel"),
),
)
if len(messageID) > 0 {
t.editMessageTgBot(chatId, messageID[0], msg, inlineKeyboard)
} else {
t.SendMsgToTgbot(chatId, msg, inlineKeyboard)
}
}
func (t *Tgbot) searchInbound(chatId int64, remark string) { func (t *Tgbot) searchInbound(chatId int64, remark string) {
inbounds, err := t.inboundService.SearchInbounds(remark) inbounds, err := t.inboundService.SearchInbounds(remark)
if err != nil { if err != nil {
@ -1689,7 +2164,12 @@ func (t *Tgbot) notifyExhausted() {
} }
func int64Contains(slice []int64, item int64) bool { func int64Contains(slice []int64, item int64) bool {
return slices.Contains(slice, item) for _, s := range slice {
if s == item {
return true
}
}
return false
} }
func (t *Tgbot) onlineClients(chatId int64, messageID ...int) { func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
@ -1848,4 +2328,4 @@ func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlin
if _, err := bot.EditMessageText(&params); err != nil { if _, err := bot.EditMessageText(&params); err != nil {
logger.Warning(err) logger.Warning(err)
} }
} }

View file

@ -140,6 +140,8 @@
"resetTraffic" = "Reset Traffic" "resetTraffic" = "Reset Traffic"
"addInbound" = "Add Inbound" "addInbound" = "Add Inbound"
"generalActions" = "General Actions" "generalActions" = "General Actions"
"autoRefresh" = "Auto-refresh"
"autoRefreshInterval" = "Interval"
"create" = "Create" "create" = "Create"
"update" = "Update" "update" = "Update"
"modifyInbound" = "Modify Inbound" "modifyInbound" = "Modify Inbound"
@ -580,6 +582,22 @@
"yes" = "✅ Yes" "yes" = "✅ Yes"
"no" = "❌ No" "no" = "❌ No"
"received_id" = "🔑📥 Received ID: {{ .ClientId }}"
"received_password" = "🔑📥 Received Password: {{ .ClientPass }}"
"received_email" = "📧📥 Received Email: {{ .ClientEmail }}"
"received_comment" = "💬📥 Received Comment: {{ .ClientComment }}"
"id_prompt" = "🔑 Default ID: {{ .ClientId }}\n\nEnter your id."
"pass_prompt" = "🔑 Default Password: {{ .ClientPassword }}\n\nEnter your password."
"email_prompt" = "📧 Default Email: {{ .ClientEmail }}\n\nEnter your email."
"comment_prompt" = "💬 Default Comment: {{ .ClientComment }}\n\nEnter your Comment."
"inbound_client_data_id" = "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!"
"inbound_client_data_pass" = "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 Password: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!"
"client_data_id" = "🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!"
"client_data_pass" = "🔑 Password: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!"
"cancel" = "❌ Process Canceled! \n\nYou can /start again anytime. 🔄"
"error_add_client" = "⚠️ Error:\n\n {{ .error }}"
"success_add_client" = "🏆 Success! \nYou can now modify it using the 'All Clients' inline button."
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Close Keyboard" "closeKeyboard" = "❌ Close Keyboard"
"cancel" = "❌ Cancel" "cancel" = "❌ Cancel"
@ -614,6 +632,11 @@
"getBanLogs" = "Get Ban Logs" "getBanLogs" = "Get Ban Logs"
"allClients" = "All Clients" "allClients" = "All Clients"
"addClient" = "Add Client"
"submitDisable" = "Submit As Disable ✅"
"use_default" = "🏷️ Use default"
"change_default" = "🔄⚙️ Change Default"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Operation successful!" "successfulOperation" = "✅ Operation successful!"
"errorOperation" = "❗ Error in operation." "errorOperation" = "❗ Error in operation."

View file

@ -142,6 +142,8 @@
"resetTraffic" = "Restablecer Tráfico" "resetTraffic" = "Restablecer Tráfico"
"addInbound" = "Agregar Entrada" "addInbound" = "Agregar Entrada"
"generalActions" = "Acciones Generales" "generalActions" = "Acciones Generales"
"autoRefresh" = "Auto-actualizar"
"autoRefreshInterval" = "Intervalo"
"create" = "Crear" "create" = "Crear"
"update" = "Actualizar" "update" = "Actualizar"
"modifyInbound" = "Modificar Entrada" "modifyInbound" = "Modificar Entrada"
@ -582,6 +584,23 @@
"yes" = "✅ Sí" "yes" = "✅ Sí"
"no" = "❌ No" "no" = "❌ No"
"received_id" = "🔑📥 ID recibido: {{ .ClientId }}"
"received_password" = "🔑📥 Contraseña recibida: {{ .ClientPass }}"
"received_email" = "📧📥 Correo electrónico recibido: {{ .ClientEmail }}"
"received_comment" = "💬📥 Comentario recibido: {{ .ClientComment }}"
"id_prompt" = "🔑 ID predeterminado: {{ .ClientId }}\n\nIntroduce tu ID."
"pass_prompt" = "🔑 Contraseña predeterminada: {{ .ClientPassword }}\n\nIntroduce tu contraseña."
"email_prompt" = "📧 Correo electrónico predeterminado: {{ .ClientEmail }}\n\nIntroduce tu correo electrónico."
"comment_prompt" = "💬 Comentario predeterminado: {{ .ClientComment }}\n\nIntroduce tu comentario."
"inbound_client_data_id" = "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Correo electrónico: {{ .ClientEmail }}\n💬 Comentario: {{ .ClientComment }}\n\n¡Ahora puedes agregar al cliente a inbound!"
"inbound_client_data_pass" = "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 Contraseña: {{ .ClientPass }}\n📧 Correo electrónico: {{ .ClientEmail }}\n💬 Comentario: {{ .ClientComment }}\n\n¡Ahora puedes agregar al cliente a inbound!"
"client_data_id" = "🔑 ID: {{ .ClientId }}\n📧 Correo electrónico: {{ .ClientEmail }}\n💬 Comentario: {{ .ClientComment }}\n\n¡Ahora puedes agregar al cliente a inbound!"
"client_data_pass" = "🔑 Contraseña: {{ .ClientPass }}\n📧 Correo electrónico: {{ .ClientEmail }}\n💬 Comentario: {{ .ClientComment }}\n\n¡Ahora puedes agregar al cliente a inbound!"
"cancel" = "❌ ¡Proceso cancelado! \n\nPuedes usar /start en cualquier momento. 🔄"
"error_add_client" = "⚠️ Error:\n\n {{ .error }}"
"success_add_client" = "🏆 ¡Éxito! \nAhora puedes modificarlo usando el botón en línea 'Todos los Clientes'."
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Cerrar Teclado" "closeKeyboard" = "❌ Cerrar Teclado"
"cancel" = "❌ Cancelar" "cancel" = "❌ Cancelar"
@ -616,6 +635,11 @@
"getBanLogs" = "Registros de prohibición" "getBanLogs" = "Registros de prohibición"
"allClients" = "Todos los Clientes" "allClients" = "Todos los Clientes"
"addClient" = "Añadir Cliente"
"submitDisable" = "Enviar como Deshabilitado ✅"
"use_default" = "🏷️ Usar por defecto"
"change_default" = "🔄⚙️ Cambiar por defecto"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ ¡Exitosa!" "successfulOperation" = "✅ ¡Exitosa!"
"errorOperation" = "❗ Error en la Operación." "errorOperation" = "❗ Error en la Operación."

View file

@ -142,6 +142,8 @@
"resetTraffic" = "ریست ترافیک" "resetTraffic" = "ریست ترافیک"
"addInbound" = "افزودن ورودی" "addInbound" = "افزودن ورودی"
"generalActions" = "عملیات کلی" "generalActions" = "عملیات کلی"
"autoRefresh" = "تازه‌سازی خودکار"
"autoRefreshInterval" = "فاصله"
"create" = "افزودن" "create" = "افزودن"
"update" = "ویرایش" "update" = "ویرایش"
"modifyInbound" = "ویرایش ورودی" "modifyInbound" = "ویرایش ورودی"
@ -582,6 +584,23 @@
"yes" = "✅ بله" "yes" = "✅ بله"
"no" = "❌ خیر" "no" = "❌ خیر"
"received_id" = "🔑📥 شناسه دریافت شده: {{ .ClientId }}"
"received_password" = "🔑📥 رمز عبور دریافت شده: {{ .ClientPass }}"
"received_email" = "📧📥 ایمیل دریافت شده: {{ .ClientEmail }}"
"received_comment" = "💬📥 نظر دریافت شده: {{ .ClientComment }}"
"id_prompt" = "🔑 شناسه پیش‌فرض: {{ .ClientId }}\n\nشناسه خود را وارد کنید."
"pass_prompt" = "🔑 رمز عبور پیش‌فرض: {{ .ClientPassword }}\n\nرمز عبور خود را وارد کنید."
"email_prompt" = "📧 ایمیل پیش‌فرض: {{ .ClientEmail }}\n\nایمیل خود را وارد کنید."
"comment_prompt" = "💬 نظر پیش‌فرض: {{ .ClientComment }}\n\nنظر خود را وارد کنید."
"inbound_client_data_id" = "🔄 ورودی: {{ .InboundRemark }}\n\n🔑 شناسه: {{ .ClientId }}\n📧 ایمیل: {{ .ClientEmail }}\n💬 نظر: {{ .ClientComment }}\n\nاکنون می‌توانید مشتری را به ورودی اضافه کنید!"
"inbound_client_data_pass" = "🔄 ورودی: {{ .InboundRemark }}\n\n🔑 رمز عبور: {{ .ClientPass }}\n📧 ایمیل: {{ .ClientEmail }}\n💬 نظر: {{ .ClientComment }}\n\nاکنون می‌توانید مشتری را به ورودی اضافه کنید!"
"client_data_id" = "🔑 شناسه: {{ .ClientId }}\n📧 ایمیل: {{ .ClientEmail }}\n💬 نظر: {{ .ClientComment }}\n\nاکنون می‌توانید مشتری را به ورودی اضافه کنید!"
"client_data_pass" = "🔑 رمز عبور: {{ .ClientPass }}\n📧 ایمیل: {{ .ClientEmail }}\n💬 نظر: {{ .ClientComment }}\n\nاکنون می‌توانید مشتری را به ورودی اضافه کنید!"
"cancel" = "❌ فرایند لغو شد! \n\nشما می‌توانید هر زمان که خواستید /start را اجرا کنید. 🔄"
"error_add_client" = "⚠️ خطا:\n\n {{ .error }}"
"success_add_client" = "🏆 موفقیت! اکنون می‌توانید آن را از طریق دکمه 'همه مشتریان' ویرایش کنید."
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ بستن کیبورد" "closeKeyboard" = "❌ بستن کیبورد"
"cancel" = "❌ لغو" "cancel" = "❌ لغو"
@ -616,6 +635,11 @@
"getBanLogs" = "گزارش های بلوک را دریافت کنید" "getBanLogs" = "گزارش های بلوک را دریافت کنید"
"allClients" = "همه مشتریان" "allClients" = "همه مشتریان"
"addClient" = "افزودن مشتری"
"submitDisable" = "ارسال به عنوان غیرفعال ✅"
"use_default" = "🏷️ استفاده از پیش‌فرض"
"change_default" = "🔄⚙️ تغییر پیش‌فرض"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ انجام شد!" "successfulOperation" = "✅ انجام شد!"
"errorOperation" = "❗ خطا در عملیات." "errorOperation" = "❗ خطا در عملیات."

View file

@ -142,6 +142,8 @@
"resetTraffic" = "Reset Traffic" "resetTraffic" = "Reset Traffic"
"addInbound" = "Tambahkan Masuk" "addInbound" = "Tambahkan Masuk"
"generalActions" = "Tindakan Umum" "generalActions" = "Tindakan Umum"
"autoRefresh" = "Pembaruan otomatis"
"autoRefreshInterval" = "Interval"
"create" = "Buat" "create" = "Buat"
"update" = "Perbarui" "update" = "Perbarui"
"modifyInbound" = "Ubah Masuk" "modifyInbound" = "Ubah Masuk"
@ -581,6 +583,24 @@
"yes" = "✅ Ya" "yes" = "✅ Ya"
"no" = "❌ Tidak" "no" = "❌ Tidak"
"received_id" = "🔑📥 ID yang diterima: {{ .ClientId }}"
"received_password" = "🔑📥 Kata sandi yang diterima: {{ .ClientPass }}"
"received_email" = "📧📥 Email yang diterima: {{ .ClientEmail }}"
"received_comment" = "💬📥 Komentar yang diterima: {{ .ClientComment }}"
"id_prompt" = "🔑 ID Default: {{ .ClientId }}\n\nMasukkan ID Anda."
"pass_prompt" = "🔑 Kata Sandi Default: {{ .ClientPassword }}\n\nMasukkan kata sandi Anda."
"email_prompt" = "📧 Email Default: {{ .ClientEmail }}\n\nMasukkan email Anda."
"comment_prompt" = "💬 Komentar Default: {{ .ClientComment }}\n\nMasukkan komentar Anda."
"inbound_client_data_id" = "🔄 Masuk: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n💬 Komentar: {{ .ClientComment }}\n\nAnda dapat menambahkan klien ke inbound sekarang!"
"inbound_client_data_pass" = "🔄 Masuk: {{ .InboundRemark }}\n\n🔑 Kata Sandi: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n💬 Komentar: {{ .ClientComment }}\n\nAnda dapat menambahkan klien ke inbound sekarang!"
"client_data_id" = "🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n💬 Komentar: {{ .ClientComment }}\n\nAnda dapat menambahkan klien ke inbound sekarang!"
"client_data_pass" = "🔑 Kata Sandi: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n💬 Komentar: {{ .ClientComment }}\n\nAnda dapat menambahkan klien ke inbound sekarang!"
"cancel" = "❌ Proses Dibatalkan! \n\nAnda dapat memulai lagi kapan saja dengan /start. 🔄"
"error_add_client" = "⚠️ Kesalahan:\n\n {{ .error }}"
"success_add_client" = "🏆 Berhasil! \nSekarang Anda dapat mengeditnya menggunakan tombol 'Semua Klien'."
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Tutup Papan Ketik" "closeKeyboard" = "❌ Tutup Papan Ketik"
"cancel" = "❌ Batal" "cancel" = "❌ Batal"
@ -615,6 +635,11 @@
"getBanLogs" = "Dapatkan Log Pemblokiran" "getBanLogs" = "Dapatkan Log Pemblokiran"
"allClients" = "Semua Klien" "allClients" = "Semua Klien"
"addClient" = "Tambah Klien"
"submitDisable" = "Kirim Sebagai Nonaktif ✅"
"use_default" = "🏷️ Gunakan Default"
"change_default" = "🔄⚙️ Ubah Default"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Operasi berhasil!" "successfulOperation" = "✅ Operasi berhasil!"
"errorOperation" = "❗ Kesalahan dalam operasi." "errorOperation" = "❗ Kesalahan dalam operasi."

View file

@ -142,6 +142,8 @@
"resetTraffic" = "トラフィックリセット" "resetTraffic" = "トラフィックリセット"
"addInbound" = "インバウンド追加" "addInbound" = "インバウンド追加"
"generalActions" = "一般操作" "generalActions" = "一般操作"
"autoRefresh" = "自動更新"
"autoRefreshInterval" = "間隔"
"create" = "追加" "create" = "追加"
"update" = "更新" "update" = "更新"
"modifyInbound" = "インバウンド修正" "modifyInbound" = "インバウンド修正"
@ -582,6 +584,23 @@
"yes" = "✅ はい" "yes" = "✅ はい"
"no" = "❌ いいえ" "no" = "❌ いいえ"
"received_id" = "🔑📥 受信したID: {{ .ClientId }}"
"received_password" = "🔑📥 受信したパスワード: {{ .ClientPass }}"
"received_email" = "📧📥 受信したメール: {{ .ClientEmail }}"
"received_comment" = "💬📥 受信したコメント: {{ .ClientComment }}"
"id_prompt" = "🔑 デフォルトのID: {{ .ClientId }}\n\nIDを入力してください。"
"pass_prompt" = "🔑 デフォルトのパスワード: {{ .ClientPassword }}\n\nパスワードを入力してください。"
"email_prompt" = "📧 デフォルトのメール: {{ .ClientEmail }}\n\nメールアドレスを入力してください。"
"comment_prompt" = "💬 デフォルトのコメント: {{ .ClientComment }}\n\nコメントを入力してください。"
"inbound_client_data_id" = "🔄 インバウンド: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 メール: {{ .ClientEmail }}\n💬 コメント: {{ .ClientComment }}\n\n今すぐクライアントをインバウンドに追加できます"
"inbound_client_data_pass" = "🔄 インバウンド: {{ .InboundRemark }}\n\n🔑 パスワード: {{ .ClientPass }}\n📧 メール: {{ .ClientEmail }}\n💬 コメント: {{ .ClientComment }}\n\n今すぐクライアントをインバウンドに追加できます"
"client_data_id" = "🔑 ID: {{ .ClientId }}\n📧 メール: {{ .ClientEmail }}\n💬 コメント: {{ .ClientComment }}\n\n今すぐクライアントをインバウンドに追加できます"
"client_data_pass" = "🔑 パスワード: {{ .ClientPass }}\n📧 メール: {{ .ClientEmail }}\n💬 コメント: {{ .ClientComment }}\n\n今すぐクライアントをインバウンドに追加できます"
"cancel" = "❌ プロセスがキャンセルされました!\n\nいつでも /start を使用できます。 🔄"
"error_add_client" = "⚠️ エラー:\n\n {{ .error }}"
"success_add_client" = "🏆 成功!\n「すべてのクライアント」ボタンを使用して、編集できます。"
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ キーボードを閉じる" "closeKeyboard" = "❌ キーボードを閉じる"
"cancel" = "❌ キャンセル" "cancel" = "❌ キャンセル"
@ -616,6 +635,11 @@
"getBanLogs" = "禁止ログ" "getBanLogs" = "禁止ログ"
"allClients" = "すべてのクライアント" "allClients" = "すべてのクライアント"
"addClient" = "クライアントを追加"
"submitDisable" = "無効として送信 ✅"
"use_default" = "🏷️ デフォルトを使用"
"change_default" = "🔄⚙️ デフォルトを変更"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ 成功!" "successfulOperation" = "✅ 成功!"
"errorOperation" = "❗ 操作エラー。" "errorOperation" = "❗ 操作エラー。"

View file

@ -142,6 +142,8 @@
"resetTraffic" = "Redefinir Tráfego" "resetTraffic" = "Redefinir Tráfego"
"addInbound" = "Adicionar Inbound" "addInbound" = "Adicionar Inbound"
"generalActions" = "Ações Gerais" "generalActions" = "Ações Gerais"
"autoRefresh" = "Atualização automática"
"autoRefreshInterval" = "Intervalo"
"create" = "Criar" "create" = "Criar"
"update" = "Atualizar" "update" = "Atualizar"
"modifyInbound" = "Modificar Inbound" "modifyInbound" = "Modificar Inbound"
@ -582,6 +584,23 @@
"yes" = "✅ Sim" "yes" = "✅ Sim"
"no" = "❌ Não" "no" = "❌ Não"
"received_id" = "🔑📥 ID recebido: {{ .ClientId }}"
"received_password" = "🔑📥 Senha recebida: {{ .ClientPass }}"
"received_email" = "📧📥 E-mail recebido: {{ .ClientEmail }}"
"received_comment" = "💬📥 Comentário recebido: {{ .ClientComment }}"
"id_prompt" = "🔑 ID padrão: {{ .ClientId }}\n\nDigite seu ID."
"pass_prompt" = "🔑 Senha padrão: {{ .ClientPassword }}\n\nDigite sua senha."
"email_prompt" = "📧 E-mail padrão: {{ .ClientEmail }}\n\nDigite seu e-mail."
"comment_prompt" = "💬 Comentário padrão: {{ .ClientComment }}\n\nDigite seu comentário."
"inbound_client_data_id" = "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 E-mail: {{ .ClientEmail }}\n💬 Comentário: {{ .ClientComment }}\n\nAgora você pode adicionar o cliente à entrada!"
"inbound_client_data_pass" = "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 Senha: {{ .ClientPass }}\n📧 E-mail: {{ .ClientEmail }}\n💬 Comentário: {{ .ClientComment }}\n\nAgora você pode adicionar o cliente à entrada!"
"client_data_id" = "🔑 ID: {{ .ClientId }}\n📧 E-mail: {{ .ClientEmail }}\n💬 Comentário: {{ .ClientComment }}\n\nAgora você pode adicionar o cliente à entrada!"
"client_data_pass" = "🔑 Senha: {{ .ClientPass }}\n📧 E-mail: {{ .ClientEmail }}\n💬 Comentário: {{ .ClientComment }}\n\nAgora você pode adicionar o cliente à entrada!"
"cancel" = "❌ Processo cancelado! \n\nVocê pode usar /start a qualquer momento. 🔄"
"error_add_client" = "⚠️ Erro:\n\n {{ .error }}"
"success_add_client" = "🏆 Sucesso! \nAgora você pode editá-lo usando o botão 'Todos os Clientes'."
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Fechar teclado" "closeKeyboard" = "❌ Fechar teclado"
"cancel" = "❌ Cancelar" "cancel" = "❌ Cancelar"
@ -616,6 +635,11 @@
"getBanLogs" = "Obter logs de banimento" "getBanLogs" = "Obter logs de banimento"
"allClients" = "Todos os clientes" "allClients" = "Todos os clientes"
"addClient" = "Adicionar Cliente"
"submitDisable" = "Enviar como Desativado ✅"
"use_default" = "🏷️ Usar padrão"
"change_default" = "🔄⚙️ Alterar Padrão"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Operação bem-sucedida!" "successfulOperation" = "✅ Operação bem-sucedida!"
"errorOperation" = "❗ Erro na operação." "errorOperation" = "❗ Erro na operação."

View file

@ -142,6 +142,8 @@
"resetTraffic" = "Сбросить трафик" "resetTraffic" = "Сбросить трафик"
"addInbound" = "Добавить подключение" "addInbound" = "Добавить подключение"
"generalActions" = "Общие действия" "generalActions" = "Общие действия"
"autoRefresh" = "Автообновление"
"autoRefreshInterval" = "Интервал"
"create" = "Создать" "create" = "Создать"
"update" = "Обновить" "update" = "Обновить"
"modifyInbound" = "Изменить подключение" "modifyInbound" = "Изменить подключение"
@ -582,6 +584,23 @@
"yes" = "✅ Да" "yes" = "✅ Да"
"no" = "❌ Нет" "no" = "❌ Нет"
"received_id" = "🔑📥 Полученный ID: {{ .ClientId }}"
"received_password" = "🔑📥 Полученный пароль: {{ .ClientPass }}"
"received_email" = "📧📥 Полученный email: {{ .ClientEmail }}"
"received_comment" = "💬📥 Полученный комментарий: {{ .ClientComment }}"
"id_prompt" = "🔑 Стандартный ID: {{ .ClientId }}\n\nВведите ваш ID."
"pass_prompt" = "🔑 Стандартный пароль: {{ .ClientPassword }}\n\nВведите ваш пароль."
"email_prompt" = "📧 Стандартный email: {{ .ClientEmail }}\n\nВведите ваш email."
"comment_prompt" = "💬 Стандартный комментарий: {{ .ClientComment }}\n\nВведите ваш комментарий."
"inbound_client_data_id" = "🔄 Входящий: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента во входящие!"
"inbound_client_data_pass" = "🔄 Входящий: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента во входящие!"
"client_data_id" = "🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента во входящие!"
"client_data_pass" = "🔑 Пароль: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента во входящие!"
"cancel" = "❌ Процесс отменен! \n\nВы можете начать заново в любое время с помощью /start. 🔄"
"error_add_client" = "⚠️ Ошибка:\n\n {{ .error }}"
"success_add_client" = "🏆 Успех! \nТеперь вы можете изменить его, используя кнопку 'Все клиенты'."
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Закрыть клавиатуру" "closeKeyboard" = "❌ Закрыть клавиатуру"
"cancel" = "❌ Отмена" "cancel" = "❌ Отмена"
@ -616,6 +635,11 @@
"getBanLogs" = "Логи блокировок" "getBanLogs" = "Логи блокировок"
"allClients" = "Все клиенты" "allClients" = "Все клиенты"
"addClient" = "Добавить клиента"
"submitDisable" = "Отправить как отключено ✅"
"use_default" = "🏷️ Использовать по умолчанию"
"change_default" = "🔄⚙️ Изменить по умолчанию"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Успешно!" "successfulOperation" = "✅ Успешно!"
"errorOperation" = "❗ Ошибка в операции." "errorOperation" = "❗ Ошибка в операции."

View file

@ -142,6 +142,8 @@
"resetTraffic" = "Trafiği Sıfırla" "resetTraffic" = "Trafiği Sıfırla"
"addInbound" = "Gelen Ekle" "addInbound" = "Gelen Ekle"
"generalActions" = "Genel Eylemler" "generalActions" = "Genel Eylemler"
"autoRefresh" = "Otomatik yenileme"
"autoRefreshInterval" = "Aralık"
"create" = "Oluştur" "create" = "Oluştur"
"update" = "Güncelle" "update" = "Güncelle"
"modifyInbound" = "Geleni Düzenle" "modifyInbound" = "Geleni Düzenle"
@ -582,6 +584,23 @@
"yes" = "✅ Evet" "yes" = "✅ Evet"
"no" = "❌ Hayır" "no" = "❌ Hayır"
"received_id" = "🔑📥 Alınan Kimlik: {{ .ClientId }}"
"received_password" = "🔑📥 Alınan Şifre: {{ .ClientPass }}"
"received_email" = "📧📥 Alınan E-posta: {{ .ClientEmail }}"
"received_comment" = "💬📥 Alınan Yorum: {{ .ClientComment }}"
"id_prompt" = "🔑 Varsayılan Kimlik: {{ .ClientId }}\n\nKimliğinizi girin."
"pass_prompt" = "🔑 Varsayılan Şifre: {{ .ClientPassword }}\n\nŞifrenizi girin."
"email_prompt" = "📧 Varsayılan E-posta: {{ .ClientEmail }}\n\nE-posta adresinizi girin."
"comment_prompt" = "💬 Varsayılan Yorum: {{ .ClientComment }}\n\nYorumunuzu girin."
"inbound_client_data_id" = "🔄 Gelen: {{ .InboundRemark }}\n\n🔑 Kimlik: {{ .ClientId }}\n📧 E-posta: {{ .ClientEmail }}\n💬 Yorum: {{ .ClientComment }}\n\nArtık müşteriyi gelen kutusuna ekleyebilirsiniz!"
"inbound_client_data_pass" = "🔄 Gelen: {{ .InboundRemark }}\n\n🔑 Şifre: {{ .ClientPass }}\n📧 E-posta: {{ .ClientEmail }}\n💬 Yorum: {{ .ClientComment }}\n\nArtık müşteriyi gelen kutusuna ekleyebilirsiniz!"
"client_data_id" = "🔑 Kimlik: {{ .ClientId }}\n📧 E-posta: {{ .ClientEmail }}\n💬 Yorum: {{ .ClientComment }}\n\nArtık müşteriyi gelen kutusuna ekleyebilirsiniz!"
"client_data_pass" = "🔑 Şifre: {{ .ClientPass }}\n📧 E-posta: {{ .ClientEmail }}\n💬 Yorum: {{ .ClientComment }}\n\nArtık müşteriyi gelen kutusuna ekleyebilirsiniz!"
"cancel" = "❌ İşlem iptal edildi! \n\nİstediğiniz zaman /start kullanabilirsiniz. 🔄"
"error_add_client" = "⚠️ Hata:\n\n {{ .error }}"
"success_add_client" = "🏆 Başarılı! \nArtık 'Tüm Müşteriler' düğmesini kullanarak düzenleyebilirsiniz."
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Klavyeyi Kapat" "closeKeyboard" = "❌ Klavyeyi Kapat"
"cancel" = "❌ İptal" "cancel" = "❌ İptal"
@ -616,6 +635,11 @@
"getBanLogs" = "Yasak Günlüklerini Al" "getBanLogs" = "Yasak Günlüklerini Al"
"allClients" = "Tüm Müşteriler" "allClients" = "Tüm Müşteriler"
"addClient" = "Müşteri Ekle"
"submitDisable" = "Devre Dışı Olarak Gönder ✅"
"use_default" = "🏷️ Varsayılanı Kullan"
"change_default" = "🔄⚙️ Varsayılanı Değiştir"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ İşlem başarılı!" "successfulOperation" = "✅ İşlem başarılı!"
"errorOperation" = "❗ İşlemde hata." "errorOperation" = "❗ İşlemde hata."

View file

@ -142,6 +142,8 @@
"resetTraffic" = "Скинути трафік" "resetTraffic" = "Скинути трафік"
"addInbound" = "Додати вхідний" "addInbound" = "Додати вхідний"
"generalActions" = "Загальні дії" "generalActions" = "Загальні дії"
"autoRefresh" = "Автооновлення"
"autoRefreshInterval" = "Інтервал"
"create" = "Створити" "create" = "Створити"
"update" = "Оновити" "update" = "Оновити"
"modifyInbound" = "Змінити вхідний" "modifyInbound" = "Змінити вхідний"
@ -582,6 +584,23 @@
"yes" = "✅ Так" "yes" = "✅ Так"
"no" = "❌ Ні" "no" = "❌ Ні"
"received_id" = "🔑📥 Отриманий ID: {{ .ClientId }}"
"received_password" = "🔑📥 Отриманий пароль: {{ .ClientPass }}"
"received_email" = "📧📥 Отриманий email: {{ .ClientEmail }}"
"received_comment" = "💬📥 Отриманий коментар: {{ .ClientComment }}"
"id_prompt" = "🔑 Стандартний ID: {{ .ClientId }}\n\nВведіть ваш ID."
"pass_prompt" = "🔑 Стандартний пароль: {{ .ClientPassword }}\n\nВведіть ваш пароль."
"email_prompt" = "📧 Стандартний email: {{ .ClientEmail }}\n\nВведіть ваш email."
"comment_prompt" = "💬 Стандартний коментар: {{ .ClientComment }}\n\nВведіть ваш коментар."
"inbound_client_data_id" = "🔄 Вхідні: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n💬 Коментар: {{ .ClientComment }}\n\nТепер ви можете додати клієнта до вхідних!"
"inbound_client_data_pass" = "🔄 Вхідні: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n💬 Коментар: {{ .ClientComment }}\n\nТепер ви можете додати клієнта до вхідних!"
"client_data_id" = "🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n💬 Коментар: {{ .ClientComment }}\n\nТепер ви можете додати клієнта до вхідних!"
"client_data_pass" = "🔑 Пароль: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n💬 Коментар: {{ .ClientComment }}\n\nТепер ви можете додати клієнта до вхідних!"
"cancel" = "❌ Процес скасовано! \n\nВи можете використати /start у будь-який час. 🔄"
"error_add_client" = "⚠️ Помилка:\n\n {{ .error }}"
"success_add_client" = "🏆 Успіх! \nТепер ви можете редагувати його за допомогою кнопки 'Усі клієнти'."
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Закрити клавіатуру" "closeKeyboard" = "❌ Закрити клавіатуру"
"cancel" = "❌ Скасувати" "cancel" = "❌ Скасувати"
@ -616,6 +635,11 @@
"getBanLogs" = "Отримати журнали заборон" "getBanLogs" = "Отримати журнали заборон"
"allClients" = "Всі Клієнти" "allClients" = "Всі Клієнти"
"addClient" = "Додати клієнта"
"submitDisable" = "Надіслати як вимкнено ✅"
"use_default" = "🏷️ Використати за замовчуванням"
"change_default" = "🔄⚙️ Змінити за замовчуванням"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Операція успішна!" "successfulOperation" = "✅ Операція успішна!"
"errorOperation" = "❗ Помилка в роботі." "errorOperation" = "❗ Помилка в роботі."

View file

@ -142,6 +142,8 @@
"resetTraffic" = "Đặt lại lưu lượng" "resetTraffic" = "Đặt lại lưu lượng"
"addInbound" = "Thêm điểm vào" "addInbound" = "Thêm điểm vào"
"generalActions" = "Hành động chung" "generalActions" = "Hành động chung"
"autoRefresh" = "Tự động làm mới"
"autoRefreshInterval" = "Khoảng thời gian"
"create" = "Tạo mới" "create" = "Tạo mới"
"update" = "Cập nhật" "update" = "Cập nhật"
"modifyInbound" = "Chỉnh sửa điểm vào (Inbound)" "modifyInbound" = "Chỉnh sửa điểm vào (Inbound)"
@ -582,6 +584,23 @@
"yes" = "✅ Có" "yes" = "✅ Có"
"no" = "❌ Không" "no" = "❌ Không"
"received_id" = "🔑📥 ID nhận được: {{ .ClientId }}"
"received_password" = "🔑📥 Mật khẩu nhận được: {{ .ClientPass }}"
"received_email" = "📧📥 Email nhận được: {{ .ClientEmail }}"
"received_comment" = "💬📥 Bình luận nhận được: {{ .ClientComment }}"
"id_prompt" = "🔑 ID mặc định: {{ .ClientId }}\n\nNhập ID của bạn."
"pass_prompt" = "🔑 Mật khẩu mặc định: {{ .ClientPassword }}\n\nNhập mật khẩu của bạn."
"email_prompt" = "📧 Email mặc định: {{ .ClientEmail }}\n\nNhập email của bạn."
"comment_prompt" = "💬 Bình luận mặc định: {{ .ClientComment }}\n\nNhập bình luận của bạn."
"inbound_client_data_id" = "🔄 Dữ liệu đến: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n💬 Bình luận: {{ .ClientComment }}\n\nBạn có thể thêm khách hàng vào danh sách đến ngay bây giờ!"
"inbound_client_data_pass" = "🔄 Dữ liệu đến: {{ .InboundRemark }}\n\n🔑 Mật khẩu: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n💬 Bình luận: {{ .ClientComment }}\n\nBạn có thể thêm khách hàng vào danh sách đến ngay bây giờ!"
"client_data_id" = "🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n💬 Bình luận: {{ .ClientComment }}\n\nBạn có thể thêm khách hàng vào danh sách đến ngay bây giờ!"
"client_data_pass" = "🔑 Mật khẩu: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n💬 Bình luận: {{ .ClientComment }}\n\nBạn có thể thêm khách hàng vào danh sách đến ngay bây giờ!"
"cancel" = "❌ Quá trình đã bị hủy! \n\nBạn có thể sử dụng /start bất cứ lúc nào. 🔄"
"error_add_client" = "⚠️ Lỗi:\n\n {{ .error }}"
"success_add_client" = "🏆 Thành công! \nGiờ đây bạn có thể chỉnh sửa bằng nút 'Tất Cả Khách Hàng'."
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Đóng Bàn Phím" "closeKeyboard" = "❌ Đóng Bàn Phím"
"cancel" = "❌ Hủy" "cancel" = "❌ Hủy"
@ -616,6 +635,11 @@
"getBanLogs" = "Cấm nhật ký" "getBanLogs" = "Cấm nhật ký"
"allClients" = "Tất cả Khách hàng" "allClients" = "Tất cả Khách hàng"
"addClient" = "Thêm Khách Hàng"
"submitDisable" = "Gửi Dưới Dạng Tắt ✅"
"use_default" = "🏷️ Sử dụng mặc định"
"change_default" = "🔄⚙️ Thay đổi mặc định"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Thành công!" "successfulOperation" = "✅ Thành công!"
"errorOperation" = "❗ Lỗi Trong Quá Trình Thực Hiện." "errorOperation" = "❗ Lỗi Trong Quá Trình Thực Hiện."

View file

@ -142,6 +142,8 @@
"resetTraffic" = "重置流量" "resetTraffic" = "重置流量"
"addInbound" = "添加入站" "addInbound" = "添加入站"
"generalActions" = "通用操作" "generalActions" = "通用操作"
"autoRefresh" = "自动刷新"
"autoRefreshInterval" = "间隔"
"create" = "添加" "create" = "添加"
"update" = "修改" "update" = "修改"
"modifyInbound" = "修改入站" "modifyInbound" = "修改入站"
@ -582,6 +584,23 @@
"yes" = "✅ 是的" "yes" = "✅ 是的"
"no" = "❌ 没有" "no" = "❌ 没有"
"received_id" = "🔑📥 接收到的ID: {{ .ClientId }}"
"received_password" = "🔑📥 接收到的密码: {{ .ClientPass }}"
"received_email" = "📧📥 接收到的电子邮件: {{ .ClientEmail }}"
"received_comment" = "💬📥 接收到的评论: {{ .ClientComment }}"
"id_prompt" = "🔑 默认ID: {{ .ClientId }}\n\n请输入您的ID。"
"pass_prompt" = "🔑 默认密码: {{ .ClientPassword }}\n\n请输入您的密码。"
"email_prompt" = "📧 默认电子邮件: {{ .ClientEmail }}\n\n请输入您的电子邮件。"
"comment_prompt" = "💬 默认评论: {{ .ClientComment }}\n\n请输入您的评论。"
"inbound_client_data_id" = "🔄 传入数据: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 电子邮件: {{ .ClientEmail }}\n💬 评论: {{ .ClientComment }}\n\n您现在可以将客户添加到传入列表"
"inbound_client_data_pass" = "🔄 传入数据: {{ .InboundRemark }}\n\n🔑 密码: {{ .ClientPass }}\n📧 电子邮件: {{ .ClientEmail }}\n💬 评论: {{ .ClientComment }}\n\n您现在可以将客户添加到传入列表"
"client_data_id" = "🔑 ID: {{ .ClientId }}\n📧 电子邮件: {{ .ClientEmail }}\n💬 评论: {{ .ClientComment }}\n\n您现在可以将客户添加到传入列表"
"client_data_pass" = "🔑 密码: {{ .ClientPass }}\n📧 电子邮件: {{ .ClientEmail }}\n💬 评论: {{ .ClientComment }}\n\n您现在可以将客户添加到传入列表"
"cancel" = "❌ 过程已取消!\n\n您可以随时使用 /start。 🔄"
"error_add_client" = "⚠️ 错误:\n\n {{ .error }}"
"success_add_client" = "🏆 成功!\n您现在可以使用'所有客户'按钮进行修改。"
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ 关闭键盘" "closeKeyboard" = "❌ 关闭键盘"
"cancel" = "❌ 取消" "cancel" = "❌ 取消"
@ -616,6 +635,11 @@
"getBanLogs" = "禁止日志" "getBanLogs" = "禁止日志"
"allClients" = "所有客户" "allClients" = "所有客户"
"addClient" = "添加客户"
"submitDisable" = "提交为禁用 ✅"
"use_default" = "🏷️ 使用默认"
"change_default" = "🔄⚙️ 更改默认"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ 成功!" "successfulOperation" = "✅ 成功!"
"errorOperation" = "❗ 操作错误。" "errorOperation" = "❗ 操作错误。"

View file

@ -142,6 +142,8 @@
"resetTraffic" = "重置流量" "resetTraffic" = "重置流量"
"addInbound" = "新增入站" "addInbound" = "新增入站"
"generalActions" = "通用操作" "generalActions" = "通用操作"
"autoRefresh" = "自動刷新"
"autoRefreshInterval" = "間隔"
"create" = "新增" "create" = "新增"
"update" = "修改" "update" = "修改"
"modifyInbound" = "修改入站" "modifyInbound" = "修改入站"
@ -582,6 +584,23 @@
"yes" = "✅ 是的" "yes" = "✅ 是的"
"no" = "❌ 沒有" "no" = "❌ 沒有"
"received_id" = "🔑📥 接收到的 ID: {{ .ClientId }}"
"received_password" = "🔑📥 接收到的密碼: {{ .ClientPass }}"
"received_email" = "📧📥 接收到的電子郵件: {{ .ClientEmail }}"
"received_comment" = "💬📥 接收到的評論: {{ .ClientComment }}"
"id_prompt" = "🔑 預設 ID: {{ .ClientId }}\n\n請輸入您的 ID。"
"pass_prompt" = "🔑 預設密碼: {{ .ClientPassword }}\n\n請輸入您的密碼。"
"email_prompt" = "📧 預設電子郵件: {{ .ClientEmail }}\n\n請輸入您的電子郵件。"
"comment_prompt" = "💬 預設評論: {{ .ClientComment }}\n\n請輸入您的評論。"
"inbound_client_data_id" = "🔄 傳入數據: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 電子郵件: {{ .ClientEmail }}\n💬 評論: {{ .ClientComment }}\n\n您現在可以將客戶添加到傳入列表"
"inbound_client_data_pass" = "🔄 傳入數據: {{ .InboundRemark }}\n\n🔑 密碼: {{ .ClientPass }}\n📧 電子郵件: {{ .ClientEmail }}\n💬 評論: {{ .ClientComment }}\n\n您現在可以將客戶添加到傳入列表"
"client_data_id" = "🔑 ID: {{ .ClientId }}\n📧 電子郵件: {{ .ClientEmail }}\n💬 評論: {{ .ClientComment }}\n\n您現在可以將客戶添加到傳入列表"
"client_data_pass" = "🔑 密碼: {{ .ClientPass }}\n📧 電子郵件: {{ .ClientEmail }}\n💬 評論: {{ .ClientComment }}\n\n您現在可以將客戶添加到傳入列表"
"cancel" = "❌ 過程已取消!\n\n您可以隨時使用 /start。 🔄"
"error_add_client" = "⚠️ 錯誤:\n\n {{ .error }}"
"success_add_client" = "🏆 成功!\n您現在可以使用'所有客戶'按鈕進行修改。"
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ 關閉鍵盤" "closeKeyboard" = "❌ 關閉鍵盤"
"cancel" = "❌ 取消" "cancel" = "❌ 取消"
@ -616,6 +635,11 @@
"getBanLogs" = "禁止日誌" "getBanLogs" = "禁止日誌"
"allClients" = "所有客戶" "allClients" = "所有客戶"
"addClient" = "新增客戶"
"submitDisable" = "提交為停用 ✅"
"use_default" = "🏷️ 使用預設"
"change_default" = "🔄⚙️ 更改預設"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ 成功!" "successfulOperation" = "✅ 成功!"
"errorOperation" = "❗ 操作錯誤。" "errorOperation" = "❗ 操作錯誤。"