Pull request 743: + client: Query Logs Infinite Scroll
Merge in DNS/adguard-home from feature/infinite_scroll_query_logs to master
Squashed commit of the following:
commit 4407ef2e7c055066257da791fbd65e6b0a495729
Merge: 40b74522 0a4781be
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Tue Sep 1 16:20:23 2020 +0300
Merge branch 'master' into feature/infinite_scroll_query_logs
commit 40b745225112cf8d664220ed8f484b0aa16e997c
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Tue Sep 1 15:46:27 2020 +0300
Remove dynamic translation of toasts
commit f08fa7b8c6a243f6b10e924aebccc183ce7814fd
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Tue Sep 1 13:59:53 2020 +0300
Remove renderLimitIdx, update isEntireLog
commit 0f1b02616faaa5759c0a3f6d8257117fa22094d9
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Tue Sep 1 11:11:14 2020 +0300
Rename variables
commit 0928570c689c1fa704af775382620d68893e7c1c
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Tue Sep 1 11:06:50 2020 +0300
Make query logs short polling function more expressive
commit 9e773cbd6c287a1c799fa2680f3462508462ea7a
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Tue Sep 1 11:06:19 2020 +0300
Fix Toast translation interface
commit f9c57033e5adc5788954cf086b2f114dd8938bcb
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Mon Aug 31 17:01:36 2020 +0300
Do not hide loader
commit b86ba48613437f5559a748ad9aa4cf79d15db082
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Mon Aug 31 16:56:34 2020 +0300
Add dynamic translation for all toasts
commit b9d1d9b447ca90a3c179e503fa5d4abd3516321e
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Mon Aug 31 16:39:29 2020 +0300
Prevent getting query logs recursion if query is not changed
commit e25189749f7912648cca4503cfa8d0ad898c4bb6
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Mon Aug 31 10:13:20 2020 +0300
Decrease page limit to 20
commit 8b248ac5276899de838abf2dc9a69e47599cfc12
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Fri Aug 28 18:47:12 2020 +0300
Return checkFilteredLogs
commit bf2d65c4a3dca0da6b15f632ae11042b7c8e2045
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Fri Aug 28 18:33:51 2020 +0300
Review changes
commit 01b5250f9d9136a1f334086d3e2f00d1a928b37b
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Fri Aug 28 15:29:59 2020 +0300
Remove checkFilteredLogs
commit 25b364c41e6a1489d930c8b3b39b1ab43723f29d
Merge: 1dc66034 2c666cbd
Author: Andrey Meshkov <am@adguard.com>
Date: Fri Aug 28 14:28:47 2020 +0300
Merge branch 'feature/infinite_scroll_query_logs' of ssh://bit.adguard.com:7999/dns/adguard-home into feature/infinite_scroll_query_logs
commit 1dc6603421cde9847e792bfe77ff6546e53fbc2a
Author: Andrey Meshkov <am@adguard.com>
Date: Fri Aug 28 14:28:01 2020 +0300
disregard maxFileScanEntries only if offset is set
commit bad741ed7f1dccf6959d43d000b8c0150f526f9e
Author: Andrey Meshkov <am@adguard.com>
Date: Fri Aug 28 11:57:45 2020 +0300
Fix search behavior when limit is specified
commit 2c666cbdde465cf17434126830dd99ceedfc4cbc
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Thu Aug 27 18:50:28 2020 +0300
Hide table ref loader during data loading
commit 8b4f7fe642ef9e87a979813dcdbd7817d64c27f9
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Thu Aug 27 18:43:24 2020 +0300
Repair search
commit 26fae1ae01a789999b8a2181d60b35663a20460a
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Thu Aug 27 17:59:27 2020 +0300
Resetting initial render index, change loader position on search
commit e2c97ae1a288438267eef9aec71b979319674a71
Author: ArtemBaskal <a.baskal@adguard.com>
Date: Thu Aug 27 16:02:03 2020 +0300
Change isScrolledIntoView
... and 32 more commits
This commit is contained in:
parent
0a4781be97
commit
6b61429572
|
@ -1356,17 +1356,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
|
||||||
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
|
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
|
||||||
},
|
},
|
||||||
"@hot-loader/react-dom": {
|
|
||||||
"version": "16.13.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@hot-loader/react-dom/-/react-dom-16.13.0.tgz",
|
|
||||||
"integrity": "sha512-lJZrmkucz2MrQJTQtJobx5MICXcfQvKihszqv655p557HPi0hMOWxrNpiHv3DWD8ugNWjtWcVWqRnFvwsHq1mQ==",
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.1.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"prop-types": "^15.6.2",
|
|
||||||
"scheduler": "^0.19.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@istanbuljs/load-nyc-config": {
|
"@istanbuljs/load-nyc-config": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
||||||
|
@ -10784,6 +10773,24 @@
|
||||||
"yallist": "^4.0.0"
|
"yallist": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mississippi": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"concat-stream": "^1.5.0",
|
||||||
|
"duplexify": "^3.4.2",
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"flush-write-stream": "^1.0.0",
|
||||||
|
"from2": "^2.1.0",
|
||||||
|
"parallel-transform": "^1.1.0",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"pumpify": "^1.3.3",
|
||||||
|
"stream-each": "^1.1.0",
|
||||||
|
"through2": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mixin-deep": {
|
"mixin-deep": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
||||||
|
@ -12169,9 +12176,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pump": {
|
"pump": {
|
||||||
"version": "2.0.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||||
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"end-of-stream": "^1.1.0",
|
"end-of-stream": "^1.1.0",
|
||||||
|
@ -12187,6 +12194,18 @@
|
||||||
"duplexify": "^3.6.0",
|
"duplexify": "^3.6.0",
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"pump": "^2.0.0"
|
"pump": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"pump": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"once": "^1.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
|
@ -13281,10 +13300,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"serialize-javascript": {
|
"serialize-javascript": {
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
|
||||||
"integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==",
|
"integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"randombytes": "^2.1.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"serve-index": {
|
"serve-index": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
|
@ -14619,16 +14641,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"terser-webpack-plugin": {
|
"terser-webpack-plugin": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
|
||||||
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
|
"integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"cacache": "^12.0.2",
|
"cacache": "^12.0.2",
|
||||||
"find-cache-dir": "^2.1.0",
|
"find-cache-dir": "^2.1.0",
|
||||||
"is-wsl": "^1.1.0",
|
"is-wsl": "^1.1.0",
|
||||||
"schema-utils": "^1.0.0",
|
"schema-utils": "^1.0.0",
|
||||||
"serialize-javascript": "^2.1.2",
|
"serialize-javascript": "^4.0.0",
|
||||||
"source-map": "^0.6.1",
|
"source-map": "^0.6.1",
|
||||||
"terser": "^4.1.2",
|
"terser": "^4.1.2",
|
||||||
"webpack-sources": "^1.4.0",
|
"webpack-sources": "^1.4.0",
|
||||||
|
@ -14688,15 +14710,6 @@
|
||||||
"path-exists": "^3.0.0"
|
"path-exists": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lru-cache": {
|
|
||||||
"version": "5.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
|
||||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"yallist": "^3.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"make-dir": {
|
"make-dir": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||||
|
@ -14707,24 +14720,6 @@
|
||||||
"semver": "^5.6.0"
|
"semver": "^5.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mississippi": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"concat-stream": "^1.5.0",
|
|
||||||
"duplexify": "^3.4.2",
|
|
||||||
"end-of-stream": "^1.1.0",
|
|
||||||
"flush-write-stream": "^1.0.0",
|
|
||||||
"from2": "^2.1.0",
|
|
||||||
"parallel-transform": "^1.1.0",
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"pumpify": "^1.3.3",
|
|
||||||
"stream-each": "^1.1.0",
|
|
||||||
"through2": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-limit": {
|
"p-limit": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
|
@ -14764,22 +14759,15 @@
|
||||||
"find-up": "^3.0.0"
|
"find-up": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pump": {
|
"serialize-javascript": {
|
||||||
"version": "3.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"end-of-stream": "^1.1.0",
|
"randombytes": "^2.1.0"
|
||||||
"once": "^1.3.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"serialize-javascript": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
@ -14794,12 +14782,6 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"figgy-pudding": "^3.5.1"
|
"figgy-pudding": "^3.5.1"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"yallist": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
"test:watch": "jest --watch"
|
"test:watch": "jest --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hot-loader/react-dom": "^16.13.0",
|
|
||||||
"@nivo/line": "^0.49.1",
|
"@nivo/line": "^0.49.1",
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
|
|
@ -139,8 +139,8 @@
|
||||||
"page_table_footer_text": "Страница",
|
"page_table_footer_text": "Страница",
|
||||||
"rows_table_footer_text": "редове",
|
"rows_table_footer_text": "редове",
|
||||||
"updated_custom_filtering_toast": "Обновени местни правила за филтриране",
|
"updated_custom_filtering_toast": "Обновени местни правила за филтриране",
|
||||||
"rule_removed_from_custom_filtering_toast": "Премахнато от местни правила за филтриране",
|
"rule_removed_from_custom_filtering_toast": "Премахнато от местни правила за филтриране: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране",
|
"rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране: {{rule}}",
|
||||||
"plain_dns": "Обикновен DNS",
|
"plain_dns": "Обикновен DNS",
|
||||||
"source_label": "Източник",
|
"source_label": "Източник",
|
||||||
"found_in_known_domain_db": "Намерен в списъците с домейни.",
|
"found_in_known_domain_db": "Намерен в списъците с домейни.",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Stránka",
|
"page_table_footer_text": "Stránka",
|
||||||
"rows_table_footer_text": "řádky",
|
"rows_table_footer_text": "řádky",
|
||||||
"updated_custom_filtering_toast": "Aktualizovaná vlastní pravidla filtrování",
|
"updated_custom_filtering_toast": "Aktualizovaná vlastní pravidla filtrování",
|
||||||
"rule_removed_from_custom_filtering_toast": "Pravidlo odstraněno z vlastních pravidel filtrování",
|
"rule_removed_from_custom_filtering_toast": "Pravidlo odstraněno z vlastních pravidel filtrování: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Pravidlo přidáno do vlastních pravidel filtrování",
|
"rule_added_to_custom_filtering_toast": "Pravidlo přidáno do vlastních pravidel filtrování: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Filtrováno pomocí {{filter}}",
|
"query_log_filtered": "Filtrováno pomocí {{filter}}",
|
||||||
"query_log_confirm_clear": "Opravdu chcete vymazat celý protokol dotazů?",
|
"query_log_confirm_clear": "Opravdu chcete vymazat celý protokol dotazů?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Side",
|
"page_table_footer_text": "Side",
|
||||||
"rows_table_footer_text": "rækker",
|
"rows_table_footer_text": "rækker",
|
||||||
"updated_custom_filtering_toast": "De brugerdefinerede filtreringsregler er blevet opdateret",
|
"updated_custom_filtering_toast": "De brugerdefinerede filtreringsregler er blevet opdateret",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regel fjernet fra de brugerdefinerede filtreringsregler",
|
"rule_removed_from_custom_filtering_toast": "Regel fjernet fra de brugerdefinerede filtreringsregler: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regel tilføjet til de brugerdefinerede filtreringsregler",
|
"rule_added_to_custom_filtering_toast": "Regel tilføjet til de brugerdefinerede filtreringsregler: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Filtreret af {{filter}}",
|
"query_log_filtered": "Filtreret af {{filter}}",
|
||||||
"query_log_confirm_clear": "Er du sikker på, at du vil rydde hele forespørgselsloggen?",
|
"query_log_confirm_clear": "Er du sikker på, at du vil rydde hele forespørgselsloggen?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Seite",
|
"page_table_footer_text": "Seite",
|
||||||
"rows_table_footer_text": "Reihen",
|
"rows_table_footer_text": "Reihen",
|
||||||
"updated_custom_filtering_toast": "Die benutzerdefinierten Filterregeln wurden aktualisiert",
|
"updated_custom_filtering_toast": "Die benutzerdefinierten Filterregeln wurden aktualisiert",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regel wurde aus den benutzerdefinierten Filterregeln entfernt",
|
"rule_removed_from_custom_filtering_toast": "Regel wurde aus den benutzerdefinierten Filterregeln entfernt: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regel wurde zu den benutzerdefinierten Filterregeln hinzugefügt",
|
"rule_added_to_custom_filtering_toast": "Regel wurde zu den benutzerdefinierten Filterregeln hinzugefügt: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Gefiltert nach {{filter}}",
|
"query_log_filtered": "Gefiltert nach {{filter}}",
|
||||||
"query_log_confirm_clear": "Möchten Sie wirklich das Abfrageprotokoll vollständig löschen?",
|
"query_log_confirm_clear": "Möchten Sie wirklich das Abfrageprotokoll vollständig löschen?",
|
||||||
|
|
|
@ -213,8 +213,8 @@
|
||||||
"page_table_footer_text": "Page",
|
"page_table_footer_text": "Page",
|
||||||
"rows_table_footer_text": "rows",
|
"rows_table_footer_text": "rows",
|
||||||
"updated_custom_filtering_toast": "Updated the custom filtering rules",
|
"updated_custom_filtering_toast": "Updated the custom filtering rules",
|
||||||
"rule_removed_from_custom_filtering_toast": "Rule removed from the custom filtering rules",
|
"rule_removed_from_custom_filtering_toast": "Rule removed from the custom filtering rules: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules",
|
"rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Filtered by {{filter}}",
|
"query_log_filtered": "Filtered by {{filter}}",
|
||||||
"query_log_confirm_clear": "Are you sure you want to clear the entire query log?",
|
"query_log_confirm_clear": "Are you sure you want to clear the entire query log?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Página",
|
"page_table_footer_text": "Página",
|
||||||
"rows_table_footer_text": "filas",
|
"rows_table_footer_text": "filas",
|
||||||
"updated_custom_filtering_toast": "Reglas de filtrado personalizado actualizadas",
|
"updated_custom_filtering_toast": "Reglas de filtrado personalizado actualizadas",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizado",
|
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizado: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regla añadida a las reglas de filtrado personalizado",
|
"rule_added_to_custom_filtering_toast": "Regla añadida a las reglas de filtrado personalizado: {{rule}}",
|
||||||
"query_log_response_status": "Estado: {{value}}",
|
"query_log_response_status": "Estado: {{value}}",
|
||||||
"query_log_filtered": "Filtrado por {{filter}}",
|
"query_log_filtered": "Filtrado por {{filter}}",
|
||||||
"query_log_confirm_clear": "¿Está seguro de que desea borrar todo el registro de consultas?",
|
"query_log_confirm_clear": "¿Está seguro de que desea borrar todo el registro de consultas?",
|
||||||
|
|
|
@ -202,8 +202,8 @@
|
||||||
"page_table_footer_text": "صفحه",
|
"page_table_footer_text": "صفحه",
|
||||||
"rows_table_footer_text": "سطر",
|
"rows_table_footer_text": "سطر",
|
||||||
"updated_custom_filtering_toast": "دستورات فیلترینگ دستی بروز رسانی شده است",
|
"updated_custom_filtering_toast": "دستورات فیلترینگ دستی بروز رسانی شده است",
|
||||||
"rule_removed_from_custom_filtering_toast": "دستور از دستورات فیلترینگ دستی حذف شد",
|
"rule_removed_from_custom_filtering_toast": "دستور از دستورات فیلترینگ دستی حذف شد {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "دستور به دستورات فیلترینگ دستی اضافه شد",
|
"rule_added_to_custom_filtering_toast": "دستور به دستورات فیلترینگ دستی اضافه شد {{rule}}",
|
||||||
"query_log_response_status": "وضعیت: {{value}}",
|
"query_log_response_status": "وضعیت: {{value}}",
|
||||||
"query_log_filtered": "فیلتر شده با {{filter}}",
|
"query_log_filtered": "فیلتر شده با {{filter}}",
|
||||||
"query_log_confirm_clear": "آیا واقعا میخواهید کل وقایع جستار را پاک کنید؟",
|
"query_log_confirm_clear": "آیا واقعا میخواهید کل وقایع جستار را پاک کنید؟",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Page",
|
"page_table_footer_text": "Page",
|
||||||
"rows_table_footer_text": "lignes",
|
"rows_table_footer_text": "lignes",
|
||||||
"updated_custom_filtering_toast": "Règles de filtrage d'utilisateur mises à jour",
|
"updated_custom_filtering_toast": "Règles de filtrage d'utilisateur mises à jour",
|
||||||
"rule_removed_from_custom_filtering_toast": "Règle retirée des règles d'utilisateur",
|
"rule_removed_from_custom_filtering_toast": "Règle retirée des règles d'utilisateur: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Règle ajoutée aux règles d'utilisateur",
|
"rule_added_to_custom_filtering_toast": "Règle ajoutée aux règles d'utilisateur: {{rule}}",
|
||||||
"query_log_response_status": "Statut : {{value}}",
|
"query_log_response_status": "Statut : {{value}}",
|
||||||
"query_log_filtered": "Filtré par {{filter}}",
|
"query_log_filtered": "Filtré par {{filter}}",
|
||||||
"query_log_confirm_clear": "Êtes-vous sûr de vouloir effacer tout le journal des requêtes ?",
|
"query_log_confirm_clear": "Êtes-vous sûr de vouloir effacer tout le journal des requêtes ?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Stranica",
|
"page_table_footer_text": "Stranica",
|
||||||
"rows_table_footer_text": "redova",
|
"rows_table_footer_text": "redova",
|
||||||
"updated_custom_filtering_toast": "Ažurirana su prilagođena pravila filtriranja",
|
"updated_custom_filtering_toast": "Ažurirana su prilagođena pravila filtriranja",
|
||||||
"rule_removed_from_custom_filtering_toast": "Pravilo je uklonjeno iz prilagođenih pravila filtriranja",
|
"rule_removed_from_custom_filtering_toast": "Pravilo je uklonjeno iz prilagođenih pravila filtriranja: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Pravilo je dodano u prilagođena pravila filtriranja",
|
"rule_added_to_custom_filtering_toast": "Pravilo je dodano u prilagođena pravila filtriranja: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Filtrirao {{filter}}",
|
"query_log_filtered": "Filtrirao {{filter}}",
|
||||||
"query_log_confirm_clear": "Jeste li sigurni da želite ukloniti zapise upita?",
|
"query_log_confirm_clear": "Jeste li sigurni da želite ukloniti zapise upita?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Halaman",
|
"page_table_footer_text": "Halaman",
|
||||||
"rows_table_footer_text": "baris",
|
"rows_table_footer_text": "baris",
|
||||||
"updated_custom_filtering_toast": "Perbarui aturan penyaringan khusus",
|
"updated_custom_filtering_toast": "Perbarui aturan penyaringan khusus",
|
||||||
"rule_removed_from_custom_filtering_toast": "Aturan dihapus dari aturan penyaringan khusus",
|
"rule_removed_from_custom_filtering_toast": "Aturan dihapus dari aturan penyaringan khusus: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Aturan ditambah ke aturan penyaringan khusus",
|
"rule_added_to_custom_filtering_toast": "Aturan ditambah ke aturan penyaringan khusus: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Difilter oleh {{filter}}",
|
"query_log_filtered": "Difilter oleh {{filter}}",
|
||||||
"query_log_confirm_clear": "Apakah Anda yakin ingin menghapus seluruh kueri log?",
|
"query_log_confirm_clear": "Apakah Anda yakin ingin menghapus seluruh kueri log?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Pagina",
|
"page_table_footer_text": "Pagina",
|
||||||
"rows_table_footer_text": "righe",
|
"rows_table_footer_text": "righe",
|
||||||
"updated_custom_filtering_toast": "Le regole dei filtri personalizzate sono state aggiornate",
|
"updated_custom_filtering_toast": "Le regole dei filtri personalizzate sono state aggiornate",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regola rimossa dalle regole dei filtri personalizzate",
|
"rule_removed_from_custom_filtering_toast": "Regola rimossa dalle regole dei filtri personalizzate: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regola aggiunta alle regole dei filtri personalizzate",
|
"rule_added_to_custom_filtering_toast": "Regola aggiunta alle regole dei filtri personalizzate: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Filtrato da {{filter}}",
|
"query_log_filtered": "Filtrato da {{filter}}",
|
||||||
"query_log_confirm_clear": "Sei sicuro di voler eliminare la query log?",
|
"query_log_confirm_clear": "Sei sicuro di voler eliminare la query log?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "ページ",
|
"page_table_footer_text": "ページ",
|
||||||
"rows_table_footer_text": "行",
|
"rows_table_footer_text": "行",
|
||||||
"updated_custom_filtering_toast": "カスタム・フィルタリングルールを更新しました",
|
"updated_custom_filtering_toast": "カスタム・フィルタリングルールを更新しました",
|
||||||
"rule_removed_from_custom_filtering_toast": "ルールをカスタム・フィルタリングルールから除去しました",
|
"rule_removed_from_custom_filtering_toast": "ルールをカスタム・フィルタリングルールから除去しました {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "ルールをカスタム・フィルタリングルールに追加しました",
|
"rule_added_to_custom_filtering_toast": "ルールをカスタム・フィルタリングルールに追加しました {{rule}}",
|
||||||
"query_log_response_status": "ステータス: {{value}}",
|
"query_log_response_status": "ステータス: {{value}}",
|
||||||
"query_log_filtered": "{{filter}}によるフィルタ",
|
"query_log_filtered": "{{filter}}によるフィルタ",
|
||||||
"query_log_confirm_clear": "クエリ・ログ全体を消去してもよろしいですか?",
|
"query_log_confirm_clear": "クエリ・ログ全体を消去してもよろしいですか?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "페이지",
|
"page_table_footer_text": "페이지",
|
||||||
"rows_table_footer_text": "행",
|
"rows_table_footer_text": "행",
|
||||||
"updated_custom_filtering_toast": "사용자 정의 필터링 규칙 업데이트",
|
"updated_custom_filtering_toast": "사용자 정의 필터링 규칙 업데이트",
|
||||||
"rule_removed_from_custom_filtering_toast": "사용자 정의 필터링 규칙에서 규칙 제거",
|
"rule_removed_from_custom_filtering_toast": "사용자 정의 필터링 규칙에서 규칙 제거 {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "사용자 정의 필터링 규칙에 추가된 규칙",
|
"rule_added_to_custom_filtering_toast": "사용자 정의 필터링 규칙에 추가된 규칙 {{rule}}",
|
||||||
"query_log_response_status": "상태: {{value}}",
|
"query_log_response_status": "상태: {{value}}",
|
||||||
"query_log_filtered": "필터: {{filter}}",
|
"query_log_filtered": "필터: {{filter}}",
|
||||||
"query_log_confirm_clear": "정말로 모든 쿼리 로그를 비우시겠습니까?",
|
"query_log_confirm_clear": "정말로 모든 쿼리 로그를 비우시겠습니까?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Pagina",
|
"page_table_footer_text": "Pagina",
|
||||||
"rows_table_footer_text": "rijen",
|
"rows_table_footer_text": "rijen",
|
||||||
"updated_custom_filtering_toast": "Aangepaste filter regels zijn bijgewerkt",
|
"updated_custom_filtering_toast": "Aangepaste filter regels zijn bijgewerkt",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regel verwijderd uit de aangepaste filterregels",
|
"rule_removed_from_custom_filtering_toast": "Regel verwijderd uit de aangepaste filterregels: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regel toegevoegd aan de aangepaste filterregels",
|
"rule_added_to_custom_filtering_toast": "Regel toegevoegd aan de aangepaste filterregels: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Gefilterd door {{filter}}",
|
"query_log_filtered": "Gefilterd door {{filter}}",
|
||||||
"query_log_confirm_clear": "Weet u zeker dat u het hele query logboek wilt legen?",
|
"query_log_confirm_clear": "Weet u zeker dat u het hele query logboek wilt legen?",
|
||||||
|
|
|
@ -207,8 +207,8 @@
|
||||||
"page_table_footer_text": "Side",
|
"page_table_footer_text": "Side",
|
||||||
"rows_table_footer_text": "rekker",
|
"rows_table_footer_text": "rekker",
|
||||||
"updated_custom_filtering_toast": "Oppdaterte de selvvalgte filtreringsreglene",
|
"updated_custom_filtering_toast": "Oppdaterte de selvvalgte filtreringsreglene",
|
||||||
"rule_removed_from_custom_filtering_toast": "Oppføringen ble fjernet fra de selvvalgte filtreringsreglene",
|
"rule_removed_from_custom_filtering_toast": "Oppføringen ble fjernet fra de selvvalgte filtreringsreglene: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Oppføringen ble lagt til i de selvvalgte filtreringsreglene",
|
"rule_added_to_custom_filtering_toast": "Oppføringen ble lagt til i de selvvalgte filtreringsreglene: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Filtrert av {{filter}}",
|
"query_log_filtered": "Filtrert av {{filter}}",
|
||||||
"query_log_confirm_clear": "Er du sikker på at du vil slette hele forespørselsloggen?",
|
"query_log_confirm_clear": "Er du sikker på at du vil slette hele forespørselsloggen?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Strona",
|
"page_table_footer_text": "Strona",
|
||||||
"rows_table_footer_text": "wierszy",
|
"rows_table_footer_text": "wierszy",
|
||||||
"updated_custom_filtering_toast": "Zaktualizowano niestandardowe reguły filtrowania",
|
"updated_custom_filtering_toast": "Zaktualizowano niestandardowe reguły filtrowania",
|
||||||
"rule_removed_from_custom_filtering_toast": "Reguła usunięta z niestandardowych reguł filtrowania",
|
"rule_removed_from_custom_filtering_toast": "Reguła usunięta z niestandardowych reguł filtrowania: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Reguła dodana do niestandardowych reguł filtrowania",
|
"rule_added_to_custom_filtering_toast": "Reguła dodana do niestandardowych reguł filtrowania: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Filtrowane przez {{filter}}",
|
"query_log_filtered": "Filtrowane przez {{filter}}",
|
||||||
"query_log_confirm_clear": "Czy na pewno chcesz wyczyścić cały dziennik zapytań?",
|
"query_log_confirm_clear": "Czy na pewno chcesz wyczyścić cały dziennik zapytań?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Página",
|
"page_table_footer_text": "Página",
|
||||||
"rows_table_footer_text": "linhas",
|
"rows_table_footer_text": "linhas",
|
||||||
"updated_custom_filtering_toast": "Regras de filtragem personalizadas atualizadas",
|
"updated_custom_filtering_toast": "Regras de filtragem personalizadas atualizadas",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas",
|
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas",
|
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Filtrado por {{filter}}",
|
"query_log_filtered": "Filtrado por {{filter}}",
|
||||||
"query_log_confirm_clear": "Você tem certeza que deseja limpar o registro de consulta?",
|
"query_log_confirm_clear": "Você tem certeza que deseja limpar o registro de consulta?",
|
||||||
|
|
|
@ -168,8 +168,8 @@
|
||||||
"page_table_footer_text": "Página",
|
"page_table_footer_text": "Página",
|
||||||
"rows_table_footer_text": "linhas",
|
"rows_table_footer_text": "linhas",
|
||||||
"updated_custom_filtering_toast": "Regras de filtragem personalizadas actualizadas",
|
"updated_custom_filtering_toast": "Regras de filtragem personalizadas actualizadas",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas",
|
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas",
|
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Filtrado por {{filter}}",
|
"query_log_filtered": "Filtrado por {{filter}}",
|
||||||
"query_log_confirm_clear": "Tem a certeza de que deseja limpar todo o registo de consulta?",
|
"query_log_confirm_clear": "Tem a certeza de que deseja limpar todo o registo de consulta?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Pagina",
|
"page_table_footer_text": "Pagina",
|
||||||
"rows_table_footer_text": "linii",
|
"rows_table_footer_text": "linii",
|
||||||
"updated_custom_filtering_toast": "Reguli personalizate de filtrare aduse la zi",
|
"updated_custom_filtering_toast": "Reguli personalizate de filtrare aduse la zi",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regulă scoasă din regullei personalizate de filtrare",
|
"rule_removed_from_custom_filtering_toast": "Regulă scoasă din regullei personalizate de filtrare: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regulă adăugată la regulile de filtrare personalizate",
|
"rule_added_to_custom_filtering_toast": "Regulă adăugată la regulile de filtrare personalizate: {{rule}}",
|
||||||
"query_log_response_status": "Statut: {{value}}",
|
"query_log_response_status": "Statut: {{value}}",
|
||||||
"query_log_filtered": "Filtrat de {{filter}}",
|
"query_log_filtered": "Filtrat de {{filter}}",
|
||||||
"query_log_confirm_clear": "Sunteți sigur că doriți să ștergeți întregul jurnal de interogări?",
|
"query_log_confirm_clear": "Sunteți sigur că doriți să ștergeți întregul jurnal de interogări?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Страница",
|
"page_table_footer_text": "Страница",
|
||||||
"rows_table_footer_text": "строк",
|
"rows_table_footer_text": "строк",
|
||||||
"updated_custom_filtering_toast": "Внесены изменения в пользовательские правила",
|
"updated_custom_filtering_toast": "Внесены изменения в пользовательские правила",
|
||||||
"rule_removed_from_custom_filtering_toast": "Правило удалено из авторского списка правил фильтрации",
|
"rule_removed_from_custom_filtering_toast": "Пользовательское правило удалено: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Пользовательское правило добавлено",
|
"rule_added_to_custom_filtering_toast": "Пользовательское правило добавлено: {{rule}}",
|
||||||
"query_log_response_status": "Статус: {{value}}",
|
"query_log_response_status": "Статус: {{value}}",
|
||||||
"query_log_filtered": "Отфильтровано с помощью {{filter}}",
|
"query_log_filtered": "Отфильтровано с помощью {{filter}}",
|
||||||
"query_log_confirm_clear": "Вы уверены, что хотите очистить весь журнал запросов?",
|
"query_log_confirm_clear": "Вы уверены, что хотите очистить весь журнал запросов?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Stránka",
|
"page_table_footer_text": "Stránka",
|
||||||
"rows_table_footer_text": "riadky",
|
"rows_table_footer_text": "riadky",
|
||||||
"updated_custom_filtering_toast": "Aktualizované vlastné filtračné pravidlá",
|
"updated_custom_filtering_toast": "Aktualizované vlastné filtračné pravidlá",
|
||||||
"rule_removed_from_custom_filtering_toast": "Pravidlo odstránené z vlastných filtračných pravidiel",
|
"rule_removed_from_custom_filtering_toast": "Pravidlo odstránené z vlastných filtračných pravidiel: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Pravidlo pridané do vlastných filtračných pravidiel",
|
"rule_added_to_custom_filtering_toast": "Pravidlo pridané do vlastných filtračných pravidiel: {{rule}}",
|
||||||
"query_log_response_status": "Stav: {{value}}",
|
"query_log_response_status": "Stav: {{value}}",
|
||||||
"query_log_filtered": "Vyfiltrované pomocou {{filter}}",
|
"query_log_filtered": "Vyfiltrované pomocou {{filter}}",
|
||||||
"query_log_confirm_clear": "Naozaj chcete vymazať celý denník dopytov?",
|
"query_log_confirm_clear": "Naozaj chcete vymazať celý denník dopytov?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Stran",
|
"page_table_footer_text": "Stran",
|
||||||
"rows_table_footer_text": "vrstic",
|
"rows_table_footer_text": "vrstic",
|
||||||
"updated_custom_filtering_toast": "Posodobljena pravila filtriranja po meri",
|
"updated_custom_filtering_toast": "Posodobljena pravila filtriranja po meri",
|
||||||
"rule_removed_from_custom_filtering_toast": "Pravilo je odstranjeno iz pravil filtriranja po meri",
|
"rule_removed_from_custom_filtering_toast": "Pravilo je odstranjeno iz pravil filtriranja po meri: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Pravilo je dodano pravilom filtriranja po meri",
|
"rule_added_to_custom_filtering_toast": "Pravilo je dodano pravilom filtriranja po meri: {{rule}}",
|
||||||
"query_log_response_status": "Stanje: {{value}}",
|
"query_log_response_status": "Stanje: {{value}}",
|
||||||
"query_log_filtered": "Filtriran z {{filter}}",
|
"query_log_filtered": "Filtriran z {{filter}}",
|
||||||
"query_log_confirm_clear": "Ali ste prepričani, da želite počistiti celoten dnevnik poizvedb?",
|
"query_log_confirm_clear": "Ali ste prepričani, da želite počistiti celoten dnevnik poizvedb?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "Stranica",
|
"page_table_footer_text": "Stranica",
|
||||||
"rows_table_footer_text": "redovi",
|
"rows_table_footer_text": "redovi",
|
||||||
"updated_custom_filtering_toast": "Ažurirana prilagođena pravila filtriranja",
|
"updated_custom_filtering_toast": "Ažurirana prilagođena pravila filtriranja",
|
||||||
"rule_removed_from_custom_filtering_toast": "Pravilo uklonjeno iz prilagođenih pravila filtriranja",
|
"rule_removed_from_custom_filtering_toast": "Pravilo uklonjeno iz prilagođenih pravila filtriranja: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Pravilo dodato u prilagođena pravila filtriranja",
|
"rule_added_to_custom_filtering_toast": "Pravilo dodato u prilagođena pravila filtriranja: {{rule}}",
|
||||||
"query_log_response_status": "Stanje: {{value}}",
|
"query_log_response_status": "Stanje: {{value}}",
|
||||||
"query_log_filtered": "Filtrirano od {{filter}}",
|
"query_log_filtered": "Filtrirano od {{filter}}",
|
||||||
"query_log_confirm_clear": "Jeste li sigurni da želite da očistite ceo dnevnik unosa?",
|
"query_log_confirm_clear": "Jeste li sigurni da želite da očistite ceo dnevnik unosa?",
|
||||||
|
|
|
@ -162,8 +162,8 @@
|
||||||
"page_table_footer_text": "Sida",
|
"page_table_footer_text": "Sida",
|
||||||
"rows_table_footer_text": "rader",
|
"rows_table_footer_text": "rader",
|
||||||
"updated_custom_filtering_toast": "Uppdaterade de egna filterreglerna",
|
"updated_custom_filtering_toast": "Uppdaterade de egna filterreglerna",
|
||||||
"rule_removed_from_custom_filtering_toast": "Regel borttagen från de egna filterreglerna",
|
"rule_removed_from_custom_filtering_toast": "Regel borttagen från de egna filterreglerna: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Regel tillagd till de egna filterreglerna",
|
"rule_added_to_custom_filtering_toast": "Regel tillagd till de egna filterreglerna: {{rule}}",
|
||||||
"query_log_response_status": "Status: {{value}}",
|
"query_log_response_status": "Status: {{value}}",
|
||||||
"query_log_filtered": "Filtrerat av {{filter}}",
|
"query_log_filtered": "Filtrerat av {{filter}}",
|
||||||
"query_log_confirm_clear": "Är du säker på att du vill rensa hela förfrågningsloggen?",
|
"query_log_confirm_clear": "Är du säker på att du vill rensa hela förfrågningsloggen?",
|
||||||
|
|
|
@ -167,8 +167,8 @@
|
||||||
"page_table_footer_text": "หน้า",
|
"page_table_footer_text": "หน้า",
|
||||||
"rows_table_footer_text": "ตาราง",
|
"rows_table_footer_text": "ตาราง",
|
||||||
"updated_custom_filtering_toast": "อัปเดตกฎการกรองที่กำหนดเอง",
|
"updated_custom_filtering_toast": "อัปเดตกฎการกรองที่กำหนดเอง",
|
||||||
"rule_removed_from_custom_filtering_toast": "ลบกฎออกจากกฎการกรองที่กำหนดเองแล้ว",
|
"rule_removed_from_custom_filtering_toast": "ลบกฎออกจากกฎการกรองที่กำหนดเองแล้ว {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "เพิ่มกฎในกฎการกรองที่กำหนดเองแล้ว",
|
"rule_added_to_custom_filtering_toast": "เพิ่มกฎในกฎการกรองที่กำหนดเองแล้ว {{rule}}",
|
||||||
"query_log_response_status": "สถานะ: {{value}}",
|
"query_log_response_status": "สถานะ: {{value}}",
|
||||||
"query_log_filtered": "กรองโดย {{filter}}",
|
"query_log_filtered": "กรองโดย {{filter}}",
|
||||||
"query_log_confirm_clear": "คุณแน่ใจหรือไม่ว่าต้องการลบบันทึกการใช้งานทั้งหมด?",
|
"query_log_confirm_clear": "คุณแน่ใจหรือไม่ว่าต้องการลบบันทึกการใช้งานทั้งหมด?",
|
||||||
|
|
|
@ -197,8 +197,8 @@
|
||||||
"page_table_footer_text": "Sayfa",
|
"page_table_footer_text": "Sayfa",
|
||||||
"rows_table_footer_text": "satır",
|
"rows_table_footer_text": "satır",
|
||||||
"updated_custom_filtering_toast": "İsteğe bağlı filtreleme kuralları güncellendi",
|
"updated_custom_filtering_toast": "İsteğe bağlı filtreleme kuralları güncellendi",
|
||||||
"rule_removed_from_custom_filtering_toast": "Kural isteğe bağlı filtreleme kurallarından kaldırıldı",
|
"rule_removed_from_custom_filtering_toast": "Özel filtreleme kurallarından kural kaldırıldı: {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Kural isteğe bağlı filtreleme kurallarına eklendi",
|
"rule_added_to_custom_filtering_toast": "Özel filtreleme kurallarına kural eklendi: {{rule}}",
|
||||||
"query_log_response_status": "Durum: {{value}}",
|
"query_log_response_status": "Durum: {{value}}",
|
||||||
"query_log_filtered": "{{filter}} tarafından filtrelendi",
|
"query_log_filtered": "{{filter}} tarafından filtrelendi",
|
||||||
"query_log_confirm_clear": "Tüm sorgu günlüğünü temizlemek istediğinizden emin misiniz?",
|
"query_log_confirm_clear": "Tüm sorgu günlüğünü temizlemek istediğinizden emin misiniz?",
|
||||||
|
|
|
@ -172,8 +172,8 @@
|
||||||
"page_table_footer_text": "Trang",
|
"page_table_footer_text": "Trang",
|
||||||
"rows_table_footer_text": "hàng",
|
"rows_table_footer_text": "hàng",
|
||||||
"updated_custom_filtering_toast": "Đã cập nhật quy tắc lọc tuỳ chỉnh",
|
"updated_custom_filtering_toast": "Đã cập nhật quy tắc lọc tuỳ chỉnh",
|
||||||
"rule_removed_from_custom_filtering_toast": "Quy tắc đã được xoá khỏi quy tắc lọc tuỳ chỉnh",
|
"rule_removed_from_custom_filtering_toast": "Quy tắc đã được xoá khỏi quy tắc lọc tuỳ chỉnh {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "Quy tắc đã được thêm vào quy tắc lọc tuỳ chỉnh",
|
"rule_added_to_custom_filtering_toast": "Quy tắc đã được thêm vào quy tắc lọc tuỳ chỉnh: {{rule}}",
|
||||||
"query_log_response_status": "Trạng thái: {{value}}",
|
"query_log_response_status": "Trạng thái: {{value}}",
|
||||||
"query_log_filtered": "Được lọc bởi {{filter}}",
|
"query_log_filtered": "Được lọc bởi {{filter}}",
|
||||||
"query_log_confirm_clear": "Bạn có chắc chắn muốn xóa toàn bộ nhật ký truy vấn không?",
|
"query_log_confirm_clear": "Bạn có chắc chắn muốn xóa toàn bộ nhật ký truy vấn không?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "页",
|
"page_table_footer_text": "页",
|
||||||
"rows_table_footer_text": "行",
|
"rows_table_footer_text": "行",
|
||||||
"updated_custom_filtering_toast": "自定义过滤规则已更新",
|
"updated_custom_filtering_toast": "自定义过滤规则已更新",
|
||||||
"rule_removed_from_custom_filtering_toast": "规则已从自定义过滤规则列表中移除",
|
"rule_removed_from_custom_filtering_toast": "规则已从自定义过滤规则列表中移除 {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "规则已添加到自定义过滤规则列表中",
|
"rule_added_to_custom_filtering_toast": "规则已添加到自定义过滤规则列表中 {{rule}}",
|
||||||
"query_log_response_status": "状态: {{value}}",
|
"query_log_response_status": "状态: {{value}}",
|
||||||
"query_log_filtered": "被 {{filter}} 过滤",
|
"query_log_filtered": "被 {{filter}} 过滤",
|
||||||
"query_log_confirm_clear": "你确定想要清除全部查询日志吗?",
|
"query_log_confirm_clear": "你确定想要清除全部查询日志吗?",
|
||||||
|
|
|
@ -208,8 +208,8 @@
|
||||||
"page_table_footer_text": "頁面",
|
"page_table_footer_text": "頁面",
|
||||||
"rows_table_footer_text": "列",
|
"rows_table_footer_text": "列",
|
||||||
"updated_custom_filtering_toast": "已更新自訂的過濾規則",
|
"updated_custom_filtering_toast": "已更新自訂的過濾規則",
|
||||||
"rule_removed_from_custom_filtering_toast": "規則從自訂的過濾規則中被移除",
|
"rule_removed_from_custom_filtering_toast": "規則從自訂的過濾規則中被移除 {{rule}}",
|
||||||
"rule_added_to_custom_filtering_toast": "規則被加至自訂的過濾規則中",
|
"rule_added_to_custom_filtering_toast": "規則被加至自訂的過濾規則中 {{rule}}",
|
||||||
"query_log_response_status": "狀態:{{value}}",
|
"query_log_response_status": "狀態:{{value}}",
|
||||||
"query_log_filtered": "被 {{filter}} 過濾",
|
"query_log_filtered": "被 {{filter}} 過濾",
|
||||||
"query_log_confirm_clear": "您確定您想要清除整個查詢記錄嗎?",
|
"query_log_confirm_clear": "您確定您想要清除整個查詢記錄嗎?",
|
||||||
|
|
|
@ -2,14 +2,17 @@ import { createAction } from 'redux-actions';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import endsWith from 'lodash/endsWith';
|
||||||
|
import escapeRegExp from 'lodash/escapeRegExp';
|
||||||
import { splitByNewLine, sortClients } from '../helpers/helpers';
|
import { splitByNewLine, sortClients } from '../helpers/helpers';
|
||||||
import {
|
import {
|
||||||
CHECK_TIMEOUT, STATUS_RESPONSE, SETTINGS_NAMES, FORM_NAME,
|
BLOCK_ACTIONS, CHECK_TIMEOUT, STATUS_RESPONSE, SETTINGS_NAMES, FORM_NAME,
|
||||||
} from '../helpers/constants';
|
} from '../helpers/constants';
|
||||||
import { areEqualVersions } from '../helpers/version';
|
import { areEqualVersions } from '../helpers/version';
|
||||||
import { getTlsStatus } from './encryption';
|
import { getTlsStatus } from './encryption';
|
||||||
import apiClient from '../api/Api';
|
import apiClient from '../api/Api';
|
||||||
import { addErrorToast, addNoticeToast, addSuccessToast } from './toasts';
|
import { addErrorToast, addNoticeToast, addSuccessToast } from './toasts';
|
||||||
|
import { getFilteringStatus, setRules } from './filtering';
|
||||||
|
|
||||||
export const toggleSettingStatus = createAction('SETTING_STATUS_TOGGLE');
|
export const toggleSettingStatus = createAction('SETTING_STATUS_TOGGLE');
|
||||||
export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
|
export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
|
||||||
|
@ -541,3 +544,35 @@ export const removeStaticLease = (config) => async (dispatch) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeToast = createAction('REMOVE_TOAST');
|
export const removeToast = createAction('REMOVE_TOAST');
|
||||||
|
|
||||||
|
export const toggleBlocking = (type, domain) => async (dispatch, getState) => {
|
||||||
|
const { userRules } = getState().filtering;
|
||||||
|
|
||||||
|
const lineEnding = !endsWith(userRules, '\n') ? '\n' : '';
|
||||||
|
const baseRule = `||${domain}^$important`;
|
||||||
|
const baseUnblocking = `@@${baseRule}`;
|
||||||
|
|
||||||
|
const blockingRule = type === BLOCK_ACTIONS.BLOCK ? baseUnblocking : baseRule;
|
||||||
|
const unblockingRule = type === BLOCK_ACTIONS.BLOCK ? baseRule : baseUnblocking;
|
||||||
|
const preparedBlockingRule = new RegExp(`(^|\n)${escapeRegExp(blockingRule)}($|\n)`);
|
||||||
|
const preparedUnblockingRule = new RegExp(`(^|\n)${escapeRegExp(unblockingRule)}($|\n)`);
|
||||||
|
|
||||||
|
const matchPreparedBlockingRule = userRules.match(preparedBlockingRule);
|
||||||
|
const matchPreparedUnblockingRule = userRules.match(preparedUnblockingRule);
|
||||||
|
|
||||||
|
if (matchPreparedBlockingRule) {
|
||||||
|
dispatch(setRules(userRules.replace(`${blockingRule}`, '')));
|
||||||
|
dispatch(addSuccessToast(i18next.t('rule_removed_from_custom_filtering_toast', { rule: blockingRule })));
|
||||||
|
} else if (!matchPreparedUnblockingRule) {
|
||||||
|
dispatch(setRules(`${userRules}${lineEnding}${unblockingRule}\n`));
|
||||||
|
dispatch(addSuccessToast(i18next.t('rule_added_to_custom_filtering_toast', { rule: unblockingRule })));
|
||||||
|
} else if (matchPreparedUnblockingRule) {
|
||||||
|
dispatch(addSuccessToast(i18next.t('rule_added_to_custom_filtering_toast', { rule: unblockingRule })));
|
||||||
|
return;
|
||||||
|
} else if (!matchPreparedBlockingRule) {
|
||||||
|
dispatch(addSuccessToast(i18next.t('rule_removed_from_custom_filtering_toast', { rule: blockingRule })));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(getFilteringStatus());
|
||||||
|
};
|
||||||
|
|
|
@ -3,9 +3,7 @@ import { createAction } from 'redux-actions';
|
||||||
import apiClient from '../api/Api';
|
import apiClient from '../api/Api';
|
||||||
import { normalizeLogs, getParamsForClientsSearch, addClientInfo } from '../helpers/helpers';
|
import { normalizeLogs, getParamsForClientsSearch, addClientInfo } from '../helpers/helpers';
|
||||||
import {
|
import {
|
||||||
DEFAULT_LOGS_FILTER,
|
DEFAULT_LOGS_FILTER, FORM_NAME, QUERY_LOGS_PAGE_LIMIT,
|
||||||
TABLE_DEFAULT_PAGE_SIZE,
|
|
||||||
TABLE_FIRST_PAGE,
|
|
||||||
} from '../helpers/constants';
|
} from '../helpers/constants';
|
||||||
import { addErrorToast, addSuccessToast } from './toasts';
|
import { addErrorToast, addSuccessToast } from './toasts';
|
||||||
|
|
||||||
|
@ -37,15 +35,22 @@ export const getAdditionalLogsRequest = createAction('GET_ADDITIONAL_LOGS_REQUES
|
||||||
export const getAdditionalLogsFailure = createAction('GET_ADDITIONAL_LOGS_FAILURE');
|
export const getAdditionalLogsFailure = createAction('GET_ADDITIONAL_LOGS_FAILURE');
|
||||||
export const getAdditionalLogsSuccess = createAction('GET_ADDITIONAL_LOGS_SUCCESS');
|
export const getAdditionalLogsSuccess = createAction('GET_ADDITIONAL_LOGS_SUCCESS');
|
||||||
|
|
||||||
const checkFilteredLogs = async (data, filter, dispatch, total) => {
|
const shortPollQueryLogs = async (data, filter, dispatch, getState, total) => {
|
||||||
const { logs, oldest } = data;
|
const { logs, oldest } = data;
|
||||||
const totalData = total || { logs };
|
const totalData = total || { logs };
|
||||||
|
|
||||||
const needToGetAdditionalLogs = (logs.length < TABLE_DEFAULT_PAGE_SIZE
|
const queryForm = getState().form[FORM_NAME.LOGS_FILTER];
|
||||||
|| totalData.logs.length < TABLE_DEFAULT_PAGE_SIZE)
|
const currentQuery = queryForm && queryForm.values.search;
|
||||||
&& oldest !== '';
|
const previousQuery = filter?.search;
|
||||||
|
const isQueryTheSame = typeof previousQuery === 'string'
|
||||||
|
&& typeof currentQuery === 'string'
|
||||||
|
&& previousQuery === currentQuery;
|
||||||
|
|
||||||
if (needToGetAdditionalLogs) {
|
const isShortPollingNeeded = (logs.length < QUERY_LOGS_PAGE_LIMIT
|
||||||
|
|| totalData.logs.length < QUERY_LOGS_PAGE_LIMIT)
|
||||||
|
&& oldest !== '' && isQueryTheSame;
|
||||||
|
|
||||||
|
if (isShortPollingNeeded) {
|
||||||
dispatch(getAdditionalLogsRequest());
|
dispatch(getAdditionalLogsRequest());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -54,7 +59,7 @@ const checkFilteredLogs = async (data, filter, dispatch, total) => {
|
||||||
filter,
|
filter,
|
||||||
});
|
});
|
||||||
if (additionalLogs.oldest.length > 0) {
|
if (additionalLogs.oldest.length > 0) {
|
||||||
return await checkFilteredLogs(additionalLogs, filter, dispatch, {
|
return await shortPollQueryLogs(additionalLogs, filter, dispatch, getState, {
|
||||||
logs: [...totalData.logs, ...additionalLogs.logs],
|
logs: [...totalData.logs, ...additionalLogs.logs],
|
||||||
oldest: additionalLogs.oldest,
|
oldest: additionalLogs.oldest,
|
||||||
});
|
});
|
||||||
|
@ -71,31 +76,25 @@ const checkFilteredLogs = async (data, filter, dispatch, total) => {
|
||||||
return totalData;
|
return totalData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setLogsPagination = createAction('LOGS_PAGINATION');
|
|
||||||
export const setLogsPage = createAction('SET_LOG_PAGE');
|
|
||||||
export const toggleDetailedLogs = createAction('TOGGLE_DETAILED_LOGS');
|
export const toggleDetailedLogs = createAction('TOGGLE_DETAILED_LOGS');
|
||||||
|
|
||||||
export const getLogsRequest = createAction('GET_LOGS_REQUEST');
|
export const getLogsRequest = createAction('GET_LOGS_REQUEST');
|
||||||
export const getLogsFailure = createAction('GET_LOGS_FAILURE');
|
export const getLogsFailure = createAction('GET_LOGS_FAILURE');
|
||||||
export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
|
export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
|
||||||
|
|
||||||
export const getLogs = (config) => async (dispatch, getState) => {
|
export const getLogs = () => async (dispatch, getState) => {
|
||||||
dispatch(getLogsRequest());
|
dispatch(getLogsRequest());
|
||||||
try {
|
try {
|
||||||
const { isFiltered, filter, page } = getState().queryLogs;
|
const { isFiltered, filter, oldest } = getState().queryLogs;
|
||||||
const data = await getLogsWithParams({
|
const data = await getLogsWithParams({
|
||||||
...config,
|
older_than: oldest,
|
||||||
filter,
|
filter,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isFiltered) {
|
if (isFiltered) {
|
||||||
const additionalData = await checkFilteredLogs(data, filter, dispatch);
|
const additionalData = await shortPollQueryLogs(data, filter, dispatch, getState);
|
||||||
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
|
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
|
||||||
dispatch(getLogsSuccess(updatedData));
|
dispatch(getLogsSuccess(updatedData));
|
||||||
dispatch(setLogsPagination({
|
|
||||||
page,
|
|
||||||
pageSize: TABLE_DEFAULT_PAGE_SIZE,
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(getLogsSuccess(data));
|
dispatch(getLogsSuccess(data));
|
||||||
}
|
}
|
||||||
|
@ -111,7 +110,7 @@ export const setLogsFilterRequest = createAction('SET_LOGS_FILTER_REQUEST');
|
||||||
*
|
*
|
||||||
* @param filter
|
* @param filter
|
||||||
* @param {string} filter.search
|
* @param {string} filter.search
|
||||||
* @param {string} filter.response_status query field of RESPONSE_FILTER object
|
* @param {string} filter.response_status 'QUERY' field of RESPONSE_FILTER object
|
||||||
* @returns function
|
* @returns function
|
||||||
*/
|
*/
|
||||||
export const setLogsFilter = (filter) => setLogsFilterRequest(filter);
|
export const setLogsFilter = (filter) => setLogsFilterRequest(filter);
|
||||||
|
@ -120,21 +119,20 @@ export const setFilteredLogsRequest = createAction('SET_FILTERED_LOGS_REQUEST');
|
||||||
export const setFilteredLogsFailure = createAction('SET_FILTERED_LOGS_FAILURE');
|
export const setFilteredLogsFailure = createAction('SET_FILTERED_LOGS_FAILURE');
|
||||||
export const setFilteredLogsSuccess = createAction('SET_FILTERED_LOGS_SUCCESS');
|
export const setFilteredLogsSuccess = createAction('SET_FILTERED_LOGS_SUCCESS');
|
||||||
|
|
||||||
export const setFilteredLogs = (filter) => async (dispatch) => {
|
export const setFilteredLogs = (filter) => async (dispatch, getState) => {
|
||||||
dispatch(setFilteredLogsRequest());
|
dispatch(setFilteredLogsRequest());
|
||||||
try {
|
try {
|
||||||
const data = await getLogsWithParams({
|
const data = await getLogsWithParams({
|
||||||
older_than: '',
|
older_than: '',
|
||||||
filter,
|
filter,
|
||||||
});
|
});
|
||||||
const additionalData = await checkFilteredLogs(data, filter, dispatch);
|
const additionalData = await shortPollQueryLogs(data, filter, dispatch, getState);
|
||||||
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
|
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
|
||||||
|
|
||||||
dispatch(setFilteredLogsSuccess({
|
dispatch(setFilteredLogsSuccess({
|
||||||
...updatedData,
|
...updatedData,
|
||||||
filter,
|
filter,
|
||||||
}));
|
}));
|
||||||
dispatch(setLogsPage(TABLE_FIRST_PAGE));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch(addErrorToast({ error }));
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(setFilteredLogsFailure(error));
|
dispatch(setFilteredLogsFailure(error));
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import { getPathWithQueryString } from '../helpers/helpers';
|
import { getPathWithQueryString } from '../helpers/helpers';
|
||||||
import { R_PATH_LAST_PART } from '../helpers/constants';
|
import { QUERY_LOGS_PAGE_LIMIT, R_PATH_LAST_PART } from '../helpers/constants';
|
||||||
import { BASE_URL } from '../../constants';
|
import { BASE_URL } from '../../constants';
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
|
@ -530,6 +530,8 @@ class Api {
|
||||||
|
|
||||||
getQueryLog(params) {
|
getQueryLog(params) {
|
||||||
const { path, method } = this.GET_QUERY_LOG;
|
const { path, method } = this.GET_QUERY_LOG;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
params.limit = QUERY_LOGS_PAGE_LIMIT;
|
||||||
const url = getPathWithQueryString(path, params);
|
const url = getPathWithQueryString(path, params);
|
||||||
return this.makeRequest(url, method);
|
return this.makeRequest(url, method);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import Header from '../Header';
|
||||||
import { changeLanguage, getDnsStatus } from '../../actions';
|
import { changeLanguage, getDnsStatus } from '../../actions';
|
||||||
|
|
||||||
import Dashboard from '../../containers/Dashboard';
|
import Dashboard from '../../containers/Dashboard';
|
||||||
import Logs from '../../containers/Logs';
|
|
||||||
import SetupGuide from '../../containers/SetupGuide';
|
import SetupGuide from '../../containers/SetupGuide';
|
||||||
import Settings from '../../containers/Settings';
|
import Settings from '../../containers/Settings';
|
||||||
import Dns from '../../containers/Dns';
|
import Dns from '../../containers/Dns';
|
||||||
|
@ -38,6 +37,7 @@ import DnsAllowlist from '../../containers/DnsAllowlist';
|
||||||
import DnsRewrites from '../../containers/DnsRewrites';
|
import DnsRewrites from '../../containers/DnsRewrites';
|
||||||
import CustomRules from '../../containers/CustomRules';
|
import CustomRules from '../../containers/CustomRules';
|
||||||
import Services from '../Filters/Services';
|
import Services from '../Filters/Services';
|
||||||
|
import Logs from '../Logs';
|
||||||
|
|
||||||
|
|
||||||
const ROUTES = [
|
const ROUTES = [
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
import classNames from 'classnames';
|
||||||
import Card from '../ui/Card';
|
import Card from '../ui/Card';
|
||||||
import Cell from '../ui/Cell';
|
import Cell from '../ui/Cell';
|
||||||
|
|
||||||
import { getPercent, getIpMatchListStatus, sortIp } from '../../helpers/helpers';
|
import { getPercent, getIpMatchListStatus, sortIp } from '../../helpers/helpers';
|
||||||
import { IP_MATCH_LIST_STATUS, STATUS_COLORS } from '../../helpers/constants';
|
import { BLOCK_ACTIONS, IP_MATCH_LIST_STATUS, STATUS_COLORS } from '../../helpers/constants';
|
||||||
import { formatClientCell } from '../../helpers/formatClientCell';
|
import { toggleClientBlock } from '../../actions/access';
|
||||||
|
import { renderFormattedClientCell } from '../../helpers/renderFormattedClientCell';
|
||||||
|
|
||||||
const getClientsPercentColor = (percent) => {
|
const getClientsPercentColor = (percent) => {
|
||||||
if (percent > 50) {
|
if (percent > 50) {
|
||||||
|
@ -20,67 +23,81 @@ const getClientsPercentColor = (percent) => {
|
||||||
return STATUS_COLORS.red;
|
return STATUS_COLORS.red;
|
||||||
};
|
};
|
||||||
|
|
||||||
const countCell = (dnsQueries) => function cell(row) {
|
const CountCell = (row) => {
|
||||||
const { value } = row;
|
const { value, original: { ip } } = row;
|
||||||
const percent = getPercent(dnsQueries, value);
|
const numDnsQueries = useSelector((state) => state.stats.numDnsQueries, shallowEqual);
|
||||||
|
|
||||||
|
const percent = getPercent(numDnsQueries, value);
|
||||||
const percentColor = getClientsPercentColor(percent);
|
const percentColor = getClientsPercentColor(percent);
|
||||||
|
|
||||||
return <Cell value={value} percent={percent} color={percentColor} search={row.original.ip} />;
|
return <Cell value={value} percent={percent} color={percentColor} search={ip} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderBlockingButton = (ipMatchListStatus, ip, handleClick, processing) => {
|
const renderBlockingButton = (ip) => {
|
||||||
const buttonProps = ipMatchListStatus === IP_MATCH_LIST_STATUS.NOT_FOUND
|
const dispatch = useDispatch();
|
||||||
? {
|
const { t } = useTranslation();
|
||||||
className: 'btn-outline-danger',
|
const processingSet = useSelector((state) => state.access.processingSet);
|
||||||
text: 'block',
|
const disallowed_clients = useSelector(
|
||||||
type: 'block',
|
(state) => state.access.disallowed_clients, shallowEqual,
|
||||||
|
);
|
||||||
|
|
||||||
|
const ipMatchListStatus = getIpMatchListStatus(ip, disallowed_clients);
|
||||||
|
|
||||||
|
if (ipMatchListStatus === IP_MATCH_LIST_STATUS.CIDR) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNotFound = ipMatchListStatus === IP_MATCH_LIST_STATUS.NOT_FOUND;
|
||||||
|
const type = isNotFound ? BLOCK_ACTIONS.BLOCK : BLOCK_ACTIONS.UNBLOCK;
|
||||||
|
const text = type;
|
||||||
|
|
||||||
|
const className = classNames('btn btn-sm', {
|
||||||
|
'btn-outline-danger': isNotFound,
|
||||||
|
'btn-outline-secondary': !isNotFound,
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleClientStatus = (type, ip) => {
|
||||||
|
const confirmMessage = type === BLOCK_ACTIONS.BLOCK ? 'client_confirm_block' : 'client_confirm_unblock';
|
||||||
|
|
||||||
|
if (window.confirm(t(confirmMessage, { ip }))) {
|
||||||
|
dispatch(toggleClientBlock(type, ip));
|
||||||
}
|
}
|
||||||
: {
|
|
||||||
className: 'btn-outline-secondary',
|
|
||||||
text: 'unblock',
|
|
||||||
type: 'unblock',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const onClick = () => toggleClientStatus(type, ip);
|
||||||
<div className="table__action button__action">
|
|
||||||
|
return <div className="table__action pl-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm ${buttonProps.className}`}
|
className={className}
|
||||||
onClick={() => handleClick(buttonProps.type, ip)}
|
onClick={onClick}
|
||||||
disabled={processing}
|
disabled={processingSet}
|
||||||
>
|
>
|
||||||
<Trans>{buttonProps.text}</Trans>
|
<Trans>{text}</Trans>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>;
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const clientCell = (t, toggleClientStatus, processing, disallowedClients) => function cell(row) {
|
const ClientCell = (row) => {
|
||||||
const { value } = row;
|
const { value, original: { info } } = row;
|
||||||
const ipMatchListStatus = getIpMatchListStatus(value, disallowedClients);
|
|
||||||
|
|
||||||
return (
|
return <>
|
||||||
<>
|
<div className="logs__row logs__row--overflow logs__row--column d-flex">
|
||||||
<div className="logs__row logs__row--overflow logs__row--column">
|
{renderFormattedClientCell(value, info, true)}
|
||||||
{formatClientCell(row, true, false)}
|
{renderBlockingButton(value)}
|
||||||
</div>
|
</div>
|
||||||
{ipMatchListStatus !== IP_MATCH_LIST_STATUS.CIDR
|
</>;
|
||||||
&& renderBlockingButton(ipMatchListStatus, value, toggleClientStatus, processing)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Clients = ({
|
const Clients = ({
|
||||||
t,
|
|
||||||
refreshButton,
|
refreshButton,
|
||||||
topClients,
|
|
||||||
subtitle,
|
subtitle,
|
||||||
dnsQueries,
|
}) => {
|
||||||
toggleClientStatus,
|
const { t } = useTranslation();
|
||||||
processingAccessSet,
|
const topClients = useSelector((state) => state.stats.topClients, shallowEqual);
|
||||||
disallowedClients,
|
const disallowedClients = useSelector((state) => state.access.disallowed_clients, shallowEqual);
|
||||||
}) => (
|
|
||||||
<Card
|
return <Card
|
||||||
title={t('top_clients')}
|
title={t('top_clients')}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
bodyType="card-table"
|
bodyType="card-table"
|
||||||
|
@ -100,14 +117,14 @@ const Clients = ({
|
||||||
Header: 'IP',
|
Header: 'IP',
|
||||||
accessor: 'ip',
|
accessor: 'ip',
|
||||||
sortMethod: sortIp,
|
sortMethod: sortIp,
|
||||||
Cell: clientCell(t, toggleClientStatus, processingAccessSet, disallowedClients),
|
Cell: ClientCell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: <Trans>requests_count</Trans>,
|
Header: <Trans>requests_count</Trans>,
|
||||||
accessor: 'count',
|
accessor: 'count',
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
maxWidth: 200,
|
maxWidth: 200,
|
||||||
Cell: countCell(dnsQueries),
|
Cell: CountCell,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
showPagination={false}
|
showPagination={false}
|
||||||
|
@ -122,24 +139,15 @@ const Clients = ({
|
||||||
|
|
||||||
const { ip } = rowInfo.original;
|
const { ip } = rowInfo.original;
|
||||||
|
|
||||||
return getIpMatchListStatus(ip, disallowedClients)
|
return getIpMatchListStatus(ip, disallowedClients) === IP_MATCH_LIST_STATUS.NOT_FOUND ? {} : { className: 'red' };
|
||||||
=== IP_MATCH_LIST_STATUS.NOT_FOUND ? {} : { className: 'red' };
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>;
|
||||||
);
|
|
||||||
|
|
||||||
Clients.propTypes = {
|
|
||||||
topClients: PropTypes.array.isRequired,
|
|
||||||
dnsQueries: PropTypes.number.isRequired,
|
|
||||||
refreshButton: PropTypes.node.isRequired,
|
|
||||||
clients: PropTypes.array.isRequired,
|
|
||||||
autoClients: PropTypes.array.isRequired,
|
|
||||||
subtitle: PropTypes.string.isRequired,
|
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
toggleClientStatus: PropTypes.func.isRequired,
|
|
||||||
processingAccessSet: PropTypes.bool.isRequired,
|
|
||||||
disallowedClients: PropTypes.string.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(Clients);
|
Clients.propTypes = {
|
||||||
|
refreshButton: PropTypes.node.isRequired,
|
||||||
|
subtitle: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Clients;
|
||||||
|
|
|
@ -47,32 +47,32 @@ const Counters = ({ refreshButton, subtitle }) => {
|
||||||
label: 'dns_query',
|
label: 'dns_query',
|
||||||
count: numDnsQueries,
|
count: numDnsQueries,
|
||||||
tooltipTitle: interval === 1 ? 'number_of_dns_query_24_hours' : t('number_of_dns_query_days', { count: interval }),
|
tooltipTitle: interval === 1 ? 'number_of_dns_query_24_hours' : t('number_of_dns_query_days', { count: interval }),
|
||||||
response_status: RESPONSE_FILTER.ALL.query,
|
response_status: RESPONSE_FILTER.ALL.QUERY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'blocked_by',
|
label: 'blocked_by',
|
||||||
count: numBlockedFiltering,
|
count: numBlockedFiltering,
|
||||||
tooltipTitle: 'number_of_dns_query_blocked_24_hours',
|
tooltipTitle: 'number_of_dns_query_blocked_24_hours',
|
||||||
response_status: RESPONSE_FILTER.BLOCKED.query,
|
response_status: RESPONSE_FILTER.BLOCKED.QUERY,
|
||||||
translationComponents: [<a href="#filters" key="0">link</a>],
|
translationComponents: [<a href="#filters" key="0">link</a>],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'stats_malware_phishing',
|
label: 'stats_malware_phishing',
|
||||||
count: numReplacedSafebrowsing,
|
count: numReplacedSafebrowsing,
|
||||||
tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec',
|
tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec',
|
||||||
response_status: RESPONSE_FILTER.BLOCKED_THREATS.query,
|
response_status: RESPONSE_FILTER.BLOCKED_THREATS.QUERY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'stats_adult',
|
label: 'stats_adult',
|
||||||
count: numReplacedParental,
|
count: numReplacedParental,
|
||||||
tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult',
|
tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult',
|
||||||
response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.query,
|
response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'enforced_save_search',
|
label: 'enforced_save_search',
|
||||||
count: numReplacedSafesearch,
|
count: numReplacedSafesearch,
|
||||||
tooltipTitle: 'number_of_dns_query_to_safe_search',
|
tooltipTitle: 'number_of_dns_query_to_safe_search',
|
||||||
response_status: RESPONSE_FILTER.SAFE_SEARCH.query,
|
response_status: RESPONSE_FILTER.SAFE_SEARCH.QUERY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'average_processing_time',
|
label: 'average_processing_time',
|
||||||
|
|
|
@ -10,7 +10,6 @@ import BlockedDomains from './BlockedDomains';
|
||||||
|
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import { BLOCK_ACTIONS } from '../../helpers/constants';
|
|
||||||
import './Dashboard.css';
|
import './Dashboard.css';
|
||||||
|
|
||||||
const Dashboard = ({
|
const Dashboard = ({
|
||||||
|
@ -19,7 +18,6 @@ const Dashboard = ({
|
||||||
getStatsConfig,
|
getStatsConfig,
|
||||||
dashboard,
|
dashboard,
|
||||||
toggleProtection,
|
toggleProtection,
|
||||||
toggleClientBlock,
|
|
||||||
stats,
|
stats,
|
||||||
access,
|
access,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -50,14 +48,6 @@ const Dashboard = ({
|
||||||
</button>;
|
</button>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleClientStatus = (type, ip) => {
|
|
||||||
const confirmMessage = type === BLOCK_ACTIONS.BLOCK ? 'client_confirm_block' : 'client_confirm_unblock';
|
|
||||||
|
|
||||||
if (window.confirm(t(confirmMessage, { ip }))) {
|
|
||||||
toggleClientBlock(type, ip);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshButton = <button
|
const refreshButton = <button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-icon btn-outline-primary btn-sm"
|
className="btn btn-icon btn-outline-primary btn-sm"
|
||||||
|
@ -122,7 +112,6 @@ const Dashboard = ({
|
||||||
clients={dashboard.clients}
|
clients={dashboard.clients}
|
||||||
autoClients={dashboard.autoClients}
|
autoClients={dashboard.autoClients}
|
||||||
refreshButton={refreshButton}
|
refreshButton={refreshButton}
|
||||||
toggleClientStatus={toggleClientStatus}
|
|
||||||
processingAccessSet={access.processingSet}
|
processingAccessSet={access.processingSet}
|
||||||
disallowedClients={access.disallowed_clients}
|
disallowedClients={access.disallowed_clients}
|
||||||
/>
|
/>
|
||||||
|
@ -157,7 +146,6 @@ Dashboard.propTypes = {
|
||||||
getStatsConfig: PropTypes.func.isRequired,
|
getStatsConfig: PropTypes.func.isRequired,
|
||||||
toggleProtection: PropTypes.func.isRequired,
|
toggleProtection: PropTypes.func.isRequired,
|
||||||
getClients: PropTypes.func.isRequired,
|
getClients: PropTypes.func.isRequired,
|
||||||
toggleClientBlock: PropTypes.func.isRequired,
|
|
||||||
getAccessList: PropTypes.func.isRequired,
|
getAccessList: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,25 +11,10 @@ import {
|
||||||
checkWhiteList,
|
checkWhiteList,
|
||||||
checkSafeSearch,
|
checkSafeSearch,
|
||||||
checkSafeBrowsing,
|
checkSafeBrowsing,
|
||||||
checkParental,
|
checkParental, getFilterName,
|
||||||
} from '../../../helpers/helpers';
|
} from '../../../helpers/helpers';
|
||||||
import { FILTERED } from '../../../helpers/constants';
|
import { FILTERED } from '../../../helpers/constants';
|
||||||
|
|
||||||
const getFilterName = (id, filters, whitelistFilters, t) => {
|
|
||||||
if (id === 0) {
|
|
||||||
return t('filtered_custom_rules');
|
|
||||||
}
|
|
||||||
|
|
||||||
const filter = filters.find((filter) => filter.id === id)
|
|
||||||
|| whitelistFilters.find((filter) => filter.id === id);
|
|
||||||
|
|
||||||
if (filter && filter.name) {
|
|
||||||
return t('query_log_filtered', { filter: filter.name });
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTitle = (reason, filterName, t, onlyFiltered) => {
|
const getTitle = (reason, filterName, t, onlyFiltered) => {
|
||||||
if (checkNotFilteredNotFound(reason)) {
|
if (checkNotFilteredNotFound(reason)) {
|
||||||
return t('check_not_found');
|
return t('check_not_found');
|
||||||
|
@ -101,7 +86,12 @@ const Info = ({
|
||||||
ip_addrs,
|
ip_addrs,
|
||||||
t,
|
t,
|
||||||
}) => {
|
}) => {
|
||||||
const filterName = getFilterName(filter_id, filters, whitelistFilters, t);
|
const filterName = getFilterName(filters,
|
||||||
|
whitelistFilters,
|
||||||
|
filter_id,
|
||||||
|
'filtered_custom_rules',
|
||||||
|
(filter) => (filter?.name ? t('query_log_filtered', { filter: filter.name }) : ''));
|
||||||
|
|
||||||
const onlyFiltered = checkSafeSearch(reason)
|
const onlyFiltered = checkSafeSearch(reason)
|
||||||
|| checkSafeBrowsing(reason)
|
|| checkSafeBrowsing(reason)
|
||||||
|| checkParental(reason);
|
|| checkParental(reason);
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Table extends Component {
|
||||||
accessor: 'url',
|
accessor: 'url',
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
Cell: ({ value }) => (
|
Cell: ({ value }) => (
|
||||||
<div className="logs__row o-hidden">
|
<div className="logs__row">
|
||||||
{isValidAbsolutePath(value) ? value
|
{isValidAbsolutePath(value) ? value
|
||||||
: <a
|
: <a
|
||||||
href={value}
|
href={value}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import propTypes from 'prop-types';
|
||||||
|
import { checkFiltered } from '../../../helpers/helpers';
|
||||||
|
import { BLOCK_ACTIONS } from '../../../helpers/constants';
|
||||||
|
import { toggleBlocking } from '../../../actions';
|
||||||
|
import IconTooltip from './IconTooltip';
|
||||||
|
import { renderFormattedClientCell } from '../../../helpers/renderFormattedClientCell';
|
||||||
|
|
||||||
|
const ClientCell = ({
|
||||||
|
client,
|
||||||
|
domain,
|
||||||
|
info,
|
||||||
|
info: { name, whois_info },
|
||||||
|
reason,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
||||||
|
const processingRules = useSelector((state) => state.filtering.processingRules);
|
||||||
|
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||||
|
|
||||||
|
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
|
||||||
|
const source = autoClient?.source;
|
||||||
|
const whoisAvailable = whois_info && Object.keys(whois_info).length > 0;
|
||||||
|
|
||||||
|
const id = nanoid();
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
address: client,
|
||||||
|
name,
|
||||||
|
country: whois_info?.country,
|
||||||
|
city: whois_info?.city,
|
||||||
|
network: whois_info?.orgname,
|
||||||
|
source_label: source,
|
||||||
|
};
|
||||||
|
|
||||||
|
const processedData = Object.entries(data);
|
||||||
|
|
||||||
|
const isFiltered = checkFiltered(reason);
|
||||||
|
|
||||||
|
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
|
||||||
|
'mt-2': isDetailed && !name && !whoisAvailable,
|
||||||
|
'white-space--nowrap': isDetailed,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hintClass = classNames('icons mr-4 icon--24 icon--lightgray', {
|
||||||
|
'my-3': isDetailed,
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderBlockingButton = (isFiltered, domain) => {
|
||||||
|
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
||||||
|
|
||||||
|
const buttonClass = classNames('btn btn-sm logs__cell--block-button', {
|
||||||
|
'btn-outline-secondary': isFiltered,
|
||||||
|
'btn-outline-danger': !isFiltered,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onClick = () => dispatch(toggleBlocking(buttonType, domain));
|
||||||
|
|
||||||
|
return <button
|
||||||
|
type="button"
|
||||||
|
className={buttonClass}
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={processingRules}
|
||||||
|
>
|
||||||
|
{t(buttonType)}
|
||||||
|
</button>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div className="o-hidden h-100 logs__cell logs__cell--client" role="gridcell">
|
||||||
|
<IconTooltip className={hintClass} columnClass='grid grid--limited' tooltipClass='px-5 pb-5 pt-4 mw-75'
|
||||||
|
xlinkHref='question' contentItemClass="contentItemClass" title="client_details"
|
||||||
|
content={processedData} placement="bottom" />
|
||||||
|
<div className={nameClass}>
|
||||||
|
<div data-tip={true} data-for={id}>
|
||||||
|
{renderFormattedClientCell(client, info, isDetailed, true)}
|
||||||
|
</div>
|
||||||
|
{isDetailed && name && !whoisAvailable
|
||||||
|
&& <div className="detailed-info d-none d-sm-block logs__text"
|
||||||
|
title={name}>
|
||||||
|
{name}
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
{renderBlockingButton(isFiltered, domain)}
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
ClientCell.propTypes = {
|
||||||
|
client: propTypes.string.isRequired,
|
||||||
|
domain: propTypes.string.isRequired,
|
||||||
|
info: propTypes.oneOfType([
|
||||||
|
propTypes.string,
|
||||||
|
propTypes.shape({
|
||||||
|
name: propTypes.string.isRequired,
|
||||||
|
whois_info: propTypes.shape({
|
||||||
|
country: propTypes.string,
|
||||||
|
city: propTypes.string,
|
||||||
|
orgname: propTypes.string,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
reason: propTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClientCell;
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import propTypes from 'prop-types';
|
||||||
|
import { formatDateTime, formatTime } from '../../../helpers/helpers';
|
||||||
|
import { DEFAULT_SHORT_DATE_FORMAT_OPTIONS, DEFAULT_TIME_FORMAT } from '../../../helpers/constants';
|
||||||
|
|
||||||
|
const DateCell = ({ time }) => {
|
||||||
|
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||||
|
|
||||||
|
if (!time) {
|
||||||
|
return '–';
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedTime = formatTime(time, DEFAULT_TIME_FORMAT);
|
||||||
|
const formattedDate = formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS);
|
||||||
|
|
||||||
|
return <div className="logs__cell logs__cell logs__cell--date text-truncate" role="gridcell">
|
||||||
|
<div className="logs__time" title={formattedTime}>{formattedTime}</div>
|
||||||
|
{isDetailed
|
||||||
|
&& <div className="detailed-info d-none d-sm-block text-truncate"
|
||||||
|
title={formattedDate}>{formattedDate}</div>}
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
DateCell.propTypes = {
|
||||||
|
time: propTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DateCell;
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import propTypes from 'prop-types';
|
||||||
import getIconTooltip from './getIconTooltip';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
||||||
LONG_TIME_FORMAT,
|
LONG_TIME_FORMAT,
|
||||||
|
@ -9,15 +10,19 @@ import {
|
||||||
} from '../../../helpers/constants';
|
} from '../../../helpers/constants';
|
||||||
import { captitalizeWords, formatDateTime, formatTime } from '../../../helpers/helpers';
|
import { captitalizeWords, formatDateTime, formatTime } from '../../../helpers/helpers';
|
||||||
import { getSourceData } from '../../../helpers/trackers/trackers';
|
import { getSourceData } from '../../../helpers/trackers/trackers';
|
||||||
|
import IconTooltip from './IconTooltip';
|
||||||
|
|
||||||
const getDomainCell = (props) => {
|
const DomainCell = ({
|
||||||
const {
|
answer_dnssec,
|
||||||
row, t, isDetailed, dnssec_enabled,
|
client_proto,
|
||||||
} = props;
|
domain,
|
||||||
|
time,
|
||||||
const {
|
tracker,
|
||||||
tracker, type, answer_dnssec, client_proto, domain, time,
|
type,
|
||||||
} = row.original;
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dnssec_enabled = useSelector((state) => state.dnsConfig.dnssec_enabled);
|
||||||
|
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||||
|
|
||||||
const hasTracker = !!tracker;
|
const hasTracker = !!tracker;
|
||||||
|
|
||||||
|
@ -72,51 +77,42 @@ const getDomainCell = (props) => {
|
||||||
|
|
||||||
const renderContent = hasTracker ? requestDetails.concat(getGrid(knownTrackerDataObj, 'known_tracker', 'pt-4')) : requestDetails;
|
const renderContent = hasTracker ? requestDetails.concat(getGrid(knownTrackerDataObj, 'known_tracker', 'pt-4')) : requestDetails;
|
||||||
|
|
||||||
const trackerHint = getIconTooltip({
|
const valueClass = classNames('w-100 text-truncate', {
|
||||||
className: privacyIconClass,
|
|
||||||
tooltipClass: 'pt-4 pb-5 px-5 mw-75',
|
|
||||||
xlinkHref: 'privacy',
|
|
||||||
contentItemClass: 'key-colon',
|
|
||||||
renderContent,
|
|
||||||
place: 'bottom',
|
|
||||||
});
|
|
||||||
|
|
||||||
const valueClass = classNames('w-100', {
|
|
||||||
'px-2 d-flex justify-content-center flex-column': isDetailed,
|
'px-2 d-flex justify-content-center flex-column': isDetailed,
|
||||||
});
|
});
|
||||||
|
|
||||||
const details = [ip, protocol].filter(Boolean)
|
const details = [ip, protocol].filter(Boolean)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
|
|
||||||
return (
|
return <div className="d-flex o-hidden logs__cell logs__cell logs__cell--domain" role="gridcell">
|
||||||
<div className="logs__row o-hidden">
|
{dnssec_enabled && <IconTooltip
|
||||||
{dnssec_enabled && getIconTooltip({
|
className={lockIconClass}
|
||||||
className: lockIconClass,
|
tooltipClass='py-4 px-5 pb-45'
|
||||||
tooltipClass: 'py-4 px-5 pb-45',
|
canShowTooltip={!!answer_dnssec}
|
||||||
canShowTooltip: answer_dnssec,
|
xlinkHref='lock'
|
||||||
xlinkHref: 'lock',
|
columnClass='w-100'
|
||||||
columnClass: 'w-100',
|
content='validated_with_dnssec'
|
||||||
content: 'validated_with_dnssec',
|
placement='bottom'
|
||||||
placement: 'bottom',
|
/>}
|
||||||
})}
|
<IconTooltip className={privacyIconClass} tooltipClass='pt-4 pb-5 px-5 mw-75'
|
||||||
{trackerHint}
|
xlinkHref='privacy' contentItemClass='key-colon' renderContent={renderContent}
|
||||||
|
place='bottom' />
|
||||||
<div className={valueClass}>
|
<div className={valueClass}>
|
||||||
<div className="text-truncate" title={domain}>{domain}</div>
|
<div className="text-truncate" title={domain}>{domain}</div>
|
||||||
{details && isDetailed
|
{details && isDetailed
|
||||||
&& <div className="detailed-info d-none d-sm-block text-truncate"
|
&& <div className="detailed-info d-none d-sm-block text-truncate"
|
||||||
title={details}>{details}</div>}
|
title={details}>{details}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getDomainCell.propTypes = {
|
DomainCell.propTypes = {
|
||||||
row: PropTypes.object.isRequired,
|
answer_dnssec: propTypes.bool.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
client_proto: propTypes.string.isRequired,
|
||||||
isDetailed: PropTypes.bool.isRequired,
|
domain: propTypes.string.isRequired,
|
||||||
toggleBlocking: PropTypes.func.isRequired,
|
time: propTypes.string.isRequired,
|
||||||
autoClients: PropTypes.array.isRequired,
|
type: propTypes.string.isRequired,
|
||||||
dnssec_enabled: PropTypes.bool.isRequired,
|
tracker: propTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getDomainCell;
|
export default DomainCell;
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import { toggleDetailedLogs } from '../../../actions/queryLogs';
|
||||||
|
import HeaderCell from './HeaderCell';
|
||||||
|
|
||||||
|
const Header = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||||
|
const disableDetailedMode = () => dispatch(toggleDetailedLogs(false));
|
||||||
|
const enableDetailedMode = () => dispatch(toggleDetailedLogs(true));
|
||||||
|
|
||||||
|
const HEADERS = [
|
||||||
|
{
|
||||||
|
className: 'logs__cell--date',
|
||||||
|
content: 'time_table_header',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'logs__cell--domain',
|
||||||
|
content: 'request_table_header',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'logs__cell--response',
|
||||||
|
content: 'response_table_header',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'logs__cell--client',
|
||||||
|
content: <>
|
||||||
|
{t('client_table_header')}
|
||||||
|
{<span>
|
||||||
|
<svg className={classNames('icons icon--24 icon--green cursor--pointer mr-2', { 'icon--selected': !isDetailed })}
|
||||||
|
onClick={disableDetailedMode}
|
||||||
|
>
|
||||||
|
<title>{t('compact')}</title>
|
||||||
|
<use xlinkHref='#list' /></svg>
|
||||||
|
<svg className={classNames('icons icon--24 icon--green cursor--pointer', { 'icon--selected': isDetailed })}
|
||||||
|
onClick={enableDetailedMode}
|
||||||
|
>
|
||||||
|
<title>{t('default')}</title>
|
||||||
|
<use xlinkHref='#detailed_list' />
|
||||||
|
</svg>
|
||||||
|
</span>}
|
||||||
|
</>,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return <div className="logs__cell--header__container px-5" role="row">
|
||||||
|
{HEADERS.map(HeaderCell)}
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
|
@ -0,0 +1,22 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import propTypes from 'prop-types';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const HeaderCell = ({ content, className }, idx) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return <div
|
||||||
|
key={idx}
|
||||||
|
className={classNames('logs__cell--header__item logs__cell logs__text--bold', className)}
|
||||||
|
role="columnheader"
|
||||||
|
>
|
||||||
|
{typeof content === 'string' ? t(content) : content}
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
HeaderCell.propTypes = {
|
||||||
|
content: propTypes.oneOfType([propTypes.string, propTypes.element]).isRequired,
|
||||||
|
className: propTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderCell;
|
|
@ -7,7 +7,7 @@ import Tooltip from '../../ui/Tooltip';
|
||||||
import 'react-popper-tooltip/dist/styles.css';
|
import 'react-popper-tooltip/dist/styles.css';
|
||||||
import './IconTooltip.css';
|
import './IconTooltip.css';
|
||||||
|
|
||||||
const getIconTooltip = ({
|
const IconTooltip = ({
|
||||||
className,
|
className,
|
||||||
contentItemClass,
|
contentItemClass,
|
||||||
columnClass,
|
columnClass,
|
||||||
|
@ -43,14 +43,14 @@ const getIconTooltip = ({
|
||||||
</Tooltip>;
|
</Tooltip>;
|
||||||
};
|
};
|
||||||
|
|
||||||
getIconTooltip.propTypes = {
|
IconTooltip.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
contentItemClass: PropTypes.string,
|
contentItemClass: PropTypes.string,
|
||||||
columnClass: PropTypes.string,
|
columnClass: PropTypes.string,
|
||||||
tooltipClass: PropTypes.string,
|
tooltipClass: PropTypes.string,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
placement: PropTypes.string,
|
placement: PropTypes.string,
|
||||||
canShowTooltip: PropTypes.string,
|
canShowTooltip: PropTypes.bool,
|
||||||
xlinkHref: PropTypes.string,
|
xlinkHref: PropTypes.string,
|
||||||
content: PropTypes.oneOfType([
|
content: PropTypes.oneOfType([
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
|
@ -59,4 +59,4 @@ getIconTooltip.propTypes = {
|
||||||
renderContent: PropTypes.arrayOf(PropTypes.element),
|
renderContent: PropTypes.arrayOf(PropTypes.element),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getIconTooltip;
|
export default IconTooltip;
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import propTypes from 'prop-types';
|
||||||
|
import { formatElapsedMs, getFilterName } from '../../../helpers/helpers';
|
||||||
|
import { FILTERED_STATUS, FILTERED_STATUS_TO_META_MAP } from '../../../helpers/constants';
|
||||||
|
import IconTooltip from './IconTooltip';
|
||||||
|
|
||||||
|
const ResponseCell = ({
|
||||||
|
elapsedMs,
|
||||||
|
originalResponse,
|
||||||
|
reason,
|
||||||
|
response,
|
||||||
|
status,
|
||||||
|
upstream,
|
||||||
|
rule,
|
||||||
|
filterId,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
|
||||||
|
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
|
||||||
|
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||||
|
|
||||||
|
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
||||||
|
|
||||||
|
const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|
||||||
|
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
|
||||||
|
|
||||||
|
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
|
||||||
|
|
||||||
|
const statusLabel = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.LABEL || reason);
|
||||||
|
const boldStatusLabel = <span className="font-weight-bold">{statusLabel}</span>;
|
||||||
|
const filter = getFilterName(filters, whitelistFilters, filterId);
|
||||||
|
|
||||||
|
const renderResponses = (responseArr) => {
|
||||||
|
if (!responseArr || responseArr.length === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>{responseArr.map((response) => {
|
||||||
|
const className = classNames('white-space--nowrap', {
|
||||||
|
'overflow-break': response.length > 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div key={response} className={className}>{`${response}\n`}</div>;
|
||||||
|
})}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const COMMON_CONTENT = {
|
||||||
|
encryption_status: boldStatusLabel,
|
||||||
|
install_settings_dns: upstream,
|
||||||
|
elapsed: formattedElapsedMs,
|
||||||
|
response_code: status,
|
||||||
|
filter,
|
||||||
|
rule_label: rule,
|
||||||
|
response_table_header: renderResponses(response),
|
||||||
|
original_response: renderResponses(originalResponse),
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = rule
|
||||||
|
? Object.entries(COMMON_CONTENT)
|
||||||
|
: Object.entries({
|
||||||
|
...COMMON_CONTENT,
|
||||||
|
filter: '',
|
||||||
|
});
|
||||||
|
const detailedInfo = isBlocked ? filter : formattedElapsedMs;
|
||||||
|
|
||||||
|
|
||||||
|
return <div className="logs__cell logs__cell--response" role="gridcell">
|
||||||
|
<IconTooltip
|
||||||
|
className={classNames('icons mr-4 icon--24 icon--lightgray', { 'my-3': isDetailed })}
|
||||||
|
columnClass='grid grid--limited'
|
||||||
|
tooltipClass='px-5 pb-5 pt-4 mw-75 custom-tooltip__response-details'
|
||||||
|
contentItemClass='text-truncate key-colon o-hidden'
|
||||||
|
xlinkHref='question'
|
||||||
|
title='response_details'
|
||||||
|
content={content}
|
||||||
|
placement='bottom'
|
||||||
|
/>
|
||||||
|
<div className="text-truncate">
|
||||||
|
<div className="text-truncate" title={statusLabel}>{statusLabel}</div>
|
||||||
|
{isDetailed && <div
|
||||||
|
className="detailed-info d-none d-sm-block pt-1 text-truncate"
|
||||||
|
title={detailedInfo}>{detailedInfo}</div>}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
ResponseCell.propTypes = {
|
||||||
|
elapsedMs: propTypes.string.isRequired,
|
||||||
|
originalResponse: propTypes.array.isRequired,
|
||||||
|
reason: propTypes.string.isRequired,
|
||||||
|
response: propTypes.array.isRequired,
|
||||||
|
status: propTypes.string.isRequired,
|
||||||
|
upstream: propTypes.string.isRequired,
|
||||||
|
rule: propTypes.string,
|
||||||
|
filterId: propTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResponseCell;
|
|
@ -1,110 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { formatClientCell } from '../../../helpers/formatClientCell';
|
|
||||||
import getIconTooltip from './getIconTooltip';
|
|
||||||
import { checkFiltered } from '../../../helpers/helpers';
|
|
||||||
import { BLOCK_ACTIONS } from '../../../helpers/constants';
|
|
||||||
|
|
||||||
const getClientCell = ({
|
|
||||||
row, t, isDetailed, toggleBlocking, autoClients, processingRules,
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
reason, client, domain, info: { name, whois_info },
|
|
||||||
} = row.original;
|
|
||||||
|
|
||||||
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
|
|
||||||
const source = autoClient?.source;
|
|
||||||
const whoisAvailable = whois_info && Object.keys(whois_info).length > 0;
|
|
||||||
|
|
||||||
const id = nanoid();
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
address: client,
|
|
||||||
name,
|
|
||||||
country: whois_info?.country,
|
|
||||||
city: whois_info?.city,
|
|
||||||
network: whois_info?.orgname,
|
|
||||||
source_label: source,
|
|
||||||
};
|
|
||||||
|
|
||||||
const processedData = Object.entries(data);
|
|
||||||
|
|
||||||
const isFiltered = checkFiltered(reason);
|
|
||||||
|
|
||||||
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
|
|
||||||
'mt-2': isDetailed && !name && !whoisAvailable,
|
|
||||||
'white-space--nowrap': isDetailed,
|
|
||||||
});
|
|
||||||
|
|
||||||
const hintClass = classNames('icons mr-4 icon--24 icon--lightgray', {
|
|
||||||
'my-3': isDetailed,
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderBlockingButton = (isFiltered, domain) => {
|
|
||||||
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
|
||||||
|
|
||||||
const buttonClass = classNames('logs__action button__action', {
|
|
||||||
'btn-outline-secondary': isFiltered,
|
|
||||||
'btn-outline-danger': !isFiltered,
|
|
||||||
'logs__action--detailed': isDetailed,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onClick = () => toggleBlocking(buttonType, domain);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={buttonClass}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`btn btn-sm ${buttonClass}`}
|
|
||||||
onClick={onClick}
|
|
||||||
disabled={processingRules}
|
|
||||||
>
|
|
||||||
{t(buttonType)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="logs__row o-hidden h-100">
|
|
||||||
{getIconTooltip({
|
|
||||||
className: hintClass,
|
|
||||||
columnClass: 'grid grid--limited',
|
|
||||||
tooltipClass: 'px-5 pb-5 pt-4 mw-75',
|
|
||||||
xlinkHref: 'question',
|
|
||||||
contentItemClass: 'text-truncate key-colon',
|
|
||||||
title: 'client_details',
|
|
||||||
content: processedData,
|
|
||||||
placement: 'bottom',
|
|
||||||
})}
|
|
||||||
<div className={nameClass}>
|
|
||||||
<div data-tip={true} data-for={id}>
|
|
||||||
{formatClientCell(row, isDetailed)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isDetailed && name && !whoisAvailable && (
|
|
||||||
<div
|
|
||||||
className="detailed-info d-none d-sm-block logs__text"
|
|
||||||
title={name}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{renderBlockingButton(isFiltered, domain)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
getClientCell.propTypes = {
|
|
||||||
row: PropTypes.object.isRequired,
|
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
isDetailed: PropTypes.bool.isRequired,
|
|
||||||
toggleBlocking: PropTypes.func.isRequired,
|
|
||||||
autoClients: PropTypes.array.isRequired,
|
|
||||||
processingRules: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getClientCell;
|
|
|
@ -1,28 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { formatTime, formatDateTime } from '../../../helpers/helpers';
|
|
||||||
import {
|
|
||||||
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
|
||||||
DEFAULT_TIME_FORMAT,
|
|
||||||
} from '../../../helpers/constants';
|
|
||||||
|
|
||||||
const getDateCell = (row, isDetailed) => {
|
|
||||||
const { time } = row.original;
|
|
||||||
|
|
||||||
if (!time) {
|
|
||||||
return '–';
|
|
||||||
}
|
|
||||||
|
|
||||||
const formattedTime = formatTime(time, DEFAULT_TIME_FORMAT);
|
|
||||||
const formattedDate = formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="logs__cell">
|
|
||||||
<div className="logs__time" title={formattedTime}>{formattedTime}</div>
|
|
||||||
{isDetailed && <div className="detailed-info d-none d-sm-block text-truncate"
|
|
||||||
title={formattedDate}>{formattedDate}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getDateCell;
|
|
|
@ -1,79 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { formatElapsedMs } from '../../../helpers/helpers';
|
|
||||||
import {
|
|
||||||
FILTERED_STATUS,
|
|
||||||
FILTERED_STATUS_TO_META_MAP,
|
|
||||||
} from '../../../helpers/constants';
|
|
||||||
import getIconTooltip from './getIconTooltip';
|
|
||||||
|
|
||||||
const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
|
||||||
const {
|
|
||||||
reason, filterId, rule, status, upstream, elapsedMs, response, originalResponse,
|
|
||||||
} = row.original;
|
|
||||||
|
|
||||||
const { filters, whitelistFilters } = filtering;
|
|
||||||
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
|
||||||
|
|
||||||
const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|
|
||||||
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
|
|
||||||
|
|
||||||
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
|
|
||||||
|
|
||||||
const statusLabel = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.label || reason);
|
|
||||||
const boldStatusLabel = <span className="font-weight-bold">{statusLabel}</span>;
|
|
||||||
const filter = getFilterName(filters, whitelistFilters, filterId, t);
|
|
||||||
|
|
||||||
const renderResponses = (responseArr) => {
|
|
||||||
if (responseArr?.length === 0) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div>{responseArr.map((response) => {
|
|
||||||
const className = classNames('white-space--nowrap', {
|
|
||||||
'overflow-break': response.length > 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
return <div key={response} className={className}>{`${response}\n`}</div>;
|
|
||||||
})}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const COMMON_CONTENT = {
|
|
||||||
encryption_status: boldStatusLabel,
|
|
||||||
install_settings_dns: upstream,
|
|
||||||
elapsed: formattedElapsedMs,
|
|
||||||
response_code: status,
|
|
||||||
filter,
|
|
||||||
rule_label: rule,
|
|
||||||
response_table_header: renderResponses(response),
|
|
||||||
original_response: renderResponses(originalResponse),
|
|
||||||
};
|
|
||||||
|
|
||||||
const content = rule
|
|
||||||
? Object.entries(COMMON_CONTENT)
|
|
||||||
: Object.entries({ ...COMMON_CONTENT, filter: '' });
|
|
||||||
const detailedInfo = isBlocked ? filter : formattedElapsedMs;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="logs__row">
|
|
||||||
{getIconTooltip({
|
|
||||||
className: classNames('icons mr-4 icon--24 icon--lightgray', { 'my-3': isDetailed }),
|
|
||||||
columnClass: 'grid grid--limited',
|
|
||||||
tooltipClass: 'px-5 pb-5 pt-4 mw-75 custom-tooltip__response-details',
|
|
||||||
contentItemClass: 'text-truncate key-colon o-hidden',
|
|
||||||
xlinkHref: 'question',
|
|
||||||
title: 'response_details',
|
|
||||||
content,
|
|
||||||
placement: 'bottom',
|
|
||||||
})}
|
|
||||||
<div className="text-truncate">
|
|
||||||
<div className="text-truncate" title={statusLabel}>{statusLabel}</div>
|
|
||||||
{isDetailed && <div
|
|
||||||
className="detailed-info d-none d-sm-block pt-1 text-truncate"
|
|
||||||
title={detailedInfo}>{detailedInfo}</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getResponseCell;
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
import propTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
captitalizeWords,
|
||||||
|
checkFiltered,
|
||||||
|
formatDateTime,
|
||||||
|
formatElapsedMs,
|
||||||
|
formatTime,
|
||||||
|
getFilterName,
|
||||||
|
processContent,
|
||||||
|
} from '../../../helpers/helpers';
|
||||||
|
import {
|
||||||
|
BLOCK_ACTIONS,
|
||||||
|
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
||||||
|
FILTERED_STATUS,
|
||||||
|
FILTERED_STATUS_TO_META_MAP,
|
||||||
|
LONG_TIME_FORMAT,
|
||||||
|
QUERY_STATUS_COLORS,
|
||||||
|
SCHEME_TO_PROTOCOL_MAP,
|
||||||
|
} from '../../../helpers/constants';
|
||||||
|
import { getSourceData } from '../../../helpers/trackers/trackers';
|
||||||
|
import { toggleBlocking } from '../../../actions';
|
||||||
|
import DateCell from './DateCell';
|
||||||
|
import DomainCell from './DomainCell';
|
||||||
|
import ResponseCell from './ResponseCell';
|
||||||
|
import ClientCell from './ClientCell';
|
||||||
|
import '../Logs.css';
|
||||||
|
|
||||||
|
const Row = memo(({
|
||||||
|
style,
|
||||||
|
rowProps,
|
||||||
|
rowProps: { reason },
|
||||||
|
isSmallScreen,
|
||||||
|
setDetailedDataCurrent,
|
||||||
|
setButtonType,
|
||||||
|
setModalOpened,
|
||||||
|
}) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dnssec_enabled = useSelector((state) => state.dnsConfig.dnssec_enabled);
|
||||||
|
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
|
||||||
|
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
|
||||||
|
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
if (!isSmallScreen) { return; }
|
||||||
|
const {
|
||||||
|
answer_dnssec,
|
||||||
|
client,
|
||||||
|
domain,
|
||||||
|
elapsedMs,
|
||||||
|
info,
|
||||||
|
reason,
|
||||||
|
response,
|
||||||
|
time,
|
||||||
|
tracker,
|
||||||
|
upstream,
|
||||||
|
type,
|
||||||
|
client_proto,
|
||||||
|
filterId,
|
||||||
|
rule,
|
||||||
|
originalResponse,
|
||||||
|
status,
|
||||||
|
} = rowProps;
|
||||||
|
|
||||||
|
const hasTracker = !!tracker;
|
||||||
|
|
||||||
|
const autoClient = autoClients
|
||||||
|
.find((autoClient) => autoClient.name === client);
|
||||||
|
|
||||||
|
const { whois_info } = info;
|
||||||
|
const country = whois_info?.country;
|
||||||
|
const city = whois_info?.city;
|
||||||
|
const network = whois_info?.orgname;
|
||||||
|
|
||||||
|
const source = autoClient?.source;
|
||||||
|
|
||||||
|
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
||||||
|
const isFiltered = checkFiltered(reason);
|
||||||
|
|
||||||
|
const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|
||||||
|
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
|
||||||
|
|
||||||
|
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
||||||
|
const onToggleBlock = () => {
|
||||||
|
dispatch(toggleBlocking(buttonType, domain));
|
||||||
|
};
|
||||||
|
|
||||||
|
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
|
||||||
|
const requestStatus = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.LABEL || reason);
|
||||||
|
|
||||||
|
const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
|
||||||
|
|
||||||
|
const sourceData = getSourceData(tracker);
|
||||||
|
|
||||||
|
const filter = getFilterName(filters, whitelistFilters, filterId);
|
||||||
|
|
||||||
|
const detailedData = {
|
||||||
|
time_table_header: formatTime(time, LONG_TIME_FORMAT),
|
||||||
|
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
|
||||||
|
encryption_status: isBlocked
|
||||||
|
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
|
||||||
|
domain,
|
||||||
|
type_table_header: type,
|
||||||
|
protocol,
|
||||||
|
known_tracker: hasTracker && 'title',
|
||||||
|
table_name: tracker?.name,
|
||||||
|
category_label: hasTracker && captitalizeWords(tracker.category),
|
||||||
|
tracker_source: hasTracker && sourceData
|
||||||
|
&& <a
|
||||||
|
href={sourceData.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="link--green">{sourceData.name}
|
||||||
|
</a>,
|
||||||
|
response_details: 'title',
|
||||||
|
install_settings_dns: upstream,
|
||||||
|
elapsed: formattedElapsedMs,
|
||||||
|
filter: rule ? filter : null,
|
||||||
|
rule_label: rule,
|
||||||
|
response_table_header: response?.join('\n'),
|
||||||
|
response_code: status,
|
||||||
|
client_details: 'title',
|
||||||
|
ip_address: client,
|
||||||
|
name: info?.name,
|
||||||
|
country,
|
||||||
|
city,
|
||||||
|
network,
|
||||||
|
source_label: source,
|
||||||
|
validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
|
||||||
|
original_response: originalResponse?.join('\n'),
|
||||||
|
[buttonType]: <div onClick={onToggleBlock}
|
||||||
|
className={classNames('title--border text-center', {
|
||||||
|
'bg--danger': isBlocked,
|
||||||
|
})}>{t(buttonType)}</div>,
|
||||||
|
};
|
||||||
|
|
||||||
|
setDetailedDataCurrent(processContent(detailedData));
|
||||||
|
setButtonType(buttonType);
|
||||||
|
setModalOpened(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||||
|
|
||||||
|
const className = classNames('d-flex px-5 logs__row',
|
||||||
|
`logs__row--${FILTERED_STATUS_TO_META_MAP?.[reason]?.COLOR ?? QUERY_STATUS_COLORS.WHITE}`, {
|
||||||
|
'logs__cell--detailed': isDetailed,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div style={style} className={className} onClick={onClick} role="row">
|
||||||
|
<DateCell {...rowProps} />
|
||||||
|
<DomainCell {...rowProps} />
|
||||||
|
<ResponseCell {...rowProps} />
|
||||||
|
<ClientCell {...rowProps} />
|
||||||
|
</div>;
|
||||||
|
});
|
||||||
|
|
||||||
|
Row.displayName = 'Row';
|
||||||
|
|
||||||
|
Row.propTypes = {
|
||||||
|
style: propTypes.object,
|
||||||
|
rowProps: propTypes.shape({
|
||||||
|
reason: propTypes.string.isRequired,
|
||||||
|
answer_dnssec: propTypes.bool.isRequired,
|
||||||
|
client: propTypes.string.isRequired,
|
||||||
|
domain: propTypes.string.isRequired,
|
||||||
|
elapsedMs: propTypes.string.isRequired,
|
||||||
|
info: propTypes.oneOfType([
|
||||||
|
propTypes.string,
|
||||||
|
propTypes.shape({
|
||||||
|
whois_info: propTypes.shape({
|
||||||
|
country: propTypes.string,
|
||||||
|
city: propTypes.string,
|
||||||
|
orgname: propTypes.string,
|
||||||
|
}),
|
||||||
|
})]),
|
||||||
|
response: propTypes.array.isRequired,
|
||||||
|
time: propTypes.string.isRequired,
|
||||||
|
tracker: propTypes.object,
|
||||||
|
upstream: propTypes.string.isRequired,
|
||||||
|
type: propTypes.string.isRequired,
|
||||||
|
client_proto: propTypes.string.isRequired,
|
||||||
|
filterId: propTypes.number,
|
||||||
|
rule: propTypes.string,
|
||||||
|
originalResponse: propTypes.array,
|
||||||
|
status: propTypes.string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
isSmallScreen: propTypes.bool.isRequired,
|
||||||
|
setDetailedDataCurrent: propTypes.func.isRequired,
|
||||||
|
setButtonType: propTypes.func.isRequired,
|
||||||
|
setModalOpened: propTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Row;
|
|
@ -107,7 +107,7 @@ const Form = (props) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
response_status, search,
|
response_status, search,
|
||||||
} = useSelector((state) => state.form[FORM_NAME.LOGS_FILTER].values, shallowEqual);
|
} = useSelector((state) => state?.form[FORM_NAME.LOGS_FILTER].values, shallowEqual);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
debouncedSearch,
|
debouncedSearch,
|
||||||
|
@ -171,14 +171,14 @@ const Form = (props) => {
|
||||||
>
|
>
|
||||||
{Object.values(RESPONSE_FILTER)
|
{Object.values(RESPONSE_FILTER)
|
||||||
.map(({
|
.map(({
|
||||||
query, label, disabled,
|
QUERY, LABEL, disabled,
|
||||||
}) => (
|
}) => (
|
||||||
<option
|
<option
|
||||||
key={label}
|
key={LABEL}
|
||||||
value={query}
|
value={QUERY}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{t(label)}
|
{t(LABEL)}
|
||||||
</option>
|
</option>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -197,5 +197,4 @@ Form.propTypes = {
|
||||||
|
|
||||||
export default reduxForm({
|
export default reduxForm({
|
||||||
form: FORM_NAME.LOGS_FILTER,
|
form: FORM_NAME.LOGS_FILTER,
|
||||||
enableReinitialize: true,
|
|
||||||
})(Form);
|
})(Form);
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
|
import { refreshFilteredLogs } from '../../../actions/queryLogs';
|
||||||
|
import { addSuccessToast } from '../../../actions/toasts';
|
||||||
|
|
||||||
const Filters = ({ filter, refreshLogs, setIsLoading }) => {
|
const Filters = ({ filter, setIsLoading }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const refreshLogs = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
await dispatch(refreshFilteredLogs());
|
||||||
|
dispatch(addSuccessToast('query_log_updated'));
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
return <div className="page-header page-header--logs">
|
return <div className="page-header page-header--logs">
|
||||||
<h1 className="page-title page-title--large">
|
<h1 className="page-title page-title--large">
|
||||||
|
@ -29,7 +40,6 @@ const Filters = ({ filter, refreshLogs, setIsLoading }) => {
|
||||||
|
|
||||||
Filters.propTypes = {
|
Filters.propTypes = {
|
||||||
filter: PropTypes.object.isRequired,
|
filter: PropTypes.object.isRequired,
|
||||||
refreshLogs: PropTypes.func.isRequired,
|
|
||||||
processingGetLogs: PropTypes.bool.isRequired,
|
processingGetLogs: PropTypes.bool.isRequired,
|
||||||
setIsLoading: PropTypes.func.isRequired,
|
setIsLoading: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import propTypes from 'prop-types';
|
||||||
|
import throttle from 'lodash/throttle';
|
||||||
|
import Loading from '../ui/Loading';
|
||||||
|
import Header from './Cells/Header';
|
||||||
|
import { getLogs } from '../../actions/queryLogs';
|
||||||
|
import Row from './Cells';
|
||||||
|
import { isScrolledIntoView } from '../../helpers/helpers';
|
||||||
|
import { QUERY_LOGS_PAGE_LIMIT } from '../../helpers/constants';
|
||||||
|
|
||||||
|
const InfiniteTable = ({
|
||||||
|
isLoading,
|
||||||
|
items,
|
||||||
|
isSmallScreen,
|
||||||
|
setDetailedDataCurrent,
|
||||||
|
setButtonType,
|
||||||
|
setModalOpened,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const loader = useRef(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isEntireLog,
|
||||||
|
processingGetLogs,
|
||||||
|
} = useSelector((state) => state.queryLogs, shallowEqual);
|
||||||
|
|
||||||
|
const loading = isLoading || processingGetLogs;
|
||||||
|
|
||||||
|
const listener = useCallback(() => {
|
||||||
|
if (loader.current && isScrolledIntoView(loader.current)) {
|
||||||
|
dispatch(getLogs());
|
||||||
|
}
|
||||||
|
}, [loader.current, isScrolledIntoView, getLogs]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
listener();
|
||||||
|
}, [items.length < QUERY_LOGS_PAGE_LIMIT]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const THROTTLE_TIME = 100;
|
||||||
|
const throttledListener = throttle(listener, THROTTLE_TIME);
|
||||||
|
|
||||||
|
window.addEventListener('scroll', throttledListener);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', throttledListener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderRow = (row, idx) => <Row
|
||||||
|
key={idx}
|
||||||
|
rowProps={row}
|
||||||
|
isSmallScreen={isSmallScreen}
|
||||||
|
setDetailedDataCurrent={setDetailedDataCurrent}
|
||||||
|
setButtonType={setButtonType}
|
||||||
|
setModalOpened={setModalOpened}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
const isNothingFound = items.length === 0 && !processingGetLogs;
|
||||||
|
|
||||||
|
return <div className='logs__table' role='grid'>
|
||||||
|
{loading && <Loading />}
|
||||||
|
<Header />
|
||||||
|
{isNothingFound
|
||||||
|
? <label className="logs__no-data">{t('nothing_found')}</label>
|
||||||
|
: <>{items.map(renderRow)}
|
||||||
|
{!isEntireLog && <div ref={loader} className="logs__loading text-center">{t('loading_table_status')}</div>}
|
||||||
|
</>}
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
InfiniteTable.propTypes = {
|
||||||
|
isLoading: propTypes.bool.isRequired,
|
||||||
|
items: propTypes.array.isRequired,
|
||||||
|
isSmallScreen: propTypes.bool.isRequired,
|
||||||
|
setDetailedDataCurrent: propTypes.func.isRequired,
|
||||||
|
setButtonType: propTypes.func.isRequired,
|
||||||
|
setModalOpened: propTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InfiniteTable;
|
|
@ -1,44 +1,21 @@
|
||||||
:root {
|
:root {
|
||||||
|
--blue: #e5effd;
|
||||||
|
--green-pale: rgba(103, 178, 121, 0.1);
|
||||||
|
--red: rgba(223, 56, 18, 0.05);
|
||||||
|
--white: #fff;
|
||||||
|
--yellow: rgba(247, 181, 0, 0.1);
|
||||||
|
--size-date: 70;
|
||||||
|
--size-domain: 180;
|
||||||
|
--size-response: 150;
|
||||||
|
--size-client: 123;
|
||||||
|
--gray-216: rgba(216, 216, 216, 0.23);
|
||||||
--gray-4d: #4D4D4D;
|
--gray-4d: #4D4D4D;
|
||||||
--gray-8: #888;
|
--gray-8: #888;
|
||||||
--danger: #DF3812;
|
--danger: #DF3812;
|
||||||
|
--white80: rgba(255, 255, 255, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__row {
|
.logs__text {
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
min-height: 26px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-table .logs__row {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__row--center {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__row--column {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__row--icons {
|
|
||||||
max-width: 180px;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__row .list-unstyled {
|
|
||||||
margin-bottom: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__text,
|
|
||||||
.logs__row .list-unstyled li {
|
|
||||||
padding: 0 1px;
|
padding: 0 1px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -54,237 +31,6 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__text--full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__text--wrap {
|
|
||||||
line-height: 1.4;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__text--nowrap {
|
|
||||||
line-height: 1.4;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__text--whois {
|
|
||||||
line-height: 1.2;
|
|
||||||
color: #9aa0ac;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__row .tooltip-custom {
|
|
||||||
top: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip__option {
|
|
||||||
height: 2.5rem !important;
|
|
||||||
width: 10.5rem;
|
|
||||||
padding: 0.3125rem 1.5rem 0.6875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip__option:hover {
|
|
||||||
background-color: var(--gray-f3);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button__action {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: opacity 0.2s ease, visibility 0.2s ease;
|
|
||||||
visibility: hidden;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table__action {
|
|
||||||
position: absolute;
|
|
||||||
top: 11px;
|
|
||||||
right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__action {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__action--detailed {
|
|
||||||
top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-td,
|
|
||||||
.clients__table .rt-td {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-thead, .logs__table .rt-tbody {
|
|
||||||
min-width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tr:hover .logs__action,
|
|
||||||
.clients__table .rt-tr:hover .table__action {
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tr-group:first-child .tooltip-custom:before {
|
|
||||||
top: calc(100% + 12px);
|
|
||||||
bottom: initial;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tr-group:first-child .tooltip-custom:after {
|
|
||||||
top: initial;
|
|
||||||
bottom: -4px;
|
|
||||||
border-top: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid #585965;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tr-group:first-child .popover__body {
|
|
||||||
top: calc(100% + 5px);
|
|
||||||
bottom: initial;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tr-group:first-child .popover__body:after {
|
|
||||||
top: -11px;
|
|
||||||
border-top: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid #585965;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-thead.-filters input,
|
|
||||||
.logs__table .rt-thead.-filters select {
|
|
||||||
padding: 6px 7px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 0.9375rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #495057;
|
|
||||||
border: 1px solid rgba(0, 40, 100, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-thead.-filters select {
|
|
||||||
background: #fff url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMCA1Jz48cGF0aCBmaWxsPScjOTk5JyBkPSdNMCAwTDEwIDBMNSA1TDAgMCcvPjwvc3ZnPg==") no-repeat right 0.75rem center;
|
|
||||||
background-size: 8px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-thead.-filters input:focus,
|
|
||||||
.logs__table .rt-thead.-filters select:focus {
|
|
||||||
border-color: #1991eb;
|
|
||||||
box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__text-wrap {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__list-wrap {
|
|
||||||
display: flex;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__list-item {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__input-wrap {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__whois {
|
|
||||||
display: inline;
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__whois::after {
|
|
||||||
content: "|";
|
|
||||||
padding: 0 5px;
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__whois:last-child::after {
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__whois-icon.icons {
|
|
||||||
position: relative;
|
|
||||||
top: -2px;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
margin-right: 1px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* New logs */
|
|
||||||
.logs__table {
|
|
||||||
background-color: #fff;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
min-height: 42rem;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table--detailed {
|
|
||||||
min-height: 50rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-thead.-header {
|
|
||||||
box-shadow: none;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-thead .rt-th {
|
|
||||||
padding: 0.9375rem 0.9375rem 0.875rem 0;
|
|
||||||
text-align: left;
|
|
||||||
border-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tbody .rt-td {
|
|
||||||
padding: 1rem 1rem 0.5rem 0;
|
|
||||||
border-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-thead .rt-th:last-child,
|
|
||||||
.logs__table .rt-tbody .rt-td:last-child {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tbody .rt-tr-group {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tr {
|
|
||||||
position: relative;
|
|
||||||
padding: 0 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tr {
|
|
||||||
position: relative;
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tr-group:not(:first-child) .rt-tr:before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
left: 1.5rem;
|
|
||||||
right: 1.5rem;
|
|
||||||
top: 0;
|
|
||||||
width: calc(100% - 3rem);
|
|
||||||
height: 2px;
|
|
||||||
background-color: rgba(216, 216, 216, 0.23);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .rt-tr-group:last-child .rt-tr:after,
|
|
||||||
.logs__table .rt-thead .rt-tr:after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__time {
|
.logs__time {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
@ -302,132 +48,24 @@
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide 3 and 4 column on mobile */
|
|
||||||
.logs__table .rt-thead .rt-th:nth-child(3),
|
|
||||||
.logs__table .rt-thead .rt-th:nth-child(4),
|
|
||||||
.logs__table .rt-tbody .rt-td:nth-child(3),
|
|
||||||
.logs__table .rt-tbody .rt-td:nth-child(4) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.logs__table .rt-thead .rt-th:nth-child(3),
|
|
||||||
.logs__table .rt-thead .rt-th:nth-child(4),
|
|
||||||
.logs__table .rt-tbody .rt-td:nth-child(3),
|
|
||||||
.logs__table .rt-tbody .rt-td:nth-child(4) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-pre {
|
.text-pre {
|
||||||
white-space: pre-wrap !important;
|
white-space: pre-wrap !important;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-pagination {
|
|
||||||
width: 11.875rem !important;
|
|
||||||
background-color: transparent;
|
|
||||||
box-shadow: none !important;
|
|
||||||
border: none !important;
|
|
||||||
align-items: center !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-pagination--padding {
|
|
||||||
padding: 2.5rem 0 2.5rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-pagination .-btn {
|
|
||||||
--side-size: 2rem;
|
|
||||||
background-color: transparent !important;
|
|
||||||
border: 1px solid var(--gray-d8) !important;
|
|
||||||
border-radius: 4px !important;
|
|
||||||
width: var(--side-size) !important;
|
|
||||||
height: var(--side-size) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-pagination .-btn:enabled:hover {
|
|
||||||
background-color: var(--gray-f3) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-pagination .-previous {
|
|
||||||
flex: 0 1 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-pagination .-next {
|
|
||||||
flex: 0 1 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-pagination .-btn {
|
|
||||||
display: flex !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .-pageInfo {
|
|
||||||
--side-size: 2rem;
|
|
||||||
font-variant-numeric: tabular-nums !important;
|
|
||||||
background-color: transparent !important;
|
|
||||||
border: 1px solid var(--gray-d8) !important;
|
|
||||||
border-radius: 4px !important;
|
|
||||||
width: var(--side-size) !important;
|
|
||||||
height: var(--side-size) !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
display: flex !important;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .pagination-bottom {
|
|
||||||
justify-content: center !important;
|
|
||||||
display: flex !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .-center:before {
|
|
||||||
content: '...';
|
|
||||||
transform: translateY(-0.25rem);
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table .-center:after {
|
|
||||||
content: '...';
|
|
||||||
transform: translateY(-0.25rem);
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon--detailed-info {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link--green {
|
.link--green {
|
||||||
color: var(--green79);
|
color: var(--green79);
|
||||||
}
|
}
|
||||||
|
|
||||||
.row--detailed {
|
|
||||||
height: 4.9rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-90 {
|
.w-90 {
|
||||||
max-width: 90% !important;
|
max-width: 90% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-85 {
|
|
||||||
height: 85% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pt-45 {
|
|
||||||
padding-top: 1.25rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pb-45 {
|
.pb-45 {
|
||||||
padding-bottom: 1.25rem !important;
|
padding-bottom: 1.25rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.py-45 {
|
|
||||||
padding-top: 1.25rem !important;
|
|
||||||
padding-bottom: 1.25rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mh-100 {
|
.mh-100 {
|
||||||
max-height: 100% !important;
|
max-height: 100% !important;
|
||||||
}
|
}
|
||||||
|
@ -493,14 +131,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
.rt-tr .logs__row .logs__text {
|
|
||||||
max-width: calc(100% - 1.5rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-small {
|
|
||||||
margin-left: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control--container {
|
.form-control--container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -517,38 +147,157 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 575px) {
|
@media screen and (max-width: 767.98px) {
|
||||||
.logs__table .rt-tr {
|
.logs__table .logs__cell--response,
|
||||||
height: 3.125rem;
|
.logs__table .logs__cell--client {
|
||||||
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__table .rt-tbody .rt-td {
|
|
||||||
padding: 0.625rem 1rem 0.875rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__table {
|
|
||||||
min-height: 42rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading__container > .-loading-inner {
|
|
||||||
top: 10rem !important;
|
|
||||||
bottom: initial !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading__text {
|
|
||||||
transform: translateY(3rem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__refresh {
|
.logs__refresh {
|
||||||
|
--size: 2.5rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 40px;
|
width: var(--size);
|
||||||
height: 40px;
|
height: var(--size);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-left: 15px;
|
margin-left: 0.9375rem;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs__cell {
|
||||||
|
padding: 1rem 1rem 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__cell--date {
|
||||||
|
width: 4.375rem;
|
||||||
|
flex: var(--size-date) 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__cell--domain {
|
||||||
|
width: 11.25rem;
|
||||||
|
flex: var(--size-domain) 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__cell--response {
|
||||||
|
width: 9.375rem;
|
||||||
|
flex: var(--size-response) 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__cell--client {
|
||||||
|
width: 7.6875rem;
|
||||||
|
flex: var(--size-client) 0 auto;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__cell--header__container > .logs__cell--header__item {
|
||||||
|
border-right: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__cell--header__container > .logs__cell--header__item:last-child {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__cell--block-button {
|
||||||
|
max-height: 1.75rem;
|
||||||
|
position: relative;
|
||||||
|
left: 10%;
|
||||||
|
top: 40%;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__row {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
min-height: 26px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table .logs__row {
|
||||||
|
border-bottom: 2px solid var(--gray-216);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table .logs__row:hover .logs__cell--block-button {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table .logs__row .logs__cell--block-button:disabled {
|
||||||
|
background-color: var(--white) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* QUERY_STATUS_COLORS */
|
||||||
|
.logs__row--blue {
|
||||||
|
background-color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__row--green {
|
||||||
|
background-color: var(--green-pale);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__row--red {
|
||||||
|
background-color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__row--white {
|
||||||
|
background-color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__row--yellow {
|
||||||
|
background-color: var(--yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__no-data {
|
||||||
|
color: var(--gray-4d);
|
||||||
|
background-color: var(--white80);
|
||||||
|
pointer-events: none;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 21rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__loading {
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table {
|
||||||
|
background-color: var(--white);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
min-height: 43rem;
|
||||||
|
max-width: 100%;
|
||||||
|
align-items: stretch;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
contain: layout;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
will-change: scroll-position;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table .logs__cell--response,
|
||||||
|
.logs__table .logs__cell--client {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__cell--header__container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table > .logs__cell--header__container > .logs__cell--client {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table .loading:after {
|
||||||
|
top: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table .loading:before {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
|
@ -1,414 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useTranslation, Trans } from 'react-i18next';
|
|
||||||
import ReactTable from 'react-table';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import endsWith from 'lodash/endsWith';
|
|
||||||
import escapeRegExp from 'lodash/escapeRegExp';
|
|
||||||
import {
|
|
||||||
BLOCK_ACTIONS,
|
|
||||||
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
|
||||||
LONG_TIME_FORMAT,
|
|
||||||
FILTERED_STATUS_TO_META_MAP,
|
|
||||||
TABLE_DEFAULT_PAGE_SIZE,
|
|
||||||
SCHEME_TO_PROTOCOL_MAP,
|
|
||||||
CUSTOM_FILTERING_RULES_ID, FILTERED_STATUS,
|
|
||||||
} from '../../helpers/constants';
|
|
||||||
import getDateCell from './Cells/getDateCell';
|
|
||||||
import getDomainCell from './Cells/getDomainCell';
|
|
||||||
import getClientCell from './Cells/getClientCell';
|
|
||||||
import getResponseCell from './Cells/getResponseCell';
|
|
||||||
|
|
||||||
import {
|
|
||||||
captitalizeWords,
|
|
||||||
checkFiltered,
|
|
||||||
formatDateTime,
|
|
||||||
formatElapsedMs,
|
|
||||||
formatTime,
|
|
||||||
processContent,
|
|
||||||
} from '../../helpers/helpers';
|
|
||||||
import Loading from '../ui/Loading';
|
|
||||||
import { getSourceData } from '../../helpers/trackers/trackers';
|
|
||||||
|
|
||||||
const Table = (props) => {
|
|
||||||
const {
|
|
||||||
setDetailedDataCurrent,
|
|
||||||
setButtonType,
|
|
||||||
setModalOpened,
|
|
||||||
isSmallScreen,
|
|
||||||
setIsLoading,
|
|
||||||
filtering,
|
|
||||||
isDetailed,
|
|
||||||
toggleDetailedLogs,
|
|
||||||
setLogsPage,
|
|
||||||
setLogsPagination,
|
|
||||||
processingGetLogs,
|
|
||||||
logs,
|
|
||||||
pages,
|
|
||||||
page,
|
|
||||||
isLoading,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const toggleBlocking = (type, domain) => {
|
|
||||||
const {
|
|
||||||
setRules, getFilteringStatus, addSuccessToast,
|
|
||||||
} = props;
|
|
||||||
const { userRules } = filtering;
|
|
||||||
|
|
||||||
const lineEnding = !endsWith(userRules, '\n') ? '\n' : '';
|
|
||||||
const baseRule = `||${domain}^$important`;
|
|
||||||
const baseUnblocking = `@@${baseRule}`;
|
|
||||||
|
|
||||||
const blockingRule = type === BLOCK_ACTIONS.BLOCK ? baseUnblocking : baseRule;
|
|
||||||
const unblockingRule = type === BLOCK_ACTIONS.BLOCK ? baseRule : baseUnblocking;
|
|
||||||
const preparedBlockingRule = new RegExp(`(^|\n)${escapeRegExp(blockingRule)}($|\n)`);
|
|
||||||
const preparedUnblockingRule = new RegExp(`(^|\n)${escapeRegExp(unblockingRule)}($|\n)`);
|
|
||||||
|
|
||||||
const matchPreparedBlockingRule = userRules.match(preparedBlockingRule);
|
|
||||||
const matchPreparedUnblockingRule = userRules.match(preparedUnblockingRule);
|
|
||||||
|
|
||||||
if (matchPreparedBlockingRule) {
|
|
||||||
setRules(userRules.replace(`${blockingRule}`, ''));
|
|
||||||
addSuccessToast(`${t('rule_removed_from_custom_filtering_toast')}: ${blockingRule}`);
|
|
||||||
} else if (!matchPreparedUnblockingRule) {
|
|
||||||
setRules(`${userRules}${lineEnding}${unblockingRule}\n`);
|
|
||||||
addSuccessToast(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
|
|
||||||
} else if (matchPreparedUnblockingRule) {
|
|
||||||
addSuccessToast(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
|
|
||||||
return;
|
|
||||||
} else if (!matchPreparedBlockingRule) {
|
|
||||||
addSuccessToast(`${t('rule_removed_from_custom_filtering_toast')}: ${blockingRule}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getFilteringStatus();
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFilterName = (filters, whitelistFilters, filterId, t) => {
|
|
||||||
if (filterId === CUSTOM_FILTERING_RULES_ID) {
|
|
||||||
return t('custom_filter_rules');
|
|
||||||
}
|
|
||||||
|
|
||||||
const filter = filters.find((filter) => filter.id === filterId)
|
|
||||||
|| whitelistFilters.find((filter) => filter.id === filterId);
|
|
||||||
let filterName = '';
|
|
||||||
|
|
||||||
if (filter) {
|
|
||||||
filterName = filter.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filterName) {
|
|
||||||
filterName = t('unknown_filter', { filterId });
|
|
||||||
}
|
|
||||||
|
|
||||||
return filterName;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
Header: t('time_table_header'),
|
|
||||||
accessor: 'time',
|
|
||||||
Cell: (row) => getDateCell(row, isDetailed),
|
|
||||||
minWidth: 70,
|
|
||||||
maxHeight: 60,
|
|
||||||
headerClassName: 'logs__text',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: t('request_table_header'),
|
|
||||||
accessor: 'domain',
|
|
||||||
Cell: (row) => {
|
|
||||||
const {
|
|
||||||
isDetailed,
|
|
||||||
autoClients,
|
|
||||||
dnssec_enabled,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return getDomainCell({
|
|
||||||
row,
|
|
||||||
t,
|
|
||||||
isDetailed,
|
|
||||||
toggleBlocking,
|
|
||||||
autoClients,
|
|
||||||
dnssec_enabled,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
minWidth: 180,
|
|
||||||
maxHeight: 60,
|
|
||||||
headerClassName: 'logs__text',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: t('response_table_header'),
|
|
||||||
accessor: 'response',
|
|
||||||
Cell: (row) => getResponseCell(
|
|
||||||
row,
|
|
||||||
filtering,
|
|
||||||
t,
|
|
||||||
isDetailed,
|
|
||||||
getFilterName,
|
|
||||||
),
|
|
||||||
minWidth: 150,
|
|
||||||
maxHeight: 60,
|
|
||||||
headerClassName: 'logs__text',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: function Header() {
|
|
||||||
return <div className="d-flex justify-content-between">
|
|
||||||
{t('client_table_header')}
|
|
||||||
{<span>
|
|
||||||
<svg
|
|
||||||
className={classNames('icons icon--24 icon--green mr-2 cursor--pointer', {
|
|
||||||
'icon--selected': !isDetailed,
|
|
||||||
})}
|
|
||||||
onClick={() => toggleDetailedLogs(false)}
|
|
||||||
>
|
|
||||||
<title><Trans>compact</Trans></title>
|
|
||||||
<use xlinkHref='#list' />
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
className={classNames('icons icon--24 icon--green cursor--pointer', {
|
|
||||||
'icon--selected': isDetailed,
|
|
||||||
})}
|
|
||||||
onClick={() => toggleDetailedLogs(true)}
|
|
||||||
>
|
|
||||||
<title><Trans>default</Trans></title>
|
|
||||||
<use xlinkHref='#detailed_list' />
|
|
||||||
</svg>
|
|
||||||
</span>}
|
|
||||||
</div>;
|
|
||||||
},
|
|
||||||
accessor: 'client',
|
|
||||||
Cell: (row) => {
|
|
||||||
const {
|
|
||||||
isDetailed,
|
|
||||||
autoClients,
|
|
||||||
filtering: { processingRules },
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return getClientCell({
|
|
||||||
row,
|
|
||||||
t,
|
|
||||||
isDetailed,
|
|
||||||
toggleBlocking,
|
|
||||||
autoClients,
|
|
||||||
processingRules,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
minWidth: 123,
|
|
||||||
maxHeight: 60,
|
|
||||||
headerClassName: 'logs__text',
|
|
||||||
className: 'pb-0',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const changePage = async (page) => {
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const { oldest, getLogs, pages } = props;
|
|
||||||
const isLastPage = pages && (page + 1 === pages);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
setLogsPage(page),
|
|
||||||
setLogsPagination({
|
|
||||||
page,
|
|
||||||
pageSize: TABLE_DEFAULT_PAGE_SIZE,
|
|
||||||
}),
|
|
||||||
].concat(isLastPage ? getLogs(oldest, page) : []));
|
|
||||||
|
|
||||||
setIsLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableClass = classNames('logs__table', {
|
|
||||||
'logs__table--detailed': isDetailed,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ReactTable
|
|
||||||
manual
|
|
||||||
minRows={0}
|
|
||||||
page={page}
|
|
||||||
pages={pages}
|
|
||||||
columns={columns}
|
|
||||||
filterable={false}
|
|
||||||
sortable={false}
|
|
||||||
resizable={false}
|
|
||||||
data={logs || []}
|
|
||||||
loading={isLoading || processingGetLogs}
|
|
||||||
showPageJump={false}
|
|
||||||
showPageSizeOptions={false}
|
|
||||||
onPageChange={changePage}
|
|
||||||
className={tableClass}
|
|
||||||
defaultPageSize={TABLE_DEFAULT_PAGE_SIZE}
|
|
||||||
loadingText={
|
|
||||||
<>
|
|
||||||
<Loading />
|
|
||||||
<h6 className="loading__text">{t('loading_table_status')}</h6>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
getLoadingProps={() => ({ className: 'loading__container' })}
|
|
||||||
rowsText={t('rows_table_footer_text')}
|
|
||||||
noDataText={!processingGetLogs
|
|
||||||
&& <label className="logs__text logs__text--bold">{t('nothing_found')}</label>}
|
|
||||||
pageText=''
|
|
||||||
ofText=''
|
|
||||||
showPagination={logs.length > 0}
|
|
||||||
getPaginationProps={() => ({ className: 'custom-pagination custom-pagination--padding' })}
|
|
||||||
getTbodyProps={() => ({ className: 'd-block' })}
|
|
||||||
previousText={
|
|
||||||
<svg className="icons icon--24 icon--gray w-100 h-100 cursor--pointer">
|
|
||||||
<title><Trans>previous_btn</Trans></title>
|
|
||||||
<use xlinkHref="#arrow-left" />
|
|
||||||
</svg>}
|
|
||||||
nextText={
|
|
||||||
<svg className="icons icon--24 icon--gray w-100 h-100 cursor--pointer">
|
|
||||||
<title><Trans>next_btn</Trans></title>
|
|
||||||
<use xlinkHref="#arrow-right" />
|
|
||||||
</svg>}
|
|
||||||
renderTotalPagesCount={() => false}
|
|
||||||
getTrGroupProps={(_state, rowInfo) => {
|
|
||||||
if (!rowInfo) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { reason } = rowInfo.original;
|
|
||||||
const colorClass = FILTERED_STATUS_TO_META_MAP[reason] ? FILTERED_STATUS_TO_META_MAP[reason].color : 'white';
|
|
||||||
|
|
||||||
return { className: colorClass };
|
|
||||||
}}
|
|
||||||
getTrProps={(state, rowInfo) => ({
|
|
||||||
className: isDetailed ? 'row--detailed' : '',
|
|
||||||
onClick: () => {
|
|
||||||
if (isSmallScreen) {
|
|
||||||
const { dnssec_enabled, autoClients } = props;
|
|
||||||
const {
|
|
||||||
answer_dnssec,
|
|
||||||
client,
|
|
||||||
domain,
|
|
||||||
elapsedMs,
|
|
||||||
info,
|
|
||||||
reason,
|
|
||||||
response,
|
|
||||||
time,
|
|
||||||
tracker,
|
|
||||||
upstream,
|
|
||||||
type,
|
|
||||||
client_proto,
|
|
||||||
filterId,
|
|
||||||
rule,
|
|
||||||
originalResponse,
|
|
||||||
status,
|
|
||||||
} = rowInfo.original;
|
|
||||||
|
|
||||||
const hasTracker = !!tracker;
|
|
||||||
|
|
||||||
const autoClient = autoClients
|
|
||||||
.find((autoClient) => autoClient.name === client);
|
|
||||||
|
|
||||||
const { whois_info } = info;
|
|
||||||
const country = whois_info?.country;
|
|
||||||
const city = whois_info?.city;
|
|
||||||
const network = whois_info?.orgname;
|
|
||||||
|
|
||||||
const source = autoClient?.source;
|
|
||||||
|
|
||||||
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
|
||||||
const isFiltered = checkFiltered(reason);
|
|
||||||
|
|
||||||
const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|
|
||||||
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
|
|
||||||
|
|
||||||
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
|
||||||
const onToggleBlock = () => {
|
|
||||||
toggleBlocking(buttonType, domain);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
|
|
||||||
const requestStatus = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.label || reason);
|
|
||||||
|
|
||||||
const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
|
|
||||||
|
|
||||||
const sourceData = getSourceData(tracker);
|
|
||||||
|
|
||||||
const { filters, whitelistFilters } = filtering;
|
|
||||||
const filter = getFilterName(filters, whitelistFilters, filterId, t);
|
|
||||||
|
|
||||||
const detailedData = {
|
|
||||||
time_table_header: formatTime(time, LONG_TIME_FORMAT),
|
|
||||||
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
|
|
||||||
encryption_status: isBlocked
|
|
||||||
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
|
|
||||||
domain,
|
|
||||||
type_table_header: type,
|
|
||||||
protocol,
|
|
||||||
known_tracker: hasTracker && 'title',
|
|
||||||
table_name: tracker?.name,
|
|
||||||
category_label: hasTracker && captitalizeWords(tracker.category),
|
|
||||||
tracker_source: hasTracker && sourceData
|
|
||||||
&& <a
|
|
||||||
href={sourceData.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="link--green">{sourceData.name}
|
|
||||||
</a>,
|
|
||||||
response_details: 'title',
|
|
||||||
install_settings_dns: upstream,
|
|
||||||
elapsed: formattedElapsedMs,
|
|
||||||
filter: rule ? filter : null,
|
|
||||||
rule_label: rule,
|
|
||||||
response_table_header: response?.join('\n'),
|
|
||||||
response_code: status,
|
|
||||||
client_details: 'title',
|
|
||||||
ip_address: client,
|
|
||||||
name: info?.name,
|
|
||||||
country,
|
|
||||||
city,
|
|
||||||
network,
|
|
||||||
source_label: source,
|
|
||||||
validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
|
|
||||||
original_response: originalResponse?.join('\n'),
|
|
||||||
[buttonType]: <div onClick={onToggleBlock}
|
|
||||||
className={classNames('title--border text-center', {
|
|
||||||
'bg--danger': isBlocked,
|
|
||||||
})}>{t(buttonType)}</div>,
|
|
||||||
};
|
|
||||||
|
|
||||||
setDetailedDataCurrent(processContent(detailedData));
|
|
||||||
setButtonType(buttonType);
|
|
||||||
setModalOpened(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Table.propTypes = {
|
|
||||||
logs: PropTypes.array.isRequired,
|
|
||||||
pages: PropTypes.number.isRequired,
|
|
||||||
page: PropTypes.number.isRequired,
|
|
||||||
autoClients: PropTypes.array.isRequired,
|
|
||||||
defaultPageSize: PropTypes.number,
|
|
||||||
oldest: PropTypes.string.isRequired,
|
|
||||||
filtering: PropTypes.object.isRequired,
|
|
||||||
processingGetLogs: PropTypes.bool.isRequired,
|
|
||||||
processingGetConfig: PropTypes.bool.isRequired,
|
|
||||||
isDetailed: PropTypes.bool.isRequired,
|
|
||||||
setLogsPage: PropTypes.func.isRequired,
|
|
||||||
setLogsPagination: PropTypes.func.isRequired,
|
|
||||||
getLogs: PropTypes.func.isRequired,
|
|
||||||
toggleDetailedLogs: PropTypes.func.isRequired,
|
|
||||||
setRules: PropTypes.func.isRequired,
|
|
||||||
addSuccessToast: PropTypes.func.isRequired,
|
|
||||||
getFilteringStatus: PropTypes.func.isRequired,
|
|
||||||
isLoading: PropTypes.bool.isRequired,
|
|
||||||
setIsLoading: PropTypes.func.isRequired,
|
|
||||||
dnssec_enabled: PropTypes.bool.isRequired,
|
|
||||||
setDetailedDataCurrent: PropTypes.func.isRequired,
|
|
||||||
setButtonType: PropTypes.func.isRequired,
|
|
||||||
setModalOpened: PropTypes.func.isRequired,
|
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Table;
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React, { Fragment, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
@ -8,24 +7,21 @@ import queryString from 'query-string';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {
|
import {
|
||||||
BLOCK_ACTIONS,
|
BLOCK_ACTIONS,
|
||||||
TABLE_DEFAULT_PAGE_SIZE,
|
|
||||||
TABLE_FIRST_PAGE,
|
|
||||||
SMALL_SCREEN_SIZE,
|
SMALL_SCREEN_SIZE,
|
||||||
} from '../../helpers/constants';
|
} from '../../helpers/constants';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import Filters from './Filters';
|
import Filters from './Filters';
|
||||||
import Table from './Table';
|
|
||||||
import Disabled from './Disabled';
|
import Disabled from './Disabled';
|
||||||
import { getFilteringStatus } from '../../actions/filtering';
|
import { getFilteringStatus } from '../../actions/filtering';
|
||||||
import { getClients } from '../../actions';
|
import { getClients } from '../../actions';
|
||||||
import { getDnsConfig } from '../../actions/dnsConfig';
|
import { getDnsConfig } from '../../actions/dnsConfig';
|
||||||
import {
|
import {
|
||||||
getLogsConfig,
|
getLogsConfig,
|
||||||
refreshFilteredLogs,
|
|
||||||
resetFilteredLogs,
|
resetFilteredLogs,
|
||||||
setFilteredLogs,
|
setFilteredLogs,
|
||||||
|
toggleDetailedLogs,
|
||||||
} from '../../actions/queryLogs';
|
} from '../../actions/queryLogs';
|
||||||
import { addSuccessToast } from '../../actions/toasts';
|
import InfiniteTable from './InfiniteTable';
|
||||||
import './Logs.css';
|
import './Logs.css';
|
||||||
|
|
||||||
const processContent = (data, buttonType) => Object.entries(data)
|
const processContent = (data, buttonType) => Object.entries(data)
|
||||||
|
@ -48,7 +44,7 @@ const processContent = (data, buttonType) => Object.entries(data)
|
||||||
keyClass = '';
|
keyClass = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return isHidden ? null : <Fragment key={key}>
|
return isHidden ? null : <div key={key}>
|
||||||
<div
|
<div
|
||||||
className={classNames(`key__${key}`, keyClass, {
|
className={classNames(`key__${key}`, keyClass, {
|
||||||
'font-weight-bold': isBoolean && value === true,
|
'font-weight-bold': isBoolean && value === true,
|
||||||
|
@ -58,11 +54,10 @@ const processContent = (data, buttonType) => Object.entries(data)
|
||||||
<div className={`value__${key} text-pre text-truncate`}>
|
<div className={`value__${key} text-pre text-truncate`}>
|
||||||
<Trans>{(isTitle || isButton || isBoolean) ? '' : value || '—'}</Trans>
|
<Trans>{(isTitle || isButton || isBoolean) ? '' : value || '—'}</Trans>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>;
|
</div>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const Logs = () => {
|
||||||
const Logs = (props) => {
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
@ -71,7 +66,14 @@ const Logs = (props) => {
|
||||||
search: search_url_param = '',
|
search: search_url_param = '',
|
||||||
} = queryString.parse(history.location.search);
|
} = queryString.parse(history.location.search);
|
||||||
|
|
||||||
const { filter } = useSelector((state) => state.queryLogs, shallowEqual);
|
const {
|
||||||
|
enabled,
|
||||||
|
processingGetConfig,
|
||||||
|
processingAdditionalLogs,
|
||||||
|
processingGetLogs,
|
||||||
|
} = useSelector((state) => state.queryLogs, shallowEqual);
|
||||||
|
const filter = useSelector((state) => state.queryLogs.filter, shallowEqual);
|
||||||
|
const logs = useSelector((state) => state.queryLogs.logs, shallowEqual);
|
||||||
|
|
||||||
const search = filter?.search || search_url_param;
|
const search = filter?.search || search_url_param;
|
||||||
const response_status = filter?.response_status || response_status_url_param;
|
const response_status = filter?.response_status || response_status_url_param;
|
||||||
|
@ -82,6 +84,7 @@ const Logs = (props) => {
|
||||||
const [isModalOpened, setModalOpened] = useState(false);
|
const [isModalOpened, setModalOpened] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const closeModal = () => setModalOpened(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
@ -94,44 +97,11 @@ const Logs = (props) => {
|
||||||
})();
|
})();
|
||||||
}, [response_status, search]);
|
}, [response_status, search]);
|
||||||
|
|
||||||
const {
|
|
||||||
filtering,
|
|
||||||
setLogsPage,
|
|
||||||
setLogsPagination,
|
|
||||||
toggleDetailedLogs,
|
|
||||||
dashboard,
|
|
||||||
dnsConfig,
|
|
||||||
queryLogs: {
|
|
||||||
enabled,
|
|
||||||
processingGetConfig,
|
|
||||||
processingAdditionalLogs,
|
|
||||||
processingGetLogs,
|
|
||||||
oldest,
|
|
||||||
logs,
|
|
||||||
pages,
|
|
||||||
page,
|
|
||||||
isDetailed,
|
|
||||||
},
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const mediaQuery = window.matchMedia(`(max-width: ${SMALL_SCREEN_SIZE}px)`);
|
const mediaQuery = window.matchMedia(`(max-width: ${SMALL_SCREEN_SIZE}px)`);
|
||||||
const mediaQueryHandler = (e) => {
|
const mediaQueryHandler = (e) => {
|
||||||
setIsSmallScreen(e.matches);
|
setIsSmallScreen(e.matches);
|
||||||
if (e.matches) {
|
if (e.matches) {
|
||||||
toggleDetailedLogs(false);
|
dispatch(toggleDetailedLogs(false));
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeModal = () => setModalOpened(false);
|
|
||||||
|
|
||||||
const getLogs = (older_than, page, initial) => {
|
|
||||||
if (enabled) {
|
|
||||||
props.getLogs({
|
|
||||||
older_than,
|
|
||||||
page,
|
|
||||||
pageSize: TABLE_DEFAULT_PAGE_SIZE,
|
|
||||||
initial,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -149,7 +119,6 @@ const Logs = (props) => {
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
dispatch(setLogsPage(TABLE_FIRST_PAGE));
|
|
||||||
dispatch(getFilteringStatus());
|
dispatch(getFilteringStatus());
|
||||||
dispatch(getClients());
|
dispatch(getClients());
|
||||||
try {
|
try {
|
||||||
|
@ -169,6 +138,7 @@ const Logs = (props) => {
|
||||||
mediaQuery.removeEventListener('change', mediaQueryHandler);
|
mediaQuery.removeEventListener('change', mediaQueryHandler);
|
||||||
} catch (e1) {
|
} catch (e1) {
|
||||||
try {
|
try {
|
||||||
|
// Safari 13.1 do not support mediaQuery.addEventListener('change', handler)
|
||||||
mediaQuery.removeListener(mediaQueryHandler);
|
mediaQuery.removeListener(mediaQueryHandler);
|
||||||
} catch (e2) {
|
} catch (e2) {
|
||||||
console.error(e2);
|
console.error(e2);
|
||||||
|
@ -179,21 +149,7 @@ const Logs = (props) => {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const refreshLogs = async () => {
|
const renderPage = () => <>
|
||||||
setIsLoading(true);
|
|
||||||
await Promise.all([
|
|
||||||
dispatch(setLogsPage(TABLE_FIRST_PAGE)),
|
|
||||||
dispatch(refreshFilteredLogs()),
|
|
||||||
]);
|
|
||||||
dispatch(addSuccessToast('query_log_updated'));
|
|
||||||
setIsLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{enabled && processingGetConfig && <Loading />}
|
|
||||||
{enabled && !processingGetConfig && (
|
|
||||||
<>
|
|
||||||
<Filters
|
<Filters
|
||||||
filter={{
|
filter={{
|
||||||
response_status,
|
response_status,
|
||||||
|
@ -202,32 +158,14 @@ const Logs = (props) => {
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
processingGetLogs={processingGetLogs}
|
processingGetLogs={processingGetLogs}
|
||||||
processingAdditionalLogs={processingAdditionalLogs}
|
processingAdditionalLogs={processingAdditionalLogs}
|
||||||
refreshLogs={refreshLogs}
|
|
||||||
/>
|
/>
|
||||||
<Table
|
<InfiniteTable
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
setIsLoading={setIsLoading}
|
items={logs}
|
||||||
logs={logs}
|
isSmallScreen={isSmallScreen}
|
||||||
pages={pages}
|
|
||||||
page={page}
|
|
||||||
autoClients={dashboard.autoClients}
|
|
||||||
oldest={oldest}
|
|
||||||
filtering={filtering}
|
|
||||||
processingGetLogs={processingGetLogs}
|
|
||||||
processingGetConfig={processingGetConfig}
|
|
||||||
isDetailed={isDetailed}
|
|
||||||
setLogsPagination={setLogsPagination}
|
|
||||||
setLogsPage={setLogsPage}
|
|
||||||
toggleDetailedLogs={toggleDetailedLogs}
|
|
||||||
getLogs={getLogs}
|
|
||||||
setRules={props.setRules}
|
|
||||||
addSuccessToast={props.addSuccessToast}
|
|
||||||
getFilteringStatus={props.getFilteringStatus}
|
|
||||||
dnssec_enabled={dnsConfig.dnssec_enabled}
|
|
||||||
setDetailedDataCurrent={setDetailedDataCurrent}
|
setDetailedDataCurrent={setDetailedDataCurrent}
|
||||||
setButtonType={setButtonType}
|
setButtonType={setButtonType}
|
||||||
setModalOpened={setModalOpened}
|
setModalOpened={setModalOpened}
|
||||||
isSmallScreen={isSmallScreen}
|
|
||||||
/>
|
/>
|
||||||
<Modal portalClassName='grid' isOpen={isSmallScreen && isModalOpened}
|
<Modal portalClassName='grid' isOpen={isSmallScreen && isModalOpened}
|
||||||
onRequestClose={closeModal}
|
onRequestClose={closeModal}
|
||||||
|
@ -251,27 +189,13 @@ const Logs = (props) => {
|
||||||
</svg>
|
</svg>
|
||||||
{processContent(detailedDataCurrent, buttonType)}
|
{processContent(detailedDataCurrent, buttonType)}
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>;
|
||||||
)}
|
|
||||||
{!enabled && !processingGetConfig && (
|
|
||||||
<Disabled />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Logs.propTypes = {
|
return <>
|
||||||
getLogs: PropTypes.func.isRequired,
|
{enabled && processingGetConfig && <Loading />}
|
||||||
queryLogs: PropTypes.object.isRequired,
|
{enabled && !processingGetConfig && renderPage()}
|
||||||
dashboard: PropTypes.object.isRequired,
|
{!enabled && !processingGetConfig && <Disabled />}
|
||||||
getFilteringStatus: PropTypes.func.isRequired,
|
</>;
|
||||||
filtering: PropTypes.object.isRequired,
|
|
||||||
setRules: PropTypes.func.isRequired,
|
|
||||||
addSuccessToast: PropTypes.func.isRequired,
|
|
||||||
setLogsPagination: PropTypes.func.isRequired,
|
|
||||||
setLogsPage: PropTypes.func.isRequired,
|
|
||||||
toggleDetailedLogs: PropTypes.func.isRequired,
|
|
||||||
dnsConfig: PropTypes.object.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Logs;
|
export default Logs;
|
||||||
|
|
|
@ -79,7 +79,7 @@ const StaticLeases = ({
|
||||||
onClick={() => handleDelete(ip, mac, hostname)}
|
onClick={() => handleDelete(ip, mac, hostname)}
|
||||||
>
|
>
|
||||||
<svg className="icons">
|
<svg className="icons">
|
||||||
<use xlinkHref="#delete" />
|
<use xlinkHref="#delete"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -1,71 +1,50 @@
|
||||||
import React, { Component } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FAILURE_TOAST_TIMEOUT, SUCCESS_TOAST_TIMEOUT } from '../../helpers/constants';
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { TOAST_TIMEOUTS } from '../../helpers/constants';
|
||||||
|
import { removeToast } from '../../actions';
|
||||||
|
|
||||||
class Toast extends Component {
|
const Toast = ({
|
||||||
state = {
|
id,
|
||||||
timerId: null,
|
message,
|
||||||
|
type,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [timerId, setTimerId] = useState(null);
|
||||||
|
|
||||||
|
const clearRemoveToastTimeout = () => clearTimeout(timerId);
|
||||||
|
const removeCurrentToast = () => dispatch(removeToast(id));
|
||||||
|
const setRemoveToastTimeout = () => {
|
||||||
|
const timeout = TOAST_TIMEOUTS[type];
|
||||||
|
const timerId = setTimeout(removeCurrentToast, timeout);
|
||||||
|
|
||||||
|
setTimerId(timerId);
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
useEffect(() => {
|
||||||
this.setRemoveToastTimeout();
|
setRemoveToastTimeout();
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
shouldComponentUpdate() {
|
return <div className={`toast toast--${type}`}
|
||||||
return false;
|
onMouseOver={clearRemoveToastTimeout}
|
||||||
}
|
onMouseOut={setRemoveToastTimeout}>
|
||||||
|
<p className="toast__content">{t(message)}</p>
|
||||||
clearRemoveToastTimeout = () => clearTimeout(this.state.timerId);
|
<button className="toast__dismiss" onClick={removeCurrentToast}>
|
||||||
|
|
||||||
setRemoveToastTimeout = () => {
|
|
||||||
const timeout = this.props.type === 'success' ? SUCCESS_TOAST_TIMEOUT : FAILURE_TOAST_TIMEOUT;
|
|
||||||
|
|
||||||
const timerId = setTimeout(() => {
|
|
||||||
this.props.removeToast(this.props.id);
|
|
||||||
}, timeout);
|
|
||||||
|
|
||||||
this.setState({ timerId });
|
|
||||||
};
|
|
||||||
|
|
||||||
showMessage(t, type, message) {
|
|
||||||
if (type === 'notice') {
|
|
||||||
return <span dangerouslySetInnerHTML={{ __html: t(message) }} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Trans>{message}</Trans>;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
type, id, t, message,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`toast toast--${type}`}
|
|
||||||
onMouseOver={this.clearRemoveToastTimeout}
|
|
||||||
onMouseOut={this.setRemoveToastTimeout}>
|
|
||||||
<p className="toast__content">
|
|
||||||
{this.showMessage(t, type, message)}
|
|
||||||
</p>
|
|
||||||
<button className="toast__dismiss" onClick={() => this.props.removeToast(id)}>
|
|
||||||
<svg stroke="#fff" fill="none" width="20" height="20" strokeWidth="2"
|
<svg stroke="#fff" fill="none" width="20" height="20" strokeWidth="2"
|
||||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="m18 6-12 12" />
|
<path d="m18 6-12 12" />
|
||||||
<path d="m6 6 12 12" />
|
<path d="m6 6 12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>;
|
||||||
);
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast.propTypes = {
|
Toast.propTypes = {
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
message: PropTypes.string.isRequired,
|
message: PropTypes.string.isRequired,
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
removeToast: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(Toast);
|
export default Toast;
|
||||||
|
|
|
@ -1,41 +1,25 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { useSelector, shallowEqual } from 'react-redux';
|
||||||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||||
import * as actionCreators from '../../actions';
|
import { TOAST_TRANSITION_TIMEOUT } from '../../helpers/constants';
|
||||||
import Toast from './Toast';
|
import Toast from './Toast';
|
||||||
|
|
||||||
import './Toast.css';
|
import './Toast.css';
|
||||||
|
|
||||||
const Toasts = (props) => (
|
const Toasts = () => {
|
||||||
<TransitionGroup className="toasts">
|
const toasts = useSelector((state) => state.toasts, shallowEqual);
|
||||||
{props.toasts.notices?.map((toast) => {
|
|
||||||
|
return <TransitionGroup className="toasts">
|
||||||
|
{toasts.notices?.map((toast) => {
|
||||||
const { id } = toast;
|
const { id } = toast;
|
||||||
return (
|
return <CSSTransition
|
||||||
<CSSTransition
|
|
||||||
key={id}
|
key={id}
|
||||||
timeout={500}
|
timeout={TOAST_TRANSITION_TIMEOUT}
|
||||||
classNames="toast"
|
classNames="toast"
|
||||||
>
|
>
|
||||||
<Toast removeToast={props.removeToast} {...toast} />
|
<Toast {...toast} />
|
||||||
</CSSTransition>
|
</CSSTransition>;
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</TransitionGroup>
|
</TransitionGroup>;
|
||||||
);
|
|
||||||
|
|
||||||
Toasts.propTypes = {
|
|
||||||
toasts: PropTypes.object,
|
|
||||||
removeToast: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
export default Toasts;
|
||||||
const { toasts } = state;
|
|
||||||
const props = { toasts };
|
|
||||||
return props;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
actionCreators,
|
|
||||||
)(Toasts);
|
|
||||||
|
|
|
@ -118,14 +118,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .red {
|
.card .logs__cell--red {
|
||||||
background-color: #fff4f2;
|
background-color: #fff4f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .green {
|
.card .logs__cell--green {
|
||||||
background-color: #f1faf3;
|
background-color: #f1faf3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .blue {
|
.card .logs__row--blue {
|
||||||
background-color: #ecf7ff;
|
background-color: #ecf7ff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,7 @@
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: rgba(255, 255, 255, 0.6);
|
background-color: rgba(255, 255, 255, 0.48);
|
||||||
opacity: 0.8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading:after {
|
.loading:after {
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import './Loading.css';
|
import './Loading.css';
|
||||||
|
|
||||||
const Loading = ({ className }) => (
|
const Loading = ({ className, text }) => {
|
||||||
<div className={classNames('loading', className)} />
|
const { t } = useTranslation();
|
||||||
);
|
return <div className={classNames('loading', className)}>{t(text)}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
Loading.propTypes = {
|
Loading.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
text: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Loading;
|
export default Loading;
|
||||||
|
|
|
@ -13,18 +13,18 @@
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-tr-group.red {
|
.rt-tr-group.logs__row--red {
|
||||||
background-color: rgba(223, 56, 18, 0.05);
|
background-color: rgba(223, 56, 18, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-tr-group.green {
|
.rt-tr-group.logs__row--green {
|
||||||
background-color: rgba(103, 178, 121, 0.1);
|
background-color: rgba(103, 178, 121, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-tr-group.blue {
|
.rt-tr-group.logs__row--blue {
|
||||||
background-color: #e5effd;
|
background-color: #e5effd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-tr-group.yellow {
|
.rt-tr-group.logs__row--yellow {
|
||||||
background-color: var(--yellow-pale);
|
background-color: var(--yellow-pale);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,13 +34,7 @@ const Tooltip = ({
|
||||||
delayShowValue = 0;
|
delayShowValue = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const renderTooltip = ({ tooltipRef, getTooltipProps }) => (
|
||||||
<TooltipTrigger
|
|
||||||
placement={placement}
|
|
||||||
trigger={triggerValue}
|
|
||||||
delayHide={delayHideValue}
|
|
||||||
delayShow={delayShowValue}
|
|
||||||
tooltip={({ tooltipRef, getTooltipProps }) => (
|
|
||||||
<div
|
<div
|
||||||
{...getTooltipProps({
|
{...getTooltipProps({
|
||||||
ref: tooltipRef,
|
ref: tooltipRef,
|
||||||
|
@ -49,9 +43,9 @@ const Tooltip = ({
|
||||||
>
|
>
|
||||||
{typeof content === 'string' ? t(content) : content}
|
{typeof content === 'string' ? t(content) : content}
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
>
|
|
||||||
{({ getTriggerProps, triggerRef }) => (
|
const renderTrigger = ({ getTriggerProps, triggerRef }) => (
|
||||||
<span
|
<span
|
||||||
{...getTriggerProps({
|
{...getTriggerProps({
|
||||||
ref: triggerRef,
|
ref: triggerRef,
|
||||||
|
@ -60,7 +54,27 @@ const Tooltip = ({
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
)}
|
);
|
||||||
|
|
||||||
|
renderTooltip.propTypes = {
|
||||||
|
tooltipRef: propTypes.object,
|
||||||
|
getTooltipProps: propTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderTrigger.propTypes = {
|
||||||
|
triggerRef: propTypes.object,
|
||||||
|
getTriggerProps: propTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TooltipTrigger
|
||||||
|
placement={placement}
|
||||||
|
trigger={triggerValue}
|
||||||
|
delayHide={delayHideValue}
|
||||||
|
delayShow={delayShowValue}
|
||||||
|
tooltip={renderTooltip}
|
||||||
|
>
|
||||||
|
{renderTrigger}
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { toggleProtection, getClients } from '../actions';
|
import { toggleProtection, getClients } from '../actions';
|
||||||
import { getStats, getStatsConfig, setStatsConfig } from '../actions/stats';
|
import { getStats, getStatsConfig, setStatsConfig } from '../actions/stats';
|
||||||
import { toggleClientBlock, getAccessList } from '../actions/access';
|
import { getAccessList } from '../actions/access';
|
||||||
import Dashboard from '../components/Dashboard';
|
import Dashboard from '../components/Dashboard';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
|
@ -16,7 +16,6 @@ const mapDispatchToProps = {
|
||||||
getStats,
|
getStats,
|
||||||
getStatsConfig,
|
getStatsConfig,
|
||||||
setStatsConfig,
|
setStatsConfig,
|
||||||
toggleClientBlock,
|
|
||||||
getAccessList,
|
getAccessList,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { getFilteringStatus, setRules } from '../actions/filtering';
|
|
||||||
import {
|
|
||||||
getLogs, setLogsPagination, setLogsPage, toggleDetailedLogs,
|
|
||||||
} from '../actions/queryLogs';
|
|
||||||
import Logs from '../components/Logs';
|
|
||||||
import { addSuccessToast } from '../actions/toasts';
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
|
||||||
const {
|
|
||||||
queryLogs, dashboard, filtering, dnsConfig,
|
|
||||||
} = state;
|
|
||||||
|
|
||||||
const props = {
|
|
||||||
queryLogs,
|
|
||||||
dashboard,
|
|
||||||
filtering,
|
|
||||||
dnsConfig,
|
|
||||||
};
|
|
||||||
return props;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
getLogs,
|
|
||||||
getFilteringStatus,
|
|
||||||
setRules,
|
|
||||||
addSuccessToast,
|
|
||||||
setLogsPagination,
|
|
||||||
setLogsPage,
|
|
||||||
toggleDetailedLogs,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
)(Logs);
|
|
|
@ -307,9 +307,7 @@ export const DEFAULT_LOGS_FILTER = {
|
||||||
|
|
||||||
export const DEFAULT_LANGUAGE = 'en';
|
export const DEFAULT_LANGUAGE = 'en';
|
||||||
|
|
||||||
export const TABLE_DEFAULT_PAGE_SIZE = 25;
|
export const QUERY_LOGS_PAGE_LIMIT = 20;
|
||||||
|
|
||||||
export const TABLE_FIRST_PAGE = 0;
|
|
||||||
|
|
||||||
export const LEASES_TABLE_DEFAULT_PAGE_SIZE = 20;
|
export const LEASES_TABLE_DEFAULT_PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
@ -327,85 +325,93 @@ export const FILTERED_STATUS = {
|
||||||
|
|
||||||
export const RESPONSE_FILTER = {
|
export const RESPONSE_FILTER = {
|
||||||
ALL: {
|
ALL: {
|
||||||
query: 'all',
|
QUERY: 'all',
|
||||||
label: 'all_queries',
|
LABEL: 'all_queries',
|
||||||
},
|
},
|
||||||
FILTERED: {
|
FILTERED: {
|
||||||
query: 'filtered',
|
QUERY: 'filtered',
|
||||||
label: 'filtered',
|
LABEL: 'filtered',
|
||||||
},
|
},
|
||||||
PROCESSED: {
|
PROCESSED: {
|
||||||
query: 'processed',
|
QUERY: 'processed',
|
||||||
label: 'show_processed_responses',
|
LABEL: 'show_processed_responses',
|
||||||
},
|
},
|
||||||
BLOCKED: {
|
BLOCKED: {
|
||||||
query: 'blocked',
|
QUERY: 'blocked',
|
||||||
label: 'show_blocked_responses',
|
LABEL: 'show_blocked_responses',
|
||||||
},
|
},
|
||||||
BLOCKED_THREATS: {
|
BLOCKED_THREATS: {
|
||||||
query: 'blocked_safebrowsing',
|
QUERY: 'blocked_safebrowsing',
|
||||||
label: 'blocked_threats',
|
LABEL: 'blocked_threats',
|
||||||
},
|
},
|
||||||
BLOCKED_ADULT_WEBSITES: {
|
BLOCKED_ADULT_WEBSITES: {
|
||||||
query: 'blocked_parental',
|
QUERY: 'blocked_parental',
|
||||||
label: 'blocked_adult_websites',
|
LABEL: 'blocked_adult_websites',
|
||||||
},
|
},
|
||||||
ALLOWED: {
|
ALLOWED: {
|
||||||
query: 'whitelisted',
|
QUERY: 'whitelisted',
|
||||||
label: 'allowed',
|
LABEL: 'allowed',
|
||||||
},
|
},
|
||||||
REWRITTEN: {
|
REWRITTEN: {
|
||||||
query: 'rewritten',
|
QUERY: 'rewritten',
|
||||||
label: 'rewritten',
|
LABEL: 'rewritten',
|
||||||
},
|
},
|
||||||
SAFE_SEARCH: {
|
SAFE_SEARCH: {
|
||||||
query: 'safe_search',
|
QUERY: 'safe_search',
|
||||||
label: 'safe_search',
|
LABEL: 'safe_search',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RESPONSE_FILTER_QUERIES = Object.values(RESPONSE_FILTER)
|
export const RESPONSE_FILTER_QUERIES = Object.values(RESPONSE_FILTER)
|
||||||
.reduce((acc, { query }) => {
|
.reduce((acc, { QUERY }) => {
|
||||||
acc[query] = query;
|
acc[QUERY] = QUERY;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
export const QUERY_STATUS_COLORS = {
|
||||||
|
BLUE: 'blue',
|
||||||
|
GREEN: 'green',
|
||||||
|
RED: 'red',
|
||||||
|
WHITE: 'white',
|
||||||
|
YELLOW: 'yellow',
|
||||||
|
};
|
||||||
|
|
||||||
export const FILTERED_STATUS_TO_META_MAP = {
|
export const FILTERED_STATUS_TO_META_MAP = {
|
||||||
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
||||||
label: RESPONSE_FILTER.ALLOWED.label,
|
LABEL: RESPONSE_FILTER.ALLOWED.LABEL,
|
||||||
color: 'green',
|
COLOR: QUERY_STATUS_COLORS.GREEN,
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: {
|
[FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: {
|
||||||
label: RESPONSE_FILTER.PROCESSED.label,
|
LABEL: RESPONSE_FILTER.PROCESSED.LABEL,
|
||||||
color: 'white',
|
COLOR: QUERY_STATUS_COLORS.WHITE,
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.FILTERED_BLOCKED_SERVICE]: {
|
[FILTERED_STATUS.FILTERED_BLOCKED_SERVICE]: {
|
||||||
label: RESPONSE_FILTER.BLOCKED.label,
|
LABEL: RESPONSE_FILTER.BLOCKED.LABEL,
|
||||||
color: 'red',
|
COLOR: QUERY_STATUS_COLORS.RED,
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
|
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
|
||||||
label: RESPONSE_FILTER.SAFE_SEARCH.label,
|
LABEL: RESPONSE_FILTER.SAFE_SEARCH.LABEL,
|
||||||
color: 'yellow',
|
COLOR: QUERY_STATUS_COLORS.YELLOW,
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.FILTERED_BLACK_LIST]: {
|
[FILTERED_STATUS.FILTERED_BLACK_LIST]: {
|
||||||
label: RESPONSE_FILTER.BLOCKED.label,
|
LABEL: RESPONSE_FILTER.BLOCKED.LABEL,
|
||||||
color: 'red',
|
COLOR: QUERY_STATUS_COLORS.RED,
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.REWRITE]: {
|
[FILTERED_STATUS.REWRITE]: {
|
||||||
label: RESPONSE_FILTER.REWRITTEN.label,
|
LABEL: RESPONSE_FILTER.REWRITTEN.LABEL,
|
||||||
color: 'blue',
|
COLOR: QUERY_STATUS_COLORS.BLUE,
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.REWRITE_HOSTS]: {
|
[FILTERED_STATUS.REWRITE_HOSTS]: {
|
||||||
label: RESPONSE_FILTER.REWRITTEN.label,
|
LABEL: RESPONSE_FILTER.REWRITTEN.LABEL,
|
||||||
color: 'blue',
|
COLOR: QUERY_STATUS_COLORS.BLUE,
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.FILTERED_SAFE_BROWSING]: {
|
[FILTERED_STATUS.FILTERED_SAFE_BROWSING]: {
|
||||||
label: RESPONSE_FILTER.BLOCKED_THREATS.label,
|
LABEL: RESPONSE_FILTER.BLOCKED_THREATS.LABEL,
|
||||||
color: 'yellow',
|
COLOR: QUERY_STATUS_COLORS.YELLOW,
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.FILTERED_PARENTAL]: {
|
[FILTERED_STATUS.FILTERED_PARENTAL]: {
|
||||||
label: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.label,
|
LABEL: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.LABEL,
|
||||||
color: 'yellow',
|
COLOR: QUERY_STATUS_COLORS.YELLOW,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -519,3 +525,17 @@ export const DHCP_DESCRIPTION_PLACEHOLDERS = {
|
||||||
lease_duration: 'dhcp_form_lease_input',
|
lease_duration: 'dhcp_form_lease_input',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TOAST_TRANSITION_TIMEOUT = 500;
|
||||||
|
|
||||||
|
export const TOAST_TYPES = {
|
||||||
|
SUCCESS: 'success',
|
||||||
|
ERROR: 'error',
|
||||||
|
NOTICE: 'notice',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TOAST_TIMEOUTS = {
|
||||||
|
[TOAST_TYPES.SUCCESS]: 5000,
|
||||||
|
[TOAST_TYPES.ERROR]: 30000,
|
||||||
|
[TOAST_TYPES.NOTICE]: 30000,
|
||||||
|
};
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { getTrackerData } from './trackers/trackers';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CHECK_TIMEOUT,
|
CHECK_TIMEOUT,
|
||||||
|
CUSTOM_FILTERING_RULES_ID,
|
||||||
DEFAULT_DATE_FORMAT_OPTIONS,
|
DEFAULT_DATE_FORMAT_OPTIONS,
|
||||||
DEFAULT_LANGUAGE,
|
DEFAULT_LANGUAGE,
|
||||||
DEFAULT_TIME_FORMAT,
|
DEFAULT_TIME_FORMAT,
|
||||||
|
@ -742,6 +743,30 @@ export const sortIp = (a, b) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {array} filters
|
||||||
|
* @param {array} whitelistFilters
|
||||||
|
* @param {number} filterId
|
||||||
|
* @param {function} t - translate
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const getFilterName = (
|
||||||
|
filters,
|
||||||
|
whitelistFilters,
|
||||||
|
filterId,
|
||||||
|
customFilterTranslationKey = 'custom_filter_rules',
|
||||||
|
resolveFilterName = (filter) => (filter ? filter.name : i18n.t('unknown_filter', { filterId })),
|
||||||
|
) => {
|
||||||
|
if (filterId === CUSTOM_FILTERING_RULES_ID) {
|
||||||
|
return i18n.t(customFilterTranslationKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchIdPredicate = (filter) => filter.id === filterId;
|
||||||
|
const filter = filters.find(matchIdPredicate) || whitelistFilters.find(matchIdPredicate);
|
||||||
|
|
||||||
|
return resolveFilterName(filter);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ip {string}
|
* @param ip {string}
|
||||||
* @param gateway_ip {string}
|
* @param gateway_ip {string}
|
||||||
|
@ -803,3 +828,11 @@ export const enrichWithConcatenatedIpAddresses = (interfaces) => Object.entries(
|
||||||
acc[k].ip_addresses = ipv4_addresses.concat(ipv6_addresses);
|
acc[k].ip_addresses = ipv4_addresses.concat(ipv6_addresses);
|
||||||
return acc;
|
return acc;
|
||||||
}, interfaces);
|
}, interfaces);
|
||||||
|
|
||||||
|
export const isScrolledIntoView = (el) => {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
const elemTop = rect.top;
|
||||||
|
const elemBottom = rect.bottom;
|
||||||
|
|
||||||
|
return elemTop < window.innerHeight && elemBottom >= 0;
|
||||||
|
};
|
||||||
|
|
|
@ -24,9 +24,17 @@ const getFormattedWhois = (whois) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatClientCell = (row, isDetailed = false, isLogs = true) => {
|
/**
|
||||||
const { value, original: { info } } = row;
|
* @param {string} value
|
||||||
let whoisContainer = '';
|
* @param {object} info
|
||||||
|
* @param {string} info.name
|
||||||
|
* @param {object} info.whois_info
|
||||||
|
* @param {boolean} [isDetailed]
|
||||||
|
* @param {boolean} [isLogs]
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
export const renderFormattedClientCell = (value, info, isDetailed = false, isLogs = false) => {
|
||||||
|
let whoisContainer = null;
|
||||||
let nameContainer = value;
|
let nameContainer = value;
|
||||||
|
|
||||||
if (info) {
|
if (info) {
|
||||||
|
@ -34,42 +42,28 @@ export const formatClientCell = (row, isDetailed = false, isLogs = true) => {
|
||||||
const whoisAvailable = whois_info && Object.keys(whois_info).length > 0;
|
const whoisAvailable = whois_info && Object.keys(whois_info).length > 0;
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
if (isLogs) {
|
const nameValue = <div className="logs__text logs__text--nowrap" title={`${name} (${value})`}>
|
||||||
nameContainer = !whoisAvailable && isDetailed
|
|
||||||
? (
|
|
||||||
<small title={value}>{value}</small>
|
|
||||||
) : (
|
|
||||||
<div className="logs__text logs__text--nowrap" title={`${name} (${value})`}>
|
|
||||||
{name} <small>{`(${value})`}</small>
|
{name} <small>{`(${value})`}</small>
|
||||||
</div>
|
</div>;
|
||||||
);
|
|
||||||
|
if (!isLogs) {
|
||||||
|
nameContainer = nameValue;
|
||||||
} else {
|
} else {
|
||||||
nameContainer = (
|
nameContainer = !whoisAvailable && isDetailed
|
||||||
<div
|
? <small title={value}>{value}</small>
|
||||||
className="logs__text logs__text--nowrap"
|
: nameValue;
|
||||||
title={`${name} (${value})`}
|
|
||||||
>
|
|
||||||
{name} <small>{`(${value})`}</small>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (whoisAvailable && isDetailed) {
|
if (whoisAvailable && isDetailed) {
|
||||||
whoisContainer = (
|
whoisContainer = <div className="logs__text logs__text--wrap logs__text--whois">
|
||||||
<div className="logs__text logs__text--wrap logs__text--whois">
|
|
||||||
{getFormattedWhois(whois_info)}
|
{getFormattedWhois(whois_info)}
|
||||||
</div>
|
</div>;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <div className="logs__text mw-100" title={value}>
|
||||||
<div className="logs__text mw-100" title={value}>
|
|
||||||
<>
|
|
||||||
{nameContainer}
|
{nameContainer}
|
||||||
{whoisContainer}
|
{whoisContainer}
|
||||||
</>
|
</div>;
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
|
@ -1,30 +1,10 @@
|
||||||
import { handleActions } from 'redux-actions';
|
import { handleActions } from 'redux-actions';
|
||||||
|
|
||||||
import * as actions from '../actions/queryLogs';
|
import * as actions from '../actions/queryLogs';
|
||||||
import { DEFAULT_LOGS_FILTER, TABLE_DEFAULT_PAGE_SIZE } from '../helpers/constants';
|
import { DEFAULT_LOGS_FILTER } from '../helpers/constants';
|
||||||
|
|
||||||
const queryLogs = handleActions(
|
const queryLogs = handleActions(
|
||||||
{
|
{
|
||||||
[actions.setLogsPagination]: (state, { payload }) => {
|
|
||||||
const { page, pageSize } = payload;
|
|
||||||
const { allLogs } = state;
|
|
||||||
const rowsStart = pageSize * page;
|
|
||||||
const rowsEnd = (pageSize * page) + pageSize;
|
|
||||||
const logsSlice = allLogs.slice(rowsStart, rowsEnd);
|
|
||||||
const pages = Math.ceil(allLogs.length / pageSize);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
pages,
|
|
||||||
logs: logsSlice,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.setLogsPage]: (state, { payload }) => ({
|
|
||||||
...state,
|
|
||||||
page: payload,
|
|
||||||
}),
|
|
||||||
|
|
||||||
[actions.setFilteredLogsRequest]: (state) => ({ ...state, processingGetLogs: true }),
|
[actions.setFilteredLogsRequest]: (state) => ({ ...state, processingGetLogs: true }),
|
||||||
[actions.setFilteredLogsFailure]: (state) => ({ ...state, processingGetLogs: false }),
|
[actions.setFilteredLogsFailure]: (state) => ({ ...state, processingGetLogs: false }),
|
||||||
[actions.toggleDetailedLogs]: (state, { payload }) => ({
|
[actions.toggleDetailedLogs]: (state, { payload }) => ({
|
||||||
|
@ -34,14 +14,7 @@ const queryLogs = handleActions(
|
||||||
|
|
||||||
[actions.setFilteredLogsSuccess]: (state, { payload }) => {
|
[actions.setFilteredLogsSuccess]: (state, { payload }) => {
|
||||||
const { logs, oldest, filter } = payload;
|
const { logs, oldest, filter } = payload;
|
||||||
const pageSize = TABLE_DEFAULT_PAGE_SIZE;
|
|
||||||
const page = 0;
|
|
||||||
|
|
||||||
const pages = Math.ceil(logs.length / pageSize);
|
|
||||||
const total = logs.length;
|
|
||||||
const rowsStart = pageSize * page;
|
|
||||||
const rowsEnd = rowsStart + pageSize;
|
|
||||||
const logsSlice = logs.slice(rowsStart, rowsEnd);
|
|
||||||
const isFiltered = filter && Object.keys(filter).some((key) => filter[key]);
|
const isFiltered = filter && Object.keys(filter).some((key) => filter[key]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -49,10 +22,8 @@ const queryLogs = handleActions(
|
||||||
oldest,
|
oldest,
|
||||||
filter,
|
filter,
|
||||||
isFiltered,
|
isFiltered,
|
||||||
pages,
|
logs,
|
||||||
total,
|
isEntireLog: logs.length < 1,
|
||||||
logs: logsSlice,
|
|
||||||
allLogs: logs,
|
|
||||||
processingGetLogs: false,
|
processingGetLogs: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -67,29 +38,13 @@ const queryLogs = handleActions(
|
||||||
[actions.getLogsFailure]: (state) => ({ ...state, processingGetLogs: false }),
|
[actions.getLogsFailure]: (state) => ({ ...state, processingGetLogs: false }),
|
||||||
[actions.getLogsSuccess]: (state, { payload }) => {
|
[actions.getLogsSuccess]: (state, { payload }) => {
|
||||||
const {
|
const {
|
||||||
logs, oldest, older_than, page, pageSize, initial,
|
logs, oldest, older_than,
|
||||||
} = payload;
|
} = payload;
|
||||||
let logsWithOffset = state.allLogs.length > 0 && !initial ? state.allLogs : logs;
|
|
||||||
let allLogs = logs;
|
|
||||||
|
|
||||||
if (older_than) {
|
|
||||||
logsWithOffset = [...state.allLogs, ...logs];
|
|
||||||
allLogs = [...state.allLogs, ...logs];
|
|
||||||
}
|
|
||||||
|
|
||||||
const pages = Math.ceil(logsWithOffset.length / pageSize);
|
|
||||||
const total = logsWithOffset.length;
|
|
||||||
const rowsStart = pageSize * page;
|
|
||||||
const rowsEnd = (pageSize * page) + pageSize;
|
|
||||||
const logsSlice = logsWithOffset.slice(rowsStart, rowsEnd);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
oldest,
|
oldest,
|
||||||
pages,
|
logs: older_than ? [...state.logs, ...logs] : logs,
|
||||||
total,
|
|
||||||
allLogs,
|
|
||||||
logs: logsSlice,
|
|
||||||
isEntireLog: logs.length < 1,
|
isEntireLog: logs.length < 1,
|
||||||
processingGetLogs: false,
|
processingGetLogs: false,
|
||||||
};
|
};
|
||||||
|
@ -126,7 +81,7 @@ const queryLogs = handleActions(
|
||||||
...state, processingAdditionalLogs: false, processingGetLogs: false,
|
...state, processingAdditionalLogs: false, processingGetLogs: false,
|
||||||
}),
|
}),
|
||||||
[actions.getAdditionalLogsSuccess]: (state) => ({
|
[actions.getAdditionalLogsSuccess]: (state) => ({
|
||||||
...state, processingAdditionalLogs: false, processingGetLogs: false,
|
...state, processingAdditionalLogs: false, processingGetLogs: false, isEntireLog: true,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -135,18 +90,15 @@ const queryLogs = handleActions(
|
||||||
processingGetConfig: false,
|
processingGetConfig: false,
|
||||||
processingSetConfig: false,
|
processingSetConfig: false,
|
||||||
processingAdditionalLogs: false,
|
processingAdditionalLogs: false,
|
||||||
logs: [],
|
|
||||||
interval: 1,
|
interval: 1,
|
||||||
allLogs: [],
|
logs: [],
|
||||||
page: 0,
|
|
||||||
pages: 0,
|
|
||||||
total: 0,
|
|
||||||
enabled: true,
|
enabled: true,
|
||||||
oldest: '',
|
oldest: '',
|
||||||
filter: DEFAULT_LOGS_FILTER,
|
filter: DEFAULT_LOGS_FILTER,
|
||||||
isFiltered: false,
|
isFiltered: false,
|
||||||
anonymize_client_ip: false,
|
anonymize_client_ip: false,
|
||||||
isDetailed: true,
|
isDetailed: true,
|
||||||
|
isEntireLog: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -5,16 +5,17 @@ import {
|
||||||
addErrorToast, addNoticeToast, addSuccessToast,
|
addErrorToast, addNoticeToast, addSuccessToast,
|
||||||
} from '../actions/toasts';
|
} from '../actions/toasts';
|
||||||
import { removeToast } from '../actions';
|
import { removeToast } from '../actions';
|
||||||
|
import { TOAST_TYPES } from '../helpers/constants';
|
||||||
|
|
||||||
const toasts = handleActions({
|
const toasts = handleActions({
|
||||||
[addErrorToast]: (state, { payload }) => {
|
[addErrorToast]: (state, { payload }) => {
|
||||||
const message = payload.error.toString();
|
const message = payload.error.toString();
|
||||||
console.error(message);
|
console.error(payload.error);
|
||||||
|
|
||||||
const errorToast = {
|
const errorToast = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
message,
|
message,
|
||||||
type: 'error',
|
type: TOAST_TYPES.ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
const newState = { ...state, notices: [...state.notices, errorToast] };
|
const newState = { ...state, notices: [...state.notices, errorToast] };
|
||||||
|
@ -24,7 +25,7 @@ const toasts = handleActions({
|
||||||
const successToast = {
|
const successToast = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
message: payload,
|
message: payload,
|
||||||
type: 'success',
|
type: TOAST_TYPES.SUCCESS,
|
||||||
};
|
};
|
||||||
|
|
||||||
const newState = { ...state, notices: [...state.notices, successToast] };
|
const newState = { ...state, notices: [...state.notices, successToast] };
|
||||||
|
@ -34,7 +35,7 @@ const toasts = handleActions({
|
||||||
const noticeToast = {
|
const noticeToast = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
message: payload.error.toString(),
|
message: payload.error.toString(),
|
||||||
type: 'notice',
|
type: TOAST_TYPES.NOTICE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const newState = { ...state, notices: [...state.notices, noticeToast] };
|
const newState = { ...state, notices: [...state.notices, noticeToast] };
|
||||||
|
|
|
@ -41,9 +41,8 @@ const config = {
|
||||||
alias: {
|
alias: {
|
||||||
MainRoot: path.resolve(__dirname, '../'),
|
MainRoot: path.resolve(__dirname, '../'),
|
||||||
ClientRoot: path.resolve(__dirname, './src'),
|
ClientRoot: path.resolve(__dirname, './src'),
|
||||||
// TODO: change to '@hot-loader/react-dom' when v16.13.1 is released
|
// TODO: uncomment when v16.13.1 is released https://stackoverflow.com/a/62671689/12942752
|
||||||
// https://stackoverflow.com/a/62671689/12942752
|
// 'react-dom': '@hot-loader/react-dom',
|
||||||
'react-dom': 'react-dom',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
|
|
|
@ -165,13 +165,12 @@ func (l *queryLog) parseSearchParams(r *http.Request) (*searchParams, error) {
|
||||||
|
|
||||||
if limit, err := strconv.ParseInt(q.Get("limit"), 10, 64); err == nil {
|
if limit, err := strconv.ParseInt(q.Get("limit"), 10, 64); err == nil {
|
||||||
p.limit = int(limit)
|
p.limit = int(limit)
|
||||||
|
|
||||||
// If limit or offset are specified explicitly, we should change the default behavior
|
|
||||||
// and scan all log records until we found enough log entries
|
|
||||||
p.maxFileScanEntries = 0
|
|
||||||
}
|
}
|
||||||
if offset, err := strconv.ParseInt(q.Get("offset"), 10, 64); err == nil {
|
if offset, err := strconv.ParseInt(q.Get("offset"), 10, 64); err == nil {
|
||||||
p.offset = int(offset)
|
p.offset = int(offset)
|
||||||
|
|
||||||
|
// If we don't use "olderThan" and use offset/limit instead, we should change the default behavior
|
||||||
|
// and scan all log records until we found enough log entries
|
||||||
p.maxFileScanEntries = 0
|
p.maxFileScanEntries = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue