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