initial commit

This commit is contained in:
Jason Minnick
2020-07-07 08:52:48 -04:00
commit 1086b1b0f2
221 changed files with 29668 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

104
.gitignore vendored Normal file
View File

@@ -0,0 +1,104 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port

31
.readme Normal file
View File

@@ -0,0 +1,31 @@
# STIX 2[x] Drag and Drop Builder
# Definitions
The definitions are a direct copy from the OASIS schemas repository without mutation.
# Definition Adapters
The definition adapters are a way to mutate the definitions to help control the flow of the visualization. All adapters inherit the Base.js adapter where much of the initial mutating happens.
# Control Property
The control property can be used to help extend custom ways to display and/or interact with the properties in the details panel. Some properties default based on their type but if more complex or unique controls are required, the control property is the way to extend functionality.
Current controls:
- hidden: Obviously hides the property in the details panel.
- literal: Outputs values as is with a control or input to edit.
- slider: Custom slider-bar control.
- csv: The csv control will take a comma separated list of values in a text control and split them into an array for the object property values.
- killchain: Used to select complex arrays.
- externalrefs: Used to build complex objects.
- stringselector: Works like the array selector but only allows for selecting a single value to populate a string.
- textarea: Allows for cleaner input of larger text amounts.
# Hoisting Vocabs
In the definitions specific to an object, I hoist the vocabs onto the property it belongs to. This makes it seamless to pass along to the array control used to select options.
Specific vocab notes
- labels: there are placeholder values located in definition-adapters/Base.js. This can easily be updated to reflect your sharing groups standard list for each object or even hidden with the `control` property.

BIN
app/.DS_Store vendored Normal file

Binary file not shown.

21
app/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Hashem Khalifa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1 @@
export default '';

View File

@@ -0,0 +1,3 @@
import idObj from 'identity-obj-proxy';
export default idObj;

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { mount } from 'enzyme';
import App from '../src/App';
import HelloWorld from '../src/components/hello-world';
describe('<App />', () => {
const wrap = mount(<App />);
it('renders', () => {
expect(wrap.find(App).exists()).toBe(true);
});
it('contains HelloWorld component', () => {
expect(wrap.find(HelloWorld).exists()).toBe(true);
});
});

47
app/babel.config.js Normal file
View File

@@ -0,0 +1,47 @@
module.exports = function(api) {
const babelEnv = api.env();
api.cache(true);
const presets = [
[
'@babel/preset-env',
{
targets: {
esmodules: true,
},
corejs: '3.0.0',
useBuiltIns: 'usage',
},
],
'@babel/preset-react',
];
const plugins = [
'@babel/transform-react-constant-elements',
'transform-react-remove-prop-types',
'transform-react-pure-class-to-function',
'@babel/plugin-transform-runtime',
'react-hot-loader/babel',
// Stage 2 https://github.com/babel/babel/tree/master/packages/babel-preset-stage-2
['@babel/plugin-proposal-decorators', { legacy: true }],
'@babel/plugin-proposal-function-sent',
'@babel/plugin-proposal-export-namespace-from',
'@babel/plugin-proposal-numeric-separator',
'@babel/plugin-proposal-throw-expressions',
// Stage 3
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-syntax-import-meta',
['@babel/plugin-proposal-class-properties', { loose: true }],
'@babel/plugin-proposal-json-strings',
];
if (babelEnv === 'production') {
plugins.push(['@babel/plugin-transform-react-inline-elements']);
}
return {
presets,
plugins,
};
};

45
app/build/css/main.css Normal file
View File

@@ -0,0 +1,45 @@
body,html,#app{margin:0px;padding:0px;position:fixed;top:0;bottom:0;left:0;right:0;font-family:"Alegreya Sans SC"}
.menu{position:fixed;bottom:20px;right:25px}.menu .row{display:flex;flex-direction:row}.menu .row .menu-item{width:40px;padding-right:10px;cursor:pointer}.menu .row .menu-item img{width:40px}.menu .row .obs{width:40px;height:40px;border-radius:5px;background-color:#46a0f5}.menu .row .obs div{padding-top:10px;padding-left:7px;color:#e1e3e6;cursor:pointer}
.top-menu{position:fixed;top:20px;right:20px}.top-menu .row{display:flex;flex-direction:row}.top-menu .row .menu-item-small{width:20px;height:20px}.top-menu .row .menu-item-medium{width:25px;height:20px}.top-menu .row .json-btn{border-radius:5px;cursor:pointer;padding:5px 4px 5px 11px;color:#fff;font-weight:bold;background-color:#46a0f5}.top-menu .row .json-paste-btn{border-radius:5px;cursor:pointer;padding:5px 4px 5px 8px;margin-right:10px;color:#fff;font-weight:bold;background-color:#46a0f5}.top-menu .row .reset-btn{border-radius:5px;cursor:pointer;padding:5px 11px 5px 11px;color:#fff;font-weight:bold;background-color:#46a0f5;margin-left:10px}.top-menu .row .reset-btn .i{width:20px;vertical-align:middle;font-size:16px}
.node{position:absolute;top:0px;left:0px;width:45px;height:45px;transition:all .5s ease}.node img{width:45px}.hide-node{display:none}.show-node{display:block}.ok-border{border:3px dashed #46a0f5}.noway-border{border:3px dashed #d31d18}
.mask{position:fixed;left:0px;top:0px;right:0px;bottom:0px;background-color:rgba(0,0,0,0.2);z-index:5}.mask .panel{position:absolute;width:600px;top:0px;right:0px;bottom:0px;background-color:#fff;box-shadow:-20px 25px 50px 0px #000;z-index:10;display:flex;flex-direction:column}.hide-mask{display:none}
.rc-slider-track{background-color:transparet}
input:focus,textarea:focus,select{outline:none}input.def,textarea,select{height:39px;border:1px solid #c5cbd2;background-color:transparent;width:97%;padding-left:10px;color:#000;font-size:16px;font-family:"Alegreya Sans SC"}textarea{height:80px}select{height:44px}::-webkit-input-placeholder{color:#4f5257}::-ms-input-placeholder{color:red}::-moz-placeholder{color:red}::-moz-placeholder{color:red}
.react-datepicker-wrapper{width:100%}.react-datepicker__input-container input{height:39px;border:1px solid #c5cbd2;background-color:transparent;width:97%;padding-left:10px;font-family:"Alegreya Sans SC";color:#000;font-size:16px;font-weight:bold}
.array-container{padding-left:20px;padding-top:20px}.array-container .array-container-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.array-container .array-container-header span{vertical-align:middle;font-size:14px;cursor:pointer}.array-container .array-container-body{height:100px;overflow:auto;border:1px solid #c5cbd2}.array-container .array-container-body .array-container-item{cursor:pointer;line-height:30px;padding-left:10px}.array-container .array-container-body .array-container-selected{background-color:#46a0f5}.array-container .array-container-body .array-container-item:hover{background-color:#e1e3e6}
.kill-chain-container{padding-left:20px;padding-top:20px}.kill-chain-container .kill-chain-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.kill-chain-container .kill-chain-header span{vertical-align:middle;font-size:14px;cursor:pointer}.kill-chain-container .kill-chain-body{overflow:auto;border:1px solid #c5cbd2}.kill-chain-container .kill-chain-body .kill-chain-options{display:flex;flex-direction:row}.kill-chain-container .kill-chain-body .kill-chain-options select{flex:.5;margin:10px 5px}.kill-chain-container .kill-chain-body .kill-chain-row{padding:5px 5px 5px 10px}.kill-chain-container .kill-chain-body .kill-chain-row .material-icons{vertical-align:middle;color:#d31d18;cursor:pointer}
.er-container{padding-left:20px;padding-top:20px}.er-container .er-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.er-container .er-header span{vertical-align:middle;font-size:14px;cursor:pointer}.er-container .er-body{overflow:auto;border:1px solid #c5cbd2;min-height:50px}.er-container .er-body .er-block{border:1px solid #c5cbd2;margin:10px}.er-container .er-body .er-block .er-block-row{display:flex;flex-direction:row}.er-container .er-body .er-block .er-block-row select{margin:10px 0px 10px 10px;flex:.5}.er-container .er-body .er-block .er-block-row div{padding:10px;flex:.5}.er-container .er-body .er-block .er-block-row span{vertical-align:middle;cursor:pointer}.er-container .er-body .er-block .er-block-row .remove{color:#d31d18}.er-container .er-body .er-block .er-block-row .add{padding-top:15px;color:#46a0f5}
.boolean{display:flex;flex-direction:row;line-height:30px;border:1px solid #c5cbd2;width:99%}.boolean div{flex:.5;text-align:center;cursor:pointer}.boolean .selected{background-color:#46a0f5}
.go-container{padding-left:20px;padding-top:20px;padding-right:10px}.go-container .go-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.go-container .go-header span{vertical-align:middle;font-size:14px;cursor:pointer}.go-container .go-body{overflow-x:hidden;border:1px solid #c5cbd2;min-height:50px;width:100%}.go-container .go-body .go-block-input{display:flex;flex-direction:row;padding:5px;width:100%}.go-container .go-body .go-block-input .input{margin:10px 0px 10px 10px;flex:.5}.go-container .go-body .go-block-input .add-container{width:50px;padding:20px 0px 0px 15px}.go-container .go-body .go-block-input .add-container span{cursor:pointer;color:#46a0f5;padding-left:5px}.go-container .go-body .go-block{margin:10px}.go-container .go-body .go-block .go-block-row{display:flex;flex-direction:row;padding:5px}.go-container .go-body .go-block .go-block-row input{margin:10px 0px 10px 10px;flex:.5}.go-container .go-body .go-block .go-block-row div{padding:10px;flex:.5}.go-container .go-body .go-block .go-block-row span{vertical-align:middle;cursor:pointer;padding-left:5px}.go-container .go-body .go-block .go-block-row .remove{color:#d31d18}.go-container .go-body .go-block .go-block-row .add{padding-top:15px;color:#46a0f5}
.ct-container{padding-left:20px;padding-top:20px;padding-right:10px}.ct-container .ct-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.ct-container .ct-header span{vertical-align:middle;font-size:14px;cursor:pointer}.ct-container .ct-body{overflow-x:hidden;border:1px solid #c5cbd2;min-height:50px;width:100%}.ct-container .ct-body .ct-block-input{display:flex;flex-direction:row;padding:5px;width:100%}.ct-container .ct-body .ct-block-input .input{margin:10px 0px 10px 10px;flex:1}.ct-container .ct-body .ct-block-input .input textarea{font-size:14px;font-family:tahoma}.ct-container .ct-body .ct-block-input .add-container{width:50px;padding:35px 0px 0px 8px}.ct-container .ct-body .ct-block-input .add-container span{cursor:pointer;color:#46a0f5;padding-left:5px}.ct-container .ct-body .ct-output{font-family:tahoma;font-size:14px;padding:0px 15px 15px 15px}.ct-container .ct-body .go-block{margin:10px}.ct-container .ct-body .go-block .go-block-row{display:flex;flex-direction:row;padding:5px}.ct-container .ct-body .go-block .go-block-row input{margin:10px 0px 10px 10px;flex:.5}.ct-container .ct-body .go-block .go-block-row div{padding:10px;flex:.5}.ct-container .ct-body .go-block .go-block-row span{vertical-align:middle;cursor:pointer;padding-left:5px}.ct-container .ct-body .go-block .go-block-row .remove{color:#d31d18}.ct-container .ct-body .go-block .go-block-row .add{padding-top:15px;color:#46a0f5}
.details{font-size:18px;display:flex;flex-direction:column;height:100%}.details .header{padding:20px;font-size:18px;height:40px;display:flex;flex-direction:row;background-color:#f1f3f5}.details .header .title{padding-top:5px;flex:1}.details .header .title img{vertical-align:middle;padding-right:5px}.details .header .delete{padding-left:7px;width:80px;display:flex;background-color:#46a0f5;border-radius:5px;padding-top:7px;color:#e1e3e6;cursor:pointer;font-size:14px}.details .header .delete span{padding-left:1px}.details .header .delete .text{padding-top:3px}.details .header .delete:hover{background-color:#d31d18}.details .body{overflow-y:scroll;overflow-x:hidden;flex:1}.details .body .item{padding-left:20px;padding-top:15px;font-weight:normal}.details .body .item .horizontal-slider{width:98%}.details .body .item .item-header{font-weight:bold;color:#46a0f5;padding-bottom:3px}.details .body .item .item-header span{padding-left:3px;vertical-align:middle;font-size:14px;cursor:pointer}.details .body .slider{padding-bottom:20px}.details .footer{height:40px}
button:focus{outline:none}button.def{width:auto;min-width:130px;height:30px;color:#e1e3e6;font-family:"Alegreya Sans SC";font-size:14px;border-color:transparent;cursor:pointer}button.def i{vertical-align:middle}button.disabled{background-color:rgba(128,128,128,0.8) !important;color:#c8c5c5 !important;cursor:auto}.disco-relationship{background:transparent;border:1px solid #243244 !important}.standard{background-color:rgba(70,160,245,0.8) !important}.confirm{background-color:rgba(83,129,60,0.8) !important}.caution{background-color:rgba(202,202,57,0.8) !important}.cancel{background-color:rgba(143,44,44,0.8) !important}
.json-viewer{display:flex;flex-direction:column;flex:1;height:100%}.json-viewer pre{flex:1;overflow:scroll;overflow-x:hidden;padding:5px 0px 0px 10px;font-family:courier;font-size:13px}.json-viewer .json-controls{display:flex;flex-direction:row;justify-content:flex-end;height:30px}
.json-paste{display:flex;flex-direction:column;flex:1;height:100%}.json-paste .paste-area{flex:1;padding:5px 0px 0px 10px;display:flex;flex-direction:column}.json-paste .paste-area div{flex:1}.json-paste .paste-area div textarea{height:97%;font-family:courier;font-size:12px}.json-paste .json-controls{display:flex;flex-direction:row;justify-content:flex-end;height:30px}
.relationship-picker{display:flex;flex-direction:column;flex:1;height:100%}.relationship-picker .header{padding-top:20px;padding-left:20px;padding-bottom:20px;height:35px}.relationship-picker .header img{vertical-align:middle}.relationship-picker .content{flex:1;overflow:auto}.relationship-picker .content .item{padding-left:20px;padding-top:10px;cursor:pointer;font-weight:bold;font-family:"Alegreya Sans SC";line-height:30px}.relationship-picker .content .item img{vertical-align:middle}.relationship-picker .content .item img.src-image{padding-right:5px}.relationship-picker .content .item img.target-image{padding-right:5px;padding-left:5px}.relationship-picker .content .item .rel-type{color:#46a0f5;padding-left:10px;padding-right:10px}
.growl{position:fixed;right:10px;top:10px;z-index:15;background-color:#46a0f5;color:#e1e3e6;padding:11px;border-radius:5px}
.canvas{position:fixed;top:50px;left:50px;bottom:50px;right:50px}
/*# sourceMappingURL=main.css.map*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

20
app/build/index.html Normal file
View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css?family=Alegreya+Sans+SC:bold|Inconsolata|Anton|Permanent+Marker|Roboto:900|Righteous|Ropa+Sans|Titillium+Web:400,700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Two+Tone" rel="stylesheet">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>STIX 2.1 Modeler</title>
<link href="css/vendors.css" rel="stylesheet"><link href="css/main.css" rel="stylesheet"></head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="app"></div>
<script type="text/javascript" src="js/runtime~main.5fefa56713048225fb7f.js" async></script><script type="text/javascript" src="js/vendors.d617334cf7af0cfc3361.js" async></script><script type="text/javascript" src="js/main.283c39973784419c7390.js" async></script></body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
!function(e){function r(r){for(var n,l,f=r[0],i=r[1],a=r[2],c=0,s=[];c<f.length;c++)l=f[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,f=1;f<t.length;f++){var i=t[f];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="";var f=window.webpackJsonp=window.webpackJsonp||[],i=f.push.bind(f);f.push=r,f=f.slice();for(var a=0;a<f.length;a++)r(f[a]);var p=i;t()}([]);
//# sourceMappingURL=runtime~main.5fefa56713048225fb7f.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,89 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/*! LeaderLine v1.0.5 (c) anseki https://anseki.github.io/leader-line/ */
/**!
* @fileOverview Kickass library to create and place poppers near their reference elements.
* @version 1.16.1
* @license
* Copyright (c) 2016 Federico Zivolo and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/** @license React v16.12.0
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.12.0
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v0.18.0
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.12.0
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

18231
app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

149
app/package.json Normal file
View File

@@ -0,0 +1,149 @@
{
"name": "minimal-webpack-react",
"version": "2.0.0",
"description": "Boilerplate for react and webpack",
"main": "index.js",
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --open",
"build": "cross-env NODE_ENV=production webpack",
"format": "prettier --write 'packages/**/*.js'",
"test": "jest --watchAll --coverage"
},
"lint-staged": {
"*.{js,jsx,scss,css,md}": [
"prettier --write --single-quote",
"eslint --fix",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"jest": {
"testEnvironment": "jsdom",
"moduleDirectories": [
"src",
"node_modules"
],
"moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.js",
"\\.(jpg|gif|ttf|eot|svg)$": "<rootDir>/__mocks__/fileMock.js"
},
"transform": {
"^.+\\.(js|jsx)$": "babel-jest",
".+\\.(css|styl|less|sass|scss)$": "<rootDir>/node_modules/jest-css-modules-transform"
},
"setupTestFrameworkScriptFile": "<rootDir>/setupTests.js",
"moduleFileExtensions": [
"css",
"scss",
"js",
"json",
"jsx"
]
},
"repository": "https://github.com/HashemKhalifa/webpack-react-boilerplate",
"author": "HashemKhalifa",
"license": "ISC",
"private": false,
"engines": {
"node": ">=8",
"npm": ">=3"
},
"dependencies": {
"@babel/plugin-transform-react-constant-elements": "7.7.4",
"@babel/plugin-transform-react-inline-elements": "7.7.4",
"axios": "^0.19.0",
"babel-plugin-transform-react-pure-class-to-function": "1.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.6.1",
"deepmerge": "^4.2.2",
"lazy-route": "^1.0.7",
"leader-line": "^1.0.5",
"lodash": "4.17.15",
"mobx-react": "^6.1.4",
"moment": "^2.24.0",
"prop-types": "15.7.2",
"rc-slider": "^9.3.0",
"react": "16.12.0",
"react-datepicker": "^2.16.0",
"react-dom": "16.12.0",
"react-hot-loader": "4.12.18",
"react-router-dom": "^5.1.2",
"react-tooltip": "^4.2.6",
"rfx-core": "^1.6.2"
},
"resolutions": {
"babel-core": "7.0.0-bridge.0"
},
"devDependencies": {
"@babel/core": "7.7.7",
"@babel/plugin-proposal-class-properties": "7.7.4",
"@babel/plugin-proposal-decorators": "7.7.4",
"@babel/plugin-proposal-export-namespace-from": "7.7.4",
"@babel/plugin-proposal-function-sent": "7.7.4",
"@babel/plugin-proposal-json-strings": "7.7.4",
"@babel/plugin-proposal-numeric-separator": "7.7.4",
"@babel/plugin-proposal-throw-expressions": "7.7.4",
"@babel/plugin-syntax-dynamic-import": "7.7.4",
"@babel/plugin-syntax-import-meta": "7.7.4",
"@babel/plugin-transform-runtime": "7.7.6",
"@babel/preset-env": "7.7.7",
"@babel/preset-react": "7.7.4",
"@babel/register": "7.7.7",
"@babel/runtime": "7.7.7",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.0.3",
"babel-jest": "24.9.0",
"babel-loader": "8.0.6",
"babel-plugin-lodash": "3.3.4",
"babel-preset-react-optimize": "1.0.1",
"browserslist": "4.8.2",
"clean-webpack-plugin": "3.0.0",
"connect-history-api-fallback": "1.6.0",
"copy-webpack-plugin": "^6.0.1",
"cross-env": "6.0.3",
"css-loader": "3.4.0",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.2",
"eslint": "6.8.0",
"eslint-config-airbnb": "18.0.1",
"eslint-config-prettier": "6.9.0",
"eslint-loader": "3.0.3",
"eslint-plugin-import": "2.19.1",
"eslint-plugin-jest": "23.1.1",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-prettier": "3.1.2",
"eslint-plugin-react": "7.17.0",
"eslint-watch": "6.0.1",
"file-loader": "5.0.2",
"html-webpack-plugin": "^3.2.0",
"husky": "3.1.0",
"identity-obj-proxy": "3.0.0",
"jest": "24.9.0",
"jest-css-modules-transform": "3.1.0",
"jest-enzyme": "7.1.2",
"jest-fetch-mock": "3.0.0",
"jsdom": "15.2.1",
"koa-connect": "2.0.1",
"lint-staged": "9.5.0",
"mini-css-extract-plugin": "0.9.0",
"mobx": "^5.15.1",
"node-sass": "^4.14.1",
"npm-check-updates": "4.0.1",
"optimize-css-assets-webpack-plugin": "5.0.3",
"prettier": "1.19.1",
"pretty-quick": "2.0.1",
"sass-loader": "8.0.0",
"script-ext-html-webpack-plugin": "2.1.4",
"skeleton-loader": "^2.0.0",
"style-loader": "1.1.2",
"terser-webpack-plugin": "2.3.1",
"webpack": "4.41.5",
"webpack-cli": "3.3.10",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "4.2.2"
}
}

9
app/renovate.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": [
"config:base"
],
"automerge": true,
"major": {
"automerge": false
}
}

24
app/setupTests.js Normal file
View File

@@ -0,0 +1,24 @@
import { JSDOM } from 'jsdom';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
global.fetch = require('jest-fetch-mock');
const exposedProperties = ['window', 'navigator', 'document'];
const { document } = new JSDOM('').window;
global.document = document;
global.window = document.defaultView;
global.HTMLElement = window.HTMLElement;
global.HTMLAnchorElement = window.HTMLAnchorElement;
Object.keys(document.defaultView).forEach(property => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property);
global[property] = document.defaultView[property];
}
});
global.navigator = {
userAgent: 'node.js',
};

BIN
app/src/.DS_Store vendored Normal file

Binary file not shown.

36
app/src/App.jsx Normal file
View File

@@ -0,0 +1,36 @@
import React, { Component } from 'react';
import {toJS} from 'mobx';
import {observer, inject} from 'mobx-react';
import { hot } from 'react-hot-loader/root';
import {withRouter, Route, NavLink, useParams} from "react-router-dom";
import LazyRoute from "lazy-route";
import defaultStyle from "./app.scss";
import Canvas from './components/Canvas';
@withRouter
@inject("store")
@observer
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="content">
<Route
exact
path={'/'}
render={(props) =>
<Canvas {...props}/>}
/>
</div>
)
}
}
export default hot(App);

12
app/src/app.scss Normal file
View File

@@ -0,0 +1,12 @@
@import './defaults';
body, html, #app {
margin: 0px;
padding: 0px;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
font-family: $default-font-family;
}

View File

@@ -0,0 +1,460 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { toJS } from "mobx";
import PropTypes from "prop-types";
import moment from "moment";
import Menu from "./menus/Menu";
import TopMenu from "./menus/TopMenu";
import Node from "./Node";
import LeaderLine from "leader-line";
import Details from "./Details";
import JsonViewer from "./JsonViewer";
import JsonPaste from "./JsonPaste";
import RelationshipPicker from "./RelationshipPicker";
import Growl from "./ui/growl/Growl";
import canvasStyle from "./canvas.scss";
@inject("store")
@observer
export default class Canvas extends React.Component {
constructor(props) {
super(props);
this.store = this.props.store.appStore;
this.generateNodeID = this.generateNodeID.bind(this);
this.onDragStartHandler = this.onDragStartHandler.bind(this);
this.onDragEndHandler = this.onDragEndHandler.bind(this);
this.onDragOverHandler = this.onDragOverHandler.bind(this);
this.onDropHandler = this.onDropHandler.bind(this);
this.onClickHandler = this.onClickHandler.bind(this);
this.onDragOverNodeHandler = this.onDragOverNodeHandler.bind(this);
this.onDropOnNodeHandler = this.onDropOnNodeHandler.bind(this);
this.onClickShowJsonHandler = this.onClickShowJsonHandler.bind(this);
this.onClickHideJsonHandler = this.onClickHideJsonHandler.bind(this);
this.onClickHideRelPickerHandler = this.onClickHideRelPickerHandler.bind(this);
this.onClickHideDetailsHandler = this.onClickHideDetailsHandler.bind(this);
this.onClickSelectRelHandler = this.onClickSelectRelHandler.bind(this);
this.onClickShowGrowlHandler = this.onClickShowGrowlHandler.bind(this);
this.onChangeNodeHandler = this.onChangeNodeHandler.bind(this);
this.onChangeDateHandler = this.onChangeDateHandler.bind(this);
this.onMessageTimerHandler = this.onMessageTimerHandler.bind(this);
this.onClickArrayHandler = this.onClickArrayHandler.bind(this);
this.onChangeSliderHandler = this.onChangeSliderHandler.bind(this);
this.onChangeCSVHandler = this.onChangeCSVHandler.bind(this);
this.onClickBooleanHandler = this.onClickBooleanHandler.bind(this);
this.onChangePhaseHandler = this.onChangePhaseHandler.bind(this);
this.onClickRemovePhaseHander = this.onClickRemovePhaseHander.bind(this);
this.onClickAddObjectHandler = this.onClickAddObjectHandler.bind(this);
this.onClickDeletePropertyHandler = this.onClickDeletePropertyHandler.bind(this);
this.onClickResetHandler = this.onClickResetHandler.bind(this);
this.onChangeERHandler = this.onChangeERHandler.bind(this);
this.onChangeGenericObjectHandler = this.onChangeGenericObjectHandler.bind(this);
this.onClickAddGenericObjectHandler = this.onClickAddGenericObjectHandler.bind(this);
this.onClickDeleteGenericObjectHandler = this.onClickDeleteGenericObjectHandler.bind(this);
this.onClickAddTextHandler = this.onClickAddTextHandler.bind(this);
this.resetBorders = this.resetBorders.bind(this);
this.onClickHideJsonPasteHandler = this.onClickHideJsonPasteHandler.bind(this);
this.onClickShowJsonPasteHandler = this.onClickShowJsonPasteHandler.bind(this);
this.onChangeJSONPasteHandler = this.onChangeJSONPasteHandler.bind(this);
this.onClickJSONPasteHandler = this.onClickJSONPasteHandler.bind(this);
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
}
componentWillMount() {
document.addEventListener("dragover", (e) => {
this.store.setMousePosition(e);
}, false);
}
componentWillUnmount() {
document.removeEventListener("dragover", (e) => {
}, false);
}
onClickHandler(node) {
this.store.showDetails = true;
this.store.setSelected(node);
}
onClickHideDetailsHandler() {
this.store.showDetails = false;
}
onClickHideJsonPasteHandler() {
this.store.showJSONPaste = false;
}
onClickShowJsonPasteHandler() {
this.store.showJSONPaste = true;
}
onClickShowGrowlHandler(message) {
this.store.growlMessage = message;
this.store.showGrowl = true;
}
transition(id, sticky, random) {
const canvas = document.getElementById("canvas");
const node = document.getElementById(id);
const calculate = (min, max) => {
return Math.random() * ((max-100) - min) + min;
};
const bounds = {
top: canvas.offsetTop+50,
bottom: (canvas.offsetTop-50) + canvas.clientHeight,
left: canvas.offsetLeft+50,
right: (canvas.offsetLeft-50) + canvas.clientWidth
};
if (node) {
// the first time the node is dropped,
// we have to do some class manipulation.
if (!sticky) {
node.classList.add('show-node');
node.classList.remove('hide-node');
}
let clientX = this.store.mousePosition.clientX;
let clientY = this.store.mousePosition.clientY;
if (clientX < bounds.left) {
clientX = bounds.left + 50;
} else if (clientX > bounds.right) {
clientX = bounds.right - 50;
}
if (clientY < bounds.top) {
clientY = bounds.top + 50;
} else if (clientX > bounds.right) {
clientY = bounds.bottom - 50;
}
if (random) {
node.style.left = `${calculate(bounds.left, bounds.right)}px`;
node.style.top = `${calculate(bounds.top, bounds.bottom)}px`;
} else {
node.style.left = `${clientX+50}px`;
node.style.top = `${clientY-50}px`;
}
}
}
onClickDeleteHandler() {
this.store.deleteSelectedNode();
}
resetBorders() {
this.store.failedRelationship = undefined;
}
onChangeNodeHandler(event) {
this.store.editNodeValues(event);
}
mutateOnEvent(property, value) {
const event = {
currentTarget: {
name: property,
value: value
}
}
this.onChangeNodeHandler(event);
}
onChangeDateHandler(property, datetime) {
const value = moment(datetime).format()
this.onChangeNodeHandler(property, value);
}
onClickArrayHandler(property, value) {
this.mutateOnEvent(property, value);
}
onChangeSliderHandler(property, value) {
this.mutateOnEvent(property, value);
}
onClickBooleanHandler(property, value) {
this.mutateOnEvent(property, value);
}
onChangePhaseHandler(property, value) {
this.mutateOnEvent(property, value);
}
onClickAddTextHandler(property, value) {
this.mutateOnEvent(property, value);
}
onChangeGenericObjectHandler(property, event) {
this.mutateOnEvent(property, event.currentTarget.value);
}
onClickRemovePhaseHander(property, value) {
this.store.removeKillChainPhase(value);
}
onChangeCSVHandler(event) {
this.store.editCSVInput(event);
}
onClickSelectRelHandler(rel) {
const persisted = this.store.manuallySelectRelationship(rel);
// if the node was persisted, we will want to move it
// to a random place on the screen.
if (persisted) {
this.drawEdge(persisted);
setTimeout(() => {
this.transition(this.store.dragging.id, false);
}, 500);
}
}
onClickAddObjectHandler(field) {
this.store.addDefaultObject(field);
}
onClickDeletePropertyHandler(select, idx) {
this.store.deleteERObjectProperty(select, idx);
}
onChangeERHandler(input, select, idx) {
this.store.changeERValue(input, select, idx);
}
generateNodeID(prefix) {
return this.store.generateNodeID(prefix);
}
onClickShowJsonHandler() {
this.store.showJSON = true;
}
onClickHideJsonHandler() {
this.store.showJSON = false;
}
onChangeJSONPasteHandler(event) {
this.store.pasteBundle = event.currentTarget.value;
}
onClickJSONPasteHandler() {
this.store.loadBundleFromPaste();
setTimeout(() => {
this.store.nodes.map(n => {
this.transition(n.id, false, true);
});
}, 200);
this.store.edges.map(e => {
this.drawEdge(e);
});
this.store.calculateLineDrag();
}
onClickHideRelPickerHandler() {
this.store.showRelPicker = false;
}
onDragStartHandler(event) {
let node = JSON.parse(event.dataTransfer.getData("node"));
this.store.dragging = node;
}
onDragEndHandler(event) {
this.store.calculateLineDrag();
}
// Drag over canvas
onDragOverHandler(event) {
event.preventDefault();
}
onMessageTimerHandler() {
setTimeout(() => {
this.store.showGrowl = false;
}, 2500);
}
// Drop on canvas
onDropHandler(event) {
event.preventDefault();
let node = JSON.parse(event.dataTransfer.getData("node"));
if (node.properties.type.enum[0] === "observable") {
this.store.growlMessage = "Observables can only be dropped onto existing STIX objects.";
this.store.showGrowl = true;
this.transition(node.id, true);
} else {
const persisted = this.store.persistNode(node);
// if the node was persisted, we will want to move it
// to a random place on the screen.
if (persisted) {
setTimeout(() => {
this.transition(node.id, false);
}, 200);
} else {
this.transition(node.id, true);
}
}
}
//Drag over another node on the screen
onDragOverNodeHandler(nodeOnScreen) {
this.store.canRelate(nodeOnScreen);
}
//Drop on another node on the screen
onDropOnNodeHandler(nodeOnScreen) {
const persisted = this.store.addNodeWithRelationship(nodeOnScreen);
if (persisted) {
this.drawEdge(persisted);
} else {
setTimeout(() => {
this.transition(this.store.dragging.id, false);
}, 200);
}
}
onClickAddGenericObjectHandler(field, o) {
this.store.addGenericObject(field, o);
}
onClickDeleteGenericObjectHandler(field, key) {
this.store.deleteGenericObject(field, key);
}
onClickResetHandler() {
this.store.reset();
}
drawEdge(persisted) {
let s = persisted.source_ref;
let t = persisted.target_ref;
setTimeout(() => {
this.transition(this.store.dragging.id, false);
let line = new LeaderLine(
document.getElementById(s),
document.getElementById(t)
)
let labelProps = LeaderLine.pathLabel(
persisted.relationship_type, {
color: '#484d59',
outlineColor: '#484d59',
fontWeight: '1px',
letterSpacing: '2px',
lineOffset: '13px'
}
);
line.setOptions({
startSocket: 'disc',
endSocket: 'disc',
middleLabel: labelProps,
dash: {
animation: false
}
});
this.store.lines.push(line);
}, 200);
}
render() {
const nodes = this.store.nodes;
const elements = Array.from(document.getElementsByTagName("svg"));
// This ensures the edges drawn will not
// interfere with the slide out panels.
elements.map(element => {
element.style.zIndex = "-1";
});
return (
<div id="canvas"
className="canvas"
onDragOver={this.onDragOverHandler}
onDrop={this.onDropHandler}>
{
nodes.map(n => {
return <Node key={n.id}
n={n}
failedRelationship={this.store.failedRelationship}
resetBorders={this.resetBorders}
onClickHandler={this.onClickHandler}
onDragStartHandler={this.onDragStartHandler}
onDragEndHandler={this.onDragEndHandler}
onDragOverNodeHandler={this.onDragOverNodeHandler}
onDropOnNodeHandler={this.onDropOnNodeHandler} />
})
}
<TopMenu onClickShowJsonHandler={this.onClickShowJsonHandler}
onClickShowJsonPasteHandler={this.onClickShowJsonPasteHandler}
onClickHideJsonHandler={this.onClickHideJsonHandler}
onClickResetHandler={this.onClickResetHandler} />
<Menu
objects={this.store.objects}
onDragStartHandler={this.onDragStartHandler}
generateNodeID={this.generateNodeID} />
<Details show={this.store.showDetails}
node={this.store.selected}
onClickHideHandler={this.onClickHideDetailsHandler}
onChangeNodeHandler={this.onChangeNodeHandler}
onChangeDateHandler={this.onChangeDateHandler}
onClickArrayHandler={this.onClickArrayHandler}
onChangeSliderHandler={this.onChangeSliderHandler}
onChangeCSVHandler={this.onChangeCSVHandler}
onClickBooleanHandler={this.onClickBooleanHandler}
onChangePhaseHandler={this.onChangePhaseHandler}
onClickRemovePhaseHander={this.onClickRemovePhaseHander}
onClickAddObjectHandler={this.onClickAddObjectHandler}
onChangeERHandler={this.onChangeERHandler}
onClickDeletePropertyHandler={this.onClickDeletePropertyHandler}
onChangeGenericObjectHandler={this.onChangeGenericObjectHandler}
onClickAddGenericObjectHandler={this.onClickAddGenericObjectHandler}
onClickDeleteGenericObjectHandler={this.onClickDeleteGenericObjectHandler}
onClickAddTextHandler={this.onClickAddTextHandler}
onClickDeleteHandler={this.onClickDeleteHandler} />
<JsonViewer show={this.store.showJSON}
json={this.store.bundle}
onClickHideHandler={this.onClickHideJsonHandler}
onClickShowGrowlHandler={this.onClickShowGrowlHandler} />
<JsonPaste show={this.store.showJSONPaste}
json={this.store.pasteBundle}
onClickHideHandler={this.onClickHideJsonPasteHandler}
onChangeJSONPasteHandler={this.onChangeJSONPasteHandler}
onClickJSONPasteHandler={this.onClickJSONPasteHandler}
value={this.store.pasteBundle} />
<RelationshipPicker show={this.store.showRelPicker}
relationships={this.store.relationships}
onClickHideHandler={this.onClickHideRelPickerHandler}
onClickSelectRelHandler={this.onClickSelectRelHandler} />
<Growl message={this.store.growlMessage}
show={this.store.showGrowl}
timer={this.onMessageTimerHandler} />
</div>
)
}
}

View File

@@ -0,0 +1,229 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { toJS } from "mobx";
import Tooltip from "react-tooltip";
import PropTypes from "prop-types";
import Panel from "./ui/panel/Panel";
import Slider from "./ui/inputs/Slider";
import Text from "./ui/inputs/Text";
import TextArea from "./ui/inputs/TextArea";
import DateTime from "./ui/inputs/DateTime";
import ArraySelector from "./ui/inputs/ArraySelector";
import KillChain from "./ui/complex/KillChain";
import ExternalReferences from "./ui/complex/ExternalReferences";
import CSVInput from "./ui/inputs/CSVInput";
import Boolean from "./ui/inputs/Boolean";
import GenericObject from "./ui/complex/GenericObject";
import ConfirmTextarea from "./ui/complex/ConfirmTextarea";
import uuid from "uuid";
import detailsStyle from './details.scss';
function importAll(r) {
let images = {};
r.keys().map((item, index) => { images[item.replace('./', '')] = r(item); });
return images;
}
const images = importAll(require.context('../imgs', false, /\.(png|jpe?g|svg)$/));
@observer
export default class Details extends React.Component {
constructor(props) {
super(props);
this.onChangeHandler = this.onChangeHandler.bind(this);
this.onChangeDateHandler = this.onChangeDateHandler.bind(this);
}
onChangeHandler(event) {
this.props.onChangeNodeHandler(event);
}
onChangeDateHandler(property, datetime) {
this.props.onChangeDateHandler(property, datetime);
}
render() {
const node = toJS(this.props.node);
let props = {};
let img;
let details = [];
let deleteIcon = <span className="material-icons">delete_forever</span>;
if (node.properties) {
props = node.properties;
img = <img src={images[node.img].default} width="30" />;
}
for (let prop in props) {
let header = <div className="item-header">{prop}
<span data-tip={props[prop].description} className="material-icons">info</span>
<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}
onChange={this.onChangeHandler} />
</div>
</div>
break;
case "dts":
control = <div className="item" key={prop}>
{header}
<div className="item-value">
<DateTime name={prop}
selected={props[prop].value}
onChange={this.onChangeDateHandler} />
</div>
</div>
break;
case "array":
control = <ArraySelector vocab={props[prop].vocab}
key={prop}
field={prop}
value={props[prop].value}
description={props[prop].description}
onClickHandler={this.props.onClickArrayHandler} />
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
case "object":
control = <div className="item" key={prop}>
{header}
</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}
onChangeHandler={this.props.onChangeSliderHandler} />
</div>
</div>
break;
case "csv":
control = <div className="item" key={prop}>
{header}
<div className="item-value">
<CSVInput key={prop}
name={prop}
value={props[prop].value}
onChangeHandler={this.props.onChangeCSVHandler} />
</div>
</div>
break;
case "killchain":
control = <KillChain vocab={props[prop].vocab}
node={node}
key={prop}
field={prop}
value={props[prop].value}
description={props[prop].description}
onChangeHandler={this.props.onChangePhaseHandler}
onClickRemoveHandler={this.props.onClickRemovePhaseHander} />
break;
case "externalrefs":
control = <ExternalReferences node={node}
key={prop}
field={prop}
value={props[prop].value}
description={props[prop].description}
onClickAddObjectHandler={this.props.onClickAddObjectHandler}
onChangeERHandler={this.props.onChangeERHandler}
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}
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}
onChange={this.onChangeHandler} />
</div>
</div>
break;
case "genericobject":
control = <GenericObject name={prop}
value={props[prop].value}
description={props[prop].description}
key={uuid()}
field={prop}
onClickAddObjectHandler={this.props.onClickAddGenericObjectHandler}
onClickDeleteObjectHandler={this.props.onClickDeleteGenericObjectHandler}
onChangeHandler={this.props.onChangeGenericObjectHandler} />
break;
case "confirmtextarea":
control = <ConfirmTextarea name={prop}
value={props[prop].value}
description={props[prop].description}
key={uuid()}
field={prop}
onClickAddTextHandler={this.props.onClickAddTextHandler} />
break;
}
details.push(
control
)
}
return (
<Panel show={this.props.show} onClickHideHandler={this.props.onClickHideHandler}>
<div className="details">
<div className="header">
<div className="title">{img} {node.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>
</div>
</Panel>
)
}
}

View File

@@ -0,0 +1,37 @@
import React from "react";
import { inject, observer } from "mobx-react";
import PropTypes from "prop-types";
import Panel from "./ui/panel/Panel";
import Button from "./ui/button/Button";
import TextArea from "./ui/inputs/TextArea";
import detailsStyle from './json-paste.scss';
@observer
export default class JsonPaste extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Panel show={this.props.show}
onClickHideHandler={this.props.onClickHideHandler}>
<div className="json-paste">
<div className="paste-area">
<TextArea onChange={this.props.onChangeJSONPasteHandler} value={this.props.value} />
</div>
<div className="json-controls">
<Button cls="def standard json-copy" text="Load" onClick={this.props.onClickJSONPasteHandler}>
<i className="material-icons">add</i>
</Button>
</div>
</div>
</Panel>
)
}
}

View File

@@ -0,0 +1,53 @@
import React from "react";
import { inject, observer } from "mobx-react";
import PropTypes from "prop-types";
import Panel from "./ui/panel/Panel";
import Button from "./ui/button/Button";
import detailsStyle from './json-viewer.scss';
@observer
export default class JsonViewer extends React.Component {
constructor(props) {
super(props);
this.onClickCopyJSONHandler = this.onClickCopyJSONHandler.bind(this);
}
onClickCopyJSONHandler() {
let me = this;
let range = document.createRange();
let message = "JSON Copied to Clipboard";
range.selectNode(
document.getElementById("json-content")
);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand("copy");
window.getSelection().removeAllRanges();
this.props.onClickShowGrowlHandler(message);
}
render() {
return (
<Panel show={this.props.show}
onClickHideHandler={this.props.onClickHideHandler}>
<div className="json-viewer">
<pre id="json-content">{JSON.stringify(this.props.json, null, 2)}</pre>
<div className="json-controls">
<Button cls="def standard json-copy" text="Copy" onClick={this.onClickCopyJSONHandler}>
<i className="material-icons">file_copy</i>
</Button>
</div>
</div>
</Panel>
)
}
}

View File

@@ -0,0 +1,9 @@
let LeaderLine = require("leader-line");
//module.exports = LeaderLine;
//const singleton = new LeaderLine();
//export default LeaderLine

View File

@@ -0,0 +1,88 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { toJS } from "mobx";
import PropTypes from "prop-types";
import classNames from "classnames";
import nodeStyle from './node.scss';
function importAll(r) {
let images = {};
r.keys().map((item, index) => { images[item.replace('./', '')] = r(item); });
return images;
}
const images = importAll(require.context('../imgs', false, /\.(png|jpe?g|svg)$/));
@observer
export default class Node extends React.Component {
constructor(props) {
super(props);
this.onDragStartHandler = this.onDragStartHandler.bind(this);
this.onDragOverHandler = this.onDragOverHandler.bind(this);
this.onDropHandler = this.onDropHandler.bind(this);
this.onDragEndHandler = this.onDragEndHandler.bind(this);
this.onDragLeaveHandler = this.onDragLeaveHandler.bind(this);
}
onClickHandler(node) {
this.props.onClickHandler(node);
}
onDragStartHandler(event) {
event.dataTransfer.setData("node", JSON.stringify(this.props.n));
this.props.onDragStartHandler(event);
}
onDragEndHandler(event) {
this.props.onDragEndHandler(event);
}
onDragOverHandler(event) {
event.preventDefault();
this.props.onDragOverNodeHandler(this.props.n);
}
onDropHandler(event) {
event.preventDefault();
this.props.onDropOnNodeHandler(this.props.n);
this.props.resetBorders();
event.stopPropagation();
}
onDragLeaveHandler(event) {
this.props.resetBorders();
}
render() {
const node = this.props.n;
let hide = true;
const cls = classNames({
node: true,
'hide-node': hide
});
let display = node.id.split("--")[0];
if (node.properties.name && node.properties.name.value) {
display = node.properties.name.value;
}
return (
<div id={node.id}
className={cls}
draggable="true"
onClick={() => this.onClickHandler(node)}
onDragStart={this.onDragStartHandler}
onDragEnd={this.onDragEndHandler}
onDragOver={this.onDragOverHandler}
onDrop={this.onDropHandler}
onDragLeave={this.onDragLeaveHandler}>
<img src={images[node.img].default} draggable="false" /> {display}
</div>
)
}
}

View File

@@ -0,0 +1,70 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { toJS } from "mobx";
import PropTypes from "prop-types";
import Panel from "./ui/panel/Panel";
import relationshipPickerStyle from './relationship-picker.scss';
function importAll(r) {
let images = {};
r.keys().map((item, index) => { images[item.replace('./', '')] = r(item); });
return images;
}
const images = importAll(require.context('../imgs', false, /\.(png|jpe?g|svg)$/));
@observer
export default class RelationshipPicker extends React.Component {
constructor(props) {
super(props);
}
onClickSelectRelHandler(relationship) {
this.props.onClickSelectRelHandler(relationship);
}
render() {
return (
<Panel show={this.props.show}
onClickHideHandler={this.props.onClickHideHandler}>
<div className="relationship-picker">
<div className="header"><img src={images["relationship.png"].default} width="20" /> Possible Relationships</div>
<div className="content">
{
this.props.relationships.map(relationship => {
let src = relationship.source_ref.split("--")[0];
let target = relationship.target_ref.split("--")[0];
let srcImg = `${src}.png`;
let targetImg = `${target}.png`;
if (!images[srcImg]) {
images[srcImg] = {};
images[srcImg].default = "imgs/3536f3f7f55d746d1a9eac4ca5073246.png";
}
if (!images[targetImg]) {
images[targetImg] = {};
images[targetImg].default = "imgs/3536f3f7f55d746d1a9eac4ca5073246.png";
}
if (relationship.subTarget) {
target = relationship.subTarget;
}
return <div className="item" key={relationship.id}
onClick={() => this.onClickSelectRelHandler(relationship)}>
<img className="src-image" src={images[srcImg].default} width="20" /> {src}
<span className="rel-type"> {relationship.relationship_type} </span>
{target} <img className="target-image" src={images[targetImg].default} width="20" />
</div>
})
}
</div>
</div>
</Panel>
)
}
}

View File

@@ -0,0 +1,7 @@
.canvas {
position: fixed;
top: 50px;
left: 50px;
bottom: 50px;
right: 50px;
}

View File

@@ -0,0 +1,93 @@
@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;
.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;
}
}

View File

@@ -0,0 +1,29 @@
.json-paste {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
.paste-area {
flex: 1;
padding: 5px 0px 0px 10px;
display: flex;
flex-direction: column;
div {
flex: 1;
textarea {
height: 97%;
font-family: courier;
font-size: 12px;
}
}
}
.json-controls {
display: flex;
flex-direction: row;
justify-content: flex-end;
height: 30px;
}
}

View File

@@ -0,0 +1,22 @@
.json-viewer {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
pre {
flex: 1;
overflow: scroll;
overflow-x: hidden;
padding: 5px 0px 0px 10px;
font-family: courier;
font-size: 13px;
}
.json-controls {
display: flex;
flex-direction: row;
justify-content: flex-end;
height: 30px;
}
}

View File

@@ -0,0 +1,35 @@
import React from "react";
import { inject, observer } from "mobx-react";
import PropTypes from "prop-types";
import MenuItem from "./MenuItem";
import menuStyle from './menu.scss';
@observer
export default class Menu extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="menu">
<div className="row">
{
this.props.objects.map((o, i) => {
if (o.active) {
return <MenuItem
key={i}
o={o}
onDragStartHandler={this.props.onDragStartHandler}
generateNodeID={this.props.generateNodeID} />
}
})
}
</div>
</div>
)
}
}

View File

@@ -0,0 +1,44 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { toJS } from "mobx";
import PropTypes from "prop-types";
import menuStyle from "./menu.scss";
function importAll(r) {
let images = {};
r.keys().map((item, index) => { images[item.replace('./', '')] = r(item); });
return images;
}
const images = importAll(require.context('../../imgs', false, /\.(png|jpe?g|svg)$/));
@observer
export default class Menu extends React.Component {
constructor(props) {
super(props);
this.onDragStartHandler = this.onDragStartHandler.bind(this);
}
onDragStartHandler(event) {
const id = this.props.generateNodeID(this.props.o.prefix);
this.props.o.id = id;
event.dataTransfer.setData("node", JSON.stringify(this.props.o));
this.props.onDragStartHandler(event);
}
render() {
const o = this.props.o;
return (
<div className="menu-item"
draggable="true"
onDragStart={this.onDragStartHandler}>
<img src={images[o.img].default} draggable="false" />
</div>
)
}
}

View File

View File

@@ -0,0 +1,35 @@
import React from "react";
import { inject, observer } from "mobx-react";
import Tooltip from "react-tooltip";
import menuStyle from './top-menu.scss';
@observer
export default class TopMenu extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="top-menu">
<div className="row">
<div data-tip={"Paste JSON"} className="json-paste-btn menu-item-medium" onClick={this.props.onClickShowJsonPasteHandler}>
{"{ + }"}
</div>
<div data-tip={"View JSON"} className="json-btn menu-item-small" onClick={this.props.onClickShowJsonHandler}>
{"{ }"}
</div>
<div data-tip={"Clear JSON"} className="reset-btn menu-item" onClick={this.props.onClickResetHandler}>
<span className="i material-icons">refresh</span> Reset
</div>
<Tooltip />
</div>
</div>
)
}
}

View File

@@ -0,0 +1,36 @@
@import '../../defaults';
.menu {
position: fixed;
bottom: 20px;
right: 25px;
.row {
display: flex;
flex-direction: row;
.menu-item {
width: 40px;
padding-right: 10px;
cursor: pointer;
img {
width: 40px;
}
}
.obs {
width: 40px;
height: 40px;
border-radius: 5px;
background-color: $default-active-bg;
div {
padding-top: 10px;
padding-left: 7px;
color: $light-font-0;
cursor: pointer;
}
}
}
}

View File

@@ -0,0 +1,58 @@
@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;
}
.json-btn {
border-radius: 5px;
cursor: pointer;
padding: 5px 4px 5px 11px;
color: #fff;
font-weight: bold;
background-color: $default-active-bg;
}
.json-paste-btn {
border-radius: 5px;
cursor: pointer;
padding: 5px 4px 5px 8px;
margin-right: 10px;
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;
}
}
}
}

View File

@@ -0,0 +1,30 @@
@import '../defaults';
.node {
position: absolute;
top: 0px;
left: 0px;
width: 45px;
height: 45px;
transition: all .5s ease;
img {
width: 45px;
}
}
.hide-node {
display: none;
}
.show-node {
display: block;
}
.ok-border {
border: 3px dashed $default-active-bg;
}
.noway-border {
border: 3px dashed $error-font;
}

View File

@@ -0,0 +1,52 @@
@import "../defaults";
.relationship-picker {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
.header {
padding-top: 20px;
padding-left: 20px;
padding-bottom: 20px;
height: 35px;
img {
vertical-align: middle;
}
}
.content {
flex: 1;
overflow: auto;
.item {
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;
}
}
}
}

View File

@@ -0,0 +1,43 @@
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import buttonStyle from './button.scss';
export default class Button extends React.Component {
constructor(props) {
super(props);
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler() {
if (this.props.onClick) {
this.props.onClick();
}
}
render() {
const clickHandler = this.props.disabled ? undefined : this.onClickHandler;
const classMap = {
def: true,
disabled: this.props.disabled,
};
if (this.props.cls) {
classMap[this.props.cls] = true;
}
const classes = classNames(classMap);
return (
<div>
<button className={classes} onClick={clickHandler} >
{this.props.children} {this.props.text}
</button>
</div>
)
}
}
Button.propTypes = {
cls: PropTypes.string,
disabled: PropTypes.bool,
onClick: PropTypes.func
};

View File

@@ -0,0 +1,48 @@
@import '../../../defaults';
button:focus {
outline: none;
}
button.def {
width: auto;
min-width: 130px;
height: 30px;
color: $light-font-0;
font-family: $default-font-family;
font-size: 14px;
border-color: transparent;
cursor: pointer;
i {
vertical-align: middle;
}
}
button.disabled {
background-color: rgba(128,128,128,.8) !important;
color: $gray-font-0 !important;
cursor: auto;
}
.disco-relationship {
background: transparent;
border: 1px solid #243244 !important;
}
.standard {
background-color: rgba(70,160,245,.8) !important;
}
.confirm {
background-color: rgba(83,129,60,.8) !important;
}
.caution {
background-color: rgba(202,202,57,.8) !important;
}
.cancel {
background-color: rgba(143,44,44,.8) !important;
}

View File

@@ -0,0 +1,79 @@
import React from "react";
import PropTypes from "prop-types";
import {observer, inject} from "mobx-react";
import classNames from "classnames";
import Tooltip from "react-tooltip";
import TextArea from "../inputs/TextArea";
import uuid from "uuid";
import confirmTextareaStyle from "./confirmtextarea.scss";
@inject("store")
@observer
export default class ConfirmTextarea extends React.Component {
constructor(props) {
super(props);
this.onChangeInputHandler = this.onChangeInputHandler.bind(this);
this.onClickAddObjectHandler = this.onClickAddObjectHandler.bind(this);
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
this.state = {
value: ""
};
}
componentDidMount() {
}
onChangeInputHandler(event) {
event.preventDefault();
this.setState({
value: event.currentTarget.value
});
}
onClickDeleteHandler(select, idx) {
this.props.onClickDeletePropertyHandler(select, idx);
}
onClickAddObjectHandler() {
this.props.onClickAddTextHandler(this.props.field, this.state.value);
}
render() {
const field = this.props.field;
const value = this.props.value ? this.props.value : [];
const description = this.props.description;
const rows = [];
return (
<div className="ct-container">
<div className="ct-header">
{field}
<span data-tip={description} className="material-icons">info</span>
<Tooltip />
</div>
<div className="ct-body">
<div className="ct-block-input">
<div className="input">
<TextArea value={this.state.value}
name={field}
onChange={this.onChangeInputHandler} />
</div>
<div className="add-container">
<span onClick={this.onClickAddObjectHandler} className="add material-icons">control_point</span>
</div>
</div>
<div className="ct-output">
{value}
</div>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,125 @@
import React from "react";
import PropTypes from "prop-types";
import {observer, inject} from "mobx-react";
import classNames from "classnames";
import Tooltip from "react-tooltip";
import Text from "../inputs/Text";
import uuid from "uuid";
import killChainStyle from "./externalreferences.scss";
@inject("store")
@observer
export default class ExternalReferences extends React.Component {
constructor(props) {
super(props);
this.onClickHandler = this.onClickHandler.bind(this);
this.onChangeERHandler = this.onChangeERHandler.bind(this);
this.onClickAddHandler = this.onClickAddHandler.bind(this);
this.onClickDeleteHandler = this.onClickDeleteHandler.bind(this);
}
componentDidMount() {
}
onChangeERHandler(event, value) {
return undefined;
}
onClickAddHandler(input, select, idx) {
this.props.onChangeERHandler(input.value, select.options[select.selectedIndex].value, idx);
input.value = "";
}
onClickHandler() {
const property = this.state.property;
const value = this.state.value;
console.log(this.state)
}
onClickDeleteHandler(select, idx) {
this.props.onClickDeletePropertyHandler(select, idx);
}
render() {
const vocab = this.props.vocab ? this.props.vocab : [];
const field = this.props.field;
const value = this.props.value;
const description = this.props.description;
const len = value.len;
return (
<div className="er-container">
<div className="er-header">
{field}
<span data-tip={description} className="material-icons">info</span>
<span data-tip="Add an External Reference" onClick={() => this.props.onClickAddObjectHandler(field)} className="add material-icons">control_point</span>
<Tooltip />
</div>
<div className="er-body">
{
value.map((p, i) => {
return (
<ReferenceBlock key={i}
i={i}
kv={p}
onChangeERHandler={this.onChangeERHandler}
onClickAddHandler={this.onClickAddHandler}
onClickDeleteHandler={this.onClickDeleteHandler} />
)
})
}
</div>
</div>
)
}
}
const ReferenceBlock = (props) => {
const blocks = [];
const idx = props.i;
const selectID = `select-${props.i}`;
const inputID = `input-${props.i}`;
const propValues = [
"source_name",
"description",
"url",
"hashes",
"external_id"
];
for (let item in props.kv) {
let remove = <span onClick={() => props.onClickDeleteHandler(item, props.i)} className="remove material-icons">highlight_off</span>;
if (item === "source_name") {
remove = undefined;
}
blocks.push(
<div key={uuid()} className="er-block-row">
<div>{item}: {props.kv[item]} {remove}</div>
</div>
)
}
return <div className="er-block">
<div className="er-block-row">
<select id={selectID}>
{
propValues.map(prop => {
return <option key={uuid()} value={prop}>{prop}</option>
})
}
</select>
<Text id={inputID} onChange={props.onChangeERHandler} />
<span className="add material-icons" onClick={() => props.onClickAddHandler(document.getElementById(inputID), document.getElementById(selectID), props.i)}>control_point</span>
</div>
{blocks}
</div>
}

View File

@@ -0,0 +1,115 @@
import React from "react";
import PropTypes from "prop-types";
import {observer, inject} from "mobx-react";
import classNames from "classnames";
import Tooltip from "react-tooltip";
import Text from "../inputs/Text";
import uuid from "uuid";
import killChainStyle from "./genericobject.scss";
@inject("store")
@observer
export default class GenericObject extends React.Component {
constructor(props) {
super(props);
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: "",
value: ""
};
}
componentDidMount() {
}
onChangeInputHandler(event) {
event.preventDefault();
this.setState({
[event.currentTarget.name]: event.currentTarget.value
});
}
onClickDeleteHandler(select, idx) {
this.props.onClickDeletePropertyHandler(select, idx);
}
onClickCreateBlankHandler() {
this.setState({
key: "",
value: ""
});
}
onClickAddObjectHandler() {
const o = {};
o[this.state.key] = this.state.value;
this.props.onClickAddObjectHandler(this.props.field, o);
}
render() {
const field = this.props.field;
const value = this.props.value ? this.props.value : [];
const description = this.props.description;
const rows = [];
for (let key in value) {
rows.push(
<ExtBlocks
key={uuid()}
v={value[key]}
k={key}
field={field}
onClickDeleteHandler={this.props.onClickDeleteObjectHandler} />
)
}
return (
<div className="go-container">
<div className="go-header">
{field}
<span data-tip={description} className="material-icons">info</span>
<Tooltip />
</div>
<div className="go-body">
<div className="go-block-input">
<div className="input">
<Text name="key" value={this.state.key} onChange={this.onChangeInputHandler} />
</div>
<div className="input">
<Text name="value" value={this.state.value} onChange={this.onChangeInputHandler} />
</div>
<div className="add-container">
<span onClick={this.onClickAddObjectHandler} className="add material-icons">control_point</span>
</div>
</div>
{rows}
</div>
</div>
)
}
}
const ExtBlocks = (props) => {
let v = props.v;
if (typeof props.v === "object") {
v = JSON.stringify(props.v);
}
return <div className="go-block">
<div className="go-block-row">{props.k}: {v} <span onClick={() => props.onClickDeleteHandler(props.field, props.k)} className="remove material-icons">highlight_off</span></div>
</div>
}

View File

@@ -0,0 +1,112 @@
import React from "react";
import PropTypes from "prop-types";
import {observer, inject} from "mobx-react";
import classNames from "classnames";
import Tooltip from "react-tooltip";
import killChainStyle from "./killchain.scss";
@inject("store")
@observer
export default class KillChain extends React.Component {
constructor(props) {
super(props);
this.onChangePhaseHandler = this.onChangePhaseHandler.bind(this);
this.populatePhase = this.populatePhase.bind(this);
}
componentDidMount() {
}
onChangePhaseHandler(event) {
const kcDomName = `kc-name-${this.props.node.id}`;
const phaseDomName = `phase-${this.props.node.id}`;
const kcIndex = document.getElementById(kcDomName).selectedIndex;
const kcValue = document.getElementById(kcDomName)[kcIndex].value;
const phaseValue = event.currentTarget.value;
const value = {
kill_chain_name: kcValue,
phase_name: phaseValue
};
this.props.onChangeHandler(this.props.field, value);
document.getElementById(kcDomName).selectedIndex = 0;
document.getElementById(phaseDomName).selectedIndex = 0;
// Reset phase name so we don't keep adding the same values
// multiple times.
document.getElementById(phaseDomName).innerHTML = "";
var option = document.createElement("option");
option.value = 0
option.text = " -- Select Phase -- ";
document.getElementById(phaseDomName).add(option);
}
populatePhase(event) {
const phaseDomName = `phase-${this.props.node.id}`;
const phaseDOM = document.getElementById(phaseDomName);
const kc = event.currentTarget.value;
this.props.vocab.map(item => {
if (item.value === kc) {
item.phases.map(phase => {
var option = document.createElement("option");
option.value = phase.phase_name;
option.text = phase.label;
phaseDOM.add(option);
});
}
});
}
render() {
const vocab = this.props.vocab ? this.props.vocab : [];
const field = this.props.field;
const value = this.props.value;
const description = this.props.description;
const len = value.len;
const kcName = `kc-name-${this.props.node.id}`;
const phaseName = `phase-${this.props.node.id}`;
return (
<div className="kill-chain-container">
<div className="kill-chain-header">
{field} <span data-tip={description} className="material-icons">info</span>
</div>
<div className="kill-chain-body">
<div className="kill-chain-options">
<select id={kcName} onChange={this.populatePhase}>
<option value={0}> -- Select Kill Chain -- </option>
{
vocab.map(item => {
return <option key={item.value}
value={item.value}>{item.label}</option>
})
}
</select>
<select id={phaseName} onChange={this.onChangePhaseHandler}>
<option value={0}> -- Select Phase -- </option>
</select>
</div>
{
value.map((p, i) => {
return (
<div key={i} className="kill-chain-row">
<div>{p.kill_chain_name} - {p.phase_name} <span onClick={() => this.props.onClickRemoveHandler(field, p)} className="material-icons">highlight_off</span></div>
</div>
)
})
}
</div>
</div>
)
}
}

View File

@@ -0,0 +1,94 @@
@import '../../../defaults';
.ct-container {
padding-left: 20px;
padding-top: 20px;
padding-right: 10px;
.ct-header {
font-weight: bold;
color: $default-active-bg;
padding-bottom: 3px;
span {
vertical-align: middle;
font-size: 14px;
cursor: pointer;
}
}
.ct-body {
overflow-x: hidden;
border: 1px solid $standard-border-color;
min-height: 50px;
width: 100%;
.ct-block-input {
display: flex;
flex-direction: row;
padding: 5px;
width: 100%;
.input {
margin: 10px 0px 10px 10px;
flex: 1;
textarea {
font-size: 14px;
font-family: tahoma;
}
}
.add-container {
width: 50px;
padding: 35px 0px 0px 8px;
span {
cursor: pointer;
color: $default-active-bg;
padding-left: 5px;
}
}
}
.ct-output {
font-family: tahoma;
font-size: 14px;
padding: 0px 15px 15px 15px;
}
.go-block {
margin: 10px;
.go-block-row {
display: flex;
flex-direction: row;
padding: 5px;
input {
margin: 10px 0px 10px 10px;
flex: .5;
}
div {
padding: 10px;
flex: .5;
}
span {
vertical-align: middle;
cursor: pointer;
padding-left: 5px;
}
.remove {
color: $error-font;
}
.add {
padding-top: 15px;
color: $default-active-bg;
}
}
}
}
}

View File

@@ -0,0 +1,56 @@
@import '../../../defaults';
.er-container {
padding-left: 20px;
padding-top: 20px;
.er-header {
font-weight: bold;
color: $default-active-bg;
padding-bottom: 3px;
span {
vertical-align: middle;
font-size: 14px;
cursor: pointer;
}
}
.er-body {
overflow: auto;
border: 1px solid $standard-border-color;
min-height: 50px;
.er-block {
border: 1px solid $standard-border-color;
margin: 10px;
.er-block-row {
display: flex;
flex-direction: row;
select {
margin: 10px 0px 10px 10px;
flex: .5;
}
div {
padding: 10px;
flex: .5;
}
span {
vertical-align: middle;
cursor: pointer;
}
.remove {
color: $error-font;
}
.add {
padding-top: 15px;
color: $default-active-bg;
}
}
}
}
}

View File

@@ -0,0 +1,84 @@
@import '../../../defaults';
.go-container {
padding-left: 20px;
padding-top: 20px;
padding-right: 10px;
.go-header {
font-weight: bold;
color: $default-active-bg;
padding-bottom: 3px;
span {
vertical-align: middle;
font-size: 14px;
cursor: pointer;
}
}
.go-body {
overflow-x: hidden;
border: 1px solid $standard-border-color;
min-height: 50px;
width: 100%;
.go-block-input {
display: flex;
flex-direction: row;
padding: 5px;
width: 100%;
.input {
margin: 10px 0px 10px 10px;
flex: .5;
}
.add-container {
width: 50px;
padding: 20px 0px 0px 15px;
span {
cursor: pointer;
color: $default-active-bg;
padding-left: 5px;
}
}
}
.go-block {
margin: 10px;
.go-block-row {
display: flex;
flex-direction: row;
padding: 5px;
input {
margin: 10px 0px 10px 10px;
flex: .5;
}
div {
padding: 10px;
flex: .5;
}
span {
vertical-align: middle;
cursor: pointer;
padding-left: 5px;
}
.remove {
color: $error-font;
}
.add {
padding-top: 15px;
color: $default-active-bg;
}
}
}
}
}

View File

@@ -0,0 +1,43 @@
@import '../../../defaults';
.kill-chain-container {
padding-left: 20px;
padding-top: 20px;
.kill-chain-header {
font-weight: bold;
color: $default-active-bg;
padding-bottom: 3px;
span {
vertical-align: middle;
font-size: 14px;
cursor: pointer;
}
}
.kill-chain-body {
overflow: auto;
border: 1px solid $standard-border-color;
.kill-chain-options {
display: flex;
flex-direction: row;
select {
flex: .5;
margin: 10px 5px;
}
}
.kill-chain-row {
padding: 5px 5px 5px 10px;
.material-icons {
vertical-align: middle;
color: $error-font;
cursor: pointer;
}
}
}
}

View File

@@ -0,0 +1,46 @@
import React from "react";
import { inject, observer } from "mobx-react";
import PropTypes from "prop-types";
import classNames from "classnames";
import growlStyle from './growl.scss';
@observer
export default class Growl extends React.Component {
constructor(props) {
super(props);
}
onClickHideHandler() {
if (this.props.onClickHideHandler) {
this.props.onClickHideHandler();
} else {
console.warn("No JSON Viewer close handler");
}
}
onClickPanelHandler(event) {
event.stopPropagation();
}
render() {
const cls = classNames({
growl: true,
'hide-mask': !this.props.show
});
if (this.props.timer) {
this.props.timer();
}
return (
<div className={cls}>
<div className="panel">
{this.props.message}
</div>
</div>
)
}
}

View File

@@ -0,0 +1,12 @@
@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;
}

View File

@@ -0,0 +1,60 @@
import React from "react";
import PropTypes from "prop-types";
import {observer, inject} from "mobx-react";
import classNames from "classnames";
import Tooltip from "react-tooltip";
import arrStyle from "./arrayselector.scss";
@inject("store")
@observer
export default class ArraySelector extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
onClickHandler(field, value) {
this.props.onClickHandler(field, value);
}
render() {
const items = this.props.vocab ? this.props.vocab : [];
const field = this.props.field;
const value = this.props.value;
const description = this.props.description;
let cls = classNames({
"array-container-item": true
});
return (
<div className="array-container">
<div className="array-container-header">
{field} <span data-tip={description} className="material-icons">info</span>
</div>
<div className="array-container-body">
{
items.map((item, i) => {
if (value.indexOf(item) > -1) {
cls = classNames({
"array-container-item": true,
"array-container-selected": true
});
} else {
cls = classNames({
"array-container-item": true
});
}
return <div className={cls} key={i} onClick={() => this.onClickHandler(field, item)}>{item}</div>
})
}
</div>
</div>
)
}
}

View File

@@ -0,0 +1,51 @@
import React from "react";
import PropTypes from "prop-types";
import {observer, inject} from "mobx-react";
import classNames from "classnames";
import csvStyle from "./boolean.scss";
@inject("store")
@observer
export default class ArraySelector extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
onClickHandler(field, value) {
this.props.onClickHandler(field, value);
}
render() {
const value = this.props.selected;
let trueCls = classNames({
selected: false
});
let falseCls = classNames({
selected: false
});
if (value) {
trueCls = classNames({
selected: true
});
} else {
falseCls = classNames({
selected: true
});
}
return (
<div className="boolean">
<div className={trueCls} onClick={() => this.props.onClick(this.props.name, true)}>True</div>
<div className={falseCls} onClick={() => this.props.onClick(this.props.name, false)}>False</div>
</div>
)
}
}

View File

@@ -0,0 +1,36 @@
import React from "react";
import PropTypes from "prop-types";
import {observer, inject} from "mobx-react";
import classNames from "classnames";
import Tooltip from "react-tooltip";
import Text from "./Text";
import csvStyle from "./csvselector.scss";
@inject("store")
@observer
export default class ArraySelector extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
onClickHandler(field, value) {
this.props.onClickHandler(field, value);
}
render() {
const value = this.props.value.join();
return (
<Text name={this.props.name}
value={value}
onChange={this.props.onChangeHandler} />
)
}
}

View File

@@ -0,0 +1,33 @@
import React from 'react';
import {observer, inject} from 'mobx-react';
import moment from 'moment';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import dateStyle from './datetime.scss';
export default class DateTime extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
onChange(datetime) {
this.props.onChange(this.props.name, datetime);
}
render() {
let dts = this.props.selected;
if (typeof dts === 'string') {
let dateObj = new Date(dts);
dts = dateObj;
}
return (
<DatePicker selected={dts} onChange={this.onChange} name={this.props.name} />
)
}
}

View File

@@ -0,0 +1,32 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { toJS } from "mobx";
import RCSlider from "rc-slider/lib/Slider";
import 'rc-slider/assets/index.css'
import textStyle from "./slider.scss";
@inject("store")
@observer
export default class Slider extends React.Component {
constructor(props) {
super(props);
this.onChangeSliderHandler = this.onChangeSliderHandler.bind(this);
}
onChangeSliderHandler(value) {
this.props.onChangeHandler(this.props.field, value);
}
render() {
return (
<RCSlider className="horizontal-slider"
value={this.props.value}
marks={{ 10: 10, 20: 20, 30: 30, 40: 40, 50: 50, 60: 60, 70: 70, 80: 80, 90: 90, 100: 100 }}
onChange={this.onChangeSliderHandler} />
)
}
}

View File

@@ -0,0 +1,68 @@
import React from "react";
import PropTypes from "prop-types";
import {observer, inject} from "mobx-react";
import textStyle from "./text.scss";
@inject("store")
@observer
export default class Text extends React.Component {
constructor(props) {
super(props);
this.onChangeHandler = this.onChangeHandler.bind(this);
}
componentDidMount() {
if (this.props.hasInitialFocus) {
this.focus();
}
}
focus() {
if (this.input) {
this.input.focus();
}
}
onKeyDownHandler(event) {
if (event.keyCode === 13 && this.props.onReturn) {
this.props.onReturn();
} else if (event.keyCode === 27 && this.props.onEscape) {
this.props.onEscape();
}
}
onChangeHandler(event) {
this.props.onChange(event);
}
render() {
let inputType = this.props.type ? this.props.type : 'text';
return (
<div>
<input
name={this.props.name}
type={inputType}
ref={c => { this.input = c }}
autoComplete={this.props.autocomplete || "off"}
className="def"
placeholder={this.props.placeholder}
onChange={this.onChangeHandler}
onKeyDown={e => this.onKeyDownHandler(e)}
value={this.props.value}
disabled={this.props.disabled}
id={this.props.id}
/>
</div>
)
}
}
Text.propTypes = {
hasInitialFocus: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onReturn: PropTypes.func,
onEscape: PropTypes.func
};

View File

@@ -0,0 +1,59 @@
import React from "react";
import {observer, inject} from "mobx-react";
import textStyle from "./text.scss";
@inject("store")
@observer
export default class TextArea extends React.Component {
constructor(props) {
super(props);
this.onChangeHandler = this.onChangeHandler.bind(this);
}
componentDidMount() {
if (this.props.hasInitialFocus) {
this.focus();
}
}
focus() {
if (this.input) {
this.input.focus();
}
}
onKeyDownHandler(event) {
if (event.keyCode === 13 && this.props.onReturn) {
this.props.onReturn();
} else if (event.keyCode === 27 && this.props.onEscape) {
this.props.onEscape();
}
}
onChangeHandler(event) {
this.props.onChange(event);
}
render() {
let rows = this.props.rows ? this.props.rows : 1;
return (
<div>
<textarea
name={this.props.name}
ref={c => { this.input = c }}
autoComplete={this.props.autocomplete || "off"}
className="def"
placeholder={this.props.placeholder}
onChange={this.onChangeHandler}
onKeyDown={e => this.onKeyDownHandler(e)}
value={this.props.value}
disabled={this.props.disabled}
id={this.props.id}
/>
</div>
)
}
}

View File

@@ -0,0 +1,38 @@
@import '../../../defaults';
.array-container {
padding-left: 20px;
padding-top: 20px;
.array-container-header {
font-weight: bold;
color: $default-active-bg;
padding-bottom: 3px;
span {
vertical-align: middle;
font-size: 14px;
cursor: pointer;
}
}
.array-container-body {
height: 100px;
overflow: auto;
border: 1px solid $standard-border-color;
.array-container-item {
cursor: pointer;
line-height: 30px;
padding-left: 10px;
}
.array-container-selected {
background-color: $default-active-bg;
}
.array-container-item:hover {
background-color: $light-font-0;
}
}
}

View File

@@ -0,0 +1,20 @@
@import '../../../defaults';
.boolean {
display: flex;
flex-direction: row;
line-height: 30px;
border: 1px solid $standard-border-color;
width: 99%;
div {
flex: .5;
text-align: center;
cursor: pointer;
}
.selected {
background-color: $default-active-bg;
}
}

View File

@@ -0,0 +1,17 @@
@import '../../../defaults';
.react-datepicker-wrapper {
width: 100%;
}
.react-datepicker__input-container input {
height: 39px;
border: 1px solid $standard-border-color;
background-color: transparent;
width: 97%;
padding-left: 10px;
font-family: $default-font-family;
color: #000;
font-size: 16px;
font-weight: bold;
}

View File

@@ -0,0 +1,5 @@
@import '../../../defaults';
.rc-slider-track {
background-color: transparet;
}

View File

@@ -0,0 +1,40 @@
@import '../../../defaults';
input:focus, textarea:focus, select {
outline:none;
}
input.def, textarea, select {
height: 39px;
border: 1px solid $standard-border-color;
background-color: transparent;
width: 97%;
padding-left: 10px;
color: $dark-font-0;
font-size: 16px;
font-family: $default-font-family;
}
textarea {
height: 80px;
}
select {
height: 44px;
}
::-webkit-input-placeholder {
color: #4f5257;
}
::-ms-input-placeholder {
color: red;
}
::-moz-placeholder {
color: red;
}
::-moz-placeholder {
color: red;
}

View File

@@ -0,0 +1,44 @@
import React from "react";
import { inject, observer } from "mobx-react";
import PropTypes from "prop-types";
import classNames from "classnames";
import panelStyle from './panel.scss';
@observer
export default class Panel extends React.Component {
constructor(props) {
super(props);
this.onClickHideHandler = this.onClickHideHandler.bind(this);
}
onClickHideHandler() {
if (this.props.onClickHideHandler) {
this.props.onClickHideHandler();
} else {
console.warn("No JSON Viewer close handler");
}
}
onClickPanelHandler(event) {
event.stopPropagation();
}
render() {
const cls = classNames({
mask: true,
'hide-mask': !this.props.show
});
return (
<div className={cls} onClick={this.onClickHideHandler}>
<div className="panel" onClick={this.onClickPanelHandler}>
{this.props.children}
</div>
</div>
)
}
}

View File

@@ -0,0 +1,28 @@
@import '../../../defaults';
.mask {
position: fixed;
left: 0px;
top: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(0, 0, 0,.2);
z-index: $panel-mask-index;
.panel {
position: absolute;
width: 600px;
top: 0px;
right: 0px;
bottom: 0px;
background-color: #fff;
box-shadow: -20px 25px 50px 0px #000;
z-index: $panel-index;
display: flex;
flex-direction: column;
}
}
.hide-mask {
display: none;
}

Some files were not shown because too many files have changed in this diff Show More