logster 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/Gemfile +2 -0
  4. data/Guardfile +2 -0
  5. data/README.md +1 -1
  6. data/Rakefile +2 -0
  7. data/assets/javascript/client-app.js +69 -53
  8. data/assets/javascript/vendor.js +580 -559
  9. data/assets/stylesheets/client-app.css +1 -1
  10. data/client-app/README.md +2 -2
  11. data/client-app/app/components/env-tab.js +16 -36
  12. data/client-app/app/components/message-row.js +1 -1
  13. data/client-app/app/components/page-nav.js +30 -0
  14. data/client-app/app/components/patterns-list.js +2 -1
  15. data/client-app/app/controllers/index.js +68 -92
  16. data/client-app/app/controllers/show.js +6 -0
  17. data/client-app/app/models/group.js +20 -0
  18. data/client-app/app/models/message-collection.js +159 -57
  19. data/client-app/app/routes/index.js +0 -2
  20. data/client-app/app/routes/settings.js +3 -1
  21. data/client-app/app/styles/app.css +17 -2
  22. data/client-app/app/templates/components/env-tab.hbs +5 -7
  23. data/client-app/app/templates/components/message-info.hbs +13 -8
  24. data/client-app/app/templates/components/page-nav.hbs +13 -0
  25. data/client-app/app/templates/components/patterns-list.hbs +6 -4
  26. data/client-app/app/templates/index.hbs +45 -11
  27. data/client-app/app/templates/settings.hbs +10 -1
  28. data/client-app/app/templates/show.hbs +2 -0
  29. data/client-app/package-lock.json +2817 -1215
  30. data/client-app/package.json +12 -12
  31. data/client-app/tests/integration/components/env-tab-test.js +29 -8
  32. data/client-app/tests/integration/components/message-info-test.js +10 -2
  33. data/lib/examples/sidekiq_logster_reporter.rb +2 -0
  34. data/lib/logster.rb +2 -2
  35. data/lib/logster/base_store.rb +40 -4
  36. data/lib/logster/cache.rb +9 -8
  37. data/lib/logster/defer_logger.rb +2 -0
  38. data/lib/logster/group.rb +124 -0
  39. data/lib/logster/grouping_pattern.rb +29 -0
  40. data/lib/logster/ignore_pattern.rb +2 -0
  41. data/lib/logster/logger.rb +2 -0
  42. data/lib/logster/message.rb +3 -1
  43. data/lib/logster/middleware/reporter.rb +2 -2
  44. data/lib/logster/middleware/viewer.rb +12 -1
  45. data/lib/logster/pattern.rb +13 -0
  46. data/lib/logster/redis_store.rb +99 -10
  47. data/lib/logster/scheduler.rb +2 -0
  48. data/lib/logster/suppression_pattern.rb +5 -2
  49. data/lib/logster/version.rb +1 -1
  50. data/lib/logster/web.rb +2 -0
  51. data/logster.gemspec +4 -1
  52. data/test/examples/test_sidekiq_reporter_example.rb +2 -0
  53. data/test/fake_data/Gemfile +2 -0
  54. data/test/fake_data/generate.rb +2 -0
  55. data/test/logster/middleware/test_viewer.rb +3 -1
  56. data/test/logster/test_base_store.rb +2 -0
  57. data/test/logster/test_cache.rb +19 -12
  58. data/test/logster/test_defer_logger.rb +2 -0
  59. data/test/logster/test_group.rb +92 -0
  60. data/test/logster/test_ignore_pattern.rb +2 -0
  61. data/test/logster/test_logger.rb +3 -1
  62. data/test/logster/test_message.rb +2 -0
  63. data/test/logster/test_pattern.rb +2 -2
  64. data/test/logster/test_redis_rate_limiter.rb +2 -0
  65. data/test/logster/test_redis_store.rb +253 -0
  66. data/test/test_helper.rb +6 -0
  67. metadata +26 -6
@@ -1 +1 @@
1
- .divider,.message-info{border-bottom:1px solid #ddd}body{font-family:Arial,"Liberation Sans","DejaVu Sans",sans-serif;font-size:12px}body.mobile,body.mobile .message{font-size:14px}pre{font-family:"Roboto Mono",Consolas,Monaco,Ubuntu Mono,monospace}table.env-table tbody tr td{border-top:none;line-height:18px;height:18px;vertical-align:top}table.env-table,table.env-table table{border-spacing:0;border-collapse:collapse}table.env-table td{padding-right:5px}tbody tr{width:98%}.message-row{font-family:Roboto;display:flex}.pattern-wrapper .pattern-input,.settings-section .api-error{font-family:Consolas,"Roboto Mono",Monaco,Ubuntu Mono,monospace}.message-row .protected,.message-row .severity{text-align:center;width:25px;flex-grow:0;flex-shrink:0;font-size:12px}.message-row div{border-top:.5px #e9e9e9 solid;padding-top:1px;padding-bottom:1px;line-height:25px}.message-row .count{width:30px;flex-grow:0;flex-shrink:0;padding-right:4px;box-sizing:border-box;font-size:11px;font-weight:700;text-align:right}.message-row .message-body{flex-grow:1;flex-shrink:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:13px}.message-row .time{flex-grow:0;flex-shrink:0;color:#999;vertical-align:top;font-size:12px;padding-right:8px}.action-panel .search,.action-panel i.fa,.btn .fa,.btn span,.pattern-wrapper .shrink,.search-clear-all .clear,input,label span{vertical-align:middle}.message-row:hover{background-color:#f8f8f8;cursor:pointer}.message-row.selected{background-color:#dfdfdf}i.fatal{color:#e00}i.error{color:#900}i.warning{color:#feb800}.debug{color:#777}.btn,.tabs a{text-decoration:none;color:#333}.action-panel .search{border:1px solid #ddd;padding:3px;box-sizing:border-box}#log-table .show-more{text-align:center;height:30px;line-height:30px;text-decoration:none;background-color:#ddd;cursor:pointer;margin-top:8px}#overlay,.divider{cursor:row-resize}#bottom-panel{position:fixed;bottom:0;left:0;right:0;height:300px;background-color:#f1f1f1;padding:0 8px 8px;z-index:2}#bottom-panel.full{position:static;background-color:inherit;height:90%}#bottom-panel.full>div{padding-bottom:40px}#bottom-panel.full .tabs{display:none}#bottom-panel.full .message-info{position:static}#bottom-panel.full .message-info .content{display:block;position:static}#bottom-panel.full button.delete,.hidden{display:none}#bottom-panel.full .save,#bottom-panel.full .share{bottom:10px}#bottom-panel.full .message-actions button{margin-top:8px}#bottom-panel.full .message-actions{position:fixed;height:40px;width:100%;left:0;bottom:0;background-color:#eee;border-top:1px solid #dfdfdf;padding-left:10px}.divider,.tabs{border-top:1px solid #ddd}.message-actions{position:absolute;bottom:5px;right:0;margin-right:10px}.message-actions button{margin-left:5px}.divider{position:fixed;bottom:310px;left:0;right:0;height:15px;background-color:#fafafa}.divider div{margin:auto;width:24px;height:1px;background-color:#ccc;position:relative}.divider .line-1{top:5px}.divider .line-2{top:6px}.divider .line-3{top:7px}#top-panel{position:fixed;top:0;left:0;right:0;bottom:320px;overflow:auto}.action-panel,.message-info{position:absolute;left:0;right:0}.message-info{top:0}.action-panel{bottom:0;font-weight:700}.action-panel input{margin:0}.severity-filters label{margin-right:18px}.search-clear-all .clear{float:right}#log-table{margin:auto;width:99%}.message-info .env-table,.message-info pre{position:relative;margin:5px 10px 10px}#overlay{position:fixed;z-index:99999;top:0;bottom:0;left:0;right:0;opacity:0}.message-info .content,.tabs{position:absolute;left:0;right:0}.message-info .content{top:0;bottom:35px;overflow:auto;display:none}.message-info .content.active{display:block}.tabs{bottom:13px;list-style-type:none;margin:0 0 5px;padding:0 0 0 10px}.tabs a,.tabs li{position:relative}.tabs li{float:left;padding-right:5px;margin:0}.tabs a{top:6px;border:1px solid #ddd;border-top:none;border-bottom-left-radius:5px;border-bottom-right-radius:5px;padding:6px;background-color:#e1e1e1}.tabs a.active{border-top:1px solid #f1f1f1;background-color:#f1f1f1}.btn{display:inline-block;margin:0;padding:5px 12px;font-size:1em;line-height:0;text-align:center;cursor:pointer;transition:all .25s;background-color:#ddd;border:none;font-weight:400}.btn:hover{color:#000;background-color:#ccc}.btn .fa{margin-right:7px}.btn:active{text-shadow:none}.btn.danger:hover{background-color:#c63c1b;color:#eee}.btn.ok{background-color:#3781dc;color:#fff}.btn.ok:hover{background-color:#286dc2;color:#fff}.search-clear-all .clear,.search-clear-all .search{height:100%}.search-clear-all .clear,.search-clear-all .search,.severity-filters label{align-self:center}.search-clear-all .footer-btns .settings{margin:0 7px}.search-clear-all{display:flex;justify-content:space-between}.search-clear-all .search{min-width:0;flex-shrink:2}.footer-btns{flex-grow:0;flex-shrink:0}@media (min-width:770px){.search-clear-all,.severity-filters{height:100%}.more-wrapping,.severity-filters{display:flex}.severity-filters{float:left}.action-panel{padding:10px;box-sizing:border-box;height:42px}.message-info{bottom:42px}}@media (max-width:770px){.severity-filters{padding:10px}.search-clear-all{padding:0 10px 10px}.action-panel{height:69px}.message-info{bottom:69px}}@media (max-width:430px){.severity-filters{overflow-x:scroll;overflow-y:hidden;white-space:nowrap}.more-wrapping:after,.more-wrapping:before{content:"";position:absolute;height:18px}.more-wrapping:before{width:15px;margin-left:-10px;background:linear-gradient(to right,#f1f1f1 0,rgba(241,241,241,.001) 100%)}.more-wrapping:after{right:0;width:23px;background:linear-gradient(to left,#f1f1f1 0,rgba(241,241,241,.001) 100%)}}.btn.no-text .fa{margin:0}.btn[disabled]{opacity:.5}.actions-menu{position:absolute;background:#fafafa;display:inline-flex;flex-direction:column;bottom:27px;right:45px;width:115px;padding:5px 5px 0;box-shadow:0 4px 14px rgba(0,0,0,.15);z-index:3}.actions-menu button{margin:0 0 5px;height:27px}.nav-controls{padding:10px}#bottom-panel:not(.full) .nav-controls{position:sticky;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;top:0;background:#f1f1f1;z-index:1;border-bottom:1px solid #ddd}.env-number{margin:0 7px}.expand-list{text-decoration:underline;color:#00f;cursor:pointer}.settings-page{max-width:1110px;margin-right:auto;margin-left:auto;font-size:15px;padding:0 10px 70px}.settings-header{display:flex;align-items:center;margin-bottom:5px}.settings-header .header-title{flex-grow:1}.settings-header .header-logo{width:50px;height:50px}.settings-section{border-top:1px solid #ddd;padding-top:15px}.settings-section .section-title{margin-top:0}.settings-section .subsection-title{margin-bottom:10px}.settings-section .tip{font-style:italic;font-size:13px;color:grey}.settings-section .pattern-wrapper{margin-top:10px;font-size:16px;display:flex;height:33px}.settings-section .api-error{margin-top:5px;color:red}.pattern-wrapper .pattern-input{width:600px;font-size:inherit;padding:5px 0;height:100%;box-sizing:border-box;vertical-align:middle;flex-grow:1;flex-shrink:1}.retro-checkbox .checkbox{margin:0}.retro-checkbox{margin-top:7px;margin-bottom:15px}.btn.new-pattern{height:100%;line-height:18px}.pattern-wrapper .shrink{height:100%;width:40px;text-align:center;box-sizing:border-box;flex-grow:0;flex-shrink:0;margin-left:10px}.fa{opacity:.7}.pattern-wrapper .shrink.reset{background:unset;width:unset;padding:0;margin-left:8px}
1
+ .divider,.message-info,.nav-controls.group-nav{border-bottom:1px solid #ddd}body{font-family:Arial,"Liberation Sans","DejaVu Sans",sans-serif;font-size:12px}body.mobile,body.mobile .message{font-size:14px}pre{font-family:"Roboto Mono",Consolas,Monaco,Ubuntu Mono,monospace}table.env-table tbody tr td{border-top:none;line-height:18px;height:18px;vertical-align:top}table.env-table,table.env-table table{border-spacing:0;border-collapse:collapse}table.env-table td{padding-right:5px}tbody tr{width:98%}.message-row{font-family:Roboto;display:flex}.pattern-wrapper .pattern-input,.settings-section .api-error{font-family:Consolas,"Roboto Mono",Monaco,Ubuntu Mono,monospace}.message-row .protected,.message-row .severity{text-align:center;width:25px;flex-grow:0;flex-shrink:0;font-size:12px}.message-row div{border-top:.5px #e9e9e9 solid;padding-top:1px;padding-bottom:1px;line-height:25px}.message-row .count{width:30px;flex-grow:0;flex-shrink:0;padding-right:4px;box-sizing:border-box;font-size:11px;font-weight:700;text-align:right}.message-row .message-body{flex-grow:1;flex-shrink:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:13px}.message-row .time{flex-grow:0;flex-shrink:0;color:#999;vertical-align:top;font-size:12px;padding-right:8px}.action-panel .search,.action-panel i.fa,.btn .fa,.btn span,.pattern-wrapper .shrink,.search-clear-all .clear,input,label span{vertical-align:middle}.message-row:hover{background-color:#f8f8f8;cursor:pointer}.message-row.selected{background-color:#dfdfdf}i.fatal{color:#e00}i.error{color:#900}i.warning{color:#feb800}.debug{color:#777}.btn,.tabs a{text-decoration:none;color:#333}.action-panel .search{border:1px solid #ddd;padding:3px;box-sizing:border-box}#log-table .show-more{text-align:center;height:30px;line-height:30px;text-decoration:none;background-color:#ddd;cursor:pointer;margin-top:8px}#overlay,.divider{cursor:row-resize}#bottom-panel{position:fixed;bottom:0;left:0;right:0;height:300px;background-color:#f1f1f1;padding:0 8px 8px;z-index:2}#bottom-panel.full{position:static;background-color:inherit;height:90%}#bottom-panel.full>div{padding-bottom:40px}#bottom-panel.full .tabs{display:none}#bottom-panel.full .message-info{position:static}#bottom-panel.full .message-info .content{display:block;position:static}#bottom-panel.full button.delete,.hidden{display:none}#bottom-panel.full .save,#bottom-panel.full .share{bottom:10px}#bottom-panel.full .message-actions button{margin-top:8px}#bottom-panel.full .message-actions{position:fixed;height:40px;width:100%;left:0;bottom:0;background-color:#eee;border-top:1px solid #dfdfdf;padding-left:10px}.divider,.tabs{border-top:1px solid #ddd}.message-actions{position:absolute;bottom:5px;right:0;margin-right:10px}.message-actions button{margin-left:5px}.divider{position:fixed;bottom:310px;left:0;right:0;height:15px;background-color:#fafafa}.divider div{margin:auto;width:24px;height:1px;background-color:#ccc;position:relative}.divider .line-1{top:5px}.divider .line-2{top:6px}.divider .line-3{top:7px}#top-panel{position:fixed;top:0;left:0;right:0;bottom:320px;overflow:auto}.action-panel,.message-info,.nav-controls.group-nav{position:absolute;left:0;right:0}.message-info{top:0}#bottom-panel.group-view .message-info{top:43px}.action-panel{bottom:0;font-weight:700}.action-panel input{margin:0}.severity-filters label{margin-right:18px}.search-clear-all .clear{float:right}#log-table{margin:auto;width:99%}.message-info .env-table,.message-info pre{position:relative;margin:5px 10px 10px}#overlay{position:fixed;z-index:99999;top:0;bottom:0;left:0;right:0;opacity:0}.message-info .content,.tabs{position:absolute;left:0;right:0}.message-info .content{top:0;bottom:35px;overflow:auto;display:none}.message-info .content.active{display:block}.tabs{bottom:13px;list-style-type:none;margin:0 0 5px;padding:0 0 0 10px}.tabs a,.tabs li{position:relative}.tabs li{float:left;padding-right:5px;margin:0}.tabs a{top:6px;border:1px solid #ddd;border-top:none;border-bottom-left-radius:5px;border-bottom-right-radius:5px;padding:6px;background-color:#e1e1e1}.tabs a.active{border-top:1px solid #f1f1f1;background-color:#f1f1f1}.btn{display:inline-block;margin:0;padding:5px 12px;font-size:1em;line-height:0;text-align:center;cursor:pointer;transition:all .25s;background-color:#ddd;border:none;font-weight:400}.btn:hover{color:#000;background-color:#ccc}.btn .fa{margin-right:7px}.btn:active{text-shadow:none}.btn.danger:hover{background-color:#c63c1b;color:#eee}.btn.ok{background-color:#3781dc;color:#fff}.btn.ok:hover{background-color:#286dc2;color:#fff}.search-clear-all .clear,.search-clear-all .search{height:100%}.search-clear-all .clear,.search-clear-all .search,.severity-filters label{align-self:center}.search-clear-all .footer-btns .settings{margin:0 7px}.search-clear-all{display:flex;justify-content:space-between}.search-clear-all .search{min-width:0;flex-shrink:2}.footer-btns{flex-grow:0;flex-shrink:0}@media (min-width:770px){.search-clear-all,.severity-filters{height:100%}.more-wrapping,.severity-filters{display:flex}.severity-filters{float:left}.action-panel{padding:10px;box-sizing:border-box;height:42px}.message-info{bottom:42px}}@media (max-width:770px){.severity-filters{padding:10px}.search-clear-all{padding:0 10px 10px}.action-panel{height:69px}.message-info{bottom:69px}}@media (max-width:430px){.severity-filters{overflow-x:scroll;overflow-y:hidden;white-space:nowrap}.more-wrapping:after,.more-wrapping:before{content:"";position:absolute;height:18px}.more-wrapping:before{width:15px;margin-left:-10px;background:linear-gradient(to right,#f1f1f1 0,rgba(241,241,241,.001) 100%)}.more-wrapping:after{right:0;width:23px;background:linear-gradient(to left,#f1f1f1 0,rgba(241,241,241,.001) 100%)}}.btn.no-text .fa{margin:0}.btn[disabled]{opacity:.5}.actions-menu{position:absolute;background:#fafafa;display:inline-flex;flex-direction:column;bottom:27px;right:45px;width:115px;padding:5px 5px 0;box-shadow:0 4px 14px rgba(0,0,0,.15);z-index:3}.actions-menu button{margin:0 0 5px;height:27px}.nav-controls{padding:10px}#bottom-panel:not(.full) .nav-controls.env-nav{position:sticky;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;top:0;background:#f1f1f1;z-index:1;border-bottom:1px solid #ddd}.current-number{margin:0 7px}.expand-list{text-decoration:underline;color:#00f;cursor:pointer}.settings-page{max-width:1110px;margin-right:auto;margin-left:auto;font-size:15px;padding:0 10px 70px}.settings-header{display:flex;align-items:center;margin-bottom:5px}.settings-header .header-title{flex-grow:1}.settings-header .header-logo{width:50px;height:50px}.settings-section{border-top:1px solid #ddd;padding-top:15px}.settings-section .section-title{margin-top:0}.settings-section .subsection-title{margin-bottom:10px}.settings-section .tip{font-style:italic;font-size:13px;color:grey}.settings-section .pattern-wrapper{margin-top:10px;font-size:16px;display:flex;height:33px}.settings-section .api-error{margin-top:5px;color:red}.pattern-wrapper .pattern-input{width:600px;font-size:inherit;padding:5px 0;height:100%;box-sizing:border-box;vertical-align:middle;flex-grow:1;flex-shrink:1}.retro-checkbox .checkbox{margin:0}.retro-checkbox{margin-top:7px;margin-bottom:15px}.btn.new-pattern{height:100%;line-height:18px}.pattern-wrapper .shrink{height:100%;width:40px;text-align:center;box-sizing:border-box;flex-grow:0;flex-shrink:0;margin-left:10px}.fa{opacity:.7}.pattern-wrapper .shrink.reset{background:unset;width:unset;padding:0;margin-left:8px}.grouping-patterns button.new-pattern{margin-top:10px}
@@ -21,8 +21,8 @@ You will need the following things properly installed on your computer.
21
21
  ## Running / Development
22
22
 
23
23
  * `ember serve`
24
- * Visit your app at [http://localhost:4200](http://localhost:4200).
25
- * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests).
24
+ * Visit your app at [http://localhost:4200/logs](http://localhost:4200).
25
+ * Visit your tests at [http://localhost:4200/logs/tests](http://localhost:4200/tests).
26
26
 
27
27
  ### Code Generators
28
28
 
@@ -4,32 +4,32 @@ import { buildHashString } from "client-app/lib/utilities";
4
4
  import Preload from "client-app/lib/preload";
5
5
 
6
6
  export default Component.extend({
7
- current: 1,
8
-
9
7
  didUpdateAttrs() {
10
- this.setProperties({
11
- current: 1,
12
- expanded: null
13
- });
8
+ this.set("expanded", null);
14
9
  },
15
10
 
11
+ currentEnv: computed("isEnvArray", "currentEnvPosition", function() {
12
+ if (this.isEnvArray) {
13
+ return this.message.env[this.currentEnvPosition];
14
+ } else {
15
+ return this.message.env;
16
+ }
17
+ }),
18
+
16
19
  isEnvArray: computed("message.env", function() {
17
20
  return Array.isArray(this.get("message.env"));
18
21
  }),
19
22
 
20
- html: computed("isEnvArray", "current", "expanded.[]", function() {
21
- if (!this.get("isEnvArray")) {
23
+ html: computed("isEnvArray", "currentEnv", "expanded.[]", function() {
24
+ if (!this.isEnvArray) {
22
25
  return buildHashString(this.get("message.env"));
23
26
  } else {
24
- const currentEnv = Em.$.extend(
25
- {},
26
- this.get("message.env")[this.get("current") - 1]
27
- );
27
+ const currentEnv = Em.$.extend({}, this.currentEnv);
28
28
  const expandableKeys = Preload.get("env_expandable_keys") || [];
29
29
  expandableKeys.forEach(key => {
30
30
  if (currentEnv.hasOwnProperty(key) && !Array.isArray(currentEnv[key])) {
31
31
  const list = [currentEnv[key]];
32
- this.get("message.env").forEach(env => {
32
+ this.message.env.forEach(env => {
33
33
  if (env[key] && list.indexOf(env[key]) === -1) {
34
34
  list.push(env[key]);
35
35
  }
@@ -37,7 +37,7 @@ export default Component.extend({
37
37
  currentEnv[key] = list.length > 1 ? list : list[0];
38
38
  }
39
39
  });
40
- return buildHashString(currentEnv, false, this.get("expanded") || []);
40
+ return buildHashString(currentEnv, false, this.expanded || []);
41
41
  }
42
42
  }),
43
43
 
@@ -50,31 +50,11 @@ export default Component.extend({
50
50
  $elem.hasClass("expand-list")
51
51
  ) {
52
52
  e.preventDefault();
53
- if (!this.get("expanded")) {
53
+ if (!this.expanded) {
54
54
  this.set("expanded", [dataKey]);
55
55
  } else {
56
- this.get("expanded").pushObject(dataKey);
56
+ this.expanded.pushObject(dataKey);
57
57
  }
58
58
  }
59
- },
60
-
61
- disableBackButtons: computed("current", function() {
62
- return this.get("current") === 1;
63
- }),
64
-
65
- disableForwardButtons: computed("current", "message.env.length", function() {
66
- return this.get("current") === this.get("message.env.length");
67
- }),
68
-
69
- actions: {
70
- takeStep(dir) {
71
- const amount = dir === "back" ? -1 : 1;
72
- this.set("current", this.get("current") + amount);
73
- },
74
-
75
- bigJump(dir) {
76
- const newCurrent = dir === "back" ? 1 : this.get("message.env.length");
77
- this.set("current", newCurrent);
78
- }
79
59
  }
80
60
  });
@@ -13,7 +13,7 @@ export default Component.extend({
13
13
  ],
14
14
 
15
15
  click() {
16
- this.selectedMessage(this.get("model"));
16
+ this.selectRow();
17
17
  },
18
18
 
19
19
  willInsertElement() {
@@ -0,0 +1,30 @@
1
+ import Component from "@ember/component";
2
+ import { computed } from "@ember/object";
3
+ import { equal } from "@ember/object/computed";
4
+
5
+ export default Component.extend({
6
+ classNames: ["nav-controls"],
7
+ classNameBindings: ["extraClasses"],
8
+ disableBackButtons: equal("position", 0),
9
+
10
+ disableForwardButtons: computed("position", "list.length", function() {
11
+ return this.position === this.get("list.length") - 1;
12
+ }),
13
+
14
+ displayNumber: computed("position", function() {
15
+ return this.position + 1;
16
+ }),
17
+
18
+ actions: {
19
+ takeStep(dir) {
20
+ const amount = dir === "back" ? -1 : 1;
21
+ const newPos = this.position + amount;
22
+ this.navigate(newPos);
23
+ },
24
+
25
+ bigJump(dir) {
26
+ const newPos = dir === "back" ? 0 : this.get("list.length") - 1;
27
+ this.navigate(newPos);
28
+ }
29
+ }
30
+ });
@@ -1,11 +1,12 @@
1
1
  import Component from "@ember/component";
2
- import { not } from "@ember/object/computed";
2
+ import { not, equal } from "@ember/object/computed";
3
3
  import { computed } from "@ember/object";
4
4
  import Pattern from "client-app/models/pattern-item";
5
5
  import { ajax } from "client-app/lib/utilities";
6
6
 
7
7
  export default Component.extend({
8
8
  immutable: not("mutable"),
9
+ showCounter: equal("key", "suppression"),
9
10
 
10
11
  init() {
11
12
  this._super(...arguments);
@@ -1,6 +1,6 @@
1
1
  import Controller from "@ember/controller";
2
2
  import { ajax } from "client-app/lib/utilities";
3
- import { observer, computed } from "@ember/object";
3
+ import { computed } from "@ember/object";
4
4
  import Preload from "client-app/lib/preload";
5
5
  import { debounce } from "@ember/runloop";
6
6
 
@@ -11,8 +11,7 @@ export default Controller.extend({
11
11
  showErr: true,
12
12
  showFatal: true,
13
13
  search: "",
14
- currentMessage: Em.computed.alias("model.currentMessage"),
15
- currentTab: null,
14
+ queryParams: ["search"],
16
15
 
17
16
  showSettings: computed(function() {
18
17
  return Preload.get("patterns_enabled");
@@ -27,131 +26,108 @@ export default Controller.extend({
27
26
  return this.site.isMobile;
28
27
  }),
29
28
 
30
- fetchEnv() {
31
- const message = this.get("currentMessage");
32
- if (message) {
33
- this.set("loadingEnv", true);
34
- return ajax(`/fetch-env/${message.key}.json`)
35
- .then(env => message.set("env", env))
36
- .always(() => this.set("loadingEnv", false));
37
- }
38
- },
39
-
40
29
  actions: {
41
30
  expandMessage(message) {
42
31
  message.expand();
43
32
  },
44
33
 
45
- selectMessage(message) {
46
- const old = this.get("currentMessage");
47
- if (old) {
48
- old.set("selected", false);
49
- }
50
-
51
- message.set("selected", true);
52
- this.setProperties({
53
- currentMessage: message,
54
- loadingEnv: false
55
- });
56
- if (!message.env && this.currentTab === "env") {
57
- this.fetchEnv();
58
- }
34
+ selectRowAction(row, opts = {}) {
35
+ this.model.selectRow(row, opts);
59
36
  },
60
37
 
61
- tabChanged(newTab) {
62
- this.setProperties({
63
- currentTab: newTab,
64
- loadingEnv: false
65
- });
66
- if (newTab === "env" && !this.get("currentMessage.env")) {
67
- this.fetchEnv();
68
- }
38
+ tabChangedAction(newTab) {
39
+ this.model.tabChanged(newTab);
69
40
  },
70
41
 
71
42
  showMoreBefore() {
72
- this.get("model").showMoreBefore();
43
+ this.model.showMoreBefore();
73
44
  },
74
45
 
75
46
  loadMore() {
76
- return this.get("model").loadMore();
47
+ return this.model.loadMore();
77
48
  },
78
49
 
79
50
  clear() {
80
51
  if (confirm("Clear the logs?\n\nCancel = No, OK = Clear")) {
81
52
  ajax("/clear", { type: "POST" }).then(() => {
82
- this.get("model").reload();
53
+ this.model.reload();
83
54
  });
84
55
  }
85
56
  },
86
57
 
87
58
  removeMessage(msg) {
88
- const messages = this.get("model");
89
- messages.destroy(msg);
59
+ const group = this.model.currentRow.group ? this.model.currentRow : null;
60
+ const rows = this.model.rows;
61
+ const idx = group ? rows.indexOf(group) : rows.indexOf(msg);
62
+
63
+ msg.destroy();
64
+ msg.set("selected", false);
65
+ this.model.set("total", this.model.total - 1);
66
+ let removedRow = false;
67
+ let messageIndex = 0;
68
+
69
+ if (group) {
70
+ messageIndex = group.messages.indexOf(msg);
71
+ group.messages.removeObject(msg);
72
+ messageIndex = Math.min(messageIndex, group.messages.length - 1);
73
+ group.decrementProperty("count");
74
+ if (group.messages.length === 0) {
75
+ rows.removeObject(group);
76
+ removedRow = true;
77
+ }
78
+ } else {
79
+ rows.removeObject(msg);
80
+ removedRow = true;
81
+ }
82
+
83
+ if (removedRow) {
84
+ if (idx > 0) {
85
+ this.model.selectRow(rows[idx - 1]);
86
+ } else if (this.model.total > 0) {
87
+ this.model.selectRow(rows[0]);
88
+ } else {
89
+ this.model.reload();
90
+ }
91
+ } else if (group) {
92
+ this.model.selectRow(rows[idx], { messageIndex });
93
+ }
90
94
  },
91
95
 
92
96
  solveMessage(msg) {
93
- const messages = this.get("model");
94
- messages.solve(msg);
95
- }
96
- },
97
+ this.model.solve(msg);
98
+ },
97
99
 
98
- updateSelectedMessage() {
99
- const currentKey = this.get("currentMessage.key");
100
- const messages = this.get("model.messages");
101
- if (currentKey && messages) {
102
- const match = messages.find(m => m.key === currentKey);
103
- if (match) {
104
- match.set("selected", true);
105
- } else {
106
- this.set("currentMessage", null);
107
- }
108
- }
109
- },
100
+ groupedMessageChangedAction(newPosition) {
101
+ this.model.groupedMessageChanged(newPosition);
102
+ },
110
103
 
111
- filter: computed(
112
- "showDebug",
113
- "showInfo",
114
- "showWarn",
115
- "showErr",
116
- "showFatal",
117
- function() {
104
+ envChangedAction(newPosition) {
105
+ this.model.envChanged(newPosition);
106
+ },
107
+
108
+ updateFilter(name) {
109
+ this.toggleProperty(name);
118
110
  const filter = [];
119
111
  ["Debug", "Info", "Warn", "Err", "Fatal"].forEach((severity, index) => {
120
112
  if (this.get(`show${severity}`)) {
121
113
  filter.push(index);
122
114
  }
123
115
  });
116
+ filter.push(5); // always show unknown, rare
117
+ this.model.set("filter", filter);
118
+ this.model.reload().then(() => this.model.updateSelectedRow());
119
+ },
124
120
 
125
- // always show unknown, rare
126
- filter.push(5);
127
- return filter;
128
- }
129
- ),
130
-
131
- filterChanged: observer("filter.length", function() {
132
- const filter = this.get("filter");
133
- const model = this.get("model");
134
- model.set("filter", filter);
135
- if (filter && this.get("initialized")) {
136
- model.reload().then(() => this.updateSelectedMessage());
137
- }
138
- }),
139
-
140
- doSearch(term) {
141
- const model = this.get("model");
142
- model.set("search", term);
143
-
144
- if (this.get("initialized")) {
145
- model.reload().then(() => this.updateSelectedMessage());
121
+ updateSearch(term) {
122
+ if (term && term.length === 1) {
123
+ return;
124
+ }
125
+ debounce(this, this.doSearch, term, 250);
146
126
  }
147
127
  },
148
128
 
149
- searchChanged: observer("search", function() {
150
- const term = this.search;
151
- const termSize = term && term.length;
152
- if (termSize && termSize === 1) {
153
- return;
154
- }
155
- debounce(this, this.doSearch, term, 250);
156
- })
129
+ doSearch(term) {
130
+ this.model.set("search", term);
131
+ this.model.reload().then(() => this.model.updateSelectedRow());
132
+ }
157
133
  });
@@ -1,6 +1,8 @@
1
1
  import Controller from "@ember/controller";
2
2
 
3
3
  export default Controller.extend({
4
+ envPosition: 0,
5
+
4
6
  actions: {
5
7
  protect() {
6
8
  this.get("model").protect();
@@ -8,6 +10,10 @@ export default Controller.extend({
8
10
 
9
11
  unprotect() {
10
12
  this.get("model").unprotect();
13
+ },
14
+
15
+ envChanged(newPosition) {
16
+ this.set("envPosition", newPosition);
11
17
  }
12
18
  }
13
19
  });
@@ -0,0 +1,20 @@
1
+ import Message from "client-app/models/message";
2
+ import { default as EmberObject, computed } from "@ember/object";
3
+ import { reads } from "@ember/object/computed";
4
+
5
+ export default EmberObject.extend({
6
+ selected: false,
7
+ showCount: true,
8
+ key: reads("regex"),
9
+ displayMessage: reads("messages.firstObject.message"),
10
+
11
+ init() {
12
+ this._super(...arguments);
13
+ const messages = this.messages.map(m => Message.create(m));
14
+ this.set("messages", messages);
15
+ },
16
+
17
+ glyph: computed(function() {
18
+ return "<i class='fa fa-clone group'></i>";
19
+ })
20
+ });
@@ -1,14 +1,43 @@
1
1
  import { ajax, increaseTitleCount } from "client-app/lib/utilities";
2
2
  import Message from "client-app/models/message";
3
+ import Group from "client-app/models/group";
3
4
  import { compare } from "@ember/utils";
4
- import { computed } from "@ember/object";
5
+ import { default as EmberObject, computed } from "@ember/object";
6
+ import { A } from "@ember/array";
5
7
 
6
8
  const BATCH_SIZE = 50;
9
+ const DEFAULT_FILTER = [0, 1, 2, 3, 4, 5];
7
10
 
8
- export default Em.Object.extend({
9
- messages: Em.A(),
10
- currentMessage: null,
11
+ export default EmberObject.extend({
11
12
  total: 0,
13
+ rows: null,
14
+ currentRow: null,
15
+ currentTab: null,
16
+ currentEnvPosition: 0,
17
+ currentGroupedMessagesPosition: 0,
18
+
19
+ init() {
20
+ this._super(...arguments);
21
+ this.setProperties({
22
+ filter: DEFAULT_FILTER,
23
+ search: "",
24
+ rows: A()
25
+ });
26
+ },
27
+
28
+ currentMessage: computed(
29
+ "currentRow",
30
+ "currentGroupedMessagesPosition",
31
+ function() {
32
+ const row = this.currentRow;
33
+ const position = this.currentGroupedMessagesPosition;
34
+ if (row && row.group) {
35
+ return row.messages[position];
36
+ } else {
37
+ return row;
38
+ }
39
+ }
40
+ ),
12
41
 
13
42
  solve(message) {
14
43
  message.solve().then(() => {
@@ -16,25 +45,85 @@ export default Em.Object.extend({
16
45
  });
17
46
  },
18
47
 
19
- destroy(message) {
20
- const messages = this.get("messages");
21
- const idx = messages.indexOf(message);
22
- message.destroy();
23
- message.set("selected", false);
24
- this.set("total", this.get("total") - 1);
25
- this.get("messages").removeObject(message);
26
-
27
- if (idx > 0) {
28
- message = messages[idx - 1];
29
- message.set("selected", true);
30
- this.set("currentMessage", message);
31
- } else {
32
- if (this.get("total") > 0) {
33
- message = messages[0];
34
- message.set("selected", true);
35
- this.set("currentMessage", message);
48
+ selectRow(row, opts = {}) {
49
+ const old = this.currentRow;
50
+ if (old) {
51
+ old.set("selected", false);
52
+ }
53
+ row.set("selected", true);
54
+ const currentGroupedMessagesPosition = opts["messageIndex"] || 0;
55
+ const shouldRefresh =
56
+ currentGroupedMessagesPosition === this.currentGroupedMessagesPosition;
57
+ this.setProperties({
58
+ currentRow: row,
59
+ loadingEnv: false,
60
+ currentGroupedMessagesPosition,
61
+ currentEnvPosition: 0
62
+ });
63
+ if (shouldRefresh)
64
+ this.notifyPropertyChange("currentGroupedMessagesPosition");
65
+ this.fetchEnv();
66
+ },
67
+
68
+ tabChanged(newTab) {
69
+ this.setProperties({
70
+ currentTab: newTab,
71
+ loadingEnv: false
72
+ });
73
+ this.fetchEnv();
74
+ },
75
+
76
+ groupedMessageChanged(newPosition) {
77
+ this.setProperties({
78
+ currentGroupedMessagesPosition: newPosition,
79
+ currentEnvPosition: 0
80
+ });
81
+ this.fetchEnv();
82
+ },
83
+
84
+ envChanged(newPosition) {
85
+ this.set("currentEnvPosition", newPosition);
86
+ this.fetchEnv();
87
+ },
88
+
89
+ fetchEnv() {
90
+ const message = this.currentMessage;
91
+ if (message && !message.env && this.currentTab === "env") {
92
+ this.set("loadingEnv", true);
93
+ return ajax(`/fetch-env/${message.key}.json`)
94
+ .then(env => message.set("env", env))
95
+ .always(() => this.set("loadingEnv", false));
96
+ }
97
+ },
98
+
99
+ findEquivalentMessageIndex(row) {
100
+ let messageIndex = 0;
101
+ if (
102
+ row &&
103
+ row.group &&
104
+ this.currentRow &&
105
+ this.currentRow.group &&
106
+ row.key === this.currentRow.key
107
+ ) {
108
+ messageIndex = row.messages.mapBy("key").indexOf(this.currentMessage.key);
109
+ messageIndex = Math.max(0, messageIndex);
110
+ }
111
+ return messageIndex;
112
+ },
113
+
114
+ updateSelectedRow() {
115
+ const currentKey = this.get("currentRow.key");
116
+ if (currentKey && this.rows) {
117
+ const match = this.rows.find(m => m.key === currentKey);
118
+ if (match) {
119
+ const messageIndex = this.findEquivalentMessageIndex(match);
120
+ this.selectRow(match, { messageIndex });
36
121
  } else {
37
- this.reload();
122
+ this.setProperties({
123
+ currentRow: null,
124
+ currentEnvPosition: 0,
125
+ currentGroupedMessagesPosition: 0
126
+ });
38
127
  }
39
128
  }
40
129
  },
@@ -43,13 +132,12 @@ export default Em.Object.extend({
43
132
  opts = opts || {};
44
133
 
45
134
  const data = {
46
- filter: this.get("filter").join("_")
135
+ filter: this.filter.join("_")
47
136
  };
48
137
 
49
- const search = this.get("search");
50
- if (!_.isEmpty(search)) {
51
- data.search = search;
52
- const regexSearch = this.get("regexSearch");
138
+ if (!_.isEmpty(this.search)) {
139
+ data.search = this.search;
140
+ const regexSearch = this.regexSearch;
53
141
  if (regexSearch) {
54
142
  data.regex_search = "true";
55
143
  }
@@ -57,6 +145,9 @@ export default Em.Object.extend({
57
145
 
58
146
  if (opts.before) {
59
147
  data.before = opts.before;
148
+ if (opts.knownGroups) {
149
+ data.known_groups = opts.knownGroups;
150
+ }
60
151
  }
61
152
 
62
153
  if (opts.after) {
@@ -70,32 +161,32 @@ export default Em.Object.extend({
70
161
  .then(data => {
71
162
  // guard against race: ensure the results we're trying to apply
72
163
  // match the current search terms
73
- if (compare(data.filter, this.get("filter")) != 0) {
164
+ if (compare(data.filter, this.filter) != 0) {
74
165
  return;
75
166
  }
76
- if (compare(data.search, this.get("search")) != 0) {
167
+ if (compare(data.search, this.search) != 0) {
77
168
  return;
78
169
  }
79
170
 
80
171
  if (data.messages.length > 0) {
81
- const newRows = this.toMessages(data.messages);
82
- const messages = this.get("messages");
172
+ const newRows = this.toObjects(data.messages);
173
+ const rows = this.rows;
83
174
  if (opts.before) {
84
- messages.unshiftObjects(newRows);
175
+ rows.unshiftObjects(newRows);
85
176
  } else {
86
- newRows.forEach(nmsg => {
87
- messages.forEach(emsg => {
88
- if (emsg.key == nmsg.key) {
89
- messages.removeObject(emsg);
90
- if (this.get("currentMessage") === emsg) {
177
+ newRows.forEach(nrow => {
178
+ rows.forEach(erow => {
179
+ if (erow.key === nrow.key) {
180
+ rows.removeObject(erow);
181
+ if (this.currentRow === erow) {
91
182
  // TODO would updateFromJson() work here?
92
- this.set("currentMessage", nmsg);
93
- nmsg.set("selected", emsg.get("selected"));
183
+ const messageIndex = this.findEquivalentMessageIndex(nrow);
184
+ this.selectRow(nrow, { messageIndex });
94
185
  }
95
186
  }
96
187
  });
97
188
  });
98
- messages.addObjects(newRows);
189
+ rows.addObjects(newRows);
99
190
  if (newRows.length > 0) {
100
191
  increaseTitleCount(newRows.length);
101
192
  }
@@ -109,7 +200,7 @@ export default Em.Object.extend({
109
200
 
110
201
  reload() {
111
202
  this.set("total", 0);
112
- this.get("messages").clear();
203
+ this.rows.clear();
113
204
 
114
205
  return this.load().then(data => this.updateCanLoadMore(data));
115
206
  },
@@ -126,43 +217,48 @@ export default Em.Object.extend({
126
217
  },
127
218
 
128
219
  loadMore() {
129
- const messages = this.get("messages");
130
- if (messages.length === 0) {
220
+ const rows = this.rows;
221
+ if (rows.length === 0) {
131
222
  this.load({});
132
223
  return;
133
224
  }
134
225
 
135
- const lastKey = messages[messages.length - 1].get("key");
226
+ const lastLog = rows[rows.length - 1];
227
+ const lastKey = lastLog.group ? lastLog.row_id : lastLog.key;
136
228
  this.load({
137
229
  after: lastKey
138
230
  });
139
231
  },
140
232
 
141
233
  hideCountInLoadMore: computed("search", "filter", function() {
142
- const search = this.get("search");
143
- const filter = this.get("filter");
144
- return (search && search.length > 0) || (filter && filter.length < 6);
234
+ const filter = this.filter;
235
+ return (
236
+ (this.search && this.search.length > 0) || (filter && filter.length < 6)
237
+ );
145
238
  }),
146
239
 
147
- moreBefore: computed("messages.length", "canLoadMore", function() {
148
- return this.get("messages.length") >= BATCH_SIZE && this.get("canLoadMore");
240
+ moreBefore: computed("rows.length", "canLoadMore", function() {
241
+ return this.get("rows.length") >= BATCH_SIZE && this.canLoadMore;
149
242
  }),
150
243
 
151
- totalBefore: computed("total", "messages.length", function() {
152
- return this.get("total") - this.get("messages").length;
244
+ totalBefore: computed("total", "rows.length", function() {
245
+ return this.total - this.rows.length;
153
246
  }),
154
247
 
155
248
  showMoreBefore: function() {
156
- const messages = this.get("messages");
157
- const firstKey = messages[0].get("key");
249
+ const rows = this.rows;
250
+ const firstLog = rows[0];
251
+ const firstKey = firstLog.group ? firstLog.row_id : firstLog.key;
252
+ const knownGroups = rows.filterBy("group").mapBy("regex");
158
253
 
159
254
  this.load({
160
- before: firstKey
255
+ before: firstKey,
256
+ knownGroups
161
257
  }).then(data => this.updateCanLoadMore(data));
162
258
  },
163
259
 
164
260
  regexSearch: computed("search", function() {
165
- const search = this.get("search");
261
+ const search = this.search;
166
262
  if (search && search.length > 2 && search[0] === "/") {
167
263
  const match = search.match(/\/(.*)\/(.*)/);
168
264
  if (match && match.length === 3) {
@@ -175,7 +271,13 @@ export default Em.Object.extend({
175
271
  }
176
272
  }),
177
273
 
178
- toMessages(messages) {
179
- return messages.map(m => Message.create(m));
274
+ toObjects(rows) {
275
+ return rows.map(m => {
276
+ if (m.group) {
277
+ return Group.create(m);
278
+ } else {
279
+ return Message.create(m);
280
+ }
281
+ });
180
282
  }
181
283
  });