mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
feat(xray): autocomplete geosite/geoip categories in the routing rule editor
Writing a routing rule today means remembering exact geosite:/geoip:/ ext:file:code syntax by hand, with no way to discover what categories actually exist in the .dat files sitting in the bin folder -- including custom ones like geosite_roscom.dat added via the Geodata auto-update feature. The Domain/IP fields in the rule editor now suggest categories as you type (e.g. "you" -> "geosite:youtube"), built live from whatever .dat files are actually on disk, while still accepting any free-typed value exactly as before. Backend: GET /panel/api/xray/getGeodataCategories scans the bin folder, parses matched geosite*/geoip*.dat files via xray-core's own exported protobuf types, and formats each category as the exact rule syntax xray-core's parser accepts -- geosite:/geoip: for the default files, ext:<file>:<code> for anything else (there's no shorthand for custom files). Cached in memory keyed by each file's (name, size, modTime) so a request-time scan is cheap until a file actually changes. Frontend: the Domain/IP inputs become Select "tags" fields fed by a new useGeodataCategories() query hook, with an explicit substring filter so "you" matches "geosite:youtube" (not a prefix). The array<->CSV-string adapter lives entirely at the FormField transform boundary, so the underlying form schema and saved rule shape are unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// TestGetGeodataCategoriesEndpoint exercises the real HTTP route (gin
|
||||
// routing, controller wiring, JSON envelope) rather than just the
|
||||
// underlying service function -- a genuine end-to-end check of
|
||||
// GET /panel/api/xray/getGeodataCategories given no DB is available in
|
||||
// this environment (go-sqlite3 needs cgo) to run the full panel binary.
|
||||
func TestGetGeodataCategoriesEndpoint(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("XUI_BIN_FOLDER", dir)
|
||||
|
||||
writeFixture(t, filepath.Join(dir, "geosite.dat"), &geodata.GeoSiteList{
|
||||
Entry: []*geodata.GeoSite{{Code: "YOUTUBE"}},
|
||||
})
|
||||
writeFixture(t, filepath.Join(dir, "geosite_roscom.dat"), &geodata.GeoSiteList{
|
||||
Entry: []*geodata.GeoSite{{Code: "SOME-CODE"}},
|
||||
})
|
||||
writeFixture(t, filepath.Join(dir, "geoip.dat"), &geodata.GeoIPList{
|
||||
Entry: []*geodata.GeoIP{{Code: "PRIVATE"}},
|
||||
})
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
NewXraySettingController(router.Group("/panel/api"))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/panel/api/xray/getGeodataCategories", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200; body=%s", resp.Code, resp.Body.String())
|
||||
}
|
||||
|
||||
body := resp.Body.String()
|
||||
for _, want := range []string{`"success":true`, `"geosite:youtube"`, `"ext:geosite_roscom.dat:some-code"`, `"geoip:private"`} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("response body %s does not contain %s", body, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeFixture(t *testing.T, path string, msg proto.Message) {
|
||||
t.Helper()
|
||||
data, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal fixture %s: %v", path, err)
|
||||
}
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
t.Fatalf("write fixture %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user