First commit
24
app/backend/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# tinycheck-backend
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
5
app/backend/babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
12010
app/backend/package-lock.json
generated
Normal file
44
app/backend/package.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "tinycheck-backend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --copy --port=4201",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.20.0",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue-router": "^3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
BIN
app/backend/public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
17
app/backend/public/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
98
app/backend/src/App.vue
Normal file
@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="backend-container off-canvas off-canvas-sidebar-show">
|
||||
<div class="backend-navbar">
|
||||
<a class="off-canvas-toggle btn btn-link btn-action" href="#sidebar">
|
||||
<i class="icon icon-menu"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="backend-sidebar off-canvas-sidebar" id="sidebar">
|
||||
<div class="backend-brand">
|
||||
<h2 @click="$router.push('/')" class="title">TinyCheck</h2>
|
||||
</div>
|
||||
<div class="backend-nav">
|
||||
<div class="accordion-container">
|
||||
<div class="accordion">
|
||||
<input id="accordion-configuration" type="checkbox" name="backend-accordion-checkbox" hidden="">
|
||||
<label class="accordion-header c-hand" for="accordion-configuration">Manage Device</label>
|
||||
<div class="accordion-body">
|
||||
<ul class="menu menu-nav">
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/device/configuration')">Device config</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/device/network')">Network config</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/device/db')">Manage database</span>
|
||||
</li>
|
||||
<!-- <li class="menu-item">
|
||||
<span @click="$router.push('/device/user')">User configuration</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion">
|
||||
<input id="accordion-iocs" type="checkbox" name="backend-accordion-checkbox" hidden="">
|
||||
<label class="accordion-header c-hand" for="accordion-iocs">Manage IOCs</label>
|
||||
<div class="accordion-body">
|
||||
<ul class="menu menu-nav">
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/iocs/manage')">Manage IOCs</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/iocs/search')">Search IOCs</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion">
|
||||
<input id="accordion-whitelist" type="checkbox" name="backend-accordion-checkbox" hidden=""/>
|
||||
<label class="accordion-header c-hand" for="accordion-whitelist">Manage Whitelist</label>
|
||||
<div class="accordion-body">
|
||||
<ul class="menu menu-nav">
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/whitelist/manage')">Manage elements</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<span @click="$router.push('/whitelist/search')">Search elements</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shortcuts">
|
||||
<a href="https://github.com/KasperskyLab/tinycheck"><img src="@/assets/github.png" class="shortcut" /></a>
|
||||
<a href="https://twitter.com/tinycheck"><img src="@/assets/twitter.png" class="shortcut" /></a>
|
||||
</div>
|
||||
</div>
|
||||
<a class="off-canvas-overlay" href="#close"></a>
|
||||
<div class="off-canvas-content">
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view/>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
document.title = 'TinyCheck Backend'
|
||||
</script>
|
||||
<style>
|
||||
@import './assets/spectre.min.css';
|
||||
@import './assets/spectre-exp.min.css';
|
||||
@import './assets/spectre-icons.min.css';
|
||||
@import './assets/custom.css';
|
||||
/* Face style for router stuff. */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition-duration: 0.3s;
|
||||
transition-property: opacity;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-active {
|
||||
opacity: 0
|
||||
}
|
||||
</style>
|
653
app/backend/src/assets/custom.css
Normal file
@ -0,0 +1,653 @@
|
||||
/*
|
||||
This CSS was forked from the awsome Spectre.css docs.
|
||||
Spectre.css Docs | MIT License | github.com/picturepan2/spectre
|
||||
*/
|
||||
|
||||
|
||||
.off-canvas .off-canvas-toggle {
|
||||
font-size: 1rem;
|
||||
left: 1.5rem;
|
||||
position: fixed;
|
||||
top: 1rem
|
||||
}
|
||||
|
||||
.off-canvas .off-canvas-sidebar {
|
||||
width: 12rem
|
||||
}
|
||||
|
||||
.off-canvas .off-canvas-content {
|
||||
padding: 0
|
||||
}
|
||||
|
||||
.backend-container {
|
||||
min-height: 100vh
|
||||
}
|
||||
|
||||
.backend-navbar {
|
||||
height: 3.8rem;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 100
|
||||
}
|
||||
|
||||
.backend-navbar .btns {
|
||||
position: absolute;
|
||||
right: 1.5rem;
|
||||
top: 1rem;
|
||||
width: 14rem
|
||||
}
|
||||
|
||||
.backend-navbar .algolia-autocomplete {
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto
|
||||
}
|
||||
|
||||
.backend-sidebar .backend-nav {
|
||||
bottom: 1.5rem;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-y: auto;
|
||||
padding: .5rem 1.5rem;
|
||||
position: fixed;
|
||||
top: 3.5rem;
|
||||
width: 12rem
|
||||
}
|
||||
|
||||
.backend-sidebar .accordion {
|
||||
margin-bottom: .75rem
|
||||
}
|
||||
|
||||
.backend-sidebar .accordion input~.accordion-header {
|
||||
color: #455060;
|
||||
font-size: .65rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase
|
||||
}
|
||||
|
||||
.backend-sidebar .accordion input:checked~.accordion-header {
|
||||
color: #505c6e
|
||||
}
|
||||
|
||||
.backend-sidebar .accordion .menu .menu-item {
|
||||
font-size: .7rem;
|
||||
padding-left: 1rem;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.backend-sidebar .accordion .menu .menu-item>a {
|
||||
background: 0 0;
|
||||
color: #66758c;
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.backend-content {
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
padding: 0 4rem;
|
||||
width: calc(100vw - 12rem)
|
||||
}
|
||||
|
||||
.backend-content>.container {
|
||||
margin-left: 0;
|
||||
max-width: 800px;
|
||||
padding-bottom: 1.5rem
|
||||
}
|
||||
|
||||
.backend-content .anchor {
|
||||
color: #6362dc;
|
||||
display: none;
|
||||
margin-left: .2rem;
|
||||
padding: 0 .2rem
|
||||
}
|
||||
|
||||
.backend-content .anchor:focus,
|
||||
.backend-content .anchor:hover {
|
||||
display: inline;
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.backend-content .s-subtitle,
|
||||
.backend-content .s-title {
|
||||
line-height: 1.8rem;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 1rem;
|
||||
padding-top: 1rem;
|
||||
position: static
|
||||
}
|
||||
|
||||
@supports ((position:-webkit-sticky) or (position:sticky)) {
|
||||
.backend-content .s-subtitle,
|
||||
.backend-content .s-title {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 99
|
||||
}
|
||||
.backend-content .s-subtitle::before,
|
||||
.backend-content .s-title::before {
|
||||
background: #fff;
|
||||
bottom: 0;
|
||||
content: "";
|
||||
display: block;
|
||||
left: -10px;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: -5px;
|
||||
z-index: -1
|
||||
}
|
||||
}
|
||||
|
||||
.backend-content .s-subtitle:hover .anchor,
|
||||
.backend-content .s-title:hover .anchor {
|
||||
display: inline
|
||||
}
|
||||
|
||||
.backend-content .s-subtitle+.backend-note,
|
||||
.backend-content .s-title+.backend-note {
|
||||
margin-top: .4rem
|
||||
}
|
||||
|
||||
.backend-content .backend-demo {
|
||||
padding-bottom: 1rem;
|
||||
padding-top: 1rem
|
||||
}
|
||||
|
||||
.backend-content .backend-demo .card {
|
||||
border: 0;
|
||||
box-shadow: 0 .25rem 1rem rgba(48, 55, 66, .15);
|
||||
height: 100%
|
||||
}
|
||||
|
||||
.backend-content .column {
|
||||
padding: .4rem
|
||||
}
|
||||
|
||||
.backend-content .backend-block {
|
||||
border-radius: .1rem;
|
||||
padding: .4rem
|
||||
}
|
||||
|
||||
.backend-content .backend-block.bg-gray {
|
||||
background: #eef0f3
|
||||
}
|
||||
|
||||
.backend-content .backend-shape {
|
||||
height: 4.8rem;
|
||||
line-height: 1.2rem;
|
||||
padding: 1.8rem 0;
|
||||
width: 4.8rem
|
||||
}
|
||||
|
||||
.backend-content .backend-dot {
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
height: .5rem;
|
||||
padding: 0;
|
||||
width: .5rem
|
||||
}
|
||||
|
||||
.backend-content .backend-table td,
|
||||
.backend-content .backend-table th {
|
||||
padding: .75rem .25rem
|
||||
}
|
||||
|
||||
.backend-content .backend-color {
|
||||
border-radius: .1rem;
|
||||
margin: .25rem 0;
|
||||
padding: 5rem .5rem .5rem
|
||||
}
|
||||
|
||||
.backend-content .backend-color .color-subtitle {
|
||||
font-size: .7rem;
|
||||
opacity: .75
|
||||
}
|
||||
|
||||
.backend-content .code .hljs-tag {
|
||||
color: #505c6e
|
||||
}
|
||||
|
||||
.backend-content .code .hljs-comment {
|
||||
color: #bcc3ce
|
||||
}
|
||||
|
||||
.backend-content .code .hljs-class,
|
||||
.backend-content .code .hljs-number,
|
||||
.backend-content .code .hljs-string,
|
||||
.backend-content .code .hljs-title {
|
||||
color: #5755d9
|
||||
}
|
||||
|
||||
.backend-content .code .hljs-attribute,
|
||||
.backend-content .code .hljs-built_in,
|
||||
.backend-content .code .hljs-keyword,
|
||||
.backend-content .code .hljs-name,
|
||||
.backend-content .code .hljs-variable {
|
||||
color: #d73e48
|
||||
}
|
||||
|
||||
.backend-content .code .hljs-hexcolor,
|
||||
.backend-content .code .hljs-value {
|
||||
color: #505c6e
|
||||
}
|
||||
|
||||
.backend-content .c-select-all {
|
||||
-webkit-user-select: all;
|
||||
-moz-user-select: all;
|
||||
-ms-user-select: all;
|
||||
user-select: all
|
||||
}
|
||||
|
||||
.backend-content .panel {
|
||||
height: 75vh
|
||||
}
|
||||
|
||||
.backend-content .panel .tile {
|
||||
margin: .75rem 0
|
||||
}
|
||||
|
||||
.backend-content .parallax {
|
||||
margin: 2rem auto
|
||||
}
|
||||
|
||||
.backend-content .form-autocomplete .menu {
|
||||
position: static
|
||||
}
|
||||
|
||||
.backend-content .example-tile-icon {
|
||||
align-content: space-around;
|
||||
align-items: center;
|
||||
background: #5755d9;
|
||||
border-radius: .1rem;
|
||||
color: #fff;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
-ms-flex-line-pack: distribute;
|
||||
font-size: 1.2rem;
|
||||
height: 2rem;
|
||||
width: 2rem
|
||||
}
|
||||
|
||||
.backend-content .example-tile-icon .icon {
|
||||
margin: auto
|
||||
}
|
||||
|
||||
.backend-content .comparison-slider {
|
||||
height: auto;
|
||||
padding-bottom: 56.2222%
|
||||
}
|
||||
|
||||
.backend-content .comparison-slider .filter-grayscale {
|
||||
filter: grayscale(75%)
|
||||
}
|
||||
|
||||
.backend-content .off-canvas {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.backend-content .off-canvas .off-canvas-toggle {
|
||||
left: .4rem;
|
||||
position: absolute;
|
||||
top: .4rem;
|
||||
z-index: 1
|
||||
}
|
||||
|
||||
.backend-brand {
|
||||
color: #CCC;
|
||||
height: 2rem;
|
||||
left: 1.5rem;
|
||||
position: fixed;
|
||||
top: .85rem
|
||||
}
|
||||
|
||||
.backend-brand .backend-logo {
|
||||
align-items: center;
|
||||
border-radius: .1rem;
|
||||
display: -ms-inline-flexbox;
|
||||
display: inline-flex;
|
||||
-ms-flex-align: center;
|
||||
font-size: .7rem;
|
||||
height: 2rem;
|
||||
padding: .2rem;
|
||||
width: auto
|
||||
}
|
||||
|
||||
.backend-brand .backend-logo:focus,
|
||||
.backend-brand .backend-logo:hover {
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.backend-brand .backend-logo img {
|
||||
display: inline-block;
|
||||
height: auto;
|
||||
width: 1.6rem
|
||||
}
|
||||
|
||||
.backend-brand .backend-logo h2 {
|
||||
display: inline-block;
|
||||
font-size: .8rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.5rem;
|
||||
margin-bottom: 0;
|
||||
margin-left: .5rem;
|
||||
margin-right: .3rem
|
||||
}
|
||||
|
||||
.backend-footer {
|
||||
color: #bcc3ce;
|
||||
padding: .5rem
|
||||
}
|
||||
|
||||
.backend-footer a {
|
||||
color: #66758c
|
||||
}
|
||||
|
||||
@media (max-width:960px) {
|
||||
.off-canvas .off-canvas-toggle {
|
||||
z-index: 300
|
||||
}
|
||||
.off-canvas .off-canvas-content {
|
||||
width: 100%
|
||||
}
|
||||
.backend-sidebar .backend-brand {
|
||||
margin: .85rem 1.5rem;
|
||||
padding: 0;
|
||||
position: static
|
||||
}
|
||||
.backend-sidebar .backend-nav {
|
||||
margin-top: 1rem;
|
||||
position: static
|
||||
}
|
||||
.backend-sidebar .menu .menu-item>a {
|
||||
padding: .3rem .4rem
|
||||
}
|
||||
.backend-navbar {
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
background: rgba(247, 248, 249, .65);
|
||||
left: 0
|
||||
}
|
||||
.backend-content {
|
||||
min-width: auto;
|
||||
padding: 0 1.5rem;
|
||||
width: 100%
|
||||
}
|
||||
.backend-content .s-subtitle,
|
||||
.backend-content .s-title {
|
||||
padding-top: 5rem;
|
||||
position: static
|
||||
}
|
||||
.backend-content .s-subtitle::before,
|
||||
.backend-content .s-title::before {
|
||||
content: none
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:600px) {
|
||||
.off-canvas .off-canvas-toggle {
|
||||
left: .5rem
|
||||
}
|
||||
.backend-navbar .btns {
|
||||
right: .9rem
|
||||
}
|
||||
.backend-sidebar .backend-brand {
|
||||
margin: .85rem 1rem
|
||||
}
|
||||
.backend-sidebar .backend-nav {
|
||||
padding: .5rem 1rem
|
||||
}
|
||||
.backend-content {
|
||||
padding: 0 .5rem
|
||||
}
|
||||
.backend-content .backend-block {
|
||||
padding: .4rem .1rem
|
||||
}
|
||||
}
|
||||
|
||||
/* Ok, here the CSS specific to TinyCheck */
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lobster';
|
||||
font-weight: normal;
|
||||
src: url('fonts/lobster.ttf') format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto-Bold";
|
||||
src: url("fonts/Roboto-Bold.eot"); /* IE9 Compat Modes */
|
||||
src: url("fonts/Roboto-Bold.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
|
||||
url("fonts/Roboto-Bold.otf") format("opentype"), /* Open Type Font */
|
||||
url("fonts/Roboto-Bold.svg") format("svg"), /* Legacy iOS */
|
||||
url("fonts/Roboto-Bold.ttf") format("truetype"), /* Safari, Android, iOS */
|
||||
url("fonts/Roboto-Bold.woff") format("woff"), /* Modern Browsers */
|
||||
url("fonts/Roboto-Bold.woff2") format("woff2"); /* Modern Browsers */
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto-Regular";
|
||||
src: url("fonts/Roboto-Regular.eot"); /* IE9 Compat Modes */
|
||||
src: url("fonts/Roboto-Regular.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
|
||||
url("fonts/Roboto-Regular.otf") format("opentype"), /* Open Type Font */
|
||||
url("fonts/Roboto-Regular.svg") format("svg"), /* Legacy iOS */
|
||||
url("fonts/Roboto-Regular.ttf") format("truetype"), /* Safari, Android, iOS */
|
||||
url("fonts/Roboto-Regular.woff") format("woff"), /* Modern Browsers */
|
||||
url("fonts/Roboto-Regular.woff2") format("woff2"); /* Modern Browsers */
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-family: Lobster;
|
||||
color: #484848;
|
||||
}
|
||||
|
||||
h4, h5 {
|
||||
font-family: "Roboto-Bold";
|
||||
color: #484848;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:hover {
|
||||
background: #494949;
|
||||
border-color: #494949;
|
||||
text-decoration: none;
|
||||
color: #DBDBDB;
|
||||
}
|
||||
|
||||
.px150 {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.width-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tab-block {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.frame-export {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-upload {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-upload .upload-field {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
z-index:1000;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
.tlp-white {
|
||||
font-size: .6rem;
|
||||
height: 1.4rem;
|
||||
padding: .05rem .3rem;
|
||||
background-color: #FFF;
|
||||
line-height: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border: 2px solid #efefef;
|
||||
vertical-align: middle;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
.tlp-green {
|
||||
font-size: .6rem;
|
||||
height: 1.4rem;
|
||||
padding: .05rem .3rem;
|
||||
background-color: #199a09cf;
|
||||
line-height: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border: 2px solid #0f8600cf;
|
||||
vertical-align: middle;
|
||||
color:#FFF;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
.tlp-amber {
|
||||
font-size: .6rem;
|
||||
height: 1.4rem;
|
||||
padding: .05rem .3rem;
|
||||
background-color: #ffc000cf;
|
||||
line-height: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border: 2px solid #ffc000cf;
|
||||
vertical-align: middle;
|
||||
color:#FFF;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
.tlp-red {
|
||||
font-size: .6rem;
|
||||
height: 1.4rem;
|
||||
padding: .05rem .3rem;
|
||||
background-color: #ff0033;
|
||||
line-height: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border: 2px solid #ff0033;
|
||||
vertical-align: middle;
|
||||
color:#FFF;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: 'Lobster';
|
||||
color: #c5c5c5;
|
||||
cursor: pointer;
|
||||
width: fit-content;
|
||||
height: 2rem;
|
||||
left: 1.5rem;
|
||||
position: fixed;
|
||||
top: .85rem
|
||||
}
|
||||
|
||||
#network-thumbnail {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.interfaces-container {
|
||||
margin-top:20px;
|
||||
margin-bottom:40px;
|
||||
}
|
||||
|
||||
.interface-label {
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: inherit;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: inherit;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tab .tab-item a:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.shortcuts {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 20000;
|
||||
padding: 20px;
|
||||
opacity: .2;
|
||||
}
|
||||
|
||||
.shortcut {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.whitespace {
|
||||
height:100px;
|
||||
display:block;
|
||||
}
|
||||
|
||||
.alert-toaster-visible {
|
||||
position:fixed;
|
||||
right:15px;
|
||||
top:15px;
|
||||
padding:10px;
|
||||
background-color: #484848;
|
||||
border-radius: 5px;
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity .5s linear;
|
||||
}
|
||||
|
||||
.alert-toaster-hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 0s .5s, opacity .5s linear;
|
||||
}
|
||||
|
||||
.comment-block {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
background-color: #FAFAFA;
|
||||
}
|
||||
|
||||
.capi {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.upper {
|
||||
text-transform: uppercase;
|
||||
}
|
BIN
app/backend/src/assets/fonts/Roboto-Bold.eot
Normal file
BIN
app/backend/src/assets/fonts/Roboto-Bold.otf
Normal file
11535
app/backend/src/assets/fonts/Roboto-Bold.svg
Normal file
After Width: | Height: | Size: 805 KiB |
BIN
app/backend/src/assets/fonts/Roboto-Bold.ttf
Normal file
BIN
app/backend/src/assets/fonts/Roboto-Bold.woff
Normal file
BIN
app/backend/src/assets/fonts/Roboto-Bold.woff2
Normal file
BIN
app/backend/src/assets/fonts/Roboto-Regular.eot
Normal file
BIN
app/backend/src/assets/fonts/Roboto-Regular.otf
Normal file
11080
app/backend/src/assets/fonts/Roboto-Regular.svg
Normal file
After Width: | Height: | Size: 784 KiB |
BIN
app/backend/src/assets/fonts/Roboto-Regular.ttf
Normal file
BIN
app/backend/src/assets/fonts/Roboto-Regular.woff
Normal file
BIN
app/backend/src/assets/fonts/Roboto-Regular.woff2
Normal file
BIN
app/backend/src/assets/fonts/lobster.ttf
Normal file
BIN
app/backend/src/assets/github.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
app/backend/src/assets/network-home.png
Normal file
After Width: | Height: | Size: 315 KiB |
BIN
app/backend/src/assets/network.png
Normal file
After Width: | Height: | Size: 133 KiB |
1
app/backend/src/assets/spectre-exp.min.css
vendored
Normal file
1
app/backend/src/assets/spectre-icons.min.css
vendored
Normal file
1
app/backend/src/assets/spectre.min.css
vendored
Normal file
BIN
app/backend/src/assets/twitter.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
10
app/backend/src/main.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
Vue.config.productionTip = true
|
||||
Vue.config.devtools = true
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
63
app/backend/src/router/index.js
Normal file
@ -0,0 +1,63 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'default',
|
||||
component: () => import('../views/home.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/device/configuration',
|
||||
name: 'device-configuration',
|
||||
component: () => import('../views/edit-configuration.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/device/network',
|
||||
name: 'device-network',
|
||||
component: () => import('../views/network-manage.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/device/db',
|
||||
name: 'db-manage',
|
||||
component: () => import('../views/db-manage.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/iocs/manage',
|
||||
name: 'iocs-manage',
|
||||
component: () => import('../views/iocs-manage.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/iocs/search',
|
||||
name: 'iocs-search',
|
||||
component: () => import('../views/iocs-search.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/whitelist/manage',
|
||||
name: 'whitelist-manage',
|
||||
component: () => import('../views/whitelist-manage.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/whitelist/search',
|
||||
name: 'whitelist-search',
|
||||
component: () => import('../views/whitelist-search.vue'),
|
||||
props: true
|
||||
}
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
75
app/backend/src/views/db-manage.vue
Normal file
@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-6 col-xs-12">
|
||||
<h3 class="s-title">Manage database</h3>
|
||||
<ul class="tab tab-block">
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('import')" v-bind:class="{ active: tabs.import }">Import database</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('export')" v-bind:class="{ active: tabs.export }">Export database</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="tabs.export">
|
||||
<iframe :src="export_url" class="frame-export"></iframe>
|
||||
</div>
|
||||
<div v-if="tabs.import">
|
||||
<label class="form-upload empty" for="upload">
|
||||
<input type="file" class="upload-field" id="upload" @change="import_from_file">
|
||||
<p class="empty-title h5">Drop or select a database to import.</p>
|
||||
<p class="empty-subtitle">The database needs to be an export from a TinyCheck instance.</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'db-manage',
|
||||
data() {
|
||||
return {
|
||||
tabs: { "import" : true, "export" : false },
|
||||
jwt:""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
switch_tab: function(tab) {
|
||||
Object.keys(this.tabs).forEach(key => {
|
||||
if( key == tab ){
|
||||
this.tabs[key] = true
|
||||
} else {
|
||||
this.tabs[key] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
import_from_file: function(ev) {
|
||||
var formData = new FormData();
|
||||
formData.append("file", ev.target.files[0]);
|
||||
axios.post('/api/config/db/import', formData, {
|
||||
headers: {
|
||||
"Content-Type" : "multipart/form-data",
|
||||
"X-Token" : this.jwt
|
||||
}
|
||||
})
|
||||
},
|
||||
async get_jwt(){
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if(response.data.token){
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.export_url = `/api/config/db/export?token=${this.jwt}`
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
202
app/backend/src/views/edit-configuration.vue
Normal file
@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div v-bind:class="{ 'alert-toaster-visible' : toaster.show, 'alert-toaster-hidden' : !toaster.show }">{{toaster.message}}</div>
|
||||
<div class="modal active" id="modal-id" v-if="check_certificate">
|
||||
<a href="#close" class="modal-overlay" aria-label="Close"></a>
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<a href="#close" class="btn btn-clear float-right" aria-label="Close" @click="check_certificate = false"></a>
|
||||
<div class="modal-title h5">Certificate validation</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="content">
|
||||
Do you trust this certificate?
|
||||
<pre class="code" data-lang="CERTIFICATE">
|
||||
<code>{{certificate}}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" @click="validate_server()">Yes I trust it.</button><a class="btn btn-link" href="#modals" @click="no_trust()">No I don't trust it</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-6 col-xs-12">
|
||||
<h3 class="s-title">Configuration </h3>
|
||||
<h5 class="s-subtitle">Device configuration</h5>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'kiosk_mode')" v-model="config.frontend.kiosk_mode">
|
||||
<i class="form-icon"></i> Use TinyCheck in Kiosk-mode.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'virtual_keyboard')" v-model="config.frontend.virtual_keyboard">
|
||||
<i class="form-icon"></i> Use virtual keyboard (for touch screen)
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'hide_mouse')" v-model="config.frontend.hide_mouse">
|
||||
<i class="form-icon"></i> Hide mouse (for touch screen)
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('network', 'tokenized_ssids')" v-model="config.network.tokenized_ssids">
|
||||
<i class="form-icon"></i> Use tokenized SSIDs (eg. [ssid-name]-[hex-str]).
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'download_links')" v-model="config.frontend.download_links">
|
||||
<i class="form-icon"></i> Use in-browser download for network captures.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'sparklines')" v-model="config.frontend.sparklines">
|
||||
<i class="form-icon"></i> Show background sparklines during the capture.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('frontend', 'remote_access')" v-model="config.frontend.remote_access">
|
||||
<i class="form-icon"></i> Allow remote access to the frontend.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="switch_config('backend', 'remote_access')" v-model="config.backend.remote_access">
|
||||
<i class="form-icon"></i> Allow remote access to the backend.
|
||||
</label>
|
||||
</div>
|
||||
<h5 class="s-subtitle">Analysis configuration</h5>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="local_analysis('analysis', 'heuristics')" v-model="config.analysis.heuristics">
|
||||
<i class="form-icon"></i> Use heuristic detection for suspect behaviour.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="local_analysis('analysis', 'iocs')" v-model="config.analysis.iocs">
|
||||
<i class="form-icon"></i> Use Indicator of Compromise (IoC) based detection.
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" @change="local_analysis('analysis', 'whitelist')" v-model="config.analysis.whitelist">
|
||||
<i class="form-icon"></i> Use whitelist to prevent false positives.
|
||||
</label>
|
||||
</div>
|
||||
<h5 class="s-subtitle">User credentials</h5>
|
||||
<div class="form-group">
|
||||
<div class="column col-10 col-xs-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="user-login">User login</label>
|
||||
<div class="input-group">
|
||||
<input class="form-input" id="user-login" type="text" v-model="config.backend.login">
|
||||
<button class="btn btn-primary input-group-btn px150" @click="change_login()">Update it</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="user-login">User password</label>
|
||||
<div class="input-group">
|
||||
<input class="form-input" id="user-login" type="password" placeholder="●●●●●●" v-model="config.backend.password">
|
||||
<button class="btn btn-primary input-group-btn px150" @click="change_password()">Update it</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="whitespace"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'edit-configuration',
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
check_certificate: false,
|
||||
certificate: "",
|
||||
toaster: { show: false, message : "", type : null }
|
||||
}
|
||||
},
|
||||
props: {},
|
||||
methods: {
|
||||
switch_config: function(cat, key) {
|
||||
axios.get(`/api/config/switch/${cat}/${key}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) {
|
||||
if (response.data.message == "Key switched to true") {
|
||||
this.toaster = { show : true, message : "Configuration updated", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
this.config[cat][key] = true
|
||||
} else if (response.data.message == "Key switched to false") {
|
||||
this.toaster = { show : true, message : "Configuration updated", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
this.config[cat][key] = false
|
||||
} else {
|
||||
this.toaster = { show : true, message : "The key doesn't exist", type : "error" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/config/list`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data) {
|
||||
this.config = response.data
|
||||
this.config.backend.password = ""
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
async get_jwt() {
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if (response.data.token) {
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
local_analysis: function(cat, key) {
|
||||
this.switch_config(cat, key);
|
||||
if (this.config.analysis.remote != false)
|
||||
this.switch_config("analysis", "remote");
|
||||
},
|
||||
change_login: function() {
|
||||
axios.get(`/api/config/edit/backend/login/${this.config.backend.login}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) {
|
||||
this.toaster = { show : true, message : "Login changed", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
} else {
|
||||
this.toaster = { show : true, message : "Login not changed", type : "error" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
change_password: function() {
|
||||
axios.get(`/api/config/edit/backend/password/${this.config.backend.password}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) {
|
||||
this.toaster = { show : true, message : "Password changed", type : "success" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
} else {
|
||||
this.toaster = { show : true, message : "Password not changed", type : "error" }
|
||||
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.load_config();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
19
app/backend/src/views/home.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-6 col-xs-12">
|
||||
<div class="container">
|
||||
<h3 class="s-title">Getting started with TinyCheck</h3>
|
||||
|
||||
<img src="@/assets/network-home.png" id="network-thumbnail" />
|
||||
|
||||
<p>TinyCheck allows you to capture easily internet communications from a smartphone or any device which can be associated to a Wi-Fi access point in order to quickly analyze and save them.
|
||||
This can be used to verify if there is any suspect or malicious communication from a smartphone, by using heuristics or specific Indicators of Compromise (IoCs).</p>
|
||||
|
||||
<p>This little backend allows you to manage the configuration of your TinyCheck instance. You can push some IOCs for detection and whitelist elements which can be seen during legit communications in order to prevent false positives.</p>
|
||||
</div>
|
||||
<div class="backend-footer container grid-lg" id="copyright">
|
||||
<p>Created with <span class="text-error">♥</span> by <a href="https://twitter.com/felixaime" target="_blank">Félix Aimé</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
254
app/backend/src/views/iocs-manage.vue
Normal file
@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-6 col-xs-12">
|
||||
<h3 class="s-title">Manage IOCs</h3>
|
||||
<ul class="tab tab-block">
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('bulk')" v-bind:class="{ active: tabs.bulk }">Bulk import</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('file')" v-bind:class="{ active: tabs.file }">File import</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('export')" v-bind:class="{ active: tabs.export }">Export IOCs</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="tabs.export">
|
||||
<iframe :src="export_url" class="frame-export"></iframe>
|
||||
</div>
|
||||
<div v-if="tabs.file">
|
||||
<label class="form-upload empty" for="upload">
|
||||
<input type="file" class="upload-field" id="upload" @change="import_from_file">
|
||||
<p class="empty-title h5">Drop or select a file to import.</p>
|
||||
<p class="empty-subtitle">The file needs to be an export from a TinyCheck instance.</p>
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="tabs.bulk">
|
||||
<div class="columns">
|
||||
<div class="column col-4 col-xs-4">
|
||||
<div class="form-group">
|
||||
<select class="form-select" placeholder="test" v-model="tag">
|
||||
<option value="">IOC(s) Tag</option>
|
||||
<option v-for="t in tags" :value="t.tag" :key="t.tag">
|
||||
{{ t.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-4 col-xs-4">
|
||||
<div class="form-group">
|
||||
<select class="form-select width-full" placeholder="test" v-model="type">
|
||||
<option value="">IOC(s) Type</option>
|
||||
<option value="unknown">Multiple (regex parsing)</option>
|
||||
<option v-for="t in types" :value="t.type" :key="t.type">
|
||||
{{ t.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-4 col-xs-4">
|
||||
<div class="form-group">
|
||||
<select class="form-select width-full" placeholder="test" v-model="tlp">
|
||||
<option value="">IOC(s) TLP</option>
|
||||
<option value="white">TLP:WHITE</option>
|
||||
<option value="green">TLP:GREEN</option>
|
||||
<option value="amber">TLP:AMBER</option>
|
||||
<option value="red">TLP:RED</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea class="form-input" id="input-example-3" placeholder="Paste your Indicators of Compromise here" rows="15" v-model="iocs"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn-primary btn col-12" v-on:click="import_from_bulk()">Import the IOCs</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="imported.length>0">
|
||||
<div class="toast toast-success">
|
||||
✓ {{imported.length}} IOC<span v-if="errors.length>1">s</span> imported successfully.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="errors.length>0">
|
||||
<div class="form-group">
|
||||
<div class="toast toast-error">
|
||||
✗ {{errors.length}} IOC<span v-if="errors.length>1">s</span> not imported, see details below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Indicator</th>
|
||||
<th>Importation error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="e in errors" v-bind:key="e.ioc">
|
||||
<td>{{ e.ioc }}</td>
|
||||
<td>{{ e.message }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="type_tag_error==true">
|
||||
<div class="form-group">
|
||||
<div class="toast toast-error">
|
||||
✗ IOC(s) not imported, see details below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="empty">
|
||||
<p class="empty-title h5">Please select a tag and a type.</p>
|
||||
<p class="empty-subtitle">If different IOCs types, select "Unknown (regex parsing)".</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'manageiocs',
|
||||
data() {
|
||||
return {
|
||||
type:"",
|
||||
tag:"",
|
||||
tlp:"",
|
||||
iocs:"",
|
||||
types:[],
|
||||
tags:[],
|
||||
errors:[],
|
||||
imported:[],
|
||||
type_tag_error: false,
|
||||
wrong_ioc_file: false,
|
||||
tabs: { "bulk" : true, "file" : false, "export" : false },
|
||||
jwt:"",
|
||||
export_url:"",
|
||||
config: {},
|
||||
watcher: ""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
import_from_bulk: function() {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
if (this.tag != "" && this.type != "" && this.tlp != ""){
|
||||
this.iocs.match(/[^\r\n]+/g).forEach(ioc => {
|
||||
this.import_ioc(this.tag, this.type, this.tlp, ioc);
|
||||
});
|
||||
this.iocs = "";
|
||||
} else {
|
||||
this.type_tag_error = true
|
||||
}
|
||||
},
|
||||
import_ioc: function(tag, type, tlp, ioc) {
|
||||
if (ioc != "" && ioc.slice(0,1) != "#"){
|
||||
if("alert " != ioc.slice(0,6)) {
|
||||
ioc = ioc.trim()
|
||||
ioc = ioc.replace(" ", "")
|
||||
ioc = ioc.replace("[", "")
|
||||
ioc = ioc.replace("]", "")
|
||||
ioc = ioc.replace("\\", "")
|
||||
ioc = ioc.replace("(", "")
|
||||
ioc = ioc.replace(")", "")
|
||||
}
|
||||
axios.get(`/api/ioc/add/${type.trim()}/${tag.trim()}/${tlp.trim()}/${ioc}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.status){
|
||||
this.imported.push(response.data);
|
||||
} else if (response.data.message){
|
||||
this.errors.push(response.data);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
delete_watcher: function(watcher) {
|
||||
var i = this.config.watchers.indexOf(watcher);
|
||||
this.config.watchers.splice(i, 1);
|
||||
},
|
||||
add_watcher: function() {
|
||||
this.config.watchers.push(this.watcher);
|
||||
this.watcher = "";
|
||||
},
|
||||
enrich_selects: function() {
|
||||
axios.get(`/api/ioc/get/tags`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.tags) this.tags = response.data.tags
|
||||
})
|
||||
.catch(err => (console.log(err)));
|
||||
axios.get(`/api/ioc/get/types`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.types) this.types = response.data.types
|
||||
})
|
||||
.catch(err => (console.log(err)));
|
||||
},
|
||||
switch_tab: function(tab) {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
|
||||
Object.keys(this.tabs).forEach(key => {
|
||||
if( key == tab ){
|
||||
this.tabs[key] = true
|
||||
} else {
|
||||
this.tabs[key] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
import_from_file: function(ev) {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
|
||||
const file = ev.target.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = e => this.$emit("load", e.target.result);
|
||||
reader.onload = () => {
|
||||
try {
|
||||
JSON.parse(reader.result).iocs.forEach(ioc => {
|
||||
this.import_ioc(ioc["tag"], ioc["type"], ioc["tlp"], ioc["value"])
|
||||
})
|
||||
} catch (error) {
|
||||
this.wrong_ioc_file = true
|
||||
}
|
||||
|
||||
}
|
||||
reader.readAsText(file);
|
||||
},
|
||||
async get_jwt(){
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if(response.data.token){
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/config/list`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data) {
|
||||
this.config = response.data
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.enrich_selects();
|
||||
this.load_config();
|
||||
this.export_url = `/api/ioc/export?token=${this.jwt}`
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
114
app/backend/src/views/iocs-search.vue
Normal file
@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-8 col-xs-12">
|
||||
<h3 class="s-title">Search IOCs</h3>
|
||||
<div class="form-group">
|
||||
<textarea class="form-input" id="input-example-3" placeholder="Paste your IOCs here" rows="3" v-model="iocs"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary col-12" v-on:click="search_iocs()">Search</button>
|
||||
</div>
|
||||
<div class="form-group" v-if="results.length>0 ">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Indicator</th>
|
||||
<th>Type</th>
|
||||
<th>Tag</th>
|
||||
<th>TLP</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in results" :key="r.tlp">
|
||||
<td>{{ r.value }}</td>
|
||||
<td class="capi">{{ r.type }}</td>
|
||||
<td class="upper">{{ r.tag }}</td>
|
||||
<td><label :class="['tlp-' + r.tlp]">{{ r.tlp }}</label></td>
|
||||
<td><button class="btn btn-sm" v-on:click="remove(r)">Delete</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else-if="first_search==false">
|
||||
<div class="empty">
|
||||
<p class="empty-title h5">IOC<span v-if="this.iocs.match(/[^\r\n]+/g).length>1">s</span> not found.</p>
|
||||
<p class="empty-subtitle">Try wildcard search to expend your search.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'iocs-search',
|
||||
data() {
|
||||
return {
|
||||
results: [],
|
||||
first_search: true,
|
||||
jwt:""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
search_iocs: function() {
|
||||
this.results = []
|
||||
this.first_search = false
|
||||
this.iocs.match(/[^\r\n]+/g).forEach(ioc => {
|
||||
ioc = ioc.trim()
|
||||
if("alert " != ioc.slice(0,6)) {
|
||||
ioc = ioc.replace(" ", "")
|
||||
ioc = ioc.replace("[", "")
|
||||
ioc = ioc.replace("]", "")
|
||||
ioc = ioc.replace("\\", "")
|
||||
ioc = ioc.replace("(", "")
|
||||
ioc = ioc.replace(")", "")
|
||||
}
|
||||
axios.get(`/api/ioc/search/${ioc}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.results.length>0){
|
||||
this.results = [].concat(this.results, response.data.results);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
});
|
||||
return true;
|
||||
},
|
||||
remove: function(elem){
|
||||
axios.get(`/api/ioc/delete/${elem.id}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.status){
|
||||
this.results = this.results.filter(function(el) { return el != elem; });
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/config/list`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data) {
|
||||
this.config = response.data
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
async get_jwt(){
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if(response.data.token){
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt()
|
||||
}
|
||||
}
|
||||
</script>
|
116
app/backend/src/views/network-manage.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-6 col-xs-12">
|
||||
<h3 class="s-title">Network configuration</h3>
|
||||
<h5 class="s-subtitle">Interfaces configuration</h5>
|
||||
<img src="@/assets/network.png" id="network-thumbnail" />
|
||||
<div class="container interfaces-container">
|
||||
<div class="columns">
|
||||
<div class="column col-6">
|
||||
<span class="interface-label">First interface</span>
|
||||
<div class="form-group">
|
||||
<div class="btn-group btn-group-block">
|
||||
<button class="btn btn-sm btn-iface" @click="change_interface('in', iface)" :class="iface == config.network.in ? 'active' : ''" v-for="iface in config.interfaces" :key="iface">{{ iface.toUpperCase() }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-6">
|
||||
<span class="interface-label">Second interface</span>
|
||||
<div class="form-group">
|
||||
<div class="btn-group btn-group-block">
|
||||
<button class="btn btn-sm btn-iface" @click="change_interface('out', iface)" :class="iface == config.network.out ? 'active' : ''" v-for="iface in config.interfaces" :key="iface">{{ iface.toUpperCase() }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="s-subtitle">Edit SSIDs names</h5>
|
||||
<div class="form-group">
|
||||
<table class="table table-striped table-hover">
|
||||
<tbody>
|
||||
<tr v-for="ssid in config.network.ssids" :key="ssid">
|
||||
<td>{{ ssid }}</td>
|
||||
<td><button class="btn btn-sm" v-on:click="delete_ssid(ssid)">Delete</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input class="form-input" v-model="ssid" type="text" placeholder="SSID name"></td>
|
||||
<td><button class="btn btn-sm" @click="add_ssid()">Add</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'manageinterface',
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
ssid: ""
|
||||
}
|
||||
},
|
||||
props: {},
|
||||
methods: {
|
||||
async get_jwt() {
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if (response.data.token) {
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/config/list`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data) {
|
||||
this.config = response.data
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
delete_ssid: function(ssid) {
|
||||
var i = this.config.network.ssids.indexOf(ssid);
|
||||
this.config.network.ssids.splice(i, 1);
|
||||
this.update_ssids();
|
||||
},
|
||||
add_ssid: function() {
|
||||
this.config.network.ssids.push(this.ssid);
|
||||
this.ssid = "";
|
||||
this.update_ssids();
|
||||
},
|
||||
update_ssids: function() {
|
||||
axios.get(`/api/config/edit/network/ssids/${this.config.network.ssids.join("|")}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) {
|
||||
console.log(response.data)
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
change_interface: function(type, iface) {
|
||||
axios.get(`/api/config/edit/network/${type}/${iface}`, {
|
||||
timeout: 10000,
|
||||
headers: { 'X-Token': this.jwt }
|
||||
}).then(response => {
|
||||
if (response.data.status) this.config.network[type] = iface
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.load_config();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
191
app/backend/src/views/whitelist-manage.vue
Normal file
@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-6 col-xs-12">
|
||||
<h3 class="s-title">Manage whitelisted elements</h3>
|
||||
<ul class="tab tab-block">
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('bulk')" v-bind:class="{ active: tabs.bulk }">Bulk elements import</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('file')" v-bind:class="{ active: tabs.file }">Import from file</a>
|
||||
</li>
|
||||
<li class="tab-item">
|
||||
<a href="#" v-on:click="switch_tab('export')" v-bind:class="{ active: tabs.export }">Export elements</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="tabs.export">
|
||||
<iframe :src="export_url" class="frame-export"></iframe>
|
||||
</div>
|
||||
<div v-if="tabs.file">
|
||||
<label class="form-upload empty" for="upload">
|
||||
<input type="file" class="upload-field" id="upload" @change="import_from_file">
|
||||
<p class="empty-title h5">Drop or select a file to import.</p>
|
||||
<p class="empty-subtitle">The file needs to be an whitelist file export from a TinyCheck instance.</p>
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="tabs.bulk">
|
||||
<div class="form-group">
|
||||
<select class="form-select width-full" placeholder="test" v-model="type">
|
||||
<option value="">Elements Type</option>
|
||||
<option value="unknown">Multiple (regex parsing)</option>
|
||||
<option v-for="t in types" :value="t.type" :key="t.type">
|
||||
{{ t.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea class="form-input" id="input-example-3" placeholder="Paste the elements to be whitelisted here" rows="15" v-model="elements"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn-primary btn col-12" v-on:click="import_from_bulk()">Whitelist elements</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="imported.length>0">
|
||||
<div class="toast toast-success">
|
||||
✓ {{imported.length}} IOC<span v-if="errors.length>1">s</span> imported successfully.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="errors.length>0">
|
||||
<div class="form-group">
|
||||
<div class="toast toast-error">
|
||||
✗ {{errors.length}} IOC<span v-if="errors.length>1">s</span> not imported, see details below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Element</th>
|
||||
<th>Importation error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="e in errors" :key="e.element">
|
||||
<td>{{ e.element }}</td>
|
||||
<td>{{ e.message }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="type_tag_error==true">
|
||||
<div class="form-group">
|
||||
<div class="toast toast-error">
|
||||
✗ IOC(s) not imported, see details below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="empty">
|
||||
<p class="empty-title h5">Please select a tag and a type.</p>
|
||||
<p class="empty-subtitle">If different IOCs types, select "Unknown (regex parsing)".</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'manageiocs',
|
||||
data() {
|
||||
return {
|
||||
type:"",
|
||||
elements:"",
|
||||
types:[],
|
||||
errors:[],
|
||||
imported:[],
|
||||
wrong_wh_file: false,
|
||||
tabs: { "bulk" : true, "file" : false, "export" : false },
|
||||
jwt:"",
|
||||
export_url:""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
import_from_bulk: function() {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
if (this.type != ""){
|
||||
this.elements.match(/[^\r\n]+/g).forEach(elem => {
|
||||
this.import_element(this.type, elem);
|
||||
});
|
||||
this.elements = "";
|
||||
} else {
|
||||
this.type_tag_error = true
|
||||
}
|
||||
},
|
||||
import_element: function(type, elem) {
|
||||
if (elem != "" && elem.slice(0,1) != "#"){
|
||||
axios.get(`/api/whitelist/add/${type.trim()}/${elem.trim()}`, {
|
||||
timeout: 10000,
|
||||
headers: { "X-Token" : this.jwt }
|
||||
}).then(response => {
|
||||
if(response.data.status){
|
||||
this.imported.push(response.data);
|
||||
} else if (response.data.message){
|
||||
this.errors.push(response.data);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
enrich_types: function() {
|
||||
axios.get(`/api/whitelist/get/types`, { timeout: 10000, headers: {'X-Token': this.jwt} })
|
||||
.then(response => {
|
||||
if(response.data.types) this.types = response.data.types
|
||||
})
|
||||
.catch(err => (console.log(err)));
|
||||
},
|
||||
switch_tab: function(tab) {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
|
||||
Object.keys(this.tabs).forEach(key => {
|
||||
if( key == tab ){
|
||||
this.tabs[key] = true
|
||||
} else {
|
||||
this.tabs[key] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
import_from_file: function(ev) {
|
||||
this.errors = []
|
||||
this.imported = []
|
||||
|
||||
const file = ev.target.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = e => this.$emit("load", e.target.result);
|
||||
reader.onload = () => {
|
||||
try {
|
||||
JSON.parse(reader.result).elements.forEach(elem => {
|
||||
this.import_element(elem["type"], elem["element"])
|
||||
})
|
||||
} catch (error) {
|
||||
this.wrong_wh_file = true
|
||||
}
|
||||
|
||||
}
|
||||
reader.readAsText(file);
|
||||
},
|
||||
async get_jwt(){
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if(response.data.token){
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt().then(() => {
|
||||
this.enrich_types();
|
||||
this.export_url = `/api/whitelist/export?token=${this.jwt}`
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
94
app/backend/src/views/whitelist-search.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="backend-content" id="content">
|
||||
<div class="column col-6 col-xs-12">
|
||||
<h3 class="s-title">Search whitelisted elements</h3>
|
||||
<div class="form-group">
|
||||
<textarea class="form-input" id="input-example-3" placeholder="Paste the elements here" rows="3" v-model="elements"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary col-12" v-on:click="search_elements()">Search</button>
|
||||
</div>
|
||||
<div class="form-group" v-if="results.length>0 ">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Element</th>
|
||||
<th>Element type</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in results" :key="r.element">
|
||||
<td>{{ r.element }}</td>
|
||||
<td>{{ r.type }}</td>
|
||||
<td><button class="btn btn-sm" v-on:click="remove(r)">Delete</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else-if="first_search==false">
|
||||
<div class="empty">
|
||||
<p class="empty-title h5">Element<span v-if="this.elements.match(/[^\r\n]+/g).length>1">s</span> not found.</p>
|
||||
<p class="empty-subtitle">Try wildcard search to expend your search.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'elements-search',
|
||||
data() {
|
||||
return {
|
||||
results: [],
|
||||
first_search: true,
|
||||
jwt:""
|
||||
}
|
||||
},
|
||||
props: { },
|
||||
methods: {
|
||||
search_elements: function() {
|
||||
this.results = []
|
||||
this.first_search = false
|
||||
this.elements.match(/[^\r\n]+/g).forEach(elem => {
|
||||
axios.get(`/api/whitelist/search/${elem.trim()}`, {
|
||||
timeout: 10000,
|
||||
headers: {'X-Token': this.jwt}
|
||||
}).then(response => {
|
||||
if(response.data.results.length>0){
|
||||
this.results = [].concat(this.results, response.data.results);
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
});
|
||||
return true;
|
||||
},
|
||||
remove: function(elem){
|
||||
axios.get(`/api/whitelist/delete/${elem.id}`, {
|
||||
timeout: 10000,
|
||||
headers: {'X-Token': this.jwt}
|
||||
}).then(response => {
|
||||
if(response.data.status){
|
||||
this.results = this.results.filter(function(el) { return el != elem; });
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
async get_jwt(){
|
||||
await axios.get(`/api/get-token`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if(response.data.token){
|
||||
this.jwt = response.data.token
|
||||
}
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.get_jwt()
|
||||
}
|
||||
}
|
||||
</script>
|
11
app/backend/vue.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
devServer: {
|
||||
proxy: {
|
||||
'^/api': {
|
||||
target: 'https://localhost:5000',
|
||||
ws: true,
|
||||
changeOrigin: true
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|