only table in clients page & better load and save
This commit is contained in:
+2
-2
@@ -258,11 +258,11 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
||||
|
||||
func (a *APIHandler) loadPartialData(c *gin.Context, objs []string) error {
|
||||
data := make(map[string]interface{}, 0)
|
||||
id := c.Query("id")
|
||||
|
||||
for _, obj := range objs {
|
||||
switch obj {
|
||||
case "inbounds":
|
||||
id := c.Query("id")
|
||||
inbounds, err := a.InboundService.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -287,7 +287,7 @@ func (a *APIHandler) loadPartialData(c *gin.Context, objs []string) error {
|
||||
}
|
||||
data[obj] = tlsConfigs
|
||||
case "clients":
|
||||
clients, err := a.ClientService.GetAll()
|
||||
clients, err := a.ClientService.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ type Client struct {
|
||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
Name string `json:"name" form:"name"`
|
||||
Config json.RawMessage `json:"config" form:"config"`
|
||||
Config json.RawMessage `json:"config,omitempty" form:"config"`
|
||||
Inbounds json.RawMessage `json:"inbounds" form:"inbounds"`
|
||||
Links json.RawMessage `json:"links" form:"links"`
|
||||
Links json.RawMessage `json:"links,omitempty" form:"links"`
|
||||
Volume int64 `json:"volume" form:"volume"`
|
||||
Expiry int64 `json:"expiry" form:"expiry"`
|
||||
Down int64 `json:"down" form:"down"`
|
||||
|
||||
@@ -16,14 +16,32 @@ type ClientService struct {
|
||||
InboundService
|
||||
}
|
||||
|
||||
func (s *ClientService) GetAll() ([]model.Client, error) {
|
||||
func (s *ClientService) Get(id string) (*[]model.Client, error) {
|
||||
if id == "" {
|
||||
return s.GetAll()
|
||||
}
|
||||
return s.getById(id)
|
||||
}
|
||||
|
||||
func (s *ClientService) getById(id string) (*[]model.Client, error) {
|
||||
db := database.GetDB()
|
||||
clients := []model.Client{}
|
||||
err := db.Model(model.Client{}).Scan(&clients).Error
|
||||
var client []model.Client
|
||||
err := db.Model(model.Client{}).Where("id in ?", strings.Split(id, ",")).Scan(&client).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clients, nil
|
||||
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
func (s *ClientService) GetAll() (*[]model.Client, error) {
|
||||
db := database.GetDB()
|
||||
var clients []model.Client
|
||||
err := db.Model(model.Client{}).Select("`id`, `enable`, `name`, `desc`, `group`, `inbounds`, `up`, `down`, `volume`, `expiry`").Scan(&clients).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &clients, nil
|
||||
}
|
||||
|
||||
func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, hostname string) ([]uint, error) {
|
||||
|
||||
@@ -120,6 +120,7 @@ func (s *ConfigService) Save(obj string, act string, data json.RawMessage, login
|
||||
var err error
|
||||
var inboundIds []uint
|
||||
var inboundId uint
|
||||
var objs []string = []string{obj}
|
||||
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
@@ -134,6 +135,7 @@ func (s *ConfigService) Save(obj string, act string, data json.RawMessage, login
|
||||
switch obj {
|
||||
case "clients":
|
||||
inboundIds, err = s.ClientService.Save(tx, act, data, hostname)
|
||||
objs = append(objs, "inbounds")
|
||||
case "tls":
|
||||
inboundIds, err = s.TlsService.Save(tx, act, data)
|
||||
case "inbounds":
|
||||
|
||||
@@ -244,9 +244,7 @@ func hysteria2Link(
|
||||
if downmbps, ok := inbound["down_mbps"].(string); ok {
|
||||
params["down_mbps"] = downmbps
|
||||
}
|
||||
fmt.Printf("%+v\n", addr["tls"])
|
||||
if tls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||
fmt.Printf("%+v\n", tls)
|
||||
if sni, ok := tls["server_name"].(string); ok {
|
||||
params["sni"] = sni
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<template>
|
||||
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||
<v-card class="rounded-lg">
|
||||
<v-card class="rounded-lg" :loading="loading">
|
||||
<v-card-title>
|
||||
{{ $t('actions.' + title) + " " + $t('objects.client') }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-skeleton-loader
|
||||
class="mx-auto border"
|
||||
width="95%"
|
||||
type="card, text, divider, list-item-two-line"
|
||||
v-if="loading"
|
||||
></v-skeleton-loader>
|
||||
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||
<v-container style="padding: 0;">
|
||||
<v-container style="padding: 0;" :hidden="loading">
|
||||
<v-tabs
|
||||
v-model="tab"
|
||||
align-tabs="center"
|
||||
@@ -178,12 +184,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { createClient, randomConfigs, updateConfigs, Link } from '@/types/clients'
|
||||
import { createClient, randomConfigs, updateConfigs, Link, Client } from '@/types/clients'
|
||||
import DatePick from '@/components/DateTime.vue'
|
||||
import { HumanReadable } from '@/plugins/utils'
|
||||
import Data from '@/store/modules/data';
|
||||
|
||||
export default {
|
||||
props: ['visible', 'data', 'id', 'inboundTags', 'groups'],
|
||||
props: ['visible', 'id', 'inboundTags', 'groups'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
@@ -198,21 +205,23 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
async updateData() {
|
||||
if (this.$props.id > 0) {
|
||||
const newData = JSON.parse(this.$props.data)
|
||||
this.loading = true
|
||||
const newData = await Data().loadClients(this.$props.id)
|
||||
this.client = createClient(newData)
|
||||
this.title = "edit"
|
||||
this.clientConfig = this.client.config
|
||||
this.loading = false
|
||||
}
|
||||
else {
|
||||
this.client = createClient()
|
||||
this.title = "add"
|
||||
this.clientConfig = randomConfigs('client')
|
||||
}
|
||||
this.links = this.client.links.filter(l => l.type == 'local')
|
||||
this.extLinks = this.client.links.filter(l => l.type == 'external')
|
||||
this.subLinks = this.client.links.filter(l => l.type == 'sub')
|
||||
this.links = this.client.links?.filter(l => l.type == 'local')?? []
|
||||
this.extLinks = this.client.links?.filter(l => l.type == 'external')?? []
|
||||
this.subLinks = this.client.links?.filter(l => l.type == 'sub')?? []
|
||||
this.tab = "t1"
|
||||
},
|
||||
closeModal() {
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<pre dir="ltr">{{ bulkData }}</pre>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { i18n } from '@/locales'
|
||||
import { Inbound } from '@/types/inbounds'
|
||||
import { Outbound } from '@/types/outbounds'
|
||||
import { Endpoint } from '@/types/endpoints'
|
||||
import { Client } from '@/types/clients'
|
||||
|
||||
const Data = defineStore('Data', {
|
||||
state: () => ({
|
||||
@@ -13,10 +14,10 @@ const Data = defineStore('Data', {
|
||||
subURI: "",
|
||||
onlines: {inbound: <string[]>[], outbound: <string[]>[], user: <string[]>[]},
|
||||
config: <any>{},
|
||||
inbounds: <Inbound[]>[],
|
||||
inbounds: <any[]>[],
|
||||
outbounds: <Outbound[]>[],
|
||||
endpoints: <Endpoint[]>[],
|
||||
clients: [],
|
||||
clients: <any>[],
|
||||
tlsConfigs: <any[]>[],
|
||||
}),
|
||||
actions: {
|
||||
@@ -55,7 +56,14 @@ const Data = defineStore('Data', {
|
||||
}
|
||||
return <Inbound[]>[]
|
||||
},
|
||||
async save (object: string, action: string, data: any): Promise<boolean> {
|
||||
async loadClients(id: number): Promise<Client> {
|
||||
const options = id > 0 ? {id: id} : {}
|
||||
const msg = await HttpUtils.get('api/clients', options)
|
||||
if(msg.success) {
|
||||
return <Client>msg.obj.clients[0]??{}
|
||||
}
|
||||
return <Client>{}
|
||||
},
|
||||
let postData = {
|
||||
object: object,
|
||||
action: action,
|
||||
|
||||
@@ -10,9 +10,9 @@ export interface Client {
|
||||
id?: number
|
||||
enable: boolean
|
||||
name: string
|
||||
config: Config
|
||||
config?: Config
|
||||
inbounds: number[]
|
||||
links: Link[]
|
||||
links?: Link[]
|
||||
volume: number
|
||||
expiry: number
|
||||
up: number
|
||||
|
||||
@@ -52,32 +52,6 @@ interface InboundBasics extends Listen {
|
||||
out_json?: any
|
||||
}
|
||||
|
||||
interface UsernamePass {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
interface NamePass {
|
||||
name: string
|
||||
password: string
|
||||
}
|
||||
interface NameUUID {
|
||||
name: string
|
||||
uuid: string
|
||||
}
|
||||
interface NameAuth {
|
||||
name: string
|
||||
auth_str: string
|
||||
}
|
||||
interface VmessUser extends NameUUID {
|
||||
alterId: number
|
||||
}
|
||||
interface VlessUser extends NameUUID {
|
||||
flow: string
|
||||
}
|
||||
interface TuicUser extends NameUUID {
|
||||
password?: string
|
||||
}
|
||||
|
||||
interface ShadowTLSHandShake extends Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
@@ -88,30 +62,21 @@ export interface Direct extends InboundBasics {
|
||||
override_address?: string
|
||||
override_port?: number
|
||||
}
|
||||
export interface Mixed extends InboundBasics {
|
||||
users?: UsernamePass[]
|
||||
}
|
||||
export interface SOCKS extends InboundBasics {
|
||||
users?: UsernamePass[]
|
||||
}
|
||||
export interface HTTP extends InboundBasics {
|
||||
users?: UsernamePass[]
|
||||
}
|
||||
export interface Mixed extends InboundBasics {}
|
||||
export interface SOCKS extends InboundBasics {}
|
||||
export interface HTTP extends InboundBasics {}
|
||||
export interface Shadowsocks extends InboundBasics {
|
||||
method: string
|
||||
password: string
|
||||
network?: "udp" | "tcp"
|
||||
users?: NamePass[]
|
||||
multiplex?: iMultiplex
|
||||
}
|
||||
export interface VMess extends InboundBasics {
|
||||
users: VmessUser[]
|
||||
tls: iTls
|
||||
multiplex?: iMultiplex
|
||||
transport?: Transport
|
||||
}
|
||||
export interface Trojan extends InboundBasics {
|
||||
users: NamePass[]
|
||||
tls: iTls
|
||||
fallback?: {
|
||||
server: string
|
||||
@@ -121,14 +86,12 @@ export interface Trojan extends InboundBasics {
|
||||
transport?: Transport
|
||||
}
|
||||
export interface Naive extends InboundBasics {
|
||||
users: UsernamePass[]
|
||||
tls: iTls,
|
||||
}
|
||||
export interface Hysteria extends InboundBasics {
|
||||
up_mbps: number
|
||||
down_mbps: number
|
||||
obfs?: string
|
||||
users: NameAuth[]
|
||||
recv_window_conn?: number
|
||||
recv_window_client?: number
|
||||
max_conn_client?: number
|
||||
@@ -137,7 +100,6 @@ export interface Hysteria extends InboundBasics {
|
||||
export interface ShadowTLS extends InboundBasics {
|
||||
version: 1|2|3
|
||||
password?: string
|
||||
users?: NamePass[]
|
||||
handshake: ShadowTLSHandShake
|
||||
handshake_for_server_name?: {
|
||||
[server_name: string]: ShadowTLSHandShake
|
||||
@@ -145,12 +107,10 @@ export interface ShadowTLS extends InboundBasics {
|
||||
strict_mode?: boolean
|
||||
}
|
||||
export interface VLESS extends InboundBasics {
|
||||
users: VlessUser[]
|
||||
multiplex?: iMultiplex
|
||||
transport?: Transport
|
||||
}
|
||||
export interface TUIC extends InboundBasics {
|
||||
users: TuicUser[]
|
||||
congestion_control: ""|"cubic"|"new_reno"|"bbr"
|
||||
auth_timeout?: string
|
||||
zero_rtt_handshake?: boolean
|
||||
@@ -163,7 +123,6 @@ export interface Hysteria2 extends InboundBasics {
|
||||
type?: "salamander"
|
||||
password: string
|
||||
}
|
||||
users: NamePass[]
|
||||
ignore_client_bandwidth?: boolean
|
||||
masquerade?: string | {
|
||||
type: string
|
||||
@@ -245,7 +204,7 @@ type userEnabledTypes = {
|
||||
vless: VLESS
|
||||
}
|
||||
|
||||
export const inboundWithUsers = ['mixed', 'socks:', 'http', 'shadowsocks', 'vmess', 'trojan', 'naive', 'hysteria', 'shadowtls', 'tuic', 'hysteria2', 'vless']
|
||||
export const inboundWithUsers = ['mixed', 'socks', 'http', 'shadowsocks', 'vmess', 'trojan', 'naive', 'hysteria', 'shadowtls', 'tuic', 'hysteria2', 'vless']
|
||||
|
||||
// Create union type from userEnabledTypes
|
||||
type InboundWithUser = userEnabledTypes[keyof userEnabledTypes]
|
||||
@@ -257,14 +216,14 @@ const defaultValues: Record<InType, Inbound> = {
|
||||
socks: <SOCKS>{ type: InTypes.SOCKS },
|
||||
http: <HTTP>{ type: InTypes.HTTP, tls_id: 0 },
|
||||
shadowsocks: <Shadowsocks>{ type: InTypes.Shadowsocks, method: 'none', multiplex: {} },
|
||||
vmess: <VMess>{ type: InTypes.VMess, users: <VmessUser[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||
trojan: <Trojan>{ type: InTypes.Trojan, users: <NamePass[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||
naive: <Naive>{ type: InTypes.Naive, users: <UsernamePass[]>[], tls_id: 0 },
|
||||
hysteria: <Hysteria>{ type: InTypes.Hysteria, users: <NameAuth[]>[], up_mbps: 100, down_mbps: 100, tls_id: 0 },
|
||||
shadowtls: <ShadowTLS>{ type: InTypes.ShadowTLS, version: 3, users: <NamePass[]>[], handshake: {}, handshake_for_server_name: {} },
|
||||
tuic: <TUIC>{ type: InTypes.TUIC, users: <TuicUser[]>[], congestion_control: "cubic", tls_id: 0 },
|
||||
hysteria2: <Hysteria2>{ type: InTypes.Hysteria2, users: <NamePass[]>[], tls_id: 0 },
|
||||
vless: <VLESS>{ type: InTypes.VLESS, users: <VlessUser[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||
vmess: <VMess>{ type: InTypes.VMess, tls_id: 0, multiplex: {}, transport: {} },
|
||||
trojan: <Trojan>{ type: InTypes.Trojan, tls_id: 0, multiplex: {}, transport: {} },
|
||||
naive: <Naive>{ type: InTypes.Naive, tls_id: 0 },
|
||||
hysteria: <Hysteria>{ type: InTypes.Hysteria, up_mbps: 100, down_mbps: 100, tls_id: 0 },
|
||||
shadowtls: <ShadowTLS>{ type: InTypes.ShadowTLS, version: 3, handshake: {}, handshake_for_server_name: {} },
|
||||
tuic: <TUIC>{ type: InTypes.TUIC, congestion_control: "cubic", tls_id: 0 },
|
||||
hysteria2: <Hysteria2>{ type: InTypes.Hysteria2, tls_id: 0 },
|
||||
vless: <VLESS>{ type: InTypes.VLESS, tls_id: 0, multiplex: {}, transport: {} },
|
||||
tun: <Tun>{ type: InTypes.Tun, mtu: 9000, stack: 'system', udp_timeout: '5m', auto_route: false },
|
||||
redirect: <Redirect>{ type: InTypes.Redirect },
|
||||
tproxy: <TProxy>{ type: InTypes.TProxy },
|
||||
|
||||
@@ -264,10 +264,6 @@ const outboundTags = computed((): string[] => {
|
||||
return [...Data().outbounds?.map((o:any) => o.tag), ...Data().endpoints?.map((e:any) => e.tag)]
|
||||
})
|
||||
|
||||
const clientNames = computed((): string[] => {
|
||||
return Data().clients.map((c:any) => c.name)
|
||||
})
|
||||
|
||||
const levels = ["trace", "debug", "info", "warn", "error", "fatal", "panic"]
|
||||
|
||||
const dnsServersTags = computed((): string[] => {
|
||||
|
||||
+13
-156
@@ -4,7 +4,6 @@
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:id="modal.id"
|
||||
:data="modal.data"
|
||||
:groups="groups"
|
||||
:inboundTags="inboundTags"
|
||||
@close="closeModal"
|
||||
@@ -116,129 +115,8 @@
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<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">
|
||||
<v-row>
|
||||
<v-col class="v-card-subtitle">
|
||||
{{ group.length>0 ? group : $t('none') }}
|
||||
<v-badge :content="(filterSettings.enabled ? filterSettings.filteredClients : clients).filter(c => c.group == group).length" inline color="info" />
|
||||
<v-icon
|
||||
:icon="openedGroups.includes(group) ? 'mdi-arrow-collapse-up' : 'mdi-arrow-collapse-down'"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="toggleGroupOpen(group)"
|
||||
></v-icon>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="openedGroups.includes(group)">
|
||||
<template v-for="item in (filterSettings.enabled ? filterSettings.filteredClients : clients).filter(c => c.group == group)" :key="item.id">
|
||||
<v-col cols="12" sm="4" md="3" lg="2">
|
||||
<v-card rounded="xl" elevation="5" min-width="200">
|
||||
<v-card-title>
|
||||
<v-row>
|
||||
<v-col>{{ item.name }}</v-col>
|
||||
<v-spacer></v-spacer>
|
||||
<v-col cols="auto">
|
||||
<v-switch color="primary"
|
||||
v-model="item.enable"
|
||||
hideDetails density="compact" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-title>
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ item.desc }}</v-col>
|
||||
</v-row>
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
||||
<v-col>
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.inbounds != ''">
|
||||
<span v-for="i in item.inbounds">{{ inbounds.find(inb => inb.id == i)?.tag }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ item.inbounds.length }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('stats.volume') }}</v-col>
|
||||
<v-col>
|
||||
{{ item.volume == 0 ? $t('unlimited') : HumanReadable.sizeFormat(item.volume) }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('date.expiry') }}</v-col>
|
||||
<v-col>
|
||||
{{ HumanReadable.remainedDays(item.expiry) }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('stats.usage') }}</v-col>
|
||||
<v-col>
|
||||
<v-tooltip activator="parent" location="bottom">
|
||||
{{ $t('stats.upload') }}:{{ HumanReadable.sizeFormat(item.up) }}<br />
|
||||
{{ $t('stats.download') }}:{{ HumanReadable.sizeFormat(item.down) }}<br />
|
||||
<template v-if="item.volume>0">
|
||||
{{ $t('remained') }}: {{ HumanReadable.sizeFormat(item.volume - (item.up + item.down)) }}
|
||||
</template>
|
||||
</v-tooltip>
|
||||
{{ HumanReadable.sizeFormat(item.up + item.down) }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('online') }}</v-col>
|
||||
<v-col>
|
||||
<template v-if="isOnline(item.name).value">
|
||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-account-edit" @click="showModal(item.id)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn style="margin-inline-start:0;" icon="mdi-account-minus" color="warning" @click="delOverlay[clients.findIndex(c => c.id == item.id)] = true">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-overlay
|
||||
v-model="delOverlay[clients.findIndex(c => c.id == item.id)]"
|
||||
contained
|
||||
class="align-center justify-center"
|
||||
>
|
||||
<v-card :title="$t('actions.del')" rounded="lg">
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delClient(item.id)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delOverlay[clients.findIndex(c => c.id == item.id)] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
<v-btn icon="mdi-qrcode" @click="showQrCode(item.id)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" text="QR-Code"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.name)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
</template>
|
||||
<v-row v-else>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
@@ -246,21 +124,19 @@
|
||||
:hide-default-footer="filterSettings.enabled ? filterSettings.filteredClients.length<=10 : clients.length<=10"
|
||||
hide-no-data
|
||||
fixed-header
|
||||
:group-by="groupBy"
|
||||
item-value="name"
|
||||
:mobile="smAndDown"
|
||||
mobile-breakpoint="sm"
|
||||
width="100%"
|
||||
class="elevation-3 rounded"
|
||||
>
|
||||
<template v-slot:group-header="{ item, columns, toggleGroup, isGroupOpen }">
|
||||
<tr>
|
||||
<td :colspan="columns.length" @click="toggleGroup(item)" style="min-height: fit-content; text-align: center;">
|
||||
<v-icon :icon="isGroupOpen(item) ? '$expand' : '$next'"></v-icon>
|
||||
{{ item.value.length>0 ? item.value : $t('none') }}
|
||||
<v-badge :content="(filterSettings.enabled ? filterSettings.filteredClients : clients).filter(c => c.group == item.value).length" inline color="success" />
|
||||
</td>
|
||||
</tr>
|
||||
<template v-slot:item.inbounds="{ item }">
|
||||
<span>
|
||||
<v-tooltip activator="parent" dir="ltr" location="start" v-if="item.inbounds != ''">
|
||||
<span v-for="i in item.inbounds">{{ inbounds.find(inb => inb.id == i)?.tag }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ item.inbounds.length }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot:item.volume="{ item }">
|
||||
<div class="text-start">
|
||||
@@ -363,13 +239,13 @@ const isOnline = (cname: string) => computed(() => {
|
||||
return Data().onlines?.user ? Data().onlines.user.includes(cname) : false
|
||||
})
|
||||
|
||||
const inbounds = computed((): Inbound[] => {
|
||||
return <Inbound[]> Data().inbounds?? []
|
||||
const inbounds = computed((): any[] => {
|
||||
return Data().inbounds?? []
|
||||
})
|
||||
|
||||
const inboundTags = computed((): any[] => {
|
||||
if (!inbounds.value) return []
|
||||
return inbounds.value?.filter(i => i.tag != "" && inboundWithUsers.includes(i.type)).map(i => { return { title: i.tag, value: i.id } })
|
||||
return inbounds.value?.filter(i => i.tag != "" && i.users).map(i => { return { title: i.tag, value: i.id } })
|
||||
})
|
||||
|
||||
const groups = computed((): string[] => {
|
||||
@@ -387,12 +263,6 @@ const filterSettings = ref({
|
||||
text: '',
|
||||
filteredClients: <any[]>[]
|
||||
})
|
||||
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: '' },
|
||||
@@ -403,30 +273,24 @@ const filterItems = [
|
||||
|
||||
const headers = [
|
||||
{ title: i18n.global.t('client.name'), key: 'name' },
|
||||
{ title: i18n.global.t('client.desc'), key: 'desc', sortable: false },
|
||||
{ title: i18n.global.t('client.group'), key: 'group' },
|
||||
{ title: i18n.global.t('pages.inbounds'), key: 'inbounds', width: 10 },
|
||||
{ title: i18n.global.t('actions.action'), key: 'actions', sortable: false},
|
||||
{ title: i18n.global.t('stats.volume'), key: 'volume' },
|
||||
{ title: i18n.global.t('date.expiry'), key: 'expiry' },
|
||||
{ title: i18n.global.t('online'), key: 'online' },
|
||||
{ key: 'data-table-group', width: 0 },
|
||||
]
|
||||
const groupBy = [
|
||||
{
|
||||
key: 'group'
|
||||
}
|
||||
]
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
id: 0,
|
||||
data: "",
|
||||
})
|
||||
|
||||
const delOverlay = ref(new Array<boolean>(clients.value.length).fill(false))
|
||||
|
||||
const showModal = async (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.data = id == 0 ? '' : JSON.stringify(clients.value.findLast(o => o.id == id))
|
||||
modal.value.visible = true
|
||||
}
|
||||
const closeModal = () => {
|
||||
@@ -481,13 +345,6 @@ const closeStats = () => {
|
||||
stats.value.visible = false
|
||||
}
|
||||
|
||||
var openedGroups = ref(<string[]>[""])
|
||||
|
||||
const toggleGroupOpen = (g: string) => {
|
||||
const index = openedGroups.value.findIndex(og => og == g)
|
||||
index == -1 ? openedGroups.value.push(g) : openedGroups.value.splice(index,1)
|
||||
}
|
||||
|
||||
const doFilter = () => {
|
||||
let filteredClients = clients.value.slice()
|
||||
if (filterSettings.value.group != '-') {
|
||||
|
||||
@@ -51,11 +51,11 @@
|
||||
<v-row>
|
||||
<v-col>{{ $t('pages.clients') }}</v-col>
|
||||
<v-col>
|
||||
<template v-if="inboundWithUsers.includes(item.type)">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="findInboundUsers(item).length > 0">
|
||||
<span v-for="u in findInboundUsers(item)">{{ u }}<br /></span>
|
||||
<template v-if="item.users">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.users.length > 0">
|
||||
<span v-for="u in item.users">{{ u }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ findInboundUsers(item).length }}
|
||||
{{ item.users.length }}
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</v-col>
|
||||
@@ -110,8 +110,7 @@ import InboundVue from '@/layouts/modals/Inbound.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Config } from '@/types/config'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Inbound, inboundWithUsers } from '@/types/inbounds'
|
||||
import { Client } from '@/types/clients'
|
||||
import { Inbound } from '@/types/inbounds'
|
||||
import { i18n } from '@/locales'
|
||||
import { push } from 'notivue'
|
||||
|
||||
@@ -135,10 +134,6 @@ const outTags = computed((): string[] => {
|
||||
return appConfig.value.outbounds?.map(i => i.tag)
|
||||
})
|
||||
|
||||
const clients = computed((): Client[] => {
|
||||
return <Client[]> Data().clients
|
||||
})
|
||||
|
||||
const onlines = computed(() => {
|
||||
return Data().onlines.inbound?? []
|
||||
})
|
||||
@@ -157,7 +152,7 @@ const showModal = (id: number) => {
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = async (data:Inbound) => {
|
||||
const saveModal = async (data:Inbound, initUsers?: number[]) => {
|
||||
// Check duplicate tag
|
||||
const oldInbound = modal.value.id > 0 ? inbounds.value.findLast(i => i.id == modal.value.id) : null
|
||||
if (data.tag != oldInbound?.tag && inTags.value.includes(data.tag)) {
|
||||
@@ -168,7 +163,7 @@ const saveModal = async (data:Inbound) => {
|
||||
}
|
||||
|
||||
// save data
|
||||
const success = await Data().save("inbounds", modal.value.id == 0 ? "new" : "edit", data)
|
||||
const success = await Data().save("inbounds", modal.value.id == 0 ? "new" : "edit", data, initUsers)
|
||||
if (success) modal.value.visible = false
|
||||
}
|
||||
|
||||
@@ -180,10 +175,6 @@ const delInbound = async (id: number) => {
|
||||
if (success) delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const findInboundUsers = (i: Inbound): string[] => {
|
||||
return clients.value.filter(c => c.inbounds.includes(i.id)).map(c => c.name)
|
||||
}
|
||||
|
||||
const stats = ref({
|
||||
visible: false,
|
||||
resource: "inbound",
|
||||
|
||||
@@ -89,7 +89,6 @@ import TlsVue from '@/layouts/modals/Tls.vue'
|
||||
import Data from '@/store/modules/data'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Inbound } from '@/types/inbounds'
|
||||
import { Client } from '@/types/clients'
|
||||
import { tls } from '@/types/tls'
|
||||
|
||||
const tlsConfigs = computed((): any[] => {
|
||||
@@ -104,10 +103,6 @@ const tlsInbounds = (id: number): string[] => {
|
||||
return inbounds.value.filter(i => i.tls_id == id).map(i => i.tag)
|
||||
}
|
||||
|
||||
const clients = computed((): any[] => {
|
||||
return <Client[]>Data().clients
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
id: 0,
|
||||
|
||||
Reference in New Issue
Block a user