@@ -70,19 +70,14 @@
-
-
-
-
-
-
+ >
@@ -183,13 +178,12 @@
\ No newline at end of file
diff --git a/frontend/src/layouts/modals/Inbound.vue b/frontend/src/layouts/modals/Inbound.vue
index cfd8cdf..e085d8d 100644
--- a/frontend/src/layouts/modals/Inbound.vue
+++ b/frontend/src/layouts/modals/Inbound.vue
@@ -1,12 +1,18 @@
-
-
+
+
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
+
-
+
-
+
@@ -45,21 +51,20 @@
-
+
-
-
-
+
+
{{ $t('in.multiDomain') }}
-
- {{ $t('in.addr') }} #{{ (index+1) }}
+
+ {{ $t('in.addr') }} #{{ (index+1) }}
-
+
@@ -79,6 +84,7 @@
color="blue-darken-1"
variant="text"
:loading="loading"
+ :disabled="!validate"
@click="saveChanges"
>
{{ $t('actions.save') }}
@@ -89,8 +95,7 @@
\ No newline at end of file
diff --git a/frontend/src/views/Clients.vue b/frontend/src/views/Clients.vue
index f2542fc..ad80205 100644
--- a/frontend/src/views/Clients.vue
+++ b/frontend/src/views/Clients.vue
@@ -3,10 +3,9 @@
- {{ $t('actions.add') }}
+ {{ $t('actions.add') }}
@@ -48,7 +47,7 @@
-
+
@@ -147,7 +146,6 @@
@@ -160,28 +158,28 @@
{{ $t('pages.inbounds') }}
-
+
- {{ i }}
+ {{ inbounds.find(inb => inb.id == i)?.tag }}
{{ item.inbounds.length }}
{{ $t('stats.volume') }}
-
+
{{ item.volume == 0 ? $t('unlimited') : HumanReadable.sizeFormat(item.volume) }}
{{ $t('date.expiry') }}
-
- {{ item.expiry == 0 ? $t('unlimited') : HumanReadable.remainedDays(item.expiry)?? $t('date.expired') }}
+
+ {{ HumanReadable.remainedDays(item.expiry) }}
{{ $t('stats.usage') }}
-
+
{{ $t('stats.upload') }}:{{ HumanReadable.sizeFormat(item.up) }}
{{ $t('stats.download') }}:{{ HumanReadable.sizeFormat(item.down) }}
@@ -194,7 +192,7 @@
{{ $t('online') }}
-
+
{{ $t('online') }}
@@ -230,7 +228,7 @@
-
+
@@ -277,7 +275,7 @@
{{ item.expiry == 0 ? $t('unlimited') : HumanReadable.remainedDays(item.expiry)?? $t('date.expired') }}
+ >{{ HumanReadable.remainedDays(item.expiry) }}
@@ -324,7 +322,7 @@
>
mdi-qrcode
-
+
@@ -347,11 +345,9 @@ 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'
+import { Client } from '@/types/clients'
import { computed, ref } from 'vue'
-import { Config, V2rayApiStats } from '@/types/config'
-import { InTypes, Inbound,InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
-import { Link, LinkUtil } from '@/plugins/link'
+import { Inbound, inboundWithUsers } from '@/types/inbounds'
import { HumanReadable } from '@/plugins/utils'
import { i18n } from '@/locales'
import { push } from 'notivue'
@@ -367,21 +363,13 @@ const isOnline = (cname: string) => computed(() => {
return Data().onlines?.user ? Data().onlines.user.includes(cname) : false
})
-const appConfig = computed((): Config => {
- return
Data().config
-})
-
-const v2rayStats = computed((): V2rayApiStats => {
- return appConfig.value.experimental.v2ray_api.stats
-})
-
const inbounds = computed((): Inbound[] => {
- return appConfig.value?.inbounds
+ return Data().inbounds?? []
})
-const inboundTags = computed((): string[] => {
+const inboundTags = computed((): any[] => {
if (!inbounds.value) return []
- return inbounds.value?.filter(i => i.tag != "" && Object.hasOwn(i,'users')).map(i => i.tag)
+ return inbounds.value?.filter(i => i.tag != "" && inboundWithUsers.includes(i.type)).map(i => { return { title: i.tag, value: i.id } })
})
const groups = computed((): string[] => {
@@ -430,128 +418,39 @@ const groupBy = [
const modal = ref({
visible: false,
- index: -1,
+ id: 0,
data: "",
- stats: false,
})
const delOverlay = ref(new Array(clients.value.length).fill(false))
-const showModal = (id: number) => {
- const index = id == -1 ? -1 : clients.value.findIndex(c => c.id == id)
- modal.value.index = index
- modal.value.data = index == -1 ? '' : JSON.stringify(clients.value[index])
- modal.value.stats = index == -1 ? false : v2rayStats.value.users.includes(clients.value[index].name)
+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 = () => {
modal.value.visible = false
}
-const saveModal = (data:any, stats:boolean) => {
+const saveModal = async (data:any) => {
// Check duplicate name
- const oldName = modal.value.index != -1 ? clients.value[modal.value.index].name : null
+ const oldName = modal.value.id > 0 ? clients.value.findLast(i => i.id == modal.value.id)?.name : null
if (data.name != oldName && clients.value.findIndex(c => c.name == data.name) != -1) {
push.error({
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('client.name')
})
return
}
- if(modal.value.index == -1) {
- clients.value.push(data)
- } else {
- clients.value[modal.value.index] = data
- }
- // Rebuild affected inbounds
- buildInboundsUsers(data.inbounds)
-
- // Rebuild links
- data.links = updateLinks(data)
-
- // Set Client Stats
- const sIndex = v2rayStats.value.users.findIndex(i => i == data.name) // Find if new user exists
-
- if (oldName != data.name) {
- v2rayStats.value.users = v2rayStats.value.users.filter(item => item != oldName)
- }
-
- if (stats) {
- // Add if dos not exist
- if (data.name.length>0 && sIndex == -1) v2rayStats.value.users.push(data.name)
- } else {
- // Delete if exists
- if (sIndex != -1) v2rayStats.value.users.splice(sIndex,1)
- }
-
- modal.value.visible = false
+ // save data
+ const success = await Data().save("clients", modal.value.id == 0 ? "new" : "edit", data)
+ if (success) modal.value.visible = false
}
-const buildInboundsUsers = (inboundTags:string[]) => {
- inboundTags.forEach(tag => {
- const inbound_index = inbounds.value.findIndex(i => i.tag == tag)
- if (inbound_index != -1){
- const users = []
- const newInbound = inbounds.value[inbound_index]
- const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(tag))
- inboundClients.forEach(c => {
- // Remove flow in non tls VLESS
- if (newInbound.type == InTypes.VLESS) {
- const vlessInbound = newInbound
- if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow)
- }
- users.push(c.config[newInbound.type])
- })
- newInbound.users = users
- // Exceptions for Naive and ShadowTLSv3
- if (users.length == 0){
- if (newInbound.type == InTypes.Naive) {
- newInbound.users = [{}]
- } else {
- if (newInbound.type == InTypes.ShadowTLS){
- const ssTls = newInbound
- if (ssTls.version == 3) newInbound.users = [{}]
- }
- }
- }
-
- inbounds.value[inbound_index] = newInbound
- }
- })
-}
-const updateLinks = (c:Client):Link[] => {
- const clientInbounds = inbounds.value.filter(i => c.inbounds.includes(i.tag))
- const newLinks = []
- clientInbounds.forEach(i =>{
- const tlsConfig = Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag))
- const cData = Data().inData?.findLast((d:any) => d.tag == i.tag)
- const addrs = cData ? cData.addrs : []
- const uris = LinkUtil.linkGenerator(c,i, tlsConfig?.client?? {}, addrs)
- if (uris.length>0){
- uris.forEach(uri => {
- newLinks.push({ type: 'local', remark: i.tag, uri: uri })
- })
- }
- })
- let links = c.links && c.links.length>0? c.links : []
- links = [...newLinks, ...links.filter(l => l.type != 'local')]
-
- return links
-}
-const delClient = (id: number) => {
- const clientIndex = clients.value.findIndex(c => c.id === id)
- const oldData = createClient(clients.value[clientIndex])
-
- // Delete stats if exists and will be orphaned
- const tagCounts = clients.value.filter(i => i.name == oldData.name).length
- const sIndex = v2rayStats.value.users.findIndex(i => i == oldData.name)
- if (tagCounts == 1 && sIndex != -1){
- v2rayStats.value.users.splice(sIndex,1)
- }
-
- clients.value.splice(clientIndex,1)
- buildInboundsUsers(oldData.inbounds)
- if (id>0) Data().delClient(id)
- delOverlay.value[clientIndex] = false
+const delClient = async (id: number) => {
+ const index = clients.value.findIndex(c => c.id === id)
+ const success = await Data().save("clients", "del", id)
+ if (success) delOverlay.value[index] = false
}
const qrcode = ref({
@@ -603,7 +502,7 @@ const doFilter = () => {
filteredClients = filteredClients.filter(c => c.enable == false)
break
case "expired":
- filteredClients = filteredClients.filter(c => HumanReadable.remainedDays(c.expiry) == null)
+ filteredClients = filteredClients.filter(c => c.expiry > 0 && c.expiry < (Date.now()/1000) )
break
case "online":
filteredClients = filteredClients.filter(c => Data().onlines?.user?.includes(c.name))
@@ -636,14 +535,20 @@ 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()
+const saveBulk = async (bulkClients: Client[]) => {
+ // Check duplicate name
+ const oldNames = new Set(clients.value.map(c => c.name))
+ const newNames = new Set(bulkClients.map(c => c.name))
+ const allNames = new Set([...clients.value.map(c => c.name), ...bulkClients.map(c => c.name)])
+ if (newNames.size != bulkClients.length || oldNames.size + newNames.size != allNames.size) {
+ push.error({
+ message: i18n.global.t('error.dplData') + ": " + i18n.global.t('client.name')
+ })
+ return
+ }
+
+ // save data
+ const success = await Data().save("clients", "addbulk", bulkClients)
+ if (success) closeBulk()
}
\ No newline at end of file
diff --git a/frontend/src/views/Endpoints.vue b/frontend/src/views/Endpoints.vue
new file mode 100644
index 0000000..6e2c40c
--- /dev/null
+++ b/frontend/src/views/Endpoints.vue
@@ -0,0 +1,166 @@
+
+
+
+
+
+ {{ $t('actions.add') }}
+
+
+
+
+
+
+
+ {{ item.type }}
+
+
+
+
+ {{ $t('in.addr') }}
+
+ {{ item.address?.length>0 ? item.address[0] : '-' }}
+
+
+
+ {{ $t('in.port') }}
+
+ {{ item.listen_port?? '-' }}
+
+
+
+ {{ $t('types.wg.peers') }}
+
+ {{ item.peers.length?? '-' }}
+
+
+
+ {{ $t('online') }}
+
+
+ {{ $t('online') }}
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('confirm') }}
+
+ {{ $t('yes') }}
+ {{ $t('no') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/Inbounds.vue b/frontend/src/views/Inbounds.vue
index 17c647d..eec2206 100644
--- a/frontend/src/views/Inbounds.vue
+++ b/frontend/src/views/Inbounds.vue
@@ -2,10 +2,7 @@
- {{ $t('actions.add') }}
+ {{ $t('actions.add') }}
@@ -35,35 +32,38 @@
{{ $t('in.addr') }}
-
+
{{ item.listen }}
{{ $t('in.port') }}
-
+
{{ item.listen_port }}
{{ $t('objects.tls') }}
-
- {{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }}
+
+ {{ item.tls_id > 0 ? $t('enable') : $t('disable') }}
{{ $t('pages.clients') }}
-
-
- {{ u }}
-
- {{ Array.isArray(item.users) ? item.users.length : '-' }}
+
+
+
+ {{ u }}
+
+ {{ findInboundUsers(item).length }}
+
+ -
{{ $t('online') }}
-
-
+
+
{{ $t('online') }}
-
@@ -72,7 +72,7 @@
-
+
@@ -89,12 +89,12 @@
{{ $t('confirm') }}
- {{ $t('yes') }}
+ {{ $t('yes') }}
{{ $t('no') }}
-
+
@@ -108,31 +108,25 @@
import Data from '@/store/modules/data'
import InboundVue from '@/layouts/modals/Inbound.vue'
import Stats from '@/layouts/modals/Stats.vue'
-import { Config, V2rayApiStats } from '@/types/config'
+import { Config } from '@/types/config'
import { computed, ref } from 'vue'
-import { InTypes, Inbound, InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
+import { Inbound, inboundWithUsers } from '@/types/inbounds'
import { Client } from '@/types/clients'
-import { Link, LinkUtil } from '@/plugins/link'
import { i18n } from '@/locales'
import { push } from 'notivue'
-import { fillData } from '@/plugins/outJson'
const appConfig = computed((): Config => {
return Data().config
})
const inbounds = computed((): Inbound[] => {
- return appConfig.value.inbounds
+ return Data().inbounds
})
const tlsConfigs = computed((): any[] => {
return Data().tlsConfigs
})
-const inData = computed((): any[] => {
- return Data().inData
-})
-
const inTags = computed((): string[] => {
return inbounds.value?.map(i => i.tag)
})
@@ -149,216 +143,45 @@ const onlines = computed(() => {
return Data().onlines.inbound ? inbounds.value.map(i => Data().onlines.inbound.includes(i.tag)) : []
})
-const v2rayStats = computed((): V2rayApiStats => {
- return appConfig.value.experimental?.v2ray_api.stats
-})
-
const modal = ref({
visible: false,
- index: -1,
- data: "",
- cData: "",
- stats: false,
+ id: 0,
})
let delOverlay = ref(new Array)
-const showModal = (index: number) => {
- modal.value.index = index
- if (index == -1){
- modal.value.data = ''
- modal.value.cData = ''
- modal.value.stats = false
- } else {
- modal.value.data = JSON.stringify(inbounds.value[index])
- modal.value.stats = v2rayStats.value.inbounds.includes(inbounds.value[index].tag)
- const inDataIndex = inData.value.findIndex(d => d.tag == inbounds.value[index].tag)
- modal.value.cData = inDataIndex == -1 ? '' : JSON.stringify(inData.value[inDataIndex])
- }
+const showModal = (id: number) => {
+ modal.value.id = id
modal.value.visible = true
}
const closeModal = () => {
modal.value.visible = false
}
-const saveModal = (data:Inbound, stats: boolean, tls_id: number, cData: any) => {
+const saveModal = async (data:Inbound) => {
// Check duplicate tag
- const oldTag = modal.value.index != -1 ? inbounds.value[modal.value.index].tag : null
- if (data.tag != oldTag && inTags.value.includes(data.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)) {
push.error({
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
})
return
}
- if (cData.id != -1) {
- cData.tag = data.tag
- fillData(cData.outJson, data,tls_id>0 ? tlsConfigs.value.findLast(t => t.id == tls_id).client : {})
- }
- // New or Edit
- if (modal.value.index == -1) {
- inbounds.value.push(data)
- if (stats && data.tag.length>0) {
- v2rayStats.value.inbounds.push(data.tag)
- }
- if (cData.id != -1){
- inData.value.push(cData)
- }
- } else {
- const oldTag = inbounds.value[modal.value.index].tag
- const sIndex = v2rayStats.value.inbounds.findIndex(i => i == data.tag) // Find if new tag exists
-
- // Update tls preset
- const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(oldTag))
- if (oldTlsConfigIndex != -1){
- tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != oldTag)
- }
-
- if (oldTag != data.tag) {
- v2rayStats.value.inbounds = v2rayStats.value.inbounds.filter(item => item != oldTag)
- changeClientInboundsTag(oldTag,data.tag)
- }
-
- if (stats) {
- // Add if dos not exist
- if (data.tag.length>0 && sIndex == -1) v2rayStats.value.inbounds.push(data.tag)
- } else {
- // Delete if exists
- if (sIndex != -1) v2rayStats.value.inbounds.splice(sIndex,1)
- }
-
- inbounds.value[modal.value.index] = data
- const inDataIndex = inData.value.findIndex(indata => indata.tag == oldTag)
- if (cData.id != -1) {
- if (inDataIndex == -1){
- inData.value.push(cData)
- } else {
- inData.value[inDataIndex] = cData
- }
- } else if (inDataIndex != -1) {
- Data().delInData(inData.value[inDataIndex].id)
- inData.value.splice(inDataIndex,1)
- }
- }
- // Update tls preset
- if (tls_id>0) {
- tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
- tlsConfigs.value.sort()
- }
-
- if (Object.hasOwn(data,'users')) {
- // Set users
- data = buildInboundsUsers(data)
- // Update links
- updateLinks(data)
- }
- modal.value.visible = false
+ // save data
+ const success = await Data().save("inbounds", modal.value.id == 0 ? "new" : "edit", data)
+ if (success) modal.value.visible = false
}
-const updateLinks = (i: any) => {
- if(i.users){
- const uClients = clients.value.filter(c => c.inbounds.includes(i.tag))
- uClients.forEach((u:Client) => {
- const clientInbounds = inbounds.value.filter(inb => u.inbounds.includes(inb.tag))
- const newLinks = []
- clientInbounds.forEach(i =>{
- const tlsClient = tlsConfigs?.value.findLast((t:any) => t.inbounds.includes(i.tag))?.client?? {}
- const cData = Data().inData?.findLast((d:any) => d.tag == i.tag)
- const addrs = cData ? cData.addrs : []
- const uris = LinkUtil.linkGenerator(u,i, tlsClient, addrs)
- if (uris.length>0){
- uris.forEach(uri => {
- newLinks.push({ type: 'local', remark: i.tag, uri: uri })
- })
- }
- })
- let links = u.links && u.links.length>0? u.links : []
- links = [...newLinks, ...links.filter(l => l.type != 'local')]
- u.links = links
- })
- }
+const delInbound = async (id: number) => {
+ const index = inbounds.value.findIndex(i => i.id == id)
+ const tag = inbounds.value[index].tag
+
+ const success = await Data().save("inbounds", "del", tag)
+ if (success) delOverlay.value[index] = false
}
-const delInbound = (index: number) => {
- const inb = inbounds.value[index]
- inbounds.value.splice(index,1)
- const tag = inb.tag
- if (Object.hasOwn(inb,'users')) {
- const inbU = inb
- if (inbU.users && inbU.users.length>0){
- inbU.users.forEach((u:any) => {
- const c_index = clients.value.findIndex(c => u.username? u.username == c.name : u.name == c.name)
- if (c_index != -1) {
- clients.value[c_index].inbounds = clients.value[c_index].inbounds.filter((x:string) => x!=tag)
- clients.value[c_index].links = clients.value[c_index].links.filter((x:any) => x.remark!=tag)
- }
- })
- }
- }
-
- // Delete binded tls if exists
- if (Object.hasOwn(inb,'tls')) {
- const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(inb.tag))
- if (oldTlsConfigIndex != -1){
- tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != inb.tag)
- }
- }
-
- // Delete stats if exists and will be orphaned
- const tagCounts = inbounds.value.filter(i => i.tag == inb.tag).length
- const sIndex = v2rayStats.value.inbounds.findIndex(i => i == inb.tag)
- if (tagCounts == 1 && sIndex != -1){
- v2rayStats.value.inbounds.splice(sIndex,1)
- }
- if (index < Data().oldData.config.inbounds.length){
- Data().delInbound(index)
- } else {
- // Delete new inbound's inData if exists
- const inDataIndex = Data().inData.findIndex((d:any) => d.tag == tag)
- if (inDataIndex != -1) Data().inData.splice(inDataIndex, 1)
- }
- delOverlay.value[index] = false
-}
-const buildInboundsUsers = (inbound:any):Inbound => {
- const users = []
- const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(inbound.tag))
- inboundClients.forEach(c => {
- // Remove flow in non tls VLESS
- if (inbound.type == InTypes.VLESS) {
- const vlessInbound = inbound
- if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow)
- }
- users.push(c.config[inbound.type])
- })
- inbound.users = users
-
- // Exceptions for Naive and ShadowTLSv3
- if (users.length == 0){
- if (inbound.type == InTypes.Naive){
- inbound.users = [{}]
- } else {
- if (inbound.type == InTypes.ShadowTLS){
- const ssTls = inbound
- if (ssTls.version == 3) inbound.users = [{}]
- }
- }
- }
-
- return inbound
-}
-const changeClientInboundsTag = (oldtag: string, newTag:string) => {
- clients.value.forEach((c, c_index) => {
- const inbound_index = c.inbounds.findIndex(i => i == oldtag)
- if (inbound_index != -1) {
- c.inbounds[inbound_index] = newTag
- clients.value[c_index].inbounds = c.inbounds
- }
- })
-}
-const findInbounsUsers = (inbound: InboundWithUser): string[] => {
- if (inbound.users === null || !Array.isArray(inbound.users) || inbound.users.length == 0) return []
-
- const users = inbound.users.map(user => "username" in user ? user.username : user.name)
- return users
+const findInboundUsers = (i: Inbound): string[] => {
+ return clients.value.filter(c => c.inbounds.includes(i.id)).map(c => c.name)
}
const stats = ref({
diff --git a/frontend/src/views/Outbounds.vue b/frontend/src/views/Outbounds.vue
index 7ace1d9..51115f3 100644
--- a/frontend/src/views/Outbounds.vue
+++ b/frontend/src/views/Outbounds.vue
@@ -3,7 +3,6 @@
v-model="modal.visible"
:visible="modal.visible"
:id="modal.id"
- :stats="modal.stats"
:data="modal.data"
:tags="outboundTags"
@close="closeModal"
@@ -18,7 +17,7 @@
/>
- {{ $t('actions.add') }}
+ {{ $t('actions.add') }}
@@ -32,26 +31,26 @@
{{ $t('in.addr') }}
-
+
{{ item.server?? '-' }}
{{ $t('in.port') }}
-
+
{{ item.server_port?? '-' }}
{{ $t('objects.tls') }}
-
+
{{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }}
{{ $t('online') }}
-
-
+
+
{{ $t('online') }}
-
@@ -60,7 +59,7 @@
-
+
@@ -77,12 +76,12 @@
{{ $t('confirm') }}
- {{ $t('yes') }}
+ {{ $t('yes') }}
{{ $t('no') }}
-
+
@@ -96,79 +95,56 @@
import Data from '@/store/modules/data'
import OutboundVue from '@/layouts/modals/Outbound.vue'
import Stats from '@/layouts/modals/Stats.vue'
-import { Config, V2rayApiStats } from '@/types/config';
import { Outbound } from '@/types/outbounds';
import { computed, ref } from 'vue'
import { i18n } from '@/locales';
import { push } from 'notivue';
-const appConfig = computed((): Config => {
- return Data().config
-})
-
const outbounds = computed((): Outbound[] => {
- return appConfig.value.outbounds
+ return Data().outbounds
})
-const outboundTags = computed((): string[] => {
+const outboundTags = computed((): any[] => {
return outbounds.value?.map((o:Outbound) => o.tag)
})
const onlines = computed(() => {
- return Data().onlines.outbound ? outbounds.value.map(i => Data().onlines.outbound.includes(i.tag)) : []
-})
-
-const v2rayStats = computed((): V2rayApiStats => {
- return appConfig.value.experimental?.v2ray_api.stats
+ return Data().onlines.outbound?? []
})
const modal = ref({
visible: false,
- id: -1,
+ id: 0,
data: "",
- stats: false,
})
let delOverlay = ref(new Array)
const showModal = (id: number) => {
modal.value.id = id
- modal.value.data = id == -1 ? '' : JSON.stringify(outbounds.value[id])
- modal.value.stats = id == -1 ? false : v2rayStats.value.outbounds.includes(outbounds.value[id].tag)
+ modal.value.data = id == 0 ? '' : JSON.stringify(outbounds.value.findLast(o => o.id == id))
modal.value.visible = true
}
const closeModal = () => {
modal.value.visible = false
}
-const saveModal = (data:Outbound, stats: boolean) => {
+const saveModal = async (data:Outbound) => {
// Check duplicate tag
- const oldTag = modal.value.id != -1 ? outbounds.value[modal.value.id].tag : null
+ const oldTag = modal.value.id > 0 ? outbounds.value.findLast(i => i.id == modal.value.id)?.tag : null
if (data.tag != oldTag && outboundTags.value.includes(data.tag)) {
push.error({
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
})
return
}
- // New or Edit
- if (modal.value.id == -1) {
- outbounds.value.push(data)
- if (stats && data.tag.length>0) {
- v2rayStats.value.outbounds.push(data.tag)
- }
- } else {
- const sIndex = v2rayStats.value.outbounds.findIndex(i => i == data.tag) // Find if new tag exists
- if (stats) {
- // Add if dos not exist
- if (data.tag.length>0 && sIndex == -1) v2rayStats.value.outbounds.push(data.tag)
- } else {
- // Delete if exists
- if (sIndex != -1) v2rayStats.value.outbounds.splice(sIndex,1)
- }
-
- outbounds.value[modal.value.id] = data
+ // save data
+ const success = await Data().save("outbounds", modal.value.id == 0 ? "new" : "edit", data)
+ if (!success) {
+ return
}
+
modal.value.visible = false
}
@@ -178,21 +154,10 @@ const stats = ref({
tag: "",
})
-const delOutbound = (index: number) => {
- const inb = outbounds.value[index]
- outbounds.value.splice(index,1)
- const tag = inb.tag
-
- // Delete stats if exists and will be orphaned
- const tagCounts = outbounds.value.filter(i => i.tag == inb.tag).length
- const sIndex = v2rayStats.value.outbounds.findIndex(i => i == inb.tag)
- if (tagCounts == 1 && sIndex != -1){
- v2rayStats.value.outbounds.splice(sIndex,1)
- }
- if (index < Data().oldData.config.outbounds.length){
- Data().delOutbound(index)
- }
- delOverlay.value[index] = false
+const delOutbound = async (tag: string) => {
+ const index = outbounds.value.findIndex(i => i.tag == tag)
+ const success = await Data().save("outbounds", "del", tag)
+ if (success) delOverlay.value[index] = false
}
const showStats = (tag: string) => {
@@ -202,4 +167,8 @@ const showStats = (tag: string) => {
const closeStats = () => {
stats.value.visible = false
}
+
+function awaitData() {
+ throw new Error('Function not implemented.');
+}
\ No newline at end of file
diff --git a/frontend/src/views/Rules.vue b/frontend/src/views/Rules.vue
index 1b9ef8f..41781ce 100644
--- a/frontend/src/views/Rules.vue
+++ b/frontend/src/views/Rules.vue
@@ -24,6 +24,9 @@
{{ $t('rule.add') }}
{{ $t('ruleset.add') }}
+
+ {{ $t('actions.save') }}
+
@@ -38,13 +41,13 @@
{{ $t('ruleset.format') }}
-
+
{{ item.format }}
{{ $t('actions.update') }}
-
+
{{ item.update_interval?? '-' }}
@@ -93,21 +96,27 @@
+
+ {{ $t('admin.action') }}
+
+ {{ item.action }}
+
+
{{ $t('objects.outbound') }}
-
- {{ item.outbound }}
+
+ {{ item.outbound?? '-' }}
{{ $t('pages.rules') }}
-
- {{ item.rules ? item.rules.length : Object.keys(item).filter(r => !["rule_set_ipcidr_match_source","invert","outbound"].includes(r)).length }}
+
+ {{ item.rules ? item.rules.length : Object.keys(item).filter(r => !actionKeys.includes(r)).length }}
{{ $t('rule.invert') }}
-
+
{{ $t( (item.invert?? false)? 'yes' : 'no') }}
@@ -144,16 +153,37 @@
\ No newline at end of file
diff --git a/frontend/src/views/Tls.vue b/frontend/src/views/Tls.vue
index f857227..34eb7e6 100644
--- a/frontend/src/views/Tls.vue
+++ b/frontend/src/views/Tls.vue
@@ -2,58 +2,61 @@
- {{ $t('actions.add') }}
+ {{ $t('actions.add') }}
-
+
{{ item.server?.server_name?.length>0 ? item.server.server_name : "-" }}
{{ $t('pages.inbounds') }}
-
-
- {{ i }}
-
- {{ item.inbounds?.length }}
+
+
+
+ {{ i }}
+
+ {{ tlsInbounds.length }}
+
+ -
ACME
-
+
{{ $t(item.server?.acme == undefined ? 'no' : 'yes') }}
ECH
-
+
{{ $t(item.server?.ech == undefined ? 'no' : 'yes') }}
Reality
-
+
{{ $t(item.server?.reality == undefined ? 'no' : 'yes') }}
-
+
-
+
@@ -66,12 +69,12 @@
{{ $t('confirm') }}
- {{ $t('yes') }}
+ {{ $t('yes') }}
{{ $t('no') }}
-
+
@@ -85,23 +88,21 @@
import TlsVue from '@/layouts/modals/Tls.vue'
import Data from '@/store/modules/data'
import { computed, ref } from 'vue'
-import { Config } from '@/types/config'
import { Inbound } from '@/types/inbounds'
import { Client } from '@/types/clients'
-import { Link, LinkUtil } from '@/plugins/link'
-import { fillData } from '@/plugins/outJson'
+import { tls } from '@/types/tls'
const tlsConfigs = computed((): any[] => {
return Data().tlsConfigs
})
-const inbounds = computed((): any[] => {
- return (Data().config)?.inbounds
+const inbounds = computed((): Inbound[] => {
+ return Data().inbounds
})
-const inData = computed((): any[] => {
- return Data().inData
-})
+const tlsInbounds = (id: number): string[] => {
+ return inbounds.value.filter(i => i.tls_id == id).map(i => i.tag)
+}
const clients = computed((): any[] => {
return Data().clients
@@ -109,21 +110,20 @@ const clients = computed((): any[] => {
const modal = ref({
visible: false,
- index: -1,
+ id: 0,
data: "",
})
const delOverlay = ref(new Array(tlsConfigs.value.length).fill(false))
-const showModal = (index: number) => {
- modal.value.index = index
- modal.value.data = index == -1 ? '{}' : JSON.stringify(tlsConfigs.value[index])
+const showModal = (id: number) => {
+ modal.value.id = id
+ modal.value.data = id == 0 ? '{}' : JSON.stringify(tlsConfigs.value.findLast(t => t.id == id))
modal.value.visible = true
}
-const clone = (index: number) => {
- let data = JSON.parse(JSON.stringify(tlsConfigs.value[index]))
+const clone = (obj: any) => {
+ let data = JSON.parse(JSON.stringify(obj))
data.id = 0
- data.inbounds = []
while (tlsConfigs.value.findIndex(t => t.name == data.name) != -1){
data.name += "-copy"
}
@@ -132,57 +132,15 @@ const clone = (index: number) => {
const closeModal = () => {
modal.value.visible = false
}
-const saveModal = (data:any) => {
- // New or Edit
- if (modal.value.index == -1) {
- tlsConfigs.value.push(data)
- } else {
- tlsConfigs.value[modal.value.index] = data
- inbounds?.value.filter(i => tlsConfigs.value[modal.value.index].inbounds.includes(i.tag)).forEach(i =>{
- if (i.tls != undefined) i.tls = data.server
- updateInData(i,data.client)
- updateLinks(i,data.client)
- })
- }
- modal.value.visible = false
+const saveModal = async (data:tls) => {
+ const success = await Data().save("tls", data.id == 0 ? "new" : "edit", data)
+ if (success) modal.value.visible = false
}
-const delTls = (index: number) => {
- if (index < Data().oldData.tlsConfigs.length){
- Data().delTls(tlsConfigs.value[index].id)
- }
- tlsConfigs.value.splice(index,1)
- delOverlay.value[index] = false
+const delTls = async (id: number) => {
+ const index = tlsConfigs.value.findIndex(t => t.id == id)
+ const success = await Data().save("tls", "del", id)
+ if (success) delOverlay.value[index] = false
}
-const updateLinks = (i:any,tlsClient:any) => {
- if(i.users){
- const uClients = clients.value.filter(c => c.inbounds.includes(i.tag))
- uClients.forEach((client:any) => {
- const clientInbounds = inbounds.value.filter(inb => client?.inbounds.includes(inb.tag))
- const newLinks = []
- clientInbounds.forEach(i =>{
- const cData = Data().inData?.findLast((d:any) => d.tag == i.tag)
- const addrs = cData ? cData.addrs : []
- const uris = LinkUtil.linkGenerator(client,i, tlsClient, addrs)
- if (uris.length>0){
- uris.forEach(uri => {
- newLinks.push({ type: 'local', remark: i.tag, uri: uri })
- })
- }
- })
- let links = client.links && client.links.length>0? client.links : []
- links = [...newLinks, ...links.filter((l:Link) => l.type != 'local')]
-
- client.links = links
- })
- }
-}
-
-const updateInData = (i:any, c:any) => {
- const inDataIndex = inData.value.findIndex(d => d.tag == i.tag)
- if (inDataIndex != -1) {
- fillData(inData.value[inDataIndex].outJson, i, c)
- }
-}
diff --git a/install.sh b/install.sh
index d4d4b89..c74334a 100755
--- a/install.sh
+++ b/install.sh
@@ -176,6 +176,21 @@ config_after_install() {
fi
}
+prepare_services() {
+ if [[ -f "/etc/systemd/system/sing-box.service" ]]; then
+ echo -e "${yellow}Stopping sing-box service... ${plain}"
+ systemctl stop sing-box
+ rm -f /usr/local/s-ui/bin/sing-box /usr/local/s-ui/bin/runSingbox.sh /usr/local/s-ui/bin/signal
+ fi
+ if [[ -e "/usr/local/s-ui/bin" ]]; then
+ echo -e "###############################################################"
+ echo -e "${green}/usr/local/s-ui/bin${red} directory exists yet!"
+ echo -e "Please check the content and delete it manually after migration ${plain}"
+ echo -e "###############################################################"
+ fi
+ systemctl daemon-reload
+}
+
install_s-ui() {
cd /tmp/
@@ -204,26 +219,26 @@ install_s-ui() {
if [[ -e /usr/local/s-ui/ ]]; then
systemctl stop s-ui
- systemctl stop sing-box
fi
tar zxvf s-ui-linux-$(arch).tar.gz
rm s-ui-linux-$(arch).tar.gz -f
- wget --no-check-certificate -O /usr/bin/s-ui https://raw.githubusercontent.com/alireza0/s-ui/main/s-ui.sh
-
- chmod +x s-ui/sui s-ui/bin/sing-box s-ui/bin/runSingbox.sh /usr/bin/s-ui
+ chmod +x s-ui/sui /s-ui/s-ui.sh
+ cp s-ui/s-ui.sh /usr/bin/s-ui
cp -rf s-ui /usr/local/
cp -f s-ui/*.service /etc/systemd/system/
rm -rf s-ui
config_after_install
+ prepare_services
- systemctl daemon-reload
systemctl enable s-ui --now
- systemctl enable sing-box --now
echo -e "${green}s-ui v${last_version}${plain} installation finished, it is up and running now..."
+ echo -e "You may access the Panel with following URL(s):${yellow}"
+ /usr/local/s-ui/sui uri
+ echo -e "${plain}"
echo -e ""
s-ui help
}
diff --git a/s-ui.sh b/s-ui.sh
index 7dfdab2..f10e105 100644
--- a/s-ui.sh
+++ b/s-ui.sh
@@ -172,7 +172,7 @@ custom_version() {
}
uninstall() {
- confirm "Are you sure you want to uninstall the panel? sing-box will also uninstalled!" "n"
+ confirm "Are you sure you want to uninstall the panel?" "n"
if [[ $? != 0 ]]; then
if [[ $# == 0 ]]; then
show_menu
@@ -181,10 +181,7 @@ uninstall() {
fi
systemctl stop s-ui
systemctl disable s-ui
- systemctl stop sing-box
- systemctl disable sing-box
rm /etc/systemd/system/s-ui.service -f
- rm /etc/systemd/system/sing-box.service -f
systemctl daemon-reload
systemctl reset-failed
rm /etc/s-ui/ -rf
@@ -257,6 +254,16 @@ view_setting() {
before_show_menu
}
+view_uri() {
+ info=$(/usr/local/s-ui/sui uri)
+ if [[ $? != 0 ]]; then
+ LOGE "Get current uri error"
+ before_show_menu
+ fi
+ LOGI "You may access the Panel with following URL(s):"
+ echo -e "${yellow}${info}${reset}"
+}
+
start() {
check_status $1
if [[ $? == 0 ]]; then
@@ -315,7 +322,6 @@ restart() {
status() {
systemctl status s-ui -l
- systemctl status sing-box -l
if [[ $# == 0 ]]; then
before_show_menu
fi
@@ -763,10 +769,10 @@ show_usage() {
echo -e "------------------------------------------"
echo -e "SUBCOMMANDS:"
echo -e "s-ui - Admin Management Script"
- echo -e "s-ui start - Start s-ui and sing-box"
- echo -e "s-ui stop - Stop s-ui and sing-box"
- echo -e "s-ui restart - Restart s-ui and sing-box"
- echo -e "s-ui status - Current Status of s-ui and sing-box"
+ echo -e "s-ui start - Start s-ui"
+ echo -e "s-ui stop - Stop s-ui"
+ echo -e "s-ui restart - Restart s-ui"
+ echo -e "s-ui status - Current Status of s-ui"
echo -e "s-ui enable - Enable Autostart on OS Startup"
echo -e "s-ui disable - Disable Autostart on OS Startup"
echo -e "s-ui log - Check s-ui Logs"
@@ -804,22 +810,13 @@ show_menu() {
${green}16.${plain} S-UI Enable Autostart
${green}17.${plain} S-UI Disable Autostart
————————————————————————————————
- ${green}18.${plain} Sing-Box Start
- ${green}19.${plain} Sing-Box Stop
- ${green}20.${plain} Sing-Box Restart
- ${green}21.${plain} Sing-Box Check State
- ${green}22.${plain} Sing-Box Check Logs
- ${green}23.${plain} Sing-Box Enable Autostart
- ${green}24.${plain} Sing-Box Disable Autostart
-————————————————————————————————
- ${green}25.${plain} Enable or Disable BBR
- ${green}26.${plain} SSL Certificate Management
- ${green}27.${plain} Cloudflare SSL Certificate
+ ${green}18.${plain} Enable or Disable BBR
+ ${green}19.${plain} SSL Certificate Management
+ ${green}20.${plain} Cloudflare SSL Certificate
————————————————————————————————
"
show_status s-ui
- show_status sing-box
- echo && read -p "Please enter your selection [0-27]: " num
+ echo && read -p "Please enter your selection [0-20]: " num
case "${num}" in
0)
@@ -853,7 +850,7 @@ show_menu() {
check_install && set_setting
;;
10)
- check_install && view_setting
+ check_install && view_setting && view_uri
;;
11)
check_install && start s-ui
@@ -877,37 +874,16 @@ show_menu() {
check_install && disable s-ui
;;
18)
- check_install && start sing-box
- ;;
- 19)
- check_install && stop sing-box
- ;;
- 20)
- check_install && restart sing-box
- ;;
- 21)
- check_install && status sing-box
- ;;
- 22)
- check_install && show_log sing-box
- ;;
- 23)
- check_install && enable sing-box
- ;;
- 24)
- check_install && disable sing-box
- ;;
- 25)
bbr_menu
;;
- 26)
+ 19)
ssl_cert_issue_main
;;
- 27)
+ 20)
ssl_cert_issue_CF
;;
*)
- LOGE "Please enter the correct number [0-27]"
+ LOGE "Please enter the correct number [0-20]"
;;
esac
}
@@ -915,22 +891,22 @@ show_menu() {
if [[ $# > 0 ]]; then
case $1 in
"start")
- check_install 0 && start s-ui 0 && start sing-box 0
+ check_install 0 && start s-ui 0
;;
"stop")
- check_install 0 && stop s-ui 0 && stop sing-box 0
+ check_install 0 && stop s-ui 0
;;
"restart")
- check_install 0 && restart s-ui 0 && restart sing-box 0
+ check_install 0 && restart s-ui 0
;;
"status")
check_install 0 && status 0
;;
"enable")
- check_install 0 && enable s-ui 0 && enable sing-box 0
+ check_install 0 && enable s-ui 0
;;
"disable")
- check_install 0 && disable s-ui 0 && disable sing-box 0
+ check_install 0 && disable s-ui 0
;;
"log")
check_install 0 && show_log s-ui 0
diff --git a/sing-box.service b/sing-box.service
deleted file mode 100644
index 8103f60..0000000
--- a/sing-box.service
+++ /dev/null
@@ -1,17 +0,0 @@
-[Unit]
-Description=sing-box service
-Documentation=https://sing-box.sagernet.org
-After=network.target nss-lookup.target network-online.target
-
-[Service]
-CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
-AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
-WorkingDirectory=/usr/local/s-ui/bin/
-ExecStart=/usr/local/s-ui/bin/runSingbox.sh
-ExecReload=/bin/kill -HUP $MAINPID
-Restart=on-failure
-RestartSec=10s
-LimitNOFILE=infinity
-
-[Install]
-WantedBy=multi-user.target