Add admin panel, manager, node_manager, bandwidth limiter, connection limiter, bonding, failover, vless encryption, mkcp transport

This commit is contained in:
Shtorm
2026-02-26 22:44:31 +03:00
parent 0f38dbba4c
commit ded1eb9635
115 changed files with 12582 additions and 301 deletions

View File

@@ -0,0 +1,259 @@
package tables
import (
"encoding/json"
"strconv"
"strings"
"time"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/db"
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/types"
"github.com/GoAdminGroup/go-admin/template/types/form"
"github.com/sagernet/sing-box/log"
CM "github.com/sagernet/sing-box/service/manager/constant"
)
func BandwidthLimiterTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) table.Table {
return func(ctx *context.Context) table.Table {
t := table.NewDefaultTable(ctx, table.Config{
CanAdd: true,
Editable: true,
Deletable: true,
Exportable: true,
PrimaryKey: table.PrimaryKey{
Type: db.Int,
Name: table.DefaultPrimaryKeyName,
},
})
squads, err := manager.GetSquads(map[string][]string{})
if err != nil {
return nil
}
squadsByID := make(map[int]string, len(squads))
squadOptions := make(types.FieldOptions, len(squads))
for i, squad := range squads {
squadsByID[squad.ID] = squad.Name
squadOptions[i] = types.FieldOption{
Text: squad.Name,
Value: strconv.Itoa(squad.ID),
}
}
info := t.GetInfo().SetFilterFormLayout(form.LayoutFilter)
info.AddField("ID", "id", db.Int).
FieldSortable()
info.AddField("Squads", "squad_ids", db.Varchar).
FieldDisplay(func(model types.FieldModel) interface{} {
values := model.Row["squad_ids"].([]interface{})
labels := template.HTML("")
labelTpl := label(ctx).SetType("success")
labelValues := make([]string, len(values))
for i, squadID := range values {
labelValues[i] = squadsByID[int(squadID.(float64))]
}
for key, label := range labelValues {
if key == len(labelValues)-1 {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
} else {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
}
}
return labels
})
info.AddField("Username", "username", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Outbound", "outbound", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Strategy", "strategy", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Connection", Value: "connection"},
{Text: "Global", Value: "global"},
},
}).
FieldSortable()
info.AddField("Mode", "mode", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Download", Value: "download"},
{Text: "Upload", Value: "upload"},
{Text: "Duplex", Value: "duplex"},
},
}).
FieldSortable()
info.AddField("Connection type", "connection_type", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "HWID", Value: "hwid"},
{Text: "Mux", Value: "mux"},
{Text: "IP", Value: "ip"},
},
}).
FieldSortable()
info.AddField("Speed", "speed", db.Varchar).
FieldSortable()
info.AddField("Created at", "created_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.AddField("Updated at", "updated_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
filters := make(map[string][]string)
listFilters := map[string][]string{
"offset": {strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)},
"limit": {param.PageSize},
}
for k, v := range param.Fields {
if strings.HasPrefix(k, "__") {
continue
}
key := strings.TrimSuffix(k, "__goadmin")
filters[key] = v
listFilters[key] = v
}
if param.SortField != "" {
if param.SortType == "asc" {
listFilters["sort_asc"] = []string{param.SortField}
} else {
listFilters["sort_desc"] = []string{param.SortField}
}
}
items, err := manager.GetBandwidthLimiters(listFilters)
if err != nil {
logger.Error(err)
return nil, 0
}
count, err := manager.GetBandwidthLimitersCount(filters)
if err != nil {
logger.Error(err)
return nil, 0
}
result := make([]map[string]interface{}, 0, len(items))
for _, item := range items {
var data map[string]interface{}
raw, _ := json.Marshal(item)
json.Unmarshal(raw, &data)
result = append(result, data)
}
return result, count
})
info.SetDeleteFn(func(ids []string) error {
for _, id := range ids {
i, err := strconv.Atoi(id)
if err != nil {
return err
}
if _, err := manager.DeleteBandwidthLimiter(i); err != nil {
return err
}
}
return nil
})
info.SetTable("bandwidth_limiters").SetTitle("Bandwidth Limiters").SetDescription("Bandwidth Limiters")
formList := t.GetForm()
formList.AddField("ID", "id", db.Int, form.Default).
FieldNotAllowAdd().
FieldNotAllowEdit()
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
FieldMust().
FieldOptions(squadOptions).
FieldDisableWhenUpdate()
formList.AddField("Username", "username", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate()
formList.AddField("Outbound", "outbound", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate()
formList.AddField("Strategy", "strategy", db.Varchar, form.SelectSingle).
FieldMust().
FieldOptions(types.FieldOptions{
{Text: "Connection", Value: "connection"},
{Text: "Global", Value: "global"},
}).
FieldOnChooseOptionsHide([]string{"", "global"}, "connection_type")
formList.AddField("Mode", "mode", db.Varchar, form.SelectSingle).
FieldMust().
FieldOptions(types.FieldOptions{
{Text: "Download", Value: "download"},
{Text: "Upload", Value: "upload"},
{Text: "Duplex", Value: "duplex"},
})
formList.AddField("Connection type", "connection_type", db.Varchar, form.SelectSingle).
FieldOptions(types.FieldOptions{
{Text: "HWID", Value: "hwid"},
{Text: "Mux", Value: "mux"},
{Text: "IP", Value: "ip"},
})
formList.AddField("Speed", "speed", db.Varchar, form.Text).
FieldMust()
formList.SetInsertFn(func(values mForm.Values) error {
squadIDs := make([]int, len(values["squad_ids[]"]))
for i, rawSquadID := range values["squad_ids[]"] {
squadID, err := strconv.Atoi(rawSquadID)
if err != nil {
return err
}
squadIDs[i] = squadID
}
_, err := manager.CreateBandwidthLimiter(CM.BandwidthLimiterCreate{
SquadIDs: squadIDs,
Username: values.Get("username"),
Outbound: values.Get("outbound"),
Strategy: values.Get("strategy"),
Mode: values.Get("mode"),
ConnectionType: values.Get("connection_type"),
Speed: values.Get("speed"),
})
return err
})
formList.SetUpdateFn(func(values mForm.Values) error {
id, err := strconv.Atoi(values.Get("id"))
if err != nil {
return err
}
_, err = manager.UpdateBandwidthLimiter(id, CM.BandwidthLimiterUpdate{
Username: values.Get("username"),
Outbound: values.Get("outbound"),
Strategy: values.Get("strategy"),
Mode: values.Get("mode"),
ConnectionType: values.Get("connection_type"),
Speed: values.Get("speed"),
})
return err
})
formList.SetTable("bandwidth_limiters").SetTitle("Bandwidth Limiters").SetDescription("Bandwidth Limiters")
return t
}
}

View File

@@ -0,0 +1,261 @@
package tables
import (
"encoding/json"
"strconv"
"strings"
"time"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/db"
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/types"
"github.com/GoAdminGroup/go-admin/template/types/form"
"github.com/sagernet/sing-box/log"
CM "github.com/sagernet/sing-box/service/manager/constant"
)
func ConnectionLimiterTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) table.Table {
return func(ctx *context.Context) table.Table {
connectionLimiterTable := table.NewDefaultTable(ctx, table.Config{
CanAdd: true,
Editable: true,
Deletable: true,
Exportable: true,
PrimaryKey: table.PrimaryKey{
Type: db.Int,
Name: table.DefaultPrimaryKeyName,
},
})
squads, err := manager.GetSquads(map[string][]string{})
if err != nil {
return nil
}
squadsByID := make(map[int]string, len(squads))
squadOptions := make(types.FieldOptions, len(squads))
for i, squad := range squads {
squadsByID[squad.ID] = squad.Name
squadOptions[i] = types.FieldOption{
Text: squad.Name,
Value: strconv.Itoa(squad.ID),
}
}
info := connectionLimiterTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
info.AddField("ID", "id", db.Int).
FieldSortable()
info.AddField("Squads", "squad_ids", db.Varchar).
FieldDisplay(func(model types.FieldModel) interface{} {
values := model.Row["squad_ids"].([]interface{})
labels := template.HTML("")
labelTpl := label(ctx).SetType("success")
labelValues := make([]string, len(values))
for i, squadID := range values {
labelValues[i] = squadsByID[int(squadID.(float64))]
}
for key, label := range labelValues {
if key == len(labelValues)-1 {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
} else {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
}
}
return labels
})
info.AddField("Username", "username", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Outbound", "outbound", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Strategy", "strategy", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Connection", Value: "connection"},
},
}).
FieldSortable()
info.AddField("Connection type", "connection_type", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Mux", Value: "mux"},
{Text: "HWID", Value: "hwid"},
{Text: "IP", Value: "ip"},
},
}).
FieldSortable()
info.AddField("Lock type", "lock_type", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Manager", Value: "manager"},
},
}).
FieldSortable()
info.AddField("Count", "count", db.Int).
FieldSortable()
info.AddField("Created at", "created_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.AddField("Updated at", "updated_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
filters := make(map[string][]string)
listFilters := map[string][]string{
"offset": {strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)},
"limit": {param.PageSize},
}
for k, v := range param.Fields {
if strings.HasPrefix(k, "__") {
continue
}
key := strings.TrimSuffix(k, "__goadmin")
filters[key] = v
listFilters[key] = v
}
if param.SortField != "" {
if param.SortType == "asc" {
listFilters["sort_asc"] = []string{param.SortField}
} else {
listFilters["sort_desc"] = []string{param.SortField}
}
}
items, err := manager.GetConnectionLimiters(listFilters)
if err != nil {
logger.Error(err)
return nil, 0
}
count, err := manager.GetConnectionLimitersCount(filters)
if err != nil {
logger.Error(err)
return nil, 0
}
result := make([]map[string]interface{}, 0, len(items))
for _, item := range items {
var data map[string]interface{}
raw, _ := json.Marshal(item)
json.Unmarshal(raw, &data)
result = append(result, data)
}
return result, count
})
info.SetDeleteFn(func(ids []string) error {
for _, id := range ids {
i, err := strconv.Atoi(id)
if err != nil {
return err
}
if _, err := manager.DeleteConnectionLimiter(i); err != nil {
return err
}
}
return nil
})
info.SetTable("connection_limiters").SetTitle("Connection Limiters").SetDescription("Connection Limiters")
formList := connectionLimiterTable.GetForm()
formList.AddField("ID", "id", db.Int, form.Default).
FieldNotAllowAdd().
FieldNotAllowEdit()
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
FieldMust().
FieldOptions(squadOptions).
FieldDisableWhenUpdate()
formList.AddField("Username", "username", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate()
formList.AddField("Outbound", "outbound", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate()
formList.AddField("Strategy", "strategy", db.Varchar, form.SelectSingle).
FieldMust().
FieldOptions(types.FieldOptions{
{Text: "Connection", Value: "connection"},
}).
FieldDefault("connection")
formList.AddField("Connection type", "connection_type", db.Varchar, form.SelectSingle).
FieldOptions(types.FieldOptions{
{Text: "Mux", Value: "mux"},
{Text: "HWID", Value: "hwid"},
{Text: "IP", Value: "ip"},
})
formList.AddField("Lock type", "lock_type", db.Varchar, form.SelectSingle).
FieldOptions(types.FieldOptions{
{Text: "Manager", Value: "manager"},
})
formList.AddField("Count", "count", db.Int, form.Number).
FieldMust().
FieldDefault("0")
formList.SetInsertFn(func(values mForm.Values) error {
squadIDs := make([]int, len(values["squad_ids[]"]))
for i, rawSquadID := range values["squad_ids[]"] {
squadID, err := strconv.Atoi(rawSquadID)
if err != nil {
return err
}
squadIDs[i] = squadID
}
count, err := strconv.ParseUint(values.Get("count"), 10, 32)
if err != nil {
return err
}
_, err = manager.CreateConnectionLimiter(CM.ConnectionLimiterCreate{
SquadIDs: squadIDs,
Username: values.Get("username"),
Outbound: values.Get("outbound"),
Strategy: values.Get("strategy"),
ConnectionType: values.Get("connection_type"),
LockType: values.Get("lock_type"),
Count: uint32(count),
})
return err
})
formList.SetUpdateFn(func(values mForm.Values) error {
id, err := strconv.Atoi(values.Get("id"))
if err != nil {
return err
}
count, err := strconv.ParseUint(values.Get("count"), 10, 32)
if err != nil {
return err
}
_, err = manager.UpdateConnectionLimiter(id, CM.ConnectionLimiterUpdate{
Username: values.Get("username"),
Outbound: values.Get("outbound"),
Strategy: values.Get("strategy"),
ConnectionType: values.Get("connection_type"),
LockType: values.Get("lock_type"),
Count: uint32(count),
})
return err
})
formList.SetTable("connection_limiters").SetTitle("Connection Limiters").SetDescription("Connection Limiters")
return connectionLimiterTable
}
}

View File

@@ -0,0 +1,201 @@
package tables
import (
"encoding/json"
"strconv"
"strings"
"time"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/config"
"github.com/GoAdminGroup/go-admin/modules/db"
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/types"
"github.com/GoAdminGroup/go-admin/template/types/form"
"github.com/gofrs/uuid/v5"
"github.com/sagernet/sing-box/log"
CM "github.com/sagernet/sing-box/service/manager/constant"
)
func label(ctx *context.Context) types.LabelAttribute {
return template.Get(ctx, config.GetTheme()).Label().SetType("success")
}
func NodeTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) (nodeTable table.Table) {
return func(ctx *context.Context) (nodeTable table.Table) {
nodeTable = table.NewDefaultTable(ctx, table.Config{
CanAdd: true,
Editable: true,
Deletable: true,
Exportable: true,
PrimaryKey: table.PrimaryKey{
Type: db.Varchar,
Name: "uuid",
},
})
squads, err := manager.GetSquads(map[string][]string{})
if err != nil {
return nil
}
squadsByID := make(map[int]string, len(squads))
squadOptions := make(types.FieldOptions, len(squads))
for i, squad := range squads {
squadsByID[squad.ID] = squad.Name
squadOptions[i] = types.FieldOption{
Text: squad.Name,
Value: strconv.Itoa(squad.ID),
}
}
info := nodeTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
info.AddField("UUID", "uuid", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Name", "name", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Squads", "squad_ids", db.Varchar).
FieldDisplay(func(model types.FieldModel) interface{} {
values := model.Row["squad_ids"].([]interface{})
labels := template.HTML("")
labelTpl := label(ctx).SetType("success")
labelValues := make([]string, len(values))
for i, squadID := range values {
labelValues[i] = squadsByID[int(squadID.(float64))]
}
for key, label := range labelValues {
if key == len(labelValues)-1 {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
} else {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
}
}
return labels
})
info.AddField("Status", "status", db.Varchar).
FieldDisplay(func(value types.FieldModel) interface{} {
uuid := value.Row["uuid"].(string)
return manager.GetNodeStatus(uuid)
})
info.AddField("Created at", "created_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.AddField("Updated at", "updated_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
filters := make(map[string][]string, len(param.Fields))
listFilters := make(map[string][]string, len(param.Fields)+2)
listFilters["offset"] = []string{strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)}
listFilters["limit"] = []string{param.PageSize}
for key, values := range param.Fields {
if key == "__pk" {
key = "uuid"
} else {
if strings.HasPrefix(key, "__") {
continue
}
key = strings.TrimSuffix(key, "__goadmin")
}
filters[key] = values
listFilters[key] = values
}
if param.SortField != "" {
if param.SortType == "asc" {
listFilters["sort_asc"] = []string{param.SortField}
} else {
listFilters["sort_desc"] = []string{param.SortField}
}
}
nodes, err := manager.GetNodes(listFilters)
if err != nil {
logger.Error(err)
return nil, 0
}
count, err := manager.GetNodesCount(filters)
if err != nil {
logger.Error(err)
return nil, 0
}
result := make([]map[string]interface{}, 0, len(nodes))
for _, node := range nodes {
var data map[string]interface{}
rawData, _ := json.Marshal(node)
json.Unmarshal(rawData, &data)
result = append(result, data)
}
return result, count
})
info.SetDeleteFn(func(ids []string) error {
for _, uuid := range ids {
if _, err := manager.DeleteNode(uuid); err != nil {
return err
}
}
return nil
})
info.SetTable("nodes").SetTitle("Nodes").SetDescription("Nodes")
defaultUUID, _ := uuid.NewV4()
formList := nodeTable.GetForm()
formList.AddField("UUID", "uuid", db.Varchar, form.Text).
FieldMust().
FieldNotAllowEdit().
FieldDefault(defaultUUID.String())
formList.AddField("Name", "name", db.Varchar, form.Text).
FieldMust()
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
FieldMust().
FieldOptions(squadOptions).
FieldDisableWhenUpdate()
formList.SetInsertFn(func(values mForm.Values) (err error) {
squadIDs := make([]int, len(values["squad_ids[]"]))
for i, rawSquadID := range values["squad_ids[]"] {
squadID, err := strconv.Atoi(rawSquadID)
if err != nil {
return err
}
squadIDs[i] = squadID
}
_, err = manager.CreateNode(CM.NodeCreate{
UUID: values.Get("uuid"),
Name: values.Get("name"),
SquadIDs: squadIDs,
})
return
})
formList.SetUpdateFn(func(values mForm.Values) (err error) {
uuid := values.Get("uuid")
_, err = manager.UpdateNode(uuid, CM.NodeUpdate{
Name: values.Get("name"),
})
return
})
formList.SetTable("nodes").SetTitle("Nodes").SetDescription("Nodes")
return
}
}

View File

@@ -0,0 +1,164 @@
package tables
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/db"
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template/types"
"github.com/GoAdminGroup/go-admin/template/types/form"
"github.com/go-playground/validator/v10"
"github.com/sagernet/sing-box/log"
CM "github.com/sagernet/sing-box/service/manager/constant"
)
func SquadTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) (squadTable table.Table) {
return func(ctx *context.Context) (squadTable table.Table) {
squadTable = table.NewDefaultTable(ctx, table.Config{
CanAdd: true,
Editable: true,
Deletable: true,
Exportable: true,
PrimaryKey: table.PrimaryKey{
Type: db.Int,
Name: table.DefaultPrimaryKeyName,
},
})
info := squadTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
info.AddField("ID", "id", db.Int).
FieldSortable()
info.AddField("Name", "name", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Created At", "created_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldSortable().
FieldFilterable(types.FilterType{FormType: form.DatetimeRange})
info.AddField("Updated At", "updated_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldSortable().
FieldFilterable(types.FilterType{FormType: form.DatetimeRange})
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
filters := make(map[string][]string, len(param.Fields))
listFilters := make(map[string][]string, len(param.Fields)+2)
listFilters["offset"] = []string{strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)}
listFilters["limit"] = []string{param.PageSize}
for key, values := range param.Fields {
if key == "__pk" {
key = "pk"
} else if strings.HasPrefix(key, "__") {
continue
} else {
key = strings.TrimSuffix(key, "__goadmin")
}
filters[key] = values
listFilters[key] = values
}
if param.SortField != "" {
if param.SortType == "asc" {
listFilters["sort_asc"] = []string{param.SortField}
} else {
listFilters["sort_desc"] = []string{param.SortField}
}
}
squads, err := manager.GetSquads(listFilters)
if err != nil {
logger.Error(err)
return nil, 0
}
count, err := manager.GetSquadsCount(filters)
if err != nil {
logger.Error(err)
return nil, 0
}
result := make([]map[string]interface{}, 0, len(squads))
for _, squad := range squads {
var data map[string]interface{}
rawData, _ := json.Marshal(squad)
json.Unmarshal(rawData, &data)
result = append(result, data)
}
return result, count
})
info.SetDeleteFn(func(ids []string) error {
for _, id := range ids {
intID, err := strconv.Atoi(id)
if err != nil {
return err
}
if _, err := manager.DeleteSquad(intID); err != nil {
return err
}
}
return nil
})
info.SetTable("squads").SetTitle("Squads").SetDescription("Squads")
formList := squadTable.GetForm()
formList.AddField("ID", "id", db.Int, form.Default).
FieldNotAllowAdd().
FieldNotAllowEdit()
formList.AddField("Name", "name", db.Varchar, form.Text).
FieldMust()
formList.SetInsertFn(func(values mForm.Values) (err error) {
_, err = manager.CreateSquad(CM.SquadCreate{
Name: values.Get("name"),
})
if err != nil {
if ve, ok := err.(validator.ValidationErrors); ok {
var errors []string
for _, e := range ve {
switch e.Tag() {
case "required":
errors = append(errors, e.StructField()+": required field missing")
default:
errors = append(errors, e.StructField()+": invalid request")
}
}
err = fmt.Errorf("%s", strings.Join(errors, "<br>"))
}
}
return
})
formList.SetUpdateFn(func(values mForm.Values) (err error) {
id, err := strconv.Atoi(values.Get("id"))
if err != nil {
return err
}
_, err = manager.UpdateSquad(id, CM.SquadUpdate{
Name: values.Get("name"),
})
return
})
formList.SetTable("squads").SetTitle("Squads").SetDescription("Squads")
return
}
}

View File

@@ -0,0 +1,282 @@
package tables
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/db"
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/types"
"github.com/GoAdminGroup/go-admin/template/types/form"
"github.com/go-playground/validator/v10"
"github.com/sagernet/sing-box/log"
CM "github.com/sagernet/sing-box/service/manager/constant"
)
func UserTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) (userTable table.Table) {
return func(ctx *context.Context) (userTable table.Table) {
userTable = table.NewDefaultTable(ctx, table.Config{
CanAdd: true,
Editable: true,
Deletable: true,
Exportable: true,
PrimaryKey: table.PrimaryKey{
Type: db.Int,
Name: table.DefaultPrimaryKeyName,
},
})
squads, err := manager.GetSquads(map[string][]string{})
if err != nil {
return nil
}
squadsByID := make(map[int]string, len(squads))
squadOptions := make(types.FieldOptions, len(squads))
for i, squad := range squads {
squadsByID[squad.ID] = squad.Name
squadOptions[i] = types.FieldOption{
Text: squad.Name,
Value: strconv.Itoa(squad.ID),
}
}
info := userTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
info.AddField("ID", "id", db.Int).
FieldSortable()
info.AddField("Squads", "squad_ids", db.Varchar).
FieldDisplay(func(model types.FieldModel) interface{} {
values := model.Row["squad_ids"].([]interface{})
labels := template.HTML("")
labelTpl := label(ctx).SetType("success")
labelValues := make([]string, len(values))
for i, squadID := range values {
labelValues[i] = squadsByID[int(squadID.(float64))]
}
for key, label := range labelValues {
if key == len(labelValues)-1 {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
} else {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
}
}
return labels
})
info.AddField("Username", "username", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Type", "type", db.Varchar).
FieldFilterable(
types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Hysteria", Value: "hysteria"},
{Text: "Hysteria2", Value: "hysteria2"},
{Text: "Trojan", Value: "trojan"},
{Text: "TUIC", Value: "tuic"},
{Text: "VLESS", Value: "vless"},
{Text: "VMess", Value: "vmess"},
},
},
).
FieldSortable()
info.AddField("Inbound", "inbound", db.Varchar).FieldFilterable().
FieldSortable()
info.AddField("Created at", "created_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.AddField("Updated at", "updated_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
filters := make(map[string][]string, len(param.Fields))
listFilters := make(map[string][]string, len(param.Fields)+2)
listFilters["offset"] = []string{strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)}
listFilters["limit"] = []string{param.PageSize}
for key, values := range param.Fields {
if key == "__pk" {
key = "pk"
} else {
if strings.HasPrefix(key, "__") {
continue
}
key = strings.TrimSuffix(key, "__goadmin")
}
filters[key] = values
listFilters[key] = values
}
if param.SortField != "" {
if param.SortType == "asc" {
listFilters["sort_asc"] = []string{param.SortField}
} else {
listFilters["sort_desc"] = []string{param.SortField}
}
}
users, err := manager.GetUsers(listFilters)
if err != nil {
logger.Error(err)
return nil, 0
}
count, err := manager.GetUsersCount(filters)
if err != nil {
logger.Error(err)
return nil, 0
}
result := make([]map[string]interface{}, 0, len(users))
for _, user := range users {
var data map[string]interface{}
rawData, _ := json.Marshal(user)
json.Unmarshal(rawData, &data)
result = append(result, data)
}
return result, count
})
info.SetDeleteFn(func(ids []string) error {
for _, id := range ids {
value, err := strconv.Atoi(id)
if err != nil {
return err
}
if _, err := manager.DeleteUser(value); err != nil {
return err
}
}
return nil
})
info.SetTable("users").SetTitle("Users").SetDescription("Users")
formList := userTable.GetForm()
formList.AddField("ID", "id", db.Int, form.Default).
FieldNotAllowEdit().
FieldNotAllowAdd()
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
FieldMust().
FieldOptions(squadOptions).
FieldDisableWhenUpdate()
formList.AddField("Username", "username", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate()
formList.AddField("Type", "type", db.Varchar, form.SelectSingle).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate().
FieldOptions(types.FieldOptions{
{Text: "Hysteria", Value: "hysteria"},
{Text: "Hysteria2", Value: "hysteria2"},
{Text: "Trojan", Value: "trojan"},
{Text: "TUIC", Value: "tuic"},
{Text: "VLESS", Value: "vless"},
{Text: "VMess", Value: "vmess"},
}).
FieldOnChooseOptionsHide([]string{""}, "inbound").
FieldOnChooseOptionsHide([]string{"", "hysteria", "hysteria2", "shadowsocks", "trojan", "tuic"}, "uuid").
FieldOnChooseOptionsHide([]string{"", "vless", "vmess"}, "password").
FieldOnChooseOptionsHide([]string{"", "hysteria", "hysteria2", "shadowsocks", "trojan", "tuic", "vmess"}, "flow").
FieldOnChooseOptionsHide([]string{"", "hysteria", "hysteria2", "shadowsocks", "trojan", "tuic", "vless"}, "alter_id")
formList.AddField("Inbound", "inbound", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate().
FieldOptionInitFn(func(val types.FieldModel) types.FieldOptions {
return types.FieldOptions{
{Value: val.Value, Text: val.Value, Selected: true},
}
})
formList.AddField("UUID", "uuid", db.Varchar, form.Text)
formList.AddField("Password", "password", db.Varchar, form.Text)
formList.AddField("Flow", "flow", db.Varchar, form.SelectSingle).
FieldOptions(types.FieldOptions{
{Text: "xtls-rprx-vision", Value: "xtls-rprx-vision"},
})
formList.AddField("Alter ID", "alter_id", db.Varchar, form.Number).
FieldDefault("0")
formList.SetInsertFn(func(values mForm.Values) (err error) {
squadIDs := make([]int, len(values["squad_ids[]"]))
for i, rawSquadID := range values["squad_ids[]"] {
squadID, err := strconv.Atoi(rawSquadID)
if err != nil {
return err
}
squadIDs[i] = squadID
}
var alterId int
if value := values.Get("alter_id"); value != "" {
alterId, err = strconv.Atoi(value)
if err != nil {
return err
}
}
_, err = manager.CreateUser(CM.UserCreate{
SquadIDs: squadIDs,
Username: values.Get("username"),
Type: values.Get("type"),
Inbound: values.Get("inbound"),
UUID: values.Get("uuid"),
Password: values.Get("password"),
Flow: values.Get("flow"),
AlterID: alterId,
})
if err != nil {
if ve, ok := err.(validator.ValidationErrors); ok {
var errors []string
for _, e := range ve {
switch e.Tag() {
case "required":
errors = append(errors, e.StructField()+": required field missing")
case "uuid4":
errors = append(errors, e.StructField()+": invalid UUID")
default:
errors = append(errors, e.StructField()+": invalid request")
}
}
err = fmt.Errorf("%s", strings.Join(errors, "<br>"))
}
}
return
})
formList.SetUpdateFn(func(values mForm.Values) (err error) {
id, err := strconv.Atoi(values.Get("id"))
if err != nil {
return err
}
var alterId int
if value := values.Get("alter_id"); value != "" {
alterId, err = strconv.Atoi(value)
if err != nil {
return err
}
}
_, err = manager.UpdateUser(id, CM.UserUpdate{
UUID: values.Get("uuid"),
Password: values.Get("password"),
Flow: values.Get("flow"),
AlterID: alterId,
})
return
})
formList.SetTable("users").SetTitle("Users").SetDescription("Users")
return
}
}