mirror of
https://github.com/Li4n0/revsuit.git
synced 2026-09-24 07:31:53 +08:00
+2
-2
@@ -53,7 +53,7 @@ func (s *Server) getRules() []*Rule {
|
||||
return s.rules
|
||||
}
|
||||
|
||||
func (s *Server) updateRules() error {
|
||||
func (s *Server) UpdateRules() error {
|
||||
db := database.DB.Model(new(Rule))
|
||||
defer s.rulesLock.Unlock()
|
||||
s.rulesLock.Lock()
|
||||
@@ -209,7 +209,7 @@ func (s *Server) Run() {
|
||||
s.livingLock.Unlock()
|
||||
}()
|
||||
|
||||
if err := s.updateRules(); err != nil {
|
||||
if err := s.UpdateRules(); err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (r *Rule) Delete() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -67,7 +67,7 @@ func (s *Server) getRules() []*Rule {
|
||||
return s.rules
|
||||
}
|
||||
|
||||
func (s *Server) updateRules() error {
|
||||
func (s *Server) UpdateRules() error {
|
||||
db := database.DB.Model(new(Rule))
|
||||
defer s.rulesLock.Unlock()
|
||||
s.rulesLock.Lock()
|
||||
@@ -94,6 +94,7 @@ func (s *Server) authenticate(user, password string) (_rule *Rule, flag, flagGro
|
||||
}
|
||||
|
||||
func (s *Server) getPasvAddressFromCache(ip, pasvAddressTpl string) (pasvAddress string) {
|
||||
pasvAddress = pasvAddressTpl
|
||||
if strings.Contains(pasvAddressTpl, ",") {
|
||||
values, ok := rebindingCache.Get(ip)
|
||||
if !ok {
|
||||
@@ -202,8 +203,8 @@ loop:
|
||||
if pasvAddress = s.getPasvAddressFromCache(ip, _rule.PasvAddress); pasvAddress == "" {
|
||||
pasvAddress = fmt.Sprintf("%s:%d", s.pasvIP, s.PasvPort)
|
||||
}
|
||||
|
||||
isRedirect = rule.CompileTpl(pasvAddress, vars) != fmt.Sprintf("%s:%d", s.pasvIP, s.PasvPort)
|
||||
pasvAddress = rule.CompileTpl(pasvAddress, vars)
|
||||
isRedirect = pasvAddress != fmt.Sprintf("%s:%d", s.pasvIP, s.PasvPort)
|
||||
case "SIZE":
|
||||
path += strings.TrimLeft(args, "/")
|
||||
if _rule == nil || isRedirect || len(_rule.Data) == 0 {
|
||||
@@ -217,8 +218,6 @@ loop:
|
||||
case "PASV":
|
||||
//Just so that ide does not prompt that there may be a nil value
|
||||
if _rule != nil {
|
||||
// return rule's pasv_address or default pasv address
|
||||
pasvAddress := rule.CompileTpl(pasvAddress, vars)
|
||||
pasvIP, pasvPort, err := net.SplitHostPort(pasvAddress)
|
||||
if err != nil {
|
||||
log.Warn("FTP failed to split rule[id:%d] pasv_address(%s) :%s", _rule.ID, pasvAddress, err)
|
||||
@@ -365,7 +364,7 @@ func (s *Server) Run() {
|
||||
s.livingLock.Unlock()
|
||||
}()
|
||||
|
||||
if err := s.updateRules(); err != nil {
|
||||
if err := s.UpdateRules(); err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
return GetServer().updateRules()
|
||||
return GetServer().UpdateRules()
|
||||
}
|
||||
|
||||
// Delete deletes the ftp rule in database and ruleSet
|
||||
@@ -65,7 +65,7 @@ func (r *Rule) Delete() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
return GetServer().updateRules()
|
||||
return GetServer().UpdateRules()
|
||||
}
|
||||
|
||||
// ListRules lists all ftp rules those satisfy the filter
|
||||
|
||||
+4
-4
@@ -48,7 +48,7 @@ func (s *Server) getRules() []*Rule {
|
||||
return s.rules
|
||||
}
|
||||
|
||||
func (s *Server) updateRules() error {
|
||||
func (s *Server) UpdateRules() error {
|
||||
db := database.DB.Model(new(Rule))
|
||||
defer s.rulesLock.Unlock()
|
||||
s.rulesLock.Lock()
|
||||
@@ -105,11 +105,11 @@ func (s *Server) ConnectionClosed(c *vmysql.Conn) {
|
||||
var clientName, clientOS, flag, flagGroup string
|
||||
|
||||
user := c.User
|
||||
schema := c.SchemaName
|
||||
supportLoadLocalData := c.SupportLoadDataLocal
|
||||
|
||||
cr, ok := s.connRulePool.Load(c.ConnectionID)
|
||||
if !ok {
|
||||
log.Warn("MySQL Connection rule(%d) not match flag", c.ConnectionID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func (s *Server) ConnectionClosed(c *vmysql.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
r, err := newRecord(_rule, flag, user, clientName, clientOS, ip, qqwry.Area(ip), supportLoadLocalData, files)
|
||||
r, err := newRecord(_rule, flag, user, schema, clientName, clientOS, ip, qqwry.Area(ip), supportLoadLocalData, files)
|
||||
if err != nil {
|
||||
log.Warn("MySQL record[rule_id: %s] created failed: %s", _rule.Name, err)
|
||||
return
|
||||
@@ -288,7 +288,7 @@ func (s *Server) Run() {
|
||||
s.Enable = false
|
||||
s.livingLock.Unlock()
|
||||
}()
|
||||
if err := s.updateRules(); err != nil {
|
||||
if err := s.UpdateRules(); err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
+6
-1
@@ -17,6 +17,7 @@ type Record struct {
|
||||
record.BaseRecord
|
||||
Username string `gorm:"index" form:"username" json:"username" notice:"username"`
|
||||
ClientName string `gorm:"index" form:"client_name" json:"client_name" notice:"client_name"`
|
||||
Schema string `gorm:"index" form:"schema" json:"schema" notice:"schema"`
|
||||
ClientOS string `gorm:"index" form:"client_os" json:"client_os" notice:"client_os"`
|
||||
LoadLocalData bool `gorm:"index" form:"load_local_data" json:"load_local_data" notice:"load_local_data"`
|
||||
Files []file.MySQLFile `form:"-" json:"files" notice:"-"`
|
||||
@@ -31,7 +32,7 @@ func (r Record) Notice() {
|
||||
notice.Notice(r)
|
||||
}
|
||||
|
||||
func newRecord(rule *Rule, flag, username, clientName, clientOS, remoteIp, ipArea string, supportLoadLocalData bool, files []file.MySQLFile) (r *Record, err error) {
|
||||
func newRecord(rule *Rule, flag, username, schema, clientName, clientOS, remoteIp, ipArea string, supportLoadLocalData bool, files []file.MySQLFile) (r *Record, err error) {
|
||||
r = &Record{
|
||||
BaseRecord: record.BaseRecord{
|
||||
Flag: flag,
|
||||
@@ -40,6 +41,7 @@ func newRecord(rule *Rule, flag, username, clientName, clientOS, remoteIp, ipAre
|
||||
RequestTime: time.Now(),
|
||||
},
|
||||
Username: username,
|
||||
Schema: schema,
|
||||
ClientName: clientName,
|
||||
ClientOS: clientOS,
|
||||
LoadLocalData: supportLoadLocalData,
|
||||
@@ -90,6 +92,9 @@ func ListRecords(c *gin.Context) {
|
||||
if mysqlRecord.ClientName != "" {
|
||||
db.Where("client_name like ?", "%"+mysqlRecord.ClientName)
|
||||
}
|
||||
if mysqlRecord.Schema != "" {
|
||||
db.Where("schema like ?", "%"+mysqlRecord.Schema)
|
||||
}
|
||||
if c.Query("load_local_data") != "" {
|
||||
if c.Query("load_local_data") == "true" {
|
||||
db.Where("load_local_data = ?", true)
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (r *Rule) Delete() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+7
-8
@@ -83,7 +83,7 @@ func (s *Server) getRules() []*Rule {
|
||||
return s.rules
|
||||
}
|
||||
|
||||
func (s *Server) updateRules() error {
|
||||
func (s *Server) UpdateRules() error {
|
||||
db := database.DB.Model(new(Rule))
|
||||
defer s.rulesLock.Unlock()
|
||||
s.rulesLock.Lock()
|
||||
@@ -116,7 +116,7 @@ func (s *Server) Restart() {
|
||||
}
|
||||
|
||||
func (s *Server) Run() {
|
||||
if err := s.updateRules(); err != nil {
|
||||
if err := s.UpdateRules(); err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
for {
|
||||
@@ -156,8 +156,12 @@ func compileTpl(c *gin.Context, tpl string, vars map[string]string) (compiled st
|
||||
|
||||
func (s *Server) Receive(c *gin.Context) {
|
||||
u := c.Request.URL.String()
|
||||
raw, err := getRawRequest(c.Request)
|
||||
if err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
for _, _rule := range s.getRules() {
|
||||
flag, flagGroup, vars := _rule.Match(u)
|
||||
flag, flagGroup, vars := _rule.Match(string(raw))
|
||||
if flag == "" {
|
||||
continue
|
||||
}
|
||||
@@ -172,11 +176,6 @@ func (s *Server) Receive(c *gin.Context) {
|
||||
delete(c.Request.Header, s.IpHeader)
|
||||
}
|
||||
|
||||
raw, err := getRawRequest(c.Request)
|
||||
if err != nil {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
|
||||
// create new record
|
||||
r, err := NewRecord(_rule, flag, c.Request.Method, u, ip, area, string(raw))
|
||||
if err != nil {
|
||||
|
||||
+4
-4
@@ -14,7 +14,7 @@ var _ record.Record = (*Record)(nil)
|
||||
|
||||
type Record struct {
|
||||
Method string `gorm:"index" form:"method" json:"method"`
|
||||
Path string `form:"path" json:"path"`
|
||||
URI string `form:"uri" json:"uri"`
|
||||
record.BaseRecord
|
||||
RawRequest string `json:"raw_request" notice:"-"`
|
||||
Rule Rule `gorm:"foreignKey:RuleName;references:Name;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" form:"-" json:"-" notice:"-"`
|
||||
@@ -37,7 +37,7 @@ func NewRecord(rule *Rule, flag, method, url, ip, area, raw string) (r *Record,
|
||||
RequestTime: time.Now(),
|
||||
},
|
||||
Method: method,
|
||||
Path: url,
|
||||
URI: url,
|
||||
RawRequest: raw,
|
||||
Rule: *rule,
|
||||
}
|
||||
@@ -78,8 +78,8 @@ func ListRecords(c *gin.Context) {
|
||||
if httpRecord.Method != "" {
|
||||
db.Where("method = ?", httpRecord.Method)
|
||||
}
|
||||
if httpRecord.Path != "" {
|
||||
db.Where("path like ?", "%"+httpRecord.Path+"%")
|
||||
if httpRecord.URI != "" {
|
||||
db.Where("path like ?", "%"+httpRecord.URI+"%")
|
||||
}
|
||||
if httpRecord.RemoteIP != "" {
|
||||
db.Where("remote_ip = ?", httpRecord.RemoteIP)
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (r *Rule) Delete() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ func (s *Server) getRules() []*Rule {
|
||||
return s.rules
|
||||
}
|
||||
|
||||
func (s *Server) updateRules() error {
|
||||
func (s *Server) UpdateRules() error {
|
||||
db := database.DB.Model(new(Rule))
|
||||
defer s.rulesLock.Unlock()
|
||||
s.rulesLock.Lock()
|
||||
@@ -165,7 +165,7 @@ func (s *Server) Run() {
|
||||
s.livingLock.Unlock()
|
||||
}()
|
||||
|
||||
if err := s.updateRules(); err != nil {
|
||||
if err := s.UpdateRules(); err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func (r *Rule) Delete() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = GetServer().updateRules()
|
||||
err = GetServer().UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/li4n0/revsuit/internal/record"
|
||||
"github.com/li4n0/revsuit/internal/recycler"
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
@@ -26,18 +25,15 @@ func ping(c *gin.Context) {
|
||||
c.String(200, "pong")
|
||||
}
|
||||
|
||||
func events(c *gin.Context) {
|
||||
log.Info("Receive client connection from %v", c.Request.RemoteAddr)
|
||||
func (revsuit *Revsuit) events(c *gin.Context) {
|
||||
id := revsuit.addClient(c)
|
||||
log.Info("Receive client[id:%d] connection from %v", id, c.Request.RemoteAddr)
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
select {
|
||||
case <-c.Writer.CloseNotify():
|
||||
return false
|
||||
case r := <-record.Channel():
|
||||
c.SSEvent("message", r.GetFlag())
|
||||
}
|
||||
return true
|
||||
<-c.Writer.CloseNotify()
|
||||
return false
|
||||
})
|
||||
log.Info("Client %s disconnect", c.Request.RemoteAddr)
|
||||
revsuit.removeClient(id)
|
||||
log.Info("Client[id:%d, remote_addr:%s] disconnect", id, c.Request.RemoteAddr)
|
||||
}
|
||||
|
||||
func recovery(c *gin.Context) {
|
||||
|
||||
@@ -40,7 +40,7 @@ func (revsuit *Revsuit) registerPlatformRouter() {
|
||||
|
||||
//platform routers
|
||||
api.GET("/auth", auth)
|
||||
api.GET("/events", events)
|
||||
api.GET("/events", revsuit.events)
|
||||
api.GET("/ping", ping)
|
||||
api.GET("/version", version)
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func (revsuit *Revsuit) registerHttpRouter() {
|
||||
// init settings router group
|
||||
settingsGroup := revsuit.http.ApiGroup.Group("setting")
|
||||
settingsGroup.GET("/exportRules", exportRules)
|
||||
settingsGroup.POST("/importRules", importRules)
|
||||
settingsGroup.POST("/importRules", revsuit.importRules)
|
||||
settingsGroup.GET("/getPlatformConfig", revsuit.getPlatformConfig)
|
||||
settingsGroup.POST("/updatePlatformConfig", revsuit.updatePlatformConfig)
|
||||
settingsGroup.GET("/getDnsConfig", revsuit.getDnsConfig)
|
||||
|
||||
+41
-2
@@ -1,10 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/li4n0/revsuit/internal/database"
|
||||
"github.com/li4n0/revsuit/internal/file"
|
||||
"github.com/li4n0/revsuit/internal/notice"
|
||||
"github.com/li4n0/revsuit/internal/record"
|
||||
"github.com/li4n0/revsuit/pkg/dns"
|
||||
"github.com/li4n0/revsuit/pkg/ftp"
|
||||
"github.com/li4n0/revsuit/pkg/mysql"
|
||||
@@ -14,7 +17,7 @@ import (
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
const VERSION = "0.1.2-beta"
|
||||
const VERSION = "0.1.3-beta"
|
||||
|
||||
type Revsuit struct {
|
||||
config *Config
|
||||
@@ -25,6 +28,29 @@ type Revsuit struct {
|
||||
mysql *mysql.Server
|
||||
rmi *rmi.Server
|
||||
ftp *ftp.Server
|
||||
|
||||
clients map[int]*gin.Context
|
||||
clientID int
|
||||
clientsLock sync.RWMutex
|
||||
clientsNum chan struct{}
|
||||
}
|
||||
|
||||
func (revsuit *Revsuit) addClient(c *gin.Context) int {
|
||||
revsuit.clientsLock.Lock()
|
||||
defer revsuit.clientsLock.Unlock()
|
||||
|
||||
revsuit.clientID++
|
||||
revsuit.clients[revsuit.clientID] = c
|
||||
revsuit.clientsNum <- struct{}{}
|
||||
return revsuit.clientID
|
||||
}
|
||||
|
||||
func (revsuit *Revsuit) removeClient(id int) {
|
||||
revsuit.clientsLock.Lock()
|
||||
defer revsuit.clientsLock.Unlock()
|
||||
|
||||
delete(revsuit.clients, id)
|
||||
<-revsuit.clientsNum
|
||||
}
|
||||
|
||||
func initDatabase(dsn string) {
|
||||
@@ -188,6 +214,8 @@ func New(c *Config) *Revsuit {
|
||||
s.dns.SetServerIP(c.ExternalIP)
|
||||
s.ftp.SetPasvIP(c.ExternalIP)
|
||||
}
|
||||
s.clients = make(map[int]*gin.Context)
|
||||
s.clientsNum = make(chan struct{}, 100)
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -207,6 +235,17 @@ func (revsuit *Revsuit) Run() {
|
||||
if revsuit.ftp != nil && revsuit.ftp.Enable {
|
||||
go revsuit.ftp.Run()
|
||||
}
|
||||
|
||||
go func() {
|
||||
for r := range record.Channel() {
|
||||
<-revsuit.clientsNum
|
||||
revsuit.clientsLock.RLock()
|
||||
for _, client := range revsuit.clients {
|
||||
client.SSEvent("message", r.GetFlag())
|
||||
client.Writer.Flush()
|
||||
}
|
||||
revsuit.clientsNum <- struct{}{}
|
||||
revsuit.clientsLock.RUnlock()
|
||||
}
|
||||
}()
|
||||
revsuit.http.Run()
|
||||
}
|
||||
|
||||
+26
-16
@@ -45,7 +45,7 @@ func exportRules(c *gin.Context) {
|
||||
c.String(200, string(out))
|
||||
}
|
||||
|
||||
func importRules(c *gin.Context) {
|
||||
func (revsuit *Revsuit) importRules(c *gin.Context) {
|
||||
var (
|
||||
db = database.DB
|
||||
rules Rules
|
||||
@@ -87,48 +87,58 @@ func importRules(c *gin.Context) {
|
||||
}
|
||||
|
||||
for _, rule := range rules.Http {
|
||||
err := db.Model(&rhttp.Rule{}).Create(&rule).Error
|
||||
if err != nil {
|
||||
if err := db.Model(&rhttp.Rule{}).Create(&rule).Error; err != nil {
|
||||
errs = append(errs, errors.Wrap(err, fmt.Sprintf("http rule[%s]", rule.Name)).Error())
|
||||
continue
|
||||
}
|
||||
count += 1
|
||||
count++
|
||||
}
|
||||
if err := revsuit.http.UpdateRules(); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
for _, rule := range rules.Dns {
|
||||
err := db.Model(&dns.Rule{}).Create(&rule).Error
|
||||
if err != nil {
|
||||
if err := db.Model(&dns.Rule{}).Create(&rule).Error; err != nil {
|
||||
errs = append(errs, errors.Wrap(err, fmt.Sprintf("dns rule[%s]", rule.Name)).Error())
|
||||
continue
|
||||
}
|
||||
count += 1
|
||||
count++
|
||||
}
|
||||
if err := revsuit.dns.UpdateRules(); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
for _, rule := range rules.Mysql {
|
||||
err := db.Model(&mysql.Rule{}).Create(&rule).Error
|
||||
if err != nil {
|
||||
if err := db.Model(&mysql.Rule{}).Create(&rule).Error; err != nil {
|
||||
errs = append(errs, errors.Wrap(err, fmt.Sprintf("mysql rule[%s]", rule.Name)).Error())
|
||||
continue
|
||||
}
|
||||
count += 1
|
||||
count++
|
||||
}
|
||||
if err := revsuit.mysql.UpdateRules(); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
for _, rule := range rules.Rmi {
|
||||
err := db.Model(&rmi.Rule{}).Create(&rule).Error
|
||||
if err != nil {
|
||||
if err := db.Model(&rmi.Rule{}).Create(&rule).Error; err != nil {
|
||||
errs = append(errs, errors.Wrap(err, fmt.Sprintf("rmi rule[%s]", rule.Name)).Error())
|
||||
continue
|
||||
}
|
||||
count += 1
|
||||
count++
|
||||
}
|
||||
if err := revsuit.rmi.UpdateRules(); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
for _, rule := range rules.Ftp {
|
||||
err := db.Model(&ftp.Rule{}).Create(&rule).Error
|
||||
if err != nil {
|
||||
if err := db.Model(&ftp.Rule{}).Create(&rule).Error; err != nil {
|
||||
errs = append(errs, errors.Wrap(err, fmt.Sprintf("ftp rule[%s]", rule.Name)).Error())
|
||||
continue
|
||||
}
|
||||
count += 1
|
||||
count++
|
||||
}
|
||||
if err := revsuit.ftp.UpdateRules(); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
|
||||
Reference in New Issue
Block a user