diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8be4fc367..4001de134 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# v4.4 - changes staged on develop
+## Improvements
+- Combined the search and multiselect interfaces into a single UI. This allows groups, software, and mitigations to be filtered alongside techniques and improves usability by moving the interface to a sidebar. See issue [#204](https://github.com/mitre-attack/attack-navigator/issues/204).
+
# v4.3 - 29 April 2021
## New Features
- Added aggregate scores. Aggregate scores are computed using the score of the technique and all sub-techniques using an "aggregate function" -- min, max, average, or sum. The aggregate score is used to determine the color of the technique in place of the technique's score. Aggregate scores are an optional feature and can be enabled in the "matrix configuration" dropdown. See issue [#269](https://github.com/mitre-attack/attack-navigator/issues/269).
diff --git a/nav-app/src/app/app.component.html b/nav-app/src/app/app.component.html
index 2e2dc5d15..a53798120 100755
--- a/nav-app/src/app/app.component.html
+++ b/nav-app/src/app/app.component.html
@@ -1,13 +1,2 @@
-
-
-
-
-
+MITRE ATT&CK® Navigator v{{nav_version}}
-
-
-
diff --git a/nav-app/src/app/app.component.ts b/nav-app/src/app/app.component.ts
index f94d84c29..d1737a7bc 100755
--- a/nav-app/src/app/app.component.ts
+++ b/nav-app/src/app/app.component.ts
@@ -2,6 +2,7 @@ import { Component, ViewChild, DoCheck, HostListener } from '@angular/core';
import { TabsComponent } from './tabs/tabs.component';
import { ConfigService } from './config.service';
import * as globals from "./globals";
+import { DataService } from './data.service';
@Component({
selector: 'app-root',
@@ -30,6 +31,4 @@ export class AppComponent {
return false;
}
}
-
-
}
diff --git a/nav-app/src/app/app.module.ts b/nav-app/src/app/app.module.ts
index 807631e4d..e83d1240f 100755
--- a/nav-app/src/app/app.module.ts
+++ b/nav-app/src/app/app.module.ts
@@ -14,6 +14,8 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatMenuModule } from '@angular/material/menu';
import { MatExpansionModule } from '@angular/material/expansion';
+import { MatSidenavModule } from '@angular/material/sidenav';
+
import { ColorPickerModule } from 'ngx-color-picker';
import { HttpClientModule } from '@angular/common/http';
@@ -27,11 +29,13 @@ import { MatrixSideComponent } from './matrix/matrix-side/matrix-side.component'
import { MatrixFlatComponent } from './matrix//matrix-flat/matrix-flat.component';
import { MatrixMiniComponent } from './matrix//matrix-mini/matrix-mini.component';
import { TooltipComponent } from './matrix/technique-cell/tooltip/tooltip.component';
-import { MultiselectComponent } from './multiselect/multiselect.component';
-import { TechniquesSearchComponent } from './techniques-search/techniques-search.component';
+import { SearchAndMultiselectComponent } from './search-and-multiselect/search-and-multiselect.component';
import { ContextmenuComponent } from './matrix/technique-cell/contextmenu/contextmenu.component';
import { TacticCellComponent } from './matrix/tactic-cell/tactic-cell.component';
import { VersionUpgradeComponent } from './version-upgrade/version-upgrade.component';
+import { SidebarComponent } from './sidebar/sidebar.component';
+import { MatCardModule } from "@angular/material/card";
+import { MatDividerModule } from "@angular/material/divider";
@NgModule({
@@ -46,11 +50,11 @@ import { VersionUpgradeComponent } from './version-upgrade/version-upgrade.compo
MatrixFlatComponent,
MatrixMiniComponent,
TooltipComponent,
- MultiselectComponent,
- TechniquesSearchComponent,
+ SearchAndMultiselectComponent,
ContextmenuComponent,
TacticCellComponent,
VersionUpgradeComponent,
+ SidebarComponent
],
imports: [
BrowserModule,
@@ -68,6 +72,9 @@ import { VersionUpgradeComponent } from './version-upgrade/version-upgrade.compo
MatExpansionModule,
MatDialogModule,
ColorPickerModule,
+ MatSidenavModule,
+ MatCardModule,
+ MatDividerModule
],
exports: [
MatSelectModule,
diff --git a/nav-app/src/app/data.service.ts b/nav-app/src/app/data.service.ts
index 56bc3e26d..8167d6646 100755
--- a/nav-app/src/app/data.service.ts
+++ b/nav-app/src/app/data.service.ts
@@ -43,14 +43,20 @@ export class DataService {
*/
parseBundle(domain: Domain, stixBundles: any[]): void {
let platforms = new Set();
+ let seenIDs = new Set();
for (let bundle of stixBundles) {
let techniqueSDOs = [];
let matrixSDOs = [];
let idToTechniqueSDO = new Map();
let idToTacticSDO = new Map();
for (let sdo of bundle.objects) { //iterate through stix domain objects in the bundle
- // ignore deprecated and revoked objects in the bundle
- if (sdo.x_mitre_deprecated || sdo.revoked) continue;
+ // ignore deprecated and revoked objects in the bundle?
+ if (sdo.x_mitre_deprecated || sdo.revoked) continue;
+
+ // filter out duplicates
+ if (!seenIDs.has(sdo.id)) seenIDs.add(sdo.id)
+ else continue;
+
// parse according to type
switch(sdo.type) {
case "intrusion-set":
@@ -133,16 +139,18 @@ export class DataService {
}
domain.techniques.push(new Technique(techniqueSDO, subtechniques, this));
}
-
+
//create matrices, which also creates tactics and filters techniques
for (let matrixSDO of matrixSDOs) {
domain.matrices.push(new Matrix(matrixSDO, idToTacticSDO, domain.techniques, this));
}
-
+
// parse platforms
for (let technique of domain.techniques) {
- for (let platform of technique.platforms) {
- platforms.add(platform)
+ if (technique.platforms) {
+ for (let platform of technique.platforms) {
+ platforms.add(platform)
+ }
}
}
for (let subtechnique of domain.subtechniques) {
@@ -302,9 +310,9 @@ export class DataService {
}
}
-/**
+/**
* Common attributes for STIX objects
- */
+ */
export abstract class BaseStix {
public readonly id: string; // STIX ID
public readonly attackID: string; // ATT&CK ID
@@ -376,7 +384,7 @@ export class Technique extends BaseStix {
this.platforms = stixSDO.x_mitre_platforms;
if (stixSDO.x_mitre_data_sources !== undefined)
this.datasources = stixSDO.x_mitre_data_sources.toString();
- else
+ else
this.datasources = "";
this.tactics = stixSDO.kill_chain_phases.map((phase) => phase.phase_name);
@@ -384,7 +392,6 @@ export class Technique extends BaseStix {
for (let subtechnique of this.subtechniques) {
subtechnique.parent = this;
}
-
}
/**
@@ -472,7 +479,7 @@ export class Mitigation extends BaseStix {
let rels = this.dataService.getDomain(domainID).relationships.mitigates;
if (rels.has(this.id)) {
return rels.get(this.id);
- }
+ }
else return [];
}
/**
@@ -522,10 +529,10 @@ export class Domain {
public relationships: any = {
// subtechnique subtechnique-of technique
// ID of technique to [] of subtechnique IDs
- subtechniques_of: new Map(),
+ subtechniques_of: new Map(),
// group uses technique
// ID of group to [] of technique IDs
- group_uses: new Map(),
+ group_uses: new Map(),
// group uses technique
// ID of group to [] of technique IDs
software_uses: new Map(),
diff --git a/nav-app/src/app/datatable/data-table.component.html b/nav-app/src/app/datatable/data-table.component.html
index 445e6304b..76c34ef2b 100755
--- a/nav-app/src/app/datatable/data-table.component.html
+++ b/nav-app/src/app/datatable/data-table.component.html
@@ -40,33 +40,14 @@