subjson and multidomain
This commit is contained in:
@@ -15,6 +15,7 @@ type APIHandler struct {
|
|||||||
service.ConfigService
|
service.ConfigService
|
||||||
service.ClientService
|
service.ClientService
|
||||||
service.TlsService
|
service.TlsService
|
||||||
|
service.InDataService
|
||||||
service.PanelService
|
service.PanelService
|
||||||
service.StatsService
|
service.StatsService
|
||||||
service.ServerService
|
service.ServerService
|
||||||
@@ -206,6 +207,10 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
inData, err := a.InDataService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
|
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -213,6 +218,7 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
|||||||
data["config"] = *config
|
data["config"] = *config
|
||||||
data["clients"] = clients
|
data["clients"] = clients
|
||||||
data["tls"] = tlsConfigs
|
data["tls"] = tlsConfigs
|
||||||
|
data["inData"] = inData
|
||||||
data["subURI"] = subURI
|
data["subURI"] = subURI
|
||||||
data["onlines"] = onlines
|
data["onlines"] = onlines
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ func InitDB(dbPath string) error {
|
|||||||
err = db.AutoMigrate(
|
err = db.AutoMigrate(
|
||||||
&model.Setting{},
|
&model.Setting{},
|
||||||
&model.Tls{},
|
&model.Tls{},
|
||||||
|
&model.InboundData{},
|
||||||
&model.User{},
|
&model.User{},
|
||||||
&model.Stats{},
|
&model.Stats{},
|
||||||
&model.Client{},
|
&model.Client{},
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ type Tls struct {
|
|||||||
Client json.RawMessage `json:"client" form:"client"`
|
Client json.RawMessage `json:"client" form:"client"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InboundData struct {
|
||||||
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Tag string `json:"tag" form:"tag"`
|
||||||
|
Addrs json.RawMessage `json:"addrs" form:"addrs"`
|
||||||
|
OutJson json.RawMessage `json:"outJson" form:"outJson"`
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
Username string `json:"username" form:"username"`
|
Username string `json:"username" form:"username"`
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ var LastUpdate int64
|
|||||||
type ConfigService struct {
|
type ConfigService struct {
|
||||||
ClientService
|
ClientService
|
||||||
TlsService
|
TlsService
|
||||||
|
InDataService
|
||||||
singbox.Controller
|
singbox.Controller
|
||||||
SettingService
|
SettingService
|
||||||
}
|
}
|
||||||
@@ -80,7 +81,7 @@ func (s *ConfigService) GetConfig() (*SingBoxConfig, error) {
|
|||||||
|
|
||||||
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
|
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
|
||||||
var err error
|
var err error
|
||||||
var clientChanges, tlsChanges, settingChanges, configChanges []model.Changes
|
var clientChanges, tlsChanges, inChanges, settingChanges, configChanges []model.Changes
|
||||||
if _, ok := changes["clients"]; ok {
|
if _, ok := changes["clients"]; ok {
|
||||||
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
|
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -93,6 +94,12 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, ok := changes["inData"]; ok {
|
||||||
|
err = json.Unmarshal([]byte(changes["inData"]), &inChanges)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, ok := changes["settings"]; ok {
|
if _, ok := changes["settings"]; ok {
|
||||||
err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
|
err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -128,6 +135,12 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(inChanges) > 0 {
|
||||||
|
err = s.InDataService.Save(tx, inChanges)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(settingChanges) > 0 {
|
if len(settingChanges) > 0 {
|
||||||
err = s.SettingService.Save(tx, settingChanges)
|
err = s.SettingService.Save(tx, settingChanges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -185,14 +198,19 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
|
|
||||||
// Log changes
|
// Log changes
|
||||||
dt := time.Now().Unix()
|
dt := time.Now().Unix()
|
||||||
allChanges := append(append(clientChanges, settingChanges...), append(configChanges, tlsChanges...)...)
|
allChanges := append(clientChanges, settingChanges...)
|
||||||
for index := range allChanges {
|
allChanges = append(allChanges, configChanges...)
|
||||||
allChanges[index].DateTime = dt
|
allChanges = append(allChanges, tlsChanges...)
|
||||||
allChanges[index].Actor = loginUser
|
allChanges = append(allChanges, inChanges...)
|
||||||
}
|
if len(allChanges) > 0 {
|
||||||
err = tx.Model(model.Changes{}).Create(&allChanges).Error
|
for index := range allChanges {
|
||||||
if err != nil {
|
allChanges[index].DateTime = dt
|
||||||
return err
|
allChanges[index].Actor = loginUser
|
||||||
|
}
|
||||||
|
err = tx.Model(model.Changes{}).Create(&allChanges).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LastUpdate = dt
|
LastUpdate = dt
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InDataService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InDataService) GetAll() ([]model.InboundData, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
inData := []model.InboundData{}
|
||||||
|
err := db.Model(model.InboundData{}).Scan(&inData).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return inData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InDataService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||||
|
var err error
|
||||||
|
for _, change := range changes {
|
||||||
|
inData := model.InboundData{}
|
||||||
|
err = json.Unmarshal(change.Obj, &inData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch change.Action {
|
||||||
|
case "new":
|
||||||
|
err = tx.Create(&inData).Error
|
||||||
|
case "del":
|
||||||
|
err = tx.Where("id = ?", change.Index).Delete(model.InboundData{}).Error
|
||||||
|
default:
|
||||||
|
err = tx.Save(inData).Error
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ var defaultValueMap = map[string]string{
|
|||||||
"subEncode": "true",
|
"subEncode": "true",
|
||||||
"subShowInfo": "false",
|
"subShowInfo": "false",
|
||||||
"subURI": "",
|
"subURI": "",
|
||||||
|
"subJsonExt": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -65,7 +66,7 @@ func (s *SettingService) GetAllSetting() (*map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Due to security principles
|
// Due to security principles
|
||||||
delete(allSetting, "webSecret")
|
delete(allSetting, "secret")
|
||||||
|
|
||||||
return &allSetting, nil
|
return &allSetting, nil
|
||||||
}
|
}
|
||||||
@@ -347,6 +348,10 @@ func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubJsonExt() (string, error) {
|
||||||
|
return s.getString("subJsonExt")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) fileExists(path string) error {
|
func (s *SettingService) fileExists(path string) error {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"s-ui/service"
|
||||||
|
"s-ui/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultJson = `
|
||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "tun",
|
||||||
|
"inet4_address": "172.19.0.1/30",
|
||||||
|
"mtu": 9000,
|
||||||
|
"auto_route": true,
|
||||||
|
"strict_route": false,
|
||||||
|
"sniff": true,
|
||||||
|
"endpoint_independent_nat": false,
|
||||||
|
"stack": "system",
|
||||||
|
"platform": {
|
||||||
|
"http_proxy": {
|
||||||
|
"enabled": true,
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 2080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"listen_port": 2080,
|
||||||
|
"sniff": true,
|
||||||
|
"users": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
type JsonService struct {
|
||||||
|
service.SettingService
|
||||||
|
LinkService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) GetJson(subId string, format string) (*string, error) {
|
||||||
|
var jsonConfig map[string]interface{}
|
||||||
|
|
||||||
|
client, inDatas, err := j.getData(subId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outbounds, outTags, err := j.getOutbounds(client.Config, inDatas)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
links := j.LinkService.GetLinks(&client.Links, "external", "")
|
||||||
|
for index, link := range links {
|
||||||
|
json, tag, err := util.GetOutbound(link, index)
|
||||||
|
if err == nil && len(tag) > 0 {
|
||||||
|
*outbounds = append(*outbounds, *json)
|
||||||
|
*outTags = append(*outTags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
j.addDefaultOutbounds(outbounds, outTags)
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(defaultJson), &jsonConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonConfig["outbounds"] = outbounds
|
||||||
|
|
||||||
|
// Add other objects from settings
|
||||||
|
j.addOthers(&jsonConfig)
|
||||||
|
|
||||||
|
result, _ := json.MarshalIndent(jsonConfig, " ", " ")
|
||||||
|
resultStr := string(result)
|
||||||
|
return &resultStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) getData(subId string) (*model.Client, *[]model.InboundData, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
client := &model.Client{}
|
||||||
|
err := db.Model(model.Client{}).Where("enable = true and name = ?", subId).First(client).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
var inbounds []string
|
||||||
|
err = json.Unmarshal(client.Inbounds, &inbounds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
inDatas := &[]model.InboundData{}
|
||||||
|
err = db.Model(model.InboundData{}).Where("tag in ?", inbounds).Find(&inDatas).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return client, inDatas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]model.InboundData) (*[]map[string]interface{}, *[]string, error) {
|
||||||
|
var outbounds []map[string]interface{}
|
||||||
|
var configs map[string]interface{}
|
||||||
|
var outTags []string
|
||||||
|
|
||||||
|
err := json.Unmarshal(clientConfig, &configs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
for _, inData := range *inDatas {
|
||||||
|
if len(inData.OutJson) < 5 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var outbound map[string]interface{}
|
||||||
|
err = json.Unmarshal(inData.OutJson, &outbound)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
protocol, _ := outbound["type"].(string)
|
||||||
|
config, _ := configs[protocol].(map[string]interface{})
|
||||||
|
for key, value := range config {
|
||||||
|
if key != "alterId" && key != "name" && key != "username" {
|
||||||
|
outbound[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrs []map[string]interface{}
|
||||||
|
err = json.Unmarshal(inData.Addrs, &addrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
tag := outbound["tag"].(string)
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
outTags = append(outTags, tag)
|
||||||
|
outbounds = append(outbounds, outbound)
|
||||||
|
} else {
|
||||||
|
for index, addr := range addrs {
|
||||||
|
// Copy original config
|
||||||
|
newOut := make(map[string]interface{}, len(outbound))
|
||||||
|
for key, value := range outbound {
|
||||||
|
newOut[key] = value
|
||||||
|
}
|
||||||
|
// Change and push copied config
|
||||||
|
newOut["server"] = addr["server"].(string)
|
||||||
|
port := addr["server_port"].(float64)
|
||||||
|
newOut["server_port"] = int(port)
|
||||||
|
newTag := fmt.Sprintf("%d.%s", index+1, tag)
|
||||||
|
outTags = append(outTags, newTag)
|
||||||
|
newOut["tag"] = newTag
|
||||||
|
outbounds = append(outbounds, newOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &outbounds, &outTags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) addDefaultOutbounds(outbounds *[]map[string]interface{}, outTags *[]string) {
|
||||||
|
outbound := []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"outbounds": append([]string{"auto", "direct"}, *outTags...),
|
||||||
|
"tag": "proxy",
|
||||||
|
"type": "selector",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "auto",
|
||||||
|
"type": "urltest",
|
||||||
|
"outbounds": outTags,
|
||||||
|
"url": "http://www.gstatic.com/generate_204",
|
||||||
|
"interval": "10m",
|
||||||
|
"tolerance": 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"tag": "block",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
*outbounds = append(outbound, *outbounds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
|
||||||
|
rules := []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"type": "logical",
|
||||||
|
"mode": "or",
|
||||||
|
"rules": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"port": 53,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"protocol": "dns",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"outbound": "dns-out",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"clash_mode": "Direct",
|
||||||
|
"outbound": "direct",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"outbound": "proxy",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
route := map[string]interface{}{
|
||||||
|
"auto_detect_interface": true,
|
||||||
|
"final": "proxy",
|
||||||
|
"rules": rules,
|
||||||
|
}
|
||||||
|
|
||||||
|
othersStr, err := j.SettingService.GetSubJsonExt()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(othersStr) == 0 {
|
||||||
|
(*jsonConfig)["route"] = route
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var othersJson map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(othersStr), &othersJson)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, ok := othersJson["log"]; ok {
|
||||||
|
(*jsonConfig)["log"] = othersJson["log"]
|
||||||
|
}
|
||||||
|
if _, ok := othersJson["dns"]; ok {
|
||||||
|
(*jsonConfig)["dns"] = othersJson["dns"]
|
||||||
|
}
|
||||||
|
if _, ok := othersJson["experimental"]; ok {
|
||||||
|
(*jsonConfig)["experimental"] = othersJson["lexperimentalog"]
|
||||||
|
}
|
||||||
|
if _, ok := othersJson["rule_set"]; ok {
|
||||||
|
route["rule_set"] = othersJson["rule_set"]
|
||||||
|
}
|
||||||
|
if settingRules, ok := othersJson["rules"].([]interface{}); ok {
|
||||||
|
route["rules"] = append(rules, settingRules...)
|
||||||
|
}
|
||||||
|
(*jsonConfig)["route"] = route
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"s-ui/logger"
|
||||||
|
"s-ui/util"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Link struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
Uri string `json:"uri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinkService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LinkService) GetLinks(linkJson *json.RawMessage, types string, clientInfo string) []string {
|
||||||
|
links := []Link{}
|
||||||
|
var result []string
|
||||||
|
err := json.Unmarshal(*linkJson, &links)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, link := range links {
|
||||||
|
switch link.Type {
|
||||||
|
case "external":
|
||||||
|
result = append(result, link.Uri)
|
||||||
|
case "sub":
|
||||||
|
result = append(result, s.getExternalSub(link.Uri)...)
|
||||||
|
case "local":
|
||||||
|
if types == "all" {
|
||||||
|
result = append(result, s.addClientInfo(link.Uri, clientInfo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LinkService) addClientInfo(uri string, clientInfo string) string {
|
||||||
|
protocol := strings.Split(uri, "://")
|
||||||
|
if len(protocol) < 2 {
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
switch protocol[0] {
|
||||||
|
case "vmess":
|
||||||
|
var vmessJson map[string]interface{}
|
||||||
|
config, err := util.B64StrToByte(protocol[1])
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sub: Error decoding vmess content:", err)
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(config, &vmessJson)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sub: Error decoding vmess content:", err)
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
vmessJson["ps"] = vmessJson["ps"].(string) + clientInfo
|
||||||
|
result, err := json.MarshalIndent(vmessJson, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sub: Error decoding vmess + clientInfo content:", err)
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
return "vmess://" + util.ByteToB64Str(result)
|
||||||
|
default:
|
||||||
|
return uri + clientInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LinkService) getExternalSub(url string) []string {
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
|
||||||
|
// Make the HTTP request
|
||||||
|
response, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sub: Error making HTTP request:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
// Read the response body
|
||||||
|
body, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sub: Error reading response body:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert if the content is Base64 encoded
|
||||||
|
links := util.StrOrBase64Encoded(string(body))
|
||||||
|
return strings.Split(links, "\n")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
type SubHandler struct {
|
type SubHandler struct {
|
||||||
service.SettingService
|
service.SettingService
|
||||||
SubService
|
SubService
|
||||||
|
JsonService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSubHandler(g *gin.RouterGroup) {
|
func NewSubHandler(g *gin.RouterGroup) {
|
||||||
@@ -23,17 +24,28 @@ func (s *SubHandler) initRouter(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
func (s *SubHandler) subs(c *gin.Context) {
|
func (s *SubHandler) subs(c *gin.Context) {
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
result, headers, err := s.SubService.GetSubs(subId)
|
format, isFormat := c.GetQuery("format")
|
||||||
if err != nil || result == nil {
|
if isFormat {
|
||||||
logger.Error(err)
|
result, err := s.JsonService.GetJson(subId, format)
|
||||||
c.String(400, "Error!")
|
if err != nil || result == nil {
|
||||||
|
logger.Error(err)
|
||||||
|
c.String(400, "Error!")
|
||||||
|
} else {
|
||||||
|
c.String(200, *result)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
result, headers, err := s.SubService.GetSubs(subId)
|
||||||
|
if err != nil || result == nil {
|
||||||
|
logger.Error(err)
|
||||||
|
c.String(400, "Error!")
|
||||||
|
} else {
|
||||||
|
|
||||||
// Add headers
|
// Add headers
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||||
|
|
||||||
c.String(200, *result)
|
c.String(200, *result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-102
@@ -1,15 +1,10 @@
|
|||||||
package sub
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"s-ui/logger"
|
|
||||||
"s-ui/service"
|
"s-ui/service"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -17,12 +12,7 @@ import (
|
|||||||
|
|
||||||
type SubService struct {
|
type SubService struct {
|
||||||
service.SettingService
|
service.SettingService
|
||||||
}
|
LinkService
|
||||||
|
|
||||||
type Link struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
Uri string `json:"uri"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string) (*string, []string, error) {
|
func (s *SubService) GetSubs(subId string) (*string, []string, error) {
|
||||||
@@ -35,29 +25,14 @@ func (s *SubService) GetSubs(subId string) (*string, []string, error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
links := []Link{}
|
|
||||||
err = json.Unmarshal([]byte(client.Links), &links)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
clientInfo := ""
|
clientInfo := ""
|
||||||
subShowInfo, _ := s.SettingService.GetSubShowInfo()
|
subShowInfo, _ := s.SettingService.GetSubShowInfo()
|
||||||
if subShowInfo {
|
if subShowInfo {
|
||||||
clientInfo = s.getClientInfo(client)
|
clientInfo = s.getClientInfo(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result string
|
linksArray := s.LinkService.GetLinks(&client.Links, "all", clientInfo)
|
||||||
for _, link := range links {
|
result := strings.Join(linksArray, "\n")
|
||||||
switch link.Type {
|
|
||||||
case "external":
|
|
||||||
result += fmt.Sprintln(link.Uri)
|
|
||||||
case "sub":
|
|
||||||
result += s.getExternalSub(link.Uri)
|
|
||||||
case "local":
|
|
||||||
result += fmt.Sprintln(s.addClientInfo(link.Uri, clientInfo))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var headers []string
|
var headers []string
|
||||||
updateInterval, _ := s.SettingService.GetSubUpdates()
|
updateInterval, _ := s.SettingService.GetSubUpdates()
|
||||||
@@ -90,80 +65,6 @@ func (s *SubService) getClientInfo(c *model.Client) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) addClientInfo(uri string, clientInfo string) string {
|
|
||||||
protocol := strings.Split(uri, "://")
|
|
||||||
if len(protocol) < 2 {
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
switch protocol[0] {
|
|
||||||
case "vmess":
|
|
||||||
var vmessJson map[string]interface{}
|
|
||||||
config, err := base64.StdEncoding.DecodeString(protocol[1])
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error decoding vmess content:", err)
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(config, &vmessJson)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error decoding vmess content:", err)
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
vmessJson["ps"] = vmessJson["ps"].(string) + clientInfo
|
|
||||||
result, err := json.MarshalIndent(vmessJson, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error decoding vmess + clientInfo content:", err)
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
return "vmess://" + base64.StdEncoding.EncodeToString(result)
|
|
||||||
default:
|
|
||||||
return uri + clientInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) getExternalSub(url string) string {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
|
|
||||||
// Make the HTTP request
|
|
||||||
response, err := client.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error making HTTP request:", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
// Read the response body
|
|
||||||
body, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error reading response body:", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the content is Base64 encoded
|
|
||||||
isBase64 := s.isBase64Encoded(string(body))
|
|
||||||
if isBase64 {
|
|
||||||
// Decode Base64 content
|
|
||||||
decodedText, err := base64.StdEncoding.DecodeString(string(body))
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error decoding Base64 content:", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(decodedText)
|
|
||||||
} else {
|
|
||||||
return string(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to check if a string is Base64 encoded
|
|
||||||
func (s *SubService) isBase64Encoded(str string) bool {
|
|
||||||
_, err := base64.StdEncoding.DecodeString(str)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) formatTraffic(trafficBytes int64) string {
|
func (s *SubService) formatTraffic(trafficBytes int64) string {
|
||||||
if trafficBytes < 1024 {
|
if trafficBytes < 1024 {
|
||||||
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
|
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "encoding/base64"
|
||||||
|
|
||||||
|
// Function to return decoded bytes if a string is Base64 encoded
|
||||||
|
func StrOrBase64Encoded(str string) string {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(str)
|
||||||
|
if err == nil {
|
||||||
|
return string(decoded)
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func B64StrToByte(str string) ([]byte, error) {
|
||||||
|
return base64.StdEncoding.DecodeString(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ByteToB64Str(b []byte) string {
|
||||||
|
return base64.StdEncoding.EncodeToString(b)
|
||||||
|
}
|
||||||
@@ -0,0 +1,453 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"s-ui/util/common"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetOutbound(uri string, i int) (*map[string]interface{}, string, error) {
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err == nil {
|
||||||
|
switch u.Scheme {
|
||||||
|
case "vmess":
|
||||||
|
return vmess(u.Host, i)
|
||||||
|
case "vless":
|
||||||
|
return vless(u, i)
|
||||||
|
case "trojan":
|
||||||
|
return trojan(u, i)
|
||||||
|
case "hy", "hysteria":
|
||||||
|
return hy(u, i)
|
||||||
|
case "hy2", "hysteria2":
|
||||||
|
return hy2(u, i)
|
||||||
|
case "tuic":
|
||||||
|
return tuic(u, i)
|
||||||
|
case "ss", "shadowsocks":
|
||||||
|
return ss(u, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, "", common.NewError("Unsupported link format")
|
||||||
|
}
|
||||||
|
|
||||||
|
func vmess(data string, i int) (*map[string]interface{}, string, error) {
|
||||||
|
dataByte, err := B64StrToByte(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
var dataJson map[string]interface{}
|
||||||
|
err = json.Unmarshal(dataByte, &dataJson)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
transport := map[string]interface{}{}
|
||||||
|
tp_net, _ := dataJson["net"].(string)
|
||||||
|
tp_type, _ := dataJson["type"].(string)
|
||||||
|
tp_host, _ := dataJson["host"].(string)
|
||||||
|
tp_path, _ := dataJson["path"].(string)
|
||||||
|
switch strings.ToLower(tp_net) {
|
||||||
|
case "tcp", "":
|
||||||
|
if tp_type == "http" {
|
||||||
|
transport["type"] = tp_type
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["host"] = strings.Split(tp_host, ",")
|
||||||
|
}
|
||||||
|
transport["path"] = tp_path
|
||||||
|
}
|
||||||
|
case "http", "h2":
|
||||||
|
transport["type"] = "http"
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["host"] = strings.Split(tp_host, ",")
|
||||||
|
}
|
||||||
|
transport["path"] = tp_path
|
||||||
|
case "ws":
|
||||||
|
transport["type"] = tp_net
|
||||||
|
transport["path"] = tp_path
|
||||||
|
transport["early_data_header_name"] = "Sec-WebSocket-Protocol"
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["headers"] = map[string]interface{}{
|
||||||
|
"Host": tp_host,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "quic":
|
||||||
|
transport["type"] = tp_net
|
||||||
|
case "grpc":
|
||||||
|
transport["type"] = tp_net
|
||||||
|
transport["service_name"] = tp_path
|
||||||
|
case "httpupgrade":
|
||||||
|
transport["type"] = tp_net
|
||||||
|
transport["path"] = tp_path
|
||||||
|
transport["host"] = tp_host
|
||||||
|
default:
|
||||||
|
return nil, "", common.NewError("Invalid vmess")
|
||||||
|
}
|
||||||
|
tls := map[string]interface{}{}
|
||||||
|
vmess_tls, _ := dataJson["tls"].(string)
|
||||||
|
if vmess_tls == "tls" {
|
||||||
|
tls["enabled"] = true
|
||||||
|
tls_sni, _ := dataJson["sni"].(string)
|
||||||
|
tls_alpn, _ := dataJson["alpn"].(string)
|
||||||
|
_, tls_insecure := dataJson["allowInsecure"]
|
||||||
|
tls_fp, _ := dataJson["fp"].(string)
|
||||||
|
if len(tls_sni) > 0 {
|
||||||
|
tls["server_name"] = tls_sni
|
||||||
|
}
|
||||||
|
if len(tls_alpn) > 0 {
|
||||||
|
tls["alpn"] = strings.Split(tls_alpn, ",")
|
||||||
|
}
|
||||||
|
if tls_insecure {
|
||||||
|
tls["insecure"] = true
|
||||||
|
}
|
||||||
|
if len(tls_fp) > 0 {
|
||||||
|
tls["utls"] = map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"fingerprint": tls_fp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag, _ := dataJson["ps"].(string)
|
||||||
|
tag = fmt.Sprintf("%d.%s", i, tag)
|
||||||
|
alter_id, ok := dataJson["aid"].(int)
|
||||||
|
if !ok {
|
||||||
|
alter_id = 0
|
||||||
|
}
|
||||||
|
vmess := map[string]interface{}{
|
||||||
|
"type": "vmess",
|
||||||
|
"tag": tag,
|
||||||
|
"server": dataJson["add"],
|
||||||
|
"server_port": dataJson["port"],
|
||||||
|
"uuid": dataJson["id"],
|
||||||
|
"security": "auto",
|
||||||
|
"alter_id": alter_id,
|
||||||
|
"tls": tls,
|
||||||
|
"transport": transport,
|
||||||
|
}
|
||||||
|
return &vmess, tag, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func vless(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
security := query.Get("security")
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 80
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
} else {
|
||||||
|
if security == "tls" || security == "reality" {
|
||||||
|
port = 443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tp_type := query.Get("type")
|
||||||
|
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
vless := map[string]interface{}{
|
||||||
|
"type": "vless",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"uuid": u.User.Username(),
|
||||||
|
"flow": query.Get("flow"),
|
||||||
|
"tls": getTls(security, &query),
|
||||||
|
"transport": getTransport(tp_type, &query),
|
||||||
|
}
|
||||||
|
return &vless, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trojan(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
security := query.Get("security")
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 80
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
} else {
|
||||||
|
if security == "tls" || security == "reality" {
|
||||||
|
port = 443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tp_type := query.Get("type")
|
||||||
|
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
trojan := map[string]interface{}{
|
||||||
|
"type": "trojan",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"password": u.User.Username(),
|
||||||
|
"tls": getTls(security, &query),
|
||||||
|
"transport": getTransport(tp_type, &query),
|
||||||
|
}
|
||||||
|
return &trojan, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hy(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 443
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tls := map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"server_name": query.Get("peer"),
|
||||||
|
}
|
||||||
|
alpn := query.Get("alpn")
|
||||||
|
insecure := query.Get("insecure")
|
||||||
|
if len(alpn) > 0 {
|
||||||
|
tls["alpn"] = strings.Split(alpn, ",")
|
||||||
|
}
|
||||||
|
if insecure == "1" || insecure == "true" {
|
||||||
|
tls["insecure"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
hy := map[string]interface{}{
|
||||||
|
"type": "hysteria",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"obfs": query.Get("obfsParam"),
|
||||||
|
"auth_str": query.Get("auth"),
|
||||||
|
"tls": tls,
|
||||||
|
}
|
||||||
|
down, _ := strconv.Atoi(query.Get("downmbps"))
|
||||||
|
up, _ := strconv.Atoi(query.Get("upmbps"))
|
||||||
|
recv_window_conn, _ := strconv.Atoi(query.Get("recv_window_conn"))
|
||||||
|
recv_window, _ := strconv.Atoi(query.Get("recv_window"))
|
||||||
|
if down > 0 {
|
||||||
|
hy["down_mbps"] = down
|
||||||
|
}
|
||||||
|
if up > 0 {
|
||||||
|
hy["up_mbps"] = up
|
||||||
|
}
|
||||||
|
if recv_window_conn > 0 {
|
||||||
|
hy["recv_window_conn"] = recv_window_conn
|
||||||
|
}
|
||||||
|
if recv_window > 0 {
|
||||||
|
hy["recv_window"] = recv_window
|
||||||
|
}
|
||||||
|
return &hy, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hy2(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 443
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tls := map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"server_name": query.Get("sni"),
|
||||||
|
}
|
||||||
|
alpn := query.Get("alpn")
|
||||||
|
insecure := query.Get("insecure")
|
||||||
|
if len(alpn) > 0 {
|
||||||
|
tls["alpn"] = strings.Split(alpn, ",")
|
||||||
|
}
|
||||||
|
if insecure == "1" || insecure == "true" {
|
||||||
|
tls["insecure"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
hy2 := map[string]interface{}{
|
||||||
|
"type": "hysteria2",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"password": u.User.Username(),
|
||||||
|
"tls": tls,
|
||||||
|
}
|
||||||
|
down, _ := strconv.Atoi(query.Get("downmbps"))
|
||||||
|
up, _ := strconv.Atoi(query.Get("upmbps"))
|
||||||
|
obfs := query.Get("obfs")
|
||||||
|
if down > 0 {
|
||||||
|
hy2["down_mbps"] = down
|
||||||
|
}
|
||||||
|
if up > 0 {
|
||||||
|
hy2["up_mbps"] = up
|
||||||
|
}
|
||||||
|
if obfs == "salamander" {
|
||||||
|
hy2["obfs"] = map[string]interface{}{
|
||||||
|
"type": "salamander",
|
||||||
|
"password": query.Get("obfs-password"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &hy2, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tuic(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 443
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tls := map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"server_name": query.Get("sni"),
|
||||||
|
}
|
||||||
|
alpn := query.Get("alpn")
|
||||||
|
insecure := query.Get("allow_insecure")
|
||||||
|
disable_sni := query.Get("disable_sni")
|
||||||
|
if len(alpn) > 0 {
|
||||||
|
tls["alpn"] = strings.Split(alpn, ",")
|
||||||
|
}
|
||||||
|
if insecure == "1" || insecure == "true" {
|
||||||
|
tls["insecure"] = true
|
||||||
|
}
|
||||||
|
if disable_sni == "1" || disable_sni == "true" {
|
||||||
|
tls["disable_sni"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
password, _ := u.User.Password()
|
||||||
|
tuic := map[string]interface{}{
|
||||||
|
"type": "tuic",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"uuid": u.User.Username(),
|
||||||
|
"password": password,
|
||||||
|
"congestion_control": query.Get("congestion_control"),
|
||||||
|
"udp_relay_mode": query.Get("udp_relay_mode"),
|
||||||
|
"tls": tls,
|
||||||
|
}
|
||||||
|
return &tuic, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ss(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 443
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
}
|
||||||
|
method := u.User.Username()
|
||||||
|
password, ok := u.User.Password()
|
||||||
|
if !ok {
|
||||||
|
decrypted := StrOrBase64Encoded(method)
|
||||||
|
decrypted_arr := strings.Split(decrypted, ":")
|
||||||
|
if len(decrypted_arr) > 1 {
|
||||||
|
method = decrypted_arr[0]
|
||||||
|
password = strings.Join(decrypted_arr[1:], ":")
|
||||||
|
} else {
|
||||||
|
return nil, "", common.NewError("Unsupported shadowsocks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
ss := map[string]interface{}{
|
||||||
|
"type": "shadowsocks",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"method": method,
|
||||||
|
"password": password,
|
||||||
|
}
|
||||||
|
|
||||||
|
v2ray_type := query.Get("type")
|
||||||
|
if len(v2ray_type) > 0 {
|
||||||
|
pl_arr := []string{}
|
||||||
|
host_header := query.Get("host")
|
||||||
|
if query.Get("security") == "tls" {
|
||||||
|
pl_arr = append(pl_arr, "tls")
|
||||||
|
}
|
||||||
|
if v2ray_type == "quic" {
|
||||||
|
pl_arr = append(pl_arr, "mode=quic")
|
||||||
|
}
|
||||||
|
if len(host_header) > 0 {
|
||||||
|
pl_arr = append(pl_arr, "host="+host_header)
|
||||||
|
}
|
||||||
|
ss["plugin"] = "v2ray-plugin"
|
||||||
|
ss["plugin_opts"] = strings.Join(pl_arr, ";")
|
||||||
|
}
|
||||||
|
plugin := query.Get("plugin")
|
||||||
|
if len(plugin) > 0 {
|
||||||
|
pl_arr := strings.Split(plugin, ";")
|
||||||
|
if len(pl_arr) > 0 {
|
||||||
|
ss["plugin"] = pl_arr[0]
|
||||||
|
ss["plugin_opts"] = strings.Join(pl_arr[1:], ";")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ss, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTransport(tp_type string, q *url.Values) *map[string]interface{} {
|
||||||
|
transport := map[string]interface{}{}
|
||||||
|
tp_host := q.Get("host")
|
||||||
|
tp_path := q.Get("path")
|
||||||
|
switch strings.ToLower(tp_type) {
|
||||||
|
case "tcp", "":
|
||||||
|
if q.Get("headerType") == "http" {
|
||||||
|
transport["type"] = "http"
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["host"] = strings.Split(tp_host, ",")
|
||||||
|
}
|
||||||
|
transport["path"] = tp_path
|
||||||
|
}
|
||||||
|
case "http", "h2":
|
||||||
|
transport["type"] = "http"
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["host"] = strings.Split(tp_host, ",")
|
||||||
|
}
|
||||||
|
transport["path"] = tp_path
|
||||||
|
case "ws":
|
||||||
|
transport["type"] = "ws"
|
||||||
|
transport["path"] = tp_path
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["headers"] = map[string]interface{}{
|
||||||
|
"Host": tp_host,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "quic":
|
||||||
|
transport["type"] = "quic"
|
||||||
|
case "grpc":
|
||||||
|
transport["type"] = "grpc"
|
||||||
|
transport["service_name"] = q.Get("serviceName")
|
||||||
|
case "httpupgrade":
|
||||||
|
transport["type"] = "httpupgrade"
|
||||||
|
transport["path"] = tp_path
|
||||||
|
transport["host"] = tp_host
|
||||||
|
}
|
||||||
|
return &transport
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTls(security string, q *url.Values) *map[string]interface{} {
|
||||||
|
tls := map[string]interface{}{}
|
||||||
|
tls_fp := q.Get("fp")
|
||||||
|
tls_sni := q.Get("sni")
|
||||||
|
tls_insecure := q.Get("allowInsecure")
|
||||||
|
tls_alpn := q.Get("alpn")
|
||||||
|
switch security {
|
||||||
|
case "tls":
|
||||||
|
tls["enabled"] = true
|
||||||
|
case "reality":
|
||||||
|
tls["enabled"] = true
|
||||||
|
tls["reality"] = map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"public_key": q.Get("pbk"),
|
||||||
|
"short_id": q.Get("sid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(tls_sni) > 0 {
|
||||||
|
tls["server_name"] = tls_sni
|
||||||
|
}
|
||||||
|
if len(tls_alpn) > 0 {
|
||||||
|
tls["alpn"] = strings.Split(tls_alpn, ",")
|
||||||
|
}
|
||||||
|
if tls_insecure == "1" || tls_insecure == "true" {
|
||||||
|
tls["insecure"] = true
|
||||||
|
}
|
||||||
|
if len(tls_fp) > 0 {
|
||||||
|
tls["utls"] = map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"fingerprint": tls_fp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &tls
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('out.addr')"
|
||||||
|
hide-details
|
||||||
|
required
|
||||||
|
v-model="addr.server">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('out.port')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
required
|
||||||
|
v-model.number="addr.server_port"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionRemark">
|
||||||
|
<v-text-field
|
||||||
|
label="Remark"
|
||||||
|
hide-details
|
||||||
|
v-model="addr.remark">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionTLS">
|
||||||
|
<v-switch
|
||||||
|
:label="$t('tls.enable')"
|
||||||
|
color="primary"
|
||||||
|
hide-details
|
||||||
|
@update:model-value="updateTls($event)"
|
||||||
|
v-model="addr.tls" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionSNI">
|
||||||
|
<v-text-field
|
||||||
|
label="SNI"
|
||||||
|
hide-details
|
||||||
|
v-model="addr.server_name">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionInsecure">
|
||||||
|
<v-switch
|
||||||
|
:label="$t('tls.insecure')"
|
||||||
|
hide-details
|
||||||
|
color="primary"
|
||||||
|
v-model="addr.insecure" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-col cols="auto" align="end" justify="center">
|
||||||
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" hide-details variant="tonal">Address Options</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionRemark" color="primary" label="Remark" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item v-if="hasTls">
|
||||||
|
<v-switch v-model="optionTLS" color="primary" :label="$t('objects.tls')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item v-if="addr.tls">
|
||||||
|
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item v-if="addr.tls">
|
||||||
|
<v-switch v-model="optionInsecure" color="primary" :label="$t('tls.insecure')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
props: ['addr', 'hasTls'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menu: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
optionTLS: {
|
||||||
|
get(): boolean { return this.$props.addr.tls != undefined },
|
||||||
|
set(v:boolean) { this.$props.addr.tls = v ? true : undefined; this.updateTls(v) }
|
||||||
|
},
|
||||||
|
optionSNI: {
|
||||||
|
get(): boolean { return this.$props.addr.server_name != undefined },
|
||||||
|
set(v:boolean) { this.$props.addr.server_name = v ? '' : undefined }
|
||||||
|
},
|
||||||
|
optionRemark: {
|
||||||
|
get(): boolean { return this.$props.addr.remark != undefined },
|
||||||
|
set(v:boolean) { this.$props.addr.remark = v ? '' : undefined }
|
||||||
|
},
|
||||||
|
optionInsecure: {
|
||||||
|
get(): boolean { return this.$props.addr.insecure != undefined },
|
||||||
|
set(v:boolean) { this.$props.addr.insecure = v ? false : undefined }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateTls(v:boolean) {
|
||||||
|
if (!v) {
|
||||||
|
delete this.$props.addr.insecure
|
||||||
|
delete this.$props.addr.server_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<template>
|
||||||
|
<v-card :subtitle="type">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.SOCKS">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="['4','4a','5']"
|
||||||
|
:label="$t('version')"
|
||||||
|
v-model="inData.outJson.version">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="needNetwork">
|
||||||
|
<Network :data="inData.outJson" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="needUot">
|
||||||
|
<UoT :data="inData.outJson" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.HTTP">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('transport.path')"
|
||||||
|
hide-details
|
||||||
|
v-model="inData.outJson.path">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.VMess || type == inTypes.VLESS">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('types.vless.udpEnc')"
|
||||||
|
:items="['none','packetaddr','xudp']"
|
||||||
|
v-model="packet_encoding">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<template v-if="type == inTypes.VMess">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('types.vmess.security')"
|
||||||
|
:items="vmessSecurities"
|
||||||
|
v-model="inData.outJson.security">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="inData.outJson.global_padding" color="primary" :label="$t('types.vmess.globalPadding')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="inData.outJson.authenticated_length" color="primary" :label="$t('types.vmess.authLen')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</template>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.Hysteria">
|
||||||
|
<v-text-field
|
||||||
|
label="Recv window"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
v-model.number="inData.outJson.recv_window">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<template v-if="type == inTypes.TUIC">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="UDP Relay Mode"
|
||||||
|
:items="['native', 'quic']"
|
||||||
|
clearable
|
||||||
|
@click:clear="delete inData.outJson.udp_relay_mode"
|
||||||
|
v-model="inData.outJson.udp_relay_mode">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" label="UDP Over Stream" v-model="inData.outJson.udp_over_stream" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</template>
|
||||||
|
</v-row>
|
||||||
|
<Headers :data="inData.outJson" v-if="type == inTypes.HTTP" />
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { InTypes } from '@/types/inbounds'
|
||||||
|
import Network from './Network.vue'
|
||||||
|
import UoT from './UoT.vue'
|
||||||
|
import Headers from './Headers.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['inData', 'type'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inTypes: InTypes,
|
||||||
|
vmessSecurities: [
|
||||||
|
"auto",
|
||||||
|
"none",
|
||||||
|
"zero",
|
||||||
|
"aes-128-gcm",
|
||||||
|
"aes-128-ctr",
|
||||||
|
"chacha20-poly1305",
|
||||||
|
],
|
||||||
|
haveNetwork: [
|
||||||
|
InTypes.SOCKS,
|
||||||
|
InTypes.Shadowsocks,
|
||||||
|
InTypes.VMess,
|
||||||
|
InTypes.Trojan,
|
||||||
|
InTypes.Hysteria,
|
||||||
|
InTypes.VLESS,
|
||||||
|
InTypes.TUIC,
|
||||||
|
InTypes.Hysteria2,
|
||||||
|
],
|
||||||
|
havUoT: [
|
||||||
|
InTypes.SOCKS,
|
||||||
|
InTypes.Shadowsocks,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
needNetwork():boolean { return this.haveNetwork.includes(this.$props.type) },
|
||||||
|
needUot():boolean { return this.havUoT.includes(this.$props.type) },
|
||||||
|
packet_encoding: {
|
||||||
|
get() { return this.$props.inData.outJson.packet_encoding != undefined ? this.$props.inData.outJson.packet_encoding : 'none'; },
|
||||||
|
set(v:string) { this.$props.inData.outJson.packet_encoding = v != "none" ? v : undefined }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Network, UoT, Headers }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,342 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="Client JSON Template">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="3" lg="2">
|
||||||
|
<v-select
|
||||||
|
v-model="ruleToDirect"
|
||||||
|
:items="geoList"
|
||||||
|
label="Rules To Direct"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="3" lg="2">
|
||||||
|
<v-select
|
||||||
|
v-model="ruleToBlock"
|
||||||
|
:items="geoList"
|
||||||
|
label="Rules To Block"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="enableLog">
|
||||||
|
<v-col cols="12" sm="6" md="3" lg="2">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('basic.log.level')"
|
||||||
|
:items="levels"
|
||||||
|
v-model="subJsonExt.log.level">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="3" lg="2">
|
||||||
|
<v-switch v-model="subJsonExt.log.timestamp" color="primary" label="Timestamp" hide-details />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="enableDns">
|
||||||
|
<v-col cols="12" sm="6" md="3" lg="2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="proxyDns"
|
||||||
|
hide-details
|
||||||
|
label="Golbal DNS"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="3" lg="2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="directDns"
|
||||||
|
hide-details
|
||||||
|
clearable
|
||||||
|
label="Direct DNS"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="3" lg="2" v-if="directDns.length>0">
|
||||||
|
<v-select
|
||||||
|
v-model="dnsToDirect"
|
||||||
|
:items="geositeList"
|
||||||
|
label="DNS To Direct"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" hide-details variant="tonal">Options</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="enableLog" color="primary" label="Log" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="enableDns" color="primary" label="DNS" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="enableExp" color="primary" label="Experimental" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
props: ['settings'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menu: false,
|
||||||
|
subJsonExt: <any>{},
|
||||||
|
levels: ["trace", "debug", "info", "warn", "error", "fatal", "panic"],
|
||||||
|
defaultLog: {
|
||||||
|
"level": "info",
|
||||||
|
"timestamp": true
|
||||||
|
},
|
||||||
|
defaultExp: {
|
||||||
|
"clash_api": {
|
||||||
|
"external_controller": "127.0.0.1:9090",
|
||||||
|
"external_ui": "ui",
|
||||||
|
"secret": "",
|
||||||
|
"external_ui_download_url": "https://mirror.ghproxy.com/https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip",
|
||||||
|
"external_ui_download_detour": "direct",
|
||||||
|
"default_mode": "rule"
|
||||||
|
},
|
||||||
|
"cache_file": {
|
||||||
|
"enabled": true,
|
||||||
|
"store_fakeip": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultDns: {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"tag": "ProxyDns",
|
||||||
|
"address": "8.8.8.8",
|
||||||
|
"detour": "proxy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "block",
|
||||||
|
"address": "rcode://success"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"server": "ProxyDns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "ProxyDns",
|
||||||
|
"strategy": "prefer_ipv4"
|
||||||
|
},
|
||||||
|
geositeList: [
|
||||||
|
{ title: "Private", value: "geosite-private" },
|
||||||
|
{ title: "Ads", value: "geosite-ads" },
|
||||||
|
{ title: "Iran", value: "geosite-ir" },
|
||||||
|
{ title: "China", value: "geosite-cn" },
|
||||||
|
{ title: "Vietnam", value: "geosite-vn" },
|
||||||
|
],
|
||||||
|
geoList: [
|
||||||
|
{ title: "DNS-Private", value: "geoip-private" },
|
||||||
|
{ title: "IP-Private", value: "geosite-private" },
|
||||||
|
{ title: "DNS-Ads", value: "geosite-ads" },
|
||||||
|
{ title: "DNS-Iran", value: "geosite-ir" },
|
||||||
|
{ title: "IP-Iran", value: "geoip-ir" },
|
||||||
|
{ title: "DNS-China", value: "geosite-cn" },
|
||||||
|
{ title: "IP-China", value: "geoip-cn" },
|
||||||
|
{ title: "DNS-Vietnam", value: "geosite-vn" },
|
||||||
|
{ title: "IP-Vietnam", value: "geoip-vn" },
|
||||||
|
],
|
||||||
|
geo: [
|
||||||
|
{
|
||||||
|
tag: "geosite-ads",
|
||||||
|
type: "remote",
|
||||||
|
format: "binary",
|
||||||
|
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/category-ads-all.srs",
|
||||||
|
download_detour: "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "geosite-private",
|
||||||
|
type: "remote",
|
||||||
|
format: "binary",
|
||||||
|
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/private.srs",
|
||||||
|
download_detour: "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "geosite-ir",
|
||||||
|
type: "remote",
|
||||||
|
format: "binary",
|
||||||
|
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/category-ir.srs",
|
||||||
|
download_detour: "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "geosite-cn",
|
||||||
|
type: "remote",
|
||||||
|
format: "binary",
|
||||||
|
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/cn.srs",
|
||||||
|
download_detour: "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "geosite-vn",
|
||||||
|
type: "remote",
|
||||||
|
format: "binary",
|
||||||
|
url: "https://github.com/Thaomtam/Geosite-vn/raw/rule-set/Geosite-vn.srs",
|
||||||
|
download_detour: "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "geoip-private",
|
||||||
|
type: "remote",
|
||||||
|
format: "binary",
|
||||||
|
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/private.srs",
|
||||||
|
download_detour: "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "geoip-ir",
|
||||||
|
type: "remote",
|
||||||
|
format: "binary",
|
||||||
|
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/ir.srs",
|
||||||
|
download_detour: "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "geoip-cn",
|
||||||
|
type: "remote",
|
||||||
|
format: "binary",
|
||||||
|
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/cn.srs",
|
||||||
|
download_detour: "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "geoip-vn",
|
||||||
|
type: "remote",
|
||||||
|
format: "binary",
|
||||||
|
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/vn.srs",
|
||||||
|
download_detour: "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
enableLog: {
|
||||||
|
get() :boolean { return this.subJsonExt?.log != undefined },
|
||||||
|
set(v:boolean) { v ? this.subJsonExt.log = this.defaultLog : delete this.subJsonExt.log }
|
||||||
|
},
|
||||||
|
enableDns: {
|
||||||
|
get() :boolean { return this.subJsonExt?.dns != undefined },
|
||||||
|
set(v:boolean) { v ? this.subJsonExt.dns = this.defaultDns : delete this.subJsonExt.dns }
|
||||||
|
},
|
||||||
|
enableExp: {
|
||||||
|
get() :boolean { return this.subJsonExt?.experimental != undefined },
|
||||||
|
set(v:boolean) { v ? this.subJsonExt.experimental = this.defaultExp : delete this.subJsonExt.experimental }
|
||||||
|
},
|
||||||
|
dns():any { return this.subJsonExt?.dns?? undefined },
|
||||||
|
proxyDns: {
|
||||||
|
get() :string { return this.dns?.servers[0]?.address?? "" },
|
||||||
|
set(v:string) { this.dns.servers[0].address = v.length>0 ? v : "8.8.8.8" }
|
||||||
|
},
|
||||||
|
directDns: {
|
||||||
|
get() :string { return this.dns?.servers?.findLast((d:any) => d.tag == "DirectDns")?.address?? "" },
|
||||||
|
set(v:string) {
|
||||||
|
const sIndex = this.dns.servers.findIndex((d:any) => d.tag == "DirectDns")
|
||||||
|
if (v?.length>0) {
|
||||||
|
if (sIndex === -1) {
|
||||||
|
this.dns.servers.push({ tag: "DirectDns", address: v, detour: "direct" })
|
||||||
|
this.dns.rules.push({ clash_mode: "Direct", server: "DirectDns" })
|
||||||
|
} else {
|
||||||
|
this.dns.servers[sIndex].address = v
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.dns.servers.splice(sIndex,1)
|
||||||
|
this.dns.rules = this.dns.rules.filter((r:any) => r.server != "DirectDns")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dnsToDirect: {
|
||||||
|
get() :string[] {
|
||||||
|
const ruleIndex = this.dns?.rules?.findIndex((r:any) => r.server == "DirectDns" && Object.hasOwn(r,'rule_set'))
|
||||||
|
return ruleIndex >= 0 ? this.dns.rules[ruleIndex].rule_set : []
|
||||||
|
},
|
||||||
|
set(v:string[]) {
|
||||||
|
const ruleIndex = this.dns?.rules?.findIndex((r:any) => r.server == "DirectDns" && Object.hasOwn(r,'rule_set'))
|
||||||
|
if (v.length>0) {
|
||||||
|
if (ruleIndex >= 0){
|
||||||
|
this.dns.rules[ruleIndex].rule_set = v
|
||||||
|
} else {
|
||||||
|
this.dns.rules.push({ rule_set: v, server: "DirectDns" })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ruleIndex != -1) this.dns.rules.splice(ruleIndex,1)
|
||||||
|
}
|
||||||
|
this.updateRuleSets()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules():any { return this.subJsonExt?.rules?? undefined },
|
||||||
|
ruleToDirect: {
|
||||||
|
get() :string[] {
|
||||||
|
const ruleIndex = this.rules?.findIndex((r:any) => r.outbound == "direct" && Object.hasOwn(r,'rule_set'))
|
||||||
|
return ruleIndex >= 0 ? this.rules[ruleIndex].rule_set : []
|
||||||
|
},
|
||||||
|
set(v:string[]) {
|
||||||
|
const ruleIndex = this.rules?.findIndex((r:any) => r.outbound == "direct" && Object.hasOwn(r,'rule_set'))
|
||||||
|
if (v.length>0) {
|
||||||
|
if (ruleIndex >= 0){
|
||||||
|
this.rules[ruleIndex].rule_set = v
|
||||||
|
} else {
|
||||||
|
if (this.rules == undefined) this.subJsonExt.rules = []
|
||||||
|
this.rules.push({ rule_set: v, outbound: "direct" })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ruleIndex != -1) this.rules.splice(ruleIndex,1)
|
||||||
|
}
|
||||||
|
this.updateRuleSets()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ruleToBlock: {
|
||||||
|
get() :string[] {
|
||||||
|
const ruleIndex = this.rules?.findIndex((r:any) => r.outbound == "block" && Object.hasOwn(r,'rule_set'))
|
||||||
|
return ruleIndex >= 0 ? this.rules[ruleIndex].rule_set : []
|
||||||
|
},
|
||||||
|
set(v:string[]) {
|
||||||
|
const ruleIndex = this.rules?.findIndex((r:any) => r.outbound == "block" && Object.hasOwn(r,'rule_set'))
|
||||||
|
if (v.length>0) {
|
||||||
|
if (ruleIndex >= 0){
|
||||||
|
this.rules[ruleIndex].rule_set = v
|
||||||
|
} else {
|
||||||
|
if (this.rules == undefined) this.subJsonExt.rules = []
|
||||||
|
this.rules.push({ rule_set: v, outbound: "block" })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ruleIndex != -1) this.rules.splice(ruleIndex,1)
|
||||||
|
}
|
||||||
|
this.updateRuleSets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateRuleSets(){
|
||||||
|
let tags = <string[]>[]
|
||||||
|
if (this.dns?.rules?.length>0) this.dns.rules.forEach((r:any) => { if (r.rule_set) tags.push(...r.rule_set) })
|
||||||
|
if (this.rules?.length>0) this.rules.forEach((r:any) => { if (r.rule_set) tags.push(...r.rule_set) })
|
||||||
|
if (tags.length>0){
|
||||||
|
this.subJsonExt.rule_set = this.geo.filter((g:any) => tags.includes(g.tag))
|
||||||
|
} else {
|
||||||
|
delete this.subJsonExt.rule_set
|
||||||
|
}
|
||||||
|
if (this.rules.length == 0) delete this.subJsonExt.rules
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
this.subJsonExt = this.$props.settings?.subJsonExt?.length>0 ? JSON.parse(this.$props.settings.subJsonExt) : <any>{}
|
||||||
|
},
|
||||||
|
watch:{
|
||||||
|
subJsonExt:{
|
||||||
|
handler(v) {
|
||||||
|
this.$props.settings.subJsonExt = Object.keys(v).length>0 ? JSON.stringify(v, null, 2) : ""
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details>{{ $t('tls.acme.options') }}</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('tls.acme.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start" v-if="tls.enabled">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start" v-if="tls.enabled">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details>{{ $t('tls.options') }}</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('tls.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details>{{ $t('tls.options') }}</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('tls.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
@@ -32,11 +32,11 @@ const saveChanges = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const oldData = computed((): any => {
|
const oldData = computed((): any => {
|
||||||
return {config: store.oldData.config, clients: store.oldData.clients, tls: store.oldData.tlsConfigs}
|
return {config: store.oldData.config, clients: store.oldData.clients, tls: store.oldData.tlsConfigs, inData: store.oldData.inData}
|
||||||
})
|
})
|
||||||
|
|
||||||
const newData = computed((): any => {
|
const newData = computed((): any => {
|
||||||
return {config: store.config, clients: store.clients, tls: store.tlsConfigs}
|
return {config: store.config, clients: store.clients, tls: store.tlsConfigs, inData: store.inData}
|
||||||
})
|
})
|
||||||
|
|
||||||
const stateChange = computed((): any => {
|
const stateChange = computed((): any => {
|
||||||
|
|||||||
@@ -5,35 +5,66 @@
|
|||||||
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
|
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text>
|
<v-card-text style="padding-top: 0; overflow-y: scroll;">
|
||||||
<v-row>
|
<v-container style="padding: 0;">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-row>
|
||||||
<v-select
|
<v-col cols="12" sm="6" md="4">
|
||||||
hide-details
|
<v-select
|
||||||
:label="$t('type')"
|
hide-details
|
||||||
:items="Object.keys(inTypes).map((key,index) => ({title: key, value: Object.values(inTypes)[index]}))"
|
:label="$t('type')"
|
||||||
v-model="inbound.type"
|
:items="Object.keys(inTypes).map((key,index) => ({title: key, value: Object.values(inTypes)[index]}))"
|
||||||
@update:modelValue="changeType">
|
v-model="inbound.type"
|
||||||
</v-select>
|
@update:modelValue="changeType">
|
||||||
</v-col>
|
</v-select>
|
||||||
<v-col cols="12" sm="6" md="4">
|
</v-col>
|
||||||
<v-text-field v-model="inbound.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
<v-col cols="12" sm="6" md="4">
|
||||||
</v-col>
|
<v-text-field v-model="inbound.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
||||||
</v-row>
|
</v-col>
|
||||||
<Listen :inbound="inbound" :inTags="inTags" />
|
</v-row>
|
||||||
<Direct v-if="inbound.type == inTypes.Direct" direction="in" :data="inbound" />
|
<v-tabs
|
||||||
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" direction="in" :data="inbound" />
|
v-if="HasInData.includes(inbound.type)"
|
||||||
<Hysteria v-if="inbound.type == inTypes.Hysteria" direction="in" :data="inbound" />
|
v-model="side"
|
||||||
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" direction="in" :data="inbound" />
|
density="compact"
|
||||||
<Naive v-if="inbound.type == inTypes.Naive" :inbound="inbound" />
|
fixed-tabs
|
||||||
<ShadowTls v-if="inbound.type == inTypes.ShadowTLS" direction="in" :data="inbound" :outTags="outTags" />
|
align-tabs="center"
|
||||||
<Tuic v-if="inbound.type == inTypes.TUIC" direction="in" :data="inbound" />
|
>
|
||||||
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
<v-tab value="s">Server Side</v-tab>
|
||||||
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
<v-tab value="c">Client Side</v-tab>
|
||||||
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" :id="id" />
|
</v-tabs>
|
||||||
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="tls_id" />
|
<v-window v-model="side" style="margin-top: 10px;">
|
||||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="in" :data="inbound" />
|
<v-window-item value="s">
|
||||||
<v-switch v-model="inboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
<Listen :inbound="inbound" :inTags="inTags" />
|
||||||
|
<Direct v-if="inbound.type == inTypes.Direct" direction="in" :data="inbound" />
|
||||||
|
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" direction="in" :data="inbound" />
|
||||||
|
<Hysteria v-if="inbound.type == inTypes.Hysteria" direction="in" :data="inbound" />
|
||||||
|
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" direction="in" :data="inbound" />
|
||||||
|
<Naive v-if="inbound.type == inTypes.Naive" :inbound="inbound" />
|
||||||
|
<ShadowTls v-if="inbound.type == inTypes.ShadowTLS" direction="in" :data="inbound" :outTags="outTags" />
|
||||||
|
<Tuic v-if="inbound.type == inTypes.TUIC" direction="in" :data="inbound" />
|
||||||
|
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
||||||
|
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
||||||
|
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" />
|
||||||
|
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="tls_id" />
|
||||||
|
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="in" :data="inbound" />
|
||||||
|
<v-switch v-model="inboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||||
|
</v-window-item>
|
||||||
|
<v-window-item value="c">
|
||||||
|
<OutJsonVue :inData="inData" :type="inbound.type" />
|
||||||
|
<v-card>
|
||||||
|
<v-card-subtitle>
|
||||||
|
Multi Domain
|
||||||
|
<v-icon @click="add_addr" icon="mdi-plus"></v-icon>
|
||||||
|
</v-card-subtitle>
|
||||||
|
<template v-for="addr,index in inData.addrs">
|
||||||
|
Address #{{ (index+1) }} <v-icon icon="mdi-delete" @click="inData.addrs.splice(index,1)" />
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<AddrVue :addr="addr" :hasTls="Object.hasOwn(inbound,'tls')" />
|
||||||
|
</template>
|
||||||
|
</v-card>
|
||||||
|
<pre dir="ltr">{{ inData }}</pre>
|
||||||
|
</v-window-item>
|
||||||
|
</v-window>
|
||||||
|
</v-container>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
@@ -59,6 +90,9 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { InTypes, createInbound } from '@/types/inbounds'
|
import { InTypes, createInbound } from '@/types/inbounds'
|
||||||
|
import { Addr, InData } from '@/plugins/inData'
|
||||||
|
import RandomUtil from '@/plugins/randomUtil'
|
||||||
|
|
||||||
import Listen from '@/components/Listen.vue'
|
import Listen from '@/components/Listen.vue'
|
||||||
import Direct from '@/components/protocols/Direct.vue'
|
import Direct from '@/components/protocols/Direct.vue'
|
||||||
import Users from '@/components/Users.vue'
|
import Users from '@/components/Users.vue'
|
||||||
@@ -68,47 +102,83 @@ import Hysteria2 from '@/components/protocols/Hysteria2.vue'
|
|||||||
import Naive from '@/components/protocols/Naive.vue'
|
import Naive from '@/components/protocols/Naive.vue'
|
||||||
import ShadowTls from '@/components/protocols/ShadowTls.vue'
|
import ShadowTls from '@/components/protocols/ShadowTls.vue'
|
||||||
import Tuic from '@/components/protocols/Tuic.vue'
|
import Tuic from '@/components/protocols/Tuic.vue'
|
||||||
import InTls from '@/components/InTLS.vue'
|
import InTls from '@/components/tls/InTLS.vue'
|
||||||
import TProxy from '@/components/protocols/TProxy.vue'
|
import TProxy from '@/components/protocols/TProxy.vue'
|
||||||
import RandomUtil from '@/plugins/randomUtil'
|
|
||||||
import Multiplex from '@/components/Multiplex.vue'
|
import Multiplex from '@/components/Multiplex.vue'
|
||||||
import Transport from '@/components/Transport.vue'
|
import Transport from '@/components/Transport.vue'
|
||||||
|
import AddrVue from '@/components/Addr.vue'
|
||||||
|
import OutJsonVue from '@/components/OutJson.vue'
|
||||||
export default {
|
export default {
|
||||||
props: ['visible', 'data', 'id', 'stats', 'inTags', 'outTags', 'tlsConfigs'],
|
props: ['visible', 'data', 'cData', 'index', 'stats', 'inTags', 'outTags', 'tlsConfigs'],
|
||||||
emits: ['close', 'save'],
|
emits: ['close', 'save'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
inbound: createInbound("direct",{ "tag": "" }),
|
inbound: createInbound("direct",{ "tag": "" }),
|
||||||
|
inData: <InData>{},
|
||||||
title: "add",
|
title: "add",
|
||||||
loading: false,
|
loading: false,
|
||||||
|
side: "s",
|
||||||
inTypes: InTypes,
|
inTypes: InTypes,
|
||||||
inboundStats: false,
|
inboundStats: false,
|
||||||
tls_id: { value: 0 },
|
tls_id: { value: 0 },
|
||||||
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
||||||
|
HasInData: [
|
||||||
|
InTypes.SOCKS,
|
||||||
|
InTypes.HTTP,
|
||||||
|
InTypes.Shadowsocks,
|
||||||
|
InTypes.VMess,
|
||||||
|
InTypes.ShadowTLS,
|
||||||
|
InTypes.Trojan,
|
||||||
|
InTypes.Hysteria,
|
||||||
|
InTypes.VLESS,
|
||||||
|
InTypes.TUIC,
|
||||||
|
InTypes.Hysteria2,
|
||||||
|
InTypes.Naive,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateData() {
|
updateData() {
|
||||||
if (this.$props.id != -1) {
|
if (this.$props.index != -1) {
|
||||||
const newData = JSON.parse(this.$props.data)
|
const newData = JSON.parse(this.$props.data)
|
||||||
this.inbound = createInbound(newData.type, newData)
|
this.inbound = createInbound(newData.type, newData)
|
||||||
this.tls_id.value = this.$props.tlsConfigs?.findLast((t:any) => t.inbounds?.includes(this.inbound.tag))?.id?? 0
|
this.tls_id.value = this.$props.tlsConfigs?.findLast((t:any) => t.inbounds?.includes(this.inbound.tag))?.id?? 0
|
||||||
|
if (this.HasInData.includes(this.inbound.type)){
|
||||||
|
this.inData = this.$props.cData?.length> 0 ? <InData>JSON.parse(this.$props.cData) : <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
||||||
|
} else {
|
||||||
|
this.inData = <InData>{id: -1}
|
||||||
|
}
|
||||||
this.title = "edit"
|
this.title = "edit"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const port = RandomUtil.randomIntRange(10000, 60000)
|
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||||
this.inbound = createInbound("direct",{ tag: "direct-"+port ,listen: "::", listen_port: port })
|
this.inbound = createInbound("direct",{ tag: "direct-"+port ,listen: "::", listen_port: port })
|
||||||
this.tls_id.value = 0
|
this.tls_id.value = 0
|
||||||
|
if (this.HasInData.includes(this.inbound.type)){
|
||||||
|
this.inData = <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
||||||
|
} else {
|
||||||
|
this.inData = <InData>{id: -1}
|
||||||
|
}
|
||||||
this.title = "add"
|
this.title = "add"
|
||||||
}
|
}
|
||||||
this.inboundStats = this.$props.stats
|
this.inboundStats = this.$props.stats
|
||||||
|
this.side = "s"
|
||||||
},
|
},
|
||||||
changeType() {
|
changeType() {
|
||||||
// Tag change only in add outbound
|
// Tag change only in add outbound
|
||||||
const tag = this.$props.id != -1 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
const tag = this.$props.index != -1 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
||||||
// Use previous data
|
// Use previous data
|
||||||
const prevConfig = { tag: tag ,listen: this.inbound.listen, listen_port: this.inbound.listen_port }
|
const prevConfig = { tag: tag ,listen: this.inbound.listen, listen_port: this.inbound.listen_port }
|
||||||
this.inbound = createInbound(this.inbound.type, prevConfig)
|
this.inbound = createInbound(this.inbound.type, prevConfig)
|
||||||
|
if (this.HasInData.includes(this.inbound.type)){
|
||||||
|
this.inData = <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
||||||
|
} else {
|
||||||
|
this.inData = <InData>{id: -1}
|
||||||
|
}
|
||||||
|
this.side = "s"
|
||||||
|
},
|
||||||
|
add_addr() {
|
||||||
|
this.inData.addrs.push(<Addr>{ server: location.hostname, server_port: this.inbound.listen_port })
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.updateData() // reset
|
this.updateData() // reset
|
||||||
@@ -116,7 +186,7 @@ export default {
|
|||||||
},
|
},
|
||||||
saveChanges() {
|
saveChanges() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.$emit('save', this.inbound, this.inboundStats, this.tls_id.value)
|
this.$emit('save', this.inbound, this.inboundStats, this.tls_id.value, this.inData)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -127,6 +197,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { Listen, InTls, Hysteria2, Naive, Direct, Shadowsocks, Users, Hysteria, ShadowTls, TProxy, Multiplex, Tuic, Transport }
|
components: {
|
||||||
|
Listen, InTls, Hysteria2, Naive, Direct, Shadowsocks,
|
||||||
|
Users, Hysteria, ShadowTls, TProxy, Multiplex, Tuic, Transport,
|
||||||
|
AddrVue, OutJsonVue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -89,7 +89,7 @@ import RandomUtil from '@/plugins/randomUtil'
|
|||||||
import Dial from '@/components/Dial.vue'
|
import Dial from '@/components/Dial.vue'
|
||||||
import Multiplex from '@/components/Multiplex.vue'
|
import Multiplex from '@/components/Multiplex.vue'
|
||||||
import Transport from '@/components/Transport.vue'
|
import Transport from '@/components/Transport.vue'
|
||||||
import OutTLS from '@/components/OutTLS.vue'
|
import OutTLS from '@/components/tls/OutTLS.vue'
|
||||||
import Direct from '@/components/protocols/Direct.vue'
|
import Direct from '@/components/protocols/Direct.vue'
|
||||||
import Socks from '@/components/protocols/Socks.vue'
|
import Socks from '@/components/protocols/Socks.vue'
|
||||||
import Http from '@/components/protocols/Http.vue'
|
import Http from '@/components/protocols/Http.vue'
|
||||||
|
|||||||
@@ -289,8 +289,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { iTls, defaultInTls } from '@/types/inTls'
|
import { iTls, defaultInTls } from '@/types/inTls'
|
||||||
import { oTls, defaultOutTls } from '@/types/outTls'
|
import { oTls, defaultOutTls } from '@/types/outTls'
|
||||||
import AcmeVue from '@/components/Acme.vue'
|
import AcmeVue from '@/components/tls/Acme.vue'
|
||||||
import EchVue from '@/components/Ech.vue'
|
import EchVue from '@/components/tls/Ech.vue'
|
||||||
import HttpUtils from '@/plugins/httputil'
|
import HttpUtils from '@/plugins/httputil'
|
||||||
import { push } from 'notivue'
|
import { push } from 'notivue'
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export interface Addr {
|
||||||
|
server: string
|
||||||
|
server_port: number
|
||||||
|
tls?: boolean
|
||||||
|
insecure?: boolean
|
||||||
|
server_name?: string
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InData {
|
||||||
|
id: number
|
||||||
|
tag: string
|
||||||
|
addrs: Addr[]
|
||||||
|
outJson: any
|
||||||
|
}
|
||||||
+283
-70
@@ -14,30 +14,29 @@ function utf8ToBase64(utf8String: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace LinkUtil {
|
export namespace LinkUtil {
|
||||||
export function linkGenerator(user: string, inbound: Inbound, tlsClient: any = null): string {
|
export function linkGenerator(user: string, inbound: Inbound, tlsClient: any = {}, addrs: any[] = []): string[] {
|
||||||
const addr = location.hostname
|
|
||||||
switch(inbound.type){
|
switch(inbound.type){
|
||||||
case InTypes.Shadowsocks:
|
case InTypes.Shadowsocks:
|
||||||
return shadowsocksLink(user,<Shadowsocks>inbound, addr)
|
return shadowsocksLink(user,<Shadowsocks>inbound, addrs)
|
||||||
case InTypes.Naive:
|
case InTypes.Naive:
|
||||||
return naiveLink(user,<Naive>inbound, addr, tlsClient)
|
return naiveLink(user,<Naive>inbound, addrs, tlsClient)
|
||||||
case InTypes.Hysteria:
|
case InTypes.Hysteria:
|
||||||
return hysteriaLink(user,<Hysteria>inbound, addr, tlsClient)
|
return hysteriaLink(user,<Hysteria>inbound, addrs, tlsClient)
|
||||||
case InTypes.Hysteria2:
|
case InTypes.Hysteria2:
|
||||||
return hysteria2Link(user,<Hysteria2>inbound, addr, tlsClient)
|
return hysteria2Link(user,<Hysteria2>inbound, addrs, tlsClient)
|
||||||
case InTypes.TUIC:
|
case InTypes.TUIC:
|
||||||
return tuicLink(user,<TUIC>inbound, addr, tlsClient)
|
return tuicLink(user,<TUIC>inbound, addrs, tlsClient)
|
||||||
case InTypes.VLESS:
|
case InTypes.VLESS:
|
||||||
return vlessLink(user,<VLESS>inbound, addr, tlsClient)
|
return vlessLink(user,<VLESS>inbound, addrs, tlsClient)
|
||||||
case InTypes.Trojan:
|
case InTypes.Trojan:
|
||||||
return trojanLink(user,<Trojan>inbound, addr, tlsClient)
|
return trojanLink(user,<Trojan>inbound, addrs, tlsClient)
|
||||||
case InTypes.VMess:
|
case InTypes.VMess:
|
||||||
return vmessLink(user,<VMess>inbound, addr, tlsClient)
|
return vmessLink(user,<VMess>inbound, addrs, tlsClient)
|
||||||
}
|
}
|
||||||
return ''
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowsocksLink(user: string, inbound: Shadowsocks, addr: string): string {
|
function shadowsocksLink(user: string, inbound: Shadowsocks, addrs: any[]): string[] {
|
||||||
const userPass = inbound.users?.find(i => i.name == user)?.password
|
const userPass = inbound.users?.find(i => i.name == user)?.password
|
||||||
const password = [userPass]
|
const password = [userPass]
|
||||||
if (inbound.method.startsWith('2022')) password.push(inbound.password)
|
if (inbound.method.startsWith('2022')) password.push(inbound.password)
|
||||||
@@ -46,17 +45,32 @@ export namespace LinkUtil {
|
|||||||
network: inbound.network?? null
|
network: inbound.network?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${addr}:${inbound.listen_port}`)
|
let links = <string[]>[]
|
||||||
for (const [key, value] of Object.entries(params)){
|
if (addrs.length == 0) {
|
||||||
if (value) {
|
const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${location.hostname}:${inbound.listen_port}`)
|
||||||
uri.searchParams.set(key, value.toString())
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hysteriaLink(user: string, inbound: Hysteria, addr: string, tlsClient: any): string {
|
function hysteriaLink(user: string, inbound: Hysteria, addrs: any[], tlsClient: any): string[] {
|
||||||
const auth = inbound.users.find(i => i.name == user)?.auth_str
|
const auth = inbound.users.find(i => i.name == user)?.auth_str
|
||||||
const params = {
|
const params = {
|
||||||
upmbps: inbound.up_mbps?? null,
|
upmbps: inbound.up_mbps?? null,
|
||||||
@@ -68,17 +82,43 @@ export namespace LinkUtil {
|
|||||||
fastopen: inbound.tcp_fast_open? 1 : 0,
|
fastopen: inbound.tcp_fast_open? 1 : 0,
|
||||||
insecure: tlsClient?.insecure ? 1 : null
|
insecure: tlsClient?.insecure ? 1 : null
|
||||||
}
|
}
|
||||||
const uri = new URL(`hysteria://${addr}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
let links = <string[]>[]
|
||||||
if (value) {
|
if (addrs.length == 0) {
|
||||||
uri.searchParams.set(key, value.toString())
|
const uri = new URL(`hysteria://${location.hostname}:${inbound.listen_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`hysteria://${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
uri.searchParams.set('peer', a.server_name)
|
||||||
|
} else {
|
||||||
|
inbound.tls.server_name ? uri.searchParams.set('peer', inbound.tls.server_name) : uri.searchParams.delete('peer')
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
uri.searchParams.set('insecure', '1')
|
||||||
|
} else {
|
||||||
|
tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hysteria2Link(user: string, inbound: Hysteria2, addr: string, tlsClient: any): string {
|
function hysteria2Link(user: string, inbound: Hysteria2, addrs: any[], tlsClient: any): string[] {
|
||||||
const password = inbound.users.find(i => i.name == user)?.password
|
const password = inbound.users.find(i => i.name == user)?.password
|
||||||
const params = {
|
const params = {
|
||||||
upmbps: inbound.up_mbps?? null,
|
upmbps: inbound.up_mbps?? null,
|
||||||
@@ -90,36 +130,85 @@ export namespace LinkUtil {
|
|||||||
fastopen: inbound.tcp_fast_open? 1 : 0,
|
fastopen: inbound.tcp_fast_open? 1 : 0,
|
||||||
insecure: tlsClient?.insecure ? 1 : null
|
insecure: tlsClient?.insecure ? 1 : null
|
||||||
}
|
}
|
||||||
const uri = new URL(`hysteria2://${password}@${addr}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
let links = <string[]>[]
|
||||||
if (value) {
|
if (addrs.length == 0) {
|
||||||
uri.searchParams.set(key, value.toString())
|
const uri = new URL(`hysteria2://${password}@${location.hostname}:${inbound.listen_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`hysteria2://${password}@${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
uri.searchParams.set('sni', a.server_name)
|
||||||
|
} else {
|
||||||
|
inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
uri.searchParams.set('insecure', '1')
|
||||||
|
} else {
|
||||||
|
tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function naiveLink(user: string, inbound: Naive, addr: string, tlsClient: any): string {
|
function naiveLink(user: string, inbound: Naive, addrs: any[], tlsClient: any): string[] {
|
||||||
const password = inbound.users.find(i => i.username == user)?.password
|
const password = inbound.users.find(i => i.username == user)?.password
|
||||||
const params = {
|
|
||||||
padding: 1,
|
let links = <string[]>[]
|
||||||
peer: inbound.tls.server_name?? null,
|
if (addrs.length == 0) {
|
||||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
const params = {
|
||||||
tfo: inbound.tcp_fast_open? 1 : 0,
|
padding: 1,
|
||||||
allowInsecure: tlsClient?.insecure ? 1 : null
|
peer: inbound.tls.server_name?? null,
|
||||||
}
|
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||||
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + addr + ":" + inbound.listen_port)}`
|
tfo: inbound.tcp_fast_open? 1 : 0,
|
||||||
const paramsArray = []
|
allowInsecure: tlsClient?.insecure ? 1 : null
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`)
|
|
||||||
}
|
}
|
||||||
|
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + location.hostname + ":" + inbound.listen_port)}`
|
||||||
|
const paramsArray = []
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
links.push(uri.toString() + "?" + paramsArray.join('&') + "#" + inbound.tag)
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const params = {
|
||||||
|
padding: 1,
|
||||||
|
peer: a.server_name?.length>0 ? a.server_name : inbound.tls.server_name?? null,
|
||||||
|
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||||
|
tfo: inbound.tcp_fast_open? 1 : 0,
|
||||||
|
allowInsecure: a.insecure ? 1 : tlsClient?.insecure ? 1 : null
|
||||||
|
}
|
||||||
|
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + a.server + ":" + a.server_port)}`
|
||||||
|
const paramsArray = []
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
links.push(uri.toString() + "?" + paramsArray.join('&') + "#" + encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return uri.toString() + "?" + paramsArray.join('&') + "#" + inbound.tag
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
function tuicLink(user: string, inbound: TUIC, addr: string, tlsClient: any): string {
|
function tuicLink(user: string, inbound: TUIC, addrs: any[], tlsClient: any): string[] {
|
||||||
const u = inbound.users.find(i => i.name == user)
|
const u = inbound.users.find(i => i.name == user)
|
||||||
const params = {
|
const params = {
|
||||||
sni: inbound.tls.server_name?? null,
|
sni: inbound.tls.server_name?? null,
|
||||||
@@ -128,14 +217,40 @@ export namespace LinkUtil {
|
|||||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||||
disable_sni: tlsClient?.disable_sni ? 1 : null
|
disable_sni: tlsClient?.disable_sni ? 1 : null
|
||||||
}
|
}
|
||||||
const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${addr}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
let links = <string[]>[]
|
||||||
if (value) {
|
if (addrs.length == 0) {
|
||||||
uri.searchParams.set(key, value.toString())
|
const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${location.hostname}:${inbound.listen_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
uri.searchParams.set('sni', a.server_name)
|
||||||
|
} else {
|
||||||
|
inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
uri.searchParams.set('allowInsecure', '1')
|
||||||
|
} else {
|
||||||
|
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTransportParams(t:Transport): any {
|
function getTransportParams(t:Transport): any {
|
||||||
@@ -171,7 +286,7 @@ export namespace LinkUtil {
|
|||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
function vlessLink(user: string, inbound: VLESS, addr: string, tlsClient: any): string {
|
function vlessLink(user: string, inbound: VLESS, addrs: any[], tlsClient: any): string[] {
|
||||||
const u = inbound.users.find(i => i.name == user)
|
const u = inbound.users.find(i => i.name == user)
|
||||||
const transport = <Transport>inbound.transport
|
const transport = <Transport>inbound.transport
|
||||||
|
|
||||||
@@ -188,17 +303,52 @@ export namespace LinkUtil {
|
|||||||
pbk: tlsClient?.reality?.public_key?? null,
|
pbk: tlsClient?.reality?.public_key?? null,
|
||||||
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
||||||
}
|
}
|
||||||
const uri = new URL(`vless://${u?.uuid}@${addr}:${inbound.listen_port}`)
|
let links = <string[]>[]
|
||||||
for (const [key, value] of Object.entries({...params, ...tParams})){
|
if (addrs.length == 0) {
|
||||||
if (value) {
|
const uri = new URL(`vless://${u?.uuid}@${location.hostname}:${inbound.listen_port}`)
|
||||||
uri.searchParams.set(key, value.toString())
|
for (const [key, value] of Object.entries({...params, ...tParams})){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`vless://${u?.uuid}@${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries({...params, ...tParams})){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.tls != undefined){
|
||||||
|
if (a.tls) {
|
||||||
|
uri.searchParams.set('security','tls')
|
||||||
|
} else {
|
||||||
|
uri.searchParams.delete('security')
|
||||||
|
uri.searchParams.delete('sni')
|
||||||
|
uri.searchParams.delete('alpn')
|
||||||
|
uri.searchParams.delete('allowInsecure')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
uri.searchParams.set('sni', a.server_name)
|
||||||
|
} else {
|
||||||
|
inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
uri.searchParams.set('allowInsecure', '1')
|
||||||
|
} else {
|
||||||
|
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function trojanLink(user: string, inbound: Trojan, addr: string, tlsClient: any): string {
|
function trojanLink(user: string, inbound: Trojan, addrs: any[], tlsClient: any): string[] {
|
||||||
const u = inbound.users.find(i => i.name == user)
|
const u = inbound.users.find(i => i.name == user)
|
||||||
const transport = <Transport>inbound.transport
|
const transport = <Transport>inbound.transport
|
||||||
|
|
||||||
@@ -214,17 +364,53 @@ export namespace LinkUtil {
|
|||||||
pbk: tlsClient?.reality?.public_key?? null,
|
pbk: tlsClient?.reality?.public_key?? null,
|
||||||
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
||||||
}
|
}
|
||||||
const uri = new URL(`trojan://${u?.password}@${addr}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries({...params, ...tParams})){
|
let links = <string[]>[]
|
||||||
if (value) {
|
if (addrs.length == 0) {
|
||||||
uri.searchParams.set(key, value.toString())
|
const uri = new URL(`trojan://${u?.password}@${location.hostname}:${inbound.listen_port}`)
|
||||||
|
for (const [key, value] of Object.entries({...params, ...tParams})){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`trojan://${u?.password}@${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries({...params, ...tParams})){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.tls != undefined){
|
||||||
|
if (a.tls) {
|
||||||
|
uri.searchParams.set('security','tls')
|
||||||
|
} else {
|
||||||
|
uri.searchParams.delete('security')
|
||||||
|
uri.searchParams.delete('sni')
|
||||||
|
uri.searchParams.delete('alpn')
|
||||||
|
uri.searchParams.delete('allowInsecure')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
uri.searchParams.set('sni', a.server_name)
|
||||||
|
} else {
|
||||||
|
inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
uri.searchParams.set('allowInsecure', '1')
|
||||||
|
} else {
|
||||||
|
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function vmessLink(user: string, inbound: VMess, addr: string, tlsClient: any): string {
|
function vmessLink(user: string, inbound: VMess, addrs: any[], tlsClient: any): string[] {
|
||||||
const u = inbound.users.find(i => i.name == user)
|
const u = inbound.users.find(i => i.name == user)
|
||||||
const transport = <Transport>inbound.transport
|
const transport = <Transport>inbound.transport
|
||||||
|
|
||||||
@@ -233,7 +419,7 @@ export namespace LinkUtil {
|
|||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
v: 2,
|
v: 2,
|
||||||
add: addr,
|
add: location.hostname,
|
||||||
aid: u?.alterId,
|
aid: u?.alterId,
|
||||||
host: tParams.host?? undefined,
|
host: tParams.host?? undefined,
|
||||||
id: u?.uuid,
|
id: u?.uuid,
|
||||||
@@ -246,6 +432,33 @@ export namespace LinkUtil {
|
|||||||
tls: Object.keys(inbound.tls).length>0? 'tls' : 'none',
|
tls: Object.keys(inbound.tls).length>0? 'tls' : 'none',
|
||||||
allowInsecure: tlsClient?.insecure ? 1 : undefined
|
allowInsecure: tlsClient?.insecure ? 1 : undefined
|
||||||
}
|
}
|
||||||
return 'vmess://' + utf8ToBase64(JSON.stringify(params, null, 2))
|
let links = <string[]>[]
|
||||||
|
if (addrs.length == 0) {
|
||||||
|
links.push('vmess://' + utf8ToBase64(JSON.stringify(params, null, 2)))
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
let newParams = {...params}
|
||||||
|
newParams.add = a.server
|
||||||
|
newParams.port = a.server_port
|
||||||
|
if (a.tls != undefined){
|
||||||
|
if (a.tls) {
|
||||||
|
newParams.tls = 'tls'
|
||||||
|
} else {
|
||||||
|
newParams.tls = 'none'
|
||||||
|
delete newParams.sni
|
||||||
|
delete newParams.allowInsecure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
newParams.sni = a.server_name
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
newParams.allowInsecure = 1
|
||||||
|
}
|
||||||
|
newParams.ps = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push('vmess://' + utf8ToBase64(JSON.stringify(newParams, null, 2)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return links
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { Hysteria, Hysteria2, Inbound, InTypes, Shadowsocks, Trojan, TUIC, VLESS, VMess, ShadowTLS } from "@/types/inbounds"
|
||||||
|
import { iTls } from "@/types/inTls"
|
||||||
|
import { oTls } from "@/types/outTls"
|
||||||
|
|
||||||
|
export function fillData(out: any, inbound: Inbound, tlsClient: any) {
|
||||||
|
if (Object.hasOwn(inbound, 'tls')) {
|
||||||
|
const inb = <any>inbound
|
||||||
|
addTls(out,inb.tls,tlsClient)
|
||||||
|
} else {
|
||||||
|
delete out.tls
|
||||||
|
}
|
||||||
|
out.type = inbound.type
|
||||||
|
out.tag = inbound.tag
|
||||||
|
out.server = location.hostname
|
||||||
|
out.server_port = inbound.listen_port
|
||||||
|
switch(inbound.type){
|
||||||
|
case InTypes.HTTP || InTypes.SOCKS:
|
||||||
|
return
|
||||||
|
case InTypes.Shadowsocks:
|
||||||
|
shadowsocksOut(out, <Shadowsocks>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.ShadowTLS:
|
||||||
|
shadowTlsOut(out, <ShadowTLS>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.Hysteria:
|
||||||
|
hysteriaOut(out, <Hysteria>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.Hysteria2:
|
||||||
|
hysteria2Out(out, <Hysteria2>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.TUIC:
|
||||||
|
tuicOut(out, <TUIC>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.VLESS:
|
||||||
|
vlessOut(out, <VLESS>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.Trojan:
|
||||||
|
trojanOut(out, <Trojan>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.VMess:
|
||||||
|
vmessOut(out, <VMess>inbound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Object.keys(out).forEach(key => delete out[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTls(out: any, tls: iTls, tlsClient: oTls){
|
||||||
|
out.tls = tlsClient
|
||||||
|
if(tls.enabled) out.tls.enabled = tls.enabled
|
||||||
|
if(tls.server_name) out.tls.server_name = tls.server_name
|
||||||
|
if(tls.alpn) out.tls.alpn = tls.alpn
|
||||||
|
if(tls.min_version) out.tls.min_version = tls.min_version
|
||||||
|
if(tls.max_version) out.tls.max_version = tls.max_version
|
||||||
|
if(tls.cipher_suites) out.tls.cipher_suites = tls.cipher_suites
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocksOut(out: any, inbound: Shadowsocks) {
|
||||||
|
out.method = inbound.method
|
||||||
|
out.multiplex = inbound.multiplex
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowTlsOut(out: any, inbound: ShadowTLS) {
|
||||||
|
if (inbound.version == 3) {
|
||||||
|
out.version = 3
|
||||||
|
} else {
|
||||||
|
Object.keys(out).forEach(key => delete out[key])
|
||||||
|
}
|
||||||
|
out.tls = { enabled: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
function hysteriaOut(out: any, inbound: Hysteria) {
|
||||||
|
out.up_mbps = inbound.down_mbps
|
||||||
|
out.down_mbps = inbound.up_mbps
|
||||||
|
out.obfs = inbound.obfs
|
||||||
|
out.recv_window_conn = inbound.recv_window_conn
|
||||||
|
out.disable_mtu_discovery = inbound.disable_mtu_discovery
|
||||||
|
}
|
||||||
|
|
||||||
|
function hysteria2Out(out: any, inbound: Hysteria2) {
|
||||||
|
out.up_mbps = inbound.down_mbps
|
||||||
|
out.down_mbps = inbound.up_mbps
|
||||||
|
out.obfs = inbound.obfs
|
||||||
|
}
|
||||||
|
|
||||||
|
function tuicOut(out: any, inbound: TUIC) {
|
||||||
|
out.congestion_control = inbound.congestion_control?? "cubic"
|
||||||
|
out.zero_rtt_handshake = inbound.zero_rtt_handshake
|
||||||
|
out.heartbeat = inbound.heartbeat
|
||||||
|
}
|
||||||
|
|
||||||
|
function vlessOut(out: any, inbound: VLESS) {
|
||||||
|
out.multiplex = inbound.multiplex
|
||||||
|
out.transport = inbound.transport
|
||||||
|
}
|
||||||
|
|
||||||
|
function trojanOut(out: any, inbound: Trojan) {
|
||||||
|
out.multiplex = inbound.multiplex
|
||||||
|
out.transport = inbound.transport
|
||||||
|
}
|
||||||
|
|
||||||
|
function vmessOut(out: any, inbound: VMess) {
|
||||||
|
out.multiplex = inbound.multiplex
|
||||||
|
out.transport = inbound.transport
|
||||||
|
}
|
||||||
@@ -37,11 +37,11 @@ export const FindDiff = {
|
|||||||
|
|
||||||
return differences
|
return differences
|
||||||
},
|
},
|
||||||
Clients(value1: any[], value2: any[]): any {
|
ArrObj(value1: any[], value2: any[], key: string): any {
|
||||||
const differences: any[] = []
|
const differences: any[] = []
|
||||||
value1.forEach((v1,index) => {
|
value1.forEach((v1,index) => {
|
||||||
if(index >= value2.length) differences.push({key: "clients", action: "new", obj: v1})
|
if(index >= value2.length) differences.push({key: key, action: "new", obj: v1})
|
||||||
else if(!this.deepCompare(v1,value2[index])) differences.push({key: "clients", action: "edit", obj: v1})
|
else if(!this.deepCompare(v1,value2[index])) differences.push({key: key, action: "edit", obj: v1})
|
||||||
})
|
})
|
||||||
return differences
|
return differences
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ const Data = defineStore('Data', {
|
|||||||
reloadItems: localStorage.getItem("reloadItems")?.split(',')?? <string[]>[],
|
reloadItems: localStorage.getItem("reloadItems")?.split(',')?? <string[]>[],
|
||||||
subURI: "",
|
subURI: "",
|
||||||
onlines: {inbound: <string[]>[], outbound: <string[]>[], user: <string[]>[]},
|
onlines: {inbound: <string[]>[], outbound: <string[]>[], user: <string[]>[]},
|
||||||
oldData: <{config: any, clients: any[], tlsConfigs: any[]}>{},
|
oldData: <{config: any, clients: any[], tlsConfigs: any[], inData: any[]}>{},
|
||||||
config: {},
|
config: <any>{},
|
||||||
clients: [],
|
clients: [],
|
||||||
tlsConfigs: [],
|
tlsConfigs: [],
|
||||||
|
inData: [],
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async loadData() {
|
async loadData() {
|
||||||
@@ -25,6 +26,7 @@ const Data = defineStore('Data', {
|
|||||||
if (msg.obj.config) this.oldData.config = msg.obj.config
|
if (msg.obj.config) this.oldData.config = msg.obj.config
|
||||||
if (msg.obj.clients) this.oldData.clients = msg.obj.clients
|
if (msg.obj.clients) this.oldData.clients = msg.obj.clients
|
||||||
if (msg.obj.tls) this.oldData.tlsConfigs = msg.obj.tls
|
if (msg.obj.tls) this.oldData.tlsConfigs = msg.obj.tls
|
||||||
|
if (msg.obj.inData) this.oldData.inData = msg.obj.inData
|
||||||
this.onlines = msg.obj.onlines
|
this.onlines = msg.obj.onlines
|
||||||
if (msg.obj.lastLog) {
|
if (msg.obj.lastLog) {
|
||||||
push.error({
|
push.error({
|
||||||
@@ -41,31 +43,51 @@ const Data = defineStore('Data', {
|
|||||||
if (data.config) this.config = data.config
|
if (data.config) this.config = data.config
|
||||||
if (data.clients) this.clients = data.clients
|
if (data.clients) this.clients = data.clients
|
||||||
if (data.tls) this.tlsConfigs = data.tls
|
if (data.tls) this.tlsConfigs = data.tls
|
||||||
|
if (data.inData) this.inData = data.inData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async pushData() {
|
async pushData() {
|
||||||
const diff = {
|
const diff = {
|
||||||
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)),
|
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config), null, 2),
|
||||||
clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)),
|
clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2),
|
||||||
tls: JSON.stringify(FindDiff.Clients(this.tlsConfigs,this.oldData.tlsConfigs)),
|
tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2),
|
||||||
|
inData: JSON.stringify(FindDiff.ArrObj(this.inData,this.oldData.inData, "inData"), null, 2),
|
||||||
}
|
}
|
||||||
const msg = await HttpUtils.post('api/save',diff)
|
const msg = await HttpUtils.post('api/save',diff)
|
||||||
if(msg.success) {
|
if(msg.success) {
|
||||||
|
this.lastLoad = 0
|
||||||
this.loadData()
|
this.loadData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async delInbound(index: number) {
|
async delInbound(index: number) {
|
||||||
const diff = {
|
const diff = {
|
||||||
config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]),
|
config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]),
|
||||||
clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)),
|
clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2),
|
||||||
tls: JSON.stringify(FindDiff.Clients(this.tlsConfigs,this.oldData.tlsConfigs)),
|
tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2),
|
||||||
|
inData: <string|undefined> undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate inData
|
||||||
|
let invalidInData = <any[]>[]
|
||||||
|
this.inData.forEach((d:any) => {
|
||||||
|
const inboundIndex = this.config.inbounds.findIndex((i:any) => i.tag == d.tag)
|
||||||
|
if (inboundIndex == -1) invalidInData.push({key: "inData", action: "del", index: d.id, obj: null})
|
||||||
|
})
|
||||||
|
if (invalidInData.length>0) {
|
||||||
|
diff.inData = JSON.stringify(invalidInData)
|
||||||
}
|
}
|
||||||
const msg = await HttpUtils.post('api/save',diff)
|
const msg = await HttpUtils.post('api/save',diff)
|
||||||
if(msg.success) {
|
if(msg.success) {
|
||||||
this.loadData()
|
this.loadData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async delInData(id: number) {
|
||||||
|
const diff = {
|
||||||
|
inData: JSON.stringify([{key: "inData", action: "del", index: id, obj: null}])
|
||||||
|
}
|
||||||
|
await HttpUtils.post('api/save',diff)
|
||||||
|
},
|
||||||
async delOutbound(index: number) {
|
async delOutbound(index: number) {
|
||||||
const diff = {
|
const diff = {
|
||||||
config: JSON.stringify([{key: "outbounds", action: "del", index: index, obj: null}]),
|
config: JSON.stringify([{key: "outbounds", action: "del", index: index, obj: null}]),
|
||||||
|
|||||||
@@ -291,9 +291,13 @@ const updateLinks = (c:Client):Link[] => {
|
|||||||
const newLinks = <Link[]>[]
|
const newLinks = <Link[]>[]
|
||||||
clientInbounds.forEach(i =>{
|
clientInbounds.forEach(i =>{
|
||||||
const tlsConfig = <any>Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag))
|
const tlsConfig = <any>Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag))
|
||||||
const uri = LinkUtil.linkGenerator(c.name,i,tlsConfig?.client)
|
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
||||||
if (uri.length>0){
|
const addrs = cData ? <any[]>cData.addrs : []
|
||||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
const uris = LinkUtil.linkGenerator(c.name,i, tlsConfig?.client?? {}, addrs)
|
||||||
|
if (uris.length>0){
|
||||||
|
uris.forEach(uri => {
|
||||||
|
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
let links = c.links && c.links.length>0? c.links : <Link[]>[]
|
let links = c.links && c.links.length>0? c.links : <Link[]>[]
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
<InboundVue
|
<InboundVue
|
||||||
v-model="modal.visible"
|
v-model="modal.visible"
|
||||||
:visible="modal.visible"
|
:visible="modal.visible"
|
||||||
:id="modal.id"
|
:index="modal.index"
|
||||||
:stats="modal.stats"
|
:stats="modal.stats"
|
||||||
:data="modal.data"
|
:data="modal.data"
|
||||||
|
:cData="modal.cData"
|
||||||
:inTags="inTags"
|
:inTags="inTags"
|
||||||
:outTags="outTags"
|
:outTags="outTags"
|
||||||
:tlsConfigs="tlsConfigs"
|
:tlsConfigs="tlsConfigs"
|
||||||
@@ -111,6 +112,7 @@ import { Client } from '@/types/clients'
|
|||||||
import { Link, LinkUtil } from '@/plugins/link'
|
import { Link, LinkUtil } from '@/plugins/link'
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
import { push } from 'notivue'
|
import { push } from 'notivue'
|
||||||
|
import { fillData } from '@/plugins/outJson'
|
||||||
|
|
||||||
const appConfig = computed((): Config => {
|
const appConfig = computed((): Config => {
|
||||||
return <Config> Data().config
|
return <Config> Data().config
|
||||||
@@ -124,6 +126,10 @@ const tlsConfigs = computed((): any[] => {
|
|||||||
return <any[]> Data().tlsConfigs
|
return <any[]> Data().tlsConfigs
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const inData = computed((): any[] => {
|
||||||
|
return <any[]> Data().inData
|
||||||
|
})
|
||||||
|
|
||||||
const inTags = computed((): string[] => {
|
const inTags = computed((): string[] => {
|
||||||
return inbounds.value?.map(i => i.tag)
|
return inbounds.value?.map(i => i.tag)
|
||||||
})
|
})
|
||||||
@@ -146,44 +152,56 @@ const v2rayStats = computed((): V2rayApiStats => {
|
|||||||
|
|
||||||
const modal = ref({
|
const modal = ref({
|
||||||
visible: false,
|
visible: false,
|
||||||
id: -1,
|
index: -1,
|
||||||
data: "",
|
data: "",
|
||||||
|
cData: "",
|
||||||
stats: false,
|
stats: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
let delOverlay = ref(new Array<boolean>)
|
let delOverlay = ref(new Array<boolean>)
|
||||||
|
|
||||||
const showModal = (id: number) => {
|
const showModal = (index: number) => {
|
||||||
modal.value.id = id
|
modal.value.index = index
|
||||||
modal.value.data = id == -1 ? '' : JSON.stringify(inbounds.value[id])
|
if (index == -1){
|
||||||
modal.value.stats = id == -1 ? false : v2rayStats.value.inbounds.includes(inbounds.value[id].tag)
|
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])
|
||||||
|
}
|
||||||
modal.value.visible = true
|
modal.value.visible = true
|
||||||
}
|
}
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
modal.value.visible = false
|
modal.value.visible = false
|
||||||
}
|
}
|
||||||
const saveModal = (data:Inbound, stats: boolean, tls_id: number) => {
|
const saveModal = (data:Inbound, stats: boolean, tls_id: number, cData: any) => {
|
||||||
// Check duplicate tag
|
// Check duplicate tag
|
||||||
const oldTag = modal.value.id != -1 ? inbounds.value[modal.value.id].tag : null
|
const oldTag = modal.value.index != -1 ? inbounds.value[modal.value.index].tag : null
|
||||||
if (data.tag != oldTag && inTags.value.includes(data.tag)) {
|
if (data.tag != oldTag && inTags.value.includes(data.tag)) {
|
||||||
push.error({
|
push.error({
|
||||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
||||||
})
|
})
|
||||||
return
|
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
|
// New or Edit
|
||||||
if (modal.value.id == -1) {
|
if (modal.value.index == -1) {
|
||||||
inbounds.value.push(data)
|
inbounds.value.push(data)
|
||||||
if (stats && data.tag.length>0) {
|
if (stats && data.tag.length>0) {
|
||||||
v2rayStats.value.inbounds.push(data.tag)
|
v2rayStats.value.inbounds.push(data.tag)
|
||||||
}
|
}
|
||||||
// Update tls preset
|
if (cData.id != -1){
|
||||||
if (tls_id>0) {
|
inData.value.push(cData)
|
||||||
tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const oldTag = inbounds.value[modal.value.id].tag
|
const oldTag = inbounds.value[modal.value.index].tag
|
||||||
const sIndex = v2rayStats.value.inbounds.findIndex(i => i == data.tag) // Find if new tag exists
|
const sIndex = v2rayStats.value.inbounds.findIndex(i => i == data.tag) // Find if new tag exists
|
||||||
|
|
||||||
// Update tls preset
|
// Update tls preset
|
||||||
@@ -191,9 +209,6 @@ const saveModal = (data:Inbound, stats: boolean, tls_id: number) => {
|
|||||||
if (oldTlsConfigIndex != -1){
|
if (oldTlsConfigIndex != -1){
|
||||||
tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != oldTag)
|
tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != oldTag)
|
||||||
}
|
}
|
||||||
if (tls_id>0) {
|
|
||||||
tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldTag != data.tag) {
|
if (oldTag != data.tag) {
|
||||||
v2rayStats.value.inbounds = v2rayStats.value.inbounds.filter(item => item != oldTag)
|
v2rayStats.value.inbounds = v2rayStats.value.inbounds.filter(item => item != oldTag)
|
||||||
@@ -208,8 +223,24 @@ const saveModal = (data:Inbound, stats: boolean, tls_id: number) => {
|
|||||||
if (sIndex != -1) v2rayStats.value.inbounds.splice(sIndex,1)
|
if (sIndex != -1) v2rayStats.value.inbounds.splice(sIndex,1)
|
||||||
}
|
}
|
||||||
|
|
||||||
inbounds.value[modal.value.id] = data
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.hasOwn(data,'users')) {
|
if (Object.hasOwn(data,'users')) {
|
||||||
// Set users
|
// Set users
|
||||||
data = buildInboundsUsers(data)
|
data = buildInboundsUsers(data)
|
||||||
@@ -226,10 +257,14 @@ const updateLinks = (i: any) => {
|
|||||||
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => client?.inbounds.includes(inb.tag))
|
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => client?.inbounds.includes(inb.tag))
|
||||||
const newLinks = <Link[]>[]
|
const newLinks = <Link[]>[]
|
||||||
clientInbounds.forEach(i =>{
|
clientInbounds.forEach(i =>{
|
||||||
const tlsClient = tlsConfigs?.value.findLast((t:any) => t.inbounds.includes(i.tag))?.client?? null
|
const tlsClient = tlsConfigs?.value.findLast((t:any) => t.inbounds.includes(i.tag))?.client?? {}
|
||||||
const uri = LinkUtil.linkGenerator(client.name,i, tlsClient)
|
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
||||||
if (uri.length>0){
|
const addrs = cData ? <any[]>cData.addrs : []
|
||||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
const uris = LinkUtil.linkGenerator(client.name,i, tlsClient, addrs)
|
||||||
|
if (uris.length>0){
|
||||||
|
uris.forEach(uri => {
|
||||||
|
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
let links = client.links && client.links.length>0? client.links : <Link[]>[]
|
let links = client.links && client.links.length>0? client.links : <Link[]>[]
|
||||||
@@ -251,8 +286,8 @@ const delInbound = (index: number) => {
|
|||||||
inbU.users.forEach((u:any) => {
|
inbU.users.forEach((u:any) => {
|
||||||
const c_index = clients.value.findIndex(c => u.username? u.username == c.name : u.name == c.name)
|
const c_index = clients.value.findIndex(c => u.username? u.username == c.name : u.name == c.name)
|
||||||
if (c_index != -1) {
|
if (c_index != -1) {
|
||||||
const clientInbounds = clients.value[c_index].inbounds.filter((x:string) => x!=tag)
|
clients.value[c_index].inbounds = clients.value[c_index].inbounds.filter((x:string) => x!=tag)
|
||||||
clients.value[c_index].inbounds = clientInbounds
|
clients.value[c_index].links = clients.value[c_index].links.filter((x:any) => x.remark!=tag)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
>
|
>
|
||||||
<v-tab value="t1">{{ $t('setting.interface') }}</v-tab>
|
<v-tab value="t1">{{ $t('setting.interface') }}</v-tab>
|
||||||
<v-tab value="t2">{{ $t('setting.sub') }}</v-tab>
|
<v-tab value="t2">{{ $t('setting.sub') }}</v-tab>
|
||||||
<v-tab value="t3">Language</v-tab>
|
<v-tab value="t3">{{ $t('setting.sub') }} JSON</v-tab>
|
||||||
|
<v-tab value="t4">Language</v-tab>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row align="center" justify="center" style="margin-bottom: 10px;">
|
<v-row align="center" justify="center" style="margin-bottom: 10px;">
|
||||||
@@ -128,6 +129,10 @@
|
|||||||
</v-window-item>
|
</v-window-item>
|
||||||
|
|
||||||
<v-window-item value="t3">
|
<v-window-item value="t3">
|
||||||
|
<SubJsonExtVue :settings="settings" />
|
||||||
|
</v-window-item>
|
||||||
|
|
||||||
|
<v-window-item value="t4">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-select
|
<v-select
|
||||||
@@ -146,11 +151,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useLocale } from "vuetify"
|
import { useLocale } from 'vuetify'
|
||||||
import { languages } from '@/locales'
|
import { languages } from '@/locales'
|
||||||
import { Ref, computed, inject, onMounted, ref } from "vue"
|
import { Ref, computed, inject, onMounted, ref } from 'vue'
|
||||||
import HttpUtils from "@/plugins/httputil"
|
import HttpUtils from '@/plugins/httputil'
|
||||||
import { FindDiff } from "@/plugins/utils"
|
import { FindDiff } from '@/plugins/utils'
|
||||||
|
import SubJsonExtVue from '@/components/SubJsonExt.vue'
|
||||||
const locale = useLocale()
|
const locale = useLocale()
|
||||||
const tab = ref("t1")
|
const tab = ref("t1")
|
||||||
const loading:Ref = inject('loading')?? ref(false)
|
const loading:Ref = inject('loading')?? ref(false)
|
||||||
@@ -177,6 +183,7 @@ const settings = ref({
|
|||||||
subEncode: "true",
|
subEncode: "true",
|
||||||
subShowInfo: "false",
|
subShowInfo: "false",
|
||||||
subURI: "",
|
subURI: "",
|
||||||
|
subJsonExt: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {loadData()})
|
onMounted(async () => {loadData()})
|
||||||
|
|||||||
@@ -144,9 +144,13 @@ const updateLinks = (i:any,tlsClient:any) => {
|
|||||||
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => client?.inbounds.includes(inb.tag))
|
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => client?.inbounds.includes(inb.tag))
|
||||||
const newLinks = <Link[]>[]
|
const newLinks = <Link[]>[]
|
||||||
clientInbounds.forEach(i =>{
|
clientInbounds.forEach(i =>{
|
||||||
const uri = LinkUtil.linkGenerator(client.name,i,tlsClient)
|
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
||||||
if (uri.length>0){
|
const addrs = cData ? <any[]>cData.addrs : []
|
||||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
const uris = LinkUtil.linkGenerator(client,i, tlsClient, addrs)
|
||||||
|
if (uris.length>0){
|
||||||
|
uris.forEach(uri => {
|
||||||
|
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
let links = client.links && client.links.length>0? client.links : <Link[]>[]
|
let links = client.links && client.links.length>0? client.links : <Link[]>[]
|
||||||
|
|||||||
Reference in New Issue
Block a user