mirror of
https://github.com/JHUAPL/STIXMODELER_UI.git
synced 2026-01-06 21:13:56 -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
|
||||
|
||||
## Overview
|
||||
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.
|
||||
A React-based user interface tool for visualizing, creating, and modifying STIX 2.1 bundles
|
||||
|
||||
## New Features
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
@@ -43,6 +71,13 @@ Earlier versions of node may not be supported
|
||||
- Added functionality for creating new Group SDOs via clicking and selecting SDOs
|
||||
- Updated dependencies and removed unused dependencies
|
||||
- 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
|
||||
|
||||
@@ -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 import and modification of nodes with "hashes" 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
# Quality Assurance
|
||||
## Style Guide
|
||||
The source code follows a modification of the [Airbnb Javascript Style Guide](https://airbnb.io/javascript/react/)
|
||||
## Automated Tools
|
||||
The project uses eslint for quality assurance and styling.
|
||||
- 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 {
|
||||
margin: 0px;
|
||||
@@ -8,5 +8,5 @@ body, html, #app {
|
||||
bottom: 0;
|
||||
left: 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 TopMenu from './menus/TopMenu';
|
||||
import Details from './Details';
|
||||
import SDOEditor from './schema/SDOEditor';
|
||||
import ExtensionEditor from './schema/ExtensionEditor';
|
||||
import FileImporter from './FileImporter';
|
||||
import JsonViewer from './bundle/JsonViewer';
|
||||
import JsonPaste from './bundle/JsonPaste';
|
||||
@@ -11,7 +11,8 @@ import SchemaPaste from './schema/SchemaPaste';
|
||||
import RelationshipPicker from './relationship/RelationshipPicker';
|
||||
import RelationshipDetails from './relationship/RelationshipDetails';
|
||||
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 SubmissionError from './SubmissionError';
|
||||
import Flow from './Flow/Flow';
|
||||
@@ -20,8 +21,7 @@ import './canvas.scss';
|
||||
|
||||
class Canvas extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
super(props);
|
||||
this.store = this.props.store.appStore;
|
||||
|
||||
this.generateNodeID = this.generateNodeID.bind(this);
|
||||
@@ -48,10 +48,16 @@ class Canvas extends React.Component {
|
||||
this.onClickShowRelDetailsHandler = this.onClickShowRelDetailsHandler.bind(
|
||||
this
|
||||
);
|
||||
this.onClickShowSDOPickerHandler = this.onClickShowSDOPickerHandler.bind(
|
||||
this.onClickShowExtensionPickerHandler = this.onClickShowExtensionPickerHandler.bind(
|
||||
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.onClickShowImporterHandler = this.onClickShowImporterHandler.bind(
|
||||
@@ -65,13 +71,13 @@ class Canvas extends React.Component {
|
||||
this.onClickCreateRelHandler = this.onClickCreateRelHandler.bind(this);
|
||||
this.onClickEditRelHandler = this.onClickEditRelHandler.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.onClickGroupNodeHandler = this.onClickGroupNodeHandler.bind(this);
|
||||
this.onClickGroupModeHandler = this.onClickGroupModeHandler.bind(this);
|
||||
this.onClickSubmitGroupingHandler = this.onClickSubmitGroupingHandler.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.onChangeBundleHandler = this.onChangeBundleHandler.bind(this);
|
||||
this.onChangeDateHandler = this.onChangeDateHandler.bind(this);
|
||||
@@ -129,24 +135,45 @@ class Canvas extends React.Component {
|
||||
);
|
||||
this.onClickSchemaPasteHandler = this.onClickSchemaPasteHandler.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.onClickSubmitHandler = this.onClickSubmitHandler.bind(this);
|
||||
this.onClickExportHandler = this.onClickExportHandler.bind(this);
|
||||
this.onClickShowSubmissionErrorHandler = this.onClickShowSubmissionErrorHandler.bind(this);
|
||||
this.onClickHideSubmissionErrorHandler = this.onClickHideSubmissionErrorHandler.bind(
|
||||
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) {
|
||||
const node = this.store.getNodeById(nodeId);
|
||||
this.store.setShowDetails(true);
|
||||
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) {
|
||||
this.store.setGroupMode(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) {
|
||||
this.store.modifyGroup(id);
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Grouping SDO, including all nodes
|
||||
* from the grouping selection.
|
||||
*/
|
||||
onClickSubmitGroupingHandler() {
|
||||
const id = this.generateNodeID('grouping--');
|
||||
this.store.createGroup(id);
|
||||
@@ -167,73 +203,140 @@ class Canvas extends React.Component {
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the relationship with the specified ID
|
||||
* to edit via the Relationship Editor panel.
|
||||
* @param {*} relId relationship id
|
||||
*/
|
||||
onClickRelHandler(relId) {
|
||||
const rel = this.store.getRelById(relId);
|
||||
this.store.setShowRelEditor(true);
|
||||
this.store.setSelectedRel(rel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the details panel.
|
||||
*/
|
||||
onClickHideDetailsHandler() {
|
||||
this.store.setShowDetails(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the Extension Editor panel.
|
||||
*/
|
||||
onClickHideEditorHandler() {
|
||||
this.store.setShowEditor(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the Json Paste panel.
|
||||
*/
|
||||
onClickHideJsonPasteHandler() {
|
||||
this.store.setShowJSONPaste(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Json Paste panel.
|
||||
*/
|
||||
onClickShowJsonPasteHandler() {
|
||||
this.store.setShowJSONPaste(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the Schema Paste panel.
|
||||
*/
|
||||
onClickHideSchemaPasteHandler() {
|
||||
this.store.setShowSchemaPaste(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Schema Paste panel.
|
||||
*/
|
||||
onClickShowSchemaPasteHandler() {
|
||||
this.store.setShowSchemaPaste(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the growl message.
|
||||
* @param {string} message growl message
|
||||
*/
|
||||
onClickShowGrowlHandler(message) {
|
||||
this.store.setGrowlMessage(message);
|
||||
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() {
|
||||
this.store.deleteSelectedNode();
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onClickDeleteSDOHandler() {
|
||||
this.store.deleteSelectedSDO();
|
||||
/**
|
||||
* Delete the selected extension.
|
||||
*/
|
||||
onClickDeleteExtHandler() {
|
||||
this.store.deleteSelectedExt();
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the selected relationship.
|
||||
*/
|
||||
onClickDeleteRelHandler() {
|
||||
this.store.deleteSelectedRelationship();
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified property value for
|
||||
* the selected node.
|
||||
* @param {*} event
|
||||
*/
|
||||
onChangeNodeHandler(event) {
|
||||
this.store.editNodeValues(event);
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
onChangeSDOHandler(event) {
|
||||
this.store.editSDOValues(event);
|
||||
this.forceUpdate();
|
||||
/**
|
||||
* Update the specified property value for
|
||||
* the selected extension.
|
||||
* @param {*} event
|
||||
*/
|
||||
onChangeExtHandler(event) {
|
||||
this.store.editExtensionValues(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a schema from a file.
|
||||
* @param {object} file schema json
|
||||
*/
|
||||
onChangeSchemaHandler(file) {
|
||||
this.store.loadSchemaFromFile(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a bundle from a file.
|
||||
* @param {*} file bundle json
|
||||
*/
|
||||
onChangeBundleHandler(file) {
|
||||
this.store.loadBundleFromFile(file);
|
||||
this.store.nodes.map((n) => {
|
||||
@@ -242,58 +345,129 @@ class Canvas extends React.Component {
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the Creator ID for SDO, SCO, and SROs created
|
||||
* via the STIX UI.
|
||||
* @param {string} id
|
||||
*/
|
||||
onChangeCreatorIDHandler(id) {
|
||||
this.store.updateCreatorID(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified date property for the
|
||||
* selected node.
|
||||
* @param {string} property
|
||||
* @param {*} datetime
|
||||
*/
|
||||
onChangeDateHandler(property, datetime) {
|
||||
const value = this.store.generateTimestamp(datetime);
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified array property for
|
||||
* the selected node.
|
||||
* @param {string} property
|
||||
* @param {*} value
|
||||
*/
|
||||
onClickArrayHandler(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) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified boolean property
|
||||
* for the selected node.
|
||||
* @param {string} property
|
||||
* @param {boolean} value
|
||||
*/
|
||||
onClickBooleanHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified kill chain property
|
||||
* for the selected node.
|
||||
* @param {string} property
|
||||
* @param {*} value
|
||||
*/
|
||||
onChangePhaseHandler(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) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified list property for the
|
||||
* selected node.
|
||||
* @param {string} property
|
||||
* @param {*} value
|
||||
*/
|
||||
onChangeListHandler(property, value) {
|
||||
this.mutateOnEvent(property, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the object property for the selected node.
|
||||
* @param {string} property
|
||||
* @param {*} event
|
||||
*/
|
||||
onChangeGenericObjectHandler(property, event) {
|
||||
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) {
|
||||
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) {
|
||||
const src = { id: srcId, };
|
||||
const target = { id: targetId, };
|
||||
const relationship = this.store.makeRelationship(src, target, rel);
|
||||
if (relationship) {
|
||||
this.onClickSelectRelHandler(relationship);
|
||||
this.store.addCustomRelationship(rel, srcId, targetId);
|
||||
// this.store.addCustomRelationship(rel, srcId, targetId);
|
||||
this.store.addCustomRelationship(rel, srcId);
|
||||
this.setUpdateFlow(true);
|
||||
} else {
|
||||
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) {
|
||||
this.store.editRelationship(rel);
|
||||
this.store.setShowRelEditor(false);
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the specified relationship.
|
||||
* @param {object} relationship
|
||||
*/
|
||||
onClickSelectRelHandler(relationship) {
|
||||
this.store.setShowRelDetails(false);
|
||||
this.store.manuallySelectRelationship(relationship);
|
||||
@@ -314,107 +496,211 @@ class Canvas extends React.Component {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object to the specified object array
|
||||
* property for the selected node.
|
||||
* @param {string} field
|
||||
* @param {list} requiredFields
|
||||
*/
|
||||
onClickAddObjectHandler(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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
this.store.changeArrayObjectValue(input, field, idx, property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the JSON Viewer panel.
|
||||
*/
|
||||
onClickShowJsonHandler() {
|
||||
this.store.mutateBundle();
|
||||
this.store.stringifyBundle();
|
||||
this.store.setShowJSON(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the JSON Viewer panel.
|
||||
*/
|
||||
onClickHideJsonHandler() {
|
||||
this.store.setShowJSON(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the store pasteBundle value to
|
||||
* the specified value.
|
||||
* @param {*} event
|
||||
*/
|
||||
onChangeJSONPasteHandler(event) {
|
||||
this.store.setPasteBundle(event.currentTarget.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a bundle from the Json Paste panel.
|
||||
*/
|
||||
onClickJSONPasteHandler() {
|
||||
this.store.loadBundleFromPaste();
|
||||
|
||||
this.store.loadBundleFromPaste();
|
||||
this.store.nodes.map((n) => {
|
||||
this.transition(n.id, true);
|
||||
});
|
||||
this.setUpdateFlow(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the store pasteSchema value to
|
||||
* the specified value.
|
||||
* @param {*} event
|
||||
*/
|
||||
onChangeSchemaPasteHandler(event) {
|
||||
this.store.setPasteSchema(event.currentTarget.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a schema from the Schema Paste panel.
|
||||
*/
|
||||
onClickSchemaPasteHandler() {
|
||||
this.store.loadSchemaFromPaste();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Relationship Details panel.
|
||||
*/
|
||||
onClickShowRelDetailsHandler() {
|
||||
this.store.setShowRelDetails(true);
|
||||
this.store.setShowRelPicker(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the Relationship Details panel.
|
||||
*/
|
||||
onClickHideRelDetailsHandler() {
|
||||
this.store.setShowRelDetails(false);
|
||||
this.store.setShowRelPicker(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the Relationship Editor panel.
|
||||
*/
|
||||
onClickHideRelEditorHandler() {
|
||||
this.store.setShowRelEditor(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the Relationship Picker panel.
|
||||
*/
|
||||
onClickHideRelPickerHandler() {
|
||||
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() {
|
||||
this.store.setShowImporter(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the File Importer panel.
|
||||
*/
|
||||
onClickShowImporterHandler() {
|
||||
this.store.setShowImporter(true);
|
||||
}
|
||||
|
||||
// Prevent event propagation.
|
||||
onDragOverHandler(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the dragged source node to the specified node.
|
||||
* @param {*} event
|
||||
*/
|
||||
onDragStartHandler(event) {
|
||||
const node = JSON.parse(event.dataTransfer.getData('node'));
|
||||
this.store.setDragging(node);
|
||||
@@ -426,7 +712,11 @@ class Canvas extends React.Component {
|
||||
}, 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) {
|
||||
event.preventDefault();
|
||||
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) {
|
||||
const sourceNode = this.store.getNodeById(sourceId);
|
||||
const targetNode = this.store.getNodeById(targetId);
|
||||
@@ -487,44 +781,87 @@ class Canvas extends React.Component {
|
||||
this.store.relationships.unshift(genericRel);
|
||||
this.store.setShowRelPicker(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update store position from React Flow
|
||||
onDragStopNodeHandler(node) {
|
||||
const n = this.store.getNodeById(node.id);
|
||||
if (n) {
|
||||
n.position = node.position;
|
||||
/**
|
||||
* Update the store node position to its respective
|
||||
* Flow node position.
|
||||
* @param {object} flowNode React Flow node
|
||||
*/
|
||||
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) {
|
||||
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) {
|
||||
this.store.deleteGenericObject(field, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the STIX UI.
|
||||
*/
|
||||
onClickResetHandler() {
|
||||
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) {
|
||||
this.store.setUpdateFlow(update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mouse position
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*/
|
||||
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) {
|
||||
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) {
|
||||
const event = {
|
||||
currentTarget: {
|
||||
@@ -536,10 +873,18 @@ class Canvas extends React.Component {
|
||||
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) {
|
||||
const canvas = document.getElementById('canvas');
|
||||
const node = this.store.getNodeById(id);
|
||||
|
||||
if (node.title == 'extension-definition') return;
|
||||
|
||||
const calculate = (min, max) => Math.random() * (max - 100 - min) + min;
|
||||
|
||||
const bounds = {
|
||||
@@ -566,10 +911,11 @@ class Canvas extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { nodes, } = this.store;
|
||||
const { edges, } = this.store;
|
||||
const sdos = this.store.getCustomSDOs();
|
||||
const extensions = this.store.getExtensions();
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -598,14 +944,17 @@ class Canvas extends React.Component {
|
||||
onClickShowSchemaPasteHandler={this.onClickShowSchemaPasteHandler}
|
||||
onClickHideJsonHandler={this.onClickHideJsonHandler}
|
||||
onClickResetHandler={this.onClickResetHandler}
|
||||
onClickSubmitHandler={this.onClickSubmitHandler}
|
||||
onClickShowSDOPickerHandler={this.onClickShowSDOPickerHandler}
|
||||
onClickExportHandler={this.onClickExportHandler}
|
||||
onClickShowExtensionPickerHandler={this.onClickShowExtensionPickerHandler}
|
||||
onClickShowLayoutPanelHandler={this.onClickShowLayoutPanelHandler}
|
||||
onClickShowImporterHandler={this.onClickShowImporterHandler}
|
||||
onChangeCreatorIDHandler={this.onChangeCreatorIDHandler}
|
||||
onClickGroupModeHandler={this.onClickGroupModeHandler}
|
||||
onClickSubmitGroupingHandler={this.onClickSubmitGroupingHandler}
|
||||
onClickShowErrorHandler={this.onClickShowSubmissionErrorHandler}
|
||||
creatorID={this.store.creatorID}
|
||||
groupMode={this.store.groupMode}
|
||||
errors={this.store.showSubmissionErrorBadge}
|
||||
/>
|
||||
|
||||
<BottomMenu
|
||||
@@ -663,12 +1012,37 @@ class Canvas extends React.Component {
|
||||
onClickDeleteRelHandler={this.onClickDeleteRelHandler}
|
||||
/>
|
||||
|
||||
<SDOEditor
|
||||
<ExtensionEditor
|
||||
show={this.store.showEditor}
|
||||
sdo={this.store.selectedSDO}
|
||||
extension={this.store.selectedExt}
|
||||
onClickHideHandler={this.onClickHideEditorHandler}
|
||||
onChangeSDOHandler={this.onChangeSDOHandler}
|
||||
onClickDeleteHandler={this.onClickDeleteSDOHandler}
|
||||
onChangeExtHandler={this.onChangeExtHandler}
|
||||
|
||||
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
|
||||
@@ -680,7 +1054,7 @@ class Canvas extends React.Component {
|
||||
|
||||
<JsonViewer
|
||||
show={this.store.showJSON}
|
||||
json={this.store.mutatedBundle}
|
||||
json={this.store.bundleJSON}
|
||||
onClickHideHandler={this.onClickHideJsonHandler}
|
||||
onClickShowGrowlHandler={this.onClickShowGrowlHandler}
|
||||
/>
|
||||
@@ -711,11 +1085,18 @@ class Canvas extends React.Component {
|
||||
onClickShowRelDetailsHandler={this.onClickShowRelDetailsHandler}
|
||||
/>
|
||||
|
||||
<SDOPicker
|
||||
id="sdo-picker"
|
||||
show={this.store.showSDOPicker}
|
||||
sdos={sdos}
|
||||
onClickHideHandler={this.onClickHideSDOPickerHandler}
|
||||
<ExtensionPicker
|
||||
id="extension-picker"
|
||||
extensions={extensions}
|
||||
show={this.store.showExtensionPicker}
|
||||
onClickHideHandler={this.onClickHideExtensionPickerHandler}
|
||||
onClickSelectExtHandler={this.onClickSelectExtHandler}
|
||||
/>
|
||||
|
||||
<LayoutPanel
|
||||
id="layout-panel"
|
||||
show={this.store.showLayoutPanel}
|
||||
onClickHideHandler={this.onClickHideLayoutPanelHandler}
|
||||
onClickSelectSDOHandler={this.onClickSelectSDOHandler}
|
||||
/>
|
||||
|
||||
@@ -729,6 +1110,7 @@ class Canvas extends React.Component {
|
||||
error={this.store.failedCollection}
|
||||
show={this.store.showSubmissionError}
|
||||
onClickHideHandler={this.onClickHideSubmissionErrorHandler}
|
||||
onClickNodeHandler={this.onClickErrorHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -17,7 +17,7 @@ import GenericObject from './ui/complex/GenericObject';
|
||||
import ConfirmTextarea from './ui/complex/ConfirmTextarea';
|
||||
import ObjectArray from './ui/complex/ObjectArray';
|
||||
|
||||
import Images from '../imgs/Images';
|
||||
import Images from '../util/Images';
|
||||
|
||||
import './details.scss';
|
||||
|
||||
@@ -57,8 +57,9 @@ class Details extends React.Component {
|
||||
}
|
||||
|
||||
for (const prop in props) {
|
||||
const cls = 'item-header';
|
||||
const header = (
|
||||
<div className="item-header">
|
||||
<div className={cls}>
|
||||
{prop}
|
||||
<span
|
||||
data-tooltip-id={`${prop}-tooltip`}
|
||||
@@ -90,13 +91,14 @@ class Details extends React.Component {
|
||||
<Text
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
required={props[prop].required}
|
||||
onChange={this.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'dts':
|
||||
case 'timestamp':
|
||||
control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
@@ -104,7 +106,9 @@ class Details extends React.Component {
|
||||
<DateTime
|
||||
name={prop}
|
||||
selected={props[prop].value}
|
||||
onChange={this.onChangeDateHandler}
|
||||
required={props[prop].required}
|
||||
onTextChange={this.onChangeHandler}
|
||||
onDateChange={this.onChangeDateHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,6 +123,7 @@ class Details extends React.Component {
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
required={props[prop].required}
|
||||
onClickHandler={this.props.onClickArrayHandler}
|
||||
/>
|
||||
);
|
||||
@@ -128,9 +133,9 @@ class Details extends React.Component {
|
||||
if (Array.isArray(refField)) {
|
||||
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 = (
|
||||
<ObjectArray
|
||||
node={node}
|
||||
@@ -138,6 +143,7 @@ class Details extends React.Component {
|
||||
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}
|
||||
@@ -159,13 +165,14 @@ class Details extends React.Component {
|
||||
<Boolean
|
||||
name={prop}
|
||||
selected={props[prop].value}
|
||||
required={props[prop].required}
|
||||
onClick={this.props.onClickBooleanHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case '../common/dictionary.json':
|
||||
case 'dictionary':
|
||||
case 'object':
|
||||
control = (
|
||||
<GenericObject
|
||||
@@ -174,6 +181,7 @@ class Details extends React.Component {
|
||||
description={props[prop].description}
|
||||
key={uuid()}
|
||||
field={prop}
|
||||
required={props[prop].required}
|
||||
onClickAddObjectHandler={
|
||||
this.props.onClickAddGenericObjectHandler
|
||||
}
|
||||
@@ -189,7 +197,7 @@ class Details extends React.Component {
|
||||
|
||||
if (props[prop].$ref && !props[prop].control) {
|
||||
switch (props[prop].$ref) {
|
||||
case '../common/identifier.json':
|
||||
case 'identifier':
|
||||
control = (
|
||||
<div className="item" key={prop}>
|
||||
{header}
|
||||
@@ -197,6 +205,7 @@ class Details extends React.Component {
|
||||
<Text
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
required={props[prop].required}
|
||||
onChange={this.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
@@ -218,6 +227,7 @@ class Details extends React.Component {
|
||||
<Slider
|
||||
value={props[prop].value}
|
||||
field={prop}
|
||||
required={props[prop].required}
|
||||
onChangeHandler={this.props.onChangeSliderHandler}
|
||||
/>
|
||||
</div>
|
||||
@@ -233,6 +243,7 @@ class Details extends React.Component {
|
||||
key={prop}
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
required={props[prop].required}
|
||||
onChangeHandler={this.props.onChangeCSVHandler}
|
||||
/>
|
||||
</div>
|
||||
@@ -248,6 +259,7 @@ class Details extends React.Component {
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
required={props[prop].required}
|
||||
onChangeHandler={this.props.onChangePhaseHandler}
|
||||
onClickRemoveHandler={this.props.onClickRemovePhaseHander}
|
||||
/>
|
||||
@@ -260,7 +272,9 @@ class Details extends React.Component {
|
||||
key={prop}
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
prefix="node"
|
||||
description={props[prop].description}
|
||||
required={props[prop].required}
|
||||
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
|
||||
onChangeERHandler={this.props.onChangeERHandler}
|
||||
onClickDeleteERHandler={this.props.onClickDeleteERHandler}
|
||||
@@ -278,6 +292,7 @@ class Details extends React.Component {
|
||||
field={prop}
|
||||
value={props[prop].value}
|
||||
description={props[prop].description}
|
||||
required={props[prop].required}
|
||||
onClickHandler={this.props.onClickArrayHandler}
|
||||
/>
|
||||
);
|
||||
@@ -290,6 +305,7 @@ class Details extends React.Component {
|
||||
<TextArea
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
required={props[prop].required}
|
||||
onChange={this.onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
@@ -305,6 +321,7 @@ class Details extends React.Component {
|
||||
key={prop}
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
required={props[prop].required}
|
||||
onChangeHandler={this.props.onChangeCSVHandler}
|
||||
/>
|
||||
</div>
|
||||
@@ -316,13 +333,15 @@ class Details extends React.Component {
|
||||
<GenericObject
|
||||
name={prop}
|
||||
value={props[prop].value}
|
||||
vocab={props[prop].vocab}
|
||||
description={props[prop].description}
|
||||
key={uuid()}
|
||||
field={prop}
|
||||
required={props[prop].required}
|
||||
onClickAddObjectHandler={
|
||||
this.props.onClickAddGenericObjectHandler
|
||||
}
|
||||
onClickDeleteArrayObjectHandler={
|
||||
onClickDeleteObjectHandler={
|
||||
this.props.onClickDeleteGenericObjectHandler
|
||||
}
|
||||
onChangeHandler={this.props.onChangeGenericObjectHandler}
|
||||
@@ -337,6 +356,7 @@ class Details extends React.Component {
|
||||
description={props[prop].description}
|
||||
key={uuid()}
|
||||
field={prop}
|
||||
required={props[prop].required}
|
||||
onClickAddTextHandler={this.props.onClickAddTextHandler}
|
||||
/>
|
||||
);
|
||||
@@ -346,6 +366,49 @@ class Details extends React.Component {
|
||||
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 (
|
||||
<Panel
|
||||
show={this.props.show}
|
||||
@@ -1,35 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import Images from '../../imgs/Images';
|
||||
import Images from '../../util/Images';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './FlowNode.scss';
|
||||
|
||||
export default class FlowNode extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selected: false,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { node, } = this.props.data;
|
||||
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) {
|
||||
display = node.properties.name.value;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
<div className={cls}
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
backgroundSize: 'contain',
|
||||
backgroundImage: `url(${node.customImg ? node.customImg : Images.getImage(node.img)})`,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
border: `${border}`,
|
||||
}}
|
||||
/>
|
||||
<Handle
|
||||
@@ -63,7 +64,7 @@ export default class FlowNode extends React.Component {
|
||||
isConnectable={this.props.isConnectable}
|
||||
/>
|
||||
|
||||
<div className="nodeLabel">
|
||||
<div className={labelCls}>
|
||||
{display}
|
||||
</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 { observer } from 'mobx-react';
|
||||
import Panel from './ui/panel/Panel';
|
||||
import Images from '../imgs/Images';
|
||||
import Images from '../util/Images';
|
||||
|
||||
import './SubmissionError.scss';
|
||||
|
||||
class SubmissionError extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const errorStructure = {};
|
||||
const msg = [];
|
||||
|
||||
this.props.error.map((item, i) => {
|
||||
if (!errorStructure.hasOwnProperty(item.node)) {
|
||||
if (!(item.node in errorStructure)){
|
||||
errorStructure[item.node] = {};
|
||||
errorStructure[item.node].name = item.name;
|
||||
errorStructure[item.node].details = [];
|
||||
errorStructure[item.node].img = item.img;
|
||||
errorStructure[item.node].details.push({
|
||||
@@ -34,7 +35,7 @@ class SubmissionError extends React.Component {
|
||||
|
||||
for (const item in errorStructure) {
|
||||
const details = [];
|
||||
|
||||
const name = errorStructure[item].name;
|
||||
if (errorStructure[item].details) {
|
||||
errorStructure[item].details.map((detail) => {
|
||||
details.push(
|
||||
@@ -50,11 +51,11 @@ class SubmissionError extends React.Component {
|
||||
});
|
||||
|
||||
msg.push(
|
||||
<div key={item}>
|
||||
<div className="header">
|
||||
<div className='submission-item' key={item} onClick={() => this.props.onClickNodeHandler(item)}>
|
||||
<div className="container-header">
|
||||
<img src={Images.getImage(errorStructure[item].img)} width="30" />
|
||||
{' '}
|
||||
{item}
|
||||
{name}
|
||||
</div>
|
||||
<div className="rows-container">
|
||||
{details}
|
||||
@@ -69,6 +70,9 @@ class SubmissionError extends React.Component {
|
||||
show={this.props.show}
|
||||
onClickHideHandler={this.props.onClickHideHandler}
|
||||
>
|
||||
<div className="header">
|
||||
Errors
|
||||
</div>
|
||||
<div className="submission-error">
|
||||
{msg}
|
||||
</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 { observer } from 'mobx-react';
|
||||
import Panel from '../ui/panel/Panel';
|
||||
@@ -24,6 +24,11 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
height: 30px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.json-copy {
|
||||
height: 35px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,12 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
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 {
|
||||
font-size: 18px;
|
||||
@@ -14,7 +14,7 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
background-color: $lt-gray-bg;
|
||||
background-color: defaults.$lt-gray-bg;
|
||||
|
||||
.title {
|
||||
padding-top: 5px;
|
||||
@@ -30,10 +30,10 @@
|
||||
padding-left: 7px;
|
||||
width: 80px;
|
||||
display: flex;
|
||||
background-color: $default-active-bg;
|
||||
background-color: defaults.$default-active-bg;
|
||||
border-radius: 5px;
|
||||
padding-top: 7px;
|
||||
color: $light-font-0;
|
||||
color: defaults.$light-font-0;
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 14px;
|
||||
@@ -48,10 +48,19 @@
|
||||
}
|
||||
|
||||
.delete:hover {
|
||||
background-color: $error-font;
|
||||
background-color: defaults.$error-font;
|
||||
}
|
||||
}
|
||||
|
||||
.invalid {
|
||||
border: red solid 1px;
|
||||
}
|
||||
|
||||
.required-warning {
|
||||
color: red;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.body {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
@@ -68,7 +77,7 @@
|
||||
|
||||
.item-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
@@ -78,6 +87,24 @@
|
||||
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 {
|
||||
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 { observer } from 'mobx-react';
|
||||
|
||||
import MenuItem from './MenuItem';
|
||||
import Images from '../../imgs/Images';
|
||||
import Images from '../../util/Images';
|
||||
|
||||
import './BottomMenu.scss';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../defaults';
|
||||
@use '../../defaults';
|
||||
|
||||
.menu {
|
||||
position: fixed;
|
||||
@@ -29,12 +29,12 @@
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 5px;
|
||||
background-color: $default-active-bg;
|
||||
background-color: defaults.$default-active-bg;
|
||||
|
||||
div {
|
||||
padding-top: 10px;
|
||||
padding-left: 7px;
|
||||
color: $light-font-0;
|
||||
color: defaults.$light-font-0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ class TopMenu extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let groupLabel = 'Select';
|
||||
let groupLabel = 'Group';
|
||||
let groupClass = '';
|
||||
let items;
|
||||
|
||||
@@ -38,7 +38,8 @@ class TopMenu extends React.Component {
|
||||
groupClass = 'cancel-btn';
|
||||
items = (
|
||||
<div id="myDropdown" className="dropdown-content">
|
||||
<a onClick={this.submitGroup}>Create Group</a>
|
||||
<a onClick={this.submitGroup}>Create Group
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -48,7 +49,7 @@ class TopMenu extends React.Component {
|
||||
<div
|
||||
data-tooltip-id="select-tooltip"
|
||||
data-tooltip-content="Select Nodes"
|
||||
className={`grouping-btn menu-item ${groupClass}`}
|
||||
className={`grouping-btn menu-btn menu-item ${groupClass}`}
|
||||
onClick={this.flipGroupMode}
|
||||
>
|
||||
{groupLabel}
|
||||
@@ -57,6 +58,8 @@ class TopMenu extends React.Component {
|
||||
</div>
|
||||
);
|
||||
|
||||
const badge = this.props.errors ? (<span className="badge"></span>) : undefined;
|
||||
|
||||
return (
|
||||
<div className="top-menu">
|
||||
<div className="row">
|
||||
@@ -74,69 +77,85 @@ class TopMenu extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-tooltip-id="paste-tooltip"
|
||||
data-tooltip-content="Paste JSON"
|
||||
className="json-paste-btn menu-item-medium"
|
||||
onClick={this.props.onClickShowJsonPasteHandler}
|
||||
data-tooltip-id="view-tooltip"
|
||||
data-tooltip-content="View Bundle"
|
||||
className="menu-btn menu-item"
|
||||
onClick={this.props.onClickShowJsonHandler}
|
||||
>
|
||||
{'{ + }'}
|
||||
<i className="material-icons">description</i>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="view-tooltip"
|
||||
data-tooltip-content="View JSON"
|
||||
className="json-btn menu-item-small"
|
||||
onClick={this.props.onClickShowJsonHandler}
|
||||
data-tooltip-id="paste-tooltip"
|
||||
data-tooltip-content="Paste Bundle"
|
||||
className="menu-btn menu-item"
|
||||
onClick={this.props.onClickShowJsonPasteHandler}
|
||||
>
|
||||
{'{ }'}
|
||||
<i className="material-icons">note_add</i>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="schema-tooltip"
|
||||
data-tooltip-content="Paste Schema"
|
||||
className="schema-paste-btn menu-item-medium"
|
||||
className="menu-btn menu-item"
|
||||
onClick={this.props.onClickShowSchemaPasteHandler}
|
||||
>
|
||||
{'{ * }'}
|
||||
<i className="material-icons">add_box</i>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="sdo-tooltip"
|
||||
data-tooltip-content="SDO Extensions"
|
||||
data-tooltip-id="layout"
|
||||
data-tooltip-content="Graph Layout and Filtering"
|
||||
className="sdos-btn menu-item"
|
||||
onClick={this.props.onClickShowSDOPickerHandler}
|
||||
onClick={this.props.onClickShowLayoutPanelHandler}
|
||||
>
|
||||
Exts
|
||||
Layout
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="import-tooltip"
|
||||
data-tooltip-content="Import Data from File"
|
||||
className="reset-btn menu-item"
|
||||
className="menu-btn menu-item"
|
||||
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>
|
||||
{group}
|
||||
<div
|
||||
data-tooltip-id="clear-tooltip"
|
||||
data-tooltip-content="Clear JSON"
|
||||
className="reset-btn menu-item"
|
||||
data-tooltip-content="Reset Bundle"
|
||||
className="reset-btn menu-btn menu-item"
|
||||
onClick={this.props.onClickResetHandler}
|
||||
>
|
||||
<span className="i material-icons">refresh</span>
|
||||
{' '}
|
||||
Reset
|
||||
<span className="material-icons">refresh</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-tooltip-id="submit-tooltip"
|
||||
data-tooltip-content="Submit JSON"
|
||||
className="reset-btn menu-item"
|
||||
onClick={this.props.onClickSubmitHandler}
|
||||
data-tooltip-content="Export JSON"
|
||||
className="menu-btn menu-item"
|
||||
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>
|
||||
|
||||
<Tooltip id="creator-tooltip" />
|
||||
@@ -147,6 +166,7 @@ class TopMenu extends React.Component {
|
||||
<Tooltip id="import-tooltip" />
|
||||
<Tooltip id="clear-tooltip" />
|
||||
<Tooltip id="submit-tooltip" />
|
||||
<Tooltip id="error-tooltip" />
|
||||
</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 Text from '../ui/inputs/Text';
|
||||
import Boolean from '../ui/inputs/Boolean';
|
||||
import Images from '../../imgs/Images';
|
||||
import Images from '../../util/Images';
|
||||
|
||||
import '../details.scss';
|
||||
import './RelationshipDetails.scss';
|
||||
|
||||
class RelationshipDetails extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
type: 'relates to',
|
||||
type: 'related-to',
|
||||
x_exclusive: false,
|
||||
};
|
||||
this.onSubmitHandler = this.onSubmitHandler.bind(this);
|
||||
@@ -45,7 +46,7 @@ class RelationshipDetails extends React.Component {
|
||||
|
||||
reset() {
|
||||
this.setState({
|
||||
type: 'relates to',
|
||||
type: 'related-to',
|
||||
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 Panel from '../ui/panel/Panel';
|
||||
import Text from '../ui/inputs/Text';
|
||||
import Images from '../../imgs/Images';
|
||||
import Images from '../../util/Images';
|
||||
|
||||
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 {
|
||||
display: flex;
|
||||
@@ -26,7 +26,7 @@
|
||||
padding-top: 10px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-family: $default-font-family;
|
||||
font-family: defaults.$default-font-family;
|
||||
line-height: 30px;
|
||||
|
||||
img {
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
|
||||
.rel-type {
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
padding-left: 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 { observer } from 'mobx-react';
|
||||
import Panel from '../ui/panel/Panel';
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
@@ -8,8 +8,8 @@ button.def {
|
||||
width: auto;
|
||||
min-width: 130px;
|
||||
height: 30px;
|
||||
color: $light-font-0;
|
||||
font-family: $default-font-family;
|
||||
color: defaults.$light-font-0;
|
||||
font-family: defaults.$default-font-family;
|
||||
font-size: 14px;
|
||||
border-color: transparent;
|
||||
cursor: pointer;
|
||||
@@ -21,7 +21,7 @@ button.def {
|
||||
|
||||
button.disabled {
|
||||
background-color: rgba(128,128,128,.8) !important;
|
||||
color: $gray-font-0 !important;
|
||||
color: defaults.$gray-font-0 !important;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import TextArea from '../inputs/TextArea';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './confirmtextarea.scss';
|
||||
|
||||
@@ -18,10 +19,6 @@ class ConfirmTextarea extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onChangeInputHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -42,6 +39,9 @@ class ConfirmTextarea extends React.Component {
|
||||
const { field, } = this.props;
|
||||
const value = this.props.value ? this.props.value : [];
|
||||
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 (
|
||||
<div className="ct-container">
|
||||
@@ -56,7 +56,10 @@ class ConfirmTextarea extends React.Component {
|
||||
</span>
|
||||
<Tooltip id={`${field}-tooltip`} />
|
||||
</div>
|
||||
<div className="ct-body">
|
||||
<div className={classNames({
|
||||
"ct-body": true,
|
||||
"invalid": invalid
|
||||
})}>
|
||||
<div className="ct-block-input">
|
||||
<div className="input">
|
||||
<TextArea
|
||||
@@ -74,6 +77,7 @@ class ConfirmTextarea extends React.Component {
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
{warning}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import classNames from 'classnames';
|
||||
import Text from '../inputs/Text';
|
||||
|
||||
import './externalreferences.scss';
|
||||
@@ -17,8 +18,6 @@ class ExternalReferences extends React.Component {
|
||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {}
|
||||
|
||||
onChangeERHandler(event, value) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -48,6 +47,10 @@ class ExternalReferences extends React.Component {
|
||||
const { field, } = this.props;
|
||||
const value = this.props.value ? this.props.value : [];
|
||||
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 (
|
||||
<div className="er-container">
|
||||
@@ -71,12 +74,16 @@ class ExternalReferences extends React.Component {
|
||||
<Tooltip id={`${field}-tooltip`} />
|
||||
<Tooltip id={`${field}-control-tooltip`} />
|
||||
</div>
|
||||
<div className="er-body">
|
||||
<div className={classNames({
|
||||
"er-body": true,
|
||||
"invalid": invalid
|
||||
})}>
|
||||
{value.map((p, i) => (
|
||||
<ReferenceBlock
|
||||
key={i}
|
||||
i={i}
|
||||
kv={p}
|
||||
prefix={prefix}
|
||||
onChangeERHandler={this.onChangeERHandler}
|
||||
onClickDeleteERHandler={this.onClickDeleteERHandler}
|
||||
onClickAddHandler={this.onClickAddHandler}
|
||||
@@ -84,6 +91,7 @@ class ExternalReferences extends React.Component {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{warning}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -92,8 +100,9 @@ class ExternalReferences extends React.Component {
|
||||
function ReferenceBlock(props) {
|
||||
const blocks = [];
|
||||
const idx = props.i;
|
||||
const selectID = `select-${props.i}`;
|
||||
const inputID = `input-${props.i}`;
|
||||
const prefix = props.prefix;
|
||||
const selectID = `select-${prefix}-${props.i}`;
|
||||
const inputID = `input-${prefix}-${props.i}`;
|
||||
|
||||
const propValues = [
|
||||
'source_name',
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import classNames from 'classnames';
|
||||
import Text from '../inputs/Text';
|
||||
|
||||
import './genericobject.scss';
|
||||
@@ -10,21 +11,18 @@ class GenericObject extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChangeSelectHandler = this.onChangeSelectHandler.bind(this);
|
||||
this.onChangeInputHandler = this.onChangeInputHandler.bind(this);
|
||||
this.onClickAddObjectHandler = this.onClickAddObjectHandler.bind(this);
|
||||
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
|
||||
this.onClickCreateBlankHandler = this.onClickCreateBlankHandler.bind(this);
|
||||
|
||||
this.state = {
|
||||
key: '',
|
||||
key: this.props.vocab? this.props.vocab[0] : '',
|
||||
value: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onChangeInputHandler(event) {
|
||||
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) {
|
||||
this.props.onClickDeletePropertyHandler(select, idx);
|
||||
this.props.onClickDeleteObjectHandler(select, idx);
|
||||
}
|
||||
|
||||
onClickCreateBlankHandler() {
|
||||
@@ -54,9 +60,13 @@ class GenericObject extends React.Component {
|
||||
|
||||
render() {
|
||||
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 {required, } = this.props;
|
||||
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) {
|
||||
rows.push(
|
||||
@@ -65,11 +75,30 @@ class GenericObject extends React.Component {
|
||||
v={value[key]}
|
||||
k={key}
|
||||
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 (
|
||||
<div className="go-container">
|
||||
<div className="go-header">
|
||||
@@ -83,11 +112,14 @@ class GenericObject extends React.Component {
|
||||
</span>
|
||||
<Tooltip id={`${field}-tooltip`} />
|
||||
</div>
|
||||
<div className="go-body">
|
||||
<div className={classNames({
|
||||
"go-body": true,
|
||||
"invalid": invalid
|
||||
})}>
|
||||
|
||||
<div className="go-block-input">
|
||||
<div className="input">
|
||||
<Text name="key" value={this.state.key} onChange={this.onChangeInputHandler} />
|
||||
{selector}
|
||||
</div>
|
||||
<div className="input">
|
||||
<Text name="value" value={this.state.value} onChange={this.onChangeInputHandler} />
|
||||
@@ -99,6 +131,7 @@ class GenericObject extends React.Component {
|
||||
|
||||
{rows}
|
||||
</div>
|
||||
{warning}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './killchain.scss';
|
||||
|
||||
@@ -12,10 +13,6 @@ class KillChain extends React.Component {
|
||||
this.populatePhase = this.populatePhase.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onChangePhaseHandler(event) {
|
||||
const kcDomName = `kc-name-${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 value = this.props.value ? this.props.value : [];
|
||||
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 phaseName = `phase-${this.props.node.id}`;
|
||||
@@ -81,7 +81,10 @@ class KillChain extends React.Component {
|
||||
</span>
|
||||
<Tooltip id={`${field}-tooltip`} />
|
||||
</div>
|
||||
<div className="kill-chain-body">
|
||||
<div className={classNames({
|
||||
"kill-chain-body": true,
|
||||
"invalid": invalid
|
||||
})}>
|
||||
<div className="kill-chain-options">
|
||||
<select id={kcName} onChange={this.populatePhase}>
|
||||
<option value={0}> -- Select Kill Chain -- </option>
|
||||
@@ -112,12 +115,13 @@ class KillChain extends React.Component {
|
||||
{' '}
|
||||
{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>
|
||||
{warning}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import classNames from 'classnames';
|
||||
import Text from '../inputs/Text';
|
||||
import './externalreferences.scss';
|
||||
|
||||
@@ -16,8 +17,6 @@ class ObjectArray extends React.Component {
|
||||
this.onClickDeletePropertyHandler = this.onClickDeletePropertyHandler.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {}
|
||||
|
||||
onChangeArrayObjectHandler(event, value) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -49,6 +48,9 @@ class ObjectArray extends React.Component {
|
||||
const { field, } = this.props;
|
||||
const value = this.props.value ? this.props.value : [];
|
||||
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 (
|
||||
<div className="er-container">
|
||||
@@ -72,7 +74,10 @@ class ObjectArray extends React.Component {
|
||||
<Tooltip id={`${field}-tooltip`} />
|
||||
<Tooltip id={`add-${field}-tooltip`} />
|
||||
</div>
|
||||
<div className="er-body">
|
||||
<div className={classNames({
|
||||
"er-body": true,
|
||||
"invalid": invalid,
|
||||
})}>
|
||||
{value.map((p, i) => (
|
||||
<ObjectBlock
|
||||
key={i}
|
||||
@@ -86,6 +91,7 @@ class ObjectArray extends React.Component {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{warning}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
.ct-container {
|
||||
padding-left: 20px;
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
.ct-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
.ct-body {
|
||||
overflow-x: hidden;
|
||||
border: 1px solid $standard-border-color;
|
||||
border: 1px solid defaults.$standard-border-color;
|
||||
min-height: 50px;
|
||||
width: 100%;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
@@ -81,12 +81,12 @@
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: $error-font;
|
||||
color: defaults.$error-font;
|
||||
}
|
||||
|
||||
.add {
|
||||
padding-top: 15px;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
.er-container {
|
||||
padding-left: 20px;
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
.er-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
@@ -18,11 +18,11 @@
|
||||
|
||||
.er-body {
|
||||
overflow: auto;
|
||||
border: 1px solid $standard-border-color;
|
||||
border: 1px solid defaults.$standard-border-color;
|
||||
min-height: 50px;
|
||||
|
||||
.er-block {
|
||||
border: 1px solid $standard-border-color;
|
||||
border: 1px solid defaults.$standard-border-color;
|
||||
margin: 10px;
|
||||
|
||||
.er-block-row {
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: $error-font;
|
||||
color: defaults.$error-font;
|
||||
}
|
||||
|
||||
.remove-er {
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
.add {
|
||||
padding-top: 15px;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
.go-container {
|
||||
padding-left: 20px;
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
.go-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
.go-body {
|
||||
overflow-x: hidden;
|
||||
border: 1px solid $standard-border-color;
|
||||
border: 1px solid defaults.$standard-border-color;
|
||||
min-height: 50px;
|
||||
width: 100%;
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
@@ -71,12 +71,12 @@
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: $error-font;
|
||||
color: defaults.$error-font;
|
||||
}
|
||||
|
||||
.add {
|
||||
padding-top: 15px;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
.kill-chain-container {
|
||||
padding-left: 20px;
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
.kill-chain-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
@@ -19,7 +19,7 @@
|
||||
.kill-chain-body {
|
||||
|
||||
overflow: auto;
|
||||
border: 1px solid $standard-border-color;
|
||||
border: 1px solid defaults.$standard-border-color;
|
||||
|
||||
.kill-chain-options {
|
||||
display: flex;
|
||||
@@ -35,7 +35,7 @@
|
||||
padding: 5px 5px 5px 10px;
|
||||
.material-icons {
|
||||
vertical-align: middle;
|
||||
color: $error-font;
|
||||
color: defaults.$error-font;
|
||||
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);
|
||||
}
|
||||
|
||||
componentDidMount() {}
|
||||
|
||||
onClickHandler(field, value) {
|
||||
this.props.onClickHandler(field, value);
|
||||
}
|
||||
@@ -19,8 +17,11 @@ class ArraySelector extends React.Component {
|
||||
render() {
|
||||
const items = this.props.vocab ? this.props.vocab : [];
|
||||
const { field, } = this.props;
|
||||
const { value, } = this.props;
|
||||
const { value, } = 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({
|
||||
'array-container-item': true,
|
||||
@@ -40,7 +41,10 @@ class ArraySelector extends React.Component {
|
||||
</span>
|
||||
<Tooltip id={`${field}-tooltip`} />
|
||||
</div>
|
||||
<div className="array-container-body">
|
||||
<div className={classNames({
|
||||
"array-container-body": true,
|
||||
"invalid": invalid
|
||||
})}>
|
||||
{items.map((item, i) => {
|
||||
if (value && value.indexOf(item) > -1) {
|
||||
cls = classNames({
|
||||
@@ -63,6 +67,7 @@ class ArraySelector extends React.Component {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{warning}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,10 +9,6 @@ class Boolean extends React.Component {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onClickHandler(field, value) {
|
||||
this.props.onClickHandler(field, value);
|
||||
}
|
||||
@@ -9,21 +9,18 @@ class CSVInput extends React.Component {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
onClickHandler(field, value) {
|
||||
this.props.onClickHandler(field, value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const value = this.props.value ? this.props.value.join() : '';
|
||||
const value = this.props.value ?? "";
|
||||
|
||||
return (
|
||||
<Text
|
||||
name={this.props.name}
|
||||
value={value}
|
||||
required={this.props.required}
|
||||
onChange={this.props.onChangeHandler}
|
||||
/>
|
||||
);
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
|
||||
import Text from './Text.jsx';
|
||||
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import './datetime.scss';
|
||||
|
||||
@@ -12,15 +14,24 @@ export default class DateTime extends React.Component {
|
||||
}
|
||||
|
||||
onChange(datetime) {
|
||||
this.props.onChange(this.props.name, datetime);
|
||||
this.props.onDateChange(this.props.name, datetime);
|
||||
}
|
||||
|
||||
render() {
|
||||
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') {
|
||||
const dateObj = new Date(dts);
|
||||
dts = dateObj;
|
||||
if (isNaN(dateObj.getTime())) {
|
||||
return control;
|
||||
} else {
|
||||
dts = dateObj;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Text from './Text';
|
||||
import { observer } from 'mobx-react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './text.scss';
|
||||
|
||||
@@ -36,6 +38,10 @@ class LabeledText extends React.Component {
|
||||
|
||||
render() {
|
||||
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 (
|
||||
<div>
|
||||
@@ -44,14 +50,18 @@ class LabeledText extends React.Component {
|
||||
type={inputType}
|
||||
ref={(c) => { this.input = c; }}
|
||||
autoComplete={this.props.autocomplete || 'off'}
|
||||
className="def"
|
||||
className={classNames({
|
||||
"def": true,
|
||||
"invalid": invalid
|
||||
})}
|
||||
placeholder={this.props.placeholder}
|
||||
onChange={this.onChangeHandler}
|
||||
onKeyDown={(e) => this.onKeyDownHandler(e)}
|
||||
value={this.props.value}
|
||||
value={value}
|
||||
disabled={this.props.disabled}
|
||||
id={this.props.id}
|
||||
/>
|
||||
{warning}
|
||||
</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 PropTypes from 'prop-types';
|
||||
import { observer } from 'mobx-react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './text.scss';
|
||||
|
||||
@@ -35,8 +36,11 @@ class Text extends React.Component {
|
||||
}
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<input
|
||||
@@ -44,14 +48,18 @@ class Text extends React.Component {
|
||||
type={inputType}
|
||||
ref={(c) => { this.input = c; }}
|
||||
autoComplete={this.props.autocomplete || 'off'}
|
||||
className="def"
|
||||
className={classNames({
|
||||
"def": true,
|
||||
"invalid": invalid
|
||||
})}
|
||||
placeholder={this.props.placeholder}
|
||||
onChange={this.onChangeHandler}
|
||||
onKeyDown={(e) => this.onKeyDownHandler(e)}
|
||||
value={this.props.value}
|
||||
value={value}
|
||||
disabled={this.props.disabled}
|
||||
id={this.props.id}
|
||||
/>
|
||||
{warning}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './text.scss';
|
||||
|
||||
@@ -38,7 +39,10 @@ class TextArea extends React.Component {
|
||||
}
|
||||
|
||||
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 (
|
||||
<div>
|
||||
@@ -48,14 +52,18 @@ class TextArea extends React.Component {
|
||||
this.input = c;
|
||||
}}
|
||||
autoComplete={this.props.autocomplete || 'off'}
|
||||
className="def"
|
||||
className={classNames({
|
||||
"def": true,
|
||||
"invalid": invalid
|
||||
})}
|
||||
placeholder={this.props.placeholder}
|
||||
onChange={this.onChangeHandler}
|
||||
onKeyDown={(e) => this.onKeyDownHandler(e)}
|
||||
value={this.props.value}
|
||||
value={value}
|
||||
disabled={this.props.disabled}
|
||||
id={this.props.id}
|
||||
/>
|
||||
{warning}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
.array-container {
|
||||
padding-left: 20px;
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
.array-container-header {
|
||||
font-weight: bold;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
padding-bottom: 3px;
|
||||
|
||||
span {
|
||||
@@ -21,12 +21,12 @@
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: $error-font;
|
||||
color: defaults.$error-font;
|
||||
}
|
||||
|
||||
.add {
|
||||
padding-top: 15px;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
}
|
||||
|
||||
.array-container-input {
|
||||
@@ -51,7 +51,7 @@
|
||||
.array-container-body {
|
||||
height: 100px;
|
||||
overflow: auto;
|
||||
border: 1px solid $standard-border-color;
|
||||
border: 1px solid defaults.$standard-border-color;
|
||||
|
||||
.array-container-item {
|
||||
cursor: pointer;
|
||||
@@ -60,11 +60,11 @@
|
||||
}
|
||||
|
||||
.array-container-selected {
|
||||
background-color: $default-active-bg;
|
||||
background-color: defaults.$default-active-bg;
|
||||
}
|
||||
|
||||
.array-container-item:hover {
|
||||
background-color: $light-font-0;
|
||||
background-color: defaults.$light-font-0;
|
||||
}
|
||||
|
||||
.array-block-input {
|
||||
@@ -83,7 +83,7 @@
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
color: $default-active-bg;
|
||||
color: defaults.$default-active-bg;
|
||||
padding-left: 5px;
|
||||
padding-top: 40%;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
.boolean {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
line-height: 30px;
|
||||
border: 1px solid $standard-border-color;
|
||||
border: 1px solid defaults.$standard-border-color;
|
||||
width: 99%;
|
||||
|
||||
div {
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: $default-active-bg;
|
||||
background-color: defaults.$default-active-bg;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
.react-datepicker-wrapper {
|
||||
width: 100%;
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
.react-datepicker__input-container input {
|
||||
height: 39px;
|
||||
border: 1px solid $standard-border-color;
|
||||
border: 1px solid defaults.$standard-border-color;
|
||||
background-color: transparent;
|
||||
width: 97%;
|
||||
padding-left: 10px;
|
||||
font-family: $default-font-family;
|
||||
font-family: defaults.$default-font-family;
|
||||
color: #000;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
@@ -1,14 +1,14 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
input.def {
|
||||
height: 39px;
|
||||
border: 1px solid $standard-border-color;
|
||||
border: 1px solid defaults.$standard-border-color;
|
||||
background-color: transparent;
|
||||
width: 97%;
|
||||
padding-left: 10px;
|
||||
color: $dark-font-0;
|
||||
color: defaults.$dark-font-0;
|
||||
font-size: 16px;
|
||||
font-family: $default-font-family;
|
||||
font-family: defaults.$default-font-family;
|
||||
}
|
||||
|
||||
.custom-file-selector {
|
||||
@@ -16,5 +16,5 @@ input.def {
|
||||
padding: 5px 4px 5px 11px;
|
||||
color: #fff;
|
||||
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 {
|
||||
background-color: transparet;
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
@@ -10,13 +10,17 @@ input.def,
|
||||
textarea,
|
||||
select {
|
||||
height: 39px;
|
||||
border: 1px solid $standard-border-color;
|
||||
border: 1px solid defaults.$standard-border-color;
|
||||
background-color: transparent;
|
||||
width: 97%;
|
||||
padding-left: 10px;
|
||||
color: $dark-font-0;
|
||||
color: defaults.$dark-font-0;
|
||||
font-size: 16px;
|
||||
font-family: $default-font-family;
|
||||
font-family: defaults.$default-font-family;
|
||||
}
|
||||
|
||||
input.def {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../defaults';
|
||||
@use '../../../defaults';
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
@@ -7,7 +7,7 @@
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
background-color: rgba(0, 0, 0,.2);
|
||||
z-index: $panel-mask-index;
|
||||
z-index: defaults.$panel-mask-index;
|
||||
|
||||
.panel {
|
||||
position: absolute;
|
||||
@@ -17,7 +17,7 @@
|
||||
bottom: 0px;
|
||||
background-color: #fff;
|
||||
box-shadow: -20px 25px 50px 0px #000;
|
||||
z-index: $panel-index;
|
||||
z-index: defaults.$panel-index;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ $default-font-family: 'Alegreya Sans SC';
|
||||
//colors
|
||||
$default-active-bg: #46a0f5;
|
||||
$error-font: #d31d18;
|
||||
$warning-font: #dda20f;
|
||||
$light-font-0: #e1e3e6;
|
||||
$gray-font-0: #c8c5c5;
|
||||
$dark-font-0: #000;
|
||||
@@ -20,10 +20,7 @@ class Artifact extends Base {
|
||||
this.properties.payload_bin.type = 'string';
|
||||
this.properties.url.type = 'string';
|
||||
this.properties.encryption_algorithm.type = 'string';
|
||||
|
||||
this.properties.hashes.value = {};
|
||||
|
||||
this.properties.hashes.control = 'genericobject';
|
||||
this.properties.encryption_algorithm.vocab = this.definitions["encryption-algorithm-enum"].enum;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class AttackPattern extends Base {
|
||||
prefix: 'attack-pattern--',
|
||||
active: true,
|
||||
relationships: [
|
||||
{ type: 'delivers', target: 'malware', },
|
||||
{ type: 'targets', target: 'identity', },
|
||||
{ type: 'targets', target: 'location', },
|
||||
{ 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);
|
||||
|
||||
this.properties.hashes.value = {};
|
||||
this.properties.hashes.control = 'genericobject';
|
||||
|
||||
this.properties.x509_v3_extensions.type = 'string';
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,7 @@ class Custom extends Base {
|
||||
const def = deepmerge(definition_extension, rawDefinition);
|
||||
super(common, def);
|
||||
|
||||
const extProps = { extension_type: extensionDefinition.extension_types[0], };
|
||||
this.properties.extensions = {};
|
||||
this.properties.extensions.type = 'object';
|
||||
this.properties.extensions.value = {};
|
||||
this.properties.extensions.value[extensionDefinition.id] = extProps;
|
||||
this.properties.extensions.control = 'hidden';
|
||||
this.extensions.push(extensionDefinition.uiid);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user