mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-06 18:06:36 +08:00
fix: restore panel list endpoints and bootstrap sqlite state
This commit is contained in:
+4
-1
@@ -257,4 +257,7 @@ gitee/
|
|||||||
doraemon.jks
|
doraemon.jks
|
||||||
device.id
|
device.id
|
||||||
commit.sh
|
commit.sh
|
||||||
sql/
|
sql/
|
||||||
|
!go-backend/internal/store/sqlite/sql/
|
||||||
|
!go-backend/internal/store/sqlite/sql/schema.sql
|
||||||
|
!go-backend/internal/store/sqlite/sql/data.sql
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ func main() {
|
|||||||
if cfg.JWTSecret == "" {
|
if cfg.JWTSecret == "" {
|
||||||
log.Println("warning: JWT_SECRET is empty")
|
log.Println("warning: JWT_SECRET is empty")
|
||||||
}
|
}
|
||||||
|
log.Printf("starting go-backend on %s (db=%s)", cfg.Addr, cfg.DBPath)
|
||||||
|
|
||||||
a, err := app.New(cfg)
|
a, err := app.New(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ func (h *Handler) WebSocketHandler() http.Handler {
|
|||||||
|
|
||||||
func (h *Handler) Register(mux *http.ServeMux) {
|
func (h *Handler) Register(mux *http.ServeMux) {
|
||||||
mux.HandleFunc("/api/v1/user/login", h.login)
|
mux.HandleFunc("/api/v1/user/login", h.login)
|
||||||
|
mux.HandleFunc("/api/v1/user/list", h.userList)
|
||||||
mux.HandleFunc("/api/v1/config/get", h.getConfigByName)
|
mux.HandleFunc("/api/v1/config/get", h.getConfigByName)
|
||||||
mux.HandleFunc("/api/v1/config/list", h.getConfigs)
|
mux.HandleFunc("/api/v1/config/list", h.getConfigs)
|
||||||
mux.HandleFunc("/api/v1/config/update", h.updateConfigs)
|
mux.HandleFunc("/api/v1/config/update", h.updateConfigs)
|
||||||
@@ -69,6 +70,15 @@ func (h *Handler) Register(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("/api/v1/captcha/check", h.checkCaptcha)
|
mux.HandleFunc("/api/v1/captcha/check", h.checkCaptcha)
|
||||||
mux.HandleFunc("/api/v1/user/package", h.userPackage)
|
mux.HandleFunc("/api/v1/user/package", h.userPackage)
|
||||||
mux.HandleFunc("/api/v1/user/updatePassword", h.updatePassword)
|
mux.HandleFunc("/api/v1/user/updatePassword", h.updatePassword)
|
||||||
|
mux.HandleFunc("/api/v1/node/list", h.nodeList)
|
||||||
|
mux.HandleFunc("/api/v1/tunnel/list", h.tunnelList)
|
||||||
|
mux.HandleFunc("/api/v1/forward/list", h.forwardList)
|
||||||
|
mux.HandleFunc("/api/v1/speed-limit/list", h.speedLimitList)
|
||||||
|
mux.HandleFunc("/api/v1/tunnel/user/tunnel", h.userTunnelVisibleList)
|
||||||
|
mux.HandleFunc("/api/v1/tunnel/user/list", h.userTunnelList)
|
||||||
|
mux.HandleFunc("/api/v1/group/tunnel/list", h.tunnelGroupList)
|
||||||
|
mux.HandleFunc("/api/v1/group/user/list", h.userGroupList)
|
||||||
|
mux.HandleFunc("/api/v1/group/permission/list", h.groupPermissionList)
|
||||||
|
|
||||||
mux.HandleFunc("/flow/test", h.flowTest)
|
mux.HandleFunc("/flow/test", h.flowTest)
|
||||||
mux.HandleFunc("/flow/config", h.flowConfig)
|
mux.HandleFunc("/flow/config", h.flowConfig)
|
||||||
@@ -182,6 +192,191 @@ func (h *Handler) getConfigs(w http.ResponseWriter, r *http.Request) {
|
|||||||
response.WriteJSON(w, response.OK(cfgMap))
|
response.WriteJSON(w, response.OK(cfgMap))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) userList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
users, err := h.repo.ListUsers()
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.WriteJSON(w, response.OK(users))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) nodeList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := h.repo.ListNodes()
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.WriteJSON(w, response.OK(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) tunnelList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := h.repo.ListTunnels()
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.WriteJSON(w, response.OK(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) forwardList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := h.repo.ListForwards()
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.WriteJSON(w, response.OK(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) speedLimitList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := h.repo.ListSpeedLimits()
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.WriteJSON(w, response.OK(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) userTunnelVisibleList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := userIDFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(401, "无效的token或token已过期"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := h.repo.ListUserAccessibleTunnels(userID)
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.WriteJSON(w, response.OK(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) userTunnelList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
UserID int64 `json:"userId"`
|
||||||
|
}
|
||||||
|
if err := decodeJSON(r.Body, &req); err != nil {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求参数错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.UserID <= 0 {
|
||||||
|
response.WriteJSON(w, response.OK([]interface{}{}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnels, err := h.repo.GetUserPackageTunnels(req.UserID)
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]map[string]interface{}, 0, len(tunnels))
|
||||||
|
for _, t := range tunnels {
|
||||||
|
item := map[string]interface{}{
|
||||||
|
"id": t.ID,
|
||||||
|
"userId": t.UserID,
|
||||||
|
"tunnelId": t.TunnelID,
|
||||||
|
"tunnelName": t.TunnelName,
|
||||||
|
"status": 1,
|
||||||
|
"flow": t.Flow,
|
||||||
|
"num": t.Num,
|
||||||
|
"expTime": t.ExpTime,
|
||||||
|
"flowResetTime": t.FlowResetTime,
|
||||||
|
"inFlow": t.InFlow,
|
||||||
|
"outFlow": t.OutFlow,
|
||||||
|
"tunnelFlow": t.TunnelFlow,
|
||||||
|
"speedId": nil,
|
||||||
|
"speedLimitName": nil,
|
||||||
|
}
|
||||||
|
if t.SpeedID.Valid {
|
||||||
|
item["speedId"] = t.SpeedID.Int64
|
||||||
|
}
|
||||||
|
if t.SpeedLimit.Valid {
|
||||||
|
item["speedLimitName"] = t.SpeedLimit.String
|
||||||
|
}
|
||||||
|
out = append(out, item)
|
||||||
|
}
|
||||||
|
response.WriteJSON(w, response.OK(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) tunnelGroupList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := h.repo.ListTunnelGroups()
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.WriteJSON(w, response.OK(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) userGroupList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := h.repo.ListUserGroups()
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.WriteJSON(w, response.OK(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) groupPermissionList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := h.repo.ListGroupPermissions()
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(w, response.Err(-2, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.WriteJSON(w, response.OK(items))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) checkCaptcha(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) checkCaptcha(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
response.WriteJSON(w, response.ErrDefault("请求失败"))
|
||||||
@@ -535,6 +730,14 @@ func parseUserID(sub string) (int64, error) {
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func userIDFromRequest(r *http.Request) (int64, error) {
|
||||||
|
claims, ok := r.Context().Value(middleware.ClaimsContextKey).(auth.Claims)
|
||||||
|
if !ok {
|
||||||
|
return 0, strconv.ErrSyntax
|
||||||
|
}
|
||||||
|
return parseUserID(claims.Sub)
|
||||||
|
}
|
||||||
|
|
||||||
func nullableNullInt64(v sql.NullInt64) interface{} {
|
func nullableNullInt64(v sql.NullInt64) interface{} {
|
||||||
if v.Valid {
|
if v.Valid {
|
||||||
return v.Int64
|
return v.Int64
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type statusWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *statusWriter) WriteHeader(code int) {
|
||||||
|
w.status = code
|
||||||
|
w.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *statusWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
hj, ok := w.ResponseWriter.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, http.ErrNotSupported
|
||||||
|
}
|
||||||
|
return hj.Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *statusWriter) Flush() {
|
||||||
|
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *statusWriter) ReadFrom(r io.Reader) (int64, error) {
|
||||||
|
if rf, ok := w.ResponseWriter.(io.ReaderFrom); ok {
|
||||||
|
return rf.ReadFrom(r)
|
||||||
|
}
|
||||||
|
return io.Copy(w.ResponseWriter, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *statusWriter) Push(target string, opts *http.PushOptions) error {
|
||||||
|
if p, ok := w.ResponseWriter.(http.Pusher); ok {
|
||||||
|
return p.Push(target, opts)
|
||||||
|
}
|
||||||
|
return http.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequestLog(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sw := &statusWriter{ResponseWriter: w, status: http.StatusOK}
|
||||||
|
start := time.Now()
|
||||||
|
next.ServeHTTP(sw, r)
|
||||||
|
log.Printf("%s %s -> %d (%s)", r.Method, r.URL.Path, sw.status, time.Since(start).String())
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ func NewRouter(h *handler.Handler, jwtSecret string) http.Handler {
|
|||||||
|
|
||||||
wrapped := middleware.Recover(mux)
|
wrapped := middleware.Recover(mux)
|
||||||
wrapped = middleware.JWT(middleware.AuthOptions{JWTSecret: jwtSecret})(wrapped)
|
wrapped = middleware.JWT(middleware.AuthOptions{JWTSecret: jwtSecret})(wrapped)
|
||||||
|
wrapped = middleware.RequestLog(wrapped)
|
||||||
wrapped = middleware.CORS(wrapped)
|
wrapped = middleware.CORS(wrapped)
|
||||||
return wrapped
|
return wrapped
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,25 @@ package sqlite
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed sql/schema.sql
|
||||||
|
var embeddedSchema string
|
||||||
|
|
||||||
|
//go:embed sql/data.sql
|
||||||
|
var embeddedSeedData string
|
||||||
|
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
}
|
}
|
||||||
@@ -85,6 +98,10 @@ type Node struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Open(path string) (*Repository, error) {
|
func Open(path string) (*Repository, error) {
|
||||||
|
if err := ensureParentDir(path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
db, err := sql.Open("sqlite", path)
|
db, err := sql.Open("sqlite", path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -95,6 +112,11 @@ func Open(path string) (*Repository, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := bootstrapSchema(db); err != nil {
|
||||||
|
_ = db.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &Repository{db: db}, nil
|
return &Repository{db: db}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,6 +437,625 @@ func (r *Repository) AddFlow(forwardID, userID int64, userTunnelID int64, inFlow
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListNodes() ([]map[string]interface{}, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return nil, errors.New("repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.Query(`
|
||||||
|
SELECT id, inx, name, server_ip, server_ip_v4, server_ip_v6, port, tcp_listen_addr, udp_listen_addr, version, http, tls, socks, status
|
||||||
|
FROM node
|
||||||
|
ORDER BY inx ASC, id ASC
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
items := make([]map[string]interface{}, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id, inx int64
|
||||||
|
var name, serverIP, port string
|
||||||
|
var serverIPV4, serverIPV6, tcpListen, udpListen, version sql.NullString
|
||||||
|
var httpVal, tlsVal, socksVal, status int
|
||||||
|
|
||||||
|
if err := rows.Scan(&id, &inx, &name, &serverIP, &serverIPV4, &serverIPV6, &port, &tcpListen, &udpListen, &version, &httpVal, &tlsVal, &socksVal, &status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"inx": inx,
|
||||||
|
"name": name,
|
||||||
|
"ip": serverIP,
|
||||||
|
"serverIp": serverIP,
|
||||||
|
"serverIpV4": nullableString(serverIPV4),
|
||||||
|
"serverIpV6": nullableString(serverIPV6),
|
||||||
|
"port": port,
|
||||||
|
"tcpListenAddr": nullableString(tcpListen),
|
||||||
|
"udpListenAddr": nullableString(udpListen),
|
||||||
|
"version": nullableString(version),
|
||||||
|
"http": httpVal,
|
||||||
|
"tls": tlsVal,
|
||||||
|
"socks": socksVal,
|
||||||
|
"status": status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListUsers() ([]map[string]interface{}, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return nil, errors.New("repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.Query(`
|
||||||
|
SELECT id, user, role_id, exp_time, flow, in_flow, out_flow, flow_reset_time, num, created_time, updated_time, status
|
||||||
|
FROM user
|
||||||
|
ORDER BY id ASC
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
items := make([]map[string]interface{}, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id int64
|
||||||
|
var user string
|
||||||
|
var roleID int
|
||||||
|
var expTime, flow, inFlow, outFlow, flowResetTime, createdTime int64
|
||||||
|
var num, status int
|
||||||
|
var updatedTime sql.NullInt64
|
||||||
|
|
||||||
|
if err := rows.Scan(&id, &user, &roleID, &expTime, &flow, &inFlow, &outFlow, &flowResetTime, &num, &createdTime, &updatedTime, &status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"user": user,
|
||||||
|
"name": user,
|
||||||
|
"roleId": roleID,
|
||||||
|
"status": status,
|
||||||
|
"flow": flow,
|
||||||
|
"num": num,
|
||||||
|
"expTime": expTime,
|
||||||
|
"flowResetTime": flowResetTime,
|
||||||
|
"createdTime": createdTime,
|
||||||
|
"updatedTime": nullableInt64(updatedTime),
|
||||||
|
"inFlow": inFlow,
|
||||||
|
"outFlow": outFlow,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListSpeedLimits() ([]map[string]interface{}, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return nil, errors.New("repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.Query(`
|
||||||
|
SELECT id, name, speed, tunnel_id, tunnel_name, status, created_time, updated_time
|
||||||
|
FROM speed_limit
|
||||||
|
ORDER BY id ASC
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
items := make([]map[string]interface{}, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id, tunnelID, createdTime int64
|
||||||
|
var name, tunnelName string
|
||||||
|
var speed, status int
|
||||||
|
var updatedTime sql.NullInt64
|
||||||
|
if err := rows.Scan(&id, &name, &speed, &tunnelID, &tunnelName, &status, &createdTime, &updatedTime); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"name": name,
|
||||||
|
"speed": speed,
|
||||||
|
"tunnelId": tunnelID,
|
||||||
|
"tunnelName": tunnelName,
|
||||||
|
"status": status,
|
||||||
|
"createdTime": createdTime,
|
||||||
|
"updatedTime": nullableInt64(updatedTime),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListForwards() ([]map[string]interface{}, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return nil, errors.New("repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.Query(`
|
||||||
|
SELECT f.id, f.user_id, f.user_name, f.name, f.tunnel_id, t.name, f.remote_addr, f.strategy,
|
||||||
|
f.in_flow, f.out_flow, f.created_time, f.status, f.inx,
|
||||||
|
GROUP_CONCAT(CASE WHEN n.server_ip IS NOT NULL AND fp.port IS NOT NULL THEN n.server_ip || ':' || fp.port END),
|
||||||
|
MIN(fp.port)
|
||||||
|
FROM forward f
|
||||||
|
LEFT JOIN tunnel t ON t.id = f.tunnel_id
|
||||||
|
LEFT JOIN forward_port fp ON fp.forward_id = f.id
|
||||||
|
LEFT JOIN node n ON n.id = fp.node_id
|
||||||
|
GROUP BY f.id
|
||||||
|
ORDER BY f.inx ASC, f.id ASC
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
items := make([]map[string]interface{}, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id, userID, tunnelID, inFlow, outFlow, createdTime, inx int64
|
||||||
|
var userName, name, tunnelName, remoteAddr, strategy string
|
||||||
|
var status int
|
||||||
|
var inIP sql.NullString
|
||||||
|
var inPort sql.NullInt64
|
||||||
|
|
||||||
|
if err := rows.Scan(&id, &userID, &userName, &name, &tunnelID, &tunnelName, &remoteAddr, &strategy, &inFlow, &outFlow, &createdTime, &status, &inx, &inIP, &inPort); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"userId": userID,
|
||||||
|
"userName": userName,
|
||||||
|
"name": name,
|
||||||
|
"tunnelId": tunnelID,
|
||||||
|
"tunnelName": tunnelName,
|
||||||
|
"inIp": nullableString(inIP),
|
||||||
|
"inPort": nullableInt64(inPort),
|
||||||
|
"remoteAddr": remoteAddr,
|
||||||
|
"strategy": strategy,
|
||||||
|
"inFlow": inFlow,
|
||||||
|
"outFlow": outFlow,
|
||||||
|
"createdTime": createdTime,
|
||||||
|
"status": status,
|
||||||
|
"inx": inx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListUserAccessibleTunnels(userID int64) ([]map[string]interface{}, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return nil, errors.New("repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.Query(`
|
||||||
|
SELECT t.id, t.name
|
||||||
|
FROM user_tunnel ut
|
||||||
|
JOIN tunnel t ON t.id = ut.tunnel_id
|
||||||
|
WHERE ut.user_id = ? AND ut.status = 1
|
||||||
|
ORDER BY t.inx ASC, t.id ASC
|
||||||
|
`, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
items := make([]map[string]interface{}, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id int64
|
||||||
|
var name string
|
||||||
|
if err := rows.Scan(&id, &name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, map[string]interface{}{"id": id, "name": name})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListTunnels() ([]map[string]interface{}, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return nil, errors.New("repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.Query(`
|
||||||
|
SELECT id, inx, name, type, flow, traffic_ratio, status, created_time, in_ip
|
||||||
|
FROM tunnel
|
||||||
|
ORDER BY inx ASC, id ASC
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
tunnelMap := make(map[int64]map[string]interface{})
|
||||||
|
orderedIDs := make([]int64, 0)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var id, inx, flow, createdTime int64
|
||||||
|
var name string
|
||||||
|
var typ, status int
|
||||||
|
var trafficRatio float64
|
||||||
|
var inIP sql.NullString
|
||||||
|
if err := rows.Scan(&id, &inx, &name, &typ, &flow, &trafficRatio, &status, &createdTime, &inIP); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnelMap[id] = map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"inx": inx,
|
||||||
|
"name": name,
|
||||||
|
"type": typ,
|
||||||
|
"flow": flow,
|
||||||
|
"trafficRatio": trafficRatio,
|
||||||
|
"status": status,
|
||||||
|
"createdTime": createdTime,
|
||||||
|
"inIp": nullableString(inIP),
|
||||||
|
"inNodeId": make([]map[string]interface{}, 0),
|
||||||
|
"outNodeId": make([]map[string]interface{}, 0),
|
||||||
|
"chainNodes": make([][]map[string]interface{}, 0),
|
||||||
|
}
|
||||||
|
orderedIDs = append(orderedIDs, id)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeIPMap := map[int64]string{}
|
||||||
|
nRows, err := r.db.Query(`SELECT id, server_ip FROM node`)
|
||||||
|
if err == nil {
|
||||||
|
for nRows.Next() {
|
||||||
|
var id int64
|
||||||
|
var ip string
|
||||||
|
if scanErr := nRows.Scan(&id, &ip); scanErr == nil {
|
||||||
|
nodeIPMap[id] = ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = nRows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
chainRows, err := r.db.Query(`
|
||||||
|
SELECT tunnel_id, chain_type, node_id, protocol, strategy, COALESCE(inx, 0)
|
||||||
|
FROM chain_tunnel
|
||||||
|
ORDER BY tunnel_id ASC, chain_type ASC, inx ASC, id ASC
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer chainRows.Close()
|
||||||
|
|
||||||
|
chainBucket := map[int64]map[int][]map[string]interface{}{}
|
||||||
|
inNodeIPs := map[int64][]string{}
|
||||||
|
|
||||||
|
for chainRows.Next() {
|
||||||
|
var tunnelID, nodeID, inx int64
|
||||||
|
var chainType int
|
||||||
|
var protocol, strategy sql.NullString
|
||||||
|
if err := chainRows.Scan(&tunnelID, &chainType, &nodeID, &protocol, &strategy, &inx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, ok := tunnelMap[tunnelID]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeObj := map[string]interface{}{
|
||||||
|
"nodeId": nodeID,
|
||||||
|
"chainType": chainType,
|
||||||
|
"inx": inx,
|
||||||
|
}
|
||||||
|
if protocol.Valid {
|
||||||
|
nodeObj["protocol"] = protocol.String
|
||||||
|
}
|
||||||
|
if strategy.Valid {
|
||||||
|
nodeObj["strategy"] = strategy.String
|
||||||
|
}
|
||||||
|
|
||||||
|
switch chainType {
|
||||||
|
case 1:
|
||||||
|
t["inNodeId"] = append(t["inNodeId"].([]map[string]interface{}), nodeObj)
|
||||||
|
if ip, ok := nodeIPMap[nodeID]; ok && ip != "" {
|
||||||
|
inNodeIPs[tunnelID] = append(inNodeIPs[tunnelID], ip)
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
if _, ok := chainBucket[tunnelID]; !ok {
|
||||||
|
chainBucket[tunnelID] = map[int][]map[string]interface{}{}
|
||||||
|
}
|
||||||
|
chainBucket[tunnelID][int(inx)] = append(chainBucket[tunnelID][int(inx)], nodeObj)
|
||||||
|
case 3:
|
||||||
|
t["outNodeId"] = append(t["outNodeId"].([]map[string]interface{}), nodeObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := chainRows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for tunnelID, groups := range chainBucket {
|
||||||
|
t := tunnelMap[tunnelID]
|
||||||
|
if t == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keys := make([]int, 0, len(groups))
|
||||||
|
for k := range groups {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Ints(keys)
|
||||||
|
ordered := make([][]map[string]interface{}, 0, len(keys))
|
||||||
|
for _, k := range keys {
|
||||||
|
ordered = append(ordered, groups[k])
|
||||||
|
}
|
||||||
|
t["chainNodes"] = ordered
|
||||||
|
|
||||||
|
if s, ok := t["inIp"].(string); !ok || strings.TrimSpace(s) == "" {
|
||||||
|
if ips := inNodeIPs[tunnelID]; len(ips) > 0 {
|
||||||
|
t["inIp"] = strings.Join(ips, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]map[string]interface{}, 0, len(orderedIDs))
|
||||||
|
for _, id := range orderedIDs {
|
||||||
|
if t, ok := tunnelMap[id]; ok {
|
||||||
|
result = append(result, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListTunnelGroups() ([]map[string]interface{}, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return nil, errors.New("repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.Query(`SELECT id, name, status, created_time FROM tunnel_group ORDER BY id ASC`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
result := make([]map[string]interface{}, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id, createdTime int64
|
||||||
|
var name string
|
||||||
|
var status int
|
||||||
|
if err := rows.Scan(&id, &name, &status, &createdTime); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, names, err := r.listTunnelGroupMembers(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"name": name,
|
||||||
|
"status": status,
|
||||||
|
"tunnelIds": ids,
|
||||||
|
"tunnelNames": names,
|
||||||
|
"createdTime": createdTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListUserGroups() ([]map[string]interface{}, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return nil, errors.New("repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.Query(`SELECT id, name, status, created_time FROM user_group ORDER BY id ASC`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
result := make([]map[string]interface{}, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id, createdTime int64
|
||||||
|
var name string
|
||||||
|
var status int
|
||||||
|
if err := rows.Scan(&id, &name, &status, &createdTime); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, names, err := r.listUserGroupMembers(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"name": name,
|
||||||
|
"status": status,
|
||||||
|
"userIds": ids,
|
||||||
|
"userNames": names,
|
||||||
|
"createdTime": createdTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListGroupPermissions() ([]map[string]interface{}, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return nil, errors.New("repository not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.Query(`
|
||||||
|
SELECT gp.id, gp.user_group_id, ug.name, gp.tunnel_group_id, tg.name, gp.created_time
|
||||||
|
FROM group_permission gp
|
||||||
|
LEFT JOIN user_group ug ON ug.id = gp.user_group_id
|
||||||
|
LEFT JOIN tunnel_group tg ON tg.id = gp.tunnel_group_id
|
||||||
|
ORDER BY gp.id ASC
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
result := make([]map[string]interface{}, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id, userGroupID, tunnelGroupID, createdTime int64
|
||||||
|
var userGroupName, tunnelGroupName sql.NullString
|
||||||
|
if err := rows.Scan(&id, &userGroupID, &userGroupName, &tunnelGroupID, &tunnelGroupName, &createdTime); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"userGroupId": userGroupID,
|
||||||
|
"userGroupName": nullableString(userGroupName),
|
||||||
|
"tunnelGroupId": tunnelGroupID,
|
||||||
|
"tunnelGroupName": nullableString(tunnelGroupName),
|
||||||
|
"createdTime": createdTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) listTunnelGroupMembers(groupID int64) ([]int64, []string, error) {
|
||||||
|
rows, err := r.db.Query(`
|
||||||
|
SELECT t.id, t.name
|
||||||
|
FROM tunnel_group_tunnel tgt
|
||||||
|
JOIN tunnel t ON t.id = tgt.tunnel_id
|
||||||
|
WHERE tgt.tunnel_group_id = ?
|
||||||
|
ORDER BY t.id ASC
|
||||||
|
`, groupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
ids := make([]int64, 0)
|
||||||
|
names := make([]string, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id int64
|
||||||
|
var name string
|
||||||
|
if err := rows.Scan(&id, &name); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ids = append(ids, id)
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return ids, names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) listUserGroupMembers(groupID int64) ([]int64, []string, error) {
|
||||||
|
rows, err := r.db.Query(`
|
||||||
|
SELECT u.id, u.user
|
||||||
|
FROM user_group_user ugu
|
||||||
|
JOIN user u ON u.id = ugu.user_id
|
||||||
|
WHERE ugu.user_group_id = ?
|
||||||
|
ORDER BY u.id ASC
|
||||||
|
`, groupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
ids := make([]int64, 0)
|
||||||
|
names := make([]string, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var id int64
|
||||||
|
var name string
|
||||||
|
if err := rows.Scan(&id, &name); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ids = append(ids, id)
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return ids, names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullableString(v sql.NullString) interface{} {
|
||||||
|
if v.Valid {
|
||||||
|
return v.String
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullableInt64(v sql.NullInt64) interface{} {
|
||||||
|
if v.Valid {
|
||||||
|
return v.Int64
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func unixMilliNow() int64 {
|
func unixMilliNow() int64 {
|
||||||
return time.Now().UnixMilli()
|
return time.Now().UnixMilli()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureParentDir(dbPath string) error {
|
||||||
|
if dbPath == "" {
|
||||||
|
return fmt.Errorf("empty db path")
|
||||||
|
}
|
||||||
|
dir := filepath.Dir(dbPath)
|
||||||
|
if dir == "" || dir == "." {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return osMkdirAll(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bootstrapSchema(db *sql.DB) error {
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("nil db")
|
||||||
|
}
|
||||||
|
|
||||||
|
var exists int
|
||||||
|
err := db.QueryRow(`SELECT COUNT(1) FROM sqlite_master WHERE type='table' AND name='user'`).Scan(&exists)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("check schema: %w", err)
|
||||||
|
}
|
||||||
|
if exists > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("sqlite schema not found, bootstrapping embedded schema")
|
||||||
|
if _, err := db.Exec(embeddedSchema); err != nil {
|
||||||
|
return fmt.Errorf("apply schema.sql: %w", err)
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(embeddedSeedData); err != nil {
|
||||||
|
return fmt.Errorf("apply data.sql: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var osMkdirAll = func(path string) error {
|
||||||
|
return os.MkdirAll(path, 0o755)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
INSERT OR IGNORE INTO user (id, user, pwd, role_id, exp_time, flow, in_flow, out_flow, flow_reset_time, num, created_time, updated_time, status)
|
||||||
|
VALUES (1, 'admin_user', '3c85cdebade1c51cf64ca9f3c09d182d', 0, 2727251700000, 99999, 0, 0, 1, 99999, 1748914865000, 1754011744252, 1);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO vite_config (id, name, value, time)
|
||||||
|
VALUES (1, 'app_name', 'flux', 1755147963000);
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
-- SQLite Auto-generated schema
|
||||||
|
-- This will be executed automatically on startup if tables don't exist
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS forward (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
user_name VARCHAR(100) NOT NULL,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
tunnel_id INTEGER NOT NULL,
|
||||||
|
remote_addr TEXT NOT NULL,
|
||||||
|
strategy VARCHAR(100) NOT NULL DEFAULT 'fifo',
|
||||||
|
in_flow INTEGER NOT NULL DEFAULT 0,
|
||||||
|
out_flow INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_time INTEGER NOT NULL,
|
||||||
|
updated_time INTEGER NOT NULL,
|
||||||
|
status INTEGER NOT NULL,
|
||||||
|
inx INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS forward_port (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
forward_id INTEGER NOT NULL,
|
||||||
|
node_id INTEGER NOT NULL,
|
||||||
|
port INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS node (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
secret VARCHAR(100) NOT NULL,
|
||||||
|
server_ip VARCHAR(100) NOT NULL,
|
||||||
|
server_ip_v4 VARCHAR(100),
|
||||||
|
server_ip_v6 VARCHAR(100),
|
||||||
|
port TEXT NOT NULL,
|
||||||
|
interface_name VARCHAR(200),
|
||||||
|
version VARCHAR(100),
|
||||||
|
http INTEGER NOT NULL DEFAULT 0,
|
||||||
|
tls INTEGER NOT NULL DEFAULT 0,
|
||||||
|
socks INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_time INTEGER NOT NULL,
|
||||||
|
updated_time INTEGER,
|
||||||
|
status INTEGER NOT NULL,
|
||||||
|
tcp_listen_addr VARCHAR(100) NOT NULL DEFAULT '[::]',
|
||||||
|
udp_listen_addr VARCHAR(100) NOT NULL DEFAULT '[::]',
|
||||||
|
inx INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS speed_limit (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
speed INTEGER NOT NULL,
|
||||||
|
tunnel_id INTEGER NOT NULL,
|
||||||
|
tunnel_name VARCHAR(100) NOT NULL,
|
||||||
|
created_time INTEGER NOT NULL,
|
||||||
|
updated_time INTEGER,
|
||||||
|
status INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS statistics_flow (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
flow INTEGER NOT NULL,
|
||||||
|
total_flow INTEGER NOT NULL,
|
||||||
|
time VARCHAR(100) NOT NULL,
|
||||||
|
created_time INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tunnel (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
traffic_ratio REAL NOT NULL DEFAULT 1.0,
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
protocol VARCHAR(10) NOT NULL DEFAULT 'tls',
|
||||||
|
flow INTEGER NOT NULL,
|
||||||
|
created_time INTEGER NOT NULL,
|
||||||
|
updated_time INTEGER NOT NULL,
|
||||||
|
status INTEGER NOT NULL,
|
||||||
|
in_ip TEXT,
|
||||||
|
inx INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS chain_tunnel (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
tunnel_id INTEGER NOT NULL ,
|
||||||
|
chain_type VARCHAR(10) NOT NULL,
|
||||||
|
node_id INTEGER NOT NULL ,
|
||||||
|
port INTEGER,
|
||||||
|
strategy VARCHAR(10),
|
||||||
|
inx INTEGER,
|
||||||
|
protocol VARCHAR(10)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user VARCHAR(100) NOT NULL,
|
||||||
|
pwd VARCHAR(100) NOT NULL,
|
||||||
|
role_id INTEGER NOT NULL,
|
||||||
|
exp_time INTEGER NOT NULL,
|
||||||
|
flow INTEGER NOT NULL,
|
||||||
|
in_flow INTEGER NOT NULL DEFAULT 0,
|
||||||
|
out_flow INTEGER NOT NULL DEFAULT 0,
|
||||||
|
flow_reset_time INTEGER NOT NULL,
|
||||||
|
num INTEGER NOT NULL,
|
||||||
|
created_time INTEGER NOT NULL,
|
||||||
|
updated_time INTEGER,
|
||||||
|
status INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_tunnel (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
tunnel_id INTEGER NOT NULL,
|
||||||
|
speed_id INTEGER,
|
||||||
|
num INTEGER NOT NULL,
|
||||||
|
flow INTEGER NOT NULL,
|
||||||
|
in_flow INTEGER NOT NULL DEFAULT 0,
|
||||||
|
out_flow INTEGER NOT NULL DEFAULT 0,
|
||||||
|
flow_reset_time INTEGER NOT NULL,
|
||||||
|
exp_time INTEGER NOT NULL,
|
||||||
|
status INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tunnel_group (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
created_time INTEGER NOT NULL,
|
||||||
|
updated_time INTEGER NOT NULL,
|
||||||
|
status INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_group (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
created_time INTEGER NOT NULL,
|
||||||
|
updated_time INTEGER NOT NULL,
|
||||||
|
status INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tunnel_group_tunnel (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
tunnel_group_id INTEGER NOT NULL,
|
||||||
|
tunnel_id INTEGER NOT NULL,
|
||||||
|
created_time INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_group_user (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_group_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
created_time INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS group_permission (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_group_id INTEGER NOT NULL,
|
||||||
|
tunnel_group_id INTEGER NOT NULL,
|
||||||
|
created_time INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS group_permission_grant (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_group_id INTEGER NOT NULL,
|
||||||
|
tunnel_group_id INTEGER NOT NULL,
|
||||||
|
user_tunnel_id INTEGER NOT NULL,
|
||||||
|
created_by_group INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_time INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_tunnel_group_name ON tunnel_group(name);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_group_name ON user_group(name);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_tunnel_group_tunnel_unique ON tunnel_group_tunnel(tunnel_group_id, tunnel_id);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_group_user_unique ON user_group_user(user_group_id, user_id);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_group_permission_unique ON group_permission(user_group_id, tunnel_group_id);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_group_permission_grant_unique ON group_permission_grant(user_group_id, tunnel_group_id, user_tunnel_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS vite_config (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name VARCHAR(200) NOT NULL UNIQUE,
|
||||||
|
value VARCHAR(200) NOT NULL,
|
||||||
|
time INTEGER NOT NULL
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user