METRON-1997 Replace Threat Triage Score Field Slider with Text Box (ruffle1986 via sardell) closes apache/metron#1334
diff --git a/metron-interface/metron-config/src/app/model/risk-level-rule.ts b/metron-interface/metron-config/src/app/model/risk-level-rule.ts
index 1cd4e8a..5d728d4 100644
--- a/metron-interface/metron-config/src/app/model/risk-level-rule.ts
+++ b/metron-interface/metron-config/src/app/model/risk-level-rule.ts
@@ -16,8 +16,8 @@
  * limitations under the License.
  */
 export class RiskLevelRule {
-  name: string = '';
-  comment: string = '';
-  rule: string = '';
-  score: number = 0;
+  name = '';
+  comment = '';
+  rule = '';
+  score = '';
 }
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts
index 1a027f5..a6b540f 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts
@@ -462,13 +462,13 @@
         riskLevelRules: [
           {
             rule: "IN_SUBNET(ip_dst_addr, '192.168.0.0/24')",
-            score: 3,
+            score: '3',
             name: 'test1',
             comment: 'This is a comment'
           },
           {
             rule: "user.type in [ 'admin', 'power' ] and asset.type == 'web'",
-            score: 3,
+            score: '3',
             name: 'test2',
             comment: 'This is another comment'
           }
@@ -480,13 +480,13 @@
     let expected: RiskLevelRule[] = [
       {
         rule: "IN_SUBNET(ip_dst_addr, '192.168.0.0/24')",
-        score: 3,
+        score: '3',
         name: 'test1',
         comment: 'This is a comment'
       },
       {
         rule: "user.type in [ 'admin', 'power' ] and asset.type == 'web'",
-        score: 3,
+        score: '3',
         name: 'test2',
         comment: 'This is another comment'
       }
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.html b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.html
index 512a990..21d0552 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.html
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.html
@@ -25,24 +25,56 @@
           <input type="text" class="form-control" name="ruleName" [(ngModel)]="newRiskLevelRule.name">
         </div>
         <div class="form-group">
-            <label attr.for="statement">TEXT</label>
-            <textarea rows="30" class="form-control" name="ruleValue" [(ngModel)]="newRiskLevelRule.rule" style="height: inherit"></textarea>
+            <label attr.for="statement">RULE</label>
+            <metron-config-ace-editor
+              name="ruleValue"
+              [(ngModel)]="newRiskLevelRule.rule"
+              [liveAutocompletion]="false"
+              [enableSnippets]="false"
+              [useWorker]="false"
+              [type]="'STELLAR'"
+              (onChange)="onRuleChange()"
+              [placeHolder]="'Enter a valid Stellar expression'"
+              [wrapLimitRangeMin]="null"
+              [wrapLimitRangeMax]="null"
+              data-qe-id="score-editor"
+            >
+            </metron-config-ace-editor>
+            <span *ngIf="!isRuleValid" class="warning-text">Invalid Stellar expression</span>
         </div>
     </form>
     <form class="form-inline">
-        <div class="form-group">
-            <label attr.for="statement" style="width: 100%">SCORE ADJUSTMENT</label>
-            <input class="score-slider" name="scoreSlider" type="range" min="0" max="100" [(ngModel)]="newRiskLevelRule.score" style="width: 72%; margin-right: 4px">
-            <div style="width: 25%; display: inline-block">
-                <metron-config-number-spinner name="scoreText" [(ngModel)]="newRiskLevelRule.score" [min]="0" [max]="100"> </metron-config-number-spinner>
-            </div>
+        <div class="form-group score">
+            <label class="score-label" attr.for="statement">SCORE ADJUSTMENT</label>
+            <metron-config-ace-editor
+              name="scoreExpression"
+              [(ngModel)]="newRiskLevelRule.score"
+              [liveAutocompletion]="false"
+              [enableSnippets]="false"
+              [useWorker]="false"
+              [type]="'STELLAR'"
+              (onChange)="onScoreChange()"
+              [placeHolder]="'Enter a valid Stellar expression'"
+              [wrapLimitRangeMin]="null"
+              [wrapLimitRangeMax]="null"
+              data-qe-id="score-editor"
+            >
+            </metron-config-ace-editor>
+            <span *ngIf="!isScoreValid" class="warning-text">Invalid Stellar expression</span>
         </div>
     </form>
 
     <div class="form-group">
         <div class="form-seperator-edit"></div>
         <div class="button-row">
-            <button type="submit" class="btn form-enable-disable-button" (click)="onSave()">SAVE</button>
+            <button
+              type="submit"
+              class="btn form-enable-disable-button"
+              (click)="onSave()"
+              data-qe-id="save-score"
+            >
+              SAVE
+            </button>
             <button class="btn form-enable-disable-button ml-1" (click)="onCancel()" >CANCEL</button>
         </div>
     </div>
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.scss b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.scss
index ca6c7d7..19d92c5 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.scss
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.scss
@@ -88,3 +88,24 @@
   background-color: $range-gradient-start;
 }
 
+.score {
+
+  &.form-group {
+    display: block;
+    width: 100%;
+  }
+  .score-label {
+    display: block;
+    margin-bottom: 5px;
+  }
+  .score-input {
+    width: 100%;
+  }
+}
+
+.warning-text {
+  color: #C0661D;
+  font-size: 12px;
+  font-family: Roboto-Medium;
+}
+
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.spec.ts
index 2f8cd24..d26a526 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.spec.ts
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.spec.ts
@@ -16,29 +16,44 @@
  * limitations under the License.
  */
 
-import {async, TestBed, ComponentFixture} from '@angular/core/testing';
+import {async, TestBed, ComponentFixture, inject} from '@angular/core/testing';
 import {SensorRuleEditorComponent} from './sensor-rule-editor.component';
 import {SharedModule} from '../../../shared/shared.module';
 import {NumberSpinnerComponent} from '../../../shared/number-spinner/number-spinner.component';
 import {RiskLevelRule} from '../../../model/risk-level-rule';
+import {AceEditorModule} from '../../../shared/ace-editor/ace-editor.module';
+import {StellarService} from '../../../service/stellar.service';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { AppConfigService } from 'app/service/app-config.service';
+import { By } from '@angular/platform-browser';
+import { Observable } from 'rxjs';
 
 describe('Component: SensorRuleEditorComponent', () => {
 
     let fixture: ComponentFixture<SensorRuleEditorComponent>;
     let component: SensorRuleEditorComponent;
+    let stellarService: StellarService;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            imports: [SharedModule
-            ],
+            imports: [SharedModule, AceEditorModule, HttpClientTestingModule],
             declarations: [ SensorRuleEditorComponent, NumberSpinnerComponent ],
             providers: [
-              SensorRuleEditorComponent
-            ]
+              SensorRuleEditorComponent,
+              StellarService,
+              {
+                provide: AppConfigService,
+                useValue: {
+                  appConfigStatic: {},
+                  getApiRoot: () => '/api/v1'
+                }
+            }]
         });
 
         fixture = TestBed.createComponent(SensorRuleEditorComponent);
         component = fixture.componentInstance;
+        stellarService = TestBed.get(StellarService);
+        fixture.detectChanges();
     }));
 
     it('should create an instance', () => {
@@ -48,6 +63,17 @@
     it('should edit rules', async(() => {
         let numCancelled = 0;
         let savedRule = new RiskLevelRule();
+
+        stellarService.validateRules = (expressions: string[]) => {
+          return new Observable((observer) => {
+            const response = {
+              [expressions[0]]: true,
+              [expressions[1]]: true,
+            };
+            observer.next(response);
+          });
+        }
+
         component.onCancelTextEditor.subscribe((cancelled: boolean) => {
           numCancelled++;
         });
@@ -55,20 +81,66 @@
           savedRule = rule;
         });
 
-        component.riskLevelRule =  {name: 'rule1', rule: 'initial rule', score: 1, comment: ''};
+        component.riskLevelRule =  {name: 'rule1', rule: 'initial rule', score: '1', comment: ''};
         component.ngOnInit();
         component.onSave();
-        let rule1 = Object.assign(new RiskLevelRule(), {name: 'rule1', rule: 'initial rule', score: 1, comment: ''});
+        let rule1 = Object.assign(new RiskLevelRule(), {name: 'rule1', rule: 'initial rule', score: '1', comment: ''});
         expect(savedRule).toEqual(rule1);
 
-        component.riskLevelRule = {name: 'rule2', rule: 'new rule', score: 2, comment: ''};
+        component.riskLevelRule = {name: 'rule2', rule: 'new rule', score: '2', comment: ''};
         component.ngOnInit();
         component.onSave();
-        let rule2 = Object.assign(new RiskLevelRule(), {name: 'rule2', rule: 'new rule', score: 2, comment: ''});
+        let rule2 = Object.assign(new RiskLevelRule(), {name: 'rule2', rule: 'new rule', score: '2', comment: ''});
         expect(savedRule).toEqual(rule2);
 
         expect(numCancelled).toEqual(0);
         component.onCancel();
         expect(numCancelled).toEqual(1);
     }));
+
+    it('should warn if either the rule or the score is invalid', inject(
+      [HttpTestingController],
+      (httpMock: HttpTestingController) => {
+        const saveButton = fixture.debugElement.query(By.css('[data-qe-id="save-score"]'));
+
+        component.newRiskLevelRule.rule = 'value > 10';
+        component.newRiskLevelRule.score = 'value &&&&';
+        fixture.detectChanges();
+        saveButton.nativeElement.click();
+        let validateRequest = httpMock.expectOne('/api/v1/stellar/validate/rules');
+        validateRequest.flush({
+          [component.newRiskLevelRule.rule]: true,
+          [component.newRiskLevelRule.score]: false
+        });
+        fixture.detectChanges();
+        let warning = fixture.debugElement.queryAll(By.css('.warning-text'));
+        expect(warning.length).toBe(1);
+
+        component.newRiskLevelRule.rule = 'value > 10';
+        component.newRiskLevelRule.score = 'value * 10';
+        fixture.detectChanges();
+        saveButton.nativeElement.click();
+        validateRequest = httpMock.expectOne('/api/v1/stellar/validate/rules');
+        validateRequest.flush({
+          [component.newRiskLevelRule.score]: true,
+          [component.newRiskLevelRule.rule]: true
+        });
+        fixture.detectChanges();
+        warning = fixture.debugElement.queryAll(By.css('.warning-text'));
+        expect(warning.length).toBe(0);
+
+        component.newRiskLevelRule.rule = 'value &&&&';
+        component.newRiskLevelRule.score = 'value &&& &&&&';
+        fixture.detectChanges();
+        saveButton.nativeElement.click();
+        validateRequest = httpMock.expectOne('/api/v1/stellar/validate/rules');
+        validateRequest.flush({
+          [component.newRiskLevelRule.score]: false,
+          [component.newRiskLevelRule.rule]: false
+        });
+        fixture.detectChanges();
+        warning = fixture.debugElement.queryAll(By.css('.warning-text'));
+        expect(warning.length).toBe(2);
+      }
+    ));
 });
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.ts
index 1bdfea1..260e1b0 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.ts
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.ts
@@ -17,6 +17,7 @@
  */
 import {Component, Input, EventEmitter, Output, OnInit} from '@angular/core';
 import {RiskLevelRule} from '../../../model/risk-level-rule';
+import {StellarService} from '../../../service/stellar.service';
 
 @Component({
   selector: 'metron-config-sensor-rule-editor',
@@ -31,19 +32,36 @@
   @Output() onCancelTextEditor: EventEmitter<boolean> = new EventEmitter<boolean>();
   @Output() onSubmitTextEditor: EventEmitter<RiskLevelRule> = new EventEmitter<RiskLevelRule>();
   newRiskLevelRule = new RiskLevelRule();
+  isScoreValid = true;
+  isRuleValid = true;
 
-  constructor() { }
+  constructor(private stellarService: StellarService) { }
 
   ngOnInit() {
     Object.assign(this.newRiskLevelRule, this.riskLevelRule);
   }
 
   onSave(): void {
-    this.onSubmitTextEditor.emit(this.newRiskLevelRule);
+    const score = this.newRiskLevelRule.score;
+    const rule = this.newRiskLevelRule.rule;
+    this.stellarService.validateRules([rule, score]).subscribe((response) => {
+      this.isScoreValid = !!response[score];
+      this.isRuleValid = !!response[rule];
+      if (this.isRuleValid && this.isScoreValid) {
+        this.onSubmitTextEditor.emit(this.newRiskLevelRule);
+      }
+    });
   }
 
   onCancel(): void {
     this.onCancelTextEditor.emit(true);
   }
 
+  onRuleChange = () => {
+    this.isRuleValid = true;
+  }
+
+  onScoreChange = () => {
+    this.isScoreValid = true;
+  }
 }
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.module.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.module.ts
index a99c8cf..16b059f 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.module.ts
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.module.ts
@@ -19,9 +19,10 @@
 import {SharedModule} from '../../../shared/shared.module';
 import {SensorRuleEditorComponent} from './sensor-rule-editor.component';
 import {NumberSpinnerModule} from '../../../shared/number-spinner/number-spinner.module';
+import {AceEditorModule} from '../../../shared/ace-editor/ace-editor.module';
 
 @NgModule ({
-  imports: [ SharedModule, NumberSpinnerModule ],
+  imports: [ SharedModule, NumberSpinnerModule, AceEditorModule ],
   declarations: [ SensorRuleEditorComponent ],
   exports: [ SensorRuleEditorComponent ]
 })
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html
index 96892f7..c3c72c6 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html
@@ -34,47 +34,21 @@
         <div class="threat-triage-summary">
             <div class="form-group">
                 <div class="rules-summary-title">Rules</div>
-                <div class="row mx-0">
-                    <div class="btn" (click)="onFilterChange(threatTriageFilter.HIGH)" [ngClass]="{'filter-button': filter != threatTriageFilter.HIGH, 'filter-button-selected': filter == threatTriageFilter.HIGH}">
-                        <i aria-hidden="true" class="fa fa-circle" style="color: red"></i> {{highAlerts}}
-                    </div>
-                    <div class="btn" (click)="onFilterChange(threatTriageFilter.MEDIUM)" [ngClass]="{'filter-button': filter != threatTriageFilter.MEDIUM, 'filter-button-selected': filter == threatTriageFilter.MEDIUM}">
-                        <i aria-hidden="true" class="fa fa-circle" style="color: orange"></i> {{mediumAlerts}}
-                    </div>
-                    <div class="btn" (click)="onFilterChange(threatTriageFilter.LOW)" [ngClass]="{'filter-button': filter != threatTriageFilter.LOW, 'filter-button-selected': filter == threatTriageFilter.LOW}">
-                        <i aria-hidden="true" class="fa fa-circle" style="color: khaki"></i> {{lowAlerts}}
-                    </div>
-                </div>
-                <div class="row mx-0 threat-triage-rules-sort">
-                    <span class="label">Sort by </span>
-                    <li class="nav-item dropdown">
-                        <span class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{{sortOrderOption[sortOrder].replace('_', ' ')}}</span>
-                        <div class="dropdown-menu bg-inverse">
-                            <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Highest_Score)">Highest Score</span>
-                            <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Lowest_Score)">Lowest Score</span>
-                            <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Highest_Name)">Highest Name</span>
-                            <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Lowest_Name)">Lowest Name</span>
-                        </div>
-                    </li>
-                </div>
             </div>
         </div>
         <div class="form-group threat-triage-rules-list">
-            <div *ngFor="let riskLevelRule of this.visibleRules">
-                <div class="row mx-0 py-0">
-                    <div class="threat-triage-rule-row" style="color: khaki; font-size: 30px; margin-top: -7px" [style.color]="getRuleColor(riskLevelRule)">
-                        <b>I</b>
-                    </div>
-                    <div class="threat-triage-rule-row" style="font-size: small; width: 8%">
+            <div class="threat-triage-rule" *ngFor="let riskLevelRule of this.visibleRules">
+                <div class="row mx-0 py-0 threat-triage-rule-wrapper">
+                    <div class="threat-triage-rule-row" style="font-size: small;" [title]="riskLevelRule.score">
                         {{ riskLevelRule.score }}
                     </div>
-                    <div class="threat-triage-rule-row threat-triage-rule-str">
+                    <div class="threat-triage-rule-row threat-triage-rule-str" [title]="getDisplayName(riskLevelRule)">
                         {{ getDisplayName(riskLevelRule) }}
                     </div>
-                    <div class="threat-triage-rule-row" style=""><i class="fa fa-i-cursor" aria-hidden="true"
-                                                                        style="cursor: pointer;" (click)="onEditRule(riskLevelRule)"></i></div>
-
-                    <div class="threat-triage-rule-row" style=""><i  class="fa fa-trash-o" aria-hidden="true" (click)="onDeleteRule(riskLevelRule)" style="cursor: pointer"></i></div>
+                    <div class="threat-triage-rule-row" style="">
+                        <i class="fa fa-i-cursor" aria-hidden="true" (click)="onEditRule(riskLevelRule)"></i>
+                        <i class="fa fa-trash-o" aria-hidden="true" (click)="onDeleteRule(riskLevelRule)"></i>
+                    </div>
                 </div>
                 <div class="form-seperator-edit"></div>
             </div>
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss
index bbc17a0..e2ce39b 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss
@@ -46,49 +46,11 @@
   padding-bottom: 5px;
 }
 
-.filter-button
-{
-  width: 32%;
-  background: $field-background;
-  border: 1px solid $form-button-border;
-  border-radius: .25em;
-  cursor: default;
-  color: $nav-active-color;
-  i {
-    font-size: smaller;
-  }
-}
-
-.filter-button-selected
-{
-  @extend .filter-button;
-  background-color: #006ea0;
-  color: #bdbdbd;
-}
-
 .threat-triage-aggregator
 {
   padding-bottom: 10px;
 }
 
-.threat-triage-rules-sort
-{
-  padding-top: 5px;
-  font-size: 12px;
-  font-family: Roboto-Regular;
-  .label
-  {
-    display: inline-block;
-    padding-right: 8px;
-  }
-  .dropdown
-  {
-    list-style: none;
-    display: inline-block;
-    color: $field-button-color;
-  }
-}
-
 .rules-summary-title
 {
   font-size: 15px;
@@ -104,10 +66,26 @@
   display: inline-block;
   position: relative;
   vertical-align: top;
+  padding: 0 10px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+
+  &:first-child {
+    padding-left: 0;
+  }
+
+  &:last-child {
+    padding-right: 0;
+    overflow: visible;
+    text-overflow: initial;
+  }
 
   .fa {
     color: $nav-active-color;
     font-size: large;
+    cursor: pointer;
+    margin: 0 3px;
   }
 }
 
@@ -124,14 +102,15 @@
 
 .threat-triage-rule-str
 {
-  width: 64%;
   padding-right: 10px;
   font-size: small;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
 }
 metron-config-sensor-rule-editor
 {
   @extend .flexbox-row-reverse;
 }
+
+.threat-triage-rule-wrapper {
+  display: flex;
+  flex-wrap: nowrap;
+}
\ No newline at end of file
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts
index 43e8e6b..8702ade 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts
@@ -18,16 +18,7 @@
 import { SimpleChange, SimpleChanges } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
 import { async, TestBed, ComponentFixture } from '@angular/core/testing';
-import {
-  SensorThreatTriageComponent,
-  SortOrderOption,
-  ThreatTriageFilter
-} from './sensor-threat-triage.component';
-import {
-  SensorEnrichmentConfig,
-  ThreatIntelConfig
-} from '../../model/sensor-enrichment-config';
-import { RiskLevelRule } from '../../model/risk-level-rule';
+import { SensorThreatTriageComponent } from './sensor-threat-triage.component';
 import { SensorEnrichmentConfigService } from '../../service/sensor-enrichment-config.service';
 import { Observable } from 'rxjs';
 import { SensorThreatTriageModule } from './sensor-threat-triage.module';
@@ -99,138 +90,4 @@
 
     fixture.destroy();
   }));
-
-  it('should get color', async(() => {
-    let sensorEnrichmentConfig = new SensorEnrichmentConfig();
-    sensorEnrichmentConfig.threatIntel = Object.assign(
-      new ThreatIntelConfig(),
-      {
-        triageConfig: {
-          riskLevelRules: {
-            ruleA: 15,
-            ruleB: 95,
-            ruleC: 50
-          },
-          aggregator: 'MAX',
-          aggregationConfig: {}
-        }
-      }
-    );
-    component.sensorEnrichmentConfig = sensorEnrichmentConfig;
-
-    let ruleA = { name: 'ruleA', rule: 'rule A', score: 15, comment: '' };
-    let ruleB = { name: 'ruleB', rule: 'rule B', score: 95, comment: '' };
-    let ruleC = { name: 'ruleC', rule: 'rule C', score: 50, comment: '' };
-
-    expect(component.getRuleColor(ruleA)).toEqual('khaki');
-    expect(component.getRuleColor(ruleB)).toEqual('red');
-    expect(component.getRuleColor(ruleC)).toEqual('orange');
-
-    fixture.destroy();
-  }));
-
-  it('should edit rules', async(() => {
-    let ruleA = { name: 'ruleA', rule: 'rule A', score: 15, comment: '' };
-    let ruleB = { name: 'ruleB', rule: 'rule B', score: 95, comment: '' };
-    let ruleC = { name: 'ruleC', rule: 'rule C', score: 50, comment: '' };
-    let ruleD = { name: 'ruleD', rule: 'rule D', score: 85, comment: '' };
-    let ruleE = { name: 'ruleE', rule: 'rule E', score: 5, comment: '' };
-    let ruleF = { name: 'ruleF', rule: 'rule F', score: 21, comment: '' };
-    let ruleG = { name: 'ruleG', rule: 'rule G', score: 100, comment: '' };
-
-    let sensorEnrichmentConfig = new SensorEnrichmentConfig();
-    sensorEnrichmentConfig.threatIntel = Object.assign(
-      new ThreatIntelConfig(),
-      {
-        triageConfig: {
-          riskLevelRules: [ruleA, ruleB, ruleC, ruleD, ruleE],
-          aggregator: 'MAX',
-          aggregationConfig: {}
-        }
-      }
-    );
-    component.sensorEnrichmentConfig = sensorEnrichmentConfig;
-
-    let changes: SimpleChanges = {
-      showThreatTriage: new SimpleChange(false, true, true)
-    };
-    component.ngOnChanges(changes);
-
-    // sorted by score high to low
-    expect(component.visibleRules).toEqual([ruleB, ruleD, ruleC, ruleA, ruleE]);
-    expect(component.lowAlerts).toEqual(2);
-    expect(component.mediumAlerts).toEqual(1);
-    expect(component.highAlerts).toEqual(2);
-
-    // sorted by name high to low
-    component.onSortOrderChange(SortOrderOption.Highest_Name);
-    expect(component.visibleRules).toEqual([ruleE, ruleD, ruleC, ruleB, ruleA]);
-
-    // sorted by score low to high
-    component.onSortOrderChange(SortOrderOption.Lowest_Score);
-    expect(component.visibleRules).toEqual([ruleE, ruleA, ruleC, ruleD, ruleB]);
-
-    // sorted by name low to high
-    component.onSortOrderChange(SortOrderOption.Lowest_Name);
-    expect(component.visibleRules).toEqual([ruleA, ruleB, ruleC, ruleD, ruleE]);
-
-    component.onNewRule();
-    expect(component.currentRiskLevelRule.name).toEqual('');
-    expect(component.currentRiskLevelRule.rule).toEqual('');
-    expect(component.currentRiskLevelRule.score).toEqual(0);
-    expect(component.showTextEditor).toEqual(true);
-
-    component.currentRiskLevelRule = new RiskLevelRule();
-    component.onCancelTextEditor();
-    expect(component.showTextEditor).toEqual(false);
-    expect(component.visibleRules).toEqual([ruleA, ruleB, ruleC, ruleD, ruleE]);
-
-    component.sortOrder = SortOrderOption.Lowest_Score;
-    component.onNewRule();
-    component.currentRiskLevelRule = ruleF;
-    expect(component.showTextEditor).toEqual(true);
-    component.onSubmitTextEditor(ruleF);
-    expect(component.visibleRules).toEqual([
-      ruleE,
-      ruleA,
-      ruleF,
-      ruleC,
-      ruleD,
-      ruleB
-    ]);
-    expect(component.lowAlerts).toEqual(2);
-    expect(component.mediumAlerts).toEqual(2);
-    expect(component.highAlerts).toEqual(2);
-    expect(component.showTextEditor).toEqual(false);
-
-    component.onDeleteRule(ruleE);
-    expect(component.visibleRules).toEqual([ruleA, ruleF, ruleC, ruleD, ruleB]);
-    expect(component.lowAlerts).toEqual(1);
-    expect(component.mediumAlerts).toEqual(2);
-    expect(component.highAlerts).toEqual(2);
-
-    component.onFilterChange(ThreatTriageFilter.LOW);
-    expect(component.visibleRules).toEqual([ruleA]);
-
-    component.onFilterChange(ThreatTriageFilter.MEDIUM);
-    expect(component.visibleRules).toEqual([ruleF, ruleC]);
-
-    component.onFilterChange(ThreatTriageFilter.HIGH);
-    expect(component.visibleRules).toEqual([ruleD, ruleB]);
-
-    component.onFilterChange(ThreatTriageFilter.HIGH);
-    expect(component.visibleRules).toEqual([ruleA, ruleF, ruleC, ruleD, ruleB]);
-
-    component.onEditRule(ruleC);
-    expect(component.currentRiskLevelRule).toEqual(ruleC);
-    expect(component.showTextEditor).toEqual(true);
-    component.onSubmitTextEditor(ruleG);
-    expect(component.visibleRules).toEqual([ruleA, ruleF, ruleD, ruleB, ruleG]);
-    expect(component.lowAlerts).toEqual(1);
-    expect(component.mediumAlerts).toEqual(1);
-    expect(component.highAlerts).toEqual(3);
-    expect(component.showTextEditor).toEqual(false);
-
-    fixture.destroy();
-  }));
 });
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts
index db32b04..c894c0c 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts
@@ -21,14 +21,6 @@
 import {RiskLevelRule} from '../../model/risk-level-rule';
 import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service';
 
-export enum SortOrderOption {
-  Lowest_Score, Highest_Score, Lowest_Name, Highest_Name
-}
-
-export enum ThreatTriageFilter {
-  NONE, LOW, MEDIUM, HIGH
-}
-
 @Component({
   selector: 'metron-config-sensor-threat-triage',
   templateUrl: './sensor-threat-triage.component.html',
@@ -39,23 +31,16 @@
 
   @Input() showThreatTriage: boolean;
   @Input() sensorEnrichmentConfig: SensorEnrichmentConfig;
-
   @Output() hideThreatTriage: EventEmitter<boolean> = new EventEmitter<boolean>();
+
   availableAggregators = [];
   visibleRules: RiskLevelRule[] = [];
-
   showTextEditor = false;
   currentRiskLevelRule: RiskLevelRule;
-
   lowAlerts = 0;
   mediumAlerts = 0;
   highAlerts = 0;
 
-  sortOrderOption = SortOrderOption;
-  sortOrder = SortOrderOption.Highest_Score;
-  threatTriageFilter = ThreatTriageFilter;
-  filter: ThreatTriageFilter = ThreatTriageFilter.NONE;
-
   constructor(private sensorEnrichmentConfigService: SensorEnrichmentConfigService) { }
 
   ngOnChanges(changes: SimpleChanges) {
@@ -69,8 +54,6 @@
     this.sensorEnrichmentConfigService.getAvailableThreatTriageAggregators().subscribe(results => {
       this.availableAggregators = results;
     });
-    this.updateBuckets();
-    this.onSortOrderChange(null);
   }
 
   onClose(): void {
@@ -111,92 +94,6 @@
     }
   }
 
-  updateBuckets() {
-    this.lowAlerts = 0;
-    this.mediumAlerts = 0;
-    this.highAlerts = 0;
-    for (let riskLevelRule of this.visibleRules) {
-      if (riskLevelRule.score <= 20) {
-        this.lowAlerts++;
-      } else if (riskLevelRule.score >= 80) {
-        this.highAlerts++;
-      } else {
-        this.mediumAlerts++;
-      }
-    }
-  }
-
-  getRuleColor(riskLevelRule: RiskLevelRule): string {
-    let color: string;
-    if (riskLevelRule.score <= 20) {
-      color = 'khaki';
-    } else if (riskLevelRule.score >= 80) {
-      color = 'red';
-    } else {
-      color = 'orange';
-    }
-    return color;
-  }
-
-  onSortOrderChange(sortOrder: any) {
-    if (sortOrder !== null) {
-      this.sortOrder = sortOrder;
-    }
-
-    // all comparisons with enums must be == and not ===
-    if (this.sortOrder == this.sortOrderOption.Highest_Score) {
-      this.visibleRules.sort((a, b) => {
-        return b.score - a.score;
-      });
-    } else if (this.sortOrder == SortOrderOption.Lowest_Score) {
-      this.visibleRules.sort((a, b) => {
-        return a.score - b.score;
-      });
-    } else if (this.sortOrder == SortOrderOption.Lowest_Name) {
-      this.visibleRules.sort((a, b) => {
-        let aName = a.name ? a.name : '';
-        let bName = b.name ? b.name : '';
-        if (aName.toLowerCase() >= bName.toLowerCase()) {
-          return 1;
-        } else if (aName.toLowerCase() < bName.toLowerCase()) {
-          return -1;
-        }
-      });
-    } else {
-      this.visibleRules.sort((a, b) => {
-        let aName = a.name ? a.name : '';
-        let bName = b.name ? b.name : '';
-        if (aName.toLowerCase() >= bName.toLowerCase()) {
-          return -1;
-        } else if (aName.toLowerCase() < bName.toLowerCase()) {
-          return 1;
-        }
-      });
-    }
-  }
-
-  onFilterChange(filter: ThreatTriageFilter) {
-    if (filter === this.filter) {
-      this.filter = ThreatTriageFilter.NONE;
-    } else {
-      this.filter = filter;
-    }
-    this.visibleRules = this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules.filter(riskLevelRule => {
-      if (this.filter === ThreatTriageFilter.NONE) {
-        return true;
-      } else {
-        if (this.filter === ThreatTriageFilter.HIGH) {
-          return riskLevelRule.score >= 80;
-        } else if (this.filter === ThreatTriageFilter.LOW) {
-          return riskLevelRule.score <= 20;
-        } else {
-          return riskLevelRule.score < 80 && riskLevelRule.score > 20;
-        }
-      }
-    });
-    this.onSortOrderChange(null);
-  }
-
   getDisplayName(riskLevelRule: RiskLevelRule): string {
     if (riskLevelRule.name) {
       return riskLevelRule.name;
diff --git a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts
index 8422544..21c62f1 100644
--- a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts
+++ b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 /// <reference path="../../../../node_modules/@types/ace/index.d.ts" />
-import { Component, AfterViewInit, ViewChild, ElementRef, forwardRef, Input} from '@angular/core';
+import { Component, AfterViewInit, ViewChild, ElementRef, forwardRef, Input, Output, EventEmitter} from '@angular/core';
 import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
 import {AutocompleteOption} from '../../model/autocomplete-option';
 
@@ -38,9 +38,15 @@
 
   inputJson: any = '';
   aceConfigEditor: AceAjax.Editor;
-  @Input() type = 'JSON';
+  @Input() type: 'GROK' | 'JSON' | 'STELLAR' = 'JSON';
   @Input() placeHolder = 'Enter text here';
   @Input() options: AutocompleteOption[] = [];
+  @Input() liveAutocompletion = true;
+  @Input() enableSnippets = true;
+  @Input() useWorker = true;
+  @Input() wrapLimitRangeMin: number | null = 72;
+  @Input() wrapLimitRangeMax: number | null = 72;
+  @Output() onChange = new EventEmitter();
   @ViewChild('aceEditor') aceEditorEle: ElementRef;
 
   private onTouchedCallback;
@@ -101,7 +107,7 @@
     parserConfigEditor.getSession().setMode(this.getEditorType());
     parserConfigEditor.getSession().setTabSize(2);
     parserConfigEditor.getSession().setUseWrapMode(true);
-    parserConfigEditor.getSession().setWrapLimitRange(72, 72);
+    parserConfigEditor.getSession().setWrapLimitRange(this.wrapLimitRangeMin, this.wrapLimitRangeMax);
 
     parserConfigEditor.$blockScrolling = Infinity;
     parserConfigEditor.setTheme('ace/theme/monokai');
@@ -110,12 +116,16 @@
       highlightActiveLine: false,
       maxLines: Infinity,
       enableBasicAutocompletion: true,
-      enableSnippets: true,
-      enableLiveAutocompletion: true
+      enableSnippets: this.enableSnippets,
+      useWorker: this.useWorker,
+      enableLiveAutocompletion: this.liveAutocompletion
     });
     parserConfigEditor.on('change', (e: any) => {
       this.inputJson = this.aceConfigEditor.getValue();
-      this.onChangeCallback(this.aceConfigEditor.getValue());
+      this.onChange.emit(this.inputJson);
+      if (typeof this.onChangeCallback === 'function') {
+        this.onChangeCallback(this.inputJson);
+      }
     });
 
     if (this.type === 'GROK') {
@@ -184,6 +194,8 @@
   private getEditorType() {
       if (this.type === 'GROK') {
         return 'ace/mode/grok';
+      } else if (this.type === 'STELLAR') {
+        return 'ace/mode/javascript';
       }
 
       return 'ace/mode/json';
diff --git a/metron-interface/metron-config/src/assets/ace/mode-javascript.js b/metron-interface/metron-config/src/assets/ace/mode-javascript.js
new file mode 100644
index 0000000..ca37d3e
--- /dev/null
+++ b/metron-interface/metron-config/src/assets/ace/mode-javascript.js
@@ -0,0 +1 @@
+ace.define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment.doc.tag",regex:"@[\\w\\d_]+"},s.getTagRule(),{defaultToken:"comment.doc",caseInsensitive:!0}]}};r.inherits(s,i),s.getTagRule=function(e){return{token:"comment.doc.tag.storage.type",regex:"\\b(?:TODO|FIXME|XXX|HACK)\\b"}},s.getStartRule=function(e){return{token:"comment.doc",regex:"\\/\\*(?=\\*)",next:e}},s.getEndRule=function(e){return{token:"comment.doc",regex:"\\*\\/",next:e}},t.DocCommentHighlightRules=s}),ace.define("ace/mode/javascript_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){var r=e.charAt(1)=="/"?2:1;if(r==1)t!=this.nextState?n.unshift(this.next,this.nextState,0):n.unshift(this.next),n[2]++;else if(r==2&&t==this.nextState){n[1]--;if(!n[1]||n[1]<0)n.shift(),n.shift()}return[{type:"meta.tag.punctuation."+(r==1?"":"end-")+"tag-open.xml",value:e.slice(0,r)},{type:"meta.tag.tag-name.xml",value:e.substr(r)}]},regex:"</?"+e+"",next:"jsxAttributes",nextState:"jsx"};this.$rules.start.unshift(t);var n={regex:"{",token:"paren.quasi.start",push:"start"};this.$rules.jsx=[n,t,{include:"reference"},{defaultToken:"string"}],this.$rules.jsxAttributes=[{token:"meta.tag.punctuation.tag-close.xml",regex:"/?>",onMatch:function(e,t,n){return t==n[0]&&n.shift(),e.length==2&&(n[0]==this.nextState&&n[1]--,(!n[1]||n[1]<0)&&n.splice(0,2)),this.next=n[0]||"start",[{type:this.token,value:e}]},nextState:"jsx"},n,f("jsxAttributes"),{token:"entity.other.attribute-name.xml",regex:e},{token:"keyword.operator.attribute-equals.xml",regex:"="},{token:"text.tag-whitespace.xml",regex:"\\s+"},{token:"string.attribute-value.xml",regex:"'",stateName:"jsx_attr_q",push:[{token:"string.attribute-value.xml",regex:"'",next:"pop"},{include:"reference"},{defaultToken:"string.attribute-value.xml"}]},{token:"string.attribute-value.xml",regex:'"',stateName:"jsx_attr_qq",push:[{token:"string.attribute-value.xml",regex:'"',next:"pop"},{include:"reference"},{defaultToken:"string.attribute-value.xml"}]},t],this.$rules.reference=[{token:"constant.language.escape.reference.xml",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"}]}function f(e){return[{token:"comment",regex:/\/\*/,next:[i.getTagRule(),{token:"comment",regex:"\\*\\/",next:e||"pop"},{defaultToken:"comment",caseInsensitive:!0}]},{token:"comment",regex:"\\/\\/",next:[i.getTagRule(),{token:"comment",regex:"$|^",next:e||"pop"},{defaultToken:"comment",caseInsensitive:!0}]}]}var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./text_highlight_rules").TextHighlightRules,o="[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*",u=function(e){var t=this.createKeywordMapper({"variable.language":"Array|Boolean|Date|Function|Iterator|Number|Object|RegExp|String|Proxy|Namespace|QName|XML|XMLList|ArrayBuffer|Float32Array|Float64Array|Int16Array|Int32Array|Int8Array|Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|Error|EvalError|InternalError|RangeError|ReferenceError|StopIteration|SyntaxError|TypeError|URIError|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt|JSON|Math|this|arguments|prototype|window|document",keyword:"const|yield|import|get|set|async|await|break|case|catch|continue|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|throw|try|typeof|let|var|while|with|debugger|__parent__|__count__|escape|unescape|with|__proto__|class|enum|extends|super|export|implements|private|public|interface|package|protected|static","storage.type":"const|let|var|function","constant.language":"null|Infinity|NaN|undefined","support.function":"alert","constant.language.boolean":"true|false"},"identifier"),n="case|do|else|finally|in|instanceof|return|throw|try|typeof|yield|void",r="\\\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|u{[0-9a-fA-F]{1,6}}|[0-2][0-7]{0,2}|3[0-7][0-7]?|[4-7][0-7]?|.)";this.$rules={no_regex:[i.getStartRule("doc-start"),f("no_regex"),{token:"string",regex:"'(?=.)",next:"qstring"},{token:"string",regex:'"(?=.)',next:"qqstring"},{token:"constant.numeric",regex:/0(?:[xX][0-9a-fA-F]+|[bB][01]+)\b/},{token:"constant.numeric",regex:/[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/},{token:["storage.type","punctuation.operator","support.function","punctuation.operator","entity.name.function","text","keyword.operator"],regex:"("+o+")(\\.)(prototype)(\\.)("+o+")(\\s*)(=)",next:"function_arguments"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["entity.name.function","text","keyword.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","entity.name.function","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function)(\\s+)(\\w+)(\\s*)(\\()",next:"function_arguments"},{token:["storage.type","text","entity.name.function","text","paren.lparen"],regex:"(function)(\\s+)("+o+")(\\s*)(\\()",next:"function_arguments"},{token:["entity.name.function","text","punctuation.operator","text","storage.type","text","paren.lparen"],regex:"("+o+")(\\s*)(:)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["text","text","storage.type","text","paren.lparen"],regex:"(:)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:"keyword",regex:"(?:"+n+")\\b",next:"start"},{token:["support.constant"],regex:/that\b/},{token:["storage.type","punctuation.operator","support.function.firebug"],regex:/(console)(\.)(warn|info|log|error|time|trace|timeEnd|assert)\b/},{token:t,regex:o},{token:"punctuation.operator",regex:/[.](?![.])/,next:"property"},{token:"keyword.operator",regex:/--|\+\+|\.{3}|===|==|=|!=|!==|<+=?|>+=?|!|&&|\|\||\?:|[!$%&*+\-~\/^]=?/,next:"start"},{token:"punctuation.operator",regex:/[?:,;.]/,next:"start"},{token:"paren.lparen",regex:/[\[({]/,next:"start"},{token:"paren.rparen",regex:/[\])}]/},{token:"comment",regex:/^#!.*$/}],property:[{token:"text",regex:"\\s+"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","entity.name.function","text","paren.lparen"],regex:"("+o+")(\\.)("+o+")(\\s*)(=)(\\s*)(function)(?:(\\s+)(\\w+))?(\\s*)(\\()",next:"function_arguments"},{token:"punctuation.operator",regex:/[.](?![.])/},{token:"support.function",regex:/(s(?:h(?:ift|ow(?:Mod(?:elessDialog|alDialog)|Help))|croll(?:X|By(?:Pages|Lines)?|Y|To)?|t(?:op|rike)|i(?:n|zeToContent|debar|gnText)|ort|u(?:p|b(?:str(?:ing)?)?)|pli(?:ce|t)|e(?:nd|t(?:Re(?:sizable|questHeader)|M(?:i(?:nutes|lliseconds)|onth)|Seconds|Ho(?:tKeys|urs)|Year|Cursor|Time(?:out)?|Interval|ZOptions|Date|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Date|FullYear)|FullYear|Active)|arch)|qrt|lice|avePreferences|mall)|h(?:ome|andleEvent)|navigate|c(?:har(?:CodeAt|At)|o(?:s|n(?:cat|textual|firm)|mpile)|eil|lear(?:Timeout|Interval)?|a(?:ptureEvents|ll)|reate(?:StyleSheet|Popup|EventObject))|t(?:o(?:GMTString|S(?:tring|ource)|U(?:TCString|pperCase)|Lo(?:caleString|werCase))|est|a(?:n|int(?:Enabled)?))|i(?:s(?:NaN|Finite)|ndexOf|talics)|d(?:isableExternalCapture|ump|etachEvent)|u(?:n(?:shift|taint|escape|watch)|pdateCommands)|j(?:oin|avaEnabled)|p(?:o(?:p|w)|ush|lugins.refresh|a(?:ddings|rse(?:Int|Float)?)|r(?:int|ompt|eference))|e(?:scape|nableExternalCapture|val|lementFromPoint|x(?:p|ec(?:Script|Command)?))|valueOf|UTC|queryCommand(?:State|Indeterm|Enabled|Value)|f(?:i(?:nd|le(?:ModifiedDate|Size|CreatedDate|UpdatedDate)|xed)|o(?:nt(?:size|color)|rward)|loor|romCharCode)|watch|l(?:ink|o(?:ad|g)|astIndexOf)|a(?:sin|nchor|cos|t(?:tachEvent|ob|an(?:2)?)|pply|lert|b(?:s|ort))|r(?:ou(?:nd|teEvents)|e(?:size(?:By|To)|calc|turnValue|place|verse|l(?:oad|ease(?:Capture|Events)))|andom)|g(?:o|et(?:ResponseHeader|M(?:i(?:nutes|lliseconds)|onth)|Se(?:conds|lection)|Hours|Year|Time(?:zoneOffset)?|Da(?:y|te)|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Da(?:y|te)|FullYear)|FullYear|A(?:ttention|llResponseHeaders)))|m(?:in|ove(?:B(?:y|elow)|To(?:Absolute)?|Above)|ergeAttributes|a(?:tch|rgins|x))|b(?:toa|ig|o(?:ld|rderWidths)|link|ack))\b(?=\()/},{token:"support.function.dom",regex:/(s(?:ub(?:stringData|mit)|plitText|e(?:t(?:NamedItem|Attribute(?:Node)?)|lect))|has(?:ChildNodes|Feature)|namedItem|c(?:l(?:ick|o(?:se|neNode))|reate(?:C(?:omment|DATASection|aption)|T(?:Head|extNode|Foot)|DocumentFragment|ProcessingInstruction|E(?:ntityReference|lement)|Attribute))|tabIndex|i(?:nsert(?:Row|Before|Cell|Data)|tem)|open|delete(?:Row|C(?:ell|aption)|T(?:Head|Foot)|Data)|focus|write(?:ln)?|a(?:dd|ppend(?:Child|Data))|re(?:set|place(?:Child|Data)|move(?:NamedItem|Child|Attribute(?:Node)?)?)|get(?:NamedItem|Element(?:sBy(?:Name|TagName|ClassName)|ById)|Attribute(?:Node)?)|blur)\b(?=\()/},{token:"support.constant",regex:/(s(?:ystemLanguage|cr(?:ipts|ollbars|een(?:X|Y|Top|Left))|t(?:yle(?:Sheets)?|atus(?:Text|bar)?)|ibling(?:Below|Above)|ource|uffixes|e(?:curity(?:Policy)?|l(?:ection|f)))|h(?:istory|ost(?:name)?|as(?:h|Focus))|y|X(?:MLDocument|SLDocument)|n(?:ext|ame(?:space(?:s|URI)|Prop))|M(?:IN_VALUE|AX_VALUE)|c(?:haracterSet|o(?:n(?:structor|trollers)|okieEnabled|lorDepth|mp(?:onents|lete))|urrent|puClass|l(?:i(?:p(?:boardData)?|entInformation)|osed|asses)|alle(?:e|r)|rypto)|t(?:o(?:olbar|p)|ext(?:Transform|Indent|Decoration|Align)|ags)|SQRT(?:1_2|2)|i(?:n(?:ner(?:Height|Width)|put)|ds|gnoreCase)|zIndex|o(?:scpu|n(?:readystatechange|Line)|uter(?:Height|Width)|p(?:sProfile|ener)|ffscreenBuffering)|NEGATIVE_INFINITY|d(?:i(?:splay|alog(?:Height|Top|Width|Left|Arguments)|rectories)|e(?:scription|fault(?:Status|Ch(?:ecked|arset)|View)))|u(?:ser(?:Profile|Language|Agent)|n(?:iqueID|defined)|pdateInterval)|_content|p(?:ixelDepth|ort|ersonalbar|kcs11|l(?:ugins|atform)|a(?:thname|dding(?:Right|Bottom|Top|Left)|rent(?:Window|Layer)?|ge(?:X(?:Offset)?|Y(?:Offset)?))|r(?:o(?:to(?:col|type)|duct(?:Sub)?|mpter)|e(?:vious|fix)))|e(?:n(?:coding|abledPlugin)|x(?:ternal|pando)|mbeds)|v(?:isibility|endor(?:Sub)?|Linkcolor)|URLUnencoded|P(?:I|OSITIVE_INFINITY)|f(?:ilename|o(?:nt(?:Size|Family|Weight)|rmName)|rame(?:s|Element)|gColor)|E|whiteSpace|l(?:i(?:stStyleType|n(?:eHeight|kColor))|o(?:ca(?:tion(?:bar)?|lName)|wsrc)|e(?:ngth|ft(?:Context)?)|a(?:st(?:M(?:odified|atch)|Index|Paren)|yer(?:s|X)|nguage))|a(?:pp(?:MinorVersion|Name|Co(?:deName|re)|Version)|vail(?:Height|Top|Width|Left)|ll|r(?:ity|guments)|Linkcolor|bove)|r(?:ight(?:Context)?|e(?:sponse(?:XML|Text)|adyState))|global|x|m(?:imeTypes|ultiline|enubar|argin(?:Right|Bottom|Top|Left))|L(?:N(?:10|2)|OG(?:10E|2E))|b(?:o(?:ttom|rder(?:Width|RightWidth|BottomWidth|Style|Color|TopWidth|LeftWidth))|ufferDepth|elow|ackground(?:Color|Image)))\b/},{token:"identifier",regex:o},{regex:"",token:"empty",next:"no_regex"}],start:[i.getStartRule("doc-start"),f("start"),{token:"string.regexp",regex:"\\/",next:"regex"},{token:"text",regex:"\\s+|^$",next:"start"},{token:"empty",regex:"",next:"no_regex"}],regex:[{token:"regexp.keyword.operator",regex:"\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"},{token:"string.regexp",regex:"/[sxngimy]*",next:"no_regex"},{token:"invalid",regex:/\{\d+\b,?\d*\}[+*]|[+*$^?][+*]|[$^][?]|\?{3,}/},{token:"constant.language.escape",regex:/\(\?[:=!]|\)|\{\d+\b,?\d*\}|[+*]\?|[()$^+*?.]/},{token:"constant.language.delimiter",regex:/\|/},{token:"constant.language.escape",regex:/\[\^?/,next:"regex_character_class"},{token:"empty",regex:"$",next:"no_regex"},{defaultToken:"string.regexp"}],regex_character_class:[{token:"regexp.charclass.keyword.operator",regex:"\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"},{token:"constant.language.escape",regex:"]",next:"regex"},{token:"constant.language.escape",regex:"-"},{token:"empty",regex:"$",next:"no_regex"},{defaultToken:"string.regexp.charachterclass"}],function_arguments:[{token:"variable.parameter",regex:o},{token:"punctuation.operator",regex:"[, ]+"},{token:"punctuation.operator",regex:"$"},{token:"empty",regex:"",next:"no_regex"}],qqstring:[{token:"constant.language.escape",regex:r},{token:"string",regex:"\\\\$",next:"qqstring"},{token:"string",regex:'"|$',next:"no_regex"},{defaultToken:"string"}],qstring:[{token:"constant.language.escape",regex:r},{token:"string",regex:"\\\\$",next:"qstring"},{token:"string",regex:"'|$",next:"no_regex"},{defaultToken:"string"}]};if(!e||!e.noES6)this.$rules.no_regex.unshift({regex:"[{}]",onMatch:function(e,t,n){this.next=e=="{"?this.nextState:"";if(e=="{"&&n.length)n.unshift("start",t);else if(e=="}"&&n.length){n.shift(),this.next=n.shift();if(this.next.indexOf("string")!=-1||this.next.indexOf("jsx")!=-1)return"paren.quasi.end"}return e=="{"?"paren.lparen":"paren.rparen"},nextState:"start"},{token:"string.quasi.start",regex:/`/,push:[{token:"constant.language.escape",regex:r},{token:"paren.quasi.start",regex:/\${/,push:"start"},{token:"string.quasi.end",regex:/`/,next:"pop"},{defaultToken:"string.quasi"}]}),(!e||e.jsx!=0)&&a.call(this);this.embedRules(i,"doc-",[i.getEndRule("no_regex")]),this.normalizeRules()};r.inherits(u,s),t.JavaScriptHighlightRules=u}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/(\{|\[)[^\}\]]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++t<a){n=e.getLine(t);var f=n.search(/\S/);if(f===-1)continue;if(r>f)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++n<s){t=e.getLine(n);var f=u.exec(t);if(!f)continue;f[1]?a--:a++;if(!a)break}var l=n;if(l>o)return new i(o,r,l,t.length)}}.call(o.prototype)}),ace.define("ace/mode/javascript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/javascript_highlight_rules","ace/mode/matching_brace_outdent","ace/range","ace/worker/worker_client","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./javascript_highlight_rules").JavaScriptHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../range").Range,a=e("../worker/worker_client").WorkerClient,f=e("./behaviour/cstyle").CstyleBehaviour,l=e("./folding/cstyle").FoldMode,c=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new f,this.foldingRules=new l};r.inherits(c,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e),s=i.tokens,o=i.state;if(s.length&&s[s.length-1].type=="comment")return r;if(e=="start"||e=="no_regex"){var u=t.match(/^.*(?:\bcase\b.*:|[\{\(\[])\s*$/);u&&(r+=n)}else if(e=="doc-start"){if(o=="start"||o=="no_regex")return"";var u=t.match(/^\s*(\/?)\*/);u&&(u[1]&&(r+=" "),r+="* ")}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.createWorker=function(e){var t=new a(["ace"],"ace/mode/javascript_worker","JavaScriptWorker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/javascript"}.call(c.prototype),t.Mode=c})
\ No newline at end of file