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",
|
external: "External Link",
|
||||||
sub: "External Subscription",
|
sub: "External Subscription",
|
||||||
},
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "Add Bulk",
|
||||||
|
order: "Order",
|
||||||
|
random: "Random",
|
||||||
|
},
|
||||||
types: {
|
types: {
|
||||||
un: "Username",
|
un: "Username",
|
||||||
pw: "Password",
|
pw: "Password",
|
||||||
|
|||||||
@@ -164,6 +164,11 @@ export default {
|
|||||||
external: "لینک خارجی",
|
external: "لینک خارجی",
|
||||||
sub: "سابسکریپشن خارجی",
|
sub: "سابسکریپشن خارجی",
|
||||||
},
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "ایجاد انبوه",
|
||||||
|
order: "ترتیب",
|
||||||
|
random: "تصادفی",
|
||||||
|
},
|
||||||
types: {
|
types: {
|
||||||
un: "نام کاربری",
|
un: "نام کاربری",
|
||||||
pw: "رمز",
|
pw: "رمز",
|
||||||
|
|||||||
@@ -165,6 +165,11 @@ export default {
|
|||||||
external: "Внешняя ссылка",
|
external: "Внешняя ссылка",
|
||||||
sub: "Внешняя подписка",
|
sub: "Внешняя подписка",
|
||||||
},
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "Добавить пакетно",
|
||||||
|
order: "Порядок",
|
||||||
|
random: "Случайный",
|
||||||
|
},
|
||||||
types: {
|
types: {
|
||||||
un: "Имя пользователя",
|
un: "Имя пользователя",
|
||||||
pw: "Пароль",
|
pw: "Пароль",
|
||||||
|
|||||||
@@ -165,6 +165,11 @@ export default {
|
|||||||
external: "Liên kết bên ngoài",
|
external: "Liên kết bên ngoài",
|
||||||
sub: "Đăng ký 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: {
|
types: {
|
||||||
un: "Tên người dùng",
|
un: "Tên người dùng",
|
||||||
pw: "Mật khẩu",
|
pw: "Mật khẩu",
|
||||||
|
|||||||
@@ -165,6 +165,11 @@ export default {
|
|||||||
external: "外部链接",
|
external: "外部链接",
|
||||||
sub: "外部订阅",
|
sub: "外部订阅",
|
||||||
},
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "批量添加",
|
||||||
|
order: "排序",
|
||||||
|
random: "随机",
|
||||||
|
},
|
||||||
types: {
|
types: {
|
||||||
un: "用户名",
|
un: "用户名",
|
||||||
pw: "密码",
|
pw: "密码",
|
||||||
|
|||||||
@@ -166,6 +166,11 @@ export default {
|
|||||||
external: "外部鏈接",
|
external: "外部鏈接",
|
||||||
sub: "外部訂閱",
|
sub: "外部訂閱",
|
||||||
},
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "批量添加",
|
||||||
|
order: "排序",
|
||||||
|
random: "隨機",
|
||||||
|
},
|
||||||
types: {
|
types: {
|
||||||
un: "用戶名",
|
un: "用戶名",
|
||||||
pw: "密碼",
|
pw: "密碼",
|
||||||
|
|||||||
@@ -11,6 +11,14 @@
|
|||||||
@close="closeModal"
|
@close="closeModal"
|
||||||
@save="saveModal"
|
@save="saveModal"
|
||||||
/>
|
/>
|
||||||
|
<ClientBulk
|
||||||
|
v-model="addBulkModal"
|
||||||
|
:visible="addBulkModal"
|
||||||
|
:groups="groups"
|
||||||
|
:inboundTags="inboundTags"
|
||||||
|
@close="closeBulk"
|
||||||
|
@save="saveBulk"
|
||||||
|
/>
|
||||||
<QrCode
|
<QrCode
|
||||||
v-model="qrcode.visible"
|
v-model="qrcode.visible"
|
||||||
:visible="qrcode.visible"
|
:visible="qrcode.visible"
|
||||||
@@ -28,11 +36,28 @@
|
|||||||
<v-col cols="auto">
|
<v-col cols="auto">
|
||||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||||
</v-col>
|
</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-col cols="auto">
|
||||||
<v-menu v-model="filterMenu" :close-on-content-click="false" location="bottom center">
|
<v-menu v-model="filterMenu" :close-on-content-click="false" location="bottom center">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('filter') }}
|
<v-btn v-bind="props" hide-details variant="text" icon>
|
||||||
<v-badge color="error" dot v-if="filterSettings.enabled" floating />
|
<v-icon :icon="filterSettings.enabled ? 'mdi-filter-check-outline' : 'mdi-filter-menu-outline'" :color="filterSettings.enabled ? 'primary' : ''" />
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
@@ -93,9 +118,9 @@
|
|||||||
</v-menu>
|
</v-menu>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="auto">
|
<v-col cols="auto">
|
||||||
<v-switch v-model="tableView" color="primary" hide-details>
|
<v-btn hide-details variant="text" icon @click="toggleClientView">
|
||||||
<template v-slot:label><v-icon icon="mdi-table"></v-icon></template>
|
<v-icon :icon="tableView ? 'mdi-table-eye' : 'mdi-table-eye-off'" :color="tableView ? 'primary' : ''"></v-icon>
|
||||||
</v-switch>
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<template v-for="group in groups" v-if="!tableView">
|
<template v-for="group in groups" v-if="!tableView">
|
||||||
@@ -319,6 +344,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Data from '@/store/modules/data'
|
import Data from '@/store/modules/data'
|
||||||
import ClientModal from '@/layouts/modals/Client.vue'
|
import ClientModal from '@/layouts/modals/Client.vue'
|
||||||
|
import ClientBulk from '@/layouts/modals/ClientBulk.vue'
|
||||||
import QrCode from '@/layouts/modals/QrCode.vue'
|
import QrCode from '@/layouts/modals/QrCode.vue'
|
||||||
import Stats from '@/layouts/modals/Stats.vue'
|
import Stats from '@/layouts/modals/Stats.vue'
|
||||||
import { Client, createClient } from '@/types/clients'
|
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)))
|
return Array.from(new Set(clients.value?.map(c => c.group)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const actionMenu = ref(false)
|
||||||
const filterMenu = ref(false)
|
const filterMenu = ref(false)
|
||||||
const filterSettings = ref({
|
const filterSettings = ref({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -372,7 +399,12 @@ const filterSettings = ref({
|
|||||||
text: '',
|
text: '',
|
||||||
filteredClients: <any[]>[]
|
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 = [
|
const filterItems = [
|
||||||
{ title: i18n.global.t('none'), value: '' },
|
{ title: i18n.global.t('none'), value: '' },
|
||||||
@@ -592,4 +624,26 @@ const clearFilter = () => {
|
|||||||
}
|
}
|
||||||
filterMenu.value = false
|
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>
|
</script>
|
||||||
Reference in New Issue
Block a user