mirror of
https://github.com/JHUAPL/STIXMODELER_UI.git
synced 2026-01-08 05:53:51 -05:00
Validation and schema management improvements
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Node_Moludes
|
||||||
|
/stix-modeler-app/node_modules
|
||||||
|
|
||||||
|
# Distribution
|
||||||
|
/stix-modeler-app/dist/
|
||||||
|
|
||||||
|
# Schemas
|
||||||
|
/stix-modeler-app/schemas/*.json
|
||||||
51
README.md
51
README.md
@@ -3,10 +3,7 @@
|
|||||||
This is a fork of the [STIX Modeler](https://github.com/STIX-Modeler/UI/tree/develop), originally created by Jason Minnick
|
This is a fork of the [STIX Modeler](https://github.com/STIX-Modeler/UI/tree/develop), originally created by Jason Minnick
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
A React-based user interface tool for visualizing, creating, and modifying STIX 2.1 bundles.
|
A React-based user interface tool for visualizing, creating, and modifying STIX 2.1 bundles
|
||||||
|
|
||||||
This material is based upon work supported by the U.S. Department of Homeland Security / Cybersecurity and Infrastructure Security Agency. Any views and conclusions contained on this page are those of the authors and should not be interpreted as necessarily representing the official policies, either
|
|
||||||
expressed or implied, of the U.S. Department of Homeland Security / Cybersecurity and Infrastructure Security Agency.
|
|
||||||
|
|
||||||
## New Features
|
## New Features
|
||||||
- Define custom STIX Domain Objects (SDO) using schemas
|
- Define custom STIX Domain Objects (SDO) using schemas
|
||||||
@@ -19,7 +16,38 @@ expressed or implied, of the U.S. Department of Homeland Security / Cybersecurit
|
|||||||
|
|
||||||
This modeler was developed in and optimized for use with node v20.11.1 and npm 10.2.4
|
This modeler was developed in and optimized for use with node v20.11.1 and npm 10.2.4
|
||||||
|
|
||||||
Earlier versions of node may not be supported
|
Other versions of node may not be supported
|
||||||
|
|
||||||
|
All listed third-party dependencies grant use, modification, and distribution rights under the MIT License.
|
||||||
|
|
||||||
|
## Third-Party Dependencies
|
||||||
|
- "@vitejs/plugin-react": "5.0.4",
|
||||||
|
- "classnames": "2.5.1",
|
||||||
|
- "d3-hierarchy": "3.1.2",
|
||||||
|
- "deepmerge": "4.3.1",
|
||||||
|
- "lodash": "4.17.21",
|
||||||
|
- "mobx": "6.15.0",
|
||||||
|
- "mobx-react": "9.2.1",
|
||||||
|
- "moment": "2.30.1",
|
||||||
|
- "prop-types": "15.8.1",
|
||||||
|
- "rc-slider": "11.1.9",
|
||||||
|
- "react": "19.2.0",
|
||||||
|
- "react-datepicker": "8.7.0",
|
||||||
|
- "react-dom": "19.2.0",
|
||||||
|
- "react-tooltip": "5.29.1",
|
||||||
|
- "reactflow": "11.11.4",
|
||||||
|
- "sass": "1.93.2",
|
||||||
|
- "uuid": "13.0.0",
|
||||||
|
- "vite": "7.1.9"
|
||||||
|
|
||||||
|
## Third-Party Development Dependencies
|
||||||
|
- "@eslint/js": "9.37.0",
|
||||||
|
- "eslint": "9.37.0",
|
||||||
|
- "globals": "16.4.0",
|
||||||
|
- "jsdom": "27.0.0",
|
||||||
|
- "typescript-eslint": "8.46.0",
|
||||||
|
- "vitest": "3.2.4"
|
||||||
|
|
||||||
|
|
||||||
# Installation and Use
|
# Installation and Use
|
||||||
|
|
||||||
@@ -43,6 +71,13 @@ Earlier versions of node may not be supported
|
|||||||
- Added functionality for creating new Group SDOs via clicking and selecting SDOs
|
- Added functionality for creating new Group SDOs via clicking and selecting SDOs
|
||||||
- Updated dependencies and removed unused dependencies
|
- Updated dependencies and removed unused dependencies
|
||||||
- Upgraded handling of default field and relationship values
|
- Upgraded handling of default field and relationship values
|
||||||
|
- Added bundle validation for required SDO properties
|
||||||
|
- Added bundle file export
|
||||||
|
- Added vitest testing infrastructure
|
||||||
|
- Added unknown object and property handling
|
||||||
|
- Added automatic schema loading
|
||||||
|
- Added UI configuration via config file
|
||||||
|
- Updated STIX schemas to latest versions
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
||||||
@@ -51,6 +86,10 @@ Earlier versions of node may not be supported
|
|||||||
- Fixed implied fields based on relationships between nodes (e.g. "created_by")
|
- Fixed implied fields based on relationships between nodes (e.g. "created_by")
|
||||||
- Fixed import and modification of nodes with "hashes" fields
|
- Fixed import and modification of nodes with "hashes" fields
|
||||||
- Added ability to delete external_reference objects from external_references fields
|
- Added ability to delete external_reference objects from external_references fields
|
||||||
|
- Fixed inclusion of invalid fields in relationship objects
|
||||||
|
- Fixed extension definition inconsistency
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Definitions
|
## Definitions
|
||||||
|
|
||||||
@@ -88,8 +127,6 @@ Specific vocab notes
|
|||||||
- labels: there are placeholder values located in definition-adapters/Base.js. This can easily be updated to reflect your sharing group or company's standard list for each object or even hidden with the `control` property.
|
- labels: there are placeholder values located in definition-adapters/Base.js. This can easily be updated to reflect your sharing group or company's standard list for each object or even hidden with the `control` property.
|
||||||
|
|
||||||
# Quality Assurance
|
# Quality Assurance
|
||||||
## Style Guide
|
|
||||||
The source code follows a modification of the [Airbnb Javascript Style Guide](https://airbnb.io/javascript/react/)
|
|
||||||
## Automated Tools
|
## Automated Tools
|
||||||
The project uses eslint for quality assurance and styling.
|
The project uses eslint for quality assurance and styling.
|
||||||
- See current code quality issues: `npm run lint`
|
- See current code quality issues: `npm run lint`
|
||||||
|
|||||||
BIN
STIX-Modeler_User_Guide.pdf
Normal file
BIN
STIX-Modeler_User_Guide.pdf
Normal file
Binary file not shown.
5515
app/package-lock.json
generated
5515
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "stix-modeler-app",
|
|
||||||
"private": true,
|
|
||||||
"version": "1.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
|
||||||
"lint:fix": "npm run lint -- --fix",
|
|
||||||
"preview": "vite preview"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"classnames": "^2.5.1",
|
|
||||||
"deepmerge": "^4.3.1",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"mobx": "^6.12.0",
|
|
||||||
"mobx-react": "^9.1.0",
|
|
||||||
"moment": "^2.30.1",
|
|
||||||
"prop-types": "^15.8.1",
|
|
||||||
"rc-slider": "^10.5.0",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-datepicker": "^6.1.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-tooltip": "^5.26.3",
|
|
||||||
"reactflow": "^11.10.4",
|
|
||||||
"sass": "^1.71.0",
|
|
||||||
"uuid": "^9.0.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/react": "^18.2.55",
|
|
||||||
"@types/react-dom": "^18.2.19",
|
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
|
||||||
"eslint": "^8.57.0",
|
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
|
||||||
"eslint-plugin-import": "^2.29.1",
|
|
||||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
|
||||||
"eslint-plugin-react": "^7.34.1",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
|
||||||
"vite": "^5.2.6"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
@import '../../defaults';
|
|
||||||
|
|
||||||
.nodeLabel {
|
|
||||||
overflow: visible;
|
|
||||||
position: relative;
|
|
||||||
bottom: -45px;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
@import "../defaults";
|
|
||||||
|
|
||||||
.submission-error {
|
|
||||||
.header {
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 18px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
vertical-align: middle;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
padding-left: 55px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: $default-active-bg;
|
|
||||||
padding-right: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
@import '../../defaults';
|
|
||||||
|
|
||||||
.top-menu {
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
.menu-item-small {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item-medium {
|
|
||||||
width: 25px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schema-paste-btn {
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px 4px 5px 11px;
|
|
||||||
margin-left: 10px;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: $default-active-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grouping-btn {
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px 11px 5px 11px;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: $default-active-bg;
|
|
||||||
width: 55px;
|
|
||||||
height: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: -25%;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
min-width: 160px;
|
|
||||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content a {
|
|
||||||
color: black;
|
|
||||||
padding: 12px 16px;
|
|
||||||
text-decoration: none;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content a:hover {background-color: #ddd;}
|
|
||||||
|
|
||||||
.dropdown:hover .dropdown-content {display: block;}
|
|
||||||
|
|
||||||
|
|
||||||
.cancel-btn:hover {
|
|
||||||
background-color: $error-font;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sdos-btn {
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px 11px 5px 11px;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: $default-active-bg;
|
|
||||||
margin-left: 10px;
|
|
||||||
|
|
||||||
.i {
|
|
||||||
width: 20px;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ctr-input {
|
|
||||||
padding-right: 10px;
|
|
||||||
width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-btn {
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px 4px 5px 8px;
|
|
||||||
margin-left: 10px;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: $default-active-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-paste-btn {
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px 4px 5px 8px;
|
|
||||||
margin-left: 5px;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: $default-active-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reset-btn {
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px 11px 5px 11px;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: $default-active-bg;
|
|
||||||
margin-left: 10px;
|
|
||||||
|
|
||||||
.i {
|
|
||||||
width: 20px;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
@import '../../defaults';
|
|
||||||
|
|
||||||
.details {
|
|
||||||
font-size: 18px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.header {
|
|
||||||
padding: 20px;
|
|
||||||
font-size: 18px;
|
|
||||||
height: 40px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
background-color: $lt-gray-bg;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
padding-top: 5px;
|
|
||||||
img {
|
|
||||||
vertical-align: middle;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete {
|
|
||||||
padding-left: 7px;
|
|
||||||
width: 80px;
|
|
||||||
display: flex;
|
|
||||||
background-color: $default-active-bg;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding-top: 7px;
|
|
||||||
color: $light-font-0;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
padding-left: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
padding-top: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete:hover {
|
|
||||||
background-color: $error-font;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: hidden;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.preview {
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-top: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
font-family: $default-font-family;
|
|
||||||
line-height: 30px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.src-image {
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.target-image {
|
|
||||||
padding-right: 5px;
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rel-type {
|
|
||||||
color: $default-active-bg;
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn {
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px 11px 5px 11px;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: $default-active-bg;
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-top: 10px;
|
|
||||||
width: fit-content;
|
|
||||||
|
|
||||||
.i {
|
|
||||||
width: 20px;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-top: 15px;
|
|
||||||
font-weight: normal;
|
|
||||||
|
|
||||||
.horizontal-slider {
|
|
||||||
width: 98%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-header {
|
|
||||||
font-weight: bold;
|
|
||||||
color: $default-active-bg;
|
|
||||||
padding-bottom: 3px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
padding-left: 3px;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { observer } from 'mobx-react';
|
|
||||||
import Panel from '../ui/panel/Panel';
|
|
||||||
import Images from '../../imgs/Images';
|
|
||||||
|
|
||||||
import './RelationshipPicker.scss';
|
|
||||||
|
|
||||||
class RelationshipPicker extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClickSelectRelHandler(relationship) {
|
|
||||||
this.props.onClickSelectRelHandler(relationship);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
// Do not allow relationship defininition for generic observables
|
|
||||||
let create;
|
|
||||||
if (this.props.relationships.length == 0 || this.props.relationships[0].target_ref) {
|
|
||||||
create = (
|
|
||||||
<div
|
|
||||||
className="item"
|
|
||||||
key="new-relationship"
|
|
||||||
onClick={this.props.onClickShowRelDetailsHandler}
|
|
||||||
>
|
|
||||||
<img className="src-image" src={Images.getImage('add.png')} width="20" />
|
|
||||||
<span className="rel-type"> Create New Relationship </span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Panel
|
|
||||||
show={this.props.show}
|
|
||||||
onClickHideHandler={this.props.onClickHideHandler}
|
|
||||||
>
|
|
||||||
<div className="relationship-picker">
|
|
||||||
<div className="header">
|
|
||||||
<img src={Images.getImage('relationship.png')} width="20" />
|
|
||||||
{' '}
|
|
||||||
Possible Relationships
|
|
||||||
</div>
|
|
||||||
<div className="content">
|
|
||||||
{create}
|
|
||||||
{
|
|
||||||
this.props.relationships.slice(1).map((relationship) => {
|
|
||||||
let src = relationship.source_ref.split('--')[0];
|
|
||||||
let target = relationship.target_ref.split('--')[0];
|
|
||||||
|
|
||||||
if (relationship.subTarget) {
|
|
||||||
target = relationship.subTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relationship.x_reverse) {
|
|
||||||
const tmp = src;
|
|
||||||
src = target;
|
|
||||||
target = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
const srcImg = Images.getImage(`${src}.png`);
|
|
||||||
const targetImg = Images.getImage(`${target}.png`);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="item"
|
|
||||||
key={relationship.id}
|
|
||||||
onClick={() => this.onClickSelectRelHandler(relationship)}
|
|
||||||
>
|
|
||||||
<img className="src-image" src={srcImg} width="20" />
|
|
||||||
{' '}
|
|
||||||
{src}
|
|
||||||
<span className="rel-type">
|
|
||||||
{' '}
|
|
||||||
{relationship.relationship_type}
|
|
||||||
{' '}
|
|
||||||
</span>
|
|
||||||
{target}
|
|
||||||
{' '}
|
|
||||||
<img className="target-image" src={targetImg} width="20" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Panel>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} export default (observer(RelationshipPicker));
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
@import "../../../defaults";
|
|
||||||
|
|
||||||
.growl {
|
|
||||||
position: fixed;
|
|
||||||
right: 10px;
|
|
||||||
top: 10px;
|
|
||||||
z-index: $growl-index;
|
|
||||||
background-color: $default-active-bg;
|
|
||||||
color: $light-font-0;
|
|
||||||
padding: 11px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
import deepmerge from 'deepmerge';
|
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
const SPEC_VERSION = 2.1;
|
|
||||||
|
|
||||||
const COMMON_RELS = [
|
|
||||||
{
|
|
||||||
type: 'created-by', target: 'identity', x_exclusive: true, x_embed: 'created_by_ref',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
type: 'includes', target: 'grouping', x_embed: 'object_refs', x_reverse: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'applies-to', target: 'note', x_embed: 'object_refs', x_reverse: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'applies-to', target: 'opinion', x_embed: 'object_refs', x_reverse: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'references', target: 'report', x_embed: 'object_refs', x_reverse: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'applies-to', target: 'marking-definition', x_embed: 'object_marking_refs', x_reverse: true,
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export class Base {
|
|
||||||
constructor(common, def) {
|
|
||||||
const commonProps = common.properties;
|
|
||||||
let defProps = {};
|
|
||||||
|
|
||||||
common.required.map((item) => {
|
|
||||||
if (commonProps[item]) {
|
|
||||||
commonProps[item].required = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (def.allOf) {
|
|
||||||
def.allOf.map((item) => {
|
|
||||||
if (item.hasOwnProperty('properties')) {
|
|
||||||
defProps = item.properties;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
defProps = def.properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (def.required) {
|
|
||||||
def.required.map((item) => {
|
|
||||||
if (defProps[item]) {
|
|
||||||
defProps[item].required = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item in def) {
|
|
||||||
this[item] = def[item];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const rel of COMMON_RELS) {
|
|
||||||
def.relationships.push(rel);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedProps = deepmerge(commonProps, defProps);
|
|
||||||
|
|
||||||
this.handleFields(mergedProps);
|
|
||||||
|
|
||||||
this.properties = mergedProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFields(mergedProps) {
|
|
||||||
// Start special handling of common object
|
|
||||||
// properties.
|
|
||||||
for (const prop in mergedProps) {
|
|
||||||
// Get (possibly nested) ref
|
|
||||||
let ref = mergedProps[prop].$ref;
|
|
||||||
for (const a in mergedProps[prop].allOf) {
|
|
||||||
if (mergedProps[prop].allOf[a].$ref) {
|
|
||||||
ref = mergedProps[prop].allOf[a].$ref;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ref = ref || '';
|
|
||||||
|
|
||||||
// set type for values with ref
|
|
||||||
if (ref.indexOf('timestamp.json') !== -1) {
|
|
||||||
mergedProps[prop].type = 'dts';
|
|
||||||
} else if (ref.indexOf('identifier.json') !== -1) {
|
|
||||||
mergedProps[prop].type = 'string';
|
|
||||||
} else if (ref.indexOf('dictionary.json') !== -1) {
|
|
||||||
mergedProps[prop].type = 'object';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set default blank values based on the prop
|
|
||||||
// type.
|
|
||||||
if (mergedProps[prop].type) {
|
|
||||||
mergedProps[prop].value = this.defaultValue(mergedProps[prop].type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergedProps.type) {
|
|
||||||
mergedProps.type.control = 'literal';
|
|
||||||
if (mergedProps.type.enum) {
|
|
||||||
mergedProps.type.value = mergedProps.type.enum[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergedProps.aliases) {
|
|
||||||
mergedProps.aliases.control = 'csv';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergedProps.kill_chain_phases) {
|
|
||||||
mergedProps.kill_chain_phases.control = 'killchain';
|
|
||||||
mergedProps.kill_chain_phases.vocab = [
|
|
||||||
{
|
|
||||||
label: 'Lockheed Kill Chain',
|
|
||||||
value: 'lockheed-martin-cyber-kill-chain',
|
|
||||||
phases: [
|
|
||||||
{
|
|
||||||
label: 'Reconnaissance',
|
|
||||||
phase_name: 'reconnaissance',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Weaponize',
|
|
||||||
phase_name: 'weaponization',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Delivery',
|
|
||||||
phase_name: 'delivery',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Exploitation',
|
|
||||||
phase_name: 'exploitation',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Installation',
|
|
||||||
phase_name: 'installation',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Command & Control (C2)',
|
|
||||||
phase_name: 'command-and-control',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Actions On Objectives',
|
|
||||||
phase_name: 'actions-on-objectives',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergedProps.external_references) {
|
|
||||||
mergedProps.external_references.control = 'externalrefs';
|
|
||||||
}
|
|
||||||
|
|
||||||
mergedProps.id.control = 'hidden';
|
|
||||||
|
|
||||||
if (mergedProps.confidence) {
|
|
||||||
mergedProps.confidence.control = 'slider';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergedProps.description) {
|
|
||||||
mergedProps.description.control = 'textarea';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* These are defaults that are to be set by the TI orchestrator
|
|
||||||
*/
|
|
||||||
|
|
||||||
mergedProps.spec_version.value = SPEC_VERSION;
|
|
||||||
mergedProps.spec_version.control = 'literal';
|
|
||||||
|
|
||||||
if (mergedProps.extensions) {
|
|
||||||
mergedProps.extensions.control = 'genericobject';
|
|
||||||
mergedProps.extensions.type = 'object';
|
|
||||||
mergedProps.extensions.value = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergedProps.created_by_ref) {
|
|
||||||
mergedProps.created_by_ref.type = 'literal';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergedProps.lang) {
|
|
||||||
mergedProps.lang.value = 'en';
|
|
||||||
mergedProps.lang.control = 'hidden';
|
|
||||||
}
|
|
||||||
|
|
||||||
mergedProps.object_marking_refs.control = 'hidden';
|
|
||||||
mergedProps.granular_markings.control = 'hidden';
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultValue(type) {
|
|
||||||
let def;
|
|
||||||
|
|
||||||
// ignores type path
|
|
||||||
if (type.includes('.json')) {
|
|
||||||
type = type.split('/').slice(-1)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'string':
|
|
||||||
def = '';
|
|
||||||
break;
|
|
||||||
case 'dts':
|
|
||||||
def = moment().utc(true).format('YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
|
|
||||||
break;
|
|
||||||
case 'integer':
|
|
||||||
def = 0;
|
|
||||||
break;
|
|
||||||
case 'array':
|
|
||||||
def = [];
|
|
||||||
break;
|
|
||||||
case 'object':
|
|
||||||
def = {};
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
def = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
|
|
||||||
flattenExtensionProperties(def) {
|
|
||||||
const properties = {};
|
|
||||||
let tmp = {};
|
|
||||||
if ('properties' in def) {
|
|
||||||
tmp = this.flattenExtensionProperties(def.properties);
|
|
||||||
for (const [key, value] of Object.entries(tmp)) {
|
|
||||||
properties[key] = value;
|
|
||||||
}
|
|
||||||
} else if ('extensions' in def) {
|
|
||||||
tmp = this.flattenExtensionProperties(def.extensions);
|
|
||||||
for (const [key, value] of Object.entries(tmp)) {
|
|
||||||
properties[key] = value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const [key, value] of Object.entries(def)) {
|
|
||||||
if (key.includes('extension-definition')) {
|
|
||||||
tmp = this.flattenExtensionProperties(value);
|
|
||||||
for (const [key, value] of Object.entries(tmp)) {
|
|
||||||
properties[key] = value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
properties[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeExtension(def, extDef) {
|
|
||||||
const { properties, } = this;
|
|
||||||
let defProps;
|
|
||||||
if (def.allOf) {
|
|
||||||
def.allOf.map((item) => {
|
|
||||||
if ('properties' in item) {
|
|
||||||
defProps = this.flattenExtensionProperties(item.properties);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
defProps = this.flattenExtensionProperties(def.properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('extension_type' in defProps) {
|
|
||||||
delete defProps.extension_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!('extensions' in properties)) {
|
|
||||||
this.properties.extensions = {};
|
|
||||||
this.properties.extensions.value = {};
|
|
||||||
this.properties.extensions.type = 'object';
|
|
||||||
this.properties.extensions.control = 'hidden';
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = { extension_type: 'property-extension', };
|
|
||||||
for (const prop in defProps) {
|
|
||||||
if ((prop !== 'extension_type') && (defProps[prop].type)) {
|
|
||||||
const value = this.defaultValue(defProps[prop].type);
|
|
||||||
props[prop] = value;
|
|
||||||
defProps[prop].value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const mergedProps = deepmerge(properties, defProps);
|
|
||||||
this.properties = mergedProps;
|
|
||||||
this.properties.extensions.value[extDef.id] = props;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB |
File diff suppressed because it is too large
Load Diff
7
stix-modeler-app/config/config.json
Normal file
7
stix-modeler-app/config/config.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"creator_id": "identity--b085a68a-bf48-4316-9667-37af78cba894",
|
||||||
|
"schema_dir": "/schemas",
|
||||||
|
"schemas": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
22
stix-modeler-app/eslint.config.js
Normal file
22
stix-modeler-app/eslint.config.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import eslint from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
eslint.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
tseslint.configs.stylistic,
|
||||||
|
globalIgnores([
|
||||||
|
"config/*",
|
||||||
|
"dist/*",
|
||||||
|
"node_modules/*",
|
||||||
|
]),
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
5466
stix-modeler-app/package-lock.json
generated
Normal file
5466
stix-modeler-app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
stix-modeler-app/package.json
Normal file
42
stix-modeler-app/package.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "stix-modeler-app",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"test": "vitest",
|
||||||
|
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"lint:fix": "npm run lint -- --fix",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
|
"d3-hierarchy": "^3.1.2",
|
||||||
|
"deepmerge": "^4.3.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"mobx": "^6.0.0",
|
||||||
|
"mobx-react": "^9.0.0",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"prop-types": "^15.8.0",
|
||||||
|
"rc-slider": "^11.1.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-datepicker": "^8.7.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-tooltip": "^5.29.0",
|
||||||
|
"reactflow": "^11.11.0",
|
||||||
|
"sass": "^1.93.0",
|
||||||
|
"uuid": "13.0.0",
|
||||||
|
"vite": "^7.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.0.0",
|
||||||
|
"eslint": "^9.0.0",
|
||||||
|
"globals": "^16.4.0",
|
||||||
|
"jsdom": "^27.0.0",
|
||||||
|
"typescript-eslint": "^8.46.0",
|
||||||
|
"vitest": "^3.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
stix-modeler-app/public/vite.svg
Normal file
1
stix-modeler-app/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
5
stix-modeler-app/schemas/README.md
Normal file
5
stix-modeler-app/schemas/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Import Schemas #
|
||||||
|
|
||||||
|
Any schemas placed in this folder can be imported via ```config.json```.
|
||||||
|
|
||||||
|
**REMEMBER**: The browser can only import files within ```stix-modeler-app```
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import './defaults';
|
@use './defaults';
|
||||||
|
|
||||||
body, html, #app {
|
body, html, #app {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
@@ -8,5 +8,5 @@ body, html, #app {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
font-family: $default-font-family;
|
font-family: defaults.$default-font-family;
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ import { inject, observer } from 'mobx-react';
|
|||||||
import BottomMenu from './menus/BottomMenu';
|
import BottomMenu from './menus/BottomMenu';
|
||||||
import TopMenu from './menus/TopMenu';
|
import TopMenu from './menus/TopMenu';
|
||||||
import Details from './Details';
|
import Details from './Details';
|
||||||
import SDOEditor from './schema/SDOEditor';
|
import ExtensionEditor from './schema/ExtensionEditor';
|
||||||
import FileImporter from './FileImporter';
|
import FileImporter from './FileImporter';
|
||||||
import JsonViewer from './bundle/JsonViewer';
|
import JsonViewer from './bundle/JsonViewer';
|
||||||
import JsonPaste from './bundle/JsonPaste';
|
import JsonPaste from './bundle/JsonPaste';
|
||||||
@@ -11,7 +11,8 @@ import SchemaPaste from './schema/SchemaPaste';
|
|||||||
import RelationshipPicker from './relationship/RelationshipPicker';
|
import RelationshipPicker from './relationship/RelationshipPicker';
|
||||||
import RelationshipDetails from './relationship/RelationshipDetails';
|
import RelationshipDetails from './relationship/RelationshipDetails';
|
||||||
import RelationshipEditor from './relationship/RelationshipEditor';
|
import RelationshipEditor from './relationship/RelationshipEditor';
|
||||||
import SDOPicker from './schema/SDOPicker';
|
import ExtensionPicker from './schema/ExtensionPicker';
|
||||||
|
import LayoutPanel from './layout/LayoutPanel';
|
||||||
import Growl from './ui/growl/Growl';
|
import Growl from './ui/growl/Growl';
|
||||||
import SubmissionError from './SubmissionError';
|
import SubmissionError from './SubmissionError';
|
||||||
import Flow from './Flow/Flow';
|
import Flow from './Flow/Flow';
|
||||||
@@ -20,8 +21,7 @@ import './canvas.scss';
|
|||||||
|
|
||||||
class Canvas extends React.Component {
|
class Canvas extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.store = this.props.store.appStore;
|
this.store = this.props.store.appStore;
|
||||||
|
|
||||||
this.generateNodeID = this.generateNodeID.bind(this);
|
this.generateNodeID = this.generateNodeID.bind(this);
|
||||||
@@ -48,10 +48,16 @@ class Canvas extends React.Component {
|
|||||||
this.onClickShowRelDetailsHandler = this.onClickShowRelDetailsHandler.bind(
|
this.onClickShowRelDetailsHandler = this.onClickShowRelDetailsHandler.bind(
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
this.onClickShowSDOPickerHandler = this.onClickShowSDOPickerHandler.bind(
|
this.onClickShowExtensionPickerHandler = this.onClickShowExtensionPickerHandler.bind(
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
this.onClickHideSDOPickerHandler = this.onClickHideSDOPickerHandler.bind(
|
this.onClickHideExtensionPickerHandler = this.onClickHideExtensionPickerHandler.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
this.onClickShowLayoutPanelHandler = this.onClickShowLayoutPanelHandler.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
this.onClickHideLayoutPanelHandler = this.onClickHideLayoutPanelHandler.bind(
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
this.onClickShowImporterHandler = this.onClickShowImporterHandler.bind(
|
this.onClickShowImporterHandler = this.onClickShowImporterHandler.bind(
|
||||||
@@ -65,13 +71,13 @@ class Canvas extends React.Component {
|
|||||||
this.onClickCreateRelHandler = this.onClickCreateRelHandler.bind(this);
|
this.onClickCreateRelHandler = this.onClickCreateRelHandler.bind(this);
|
||||||
this.onClickEditRelHandler = this.onClickEditRelHandler.bind(this);
|
this.onClickEditRelHandler = this.onClickEditRelHandler.bind(this);
|
||||||
this.onClickSelectRelHandler = this.onClickSelectRelHandler.bind(this);
|
this.onClickSelectRelHandler = this.onClickSelectRelHandler.bind(this);
|
||||||
this.onClickSelectSDOHandler = this.onClickSelectSDOHandler.bind(this);
|
this.onClickSelectExtHandler = this.onClickSelectExtHandler.bind(this);
|
||||||
this.onClickShowGrowlHandler = this.onClickShowGrowlHandler.bind(this);
|
this.onClickShowGrowlHandler = this.onClickShowGrowlHandler.bind(this);
|
||||||
this.onClickGroupNodeHandler = this.onClickGroupNodeHandler.bind(this);
|
this.onClickGroupNodeHandler = this.onClickGroupNodeHandler.bind(this);
|
||||||
this.onClickGroupModeHandler = this.onClickGroupModeHandler.bind(this);
|
this.onClickGroupModeHandler = this.onClickGroupModeHandler.bind(this);
|
||||||
this.onClickSubmitGroupingHandler = this.onClickSubmitGroupingHandler.bind(this);
|
this.onClickSubmitGroupingHandler = this.onClickSubmitGroupingHandler.bind(this);
|
||||||
this.onChangeNodeHandler = this.onChangeNodeHandler.bind(this);
|
this.onChangeNodeHandler = this.onChangeNodeHandler.bind(this);
|
||||||
this.onChangeSDOHandler = this.onChangeSDOHandler.bind(this);
|
this.onChangeExtHandler = this.onChangeExtHandler.bind(this);
|
||||||
this.onChangeSchemaHandler = this.onChangeSchemaHandler.bind(this);
|
this.onChangeSchemaHandler = this.onChangeSchemaHandler.bind(this);
|
||||||
this.onChangeBundleHandler = this.onChangeBundleHandler.bind(this);
|
this.onChangeBundleHandler = this.onChangeBundleHandler.bind(this);
|
||||||
this.onChangeDateHandler = this.onChangeDateHandler.bind(this);
|
this.onChangeDateHandler = this.onChangeDateHandler.bind(this);
|
||||||
@@ -129,24 +135,45 @@ class Canvas extends React.Component {
|
|||||||
);
|
);
|
||||||
this.onClickSchemaPasteHandler = this.onClickSchemaPasteHandler.bind(this);
|
this.onClickSchemaPasteHandler = this.onClickSchemaPasteHandler.bind(this);
|
||||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||||
this.onClickDeleteSDOHandler = this.onClickDeleteSDOHandler.bind(this);
|
this.onClickDeleteExtHandler = this.onClickDeleteExtHandler.bind(this);
|
||||||
this.onClickDeleteRelHandler = this.onClickDeleteRelHandler.bind(this);
|
this.onClickDeleteRelHandler = this.onClickDeleteRelHandler.bind(this);
|
||||||
this.onClickSubmitHandler = this.onClickSubmitHandler.bind(this);
|
this.onClickExportHandler = this.onClickExportHandler.bind(this);
|
||||||
|
this.onClickShowSubmissionErrorHandler = this.onClickShowSubmissionErrorHandler.bind(this);
|
||||||
this.onClickHideSubmissionErrorHandler = this.onClickHideSubmissionErrorHandler.bind(
|
this.onClickHideSubmissionErrorHandler = this.onClickHideSubmissionErrorHandler.bind(
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
|
this.onClickErrorHandler = this.onClickErrorHandler.bind(this);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
/**
|
||||||
document.removeEventListener('dragover', () => {}, false);
|
* Select the node with specified id.
|
||||||
}
|
* @param {string} nodeId id of node
|
||||||
|
*/
|
||||||
onClickHandler(nodeId) {
|
onClickHandler(nodeId) {
|
||||||
const node = this.store.getNodeById(nodeId);
|
const node = this.store.getNodeById(nodeId);
|
||||||
this.store.setShowDetails(true);
|
this.store.setShowDetails(true);
|
||||||
this.store.setSelected(node);
|
this.store.setSelected(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the node associated with the specified id,
|
||||||
|
* from the Submission Errors panel.
|
||||||
|
* @param {string} nodeId
|
||||||
|
*/
|
||||||
|
onClickErrorHandler(nodeId) {
|
||||||
|
this.store.setShowSubmissionError(false);
|
||||||
|
this.store.showSubmissionErrorBadge = false;
|
||||||
|
const node = this.store.getNodeById(nodeId);
|
||||||
|
this.store.setShowDetails(true);
|
||||||
|
this.store.setSelected(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate or deactivate grouping selection.
|
||||||
|
* @param {boolean} isGrouping whether currently selecting group
|
||||||
|
*/
|
||||||
onClickGroupModeHandler(isGrouping) {
|
onClickGroupModeHandler(isGrouping) {
|
||||||
this.store.setGroupMode(isGrouping);
|
this.store.setGroupMode(isGrouping);
|
||||||
if (!isGrouping) {
|
if (!isGrouping) {
|
||||||
@@ -155,11 +182,20 @@ class Canvas extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add or remove the node with specified node from
|
||||||
|
* grouping selection.
|
||||||
|
* @param {string} id node id
|
||||||
|
*/
|
||||||
onClickGroupNodeHandler(id) {
|
onClickGroupNodeHandler(id) {
|
||||||
this.store.modifyGroup(id);
|
this.store.modifyGroup(id);
|
||||||
this.setUpdateFlow(true);
|
this.setUpdateFlow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Grouping SDO, including all nodes
|
||||||
|
* from the grouping selection.
|
||||||
|
*/
|
||||||
onClickSubmitGroupingHandler() {
|
onClickSubmitGroupingHandler() {
|
||||||
const id = this.generateNodeID('grouping--');
|
const id = this.generateNodeID('grouping--');
|
||||||
this.store.createGroup(id);
|
this.store.createGroup(id);
|
||||||
@@ -167,73 +203,140 @@ class Canvas extends React.Component {
|
|||||||
this.setUpdateFlow(true);
|
this.setUpdateFlow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the relationship with the specified ID
|
||||||
|
* to edit via the Relationship Editor panel.
|
||||||
|
* @param {*} relId relationship id
|
||||||
|
*/
|
||||||
onClickRelHandler(relId) {
|
onClickRelHandler(relId) {
|
||||||
const rel = this.store.getRelById(relId);
|
const rel = this.store.getRelById(relId);
|
||||||
this.store.setShowRelEditor(true);
|
this.store.setShowRelEditor(true);
|
||||||
this.store.setSelectedRel(rel);
|
this.store.setSelectedRel(rel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the details panel.
|
||||||
|
*/
|
||||||
onClickHideDetailsHandler() {
|
onClickHideDetailsHandler() {
|
||||||
this.store.setShowDetails(false);
|
this.store.setShowDetails(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the Extension Editor panel.
|
||||||
|
*/
|
||||||
onClickHideEditorHandler() {
|
onClickHideEditorHandler() {
|
||||||
this.store.setShowEditor(false);
|
this.store.setShowEditor(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the Json Paste panel.
|
||||||
|
*/
|
||||||
onClickHideJsonPasteHandler() {
|
onClickHideJsonPasteHandler() {
|
||||||
this.store.setShowJSONPaste(false);
|
this.store.setShowJSONPaste(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the Json Paste panel.
|
||||||
|
*/
|
||||||
onClickShowJsonPasteHandler() {
|
onClickShowJsonPasteHandler() {
|
||||||
this.store.setShowJSONPaste(true);
|
this.store.setShowJSONPaste(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the Schema Paste panel.
|
||||||
|
*/
|
||||||
onClickHideSchemaPasteHandler() {
|
onClickHideSchemaPasteHandler() {
|
||||||
this.store.setShowSchemaPaste(false);
|
this.store.setShowSchemaPaste(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the Schema Paste panel.
|
||||||
|
*/
|
||||||
onClickShowSchemaPasteHandler() {
|
onClickShowSchemaPasteHandler() {
|
||||||
this.store.setShowSchemaPaste(true);
|
this.store.setShowSchemaPaste(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the growl message.
|
||||||
|
* @param {string} message growl message
|
||||||
|
*/
|
||||||
onClickShowGrowlHandler(message) {
|
onClickShowGrowlHandler(message) {
|
||||||
this.store.setGrowlMessage(message);
|
this.store.setGrowlMessage(message);
|
||||||
this.store.setShowGrowl(true);
|
this.store.setShowGrowl(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickHideSubmissionErrorHandler() {
|
/**
|
||||||
this.store.resetSubmissionError();
|
* Hide the Submission Error panel.
|
||||||
|
*/
|
||||||
|
onClickShowSubmissionErrorHandler() {
|
||||||
|
this.store.setShowSubmissionError(true);
|
||||||
|
this.store.validateSubmission();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the Submission Error panel.
|
||||||
|
*/
|
||||||
|
onClickHideSubmissionErrorHandler() {
|
||||||
|
this.store.showSubmissionErrorBadge = false;
|
||||||
|
this.store.setShowSubmissionError(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the selected node.
|
||||||
|
*/
|
||||||
onClickDeleteHandler() {
|
onClickDeleteHandler() {
|
||||||
this.store.deleteSelectedNode();
|
this.store.deleteSelectedNode();
|
||||||
this.setUpdateFlow(true);
|
this.setUpdateFlow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickDeleteSDOHandler() {
|
/**
|
||||||
this.store.deleteSelectedSDO();
|
* Delete the selected extension.
|
||||||
|
*/
|
||||||
|
onClickDeleteExtHandler() {
|
||||||
|
this.store.deleteSelectedExt();
|
||||||
|
this.setUpdateFlow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the selected relationship.
|
||||||
|
*/
|
||||||
onClickDeleteRelHandler() {
|
onClickDeleteRelHandler() {
|
||||||
this.store.deleteSelectedRelationship();
|
this.store.deleteSelectedRelationship();
|
||||||
this.setUpdateFlow(true);
|
this.setUpdateFlow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified property value for
|
||||||
|
* the selected node.
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
onChangeNodeHandler(event) {
|
onChangeNodeHandler(event) {
|
||||||
this.store.editNodeValues(event);
|
this.store.editNodeValues(event);
|
||||||
this.setUpdateFlow(true);
|
this.setUpdateFlow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeSDOHandler(event) {
|
/**
|
||||||
this.store.editSDOValues(event);
|
* Update the specified property value for
|
||||||
this.forceUpdate();
|
* the selected extension.
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
|
onChangeExtHandler(event) {
|
||||||
|
this.store.editExtensionValues(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a schema from a file.
|
||||||
|
* @param {object} file schema json
|
||||||
|
*/
|
||||||
onChangeSchemaHandler(file) {
|
onChangeSchemaHandler(file) {
|
||||||
this.store.loadSchemaFromFile(file);
|
this.store.loadSchemaFromFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a bundle from a file.
|
||||||
|
* @param {*} file bundle json
|
||||||
|
*/
|
||||||
onChangeBundleHandler(file) {
|
onChangeBundleHandler(file) {
|
||||||
this.store.loadBundleFromFile(file);
|
this.store.loadBundleFromFile(file);
|
||||||
this.store.nodes.map((n) => {
|
this.store.nodes.map((n) => {
|
||||||
@@ -242,58 +345,129 @@ class Canvas extends React.Component {
|
|||||||
this.setUpdateFlow(true);
|
this.setUpdateFlow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the Creator ID for SDO, SCO, and SROs created
|
||||||
|
* via the STIX UI.
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
onChangeCreatorIDHandler(id) {
|
onChangeCreatorIDHandler(id) {
|
||||||
this.store.updateCreatorID(id);
|
this.store.updateCreatorID(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified date property for the
|
||||||
|
* selected node.
|
||||||
|
* @param {string} property
|
||||||
|
* @param {*} datetime
|
||||||
|
*/
|
||||||
onChangeDateHandler(property, datetime) {
|
onChangeDateHandler(property, datetime) {
|
||||||
const value = this.store.generateTimestamp(datetime);
|
const value = this.store.generateTimestamp(datetime);
|
||||||
this.mutateOnEvent(property, value);
|
this.mutateOnEvent(property, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified array property for
|
||||||
|
* the selected node.
|
||||||
|
* @param {string} property
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
onClickArrayHandler(property, value) {
|
onClickArrayHandler(property, value) {
|
||||||
this.mutateOnEvent(property, value);
|
this.mutateOnEvent(property, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified property for
|
||||||
|
* the selected node (for Slider inputs).
|
||||||
|
* @param {string} property
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
onChangeSliderHandler(property, value) {
|
onChangeSliderHandler(property, value) {
|
||||||
this.mutateOnEvent(property, value);
|
this.mutateOnEvent(property, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified boolean property
|
||||||
|
* for the selected node.
|
||||||
|
* @param {string} property
|
||||||
|
* @param {boolean} value
|
||||||
|
*/
|
||||||
onClickBooleanHandler(property, value) {
|
onClickBooleanHandler(property, value) {
|
||||||
this.mutateOnEvent(property, value);
|
this.mutateOnEvent(property, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified kill chain property
|
||||||
|
* for the selected node.
|
||||||
|
* @param {string} property
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
onChangePhaseHandler(property, value) {
|
onChangePhaseHandler(property, value) {
|
||||||
this.mutateOnEvent(property, value);
|
this.mutateOnEvent(property, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified property for the
|
||||||
|
* selected node (for Confirm Text Area inputs).
|
||||||
|
* @param {string} property
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
onClickAddTextHandler(property, value) {
|
onClickAddTextHandler(property, value) {
|
||||||
this.mutateOnEvent(property, value);
|
this.mutateOnEvent(property, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified list property for the
|
||||||
|
* selected node.
|
||||||
|
* @param {string} property
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
onChangeListHandler(property, value) {
|
onChangeListHandler(property, value) {
|
||||||
this.mutateOnEvent(property, value);
|
this.mutateOnEvent(property, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the object property for the selected node.
|
||||||
|
* @param {string} property
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
onChangeGenericObjectHandler(property, event) {
|
onChangeGenericObjectHandler(property, event) {
|
||||||
this.mutateOnEvent(property, event.currentTarget.value);
|
this.mutateOnEvent(property, event.currentTarget.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickRemovePhaseHander(property, value) {
|
/**
|
||||||
this.store.removeKillChainPhase(value);
|
* Remove the specified kill chain phase property
|
||||||
|
* for the selected node.
|
||||||
|
* @param {string} property
|
||||||
|
* @param {number} idx index of phase in kill chain
|
||||||
|
*/
|
||||||
|
onClickRemovePhaseHander(property, idx) {
|
||||||
|
this.store.deleteArrayObject(idx, property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified array property for the selected value
|
||||||
|
* (for use with Comma Seperated Value inputs).
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
onChangeCSVHandler(event) {
|
onChangeCSVHandler(event) {
|
||||||
this.store.editCSVInput(event);
|
this.store.editCSVInput(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new relationship between the specified
|
||||||
|
* source and target.
|
||||||
|
* @param {string} srcId id of source
|
||||||
|
* @param {string} targetId id of target
|
||||||
|
* @param {object} rel relationship object
|
||||||
|
*/
|
||||||
onClickCreateRelHandler(srcId, targetId, rel) {
|
onClickCreateRelHandler(srcId, targetId, rel) {
|
||||||
const src = { id: srcId, };
|
const src = { id: srcId, };
|
||||||
const target = { id: targetId, };
|
const target = { id: targetId, };
|
||||||
const relationship = this.store.makeRelationship(src, target, rel);
|
const relationship = this.store.makeRelationship(src, target, rel);
|
||||||
if (relationship) {
|
if (relationship) {
|
||||||
this.onClickSelectRelHandler(relationship);
|
this.onClickSelectRelHandler(relationship);
|
||||||
this.store.addCustomRelationship(rel, srcId, targetId);
|
// this.store.addCustomRelationship(rel, srcId, targetId);
|
||||||
|
this.store.addCustomRelationship(rel, srcId);
|
||||||
this.setUpdateFlow(true);
|
this.setUpdateFlow(true);
|
||||||
} else {
|
} else {
|
||||||
this.store.setGrowlMessage('Could not create relationship');
|
this.store.setGrowlMessage('Could not create relationship');
|
||||||
@@ -301,12 +475,20 @@ class Canvas extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit the specified relationship.
|
||||||
|
* @param {object} rel
|
||||||
|
*/
|
||||||
onClickEditRelHandler(rel) {
|
onClickEditRelHandler(rel) {
|
||||||
this.store.editRelationship(rel);
|
this.store.editRelationship(rel);
|
||||||
this.store.setShowRelEditor(false);
|
this.store.setShowRelEditor(false);
|
||||||
this.setUpdateFlow(true);
|
this.setUpdateFlow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the specified relationship.
|
||||||
|
* @param {object} relationship
|
||||||
|
*/
|
||||||
onClickSelectRelHandler(relationship) {
|
onClickSelectRelHandler(relationship) {
|
||||||
this.store.setShowRelDetails(false);
|
this.store.setShowRelDetails(false);
|
||||||
this.store.manuallySelectRelationship(relationship);
|
this.store.manuallySelectRelationship(relationship);
|
||||||
@@ -314,107 +496,211 @@ class Canvas extends React.Component {
|
|||||||
this.setUpdateFlow(true);
|
this.setUpdateFlow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickSelectSDOHandler(sdo) {
|
/**
|
||||||
this.store.setSelectedSDO(sdo);
|
* Select the specified extension.
|
||||||
|
* @param {object} extension
|
||||||
|
*/
|
||||||
|
onClickSelectExtHandler(extension) {
|
||||||
|
this.store.setSelectedExt(extension)
|
||||||
this.store.setShowEditor(true);
|
this.store.setShowEditor(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an object to the specified object array
|
||||||
|
* property for the selected node.
|
||||||
|
* @param {string} field
|
||||||
|
* @param {list} requiredFields
|
||||||
|
*/
|
||||||
onClickAddObjectHandler(field, requiredFields) {
|
onClickAddObjectHandler(field, requiredFields) {
|
||||||
this.store.addDefaultObject(field, requiredFields);
|
this.store.addDefaultObject(field, requiredFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the specified property from an external reference
|
||||||
|
* for the selected node.
|
||||||
|
* @param {object} select property to delete
|
||||||
|
* @param {number} idx index of external reference in external references
|
||||||
|
*/
|
||||||
onClickDeletePropertyHandler(select, idx) {
|
onClickDeletePropertyHandler(select, idx) {
|
||||||
this.store.deleteERObjectProperty(select, idx);
|
this.store.deleteERObjectProperty(select, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the specified field for an object in the specified
|
||||||
|
* property array for the selected node.
|
||||||
|
* @param {string} select object property
|
||||||
|
* @param {number} idx index of object in node property
|
||||||
|
* @param {string} property node property
|
||||||
|
*/
|
||||||
onClickDeleteArrayObjectPropertyHandler(select, idx, property) {
|
onClickDeleteArrayObjectPropertyHandler(select, idx, property) {
|
||||||
this.store.deleteArrayObjectProperty(select, idx, property);
|
this.store.deleteArrayObjectProperty(select, idx, property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the specified external reference from the
|
||||||
|
* external references property for the selected node.
|
||||||
|
* @param {number} idx external reference index
|
||||||
|
*/
|
||||||
onClickDeleteERHandler(idx) {
|
onClickDeleteERHandler(idx) {
|
||||||
this.store.deleteERObject(idx);
|
this.store.deleteERObject(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the specified object from the specified
|
||||||
|
* array property for the selected node.
|
||||||
|
* @param {number} idx index of object in node property
|
||||||
|
* @param {string} property node property
|
||||||
|
*/
|
||||||
onClickDeleteArrayObjectHandler(idx, property) {
|
onClickDeleteArrayObjectHandler(idx, property) {
|
||||||
this.store.deleteArrayObject(idx, property);
|
this.store.deleteArrayObject(idx, property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified field for an object in the specified
|
||||||
|
* property array for the selected node.
|
||||||
|
* @param {string} select object property
|
||||||
|
* @param {number} idx index of object in node property
|
||||||
|
* @param {string} property node property
|
||||||
|
*/
|
||||||
onChangeERHandler(input, select, idx) {
|
onChangeERHandler(input, select, idx) {
|
||||||
this.store.changeERValue(input, select, idx);
|
this.store.changeERValue(input, select, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified object from the specified
|
||||||
|
* array property for the selected node.
|
||||||
|
* @param {number} idx index of object in node property
|
||||||
|
* @param {string} property node property
|
||||||
|
*/
|
||||||
onChangeArrayObjectHandler(input, field, idx, property) {
|
onChangeArrayObjectHandler(input, field, idx, property) {
|
||||||
this.store.changeArrayObjectValue(input, field, idx, property);
|
this.store.changeArrayObjectValue(input, field, idx, property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the JSON Viewer panel.
|
||||||
|
*/
|
||||||
onClickShowJsonHandler() {
|
onClickShowJsonHandler() {
|
||||||
this.store.mutateBundle();
|
this.store.stringifyBundle();
|
||||||
this.store.setShowJSON(true);
|
this.store.setShowJSON(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the JSON Viewer panel.
|
||||||
|
*/
|
||||||
onClickHideJsonHandler() {
|
onClickHideJsonHandler() {
|
||||||
this.store.setShowJSON(false);
|
this.store.setShowJSON(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the store pasteBundle value to
|
||||||
|
* the specified value.
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
onChangeJSONPasteHandler(event) {
|
onChangeJSONPasteHandler(event) {
|
||||||
this.store.setPasteBundle(event.currentTarget.value);
|
this.store.setPasteBundle(event.currentTarget.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a bundle from the Json Paste panel.
|
||||||
|
*/
|
||||||
onClickJSONPasteHandler() {
|
onClickJSONPasteHandler() {
|
||||||
this.store.loadBundleFromPaste();
|
this.store.loadBundleFromPaste();
|
||||||
|
|
||||||
this.store.nodes.map((n) => {
|
this.store.nodes.map((n) => {
|
||||||
this.transition(n.id, true);
|
this.transition(n.id, true);
|
||||||
});
|
});
|
||||||
this.setUpdateFlow(true);
|
this.setUpdateFlow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the store pasteSchema value to
|
||||||
|
* the specified value.
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
onChangeSchemaPasteHandler(event) {
|
onChangeSchemaPasteHandler(event) {
|
||||||
this.store.setPasteSchema(event.currentTarget.value);
|
this.store.setPasteSchema(event.currentTarget.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a schema from the Schema Paste panel.
|
||||||
|
*/
|
||||||
onClickSchemaPasteHandler() {
|
onClickSchemaPasteHandler() {
|
||||||
this.store.loadSchemaFromPaste();
|
this.store.loadSchemaFromPaste();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the Relationship Details panel.
|
||||||
|
*/
|
||||||
onClickShowRelDetailsHandler() {
|
onClickShowRelDetailsHandler() {
|
||||||
this.store.setShowRelDetails(true);
|
this.store.setShowRelDetails(true);
|
||||||
this.store.setShowRelPicker(false);
|
this.store.setShowRelPicker(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the Relationship Details panel.
|
||||||
|
*/
|
||||||
onClickHideRelDetailsHandler() {
|
onClickHideRelDetailsHandler() {
|
||||||
this.store.setShowRelDetails(false);
|
this.store.setShowRelDetails(false);
|
||||||
this.store.setShowRelPicker(true);
|
this.store.setShowRelPicker(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the Relationship Editor panel.
|
||||||
|
*/
|
||||||
onClickHideRelEditorHandler() {
|
onClickHideRelEditorHandler() {
|
||||||
this.store.setShowRelEditor(false);
|
this.store.setShowRelEditor(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the Relationship Picker panel.
|
||||||
|
*/
|
||||||
onClickHideRelPickerHandler() {
|
onClickHideRelPickerHandler() {
|
||||||
this.store.setShowRelPicker(false);
|
this.store.setShowRelPicker(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickShowSDOPickerHandler() {
|
/**
|
||||||
this.store.setShowSDOPicker(true);
|
* Show the Extension Picker panel.
|
||||||
|
*/
|
||||||
|
onClickShowExtensionPickerHandler() {
|
||||||
|
this.store.setShowExtensionPicker(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickHideSDOPickerHandler() {
|
/**
|
||||||
this.store.setShowSDOPicker(false);
|
* Hide the Extension Picker panel.
|
||||||
|
*/
|
||||||
|
onClickHideExtensionPickerHandler() {
|
||||||
|
this.store.setShowExtensionPicker(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClickShowLayoutPanelHandler() {
|
||||||
|
this.store.setShowLayoutPanel(true);
|
||||||
|
}
|
||||||
|
onClickHideLayoutPanelHandler() {
|
||||||
|
this.store.setShowLayoutPanel(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the File Importer panel.
|
||||||
|
*/
|
||||||
onClickHideImporterHandler() {
|
onClickHideImporterHandler() {
|
||||||
this.store.setShowImporter(false);
|
this.store.setShowImporter(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the File Importer panel.
|
||||||
|
*/
|
||||||
onClickShowImporterHandler() {
|
onClickShowImporterHandler() {
|
||||||
this.store.setShowImporter(true);
|
this.store.setShowImporter(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent event propagation.
|
||||||
onDragOverHandler(event) {
|
onDragOverHandler(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the dragged source node to the specified node.
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
onDragStartHandler(event) {
|
onDragStartHandler(event) {
|
||||||
const node = JSON.parse(event.dataTransfer.getData('node'));
|
const node = JSON.parse(event.dataTransfer.getData('node'));
|
||||||
this.store.setDragging(node);
|
this.store.setDragging(node);
|
||||||
@@ -426,7 +712,11 @@ class Canvas extends React.Component {
|
|||||||
}, 2500);
|
}, 2500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop on canvas
|
/**
|
||||||
|
* Create a new node of the dropped icon type, either
|
||||||
|
* directly or as an observable for the drop target.
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
onDropHandler(event) {
|
onDropHandler(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const node = this.store.dragging;
|
const node = this.store.dragging;
|
||||||
@@ -471,7 +761,11 @@ class Canvas extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect two nodes via a new relationship
|
/**
|
||||||
|
* Create a relationship between the source and target nodes.
|
||||||
|
* @param {string} sourceId source id
|
||||||
|
* @param {string} targetId target id
|
||||||
|
*/
|
||||||
onConnectNodeHandler(sourceId, targetId) {
|
onConnectNodeHandler(sourceId, targetId) {
|
||||||
const sourceNode = this.store.getNodeById(sourceId);
|
const sourceNode = this.store.getNodeById(sourceId);
|
||||||
const targetNode = this.store.getNodeById(targetId);
|
const targetNode = this.store.getNodeById(targetId);
|
||||||
@@ -487,44 +781,87 @@ class Canvas extends React.Component {
|
|||||||
this.store.relationships.unshift(genericRel);
|
this.store.relationships.unshift(genericRel);
|
||||||
this.store.setShowRelPicker(true);
|
this.store.setShowRelPicker(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update store position from React Flow
|
/**
|
||||||
onDragStopNodeHandler(node) {
|
* Update the store node position to its respective
|
||||||
const n = this.store.getNodeById(node.id);
|
* Flow node position.
|
||||||
if (n) {
|
* @param {object} flowNode React Flow node
|
||||||
n.position = node.position;
|
*/
|
||||||
|
onDragStopNodeHandler(flowNode) {
|
||||||
|
const node = this.store.getNodeById(flowNode.id);
|
||||||
|
if (node) {
|
||||||
|
node.position = flowNode.position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an object to the specified object property
|
||||||
|
* for the selected node.
|
||||||
|
* @param {string} field node property
|
||||||
|
* @param {object} o object to add
|
||||||
|
*/
|
||||||
onClickAddGenericObjectHandler(field, o) {
|
onClickAddGenericObjectHandler(field, o) {
|
||||||
this.store.addGenericObject(field, o);
|
this.store.addGenericObject(field, o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an object from the specified object property
|
||||||
|
* for the selected node.
|
||||||
|
* @param {string} field node property
|
||||||
|
* @param {string} key key of object to delete
|
||||||
|
*/
|
||||||
onClickDeleteGenericObjectHandler(field, key) {
|
onClickDeleteGenericObjectHandler(field, key) {
|
||||||
this.store.deleteGenericObject(field, key);
|
this.store.deleteGenericObject(field, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the STIX UI.
|
||||||
|
*/
|
||||||
onClickResetHandler() {
|
onClickResetHandler() {
|
||||||
this.store.reset();
|
this.store.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickSubmitHandler() {
|
/**
|
||||||
this.store.submit();
|
* Export the STIX bundle.
|
||||||
|
*/
|
||||||
|
onClickExportHandler() {
|
||||||
|
this.store.stringifyBundle();
|
||||||
|
this.store.export();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force React Flow to rerender.
|
||||||
|
* @param {boolean} update whether to rerender
|
||||||
|
*/
|
||||||
setUpdateFlow(update) {
|
setUpdateFlow(update) {
|
||||||
this.store.setUpdateFlow(update);
|
this.store.setUpdateFlow(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the mouse position
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
*/
|
||||||
setMousePosition(x, y) {
|
setMousePosition(x, y) {
|
||||||
this.store.setMousePosition(x, y);
|
this.store.setMousePosition(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a new node id.
|
||||||
|
* @param {string} prefix prefix of id
|
||||||
|
* @returns new node id
|
||||||
|
*/
|
||||||
generateNodeID(prefix) {
|
generateNodeID(prefix) {
|
||||||
return this.store.generateNodeID(prefix);
|
return this.store.generateNodeID(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a node property and value into an event object.
|
||||||
|
* @param {string} property node property
|
||||||
|
* @param {*} value node value
|
||||||
|
*/
|
||||||
mutateOnEvent(property, value) {
|
mutateOnEvent(property, value) {
|
||||||
const event = {
|
const event = {
|
||||||
currentTarget: {
|
currentTarget: {
|
||||||
@@ -536,10 +873,18 @@ class Canvas extends React.Component {
|
|||||||
this.onChangeNodeHandler(event);
|
this.onChangeNodeHandler(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the position of the specified node.
|
||||||
|
* @param {string} id node id
|
||||||
|
* @param {boolean} random whether to set at random or mouse position
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
transition(id, random) {
|
transition(id, random) {
|
||||||
const canvas = document.getElementById('canvas');
|
const canvas = document.getElementById('canvas');
|
||||||
const node = this.store.getNodeById(id);
|
const node = this.store.getNodeById(id);
|
||||||
|
|
||||||
|
if (node.title == 'extension-definition') return;
|
||||||
|
|
||||||
const calculate = (min, max) => Math.random() * (max - 100 - min) + min;
|
const calculate = (min, max) => Math.random() * (max - 100 - min) + min;
|
||||||
|
|
||||||
const bounds = {
|
const bounds = {
|
||||||
@@ -566,10 +911,11 @@ class Canvas extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { nodes, } = this.store;
|
const { nodes, } = this.store;
|
||||||
const { edges, } = this.store;
|
const { edges, } = this.store;
|
||||||
const sdos = this.store.getCustomSDOs();
|
const extensions = this.store.getExtensions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -598,14 +944,17 @@ class Canvas extends React.Component {
|
|||||||
onClickShowSchemaPasteHandler={this.onClickShowSchemaPasteHandler}
|
onClickShowSchemaPasteHandler={this.onClickShowSchemaPasteHandler}
|
||||||
onClickHideJsonHandler={this.onClickHideJsonHandler}
|
onClickHideJsonHandler={this.onClickHideJsonHandler}
|
||||||
onClickResetHandler={this.onClickResetHandler}
|
onClickResetHandler={this.onClickResetHandler}
|
||||||
onClickSubmitHandler={this.onClickSubmitHandler}
|
onClickExportHandler={this.onClickExportHandler}
|
||||||
onClickShowSDOPickerHandler={this.onClickShowSDOPickerHandler}
|
onClickShowExtensionPickerHandler={this.onClickShowExtensionPickerHandler}
|
||||||
|
onClickShowLayoutPanelHandler={this.onClickShowLayoutPanelHandler}
|
||||||
onClickShowImporterHandler={this.onClickShowImporterHandler}
|
onClickShowImporterHandler={this.onClickShowImporterHandler}
|
||||||
onChangeCreatorIDHandler={this.onChangeCreatorIDHandler}
|
onChangeCreatorIDHandler={this.onChangeCreatorIDHandler}
|
||||||
onClickGroupModeHandler={this.onClickGroupModeHandler}
|
onClickGroupModeHandler={this.onClickGroupModeHandler}
|
||||||
onClickSubmitGroupingHandler={this.onClickSubmitGroupingHandler}
|
onClickSubmitGroupingHandler={this.onClickSubmitGroupingHandler}
|
||||||
|
onClickShowErrorHandler={this.onClickShowSubmissionErrorHandler}
|
||||||
creatorID={this.store.creatorID}
|
creatorID={this.store.creatorID}
|
||||||
groupMode={this.store.groupMode}
|
groupMode={this.store.groupMode}
|
||||||
|
errors={this.store.showSubmissionErrorBadge}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BottomMenu
|
<BottomMenu
|
||||||
@@ -663,12 +1012,37 @@ class Canvas extends React.Component {
|
|||||||
onClickDeleteRelHandler={this.onClickDeleteRelHandler}
|
onClickDeleteRelHandler={this.onClickDeleteRelHandler}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SDOEditor
|
<ExtensionEditor
|
||||||
show={this.store.showEditor}
|
show={this.store.showEditor}
|
||||||
sdo={this.store.selectedSDO}
|
extension={this.store.selectedExt}
|
||||||
onClickHideHandler={this.onClickHideEditorHandler}
|
onClickHideHandler={this.onClickHideEditorHandler}
|
||||||
onChangeSDOHandler={this.onChangeSDOHandler}
|
onChangeExtHandler={this.onChangeExtHandler}
|
||||||
onClickDeleteHandler={this.onClickDeleteSDOHandler}
|
|
||||||
|
onChangeNodeHandler={this.onChangeNodeHandler}
|
||||||
|
onChangeDateHandler={this.onChangeDateHandler}
|
||||||
|
onClickArrayHandler={this.onClickArrayHandler}
|
||||||
|
onChangeListHandler={this.onChangeListHandler}
|
||||||
|
onChangeSliderHandler={this.onChangeSliderHandler}
|
||||||
|
onChangeCSVHandler={this.onChangeCSVHandler}
|
||||||
|
onClickBooleanHandler={this.onClickBooleanHandler}
|
||||||
|
onChangePhaseHandler={this.onChangePhaseHandler}
|
||||||
|
onClickRemovePhaseHander={this.onClickRemovePhaseHander}
|
||||||
|
onClickAddObjectHandler={this.onClickAddObjectHandler}
|
||||||
|
onClickDeleteERHandler={this.onClickDeleteERHandler}
|
||||||
|
onChangeERHandler={this.onChangeERHandler}
|
||||||
|
onClickDeletePropertyHandler={this.onClickDeletePropertyHandler}
|
||||||
|
onClickDeleteArrayObjectHandler={this.onClickDeleteArrayObjectHandler}
|
||||||
|
onChangeArrayObjectHandler={this.onChangeArrayObjectHandler}
|
||||||
|
onClickDeleteArrayObjectPropertyHandler={
|
||||||
|
this.onClickDeleteArrayObjectPropertyHandler
|
||||||
|
}
|
||||||
|
onChangeGenericObjectHandler={this.onChangeGenericObjectHandler}
|
||||||
|
onClickAddGenericObjectHandler={this.onClickAddGenericObjectHandler}
|
||||||
|
onClickDeleteGenericObjectHandler={
|
||||||
|
this.onClickDeleteGenericObjectHandler
|
||||||
|
}
|
||||||
|
onClickAddTextHandler={this.onClickAddTextHandler}
|
||||||
|
onClickDeleteHandler={this.onClickDeleteExtHandler}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FileImporter
|
<FileImporter
|
||||||
@@ -680,7 +1054,7 @@ class Canvas extends React.Component {
|
|||||||
|
|
||||||
<JsonViewer
|
<JsonViewer
|
||||||
show={this.store.showJSON}
|
show={this.store.showJSON}
|
||||||
json={this.store.mutatedBundle}
|
json={this.store.bundleJSON}
|
||||||
onClickHideHandler={this.onClickHideJsonHandler}
|
onClickHideHandler={this.onClickHideJsonHandler}
|
||||||
onClickShowGrowlHandler={this.onClickShowGrowlHandler}
|
onClickShowGrowlHandler={this.onClickShowGrowlHandler}
|
||||||
/>
|
/>
|
||||||
@@ -711,11 +1085,18 @@ class Canvas extends React.Component {
|
|||||||
onClickShowRelDetailsHandler={this.onClickShowRelDetailsHandler}
|
onClickShowRelDetailsHandler={this.onClickShowRelDetailsHandler}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SDOPicker
|
<ExtensionPicker
|
||||||
id="sdo-picker"
|
id="extension-picker"
|
||||||
show={this.store.showSDOPicker}
|
extensions={extensions}
|
||||||
sdos={sdos}
|
show={this.store.showExtensionPicker}
|
||||||
onClickHideHandler={this.onClickHideSDOPickerHandler}
|
onClickHideHandler={this.onClickHideExtensionPickerHandler}
|
||||||
|
onClickSelectExtHandler={this.onClickSelectExtHandler}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LayoutPanel
|
||||||
|
id="layout-panel"
|
||||||
|
show={this.store.showLayoutPanel}
|
||||||
|
onClickHideHandler={this.onClickHideLayoutPanelHandler}
|
||||||
onClickSelectSDOHandler={this.onClickSelectSDOHandler}
|
onClickSelectSDOHandler={this.onClickSelectSDOHandler}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -729,6 +1110,7 @@ class Canvas extends React.Component {
|
|||||||
error={this.store.failedCollection}
|
error={this.store.failedCollection}
|
||||||
show={this.store.showSubmissionError}
|
show={this.store.showSubmissionError}
|
||||||
onClickHideHandler={this.onClickHideSubmissionErrorHandler}
|
onClickHideHandler={this.onClickHideSubmissionErrorHandler}
|
||||||
|
onClickNodeHandler={this.onClickErrorHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -17,7 +17,7 @@ import GenericObject from './ui/complex/GenericObject';
|
|||||||
import ConfirmTextarea from './ui/complex/ConfirmTextarea';
|
import ConfirmTextarea from './ui/complex/ConfirmTextarea';
|
||||||
import ObjectArray from './ui/complex/ObjectArray';
|
import ObjectArray from './ui/complex/ObjectArray';
|
||||||
|
|
||||||
import Images from '../imgs/Images';
|
import Images from '../util/Images';
|
||||||
|
|
||||||
import './details.scss';
|
import './details.scss';
|
||||||
|
|
||||||
@@ -57,8 +57,9 @@ class Details extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const prop in props) {
|
for (const prop in props) {
|
||||||
|
const cls = 'item-header';
|
||||||
const header = (
|
const header = (
|
||||||
<div className="item-header">
|
<div className={cls}>
|
||||||
{prop}
|
{prop}
|
||||||
<span
|
<span
|
||||||
data-tooltip-id={`${prop}-tooltip`}
|
data-tooltip-id={`${prop}-tooltip`}
|
||||||
@@ -90,13 +91,14 @@ class Details extends React.Component {
|
|||||||
<Text
|
<Text
|
||||||
name={prop}
|
name={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
onChange={this.onChangeHandler}
|
onChange={this.onChangeHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'dts':
|
case 'timestamp':
|
||||||
control = (
|
control = (
|
||||||
<div className="item" key={prop}>
|
<div className="item" key={prop}>
|
||||||
{header}
|
{header}
|
||||||
@@ -104,7 +106,9 @@ class Details extends React.Component {
|
|||||||
<DateTime
|
<DateTime
|
||||||
name={prop}
|
name={prop}
|
||||||
selected={props[prop].value}
|
selected={props[prop].value}
|
||||||
onChange={this.onChangeDateHandler}
|
required={props[prop].required}
|
||||||
|
onTextChange={this.onChangeHandler}
|
||||||
|
onDateChange={this.onChangeDateHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,6 +123,7 @@ class Details extends React.Component {
|
|||||||
field={prop}
|
field={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
description={props[prop].description}
|
description={props[prop].description}
|
||||||
|
required={props[prop].required}
|
||||||
onClickHandler={this.props.onClickArrayHandler}
|
onClickHandler={this.props.onClickArrayHandler}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -128,9 +133,9 @@ class Details extends React.Component {
|
|||||||
if (Array.isArray(refField)) {
|
if (Array.isArray(refField)) {
|
||||||
refField = refField[0];
|
refField = refField[0];
|
||||||
}
|
}
|
||||||
const ref = refField.$ref ? refField.$ref : refField.type;
|
const ref = refField.$ref ?? refField.type;
|
||||||
|
|
||||||
if (ref === '../common/dictionary.json' || ref === 'object') {
|
if (ref.includes('dictionary.json') || ref === 'object') {
|
||||||
control = (
|
control = (
|
||||||
<ObjectArray
|
<ObjectArray
|
||||||
node={node}
|
node={node}
|
||||||
@@ -138,6 +143,7 @@ class Details extends React.Component {
|
|||||||
field={prop}
|
field={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
description={props[prop].description}
|
description={props[prop].description}
|
||||||
|
required={props[prop].required}
|
||||||
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
|
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
|
||||||
onChangeObjectHandler={this.props.onChangeArrayObjectHandler}
|
onChangeObjectHandler={this.props.onChangeArrayObjectHandler}
|
||||||
onClickDeleteArrayObjectHandler={this.props.onClickDeleteArrayObjectHandler}
|
onClickDeleteArrayObjectHandler={this.props.onClickDeleteArrayObjectHandler}
|
||||||
@@ -159,13 +165,14 @@ class Details extends React.Component {
|
|||||||
<Boolean
|
<Boolean
|
||||||
name={prop}
|
name={prop}
|
||||||
selected={props[prop].value}
|
selected={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
onClick={this.props.onClickBooleanHandler}
|
onClick={this.props.onClickBooleanHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case '../common/dictionary.json':
|
case 'dictionary':
|
||||||
case 'object':
|
case 'object':
|
||||||
control = (
|
control = (
|
||||||
<GenericObject
|
<GenericObject
|
||||||
@@ -174,6 +181,7 @@ class Details extends React.Component {
|
|||||||
description={props[prop].description}
|
description={props[prop].description}
|
||||||
key={uuid()}
|
key={uuid()}
|
||||||
field={prop}
|
field={prop}
|
||||||
|
required={props[prop].required}
|
||||||
onClickAddObjectHandler={
|
onClickAddObjectHandler={
|
||||||
this.props.onClickAddGenericObjectHandler
|
this.props.onClickAddGenericObjectHandler
|
||||||
}
|
}
|
||||||
@@ -189,7 +197,7 @@ class Details extends React.Component {
|
|||||||
|
|
||||||
if (props[prop].$ref && !props[prop].control) {
|
if (props[prop].$ref && !props[prop].control) {
|
||||||
switch (props[prop].$ref) {
|
switch (props[prop].$ref) {
|
||||||
case '../common/identifier.json':
|
case 'identifier':
|
||||||
control = (
|
control = (
|
||||||
<div className="item" key={prop}>
|
<div className="item" key={prop}>
|
||||||
{header}
|
{header}
|
||||||
@@ -197,6 +205,7 @@ class Details extends React.Component {
|
|||||||
<Text
|
<Text
|
||||||
name={prop}
|
name={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
onChange={this.onChangeHandler}
|
onChange={this.onChangeHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -218,6 +227,7 @@ class Details extends React.Component {
|
|||||||
<Slider
|
<Slider
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
field={prop}
|
field={prop}
|
||||||
|
required={props[prop].required}
|
||||||
onChangeHandler={this.props.onChangeSliderHandler}
|
onChangeHandler={this.props.onChangeSliderHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -233,6 +243,7 @@ class Details extends React.Component {
|
|||||||
key={prop}
|
key={prop}
|
||||||
name={prop}
|
name={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
onChangeHandler={this.props.onChangeCSVHandler}
|
onChangeHandler={this.props.onChangeCSVHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -248,6 +259,7 @@ class Details extends React.Component {
|
|||||||
field={prop}
|
field={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
description={props[prop].description}
|
description={props[prop].description}
|
||||||
|
required={props[prop].required}
|
||||||
onChangeHandler={this.props.onChangePhaseHandler}
|
onChangeHandler={this.props.onChangePhaseHandler}
|
||||||
onClickRemoveHandler={this.props.onClickRemovePhaseHander}
|
onClickRemoveHandler={this.props.onClickRemovePhaseHander}
|
||||||
/>
|
/>
|
||||||
@@ -260,7 +272,9 @@ class Details extends React.Component {
|
|||||||
key={prop}
|
key={prop}
|
||||||
field={prop}
|
field={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
|
prefix="node"
|
||||||
description={props[prop].description}
|
description={props[prop].description}
|
||||||
|
required={props[prop].required}
|
||||||
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
|
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
|
||||||
onChangeERHandler={this.props.onChangeERHandler}
|
onChangeERHandler={this.props.onChangeERHandler}
|
||||||
onClickDeleteERHandler={this.props.onClickDeleteERHandler}
|
onClickDeleteERHandler={this.props.onClickDeleteERHandler}
|
||||||
@@ -278,6 +292,7 @@ class Details extends React.Component {
|
|||||||
field={prop}
|
field={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
description={props[prop].description}
|
description={props[prop].description}
|
||||||
|
required={props[prop].required}
|
||||||
onClickHandler={this.props.onClickArrayHandler}
|
onClickHandler={this.props.onClickArrayHandler}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -290,6 +305,7 @@ class Details extends React.Component {
|
|||||||
<TextArea
|
<TextArea
|
||||||
name={prop}
|
name={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
onChange={this.onChangeHandler}
|
onChange={this.onChangeHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -305,6 +321,7 @@ class Details extends React.Component {
|
|||||||
key={prop}
|
key={prop}
|
||||||
name={prop}
|
name={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
onChangeHandler={this.props.onChangeCSVHandler}
|
onChangeHandler={this.props.onChangeCSVHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -316,13 +333,15 @@ class Details extends React.Component {
|
|||||||
<GenericObject
|
<GenericObject
|
||||||
name={prop}
|
name={prop}
|
||||||
value={props[prop].value}
|
value={props[prop].value}
|
||||||
|
vocab={props[prop].vocab}
|
||||||
description={props[prop].description}
|
description={props[prop].description}
|
||||||
key={uuid()}
|
key={uuid()}
|
||||||
field={prop}
|
field={prop}
|
||||||
|
required={props[prop].required}
|
||||||
onClickAddObjectHandler={
|
onClickAddObjectHandler={
|
||||||
this.props.onClickAddGenericObjectHandler
|
this.props.onClickAddGenericObjectHandler
|
||||||
}
|
}
|
||||||
onClickDeleteArrayObjectHandler={
|
onClickDeleteObjectHandler={
|
||||||
this.props.onClickDeleteGenericObjectHandler
|
this.props.onClickDeleteGenericObjectHandler
|
||||||
}
|
}
|
||||||
onChangeHandler={this.props.onChangeGenericObjectHandler}
|
onChangeHandler={this.props.onChangeGenericObjectHandler}
|
||||||
@@ -337,6 +356,7 @@ class Details extends React.Component {
|
|||||||
description={props[prop].description}
|
description={props[prop].description}
|
||||||
key={uuid()}
|
key={uuid()}
|
||||||
field={prop}
|
field={prop}
|
||||||
|
required={props[prop].required}
|
||||||
onClickAddTextHandler={this.props.onClickAddTextHandler}
|
onClickAddTextHandler={this.props.onClickAddTextHandler}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -346,6 +366,49 @@ class Details extends React.Component {
|
|||||||
details.push(control);
|
details.push(control);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unknownProperties = Object.keys(props).filter(prop => props[prop].type === 'unknown');
|
||||||
|
if (unknownProperties.length) {
|
||||||
|
const msg = `Import extension schema(s) to enable modification`
|
||||||
|
const header = (
|
||||||
|
<div className="item-header">
|
||||||
|
Unknown Properties
|
||||||
|
<span
|
||||||
|
data-tooltip-id="unknown-tooltip"
|
||||||
|
className="material-icons"
|
||||||
|
data-tooltip-content={msg}
|
||||||
|
>
|
||||||
|
info
|
||||||
|
</span>
|
||||||
|
<Tooltip id="unknown-tooltip" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const propItems = [];
|
||||||
|
const maxLength = 50;
|
||||||
|
for (const prop of unknownProperties) {
|
||||||
|
let value = props[prop].value;
|
||||||
|
value = (typeof value == 'object')? JSON.stringify(value, null, 2) : String(value);
|
||||||
|
value = (value.length > maxLength)? `${value.substring(0, maxLength)}...` : value;
|
||||||
|
|
||||||
|
propItems.push(
|
||||||
|
<div className="item-value">
|
||||||
|
<span className="unknown-header">{"\u2043"} {prop} </span>
|
||||||
|
<span className='unknown-value'>{value}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let control = (
|
||||||
|
<div className="item" key="unknown">
|
||||||
|
{header}
|
||||||
|
<ul className="item-value" id='unknown-properties'>
|
||||||
|
{propItems}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
details.push(control);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
show={this.props.show}
|
show={this.props.show}
|
||||||
@@ -1,35 +1,36 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Handle, Position } from 'reactflow';
|
import { Handle, Position } from 'reactflow';
|
||||||
import Images from '../../imgs/Images';
|
import Images from '../../util/Images';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import './FlowNode.scss';
|
import './FlowNode.scss';
|
||||||
|
|
||||||
export default class FlowNode extends React.Component {
|
export default class FlowNode extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
|
||||||
selected: false,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { node, } = this.props.data;
|
const { node, } = this.props.data;
|
||||||
let display = node.id.split('--')[0];
|
let display = node.id.split('--')[0];
|
||||||
const border = node.selected ? 'solid 1px blue' : '';
|
|
||||||
|
let cls = classNames({
|
||||||
|
'node-item': true,
|
||||||
|
'selected': node.selected,
|
||||||
|
});
|
||||||
|
|
||||||
|
let labelCls = classNames({
|
||||||
|
"node-label": true,
|
||||||
|
})
|
||||||
|
|
||||||
if (node.properties.name && node.properties.name.value) {
|
if (node.properties.name && node.properties.name.value) {
|
||||||
display = node.properties.name.value;
|
display = node.properties.name.value;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div className={cls}
|
||||||
style={{
|
style={{
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
backgroundSize: 'contain',
|
|
||||||
backgroundImage: `url(${node.customImg ? node.customImg : Images.getImage(node.img)})`,
|
backgroundImage: `url(${node.customImg ? node.customImg : Images.getImage(node.img)})`,
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
border: `${border}`,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Handle
|
<Handle
|
||||||
@@ -63,7 +64,7 @@ export default class FlowNode extends React.Component {
|
|||||||
isConnectable={this.props.isConnectable}
|
isConnectable={this.props.isConnectable}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="nodeLabel">
|
<div className={labelCls}>
|
||||||
{display}
|
{display}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
25
stix-modeler-app/src/components/Flow/FlowNode.scss
Normal file
25
stix-modeler-app/src/components/Flow/FlowNode.scss
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
@use '../../defaults';
|
||||||
|
|
||||||
|
.node-label {
|
||||||
|
overflow: visible;
|
||||||
|
position: relative;
|
||||||
|
bottom: -100%;
|
||||||
|
line-height: 11px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-item {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: 'no-repeat';
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
border: 1px solid blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hovered {
|
||||||
|
border: 1px solid red;
|
||||||
|
}
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
/* eslint-disable react/prefer-stateless-function */
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import Panel from './ui/panel/Panel';
|
import Panel from './ui/panel/Panel';
|
||||||
import Images from '../imgs/Images';
|
import Images from '../util/Images';
|
||||||
|
|
||||||
import './SubmissionError.scss';
|
import './SubmissionError.scss';
|
||||||
|
|
||||||
class SubmissionError extends React.Component {
|
class SubmissionError extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const errorStructure = {};
|
const errorStructure = {};
|
||||||
const msg = [];
|
const msg = [];
|
||||||
|
|
||||||
this.props.error.map((item, i) => {
|
this.props.error.map((item, i) => {
|
||||||
if (!errorStructure.hasOwnProperty(item.node)) {
|
if (!(item.node in errorStructure)){
|
||||||
errorStructure[item.node] = {};
|
errorStructure[item.node] = {};
|
||||||
|
errorStructure[item.node].name = item.name;
|
||||||
errorStructure[item.node].details = [];
|
errorStructure[item.node].details = [];
|
||||||
errorStructure[item.node].img = item.img;
|
errorStructure[item.node].img = item.img;
|
||||||
errorStructure[item.node].details.push({
|
errorStructure[item.node].details.push({
|
||||||
@@ -34,7 +35,7 @@ class SubmissionError extends React.Component {
|
|||||||
|
|
||||||
for (const item in errorStructure) {
|
for (const item in errorStructure) {
|
||||||
const details = [];
|
const details = [];
|
||||||
|
const name = errorStructure[item].name;
|
||||||
if (errorStructure[item].details) {
|
if (errorStructure[item].details) {
|
||||||
errorStructure[item].details.map((detail) => {
|
errorStructure[item].details.map((detail) => {
|
||||||
details.push(
|
details.push(
|
||||||
@@ -50,11 +51,11 @@ class SubmissionError extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
msg.push(
|
msg.push(
|
||||||
<div key={item}>
|
<div className='submission-item' key={item} onClick={() => this.props.onClickNodeHandler(item)}>
|
||||||
<div className="header">
|
<div className="container-header">
|
||||||
<img src={Images.getImage(errorStructure[item].img)} width="30" />
|
<img src={Images.getImage(errorStructure[item].img)} width="30" />
|
||||||
{' '}
|
{' '}
|
||||||
{item}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
<div className="rows-container">
|
<div className="rows-container">
|
||||||
{details}
|
{details}
|
||||||
@@ -69,6 +70,9 @@ class SubmissionError extends React.Component {
|
|||||||
show={this.props.show}
|
show={this.props.show}
|
||||||
onClickHideHandler={this.props.onClickHideHandler}
|
onClickHideHandler={this.props.onClickHideHandler}
|
||||||
>
|
>
|
||||||
|
<div className="header">
|
||||||
|
Errors
|
||||||
|
</div>
|
||||||
<div className="submission-error">
|
<div className="submission-error">
|
||||||
{msg}
|
{msg}
|
||||||
</div>
|
</div>
|
||||||
42
stix-modeler-app/src/components/SubmissionError.scss
Normal file
42
stix-modeler-app/src/components/SubmissionError.scss
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
@use "../defaults";
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 30px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: defaults.$lt-gray-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.submission-error {
|
||||||
|
|
||||||
|
.container-header {
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: middle;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
padding-left: 55px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: defaults.$default-active-bg;
|
||||||
|
padding-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submission-item {
|
||||||
|
border: 1px black solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submission-item:hover {
|
||||||
|
background-color: beige;
|
||||||
|
}
|
||||||
|
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable react/prefer-stateless-function */
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import Panel from '../ui/panel/Panel';
|
import Panel from '../ui/panel/Panel';
|
||||||
@@ -24,6 +24,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
height: 30px;
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-copy {
|
||||||
|
height: 35px;
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,12 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
height: 30px;
|
padding: 2px;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.json-copy {
|
||||||
|
height: 35px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../defaults';
|
@use '../defaults';
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
background-color: $lt-gray-bg;
|
background-color: defaults.$lt-gray-bg;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
@@ -30,10 +30,10 @@
|
|||||||
padding-left: 7px;
|
padding-left: 7px;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: $default-active-bg;
|
background-color: defaults.$default-active-bg;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding-top: 7px;
|
padding-top: 7px;
|
||||||
color: $light-font-0;
|
color: defaults.$light-font-0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -48,10 +48,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.delete:hover {
|
.delete:hover {
|
||||||
background-color: $error-font;
|
background-color: defaults.$error-font;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invalid {
|
||||||
|
border: red solid 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required-warning {
|
||||||
|
color: red;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
@@ -68,7 +77,7 @@
|
|||||||
|
|
||||||
.item-header {
|
.item-header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@@ -78,6 +87,24 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inferred-header {
|
||||||
|
color: defaults.$warning-font;
|
||||||
|
}
|
||||||
|
|
||||||
|
#unknown-properties {
|
||||||
|
padding-left: 10px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unknown-header {
|
||||||
|
color: defaults.$error-font;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.unknown-value {
|
||||||
|
color: #849BB0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
94
stix-modeler-app/src/components/layout/LayoutPanel.jsx
Normal file
94
stix-modeler-app/src/components/layout/LayoutPanel.jsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { inject, observer } from 'mobx-react';
|
||||||
|
import Panel from '../ui/panel/Panel';
|
||||||
|
import RadioGroup from '../ui/inputs/RadioGroup';
|
||||||
|
import OrientationRadioGroup from '../ui/inputs/OrientationRadioGroup';
|
||||||
|
|
||||||
|
import '../layout/LayoutPanel.scss';
|
||||||
|
|
||||||
|
class LayoutPanel extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.store = this.props.store.appStore;
|
||||||
|
this.store.setNodeTypes(["campaign", "identity"]);
|
||||||
|
this.layoutMethod = this.store.getLayoutMethod();
|
||||||
|
this.state = {
|
||||||
|
horizontalSpacing: '',
|
||||||
|
verticalSpacing: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLayoutChange = (newLayoutMethod) => {
|
||||||
|
this.store.setLayoutMethod(newLayoutMethod); // Update store
|
||||||
|
this.store.setNodeLayout(); // Redraw graph
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOrientationChange = (newOrientation) => {
|
||||||
|
this.store.setOrientation(newOrientation); // Update store
|
||||||
|
this.store.setNodeLayout(); // Redraw graph
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHorizontalSpacingChange = (event) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
this.store.setHorizontalSpacing(newValue);
|
||||||
|
this.store.setNodeLayout(); // Redraw graph
|
||||||
|
};
|
||||||
|
|
||||||
|
handleVerticalSpacingChange = (event) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
this.store.setVerticalSpacing(newValue);
|
||||||
|
this.store.setNodeLayout(); // Redraw graph
|
||||||
|
};
|
||||||
|
|
||||||
|
handleAlignDistributeClick = (action) => {
|
||||||
|
this.store.setAlignmentOrDistribution(action); // Store action type
|
||||||
|
// this.store.updateNodeLayout(); // Apply layout changes
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Panel
|
||||||
|
show={this.props.show}
|
||||||
|
onClickHideHandler={this.props.onClickHideHandler}
|
||||||
|
>
|
||||||
|
<div className="layout-panel">
|
||||||
|
<div className="header">Layout Panel</div>
|
||||||
|
<div className="content">
|
||||||
|
<RadioGroup
|
||||||
|
// defaultLayoutMethod={this.store.getLayoutMethod()}
|
||||||
|
defaultLayoutMethod={""}
|
||||||
|
onLayoutChange={this.handleLayoutChange}
|
||||||
|
/>
|
||||||
|
<OrientationRadioGroup
|
||||||
|
defaultOrientation={this.store.getOrientation()}
|
||||||
|
onOrientationChange={this.handleOrientationChange}
|
||||||
|
/>
|
||||||
|
<hr />
|
||||||
|
<p>Node Default Spacing Options (pixels)</p>
|
||||||
|
<div className="spacing-input">
|
||||||
|
<label htmlFor="horizontalSpacing">Horizontal Spacing:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="horizontalSpacing"
|
||||||
|
value={this.store.getHorizontalSpacing()}
|
||||||
|
onChange={this.handleHorizontalSpacingChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="spacing-input">
|
||||||
|
<label htmlFor="verticalSpacing">Vertical Spacing:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="verticalSpacing"
|
||||||
|
value={this.store.getVerticalSpacing()}
|
||||||
|
onChange={this.handleVerticalSpacingChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default inject('store')(observer(LayoutPanel));
|
||||||
48
stix-modeler-app/src/components/layout/LayoutPanel.scss
Normal file
48
stix-modeler-app/src/components/layout/LayoutPanel.scss
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.header {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
border: solid 1px #dddddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-panel {
|
||||||
|
.spacing-input {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 2;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 40px; // adjust the size as needed
|
||||||
|
height: 40px; // adjust the size as needed
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px 2px rgba(0, 123, 255, 0.75); // blue glow effect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
/* eslint-disable react/prefer-stateless-function */
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import MenuItem from './MenuItem';
|
import MenuItem from './MenuItem';
|
||||||
import Images from '../../imgs/Images';
|
import Images from '../../util/Images';
|
||||||
|
|
||||||
import './BottomMenu.scss';
|
import './BottomMenu.scss';
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../defaults';
|
@use '../../defaults';
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -29,12 +29,12 @@
|
|||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: $default-active-bg;
|
background-color: defaults.$default-active-bg;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-left: 7px;
|
padding-left: 7px;
|
||||||
color: $light-font-0;
|
color: defaults.$light-font-0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ class TopMenu extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let groupLabel = 'Select';
|
let groupLabel = 'Group';
|
||||||
let groupClass = '';
|
let groupClass = '';
|
||||||
let items;
|
let items;
|
||||||
|
|
||||||
@@ -38,7 +38,8 @@ class TopMenu extends React.Component {
|
|||||||
groupClass = 'cancel-btn';
|
groupClass = 'cancel-btn';
|
||||||
items = (
|
items = (
|
||||||
<div id="myDropdown" className="dropdown-content">
|
<div id="myDropdown" className="dropdown-content">
|
||||||
<a onClick={this.submitGroup}>Create Group</a>
|
<a onClick={this.submitGroup}>Create Group
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,7 @@ class TopMenu extends React.Component {
|
|||||||
<div
|
<div
|
||||||
data-tooltip-id="select-tooltip"
|
data-tooltip-id="select-tooltip"
|
||||||
data-tooltip-content="Select Nodes"
|
data-tooltip-content="Select Nodes"
|
||||||
className={`grouping-btn menu-item ${groupClass}`}
|
className={`grouping-btn menu-btn menu-item ${groupClass}`}
|
||||||
onClick={this.flipGroupMode}
|
onClick={this.flipGroupMode}
|
||||||
>
|
>
|
||||||
{groupLabel}
|
{groupLabel}
|
||||||
@@ -57,6 +58,8 @@ class TopMenu extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const badge = this.props.errors ? (<span className="badge"></span>) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="top-menu">
|
<div className="top-menu">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@@ -74,69 +77,85 @@ class TopMenu extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
data-tooltip-id="paste-tooltip"
|
data-tooltip-id="view-tooltip"
|
||||||
data-tooltip-content="Paste JSON"
|
data-tooltip-content="View Bundle"
|
||||||
className="json-paste-btn menu-item-medium"
|
className="menu-btn menu-item"
|
||||||
onClick={this.props.onClickShowJsonPasteHandler}
|
onClick={this.props.onClickShowJsonHandler}
|
||||||
>
|
>
|
||||||
{'{ + }'}
|
<i className="material-icons">description</i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-tooltip-id="view-tooltip"
|
data-tooltip-id="paste-tooltip"
|
||||||
data-tooltip-content="View JSON"
|
data-tooltip-content="Paste Bundle"
|
||||||
className="json-btn menu-item-small"
|
className="menu-btn menu-item"
|
||||||
onClick={this.props.onClickShowJsonHandler}
|
onClick={this.props.onClickShowJsonPasteHandler}
|
||||||
>
|
>
|
||||||
{'{ }'}
|
<i className="material-icons">note_add</i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-tooltip-id="schema-tooltip"
|
data-tooltip-id="schema-tooltip"
|
||||||
data-tooltip-content="Paste Schema"
|
data-tooltip-content="Paste Schema"
|
||||||
className="schema-paste-btn menu-item-medium"
|
className="menu-btn menu-item"
|
||||||
onClick={this.props.onClickShowSchemaPasteHandler}
|
onClick={this.props.onClickShowSchemaPasteHandler}
|
||||||
>
|
>
|
||||||
{'{ * }'}
|
<i className="material-icons">add_box</i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-tooltip-id="sdo-tooltip"
|
data-tooltip-id="layout"
|
||||||
data-tooltip-content="SDO Extensions"
|
data-tooltip-content="Graph Layout and Filtering"
|
||||||
className="sdos-btn menu-item"
|
className="sdos-btn menu-item"
|
||||||
onClick={this.props.onClickShowSDOPickerHandler}
|
onClick={this.props.onClickShowLayoutPanelHandler}
|
||||||
>
|
>
|
||||||
Exts
|
Layout
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-tooltip-id="import-tooltip"
|
data-tooltip-id="import-tooltip"
|
||||||
data-tooltip-content="Import Data from File"
|
data-tooltip-content="Import Data from File"
|
||||||
className="reset-btn menu-item"
|
className="menu-btn menu-item"
|
||||||
onClick={this.props.onClickShowImporterHandler}
|
onClick={this.props.onClickShowImporterHandler}
|
||||||
>
|
>
|
||||||
Import
|
<i className="material-icons">folder</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
data-tooltip-id="sdo-tooltip"
|
||||||
|
data-tooltip-content="SDO Extensions"
|
||||||
|
className="menu-btn menu-item"
|
||||||
|
onClick={this.props.onClickShowExtensionPickerHandler}
|
||||||
|
>
|
||||||
|
EXT
|
||||||
</div>
|
</div>
|
||||||
{group}
|
{group}
|
||||||
<div
|
<div
|
||||||
data-tooltip-id="clear-tooltip"
|
data-tooltip-id="clear-tooltip"
|
||||||
data-tooltip-content="Clear JSON"
|
data-tooltip-content="Reset Bundle"
|
||||||
className="reset-btn menu-item"
|
className="reset-btn menu-btn menu-item"
|
||||||
onClick={this.props.onClickResetHandler}
|
onClick={this.props.onClickResetHandler}
|
||||||
>
|
>
|
||||||
<span className="i material-icons">refresh</span>
|
<span className="material-icons">refresh</span>
|
||||||
{' '}
|
|
||||||
Reset
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-tooltip-id="submit-tooltip"
|
data-tooltip-id="submit-tooltip"
|
||||||
data-tooltip-content="Submit JSON"
|
data-tooltip-content="Export JSON"
|
||||||
className="reset-btn menu-item"
|
className="menu-btn menu-item"
|
||||||
onClick={this.props.onClickSubmitHandler}
|
onClick={this.props.onClickExportHandler}
|
||||||
>
|
>
|
||||||
<span className="i material-icons">add</span>
|
<i className="material-icons">save</i>
|
||||||
{' '}
|
{' '}
|
||||||
Submit
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
data-tooltip-id="error-tooltip"
|
||||||
|
data-tooltip-content="Bundle Errors"
|
||||||
|
className="menu-btn menu-item"
|
||||||
|
onClick={this.props.onClickShowErrorHandler}
|
||||||
|
>
|
||||||
|
<span className="material-icons">error</span>
|
||||||
|
{badge}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tooltip id="creator-tooltip" />
|
<Tooltip id="creator-tooltip" />
|
||||||
@@ -147,6 +166,7 @@ class TopMenu extends React.Component {
|
|||||||
<Tooltip id="import-tooltip" />
|
<Tooltip id="import-tooltip" />
|
||||||
<Tooltip id="clear-tooltip" />
|
<Tooltip id="clear-tooltip" />
|
||||||
<Tooltip id="submit-tooltip" />
|
<Tooltip id="submit-tooltip" />
|
||||||
|
<Tooltip id="error-tooltip" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
98
stix-modeler-app/src/components/menus/TopMenu.scss
Normal file
98
stix-modeler-app/src/components/menus/TopMenu.scss
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
@use '../../defaults';
|
||||||
|
|
||||||
|
.top-menu {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 10px 8px 0px 8px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: defaults.$default-active-bg;
|
||||||
|
margin-left: 10px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.i {
|
||||||
|
width: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
background-color: #2f689d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grouping-btn {
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: defaults.$default-active-bg;
|
||||||
|
width: 55px;
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: -25%;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
min-width: 160px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a {
|
||||||
|
color: black;
|
||||||
|
padding: 12px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a:hover {background-color: #ddd;}
|
||||||
|
|
||||||
|
.dropdown:hover .dropdown-content {display: block;}
|
||||||
|
|
||||||
|
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: defaults.$error-font;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctr-input {
|
||||||
|
padding-right: 10px;
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.reset-btn:hover {
|
||||||
|
background-color: defaults.$error-font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -3px;
|
||||||
|
right: -3px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: red;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,16 @@ import { Tooltip } from 'react-tooltip';
|
|||||||
import Panel from '../ui/panel/Panel';
|
import Panel from '../ui/panel/Panel';
|
||||||
import Text from '../ui/inputs/Text';
|
import Text from '../ui/inputs/Text';
|
||||||
import Boolean from '../ui/inputs/Boolean';
|
import Boolean from '../ui/inputs/Boolean';
|
||||||
import Images from '../../imgs/Images';
|
import Images from '../../util/Images';
|
||||||
|
|
||||||
|
import '../details.scss';
|
||||||
import './RelationshipDetails.scss';
|
import './RelationshipDetails.scss';
|
||||||
|
|
||||||
class RelationshipDetails extends React.Component {
|
class RelationshipDetails extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
type: 'relates to',
|
type: 'related-to',
|
||||||
x_exclusive: false,
|
x_exclusive: false,
|
||||||
};
|
};
|
||||||
this.onSubmitHandler = this.onSubmitHandler.bind(this);
|
this.onSubmitHandler = this.onSubmitHandler.bind(this);
|
||||||
@@ -45,7 +46,7 @@ class RelationshipDetails extends React.Component {
|
|||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.setState({
|
this.setState({
|
||||||
type: 'relates to',
|
type: 'related-to',
|
||||||
x_exclusive: false,
|
x_exclusive: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
@use '../../defaults';
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-top: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: defaults.$default-font-family;
|
||||||
|
line-height: 30px;
|
||||||
|
img {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
img.src-image {
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
img.target-image {
|
||||||
|
padding-right: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
.rel-type {
|
||||||
|
color: defaults.$default-active-bg;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 11px 5px 11px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: defaults.$default-active-bg;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
.i {
|
||||||
|
width: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import { toJS } from 'mobx';
|
|||||||
import { Tooltip } from 'react-tooltip';
|
import { Tooltip } from 'react-tooltip';
|
||||||
import Panel from '../ui/panel/Panel';
|
import Panel from '../ui/panel/Panel';
|
||||||
import Text from '../ui/inputs/Text';
|
import Text from '../ui/inputs/Text';
|
||||||
import Images from '../../imgs/Images';
|
import Images from '../../util/Images';
|
||||||
|
|
||||||
import './RelationshipDetails.scss';
|
import './RelationshipDetails.scss';
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import Panel from '../ui/panel/Panel';
|
||||||
|
import Images from '../../util/Images';
|
||||||
|
|
||||||
|
import './RelationshipPicker.scss';
|
||||||
|
|
||||||
|
class RelationshipPicker extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickSelectRelHandler(relationship) {
|
||||||
|
this.props.onClickSelectRelHandler(relationship);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// Do not allow relationship defininition for generic observables
|
||||||
|
let create;
|
||||||
|
if (this.props.relationships.length == 0 || this.props.relationships[0].target_ref) {
|
||||||
|
create = (
|
||||||
|
<div
|
||||||
|
className="item"
|
||||||
|
key="new-relationship"
|
||||||
|
onClick={this.props.onClickShowRelDetailsHandler}
|
||||||
|
>
|
||||||
|
<img className="src-image" src={Images.getImage('add.png')} width="20" />
|
||||||
|
<span className="rel-type"> Create New Relationship </span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel
|
||||||
|
show={this.props.show}
|
||||||
|
onClickHideHandler={this.props.onClickHideHandler}
|
||||||
|
>
|
||||||
|
<div className="relationship-picker">
|
||||||
|
<div className="header">
|
||||||
|
<img src={Images.getImage('relationship.png')} width="20" />
|
||||||
|
{' '}
|
||||||
|
Possible Relationships
|
||||||
|
</div>
|
||||||
|
<div className="content">
|
||||||
|
{create}
|
||||||
|
{
|
||||||
|
this.props.relationships.slice(1).map((relationship) => {
|
||||||
|
const src = relationship.source_ref.split('--')[0];
|
||||||
|
const target = relationship.subTarget ??
|
||||||
|
relationship.target_ref.split('--')[0];
|
||||||
|
const srcImg = Images.getImage(relationship.srcImg);
|
||||||
|
const targetImg = Images.getImage(relationship.targetImg);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="item"
|
||||||
|
key={relationship.id}
|
||||||
|
onClick={() => this.onClickSelectRelHandler(relationship)}
|
||||||
|
>
|
||||||
|
<img className="src-image" src={srcImg} width="20" />
|
||||||
|
{' '}
|
||||||
|
{src}
|
||||||
|
<span className="rel-type">
|
||||||
|
{' '}
|
||||||
|
{relationship.relationship_type}
|
||||||
|
{' '}
|
||||||
|
</span>
|
||||||
|
{target}
|
||||||
|
{' '}
|
||||||
|
<img className="target-image" src={targetImg} width="20" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} export default (observer(RelationshipPicker));
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../../defaults";
|
@use "../../defaults";
|
||||||
|
|
||||||
.relationship-picker {
|
.relationship-picker {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-family: $default-font-family;
|
font-family: defaults.$default-font-family;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rel-type {
|
.rel-type {
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
353
stix-modeler-app/src/components/schema/ExtensionEditor.jsx
Normal file
353
stix-modeler-app/src/components/schema/ExtensionEditor.jsx
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import { toJS } from 'mobx';
|
||||||
|
import { Tooltip } from 'react-tooltip';
|
||||||
|
import Panel from '../ui/panel/Panel';
|
||||||
|
import Images from '../../util/Images';
|
||||||
|
|
||||||
|
import ArraySelector from '../ui/inputs/ArraySelector';
|
||||||
|
import Boolean from '../ui/inputs/Boolean';
|
||||||
|
import CSVInput from '../ui/inputs/CSVInput';
|
||||||
|
import DateTime from '../ui/inputs/DateTime';
|
||||||
|
import ExternalReferences from '../ui/complex/ExternalReferences';
|
||||||
|
import FileSelector from '../ui/inputs/FileSelector';
|
||||||
|
import ObjectArray from '../ui/complex/ObjectArray';
|
||||||
|
import Slider from '../ui/inputs/Slider';
|
||||||
|
import Text from '../ui/inputs/Text';
|
||||||
|
import TextArea from '../ui/inputs/TextArea';
|
||||||
|
|
||||||
|
|
||||||
|
import '../details.scss';
|
||||||
|
|
||||||
|
class ExtensionEditor extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.onChangeExtHandler = this.onChangeExtHandler.bind(this);
|
||||||
|
this.onChangeHandler = this.onChangeHandler.bind(this)
|
||||||
|
this.onChangeDateHandler = this.onChangeDateHandler.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeExtHandler(event) {
|
||||||
|
if (event.target.files && event.target.files[0]) {
|
||||||
|
const value = URL.createObjectURL(event.target.files[0]);
|
||||||
|
|
||||||
|
const mutatedEvent = {
|
||||||
|
currentTarget: {
|
||||||
|
name: 'customImg',
|
||||||
|
value: value
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.props.onChangeExtHandler(mutatedEvent);
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeHandler(event) {
|
||||||
|
this.props.onChangeNodeHandler(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeDateHandler(property, datetime) {
|
||||||
|
this.props.onChangeDateHandler(property, datetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const extension = toJS(this.props.extension);
|
||||||
|
let props = {};
|
||||||
|
let img;
|
||||||
|
const details = [];
|
||||||
|
|
||||||
|
const deleteIcon = <span className="material-icons">delete_forever</span>;
|
||||||
|
|
||||||
|
if (!extension) return;
|
||||||
|
|
||||||
|
props = extension.properties;
|
||||||
|
if (extension.customImg !== undefined) {
|
||||||
|
img = <img src={extension.customImg} alt="Custom" width="30" />;
|
||||||
|
} else {
|
||||||
|
img = <img src={Images.getImage(extension.img)} alt="Custom" width="30" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = (
|
||||||
|
<div className="item-header">
|
||||||
|
Update Icon
|
||||||
|
<span
|
||||||
|
data-tooltip-id="icon-tooltip"
|
||||||
|
className="material-icons"
|
||||||
|
data-tooltip-content="Set SDO icon to a local image"
|
||||||
|
>
|
||||||
|
info
|
||||||
|
</span>
|
||||||
|
<Tooltip id="icon-tooltip" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
let control = (
|
||||||
|
<div className="item" key="icon">
|
||||||
|
{header}
|
||||||
|
<FileSelector
|
||||||
|
name="image"
|
||||||
|
type="image/*"
|
||||||
|
key="icon"
|
||||||
|
multiple={false}
|
||||||
|
onChange={this.onChangeExtHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
details.push(control);
|
||||||
|
|
||||||
|
for (const prop in props) {
|
||||||
|
const header = (
|
||||||
|
<div className="item-header">
|
||||||
|
{prop}
|
||||||
|
<span
|
||||||
|
data-tooltip-id={`${prop}-tooltip`}
|
||||||
|
className="material-icons"
|
||||||
|
data-tooltip-content={props[prop].description}
|
||||||
|
>
|
||||||
|
info
|
||||||
|
</span>
|
||||||
|
<Tooltip id={`${prop}-tooltip`} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
let control = (
|
||||||
|
<div className="item" key={prop}>
|
||||||
|
{header}
|
||||||
|
<div className="item-value">{props[prop].value}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// If there is no type, we do not want to process. If a "control"
|
||||||
|
// is defined, that indicates special handling of the value.
|
||||||
|
if (props[prop].type && !props[prop].control) {
|
||||||
|
switch (props[prop].type) {
|
||||||
|
case 'number':
|
||||||
|
case 'string':
|
||||||
|
control = (
|
||||||
|
<div className="item" key={prop}>
|
||||||
|
{header}
|
||||||
|
<div className="item-value">
|
||||||
|
<Text
|
||||||
|
name={prop}
|
||||||
|
value={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
|
onChange={this.onChangeHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'timestamp':
|
||||||
|
control = (
|
||||||
|
<div className="item" key={prop}>
|
||||||
|
{header}
|
||||||
|
<div className="item-value">
|
||||||
|
<DateTime
|
||||||
|
name={prop}
|
||||||
|
selected={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
|
onTextChange={this.onChangeHandler}
|
||||||
|
onDateChange={this.onChangeDateHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
if (props[prop].vocab) {
|
||||||
|
control = (
|
||||||
|
<ArraySelector
|
||||||
|
vocab={props[prop].vocab}
|
||||||
|
key={prop}
|
||||||
|
field={prop}
|
||||||
|
value={props[prop].value}
|
||||||
|
description={props[prop].description}
|
||||||
|
required={props[prop].required}
|
||||||
|
onClickHandler={this.props.onClickArrayHandler}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Get array subtype, possibly nested
|
||||||
|
let refField = props[prop].items;
|
||||||
|
if (Array.isArray(refField)) {
|
||||||
|
refField = refField[0];
|
||||||
|
}
|
||||||
|
const ref = refField.$ref ?? refField.type;
|
||||||
|
|
||||||
|
if (ref.includes('dictionary.json') || ref === 'object') {
|
||||||
|
control = (
|
||||||
|
<ObjectArray
|
||||||
|
node={extension}
|
||||||
|
key={prop}
|
||||||
|
field={prop}
|
||||||
|
value={props[prop].value}
|
||||||
|
description={props[prop].description}
|
||||||
|
required={props[prop].required}
|
||||||
|
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
|
||||||
|
onChangeObjectHandler={this.props.onChangeArrayObjectHandler}
|
||||||
|
onClickDeleteArrayObjectHandler={this.props.onClickDeleteArrayObjectHandler}
|
||||||
|
onClickDeleteArrayObjectPropertyHandler={
|
||||||
|
this.props.onClickDeleteArrayObjectPropertyHandler
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
props[prop].control = 'listtextarea';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'boolean':
|
||||||
|
control = (
|
||||||
|
<div className="item" key={prop}>
|
||||||
|
{header}
|
||||||
|
<div className="item-value">
|
||||||
|
<Boolean
|
||||||
|
name={prop}
|
||||||
|
selected={props[prop].value}
|
||||||
|
onClick={this.props.onClickBooleanHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props[prop].$ref && !props[prop].control) {
|
||||||
|
switch (props[prop].$ref) {
|
||||||
|
case '../common/identifier.json':
|
||||||
|
control = (
|
||||||
|
<div className="item" key={prop}>
|
||||||
|
{header}
|
||||||
|
<div className="item-value">
|
||||||
|
<Text
|
||||||
|
name={prop}
|
||||||
|
value={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
|
onChange={this.onChangeHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (props[prop].control) {
|
||||||
|
case 'hidden':
|
||||||
|
control = '';
|
||||||
|
break;
|
||||||
|
case 'slider':
|
||||||
|
control = (
|
||||||
|
<div className="item slider" key={prop}>
|
||||||
|
{header}
|
||||||
|
<div className="item-value">
|
||||||
|
<Slider
|
||||||
|
value={props[prop].value}
|
||||||
|
field={prop}
|
||||||
|
required={props[prop].required}
|
||||||
|
onChangeHandler={this.props.onChangeSliderHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'externalrefs':
|
||||||
|
control = (
|
||||||
|
<ExternalReferences
|
||||||
|
node={extension}
|
||||||
|
key={prop}
|
||||||
|
field={prop}
|
||||||
|
value={props[prop].value}
|
||||||
|
prefix="extension"
|
||||||
|
description={props[prop].description}
|
||||||
|
required={props[prop].required}
|
||||||
|
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
|
||||||
|
onChangeERHandler={this.props.onChangeERHandler}
|
||||||
|
onClickDeleteERHandler={this.props.onClickDeleteERHandler}
|
||||||
|
onClickDeletePropertyHandler={
|
||||||
|
this.props.onClickDeletePropertyHandler
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'stringselector':
|
||||||
|
control = (
|
||||||
|
<ArraySelector
|
||||||
|
vocab={props[prop].vocab}
|
||||||
|
key={prop}
|
||||||
|
field={prop}
|
||||||
|
value={props[prop].value}
|
||||||
|
description={props[prop].description}
|
||||||
|
required={props[prop].required}
|
||||||
|
onClickHandler={this.props.onClickArrayHandler}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'textarea':
|
||||||
|
control = (
|
||||||
|
<div className="item" key={prop}>
|
||||||
|
{header}
|
||||||
|
<div className="item-value">
|
||||||
|
<TextArea
|
||||||
|
name={prop}
|
||||||
|
value={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
|
onChange={this.onChangeHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'listtextarea':
|
||||||
|
control = (
|
||||||
|
<div className="item" key={prop}>
|
||||||
|
{header}
|
||||||
|
<div className="item-value">
|
||||||
|
<CSVInput
|
||||||
|
key={prop}
|
||||||
|
name={prop}
|
||||||
|
value={props[prop].value}
|
||||||
|
required={props[prop].required}
|
||||||
|
onChangeHandler={this.props.onChangeCSVHandler}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
details.push(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel
|
||||||
|
show={this.props.show}
|
||||||
|
onClickHideHandler={this.props.onClickHideHandler}
|
||||||
|
>
|
||||||
|
<div className="details">
|
||||||
|
<div className="header">
|
||||||
|
<div className="title">
|
||||||
|
{img}
|
||||||
|
{' '}
|
||||||
|
{extension.id}
|
||||||
|
</div>
|
||||||
|
<div className="delete" onClick={this.props.onClickDeleteHandler}>
|
||||||
|
{deleteIcon}
|
||||||
|
{' '}
|
||||||
|
<span className="text">Delete</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="body">
|
||||||
|
{details}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="footer" />
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} export default (observer(ExtensionEditor));
|
||||||
52
stix-modeler-app/src/components/schema/ExtensionPicker.jsx
Normal file
52
stix-modeler-app/src/components/schema/ExtensionPicker.jsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import Panel from '../ui/panel/Panel';
|
||||||
|
import Images from '../../util/Images';
|
||||||
|
|
||||||
|
import '../relationship/RelationshipPicker.scss';
|
||||||
|
|
||||||
|
class ExtensionPicker extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickSelectExtHandler(sdo) {
|
||||||
|
this.props.onClickHideHandler();
|
||||||
|
this.props.onClickSelectExtHandler(sdo);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Panel
|
||||||
|
show={this.props.show}
|
||||||
|
onClickHideHandler={this.props.onClickHideHandler}
|
||||||
|
>
|
||||||
|
<div className="relationship-picker">
|
||||||
|
<div className="header">STIX Domain Object (SDO) Extensions</div>
|
||||||
|
<div className="content">
|
||||||
|
{
|
||||||
|
this.props.extensions.map((ext) => {
|
||||||
|
let img = Images.getImage(ext.img);
|
||||||
|
if (ext.customImg) {
|
||||||
|
img = ext.customImg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="item"
|
||||||
|
key={ext.id}
|
||||||
|
onClick={() => this.onClickSelectExtHandler(ext)}
|
||||||
|
>
|
||||||
|
<img className="src-image" src={img} width="20" />
|
||||||
|
{' '}
|
||||||
|
{ext.properties.name.value.length > 0 ? ext.properties.name.value : ext.id}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} export default (observer(ExtensionPicker));
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable react/prefer-stateless-function */
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import Panel from '../ui/panel/Panel';
|
import Panel from '../ui/panel/Panel';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
button:focus {
|
button:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -8,8 +8,8 @@ button.def {
|
|||||||
width: auto;
|
width: auto;
|
||||||
min-width: 130px;
|
min-width: 130px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
color: $light-font-0;
|
color: defaults.$light-font-0;
|
||||||
font-family: $default-font-family;
|
font-family: defaults.$default-font-family;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -21,7 +21,7 @@ button.def {
|
|||||||
|
|
||||||
button.disabled {
|
button.disabled {
|
||||||
background-color: rgba(128,128,128,.8) !important;
|
background-color: rgba(128,128,128,.8) !important;
|
||||||
color: $gray-font-0 !important;
|
color: defaults.$gray-font-0 !important;
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Tooltip } from 'react-tooltip';
|
import { Tooltip } from 'react-tooltip';
|
||||||
import TextArea from '../inputs/TextArea';
|
import TextArea from '../inputs/TextArea';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import './confirmtextarea.scss';
|
import './confirmtextarea.scss';
|
||||||
|
|
||||||
@@ -18,10 +19,6 @@ class ConfirmTextarea extends React.Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeInputHandler(event) {
|
onChangeInputHandler(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -42,6 +39,9 @@ class ConfirmTextarea extends React.Component {
|
|||||||
const { field, } = this.props;
|
const { field, } = this.props;
|
||||||
const value = this.props.value ? this.props.value : [];
|
const value = this.props.value ? this.props.value : [];
|
||||||
const { description, } = this.props;
|
const { description, } = this.props;
|
||||||
|
const {required, } = this.props;
|
||||||
|
const invalid = required && !value.length;
|
||||||
|
const warning = invalid? (<div className='required-warning'>This field is required</div>) : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ct-container">
|
<div className="ct-container">
|
||||||
@@ -56,7 +56,10 @@ class ConfirmTextarea extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
<Tooltip id={`${field}-tooltip`} />
|
<Tooltip id={`${field}-tooltip`} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ct-body">
|
<div className={classNames({
|
||||||
|
"ct-body": true,
|
||||||
|
"invalid": invalid
|
||||||
|
})}>
|
||||||
<div className="ct-block-input">
|
<div className="ct-block-input">
|
||||||
<div className="input">
|
<div className="input">
|
||||||
<TextArea
|
<TextArea
|
||||||
@@ -74,6 +77,7 @@ class ConfirmTextarea extends React.Component {
|
|||||||
{value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Tooltip } from 'react-tooltip';
|
import { Tooltip } from 'react-tooltip';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import classNames from 'classnames';
|
||||||
import Text from '../inputs/Text';
|
import Text from '../inputs/Text';
|
||||||
|
|
||||||
import './externalreferences.scss';
|
import './externalreferences.scss';
|
||||||
@@ -17,8 +18,6 @@ class ExternalReferences extends React.Component {
|
|||||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {}
|
|
||||||
|
|
||||||
onChangeERHandler(event, value) {
|
onChangeERHandler(event, value) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -48,6 +47,10 @@ class ExternalReferences extends React.Component {
|
|||||||
const { field, } = this.props;
|
const { field, } = this.props;
|
||||||
const value = this.props.value ? this.props.value : [];
|
const value = this.props.value ? this.props.value : [];
|
||||||
const { description, } = this.props;
|
const { description, } = this.props;
|
||||||
|
const { prefix, } = this.props;
|
||||||
|
const { required, } = this.props;
|
||||||
|
const invalid = required && !value.length;
|
||||||
|
const warning = invalid? (<div className='required-warning'>This field is required</div>) : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="er-container">
|
<div className="er-container">
|
||||||
@@ -71,12 +74,16 @@ class ExternalReferences extends React.Component {
|
|||||||
<Tooltip id={`${field}-tooltip`} />
|
<Tooltip id={`${field}-tooltip`} />
|
||||||
<Tooltip id={`${field}-control-tooltip`} />
|
<Tooltip id={`${field}-control-tooltip`} />
|
||||||
</div>
|
</div>
|
||||||
<div className="er-body">
|
<div className={classNames({
|
||||||
|
"er-body": true,
|
||||||
|
"invalid": invalid
|
||||||
|
})}>
|
||||||
{value.map((p, i) => (
|
{value.map((p, i) => (
|
||||||
<ReferenceBlock
|
<ReferenceBlock
|
||||||
key={i}
|
key={i}
|
||||||
i={i}
|
i={i}
|
||||||
kv={p}
|
kv={p}
|
||||||
|
prefix={prefix}
|
||||||
onChangeERHandler={this.onChangeERHandler}
|
onChangeERHandler={this.onChangeERHandler}
|
||||||
onClickDeleteERHandler={this.onClickDeleteERHandler}
|
onClickDeleteERHandler={this.onClickDeleteERHandler}
|
||||||
onClickAddHandler={this.onClickAddHandler}
|
onClickAddHandler={this.onClickAddHandler}
|
||||||
@@ -84,6 +91,7 @@ class ExternalReferences extends React.Component {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -92,8 +100,9 @@ class ExternalReferences extends React.Component {
|
|||||||
function ReferenceBlock(props) {
|
function ReferenceBlock(props) {
|
||||||
const blocks = [];
|
const blocks = [];
|
||||||
const idx = props.i;
|
const idx = props.i;
|
||||||
const selectID = `select-${props.i}`;
|
const prefix = props.prefix;
|
||||||
const inputID = `input-${props.i}`;
|
const selectID = `select-${prefix}-${props.i}`;
|
||||||
|
const inputID = `input-${prefix}-${props.i}`;
|
||||||
|
|
||||||
const propValues = [
|
const propValues = [
|
||||||
'source_name',
|
'source_name',
|
||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Tooltip } from 'react-tooltip';
|
import { Tooltip } from 'react-tooltip';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import classNames from 'classnames';
|
||||||
import Text from '../inputs/Text';
|
import Text from '../inputs/Text';
|
||||||
|
|
||||||
import './genericobject.scss';
|
import './genericobject.scss';
|
||||||
@@ -10,21 +11,18 @@ class GenericObject extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.onChangeSelectHandler = this.onChangeSelectHandler.bind(this);
|
||||||
this.onChangeInputHandler = this.onChangeInputHandler.bind(this);
|
this.onChangeInputHandler = this.onChangeInputHandler.bind(this);
|
||||||
this.onClickAddObjectHandler = this.onClickAddObjectHandler.bind(this);
|
this.onClickAddObjectHandler = this.onClickAddObjectHandler.bind(this);
|
||||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||||
this.onClickCreateBlankHandler = this.onClickCreateBlankHandler.bind(this);
|
this.onClickCreateBlankHandler = this.onClickCreateBlankHandler.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
key: '',
|
key: this.props.vocab? this.props.vocab[0] : '',
|
||||||
value: '',
|
value: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeInputHandler(event) {
|
onChangeInputHandler(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -33,8 +31,16 @@ class GenericObject extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeSelectHandler(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const key = document.getElementById(`select-${this.props.field}`).value;
|
||||||
|
this.setState({
|
||||||
|
"key": key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onClickDeleteHandler(select, idx) {
|
onClickDeleteHandler(select, idx) {
|
||||||
this.props.onClickDeletePropertyHandler(select, idx);
|
this.props.onClickDeleteObjectHandler(select, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickCreateBlankHandler() {
|
onClickCreateBlankHandler() {
|
||||||
@@ -54,9 +60,13 @@ class GenericObject extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { field, } = this.props;
|
const { field, } = this.props;
|
||||||
const value = this.props.value ? this.props.value : [];
|
const { vocab, } = this.props;
|
||||||
|
const value = this.props.value ?? {};
|
||||||
const { description, } = this.props;
|
const { description, } = this.props;
|
||||||
|
const {required, } = this.props;
|
||||||
const rows = [];
|
const rows = [];
|
||||||
|
const invalid = required && !Object.keys(value).length;
|
||||||
|
const warning = invalid? (<div className='required-warning'>This field is required</div>) : "";
|
||||||
|
|
||||||
for (const key in value) {
|
for (const key in value) {
|
||||||
rows.push(
|
rows.push(
|
||||||
@@ -65,11 +75,30 @@ class GenericObject extends React.Component {
|
|||||||
v={value[key]}
|
v={value[key]}
|
||||||
k={key}
|
k={key}
|
||||||
field={field}
|
field={field}
|
||||||
onClickDeleteHandler={this.props.onClickDeleteObjectHandler}
|
onClickDeleteHandler={this.onClickDeleteHandler}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let selector;
|
||||||
|
if (vocab && vocab.length) {
|
||||||
|
selector = (
|
||||||
|
<select id={`select-${field}`} defaultValue={vocab[0]} onChange={this.onChangeSelectHandler}>
|
||||||
|
{vocab.map((key) => (
|
||||||
|
<option id={key} value={key}>
|
||||||
|
{key}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
selector = (
|
||||||
|
<Text name="key" value={this.state.key} onChange={this.onChangeInputHandler} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="go-container">
|
<div className="go-container">
|
||||||
<div className="go-header">
|
<div className="go-header">
|
||||||
@@ -83,11 +112,14 @@ class GenericObject extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
<Tooltip id={`${field}-tooltip`} />
|
<Tooltip id={`${field}-tooltip`} />
|
||||||
</div>
|
</div>
|
||||||
<div className="go-body">
|
<div className={classNames({
|
||||||
|
"go-body": true,
|
||||||
|
"invalid": invalid
|
||||||
|
})}>
|
||||||
|
|
||||||
<div className="go-block-input">
|
<div className="go-block-input">
|
||||||
<div className="input">
|
<div className="input">
|
||||||
<Text name="key" value={this.state.key} onChange={this.onChangeInputHandler} />
|
{selector}
|
||||||
</div>
|
</div>
|
||||||
<div className="input">
|
<div className="input">
|
||||||
<Text name="value" value={this.state.value} onChange={this.onChangeInputHandler} />
|
<Text name="value" value={this.state.value} onChange={this.onChangeInputHandler} />
|
||||||
@@ -99,6 +131,7 @@ class GenericObject extends React.Component {
|
|||||||
|
|
||||||
{rows}
|
{rows}
|
||||||
</div>
|
</div>
|
||||||
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Tooltip } from 'react-tooltip';
|
import { Tooltip } from 'react-tooltip';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import './killchain.scss';
|
import './killchain.scss';
|
||||||
|
|
||||||
@@ -12,10 +13,6 @@ class KillChain extends React.Component {
|
|||||||
this.populatePhase = this.populatePhase.bind(this);
|
this.populatePhase = this.populatePhase.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangePhaseHandler(event) {
|
onChangePhaseHandler(event) {
|
||||||
const kcDomName = `kc-name-${this.props.node.id}`;
|
const kcDomName = `kc-name-${this.props.node.id}`;
|
||||||
const phaseDomName = `phase-${this.props.node.id}`;
|
const phaseDomName = `phase-${this.props.node.id}`;
|
||||||
@@ -64,6 +61,9 @@ class KillChain extends React.Component {
|
|||||||
const { field, } = this.props;
|
const { field, } = this.props;
|
||||||
const value = this.props.value ? this.props.value : [];
|
const value = this.props.value ? this.props.value : [];
|
||||||
const { description, } = this.props;
|
const { description, } = this.props;
|
||||||
|
const {required, } = this.props;
|
||||||
|
const invalid = required && !value.length;
|
||||||
|
const warning = invalid? (<div className='required-warning'>This field is required</div>) : "";
|
||||||
|
|
||||||
const kcName = `kc-name-${this.props.node.id}`;
|
const kcName = `kc-name-${this.props.node.id}`;
|
||||||
const phaseName = `phase-${this.props.node.id}`;
|
const phaseName = `phase-${this.props.node.id}`;
|
||||||
@@ -81,7 +81,10 @@ class KillChain extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
<Tooltip id={`${field}-tooltip`} />
|
<Tooltip id={`${field}-tooltip`} />
|
||||||
</div>
|
</div>
|
||||||
<div className="kill-chain-body">
|
<div className={classNames({
|
||||||
|
"kill-chain-body": true,
|
||||||
|
"invalid": invalid
|
||||||
|
})}>
|
||||||
<div className="kill-chain-options">
|
<div className="kill-chain-options">
|
||||||
<select id={kcName} onChange={this.populatePhase}>
|
<select id={kcName} onChange={this.populatePhase}>
|
||||||
<option value={0}> -- Select Kill Chain -- </option>
|
<option value={0}> -- Select Kill Chain -- </option>
|
||||||
@@ -112,12 +115,13 @@ class KillChain extends React.Component {
|
|||||||
{' '}
|
{' '}
|
||||||
{p.phase_name}
|
{p.phase_name}
|
||||||
{' '}
|
{' '}
|
||||||
<span onClick={() => this.props.onClickRemoveHandler(field, p)} className="material-icons">highlight_off</span>
|
<span onClick={() => this.props.onClickRemoveHandler(field, i)} className="material-icons">highlight_off</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Tooltip } from 'react-tooltip';
|
import { Tooltip } from 'react-tooltip';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import classNames from 'classnames';
|
||||||
import Text from '../inputs/Text';
|
import Text from '../inputs/Text';
|
||||||
import './externalreferences.scss';
|
import './externalreferences.scss';
|
||||||
|
|
||||||
@@ -16,8 +17,6 @@ class ObjectArray extends React.Component {
|
|||||||
this.onClickDeletePropertyHandler = this.onClickDeletePropertyHandler.bind(this);
|
this.onClickDeletePropertyHandler = this.onClickDeletePropertyHandler.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {}
|
|
||||||
|
|
||||||
onChangeArrayObjectHandler(event, value) {
|
onChangeArrayObjectHandler(event, value) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -49,6 +48,9 @@ class ObjectArray extends React.Component {
|
|||||||
const { field, } = this.props;
|
const { field, } = this.props;
|
||||||
const value = this.props.value ? this.props.value : [];
|
const value = this.props.value ? this.props.value : [];
|
||||||
const { description, } = this.props;
|
const { description, } = this.props;
|
||||||
|
const {required, } = this.props;
|
||||||
|
const invalid = required && !value.length;
|
||||||
|
const warning = invalid? (<div className='required-warning'>This field is required</div>) : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="er-container">
|
<div className="er-container">
|
||||||
@@ -72,7 +74,10 @@ class ObjectArray extends React.Component {
|
|||||||
<Tooltip id={`${field}-tooltip`} />
|
<Tooltip id={`${field}-tooltip`} />
|
||||||
<Tooltip id={`add-${field}-tooltip`} />
|
<Tooltip id={`add-${field}-tooltip`} />
|
||||||
</div>
|
</div>
|
||||||
<div className="er-body">
|
<div className={classNames({
|
||||||
|
"er-body": true,
|
||||||
|
"invalid": invalid,
|
||||||
|
})}>
|
||||||
{value.map((p, i) => (
|
{value.map((p, i) => (
|
||||||
<ObjectBlock
|
<ObjectBlock
|
||||||
key={i}
|
key={i}
|
||||||
@@ -86,6 +91,7 @@ class ObjectArray extends React.Component {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
.ct-container {
|
.ct-container {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
.ct-header {
|
.ct-header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
.ct-body {
|
.ct-body {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
border: 1px solid $standard-border-color;
|
border: 1px solid defaults.$standard-border-color;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,12 +81,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.remove {
|
.remove {
|
||||||
color: $error-font;
|
color: defaults.$error-font;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add {
|
.add {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
.er-container {
|
.er-container {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
.er-header {
|
.er-header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@@ -18,11 +18,11 @@
|
|||||||
|
|
||||||
.er-body {
|
.er-body {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid $standard-border-color;
|
border: 1px solid defaults.$standard-border-color;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
|
|
||||||
.er-block {
|
.er-block {
|
||||||
border: 1px solid $standard-border-color;
|
border: 1px solid defaults.$standard-border-color;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
|
|
||||||
.er-block-row {
|
.er-block-row {
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.remove {
|
.remove {
|
||||||
color: $error-font;
|
color: defaults.$error-font;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove-er {
|
.remove-er {
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
.add {
|
.add {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
.go-container {
|
.go-container {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
.go-header {
|
.go-header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
.go-body {
|
.go-body {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
border: 1px solid $standard-border-color;
|
border: 1px solid defaults.$standard-border-color;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,12 +71,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.remove {
|
.remove {
|
||||||
color: $error-font;
|
color: defaults.$error-font;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add {
|
.add {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
.kill-chain-container {
|
.kill-chain-container {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
.kill-chain-header {
|
.kill-chain-header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
.kill-chain-body {
|
.kill-chain-body {
|
||||||
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid $standard-border-color;
|
border: 1px solid defaults.$standard-border-color;
|
||||||
|
|
||||||
.kill-chain-options {
|
.kill-chain-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
padding: 5px 5px 5px 10px;
|
padding: 5px 5px 5px 10px;
|
||||||
.material-icons {
|
.material-icons {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
color: $error-font;
|
color: defaults.$error-font;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
stix-modeler-app/src/components/ui/growl/growl.scss
Normal file
12
stix-modeler-app/src/components/ui/growl/growl.scss
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
@use "../../../defaults";
|
||||||
|
|
||||||
|
.growl {
|
||||||
|
position: fixed;
|
||||||
|
left: 10px;
|
||||||
|
top: 10px;
|
||||||
|
z-index: defaults.$growl-index;
|
||||||
|
background-color: defaults.$default-active-bg;
|
||||||
|
color: defaults.$light-font-0;
|
||||||
|
padding: 11px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
@@ -10,8 +10,6 @@ class ArraySelector extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {}
|
|
||||||
|
|
||||||
onClickHandler(field, value) {
|
onClickHandler(field, value) {
|
||||||
this.props.onClickHandler(field, value);
|
this.props.onClickHandler(field, value);
|
||||||
}
|
}
|
||||||
@@ -19,8 +17,11 @@ class ArraySelector extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const items = this.props.vocab ? this.props.vocab : [];
|
const items = this.props.vocab ? this.props.vocab : [];
|
||||||
const { field, } = this.props;
|
const { field, } = this.props;
|
||||||
const { value, } = this.props;
|
const { value, } = this.props ?? [];
|
||||||
const { description, } = this.props;
|
const { description, } = this.props;
|
||||||
|
const { required, } = this.props;
|
||||||
|
const invalid = required && !value.length;
|
||||||
|
const warning = invalid? (<div className='required-warning'>This field is required</div>) : "";
|
||||||
|
|
||||||
let cls = classNames({
|
let cls = classNames({
|
||||||
'array-container-item': true,
|
'array-container-item': true,
|
||||||
@@ -40,7 +41,10 @@ class ArraySelector extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
<Tooltip id={`${field}-tooltip`} />
|
<Tooltip id={`${field}-tooltip`} />
|
||||||
</div>
|
</div>
|
||||||
<div className="array-container-body">
|
<div className={classNames({
|
||||||
|
"array-container-body": true,
|
||||||
|
"invalid": invalid
|
||||||
|
})}>
|
||||||
{items.map((item, i) => {
|
{items.map((item, i) => {
|
||||||
if (value && value.indexOf(item) > -1) {
|
if (value && value.indexOf(item) > -1) {
|
||||||
cls = classNames({
|
cls = classNames({
|
||||||
@@ -63,6 +67,7 @@ class ArraySelector extends React.Component {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -9,10 +9,6 @@ class Boolean extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onClickHandler(field, value) {
|
onClickHandler(field, value) {
|
||||||
this.props.onClickHandler(field, value);
|
this.props.onClickHandler(field, value);
|
||||||
}
|
}
|
||||||
@@ -9,21 +9,18 @@ class CSVInput extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onClickHandler(field, value) {
|
onClickHandler(field, value) {
|
||||||
this.props.onClickHandler(field, value);
|
this.props.onClickHandler(field, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const value = this.props.value ? this.props.value.join() : '';
|
const value = this.props.value ?? "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
name={this.props.name}
|
name={this.props.name}
|
||||||
value={value}
|
value={value}
|
||||||
|
required={this.props.required}
|
||||||
onChange={this.props.onChangeHandler}
|
onChange={this.props.onChangeHandler}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import DatePicker from 'react-datepicker';
|
import DatePicker from 'react-datepicker';
|
||||||
|
|
||||||
|
import Text from './Text.jsx';
|
||||||
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
import './datetime.scss';
|
import './datetime.scss';
|
||||||
|
|
||||||
@@ -12,15 +14,24 @@ export default class DateTime extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onChange(datetime) {
|
onChange(datetime) {
|
||||||
this.props.onChange(this.props.name, datetime);
|
this.props.onDateChange(this.props.name, datetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let dts = this.props.selected;
|
let dts = this.props.selected;
|
||||||
|
let control = (<Text
|
||||||
|
name={this.props.name} value={dts} required={this.props.required}
|
||||||
|
onChange={this.props.onTextChange}
|
||||||
|
/>);
|
||||||
|
|
||||||
|
|
||||||
if (typeof dts === 'string') {
|
if (typeof dts === 'string') {
|
||||||
const dateObj = new Date(dts);
|
const dateObj = new Date(dts);
|
||||||
dts = dateObj;
|
if (isNaN(dateObj.getTime())) {
|
||||||
|
return control;
|
||||||
|
} else {
|
||||||
|
dts = dateObj;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import Text from './Text';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import './text.scss';
|
import './text.scss';
|
||||||
|
|
||||||
@@ -36,6 +38,10 @@ class LabeledText extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const inputType = this.props.type ? this.props.type : 'text';
|
const inputType = this.props.type ? this.props.type : 'text';
|
||||||
|
const { required, } = this.props ?? "";
|
||||||
|
const { value, } = this.props;
|
||||||
|
const invalid = required && !value.length;
|
||||||
|
const warning = invalid? (<div className='required-warning'>This field is required</div>) : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -44,14 +50,18 @@ class LabeledText extends React.Component {
|
|||||||
type={inputType}
|
type={inputType}
|
||||||
ref={(c) => { this.input = c; }}
|
ref={(c) => { this.input = c; }}
|
||||||
autoComplete={this.props.autocomplete || 'off'}
|
autoComplete={this.props.autocomplete || 'off'}
|
||||||
className="def"
|
className={classNames({
|
||||||
|
"def": true,
|
||||||
|
"invalid": invalid
|
||||||
|
})}
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
onChange={this.onChangeHandler}
|
onChange={this.onChangeHandler}
|
||||||
onKeyDown={(e) => this.onKeyDownHandler(e)}
|
onKeyDown={(e) => this.onKeyDownHandler(e)}
|
||||||
value={this.props.value}
|
value={value}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
id={this.props.id}
|
id={this.props.id}
|
||||||
/>
|
/>
|
||||||
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const OrientationRadioGroup = ({ defaultOrientation, onOrientationChange }) => {
|
||||||
|
const [selectedOption, setSelectedOption] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultOrientation) {
|
||||||
|
setSelectedOption(defaultOrientation);
|
||||||
|
}
|
||||||
|
}, [defaultOrientation]);
|
||||||
|
|
||||||
|
const handleOptionChange = (event) => {
|
||||||
|
const newOrientation = event.target.value;
|
||||||
|
setSelectedOption(newOrientation);
|
||||||
|
if (onOrientationChange) {
|
||||||
|
onOrientationChange(newOrientation);
|
||||||
|
} else {
|
||||||
|
console.error("onOrientationChange function is missing!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>Choose Orientation</p>
|
||||||
|
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="rowView"
|
||||||
|
name="orientation"
|
||||||
|
value="Row View"
|
||||||
|
checked={selectedOption === 'Row View'}
|
||||||
|
onChange={handleOptionChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="rowView">Row View</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="columnView"
|
||||||
|
name="orientation"
|
||||||
|
value="Column View"
|
||||||
|
checked={selectedOption === 'Column View'}
|
||||||
|
onChange={handleOptionChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="columnView">Column View</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrientationRadioGroup;
|
||||||
61
stix-modeler-app/src/components/ui/inputs/RadioGroup.jsx
Normal file
61
stix-modeler-app/src/components/ui/inputs/RadioGroup.jsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const RadioGroup = ({ defaultLayoutMethod, onLayoutChange }) => {
|
||||||
|
const [selectedOption, setSelectedOption] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultLayoutMethod) {
|
||||||
|
setSelectedOption(defaultLayoutMethod);
|
||||||
|
}
|
||||||
|
}, [defaultLayoutMethod]);
|
||||||
|
|
||||||
|
const handleOptionChange = (event) => {
|
||||||
|
const newLayout = event.target.value;
|
||||||
|
setSelectedOption(newLayout);
|
||||||
|
if (onLayoutChange) {
|
||||||
|
onLayoutChange(newLayout);
|
||||||
|
} else {
|
||||||
|
console.error("onLayoutChange function is missing!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>Choose Layout Mode</p>
|
||||||
|
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="hierarchy"
|
||||||
|
name="layout"
|
||||||
|
value="Hierarchy"
|
||||||
|
checked={selectedOption === 'Hierarchy'}
|
||||||
|
onChange={handleOptionChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="hierarchy">Hierarchy</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="grid"
|
||||||
|
name="layout"
|
||||||
|
value="Grid"
|
||||||
|
checked={selectedOption === 'Grid'}
|
||||||
|
onChange={handleOptionChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="grid">Grid</label>
|
||||||
|
</div>
|
||||||
|
{/* <div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="randomized"
|
||||||
|
name="layout"
|
||||||
|
value="Randomized"
|
||||||
|
checked={selectedOption === 'Randomized'}
|
||||||
|
onChange={handleOptionChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="randomized">Randomized</label>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RadioGroup;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import './text.scss';
|
import './text.scss';
|
||||||
|
|
||||||
@@ -35,8 +36,11 @@ class Text extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const inputType = this.props.type ? this.props.type : 'text';
|
const inputType = this.props.type ?? 'text';
|
||||||
|
const { required, } = this.props;
|
||||||
|
const { value, } = this.props ?? "";
|
||||||
|
const invalid = required && !`${value}`.length; // prevents 0 from being interpreted as missing field
|
||||||
|
const warning = invalid? (<div className='required-warning'>This field is required</div>) : "";
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
@@ -44,14 +48,18 @@ class Text extends React.Component {
|
|||||||
type={inputType}
|
type={inputType}
|
||||||
ref={(c) => { this.input = c; }}
|
ref={(c) => { this.input = c; }}
|
||||||
autoComplete={this.props.autocomplete || 'off'}
|
autoComplete={this.props.autocomplete || 'off'}
|
||||||
className="def"
|
className={classNames({
|
||||||
|
"def": true,
|
||||||
|
"invalid": invalid
|
||||||
|
})}
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
onChange={this.onChangeHandler}
|
onChange={this.onChangeHandler}
|
||||||
onKeyDown={(e) => this.onKeyDownHandler(e)}
|
onKeyDown={(e) => this.onKeyDownHandler(e)}
|
||||||
value={this.props.value}
|
value={value}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
id={this.props.id}
|
id={this.props.id}
|
||||||
/>
|
/>
|
||||||
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import './text.scss';
|
import './text.scss';
|
||||||
|
|
||||||
@@ -38,7 +39,10 @@ class TextArea extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const rows = this.props.rows ? this.props.rows : 1;
|
const {required, } = this.props;
|
||||||
|
const { value, } = this.props;
|
||||||
|
const invalid = required && !value.length;
|
||||||
|
const warning = invalid? (<div className='required-warning'>This field is required</div>) : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -48,14 +52,18 @@ class TextArea extends React.Component {
|
|||||||
this.input = c;
|
this.input = c;
|
||||||
}}
|
}}
|
||||||
autoComplete={this.props.autocomplete || 'off'}
|
autoComplete={this.props.autocomplete || 'off'}
|
||||||
className="def"
|
className={classNames({
|
||||||
|
"def": true,
|
||||||
|
"invalid": invalid
|
||||||
|
})}
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
onChange={this.onChangeHandler}
|
onChange={this.onChangeHandler}
|
||||||
onKeyDown={(e) => this.onKeyDownHandler(e)}
|
onKeyDown={(e) => this.onKeyDownHandler(e)}
|
||||||
value={this.props.value}
|
value={value}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
id={this.props.id}
|
id={this.props.id}
|
||||||
/>
|
/>
|
||||||
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
.array-container {
|
.array-container {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
.array-container-header {
|
.array-container-header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.remove {
|
.remove {
|
||||||
color: $error-font;
|
color: defaults.$error-font;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add {
|
.add {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.array-container-input {
|
.array-container-input {
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
.array-container-body {
|
.array-container-body {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid $standard-border-color;
|
border: 1px solid defaults.$standard-border-color;
|
||||||
|
|
||||||
.array-container-item {
|
.array-container-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -60,11 +60,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.array-container-selected {
|
.array-container-selected {
|
||||||
background-color: $default-active-bg;
|
background-color: defaults.$default-active-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.array-container-item:hover {
|
.array-container-item:hover {
|
||||||
background-color: $light-font-0;
|
background-color: defaults.$light-font-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.array-block-input {
|
.array-block-input {
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: $default-active-bg;
|
color: defaults.$default-active-bg;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
padding-top: 40%;
|
padding-top: 40%;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
.boolean {
|
.boolean {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
border: 1px solid $standard-border-color;
|
border: 1px solid defaults.$standard-border-color;
|
||||||
width: 99%;
|
width: 99%;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
background-color: $default-active-bg;
|
background-color: defaults.$default-active-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
.react-datepicker-wrapper {
|
.react-datepicker-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -6,11 +6,11 @@
|
|||||||
|
|
||||||
.react-datepicker__input-container input {
|
.react-datepicker__input-container input {
|
||||||
height: 39px;
|
height: 39px;
|
||||||
border: 1px solid $standard-border-color;
|
border: 1px solid defaults.$standard-border-color;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
width: 97%;
|
width: 97%;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
font-family: $default-font-family;
|
font-family: defaults.$default-font-family;
|
||||||
color: #000;
|
color: #000;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
input.def {
|
input.def {
|
||||||
height: 39px;
|
height: 39px;
|
||||||
border: 1px solid $standard-border-color;
|
border: 1px solid defaults.$standard-border-color;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
width: 97%;
|
width: 97%;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
color: $dark-font-0;
|
color: defaults.$dark-font-0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: $default-font-family;
|
font-family: defaults.$default-font-family;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-file-selector {
|
.custom-file-selector {
|
||||||
@@ -16,5 +16,5 @@ input.def {
|
|||||||
padding: 5px 4px 5px 11px;
|
padding: 5px 4px 5px 11px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background-color: $default-active-bg;
|
background-color: defaults.$default-active-bg;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
.rc-slider-track {
|
.rc-slider-track {
|
||||||
background-color: transparet;
|
background-color: transparet;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
input:focus,
|
input:focus,
|
||||||
textarea:focus,
|
textarea:focus,
|
||||||
@@ -10,13 +10,17 @@ input.def,
|
|||||||
textarea,
|
textarea,
|
||||||
select {
|
select {
|
||||||
height: 39px;
|
height: 39px;
|
||||||
border: 1px solid $standard-border-color;
|
border: 1px solid defaults.$standard-border-color;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
width: 97%;
|
width: 97%;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
color: $dark-font-0;
|
color: defaults.$dark-font-0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: $default-font-family;
|
font-family: defaults.$default-font-family;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.def {
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../../defaults';
|
@use '../../../defaults';
|
||||||
|
|
||||||
.mask {
|
.mask {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
right: 0px;
|
right: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
background-color: rgba(0, 0, 0,.2);
|
background-color: rgba(0, 0, 0,.2);
|
||||||
z-index: $panel-mask-index;
|
z-index: defaults.$panel-mask-index;
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: -20px 25px 50px 0px #000;
|
box-shadow: -20px 25px 50px 0px #000;
|
||||||
z-index: $panel-index;
|
z-index: defaults.$panel-index;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ $default-font-family: 'Alegreya Sans SC';
|
|||||||
//colors
|
//colors
|
||||||
$default-active-bg: #46a0f5;
|
$default-active-bg: #46a0f5;
|
||||||
$error-font: #d31d18;
|
$error-font: #d31d18;
|
||||||
|
$warning-font: #dda20f;
|
||||||
$light-font-0: #e1e3e6;
|
$light-font-0: #e1e3e6;
|
||||||
$gray-font-0: #c8c5c5;
|
$gray-font-0: #c8c5c5;
|
||||||
$dark-font-0: #000;
|
$dark-font-0: #000;
|
||||||
@@ -20,10 +20,7 @@ class Artifact extends Base {
|
|||||||
this.properties.payload_bin.type = 'string';
|
this.properties.payload_bin.type = 'string';
|
||||||
this.properties.url.type = 'string';
|
this.properties.url.type = 'string';
|
||||||
this.properties.encryption_algorithm.type = 'string';
|
this.properties.encryption_algorithm.type = 'string';
|
||||||
|
this.properties.encryption_algorithm.vocab = this.definitions["encryption-algorithm-enum"].enum;
|
||||||
this.properties.hashes.value = {};
|
|
||||||
|
|
||||||
this.properties.hashes.control = 'genericobject';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ class AttackPattern extends Base {
|
|||||||
prefix: 'attack-pattern--',
|
prefix: 'attack-pattern--',
|
||||||
active: true,
|
active: true,
|
||||||
relationships: [
|
relationships: [
|
||||||
|
{ type: 'delivers', target: 'malware', },
|
||||||
{ type: 'targets', target: 'identity', },
|
{ type: 'targets', target: 'identity', },
|
||||||
{ type: 'targets', target: 'location', },
|
{ type: 'targets', target: 'location', },
|
||||||
{ type: 'targets', target: 'vulnerability', },
|
{ type: 'targets', target: 'vulnerability', },
|
||||||
402
stix-modeler-app/src/definition-adapters/Base.js
Normal file
402
stix-modeler-app/src/definition-adapters/Base.js
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
import deepmerge from 'deepmerge';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const SPEC_VERSION = 2.1;
|
||||||
|
|
||||||
|
const COMMON_RELS = [
|
||||||
|
{
|
||||||
|
type: 'includes', target: 'grouping', x_embed: 'object_refs', x_reverse: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'applies-to', target: 'note', x_embed: 'object_refs', x_reverse: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'applies-to', target: 'opinion', x_embed: 'object_refs', x_reverse: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'references', target: 'report', x_embed: 'object_refs', x_reverse: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'applies-to', target: 'marking-definition', x_embed: 'object_marking_refs', x_reverse: true,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export class Base {
|
||||||
|
constructor(common, def) {
|
||||||
|
const commonProps = common.properties;
|
||||||
|
let defProps = {};
|
||||||
|
|
||||||
|
// Set required common properties
|
||||||
|
common.required.map((item) => {
|
||||||
|
if (commonProps[item]) {
|
||||||
|
commonProps[item].required = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get type-specific properties
|
||||||
|
if (def.allOf) {
|
||||||
|
def.allOf.map((item) => {
|
||||||
|
if ('properties' in item) {
|
||||||
|
defProps = item.properties;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
defProps = def.properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set required type-specific properties
|
||||||
|
if (def.required) {
|
||||||
|
def.required.map((item) => {
|
||||||
|
if (defProps[item]) {
|
||||||
|
defProps[item].required = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set properties for singleton from schema
|
||||||
|
for (const item in def) {
|
||||||
|
this[item] = def[item];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add common relationships
|
||||||
|
for (const rel of COMMON_RELS) {
|
||||||
|
def.relationships.push(rel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only SROs and SCOs may have this property
|
||||||
|
if ("created_by_ref" in commonProps) {
|
||||||
|
def.relationships.push({
|
||||||
|
type: 'created-by', target: 'identity', x_exclusive: true, x_embed: 'created_by_ref',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
|
const mergedProps = deepmerge(commonProps, defProps);
|
||||||
|
|
||||||
|
this.handleFields(mergedProps);
|
||||||
|
|
||||||
|
this.properties = mergedProps;
|
||||||
|
this.extensions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default values and control types for the specified properties
|
||||||
|
* @param {object} mergedProps properties (common and sdo-specific)
|
||||||
|
*/
|
||||||
|
handleFields(mergedProps) {
|
||||||
|
// Start special handling of common object
|
||||||
|
// properties.
|
||||||
|
for (const prop in mergedProps) {
|
||||||
|
// Get (possibly nested) ref
|
||||||
|
let ref = mergedProps[prop].$ref;
|
||||||
|
for (const a in mergedProps[prop].allOf) {
|
||||||
|
if (mergedProps[prop].allOf[a].$ref) {
|
||||||
|
ref = mergedProps[prop].allOf[a].$ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ref = ref || '';
|
||||||
|
|
||||||
|
if (ref.length) {
|
||||||
|
mergedProps[prop].type = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default blank values based on the prop
|
||||||
|
// type.
|
||||||
|
if (mergedProps[prop].type) {
|
||||||
|
mergedProps[prop].value = this.defaultValue(mergedProps[prop]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergedProps.type) {
|
||||||
|
mergedProps.type.control = 'literal';
|
||||||
|
if (mergedProps.type.enum) {
|
||||||
|
mergedProps.type.value = mergedProps.type.enum[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergedProps.aliases) {
|
||||||
|
mergedProps.aliases.control = 'csv';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergedProps.kill_chain_phases) {
|
||||||
|
mergedProps.kill_chain_phases.control = 'killchain';
|
||||||
|
mergedProps.kill_chain_phases.vocab = [
|
||||||
|
{
|
||||||
|
label: 'Lockheed Kill Chain',
|
||||||
|
value: 'lockheed-martin-cyber-kill-chain',
|
||||||
|
phases: [
|
||||||
|
{
|
||||||
|
label: 'Reconnaissance',
|
||||||
|
phase_name: 'reconnaissance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Weaponize',
|
||||||
|
phase_name: 'weaponization',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delivery',
|
||||||
|
phase_name: 'delivery',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Exploitation',
|
||||||
|
phase_name: 'exploitation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Installation',
|
||||||
|
phase_name: 'installation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Command & Control (C2)',
|
||||||
|
phase_name: 'command-and-control',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Actions On Objectives',
|
||||||
|
phase_name: 'actions-on-objectives',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MITRE ATT&CK',
|
||||||
|
value: 'mitre-attack',
|
||||||
|
phases: [
|
||||||
|
{
|
||||||
|
label: 'Reconnaissance',
|
||||||
|
phase_name: 'reconnaissance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Resource Development',
|
||||||
|
phase_name: 'resource-development',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Initial Access',
|
||||||
|
phase_name: 'initial-access',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Execution',
|
||||||
|
phase_name: 'execution',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Persistence',
|
||||||
|
phase_name: 'persistence',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Privilege Escalation',
|
||||||
|
phase_name: 'privilege-escalation'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Defense Evasion',
|
||||||
|
phase_name: 'defense-evasion',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Credential Access',
|
||||||
|
phase_name: 'credential-access',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Discovery',
|
||||||
|
phase_name: 'discovery',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Lateral Movement',
|
||||||
|
phase_name: 'lateral-movement',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Collection',
|
||||||
|
phase_name: 'collection'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Command & Control (C2)',
|
||||||
|
phase_name: 'command-and-control',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Exfiltration',
|
||||||
|
phase_name: 'exfiltration',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Impact',
|
||||||
|
phase_name: 'impact'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergedProps.external_references) {
|
||||||
|
mergedProps.external_references.control = 'externalrefs';
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedProps.id.control = 'hidden';
|
||||||
|
|
||||||
|
if (mergedProps.confidence) {
|
||||||
|
mergedProps.confidence.control = 'slider';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergedProps.description) {
|
||||||
|
mergedProps.description.control = 'textarea';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergedProps.hashes) {
|
||||||
|
mergedProps.hashes.control = 'killchain';
|
||||||
|
mergedProps.hashes.vocab = [
|
||||||
|
'MD5', 'SHA-1', 'SHA-256', 'SHA-512',
|
||||||
|
'SHA3-256', 'SHA3-512', 'SSDEEP'
|
||||||
|
]
|
||||||
|
mergedProps.hashes.control = 'genericobject';
|
||||||
|
mergedProps.hashes.type = 'object';
|
||||||
|
mergedProps.hashes.value = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are defaults that are to be set by the TI orchestrator
|
||||||
|
*/
|
||||||
|
|
||||||
|
mergedProps.spec_version.value = SPEC_VERSION;
|
||||||
|
mergedProps.spec_version.control = 'literal';
|
||||||
|
|
||||||
|
if (mergedProps.extensions) {
|
||||||
|
mergedProps.extensions.control = 'genericobject';
|
||||||
|
mergedProps.extensions.type = 'object';
|
||||||
|
mergedProps.extensions.value = {};
|
||||||
|
mergedProps.extensions.control = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (mergedProps.lang) {
|
||||||
|
mergedProps.lang.value = 'en';
|
||||||
|
mergedProps.lang.control = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedProps.object_marking_refs.control = 'hidden';
|
||||||
|
mergedProps.granular_markings.control = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default empty value for the specified
|
||||||
|
* property's type
|
||||||
|
* @param {object} def property
|
||||||
|
* @returns default value for property
|
||||||
|
*/
|
||||||
|
defaultValue(def) {
|
||||||
|
let type = def.type;
|
||||||
|
let value;
|
||||||
|
|
||||||
|
type = type.split('/').slice(-1)[0];
|
||||||
|
type = type.split('.json')[0];
|
||||||
|
def.type = type;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'boolean':
|
||||||
|
value = false;
|
||||||
|
break;
|
||||||
|
case 'dictionary':
|
||||||
|
case 'external-reference':
|
||||||
|
case 'hashes':
|
||||||
|
case 'hashes-type':
|
||||||
|
case 'object':
|
||||||
|
case 'observable-container':
|
||||||
|
value = {};
|
||||||
|
def.type = 'object';
|
||||||
|
break;
|
||||||
|
case 'float':
|
||||||
|
case 'integer':
|
||||||
|
case 'number':
|
||||||
|
value = 0;
|
||||||
|
def.type = 'number';
|
||||||
|
break;
|
||||||
|
case 'binary':
|
||||||
|
case 'hex':
|
||||||
|
case 'identifier':
|
||||||
|
case 'open-vocab':
|
||||||
|
case 'string':
|
||||||
|
case 'url-regex':
|
||||||
|
value = "";
|
||||||
|
def.type = 'string';
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
case 'list':
|
||||||
|
def.type = 'array';
|
||||||
|
case 'enum':
|
||||||
|
case 'kill-chain-phase':
|
||||||
|
value = [];
|
||||||
|
break;
|
||||||
|
case 'timestamp':
|
||||||
|
value = moment().utc(true).format('YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flatten nested extension properties
|
||||||
|
* (Enables resiliency for non-standard schema formats)
|
||||||
|
* @param {*} schema
|
||||||
|
* @returns dictionary of properties
|
||||||
|
*/
|
||||||
|
flattenExtensionProperties(schema) {
|
||||||
|
let extProps = {}
|
||||||
|
let properties;
|
||||||
|
if (schema.allOf) {
|
||||||
|
schema.allOf.map((item) => {
|
||||||
|
if ('properties' in item) {
|
||||||
|
properties = item.properties
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
properties = schema.properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of nested property extensions
|
||||||
|
let found = true;
|
||||||
|
while (found) {
|
||||||
|
found = false;
|
||||||
|
for (const [key, value] of Object.entries(properties)) {
|
||||||
|
if (key == 'properties' || key == 'extensions' ||
|
||||||
|
key.includes("extension-definition")) {
|
||||||
|
properties = value;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [prop, def] of Object.entries(properties)) {
|
||||||
|
if (prop != 'extension_type') {
|
||||||
|
const value = this.defaultValue(def);
|
||||||
|
extProps[prop] = def;
|
||||||
|
def.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return extProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge properties from a property-extension schema into the
|
||||||
|
* SDO singleton
|
||||||
|
* @param {*} def schema of extended properties
|
||||||
|
* @param {*} extDef extension definition node
|
||||||
|
*/
|
||||||
|
mergeExtension(def, extDef) {
|
||||||
|
const { properties, } = this;
|
||||||
|
|
||||||
|
if (!('extensions' in properties)) {
|
||||||
|
this.properties.extensions = {};
|
||||||
|
this.properties.extensions.value = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this object uses extensions (such as network-traffic),
|
||||||
|
// do not allow further editing via gui
|
||||||
|
this.properties.extensions.type = 'object';
|
||||||
|
this.properties.extensions.control = 'hidden';
|
||||||
|
|
||||||
|
const extProps = this.flattenExtensionProperties(def);
|
||||||
|
// Only toplevel-property-extensions can have extension_properties field,
|
||||||
|
// but this useful, so hold onto it
|
||||||
|
extDef.extension_properties = Object.keys(extProps);
|
||||||
|
const mergedProps = deepmerge(properties, extProps);
|
||||||
|
this.properties = mergedProps;
|
||||||
|
this.extensions.push(extDef.uiid);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,9 +17,6 @@ class Certificate extends Base {
|
|||||||
|
|
||||||
super(common, def);
|
super(common, def);
|
||||||
|
|
||||||
this.properties.hashes.value = {};
|
|
||||||
this.properties.hashes.control = 'genericobject';
|
|
||||||
|
|
||||||
this.properties.x509_v3_extensions.type = 'string';
|
this.properties.x509_v3_extensions.type = 'string';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,12 +24,7 @@ class Custom extends Base {
|
|||||||
const def = deepmerge(definition_extension, rawDefinition);
|
const def = deepmerge(definition_extension, rawDefinition);
|
||||||
super(common, def);
|
super(common, def);
|
||||||
|
|
||||||
const extProps = { extension_type: extensionDefinition.extension_types[0], };
|
this.extensions.push(extensionDefinition.uiid);
|
||||||
this.properties.extensions = {};
|
|
||||||
this.properties.extensions.type = 'object';
|
|
||||||
this.properties.extensions.value = {};
|
|
||||||
this.properties.extensions.value[extensionDefinition.id] = extProps;
|
|
||||||
this.properties.extensions.control = 'hidden';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user