mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-01-12 01:59:00 +08:00
feat: support rule disabling and hit/miss count/at tracking in restful api (#2502)
This commit is contained in:
parent
efb800866e
commit
19a6b5d6f7
@ -34,6 +34,7 @@ import (
|
||||
R "github.com/metacubex/mihomo/rules"
|
||||
RC "github.com/metacubex/mihomo/rules/common"
|
||||
RP "github.com/metacubex/mihomo/rules/provider"
|
||||
RW "github.com/metacubex/mihomo/rules/wrapper"
|
||||
T "github.com/metacubex/mihomo/tunnel"
|
||||
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
@ -1083,6 +1084,10 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, ruleProviders
|
||||
}
|
||||
}
|
||||
|
||||
if format == "rules" { // only wrap top level rules
|
||||
parsed = RW.NewRuleWrapper(parsed)
|
||||
}
|
||||
|
||||
rules = append(rules, parsed)
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package constant
|
||||
|
||||
import "time"
|
||||
|
||||
// Rule Type
|
||||
const (
|
||||
Domain RuleType = iota
|
||||
@ -126,6 +128,27 @@ type Rule interface {
|
||||
ProviderNames() []string
|
||||
}
|
||||
|
||||
type RuleWrapper interface {
|
||||
Rule
|
||||
|
||||
// SetDisabled to set enable/disable rule
|
||||
SetDisabled(v bool)
|
||||
// IsDisabled return rule is disabled or not
|
||||
IsDisabled() bool
|
||||
|
||||
// HitCount for statistics
|
||||
HitCount() uint64
|
||||
// HitAt for statistics
|
||||
HitAt() time.Time
|
||||
// MissCount for statistics
|
||||
MissCount() uint64
|
||||
// MissAt for statistics
|
||||
MissAt() time.Time
|
||||
|
||||
// Unwrap return Rule
|
||||
Unwrap() Rule
|
||||
}
|
||||
|
||||
type RuleMatchHelper struct {
|
||||
ResolveIP func()
|
||||
FindProcess func()
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
|
||||
@ -12,26 +14,46 @@ import (
|
||||
func ruleRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getRules)
|
||||
if !embedMode { // disallow update/patch rules in embed mode
|
||||
r.Patch("/disable", disableRules)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
Index int `json:"index"`
|
||||
Type string `json:"type"`
|
||||
Payload string `json:"payload"`
|
||||
Proxy string `json:"proxy"`
|
||||
Size int `json:"size"`
|
||||
|
||||
// from RuleWrapper
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
HitCount uint64 `json:"hitCount,omitempty"`
|
||||
HitAt time.Time `json:"hitAt,omitempty"`
|
||||
MissCount uint64 `json:"missCount,omitempty"`
|
||||
MissAt time.Time `json:"missAt,omitempty"`
|
||||
}
|
||||
|
||||
func getRules(w http.ResponseWriter, r *http.Request) {
|
||||
rawRules := tunnel.Rules()
|
||||
rules := []Rule{}
|
||||
for _, rule := range rawRules {
|
||||
rules := make([]Rule, 0, len(rawRules))
|
||||
for index, rule := range rawRules {
|
||||
r := Rule{
|
||||
Index: index,
|
||||
Type: rule.RuleType().String(),
|
||||
Payload: rule.Payload(),
|
||||
Proxy: rule.Adapter(),
|
||||
Size: -1,
|
||||
}
|
||||
if ruleWrapper, ok := rule.(constant.RuleWrapper); ok {
|
||||
r.Disabled = ruleWrapper.IsDisabled()
|
||||
r.HitCount = ruleWrapper.HitCount()
|
||||
r.HitAt = ruleWrapper.HitAt()
|
||||
r.MissCount = ruleWrapper.MissCount()
|
||||
r.MissAt = ruleWrapper.MissAt()
|
||||
rule = ruleWrapper.Unwrap() // unwrap RuleWrapper
|
||||
}
|
||||
if rule.RuleType() == constant.GEOIP || rule.RuleType() == constant.GEOSITE {
|
||||
r.Size = rule.(constant.RuleGroup).GetRecodeSize()
|
||||
}
|
||||
@ -43,3 +65,29 @@ func getRules(w http.ResponseWriter, r *http.Request) {
|
||||
"rules": rules,
|
||||
})
|
||||
}
|
||||
|
||||
// disableRules disable or enable rules by their indexes.
|
||||
func disableRules(w http.ResponseWriter, r *http.Request) {
|
||||
// key: rule index, value: disabled
|
||||
var payload map[int]bool
|
||||
if err := render.DecodeJSON(r.Body, &payload); err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if len(payload) != 0 {
|
||||
rules := tunnel.Rules()
|
||||
for index, disabled := range payload {
|
||||
if index < 0 || index >= len(rules) {
|
||||
continue
|
||||
}
|
||||
rule := rules[index]
|
||||
if ruleWrapper, ok := rule.(constant.RuleWrapper); ok {
|
||||
ruleWrapper.SetDisabled(disabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
72
rules/wrapper/wrapper.go
Normal file
72
rules/wrapper/wrapper.go
Normal file
@ -0,0 +1,72 @@
|
||||
package wrapper
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
type RuleWrapper struct {
|
||||
C.Rule
|
||||
disabled atomic.Bool
|
||||
hitCount atomic.Uint64
|
||||
hitAt atomic.TypedValue[time.Time]
|
||||
missCount atomic.Uint64
|
||||
missAt atomic.TypedValue[time.Time]
|
||||
}
|
||||
|
||||
func (r *RuleWrapper) IsDisabled() bool {
|
||||
return r.disabled.Load()
|
||||
}
|
||||
|
||||
func (r *RuleWrapper) SetDisabled(v bool) {
|
||||
r.disabled.Store(v)
|
||||
}
|
||||
|
||||
func (r *RuleWrapper) HitCount() uint64 {
|
||||
return r.hitCount.Load()
|
||||
}
|
||||
|
||||
func (r *RuleWrapper) HitAt() time.Time {
|
||||
return r.hitAt.Load()
|
||||
}
|
||||
|
||||
func (r *RuleWrapper) MissCount() uint64 {
|
||||
return r.missCount.Load()
|
||||
}
|
||||
|
||||
func (r *RuleWrapper) MissAt() time.Time {
|
||||
return r.missAt.Load()
|
||||
}
|
||||
|
||||
func (r *RuleWrapper) Unwrap() C.Rule {
|
||||
return r.Rule
|
||||
}
|
||||
|
||||
func (r *RuleWrapper) Hit() {
|
||||
r.hitCount.Add(1)
|
||||
r.hitAt.Store(time.Now())
|
||||
}
|
||||
|
||||
func (r *RuleWrapper) Miss() {
|
||||
r.missCount.Add(1)
|
||||
r.missAt.Store(time.Now())
|
||||
}
|
||||
|
||||
func (r *RuleWrapper) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
|
||||
if r.IsDisabled() {
|
||||
return false, ""
|
||||
}
|
||||
ok, adapter := r.Rule.Match(metadata, helper)
|
||||
if ok {
|
||||
r.Hit()
|
||||
} else {
|
||||
r.Miss()
|
||||
}
|
||||
return ok, adapter
|
||||
}
|
||||
|
||||
func NewRuleWrapper(rule C.Rule) C.RuleWrapper {
|
||||
return &RuleWrapper{Rule: rule}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user