From 91c271236605b3e4c22a1d673c25f91536223b7f Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 28 Jan 2020 14:06:52 +0300 Subject: [PATCH 1/3] + clients: support per-client tags --- AGHTechDoc.md | 4 +++ dnsfilter/dnsfilter.go | 9 +++--- go.mod | 2 +- go.sum | 4 +-- home/clients.go | 67 ++++++++++++++++++++++++++++++++++-------- home/clients_http.go | 6 ++++ home/clients_tags.go | 26 ++++++++++++++++ home/dns.go | 2 ++ 8 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 home/clients_tags.go diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 8d1de3d7..7d223c5b 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -673,6 +673,7 @@ Response: { name: "client1" ids: ["...", ...] // IP, CIDR or MAC + tags: ["...", ...] use_global_settings: true filtering_enabled: false parental_enabled: false @@ -698,6 +699,7 @@ Response: } } ] + supported_tags: ["...", ...] } Supported keys for `whois_info`: orgname, country, city. @@ -712,6 +714,7 @@ Request: { name: "client1" ids: ["...", ...] // IP, CIDR or MAC + tags: ["...", ...] use_global_settings: true filtering_enabled: false parental_enabled: false @@ -742,6 +745,7 @@ Request: data: { name: "client1" ids: ["...", ...] // IP, CIDR or MAC + tags: ["...", ...] use_global_settings: true filtering_enabled: false parental_enabled: false diff --git a/dnsfilter/dnsfilter.go b/dnsfilter/dnsfilter.go index 0f667850..4c84c1e8 100644 --- a/dnsfilter/dnsfilter.go +++ b/dnsfilter/dnsfilter.go @@ -31,6 +31,7 @@ type RequestFilteringSettings struct { SafeSearchEnabled bool SafeBrowsingEnabled bool ParentalEnabled bool + ClientTags []string ServicesRules []ServiceEntry } @@ -264,7 +265,7 @@ func (d *Dnsfilter) CheckHostRules(host string, qtype uint16, setts *RequestFilt return Result{}, nil } - return d.matchHost(host, qtype) + return d.matchHost(host, qtype, setts.ClientTags) } // CheckHost tries to match the host against filtering rules, @@ -286,7 +287,7 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering // try filter lists first if setts.FilteringEnabled { - result, err = d.matchHost(host, qtype) + result, err = d.matchHost(host, qtype, setts.ClientTags) if err != nil { return result, err } @@ -475,14 +476,14 @@ func (d *Dnsfilter) initFiltering(filters map[int]string) error { } // matchHost is a low-level way to check only if hostname is filtered by rules, skipping expensive safebrowsing and parental lookups -func (d *Dnsfilter) matchHost(host string, qtype uint16) (Result, error) { +func (d *Dnsfilter) matchHost(host string, qtype uint16, ctags []string) (Result, error) { d.engineLock.RLock() defer d.engineLock.RUnlock() if d.filteringEngine == nil { return Result{}, nil } - frules, ok := d.filteringEngine.Match(host) + frules, ok := d.filteringEngine.Match(host, ctags) if !ok { return Result{}, nil } diff --git a/go.mod b/go.mod index 61de5bee..902c44c4 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( github.com/AdguardTeam/dnsproxy v0.23.7 github.com/AdguardTeam/golibs v0.3.0 - github.com/AdguardTeam/urlfilter v0.7.2 + github.com/AdguardTeam/urlfilter v0.8.1 github.com/NYTimes/gziphandler v1.1.1 github.com/etcd-io/bbolt v1.3.3 github.com/go-test/deep v1.0.4 // indirect diff --git a/go.sum b/go.sum index a407e811..c1fc4421 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/AdguardTeam/golibs v0.3.0/go.mod h1:R3M+mAg3nWG4X4Hsag5eef/TckHFH12ZY github.com/AdguardTeam/gomitmproxy v0.1.2/go.mod h1:Mrt/3EfiXIYY2aZ7KsLuCUJzUARD/fWJ119IfzOB13M= github.com/AdguardTeam/urlfilter v0.7.0 h1:ffFLt4rA3GX8PJYGL3bGcT5bSxZlML5k6cKpSeN2UI8= github.com/AdguardTeam/urlfilter v0.7.0/go.mod h1:GHXPzEG59ezyff22lXSQ7dicj1kFZBrH5kmZ6EvQzfk= -github.com/AdguardTeam/urlfilter v0.7.2 h1:0XyepkVAvY1eYtNKgDRCkookfaBmROvY6VE23ebPUNc= -github.com/AdguardTeam/urlfilter v0.7.2/go.mod h1:GHXPzEG59ezyff22lXSQ7dicj1kFZBrH5kmZ6EvQzfk= +github.com/AdguardTeam/urlfilter v0.8.1 h1:9YRQOR15DU7+k01PWAgc/Ay12jjxVqSi6P0+whFm0f4= +github.com/AdguardTeam/urlfilter v0.8.1/go.mod h1:GHXPzEG59ezyff22lXSQ7dicj1kFZBrH5kmZ6EvQzfk= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705 h1:UUppSQnhf4Yc6xGxSkoQpPhb7RVzuv5Nb1mwJ5VId9s= diff --git a/home/clients.go b/home/clients.go index 65ce78e2..fb96fd76 100644 --- a/home/clients.go +++ b/home/clients.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "runtime" + "sort" "strings" "sync" "time" @@ -26,6 +27,7 @@ const ( // Client information type Client struct { IDs []string + Tags []string Name string UseOwnSettings bool // false: use global settings FilteringEnabled bool @@ -69,6 +71,8 @@ type clientsContainer struct { ipHost map[string]*ClientHost // IP -> Hostname lock sync.Mutex + allTags map[string]bool + // dhcpServer is used for looking up clients IP addresses by MAC addresses dhcpServer *dhcpd.Server @@ -84,6 +88,12 @@ func (clients *clientsContainer) Init(objects []clientObject, dhcpServer *dhcpd. clients.list = make(map[string]*Client) clients.idIndex = make(map[string]*Client) clients.ipHost = make(map[string]*ClientHost) + + clients.allTags = make(map[string]bool) + for _, t := range clientTags { + clients.allTags[t] = false + } + clients.dhcpServer = dhcpServer clients.addFromConfig(objects) @@ -96,6 +106,7 @@ func (clients *clientsContainer) Init(objects []clientObject, dhcpServer *dhcpd. type clientObject struct { Name string `yaml:"name"` + Tags []string `yaml:"tags"` IDs []string `yaml:"ids"` UseGlobalSettings bool `yaml:"use_global_settings"` FilteringEnabled bool `yaml:"filtering_enabled"` @@ -109,6 +120,11 @@ type clientObject struct { Upstreams []string `yaml:"upstreams"` } +func (clients *clientsContainer) tagKnown(tag string) bool { + _, ok := clients.allTags[tag] + return ok +} + func (clients *clientsContainer) addFromConfig(objects []clientObject) { for _, cy := range objects { cli := Client{ @@ -125,6 +141,16 @@ func (clients *clientsContainer) addFromConfig(objects []clientObject) { Upstreams: cy.Upstreams, } + + for _, t := range cy.Tags { + if !clients.tagKnown(t) { + log.Debug("Clients: skipping unknown tag '%s'", t) + continue + } + cli.Tags = append(cli.Tags, t) + } + sort.Strings(cli.Tags) + _, err := clients.Add(cli) if err != nil { log.Tracef("clientAdd: %s", err) @@ -146,14 +172,10 @@ func (clients *clientsContainer) WriteDiskConfig(objects *[]clientObject) { UseGlobalBlockedServices: !cli.UseOwnBlockedServices, } - cy.IDs = make([]string, len(cli.IDs)) - copy(cy.IDs, cli.IDs) - - cy.BlockedServices = make([]string, len(cli.BlockedServices)) - copy(cy.BlockedServices, cli.BlockedServices) - - cy.Upstreams = make([]string, len(cli.Upstreams)) - copy(cy.Upstreams, cli.Upstreams) + cy.Tags = stringArrayDup(cli.Tags) + cy.IDs = stringArrayDup(cli.IDs) + cy.BlockedServices = stringArrayDup(cli.BlockedServices) + cy.Upstreams = stringArrayDup(cli.Upstreams) *objects = append(*objects, cy) } @@ -189,12 +211,26 @@ func (clients *clientsContainer) Exists(ip string, source clientSource) bool { return true } +func stringArrayDup(a []string) []string { + a2 := make([]string, len(a)) + copy(a2, a) + return a2 +} + // Find searches for a client by IP func (clients *clientsContainer) Find(ip string) (Client, bool) { clients.lock.Lock() defer clients.lock.Unlock() - return clients.findByIP(ip) + c, ok := clients.findByIP(ip) + if !ok { + return Client{}, false + } + c.IDs = stringArrayDup(c.IDs) + c.Tags = stringArrayDup(c.Tags) + c.BlockedServices = stringArrayDup(c.BlockedServices) + c.Upstreams = stringArrayDup(c.Upstreams) + return c, true } func upstreamArrayCopy(a []upstream.Upstream) []upstream.Upstream { @@ -297,7 +333,7 @@ func (clients *clientsContainer) FindAutoClient(ip string) (ClientHost, bool) { } // Check if Client object's fields are correct -func (c *Client) check() error { +func (clients *clientsContainer) check(c *Client) error { if len(c.Name) == 0 { return fmt.Errorf("Invalid Name") } @@ -326,6 +362,13 @@ func (c *Client) check() error { return fmt.Errorf("Invalid ID: %s", id) } + for _, t := range c.Tags { + if !clients.tagKnown(t) { + return fmt.Errorf("Invalid tag: %s", t) + } + } + sort.Strings(c.Tags) + if len(c.Upstreams) != 0 { err := dnsforward.ValidateUpstreams(c.Upstreams) if err != nil { @@ -339,7 +382,7 @@ func (c *Client) check() error { // Add a new client object // Return true: success; false: client exists. func (clients *clientsContainer) Add(c Client) (bool, error) { - e := c.check() + e := clients.check(&c) if e != nil { return false, e } @@ -408,7 +451,7 @@ func arraysEqual(a, b []string) bool { // Update a client func (clients *clientsContainer) Update(name string, c Client) error { - err := c.check() + err := clients.check(&c) if err != nil { return err } diff --git a/home/clients_http.go b/home/clients_http.go index aaaef5e0..c48e4937 100644 --- a/home/clients_http.go +++ b/home/clients_http.go @@ -9,6 +9,7 @@ import ( type clientJSON struct { IDs []string `json:"ids"` + Tags []string `json:"tags"` Name string `json:"name"` UseGlobalSettings bool `json:"use_global_settings"` FilteringEnabled bool `json:"filtering_enabled"` @@ -33,6 +34,7 @@ type clientHostJSON struct { type clientListJSON struct { Clients []clientJSON `json:"clients"` AutoClients []clientHostJSON `json:"auto_clients"` + Tags []string `json:"supported_tags"` } // respond with information about configured clients @@ -71,6 +73,8 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http } clients.lock.Unlock() + data.Tags = clientTags + w.Header().Set("Content-Type", "application/json") e := json.NewEncoder(w).Encode(data) if e != nil { @@ -84,6 +88,7 @@ func jsonToClient(cj clientJSON) (*Client, error) { c := Client{ Name: cj.Name, IDs: cj.IDs, + Tags: cj.Tags, UseOwnSettings: !cj.UseGlobalSettings, FilteringEnabled: cj.FilteringEnabled, ParentalEnabled: cj.ParentalEnabled, @@ -103,6 +108,7 @@ func clientToJSON(c *Client) clientJSON { cj := clientJSON{ Name: c.Name, IDs: c.IDs, + Tags: c.Tags, UseGlobalSettings: !c.UseOwnSettings, FilteringEnabled: c.FilteringEnabled, ParentalEnabled: c.ParentalEnabled, diff --git a/home/clients_tags.go b/home/clients_tags.go new file mode 100644 index 00000000..c40caa2a --- /dev/null +++ b/home/clients_tags.go @@ -0,0 +1,26 @@ +package home + +var clientTags = []string{ + "device_audio", + "device_gameconsole", + "device_laptop", + "device_nas", // Network-attached Storage + "device_other", + "device_pc", + "device_phone", + "device_printer", + "device_tablet", + "device_tv", + + "os_android", + "os_ios", + "os_linux", + "os_macos", + "os_other", + "os_windows", + + "user_admin", + "user_child", + "user_other", + "user_regular", +} diff --git a/home/dns.go b/home/dns.go index 6a8be1fd..4d5dceb3 100644 --- a/home/dns.go +++ b/home/dns.go @@ -204,6 +204,8 @@ func applyAdditionalFiltering(clientAddr string, setts *dnsfilter.RequestFilteri ApplyBlockedServices(setts, c.BlockedServices) } + setts.ClientTags = c.Tags + if !c.UseOwnSettings { return } From b519c3a83fcbf2c0e50b1f153aa3a943348afa03 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 28 Jan 2020 14:07:11 +0300 Subject: [PATCH 2/3] * minor --- home/filter.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/home/filter.go b/home/filter.go index 5a810c7e..6b0a16ef 100644 --- a/home/filter.go +++ b/home/filter.go @@ -220,16 +220,15 @@ func assignUniqueFilterID() int64 { func periodicallyRefreshFilters() { const maxInterval = 1 * 60 * 60 intval := 5 // use a dynamically increasing time interval - nUpdated := 0 for { isNetworkErr := false if config.DNS.FiltersUpdateIntervalHours != 0 && refreshStatus == 0 { refreshStatus = 1 refreshLock.Lock() - nUpdated, isNetworkErr = refreshFiltersIfNecessary(false) + _, isNetworkErr = refreshFiltersIfNecessary(false) refreshLock.Unlock() refreshStatus = 0 - if nUpdated != 0 { + if !isNetworkErr { intval = maxInterval } } @@ -305,6 +304,10 @@ func refreshFiltersIfNecessary(force bool) (int, bool) { } config.RUnlock() + if len(updateFilters) == 0 { + return 0, false + } + nfail := 0 for i := range updateFilters { uf := &updateFilters[i] From 67956597be3bab44124277b7a0e23c02a49da983 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Tue, 28 Jan 2020 14:07:47 +0300 Subject: [PATCH 3/3] + client: handle client tags --- client/package-lock.json | 371 ++++++++++++++++-- client/package.json | 1 + client/src/__locales/en.json | 7 +- client/src/actions/index.js | 1 + .../Settings/Clients/ClientsTable.js | 43 +- .../src/components/Settings/Clients/Form.js | 41 ++ .../src/components/Settings/Clients/Modal.js | 3 + .../src/components/Settings/Clients/index.js | 1 + client/src/components/ui/Select.css | 25 +- client/src/reducers/index.js | 4 +- 10 files changed, 467 insertions(+), 30 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 85bf2e6a..81899b0c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -239,6 +239,31 @@ "@babel/types": "7.0.0-beta.44" } }, + "@babel/helper-module-imports": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "requires": { + "@babel/types": "^7.8.3" + }, + "dependencies": { + "@babel/types": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + } + } + }, "@babel/helper-split-export-declaration": { "version": "7.0.0-beta.44", "resolved": "http://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz", @@ -518,6 +543,102 @@ "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", "dev": true }, + "@emotion/cache": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.27.tgz", + "integrity": "sha512-Zp8BEpbMunFsTcqAK4D7YTm3MvCp1SekflSLJH8lze2fCcSZ/yMkXHo8kb3t1/1Tdd3hAqf3Fb7z9VZ+FMiC9w==", + "requires": { + "@emotion/sheet": "0.9.4", + "@emotion/stylis": "0.8.5", + "@emotion/utils": "0.11.3", + "@emotion/weak-memoize": "0.2.5" + } + }, + "@emotion/core": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.27.tgz", + "integrity": "sha512-XbD5R36pVbohQMnKfajHv43g8EbN4NHdF6Zh9zg/C0nr0jqwOw3gYnC07Xj3yG43OYSRyrGsoQ5qPwc8ycvLZw==", + "requires": { + "@babel/runtime": "^7.5.5", + "@emotion/cache": "^10.0.27", + "@emotion/css": "^10.0.27", + "@emotion/serialize": "^0.11.15", + "@emotion/sheet": "0.9.4", + "@emotion/utils": "0.11.3" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz", + "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + } + } + }, + "@emotion/css": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", + "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", + "requires": { + "@emotion/serialize": "^0.11.15", + "@emotion/utils": "0.11.3", + "babel-plugin-emotion": "^10.0.27" + } + }, + "@emotion/hash": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.4.tgz", + "integrity": "sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A==" + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "@emotion/serialize": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.15.tgz", + "integrity": "sha512-YE+qnrmGwyR+XB5j7Bi+0GT1JWsdcjM/d4POu+TXkcnrRs4RFCCsi3d/Ebf+wSStHqAlTT2+dfd+b9N9EO2KBg==", + "requires": { + "@emotion/hash": "0.7.4", + "@emotion/memoize": "0.7.4", + "@emotion/unitless": "0.7.5", + "@emotion/utils": "0.11.3", + "csstype": "^2.5.7" + } + }, + "@emotion/sheet": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", + "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + }, + "@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -608,6 +729,11 @@ "integrity": "sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA==", "dev": true }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, "@types/q": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", @@ -752,7 +878,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -1311,6 +1436,123 @@ "babel-runtime": "^6.22.0" } }, + "babel-plugin-emotion": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.27.tgz", + "integrity": "sha512-SUNYcT4FqhOqvwv0z1oeYhqgheU8qrceLojuHyX17ngo7WtWqN5I9l3IGHzf21Xraj465CVzF4IvOlAF+3ed0A==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@emotion/hash": "0.7.4", + "@emotion/memoize": "0.7.4", + "@emotion/serialize": "^0.11.15", + "babel-plugin-macros": "^2.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^1.0.5", + "find-root": "^1.1.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "requires": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/runtime": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz", + "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + }, + "resolve": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz", + "integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + } + } + }, "babel-plugin-syntax-async-functions": { "version": "6.13.0", "resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", @@ -1356,8 +1598,7 @@ "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "dev": true + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", @@ -2441,7 +2682,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -2985,7 +3225,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -2993,8 +3232,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "commander": { "version": "2.17.1", @@ -3122,8 +3360,7 @@ "convert-source-map": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" }, "cookie": { "version": "0.3.1", @@ -3454,6 +3691,11 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "csstype": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz", + "integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==" + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -4034,7 +4276,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -4148,8 +4389,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escope": { "version": "3.6.0", @@ -4570,8 +4810,7 @@ "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, "etag": { "version": "1.8.1", @@ -5087,6 +5326,11 @@ "pkg-dir": "^2.0.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -5922,8 +6166,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-value": { "version": "1.0.0", @@ -6910,8 +7153,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { "version": "1.0.1", @@ -7206,8 +7448,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-schema-traverse": { "version": "0.4.1", @@ -7289,6 +7530,11 @@ "type-check": "~0.3.2" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" + }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -7593,6 +7839,11 @@ "mimic-fn": "^1.0.0" } }, + "memoize-one": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", + "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -8292,6 +8543,21 @@ "no-case": "^2.2.0" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + } + } + }, "parse-asn1": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", @@ -8380,8 +8646,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "1.7.0", @@ -10082,6 +10347,14 @@ } } }, + "react-input-autosize": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", + "integrity": "sha512-jQJgYCA3S0j+cuOwzuCd1OjmBmnZLdqQdiLKRYrsMMzbjUrVDS5RvJUDwJqA7sKuksDuzFtm6hZGKFu7Mjk5aw==", + "requires": { + "prop-types": "^15.5.8" + } + }, "react-is": { "version": "16.6.3", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", @@ -10200,6 +10473,36 @@ "prop-types": "^15.6.0" } }, + "react-select": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-3.0.8.tgz", + "integrity": "sha512-v9LpOhckLlRmXN5A6/mGGEft4FMrfaBFTGAnuPHcUgVId7Je42kTq9y0Z+Ye5z8/j0XDT3zUqza8gaRaI1PZIg==", + "requires": { + "@babel/runtime": "^7.4.4", + "@emotion/cache": "^10.0.9", + "@emotion/core": "^10.0.9", + "@emotion/css": "^10.0.9", + "memoize-one": "^5.0.0", + "prop-types": "^15.6.0", + "react-input-autosize": "^2.2.2", + "react-transition-group": "^2.2.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz", + "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + } + } + }, "react-table": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.10.3.tgz", @@ -12149,7 +12452,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -13617,6 +13919,29 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, + "yaml": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.2.tgz", + "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==", + "requires": { + "@babel/runtime": "^7.6.3" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz", + "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + } + } + }, "yargs-parser": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", diff --git a/client/package.json b/client/package.json index 93620f74..b2be9b5b 100644 --- a/client/package.json +++ b/client/package.json @@ -27,6 +27,7 @@ "react-redux-loading-bar": "^4.0.7", "react-router-dom": "^4.2.2", "react-router-hash-link": "^1.2.2", + "react-select": "^3.0.8", "react-table": "^6.10.3", "react-transition-group": "^2.4.0", "redux": "^4.0.0", diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 78ae0a2c..b594c20a 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -327,7 +327,7 @@ "client_edit": "Edit Client", "client_identifier": "Identifier", "ip_address": "IP address", - "client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address. Please note, that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server", + "client_identifier_desc": "Clients can be identified by the IP address, CIDR, MAC address. Please note that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server", "form_enter_ip": "Enter IP", "form_enter_mac": "Enter MAC", "form_enter_id": "Enter identifier", @@ -443,5 +443,8 @@ "disable_ipv6_desc": "If this feature is enabled, all DNS queries for IPv6 addresses (type AAAA) will be dropped.", "autofix_warning_text": "If you click \"Fix\", AdGuardHome will configure your system to use AdGuardHome DNS server.", "autofix_warning_list": "It will perform these tasks: <0>Deactivate system DNSStubListener <0>Set DNS server address to 127.0.0.1 <0>Replace symbolic link target of /etc/resolv.conf to /run/systemd/resolve/resolv.conf <0>Stop DNSStubListener (reload systemd-resolved service)", - "autofix_warning_result": "As a result all DNS requests from your system will be processed by AdGuardHome by default." + "autofix_warning_result": "As a result all DNS requests from your system will be processed by AdGuardHome by default.", + "tags_title": "Tags", + "tags_desc": "You can select the tags that correspond to the client. Tags can be included in the filtering rules and allow you to apply them more accurately. <0>Learn more", + "form_select_tags": "Select client tags" } diff --git a/client/src/actions/index.js b/client/src/actions/index.js index ac3cf514..b3e4d2bb 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -208,6 +208,7 @@ export const getClients = () => async (dispatch) => { dispatch(getClientsSuccess({ clients: sortedClients || [], autoClients: sortedAutoClients || [], + supportedTags: data.supported_tags || [], })); } catch (error) { dispatch(addErrorToast({ error })); diff --git a/client/src/components/Settings/Clients/ClientsTable.js b/client/src/components/Settings/Clients/ClientsTable.js index 82cdeebe..a81da13a 100644 --- a/client/src/components/Settings/Clients/ClientsTable.js +++ b/client/src/components/Settings/Clients/ClientsTable.js @@ -33,6 +33,10 @@ class ClientsTable extends Component { } else { config.upstreams = []; } + + if (values.tags) { + config.tags = values.tags.map(tag => tag.value); + } } if (this.props.modalType === MODAL_TYPE.EDIT) { @@ -40,22 +44,29 @@ class ClientsTable extends Component { } else { this.handleFormAdd(config); } - this.props.getStats(); }; + getOptionsWithLabels = options => ( + options.map(option => ({ value: option, label: option })) + ); + getClient = (name, clients) => { const client = clients.find(item => name === item.name); if (client) { - const { upstreams, whois_info, ...values } = client; + const { + upstreams, tags, whois_info, ...values + } = client; return { upstreams: (upstreams && upstreams.join('\n')) || '', + tags: (tags && this.getOptionsWithLabels(tags)) || [], ...values, }; } return { ids: [''], + tags: [], use_global_settings: true, use_global_blocked_services: true, }; @@ -160,6 +171,30 @@ class ClientsTable extends Component { ); }, }, + { + Header: this.props.t('tags_title'), + accessor: 'tags', + minWidth: 140, + Cell: (row) => { + const { value } = row; + + if (!value || value.length < 1) { + return '–'; + } + + return ( +
+ + {value.map(tag => ( +
+ {tag} +
+ ))} +
+
+ ); + }, + }, { Header: this.props.t('requests_count'), id: 'statistics', @@ -223,9 +258,11 @@ class ClientsTable extends Component { toggleClientModal, processingAdding, processingUpdating, + supportedTags, } = this.props; const currentClientData = this.getClient(modalClientName, clients); + const tagsOptions = this.getOptionsWithLabels(supportedTags); return ( @@ -294,6 +332,7 @@ ClientsTable.propTypes = { processingDeleting: PropTypes.bool.isRequired, processingUpdating: PropTypes.bool.isRequired, getStats: PropTypes.func.isRequired, + supportedTags: PropTypes.array.isRequired, }; export default withNamespaces()(ClientsTable); diff --git a/client/src/components/Settings/Clients/Form.js b/client/src/components/Settings/Clients/Form.js index 2bf983ea..dfed84fb 100644 --- a/client/src/components/Settings/Clients/Form.js +++ b/client/src/components/Settings/Clients/Form.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import { Field, FieldArray, reduxForm, formValueSelector } from 'redux-form'; import { Trans, withNamespaces } from 'react-i18next'; import flow from 'lodash/flow'; +import Select from 'react-select'; import i18n from '../../../i18n'; import Tabs from '../../ui/Tabs'; @@ -99,6 +100,23 @@ const renderFieldsWrapper = (placeholder, buttonTitle) => // Should create function outside of component to prevent component re-renders const renderFields = renderFieldsWrapper(i18n.t('form_enter_id'), i18n.t('form_add_id')); +const renderMultiselect = (props) => { + const { input, placeholder, options } = props; + + return ( +