bulk client creation #285
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||
<v-card class="rounded-lg">
|
||||
<v-card-title>
|
||||
{{ $t('bulk.add') }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||
<v-container style="padding: 0;">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model.number="count" type="number" min="1" max="100" :label="$t('count')" hide-details></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-combobox
|
||||
chips
|
||||
multiple
|
||||
v-model="bulkData.name"
|
||||
:items="patterns"
|
||||
:label="$t('client.name')"
|
||||
hide-details>
|
||||
</v-combobox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-combobox
|
||||
chips
|
||||
multiple
|
||||
v-model="bulkData.desc"
|
||||
:items="patterns"
|
||||
:label="$t('client.desc')"
|
||||
hide-details>
|
||||
</v-combobox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-combobox v-model="bulkData.group" :items="groups" :label="$t('client.group')" hide-details></v-combobox>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field v-model.number="bulkData.Volume" type="number" min="0" :label="$t('stats.volume')" suffix="GiB" hide-details></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<DatePick :expiry="bulkData.expiry" @submit="setDate" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-combobox
|
||||
v-model="bulkData.clientInbounds"
|
||||
:items="inboundTags"
|
||||
:label="$t('client.inboundTags')"
|
||||
multiple
|
||||
chips
|
||||
hide-details
|
||||
></v-combobox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-switch v-model="bulkData.clientStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="blue-darken-1"
|
||||
variant="outlined"
|
||||
@click="closeModal"
|
||||
>
|
||||
{{ $t('actions.close') }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="blue-darken-1"
|
||||
variant="tonal"
|
||||
:loading="loading"
|
||||
@click="saveChanges"
|
||||
>
|
||||
{{ $t('actions.save') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import DatePick from '@/components/DateTime.vue'
|
||||
import { push } from 'notivue'
|
||||
import RandomUtil from '@/plugins/randomUtil'
|
||||
import { Client, createClient, randomConfigs } from '@/types/clients'
|
||||
import { i18n } from '@/locales';
|
||||
|
||||
export default {
|
||||
props: ['visible', 'inboundTags', 'groups'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
count: 1,
|
||||
clients: <Client[]>[],
|
||||
bulkData: {
|
||||
name: <any[]>[],
|
||||
desc: <any[]>[],
|
||||
group: '',
|
||||
clientInbounds: [],
|
||||
expiry: 0,
|
||||
Volume: 0,
|
||||
clientStats: false,
|
||||
},
|
||||
patterns: [
|
||||
{ title: i18n.global.t("bulk.random"), value: "random" },
|
||||
{ title: i18n.global.t("bulk.order"), value: "order" },
|
||||
],
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetData() {
|
||||
this.count = 1,
|
||||
this.clients = [],
|
||||
this.bulkData = {
|
||||
name: [this.patterns[1], "-", this.patterns[0]],
|
||||
desc: [],
|
||||
group: '',
|
||||
clientInbounds: [],
|
||||
expiry: 0,
|
||||
Volume: 0,
|
||||
clientStats: false,
|
||||
}
|
||||
},
|
||||
closeModal() {
|
||||
this.$emit('close')
|
||||
},
|
||||
saveChanges() {
|
||||
if (this.bulkData.name.findIndex(n => typeof(n) == 'object') == -1) {
|
||||
push.error(i18n.global.t('error.dplData'))
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
for(let i=0;i<this.count;i++){
|
||||
const name = this.genByPattern(this.bulkData.name, i)
|
||||
this.clients.push(createClient({
|
||||
enable: true,
|
||||
name: name,
|
||||
config: randomConfigs(name),
|
||||
inbounds: this.bulkData.clientInbounds,
|
||||
links: [],
|
||||
volume: this.bulkData.Volume*(1024 ** 3),
|
||||
expiry: this.bulkData.expiry,
|
||||
up: 0,
|
||||
down: 0,
|
||||
desc: this.genByPattern(this.bulkData.desc, i),
|
||||
group: this.bulkData.group
|
||||
}))
|
||||
}
|
||||
this.$emit('save', this.clients, this.bulkData.clientInbounds, this.bulkData.clientStats)
|
||||
this.resetData() // reset to default
|
||||
this.loading = false
|
||||
},
|
||||
genByPattern(pattern: any[], order :number){
|
||||
if (pattern.length == 0) return RandomUtil.randomSeq(8)
|
||||
let result = ''
|
||||
pattern.forEach(p => {
|
||||
switch(typeof p){
|
||||
case 'object':
|
||||
switch(p.value){
|
||||
case "random":
|
||||
result += RandomUtil.randomSeq(8)
|
||||
break
|
||||
case "order":
|
||||
result += order+1
|
||||
}
|
||||
break
|
||||
default:
|
||||
result += p
|
||||
}
|
||||
})
|
||||
return result
|
||||
},
|
||||
setDate(v:number){
|
||||
this.bulkData.expiry = v
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
this.resetData()
|
||||
}
|
||||
},
|
||||
},
|
||||
components: { DatePick },
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -165,6 +165,11 @@ export default {
|
||||
external: "External Link",
|
||||
sub: "External Subscription",
|
||||
},
|
||||
bulk: {
|
||||
add: "Add Bulk",
|
||||
order: "Order",
|
||||
random: "Random",
|
||||
},
|
||||
types: {
|
||||
un: "Username",
|
||||
pw: "Password",
|
||||
|
||||
@@ -164,6 +164,11 @@ export default {
|
||||
external: "لینک خارجی",
|
||||
sub: "سابسکریپشن خارجی",
|
||||
},
|
||||
bulk: {
|
||||
add: "ایجاد انبوه",
|
||||
order: "ترتیب",
|
||||
random: "تصادفی",
|
||||
},
|
||||
types: {
|
||||
un: "نام کاربری",
|
||||
pw: "رمز",
|
||||
|
||||
@@ -165,6 +165,11 @@ export default {
|
||||
external: "Внешняя ссылка",
|
||||
sub: "Внешняя подписка",
|
||||
},
|
||||
bulk: {
|
||||
add: "Добавить пакетно",
|
||||
order: "Порядок",
|
||||
random: "Случайный",
|
||||
},
|
||||
types: {
|
||||
un: "Имя пользователя",
|
||||
pw: "Пароль",
|
||||
|
||||
@@ -165,6 +165,11 @@ export default {
|
||||
external: "Liên kết bên ngoài",
|
||||
sub: "Đăng ký bên ngoài",
|
||||
},
|
||||
bulk: {
|
||||
add: "Thêm Hàng loạt",
|
||||
order: "Sắp xếp",
|
||||
random: "Ngẫu nhiên",
|
||||
},
|
||||
types: {
|
||||
un: "Tên người dùng",
|
||||
pw: "Mật khẩu",
|
||||
|
||||
@@ -165,6 +165,11 @@ export default {
|
||||
external: "外部链接",
|
||||
sub: "外部订阅",
|
||||
},
|
||||
bulk: {
|
||||
add: "批量添加",
|
||||
order: "排序",
|
||||
random: "随机",
|
||||
},
|
||||
types: {
|
||||
un: "用户名",
|
||||
pw: "密码",
|
||||
|
||||
@@ -166,6 +166,11 @@ export default {
|
||||
external: "外部鏈接",
|
||||
sub: "外部訂閱",
|
||||
},
|
||||
bulk: {
|
||||
add: "批量添加",
|
||||
order: "排序",
|
||||
random: "隨機",
|
||||
},
|
||||
types: {
|
||||
un: "用戶名",
|
||||
pw: "密碼",
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
/>
|
||||
<ClientBulk
|
||||
v-model="addBulkModal"
|
||||
:visible="addBulkModal"
|
||||
:groups="groups"
|
||||
:inboundTags="inboundTags"
|
||||
@close="closeBulk"
|
||||
@save="saveBulk"
|
||||
/>
|
||||
<QrCode
|
||||
v-model="qrcode.visible"
|
||||
:visible="qrcode.visible"
|
||||
@@ -28,11 +36,28 @@
|
||||
<v-col cols="auto">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-menu v-model="actionMenu" :close-on-content-click="false" location="bottom center">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details variant="text" icon>
|
||||
<v-icon icon="mdi-tools" color="primary" />
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list density="compact" nav>
|
||||
<v-list-item link @click="addBulk">
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-account-multiple-plus"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title v-text="$t('bulk.add')"></v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-menu v-model="filterMenu" :close-on-content-click="false" location="bottom center">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('filter') }}
|
||||
<v-badge color="error" dot v-if="filterSettings.enabled" floating />
|
||||
<v-btn v-bind="props" hide-details variant="text" icon>
|
||||
<v-icon :icon="filterSettings.enabled ? 'mdi-filter-check-outline' : 'mdi-filter-menu-outline'" :color="filterSettings.enabled ? 'primary' : ''" />
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
@@ -93,9 +118,9 @@
|
||||
</v-menu>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-switch v-model="tableView" color="primary" hide-details>
|
||||
<template v-slot:label><v-icon icon="mdi-table"></v-icon></template>
|
||||
</v-switch>
|
||||
<v-btn hide-details variant="text" icon @click="toggleClientView">
|
||||
<v-icon :icon="tableView ? 'mdi-table-eye' : 'mdi-table-eye-off'" :color="tableView ? 'primary' : ''"></v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-for="group in groups" v-if="!tableView">
|
||||
@@ -319,6 +344,7 @@
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data'
|
||||
import ClientModal from '@/layouts/modals/Client.vue'
|
||||
import ClientBulk from '@/layouts/modals/ClientBulk.vue'
|
||||
import QrCode from '@/layouts/modals/QrCode.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Client, createClient } from '@/types/clients'
|
||||
@@ -364,6 +390,7 @@ const groups = computed((): string[] => {
|
||||
return Array.from(new Set(clients.value?.map(c => c.group)))
|
||||
})
|
||||
|
||||
const actionMenu = ref(false)
|
||||
const filterMenu = ref(false)
|
||||
const filterSettings = ref({
|
||||
enabled: false,
|
||||
@@ -372,7 +399,12 @@ const filterSettings = ref({
|
||||
text: '',
|
||||
filteredClients: <any[]>[]
|
||||
})
|
||||
const tableView = ref(false)
|
||||
const tableView = ref(localStorage.getItem('clientView') == 'table')
|
||||
|
||||
const toggleClientView = () => {
|
||||
localStorage.setItem('clientView',tableView.value ? 'tile' : 'table')
|
||||
tableView.value = !tableView.value
|
||||
}
|
||||
|
||||
const filterItems = [
|
||||
{ title: i18n.global.t('none'), value: '' },
|
||||
@@ -592,4 +624,26 @@ const clearFilter = () => {
|
||||
}
|
||||
filterMenu.value = false
|
||||
}
|
||||
|
||||
const addBulkModal = ref(false)
|
||||
|
||||
const addBulk = () => {
|
||||
addBulkModal.value = true
|
||||
actionMenu.value = false
|
||||
}
|
||||
|
||||
const closeBulk = () => {
|
||||
addBulkModal.value = false
|
||||
}
|
||||
|
||||
const saveBulk = (bulkClients: Client[], clientInbounds: string[], clientStats: boolean) => {
|
||||
bulkClients.forEach((c,c_index) => {
|
||||
bulkClients[c_index].links = updateLinks(c)
|
||||
})
|
||||
clients.value.push(...bulkClients)
|
||||
buildInboundsUsers(clientInbounds)
|
||||
// Stats
|
||||
if (clientStats) v2rayStats.value.users.push(...bulkClients.map(bc => bc.name))
|
||||
closeBulk()
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user