Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21add1f3ce | |||
| 9968f3885f | |||
| 2ac13ef8f4 | |||
| 4900c14295 | |||
| 55a6d78114 | |||
| caa115bbe3 | |||
| e3be3be9d9 | |||
| 988675a7a7 | |||
| 458f0c20da | |||
| f8fbc3c329 | |||
| 89bc3b5b23 | |||
| edfe0c86e7 | |||
| 6865c8b49d | |||
| 07947c9665 | |||
| 09616b6fac | |||
| 15105710bc |
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && sudo apt-get install upx -yq
|
sudo apt-get update
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
sudo apt install gcc-aarch64-linux-gnu
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||||
@@ -69,22 +69,18 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
#### Build Sing-Box
|
#### Build Sing-Box
|
||||||
git clone -b v1.8.13 https://github.com/SagerNet/sing-box
|
export VERSION=v1.8.14
|
||||||
|
git clone -b $VERSION https://github.com/SagerNet/sing-box
|
||||||
cd sing-box
|
cd sing-box
|
||||||
go build -v -gcflags=all="-l -B -C" -mod=mod -trimpath \
|
go build -tags with_quic,with_grpc,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_v2ray_api,with_clash_api,with_gvisor \
|
||||||
-ldflags "-s -w -buildid= -extldflags '-static'" -a \
|
-v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' -s -w -buildid=" \
|
||||||
-tags='netgo osusergo static_build with_quic with_grpc with_wireguard with_ech with_utls with_reality_server with_acme with_v2ray_api with_clash_api with_gvisor' \
|
|
||||||
-o sing-box ./cmd/sing-box
|
-o sing-box ./cmd/sing-box
|
||||||
upx --ultra-brute -9 -v --lzma --best --force sing-box
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
### Build s-ui
|
### Build s-ui
|
||||||
cd backend
|
cd backend
|
||||||
go build -v -gcflags=all="-l -B -C" -mod=mod -trimpath \
|
go build -o ../sui main.go
|
||||||
-ldflags "-s -w -buildid= -extldflags '-static'" -a -tags='netgo osusergo static_build sqlite_omit_load_extension' \
|
|
||||||
-o ../sui main.go
|
|
||||||
cd ..
|
cd ..
|
||||||
upx --ultra-brute -9 -v --lzma --best --force sui
|
|
||||||
|
|
||||||
mkdir s-ui
|
mkdir s-ui
|
||||||
cp sui s-ui/
|
cp sui s-ui/
|
||||||
|
|||||||
+5
-4
@@ -3,17 +3,18 @@ WORKDIR /app
|
|||||||
COPY frontend/ ./
|
COPY frontend/ ./
|
||||||
RUN npm install && npm run build
|
RUN npm install && npm run build
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS backend-builder
|
FROM golang:1.22-alpine AS backend-builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
||||||
ENV CGO_ENABLED=1
|
ENV CGO_ENABLED=1
|
||||||
RUN apk --no-cache --update add build-base gcc wget unzip
|
ENV GOARCH=$TARGETARCH
|
||||||
|
RUN apk update && apk --no-cache --update add build-base gcc wget unzip
|
||||||
COPY backend/ ./
|
COPY backend/ ./
|
||||||
COPY --from=front-builder /app/dist/ /app/web/html/
|
COPY --from=front-builder /app/dist/ /app/web/html/
|
||||||
RUN go build -o sui main.go
|
RUN go build -ldflags="-w -s" -o sui main.go
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM alpine
|
FROM --platform=$TARGETPLATFORM alpine
|
||||||
LABEL org.opencontainers.image.authors="alireza7@gmail.com"
|
LABEL org.opencontainers.image.authors="alireza7@gmail.com"
|
||||||
ENV TZ=Asia/Tehran
|
ENV TZ=Asia/Tehran
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.0.3
|
0.0.4
|
||||||
@@ -22,4 +22,5 @@ func (s *DelStatsJob) Run() {
|
|||||||
logger.Warning("Deleting old statistics failed: ", err)
|
logger.Warning("Deleting old statistics failed: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.Debug("Stats older than ", s.trafficAge, " days were deleted")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var defaultValueMap = map[string]string{
|
|||||||
"webListen": "",
|
"webListen": "",
|
||||||
"webDomain": "",
|
"webDomain": "",
|
||||||
"webPort": "2095",
|
"webPort": "2095",
|
||||||
"webSecret": common.Random(32),
|
"secret": common.Random(32),
|
||||||
"webCertFile": "",
|
"webCertFile": "",
|
||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
"webPath": "/app/",
|
"webPath": "/app/",
|
||||||
@@ -191,11 +191,11 @@ func (s *SettingService) SetWebPath(webPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSecret() ([]byte, error) {
|
func (s *SettingService) GetSecret() ([]byte, error) {
|
||||||
secret, err := s.getString("webSecret")
|
secret, err := s.getString("secret")
|
||||||
if secret == defaultValueMap["webSecret"] {
|
if secret == defaultValueMap["secret"] {
|
||||||
err := s.saveSetting("webSecret", secret)
|
err := s.saveSetting("secret", secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("save webSecret failed:", err)
|
logger.Warning("save secret failed:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []byte(secret), err
|
return []byte(secret), err
|
||||||
@@ -318,10 +318,10 @@ func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
|||||||
json.Unmarshal(change.Obj, &obj)
|
json.Unmarshal(change.Obj, &obj)
|
||||||
|
|
||||||
// Secure file existance check
|
// Secure file existance check
|
||||||
if key == "webCertFile" ||
|
if obj != "" && (key == "webCertFile" ||
|
||||||
key == "webKeyFile" ||
|
key == "webKeyFile" ||
|
||||||
key == "subCertFile" ||
|
key == "subCertFile" ||
|
||||||
key == "subKeyFile" {
|
key == "subKeyFile") {
|
||||||
err = s.fileExists(obj)
|
err = s.fileExists(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewError(" -> ", obj, " is not exists")
|
return common.NewError(" -> ", obj, " is not exists")
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import "math/rand"
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
var allSeq [62]rune
|
var (
|
||||||
|
allSeq []rune
|
||||||
|
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
for _, char := range chars {
|
||||||
|
allSeq = append(allSeq, char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Random(n int) string {
|
func Random(n int) string {
|
||||||
runes := make([]rune, n)
|
runes := make([]rune, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
runes[i] = allSeq[rand.Intn(len(allSeq))]
|
runes[i] = allSeq[rnd.Intn(len(allSeq))]
|
||||||
}
|
}
|
||||||
return string(runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS singbox-builder
|
|||||||
LABEL maintainer="Alireza <alireza7@gmail.com>"
|
LABEL maintainer="Alireza <alireza7@gmail.com>"
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETOS TARGETARCH
|
ARG TARGETOS TARGETARCH
|
||||||
ARG SINGBOX_VER=v1.8.10
|
ARG SINGBOX_VER=v1.8.13
|
||||||
ARG SINGBOX_TAGS="with_quic,with_grpc,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_v2ray_api,with_clash_api,with_gvisor"
|
ARG SINGBOX_TAGS="with_quic,with_grpc,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_v2ray_api,with_clash_api,with_gvisor"
|
||||||
ARG GOPROXY=""
|
ARG GOPROXY=""
|
||||||
ENV GOPROXY ${GOPROXY}
|
ENV GOPROXY ${GOPROXY}
|
||||||
@@ -18,7 +18,7 @@ RUN set -ex \
|
|||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$SINGBOX_VER\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$SINGBOX_VER\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM alpine
|
FROM --platform=$TARGETPLATFORM alpine
|
||||||
LABEL maintainer="Alireza <alireza7@gmail.com>"
|
LABEL maintainer="Alireza <alireza7@gmail.com>"
|
||||||
ENV TZ=Asia/Tehran
|
ENV TZ=Asia/Tehran
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
---
|
---
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
s-ui:
|
s-ui:
|
||||||
image: alireza7/s-ui
|
image: alireza7/s-ui
|
||||||
|
|||||||
Generated
+560
-1900
File diff suppressed because it is too large
Load Diff
+20
-20
@@ -3,41 +3,41 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint . --fix --ignore-path .gitignore"
|
"lint": "eslint . --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "7.0.96",
|
"@mdi/font": "7.0.96",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.7.2",
|
||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.3",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"core-js": "^3.29.0",
|
"core-js": "^3.37.1",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"qrcode.vue": "^3.4.1",
|
"qrcode.vue": "^3.4.1",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "^0.10.0",
|
||||||
"vue": "^3.2.0",
|
"vue": "^3.2.0",
|
||||||
"vue-chartjs": "^5.3.0",
|
"vue-chartjs": "^5.3.1",
|
||||||
"vue-i18n": "^9.8.0",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-router": "^4.0.0",
|
"vue-router": "^4.3.2",
|
||||||
"vue3-persian-datetime-picker": "^1.2.2",
|
"vue3-persian-datetime-picker": "^1.2.2",
|
||||||
"vuetify": "^3.0.0"
|
"vuetify": "^3.6.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.21.4",
|
"@babel/types": "^7.24.5",
|
||||||
"@types/node": "^18.15.0",
|
"@types/node": "^18.19.33",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.6.2",
|
||||||
"@vue/eslint-config-typescript": "^11.0.0",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"eslint": "^8.22.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-vue": "^9.3.0",
|
"eslint-plugin-vue": "^9.26.0",
|
||||||
"material-design-icons-iconfont": "^6.7.0",
|
"material-design-icons-iconfont": "^6.7.0",
|
||||||
"sass": "^1.60.0",
|
"sass": "^1.77.2",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.4.5",
|
||||||
"unplugin-fonts": "^1.0.3",
|
"unplugin-fonts": "^1.1.1",
|
||||||
"vite": "^4.5.3",
|
"vite": "^4.5.3",
|
||||||
"vite-plugin-vuetify": "^1.0.0",
|
"vite-plugin-vuetify": "^1.0.2",
|
||||||
"vue-tsc": "^1.2.0"
|
"vue-tsc": "^1.8.27"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Hysteria2">
|
<v-card subtitle="Hysteria2">
|
||||||
<v-row v-if="direction == 'in'">
|
<v-row v-if="direction == 'in'">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4" v-if="data.masquerade != undefined">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="HTTP3 server on auth fail"
|
label="HTTP3 server on auth fail"
|
||||||
hide-details
|
hide-details
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="data.obfs">
|
<v-row v-if="data.obfs != undefined">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:label="$t('types.hy.obfs')"
|
:label="$t('types.hy.obfs')"
|
||||||
@@ -66,6 +66,9 @@
|
|||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionObfs" color="primary" :label="$t('types.hy.obfs')" hide-details></v-switch>
|
<v-switch v-model="optionObfs" color="primary" :label="$t('types.hy.obfs')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionMasq" color="primary" label="Masquerade" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@@ -95,6 +98,10 @@ export default {
|
|||||||
optionObfs: {
|
optionObfs: {
|
||||||
get(): boolean { return this.$props.data.obfs != undefined },
|
get(): boolean { return this.$props.data.obfs != undefined },
|
||||||
set(v:boolean) { this.$props.data.obfs = v ? { type: "salamander", password: "" } : undefined }
|
set(v:boolean) { this.$props.data.obfs = v ? { type: "salamander", password: "" } : undefined }
|
||||||
|
},
|
||||||
|
optionMasq: {
|
||||||
|
get(): boolean { return this.$props.data.masquerade != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.masquerade = v ? "" : undefined }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Network }
|
components: { Network }
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ const gaugeColor = computed(() => {
|
|||||||
background: `rgb(var(--v-theme-${gaugeColor}))`
|
background: `rgb(var(--v-theme-${gaugeColor}))`
|
||||||
}">
|
}">
|
||||||
</div>
|
</div>
|
||||||
<span class="gauge__cover" dir="ltr" v-html="data.text">
|
<div class="gauge__cover"><span dir="ltr" v-html="data.text"></span></div>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:label="$t('out.addr')"
|
:label="$t('out.port')"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
hide-details
|
hide-details
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios';
|
||||||
|
|
||||||
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
|
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||||
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
|
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
|
||||||
|
|
||||||
axios.defaults.baseURL = "./"
|
axios.defaults.baseURL = "./"
|
||||||
|
const pendingRequests = new Map()
|
||||||
|
|
||||||
axios.interceptors.request.use(
|
axios.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
|
// Generate a unique key for the request
|
||||||
|
const requestKey = `${config.method}:${config.url}`
|
||||||
|
|
||||||
|
// Check if there is already a pending request with the same key
|
||||||
|
if (pendingRequests.has(requestKey)) {
|
||||||
|
const cancelSource = pendingRequests.get(requestKey)
|
||||||
|
cancelSource.cancel('Duplicate request cancelled')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new cancel token for the request
|
||||||
|
const cancelSource = axios.CancelToken.source()
|
||||||
|
config.cancelToken = cancelSource.token
|
||||||
|
|
||||||
|
// Store the cancel token in the pending requests map
|
||||||
|
pendingRequests.set(requestKey, cancelSource)
|
||||||
|
|
||||||
if (config.data instanceof FormData) {
|
if (config.data instanceof FormData) {
|
||||||
config.headers['Content-Type'] = 'multipart/form-data'
|
config.headers['Content-Type'] = 'multipart/form-data'
|
||||||
}
|
}
|
||||||
@@ -15,6 +32,26 @@ axios.interceptors.request.use(
|
|||||||
(error) => Promise.reject(error),
|
(error) => Promise.reject(error),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
// Remove the request from the pending requests map
|
||||||
|
const requestKey = `${response.config.method}:${response.config.url}`
|
||||||
|
pendingRequests.delete(requestKey)
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (axios.isCancel(error)) {
|
||||||
|
// Handle duplicate request cancellation here if needed
|
||||||
|
console.warn(error.message)
|
||||||
|
} else {
|
||||||
|
// Remove the request from the pending requests map on error
|
||||||
|
const requestKey = `${error.config.method}:${error.config.url}`
|
||||||
|
pendingRequests.delete(requestKey)
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const api = axios.create()
|
const api = axios.create()
|
||||||
|
|
||||||
export default api
|
export default api
|
||||||
|
|||||||
@@ -140,10 +140,12 @@ export namespace LinkUtil {
|
|||||||
host: <string|null>'',
|
host: <string|null>'',
|
||||||
path: <string|null>'',
|
path: <string|null>'',
|
||||||
serviceName: <string|null>'',
|
serviceName: <string|null>'',
|
||||||
|
type: <string|null>null,
|
||||||
}
|
}
|
||||||
switch (t.type){
|
switch (t.type){
|
||||||
case TrspTypes.HTTP:
|
case TrspTypes.HTTP:
|
||||||
const th = <HTTP>t
|
const th = <HTTP>t
|
||||||
|
params.type = 'http'
|
||||||
params.host = th.host?.join(',')?? null
|
params.host = th.host?.join(',')?? null
|
||||||
params.path = th.path?? null
|
params.path = th.path?? null
|
||||||
break
|
break
|
||||||
@@ -225,6 +227,7 @@ export namespace LinkUtil {
|
|||||||
host: tParams.host,
|
host: tParams.host,
|
||||||
id: u?.uuid,
|
id: u?.uuid,
|
||||||
net: transport?.type?? 'tcp',
|
net: transport?.type?? 'tcp',
|
||||||
|
type: transport?.type == 'http' ? 'http' : undefined,
|
||||||
path: tParams.path,
|
path: tParams.path,
|
||||||
port: inbound.listen_port,
|
port: inbound.listen_port,
|
||||||
ps: inbound.tag,
|
ps: inbound.tag,
|
||||||
|
|||||||
@@ -158,7 +158,9 @@ const closeModal = () => {
|
|||||||
modal.value.visible = false
|
modal.value.visible = false
|
||||||
}
|
}
|
||||||
const saveModal = (data:Inbound, stats: boolean) => {
|
const saveModal = (data:Inbound, stats: boolean) => {
|
||||||
if (inbounds.value.findIndex(c => c.tag == data.tag) != modal.value.id) {
|
// Check duplicate tag
|
||||||
|
const oldTag = modal.value.id != -1 ? inbounds.value[modal.value.id].tag : null
|
||||||
|
if (data.tag != oldTag && inTags.value.includes(data.tag)) {
|
||||||
const sb = Message()
|
const sb = Message()
|
||||||
sb.showMessage(i18n.global.t('error.dplData') + ': ' + i18n.global.t('objects.tag') ,'error', 5000)
|
sb.showMessage(i18n.global.t('error.dplData') + ': ' + i18n.global.t('objects.tag') ,'error', 5000)
|
||||||
return
|
return
|
||||||
@@ -188,10 +190,12 @@ const saveModal = (data:Inbound, stats: boolean) => {
|
|||||||
|
|
||||||
inbounds.value[modal.value.id] = data
|
inbounds.value[modal.value.id] = data
|
||||||
}
|
}
|
||||||
// Set users
|
if (Object.hasOwn(data,'users')) {
|
||||||
data = buildInboundsUsers(data)
|
// Set users
|
||||||
// Update links
|
data = buildInboundsUsers(data)
|
||||||
if (Object.hasOwn(data,'users')) updateLinks(data)
|
// Update links
|
||||||
|
updateLinks(data)
|
||||||
|
}
|
||||||
modal.value.visible = false
|
modal.value.visible = false
|
||||||
}
|
}
|
||||||
const updateLinks = (i: InboundWithUser) => {
|
const updateLinks = (i: InboundWithUser) => {
|
||||||
|
|||||||
@@ -139,7 +139,9 @@ const closeModal = () => {
|
|||||||
modal.value.visible = false
|
modal.value.visible = false
|
||||||
}
|
}
|
||||||
const saveModal = (data:Outbound, stats: boolean) => {
|
const saveModal = (data:Outbound, stats: boolean) => {
|
||||||
if (outbounds.value.findIndex(c => c.tag == data.tag) != modal.value.id) {
|
// Check duplicate tag
|
||||||
|
const oldTag = modal.value.id != -1 ? outbounds.value[modal.value.id].tag : null
|
||||||
|
if (data.tag != oldTag && outboundTags.value.includes(data.tag)) {
|
||||||
const sb = Message()
|
const sb = Message()
|
||||||
sb.showMessage(i18n.global.t('error.dplData') + ': ' + i18n.global.t('objects.tag') ,'error', 5000)
|
sb.showMessage(i18n.global.t('error.dplData') + ': ' + i18n.global.t('objects.tag') ,'error', 5000)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<v-text-field v-model="settings.webListen" :label="$t('setting.addr')" hide-details></v-text-field>
|
<v-text-field v-model="settings.webListen" :label="$t('setting.addr')" hide-details></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field v-model="webPort" :label="$t('setting.port')" hide-details></v-text-field>
|
<v-text-field v-model.number="webPort" min="1" type="number" :label="$t('setting.port')" hide-details></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field v-model="settings.webPath" :label="$t('setting.webPath')" hide-details></v-text-field>
|
<v-text-field v-model="settings.webPath" :label="$t('setting.webPath')" hide-details></v-text-field>
|
||||||
@@ -51,8 +51,9 @@
|
|||||||
<v-text-field
|
<v-text-field
|
||||||
type="number"
|
type="number"
|
||||||
v-model.number="sessionMaxAge"
|
v-model.number="sessionMaxAge"
|
||||||
|
min="0"
|
||||||
:label="$t('setting.sessionAge')"
|
:label="$t('setting.sessionAge')"
|
||||||
:suffix="$t('date.h')"
|
:suffix="$t('date.m')"
|
||||||
hide-details
|
hide-details
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -60,6 +61,7 @@
|
|||||||
<v-text-field
|
<v-text-field
|
||||||
type="number"
|
type="number"
|
||||||
v-model.number="trafficAge"
|
v-model.number="trafficAge"
|
||||||
|
min="0"
|
||||||
:label="$t('setting.trafficAge')"
|
:label="$t('setting.trafficAge')"
|
||||||
:suffix="$t('date.d')"
|
:suffix="$t('date.d')"
|
||||||
hide-details
|
hide-details
|
||||||
@@ -87,7 +89,7 @@
|
|||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
type="number"
|
type="number"
|
||||||
v-model="subPort"
|
v-model.number="subPort"
|
||||||
min="1"
|
min="1"
|
||||||
:label="$t('setting.port')"
|
:label="$t('setting.port')"
|
||||||
hide-details></v-text-field>
|
hide-details></v-text-field>
|
||||||
@@ -114,6 +116,7 @@
|
|||||||
<v-text-field
|
<v-text-field
|
||||||
type="number"
|
type="number"
|
||||||
v-model.number="subUpdates"
|
v-model.number="subUpdates"
|
||||||
|
min="0"
|
||||||
:label="$t('setting.update')"
|
:label="$t('setting.update')"
|
||||||
hide-details
|
hide-details
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
@@ -248,28 +251,28 @@ const subShowInfo = computed({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const webPort = computed({
|
const webPort = computed({
|
||||||
get: () => { return parseInt(settings.value.webPort) },
|
get: () => { return settings.value.webPort.length>0 ? parseInt(settings.value.webPort) : 2095 },
|
||||||
set: (v:number) => { settings.value.webPort = v.toString() }
|
set: (v:number) => { settings.value.webPort = v>0 ? v.toString() : "2095" }
|
||||||
})
|
})
|
||||||
|
|
||||||
const sessionMaxAge = computed({
|
const sessionMaxAge = computed({
|
||||||
get: () => { return parseInt(settings.value.sessionMaxAge) },
|
get: () => { return settings.value.sessionMaxAge.length>0 ? parseInt(settings.value.sessionMaxAge) : 0 },
|
||||||
set: (v:number) => { settings.value.sessionMaxAge = v.toString() }
|
set: (v:number) => { settings.value.sessionMaxAge = v>0 ? v.toString() : "0" }
|
||||||
})
|
})
|
||||||
|
|
||||||
const trafficAge = computed({
|
const trafficAge = computed({
|
||||||
get: () => { return parseInt(settings.value.trafficAge) },
|
get: () => { return settings.value.trafficAge.length>0 ? parseInt(settings.value.trafficAge) : 0 },
|
||||||
set: (v:number) => { settings.value.trafficAge = v.toString() }
|
set: (v:number) => { settings.value.trafficAge = v>0 ? v.toString() : "0" }
|
||||||
})
|
})
|
||||||
|
|
||||||
const subPort = computed({
|
const subPort = computed({
|
||||||
get: () => { return parseInt(settings.value.subPort) },
|
get: () => { return settings.value.subPort.length>0 ? parseInt(settings.value.subPort) : 2096 },
|
||||||
set: (v:number) => { settings.value.subPort = v.toString() }
|
set: (v:number) => { settings.value.subPort = v>0 ? v.toString() : "2096" }
|
||||||
})
|
})
|
||||||
|
|
||||||
const subUpdates = computed({
|
const subUpdates = computed({
|
||||||
get: () => { return parseInt(settings.value.subUpdates) },
|
get: () => { return settings.value.subUpdates.length>0 ? parseInt(settings.value.subUpdates) : 12 },
|
||||||
set: (v:number) => { settings.value.subUpdates = v.toString() }
|
set: (v:number) => { settings.value.subUpdates = v>0 ? v.toString() : "12" }
|
||||||
})
|
})
|
||||||
|
|
||||||
const stateChange = computed(() => {
|
const stateChange = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user