logster 2.4.2 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
  });