First commit
@@ -0,0 +1,107 @@
|
||||
# Build
|
||||
dist/
|
||||
|
||||
# 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
|
||||
@@ -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/).
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
After Width: | Height: | Size: 805 KiB |
|
After Width: | Height: | Size: 784 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
@@ -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')
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
devServer: {
|
||||
proxy: {
|
||||
'^/api': {
|
||||
target: 'https://localhost:5000',
|
||||
ws: true,
|
||||
changeOrigin: true
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# tinycheck-new
|
||||
|
||||
## 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/).
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "tinycheck-new",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --copy --port=4202",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fnando/sparkline": "^0.3.10",
|
||||
"axios": "^0.20.0",
|
||||
"core-js": "^3.6.5",
|
||||
"sass": "^1.27.0",
|
||||
"sass-loader": "^10.0.4",
|
||||
"simple-keyboard": "^2.30.25",
|
||||
"vue": "^2.6.12",
|
||||
"vue-router": "^3.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.6",
|
||||
"@vue/cli-plugin-eslint": "~4.5.6",
|
||||
"@vue/cli-service": "~4.5.6",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.9.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.pythonPath": "/usr/local/opt/python@3.8/bin/python3.8"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view />
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import './assets/spectre.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>
|
||||
|
||||
<script>
|
||||
document.title = 'TinyCheck Frontend'
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,636 @@
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: "Roboto-Regular";
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#qrcode {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin-top:10px;
|
||||
margin-left:5px;
|
||||
border:1px #000;
|
||||
border-radius:5px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 0;
|
||||
box-shadow: 0 0.25rem 1rem rgba(48,55,66,.15);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.empty-subtitle {
|
||||
font-family: "Roboto";
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-family: 'Lobster';
|
||||
font-size:30px;
|
||||
}
|
||||
|
||||
/*
|
||||
The min Chrome with is not 480 but 500 so we have to define our app div
|
||||
with 480 width on a 480 screen.
|
||||
*/
|
||||
@media only screen and (max-width: 500px) {
|
||||
|
||||
#app {
|
||||
height: 320px;
|
||||
display:block;
|
||||
width: 480px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#tinycheck-logo {
|
||||
width:370px;
|
||||
}
|
||||
|
||||
.apcard {
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: 170px;
|
||||
display: block;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.sparklines-container {
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
flex-wrap: wrap;
|
||||
justify-content:center;
|
||||
align-items:center;
|
||||
align-content:center;
|
||||
width: 480px;
|
||||
}
|
||||
|
||||
.keyboardinput {
|
||||
padding:15px;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.warning-title {
|
||||
font-family: "lobster";
|
||||
font-weight: lighter;
|
||||
font-size: 28px;
|
||||
margin-bottom: .5em;
|
||||
margin-top: .5em;
|
||||
color:#FFF;
|
||||
text-shadow: 1px 2px 3px #0000005c;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.report-wrapper {
|
||||
width:90%;
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
.device-ctx {
|
||||
width:90%;
|
||||
margin:20px 0px 20px 10px;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
width:90%;
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
.high-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #e53935;
|
||||
}
|
||||
|
||||
.med-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f1602b;
|
||||
}
|
||||
|
||||
.low-wrapper, .none-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #56ab2f;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 501px) {
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
position:absolute;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.apcard {
|
||||
width: 500px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: fit-content;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#tinycheck-logo {
|
||||
width:500px;
|
||||
}
|
||||
|
||||
.warning-title {
|
||||
font-family: "lobster";
|
||||
font-weight: lighter;
|
||||
font-weight: bold;
|
||||
margin-bottom: .5em;
|
||||
margin-top: .5em;
|
||||
color:#FFF;
|
||||
text-shadow: 1px 2px 3px #0000005c;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.report-wrapper {
|
||||
width:60%;
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
.device-ctx {
|
||||
padding:15px;
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
.high-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, #e53935, #e35d5b);
|
||||
}
|
||||
|
||||
.med-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, #ff4b1f, #ff9068);
|
||||
}
|
||||
|
||||
.low-wrapper, .none-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, #56ab2f, #a8e063);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.width-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.center {
|
||||
width: fit-content;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: fit-content;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.light-grey {
|
||||
color:#999;
|
||||
}
|
||||
|
||||
.timer {
|
||||
font-size:40px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f7f8f9;
|
||||
}
|
||||
|
||||
footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding-left:40px;
|
||||
padding-right:40px;
|
||||
padding-bottom:40px;
|
||||
}
|
||||
|
||||
.container-padding-right {
|
||||
padding-left:40px;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
text-transform: uppercase;
|
||||
color : #999;
|
||||
font-size:12px;
|
||||
display: block;
|
||||
padding-bottom:10px;
|
||||
padding-top:30px;
|
||||
}
|
||||
|
||||
.keyboardinput {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
padding: 20px;
|
||||
font-size: 20px;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.legend {
|
||||
color:#9a9a9a;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #CCC;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Spectre CSS tweaks */
|
||||
|
||||
.btn {
|
||||
height: fit-content;
|
||||
border-radius: 5px;
|
||||
padding: .4rem .7rem;
|
||||
font-size: 1rem;
|
||||
color: #4c4c4c;
|
||||
background:#f7f8f9;
|
||||
border: 1px solid #4c4c4c;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
height: fit-content;
|
||||
border-radius: 5px;
|
||||
padding: .4rem .7rem;
|
||||
font-size: 1rem;
|
||||
color: #4c4c4c;
|
||||
background:#FFF;
|
||||
border: 1px solid #4c4c4c;
|
||||
}
|
||||
|
||||
.btn.active,.btn:active {
|
||||
height: fit-content;
|
||||
border-radius: 5px;
|
||||
padding: .4rem .7rem;
|
||||
font-size: 1rem;
|
||||
color: #4c4c4c;
|
||||
background:#FFF;
|
||||
border: 1px solid #4c4c4c;
|
||||
}
|
||||
|
||||
.btn.btn-light {
|
||||
background: #f9f9f9;
|
||||
border-color: #c1c1c1;
|
||||
color: #c1c1c1;
|
||||
}
|
||||
|
||||
.loadingsplash {
|
||||
margin-top:20px;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
animation: loading .5s infinite linear;
|
||||
background: 0 0;
|
||||
border: .1rem solid #000;
|
||||
border-radius: 50%;
|
||||
border-right-color: transparent;
|
||||
border-top-color: transparent;
|
||||
content: "";
|
||||
display: block;
|
||||
height: .8rem;
|
||||
left: 50%;
|
||||
margin-left: -.4rem;
|
||||
margin-top: -.4rem;
|
||||
opacity: 1;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: .8rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.loading.loading-lg::after {
|
||||
height: 1.6rem;
|
||||
margin-left: -.8rem;
|
||||
margin-top: -.8rem;
|
||||
width: 1.6rem;
|
||||
}
|
||||
|
||||
.divider-vert[data-content]::after, .divider[data-content]::after {
|
||||
background: #f7f8f9;
|
||||
}
|
||||
|
||||
.white-bg[data-content]::after, .white-bg[data-content]::after {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
#sparkline {
|
||||
stroke: #e8e8e8;
|
||||
fill: #f1f1f1;
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.capture-wrapper {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn.btn-primary {
|
||||
border-radius: 5px;
|
||||
border: 1px solid #333;
|
||||
color: #FAFAFA;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.btn.btn-primary:hover {
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgb(87, 87, 87);
|
||||
color: #FAFAFA;
|
||||
background-color: rgb(87, 87, 87);
|
||||
}
|
||||
|
||||
.btn.btn-primary:active {
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgb(87, 87, 87);
|
||||
color: #FAFAFA;
|
||||
background-color: rgb(87, 87, 87);
|
||||
}
|
||||
|
||||
.btn-report-high {
|
||||
border-radius: 5px;
|
||||
border: 2px solid #ffffff;
|
||||
color: #e34b49;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.btn-report-high:hover {
|
||||
border-radius: 5px;
|
||||
border: 2px solid #ffffff;
|
||||
color: #e34b49;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.btn-report-moderate {
|
||||
border-radius: 5px;
|
||||
border: 2px solid #ffffff;
|
||||
color: #ff4b1f;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.btn-report-moderate:hover {
|
||||
border-radius: 5px;
|
||||
border: 2px solid #ffffff;
|
||||
color: #ff4b1f;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.btn-report-low {
|
||||
border-radius: 5px;
|
||||
border: 2px solid #ffffff;
|
||||
color: #56ab2f;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.btn-report-low:hover {
|
||||
border-radius: 5px;
|
||||
border: 2px solid #ffffff;
|
||||
color: #56ab2f;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.btn-report-low-light {
|
||||
border-radius: 5px;
|
||||
border: 2px solid #ffffff;
|
||||
color: #ffffff;
|
||||
background-color:transparent;
|
||||
margin-right:10px;
|
||||
}
|
||||
|
||||
.btn-report-low-light:hover {
|
||||
border-radius: 5px;
|
||||
border: 2px solid #ffffff;
|
||||
color: #ffffff;
|
||||
background-color:transparent;
|
||||
margin-right:10px;
|
||||
}
|
||||
|
||||
.alert-body {
|
||||
background-color: #FFF;
|
||||
list-style: none;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #EEE;
|
||||
}
|
||||
|
||||
.alert-body>.title {
|
||||
display: block;
|
||||
padding: 5px 5px 5px 10px;
|
||||
}
|
||||
|
||||
.high-label {
|
||||
background-color: #e53d38;
|
||||
padding: 5px;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
border-radius: 3px 0px 0px 0px;
|
||||
margin: 0px;
|
||||
color: #FFF;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.moderate-label {
|
||||
background-color: #ff7e33eb;
|
||||
padding: 5px;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
border-radius: 3px 0px 0px 0px;
|
||||
margin: 0px;
|
||||
color: #FFF;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.low-label {
|
||||
background-color: #4fce0eb8;
|
||||
padding: 5px;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
border-radius: 3px 0px 0px 0px;
|
||||
margin: 0px;
|
||||
color: #FFF;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.description>i {
|
||||
font-style: inherit;
|
||||
color: #353535;
|
||||
padding: 2px 5px 2px 5px;
|
||||
border-radius: 5px;
|
||||
background-color: #F2F2F2;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
.alert-id {
|
||||
background-color: #636363;
|
||||
padding: 5px;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
border-radius: 0px 3px 0px 0px;
|
||||
margin: 0px;
|
||||
color: #FFF;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight:500;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
margin-top:20px;
|
||||
margin-bottom:20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#controls-analysis {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.lobster {
|
||||
font-family: 'lobster';
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.wifi-login {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.frame-download {
|
||||
width:1px;
|
||||
height:1px;
|
||||
border:0;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
border-color: inherit;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form-select:focus {
|
||||
border-color: inherit;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: inherit;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Used by the legend on analysis */
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn ease 1s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity:0;
|
||||
}
|
||||
100% {
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 805 KiB |
|
After Width: | Height: | Size: 784 KiB |
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="112" height="195" viewBox="0 0 112 195" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="3.5" y1="3.5" x2="3.50001" y2="191.5" stroke="black" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="7" width="105" height="195" fill="#F7F8F9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 294 B |
@@ -0,0 +1,16 @@
|
||||
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="40px" height="40px" viewBox="0 0 40 40" enable-background="new 0 0 40 40" xml:space="preserve">
|
||||
<path opacity="0.2" fill="#000" d="M20.201,5.169c-8.254,0-14.946,6.692-14.946,14.946c0,8.255,6.692,14.946,14.946,14.946
|
||||
s14.946-6.691,14.946-14.946C35.146,11.861,28.455,5.169,20.201,5.169z M20.201,31.749c-6.425,0-11.634-5.208-11.634-11.634
|
||||
c0-6.425,5.209-11.634,11.634-11.634c6.425,0,11.633,5.209,11.633,11.634C31.834,26.541,26.626,31.749,20.201,31.749z"/>
|
||||
<path fill="#f7f8f9" d="M26.013,10.047l1.654-2.866c-2.198-1.272-4.743-2.012-7.466-2.012h0v3.312h0
|
||||
C22.32,8.481,24.301,9.057,26.013,10.047z">
|
||||
<animateTransform attributeType="xml"
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 20 20"
|
||||
to="360 20 20"
|
||||
dur="0.5s"
|
||||
repeatCount="indefinite"/>
|
||||
</path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 970 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="106" height="106" viewBox="0 0 106 106" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="53" cy="53" r="53" fill="#40D8A1"/>
|
||||
<path d="M29 52.5L47.5 70.5" stroke="white" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="79" y1="40.0711" x2="48.0711" y2="71" stroke="white" stroke-width="10" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 377 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="548" height="199" viewBox="0 0 548 199" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="403" y="27" width="142" height="145" rx="8" fill="white" stroke="black" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M0 30C0 13.4315 13.4315 0 30 0H428C432.418 0 436 3.58172 436 8V191C436 195.418 432.418 199 428 199H30C13.4315 199 0 185.569 0 169V30Z" fill="black"/>
|
||||
<rect x="477" y="55" width="26" height="26" fill="white" stroke="black" stroke-width="6"/>
|
||||
<rect x="477" y="117" width="26" height="26" fill="white" stroke="black" stroke-width="6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 602 B |
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<circle cx="50" cy="50" r="0" fill="none" stroke="#f3f3f3" stroke-width="2">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="1.4925373134328357s" values="0;30" keyTimes="0;1" keySplines="0 0.2 0.8 1" calcMode="spline" begin="-0.7462686567164178s"></animate>
|
||||
<animate attributeName="opacity" repeatCount="indefinite" dur="1.4925373134328357s" values="1;0" keyTimes="0;1" keySplines="0.2 0 0.8 1" calcMode="spline" begin="-0.7462686567164178s"></animate>
|
||||
</circle>
|
||||
<circle cx="50" cy="50" r="0" fill="none" stroke="#d8dddf" stroke-width="2">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="1.4925373134328357s" values="0;30" keyTimes="0;1" keySplines="0 0.2 0.8 1" calcMode="spline"></animate>
|
||||
<animate attributeName="opacity" repeatCount="indefinite" dur="1.4925373134328357s" values="1;0" keyTimes="0;1" keySplines="0.2 0 0.8 1" calcMode="spline"></animate>
|
||||
</circle>
|
||||
<!-- [ldio] generated by https://loading.io/ --></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 88 KiB |
@@ -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')
|
||||
@@ -0,0 +1,62 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'loader',
|
||||
component: () => import('../views/splash-screen.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
component: () => import('../views/home.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/wifi-select',
|
||||
name: 'wifi-select',
|
||||
component: () => import('../views/wifi-select.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/generate-ap',
|
||||
name: 'generate-ap',
|
||||
component: () => import('../views/generate-ap.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/capture',
|
||||
name: 'capture',
|
||||
component: () => import('../views/capture.vue'),
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/save-capture',
|
||||
name: 'save-capture',
|
||||
component: () => import('../views/save-capture.vue'),
|
||||
props: true
|
||||
},
|
||||
{ path: '/analysis',
|
||||
name: 'analysis',
|
||||
component: () => import('../views/analysis.vue'),
|
||||
props: true
|
||||
},
|
||||
{ path: '/report',
|
||||
name: 'report',
|
||||
component: () => import('../views/report.vue'),
|
||||
props: true
|
||||
}
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div :class="keyboardClass"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Keyboard from "simple-keyboard";
|
||||
import "simple-keyboard/build/css/index.css";
|
||||
|
||||
export default {
|
||||
name: "SimpleKeyboard",
|
||||
props: {
|
||||
keyboardClass: {
|
||||
default: "simple-keyboard",
|
||||
type: String
|
||||
},
|
||||
input: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
keyboard: null
|
||||
}),
|
||||
mounted() {
|
||||
this.keyboard = new Keyboard({
|
||||
onChange: this.onChange,
|
||||
onKeyPress: this.onKeyPress
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onChange(input) {
|
||||
this.$emit("onChange", input);
|
||||
},
|
||||
onKeyPress(button) {
|
||||
this.$emit("onKeyPress", button);
|
||||
|
||||
/**
|
||||
* If you want to handle the shift and caps lock buttons
|
||||
*/
|
||||
if (button === "{shift}" || button === "{lock}") this.handleShift();
|
||||
},
|
||||
handleShift() {
|
||||
let currentLayout = this.keyboard.options.layoutName;
|
||||
let shiftToggle = currentLayout === "default" ? "shift" : "default";
|
||||
|
||||
this.keyboard.setOptions({
|
||||
layoutName: shiftToggle
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
input(input) {
|
||||
this.keyboard.setInput(input);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div class="center">
|
||||
<div v-if="question">
|
||||
<p>Do you want to analyze the captured communications?</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn" v-on:click="save_capture()">No, just save them</button> <button class="btn btn-primary" v-on:click="start_analysis()">Yes, let's do it</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="running">
|
||||
<img src="@/assets/loading.svg"/>
|
||||
<p class="legend" v-if="!long_waiting">Please wait during the analysis...</p>
|
||||
<p class="legend fade-in" v-if="long_waiting">Yes, it can take some time...</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from '../router'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'analysis',
|
||||
data() {
|
||||
return {
|
||||
question: true,
|
||||
running: false,
|
||||
check_alerts: false,
|
||||
long_waiting: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
capture_token: String
|
||||
},
|
||||
methods: {
|
||||
start_analysis: function() {
|
||||
this.question = false
|
||||
this.running = true
|
||||
setTimeout(function () { this.long_waiting = true }.bind(this), 15000);
|
||||
axios.get(`/api/analysis/start/${this.capture_token}`, { timeout: 60000 })
|
||||
.then(response => {
|
||||
if(response.data.message == "Analysis started")
|
||||
this.check_alerts = setInterval(() => { this.get_alerts(); }, 500);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
get_alerts: function() {
|
||||
axios.get(`/api/analysis/report/${this.capture_token}`, { timeout: 60000 })
|
||||
.then(response => {
|
||||
if(response.data.message != "No report yet"){
|
||||
clearInterval(this.check_alerts);
|
||||
this.long_waiting = false;
|
||||
this.running = false;
|
||||
router.replace({ name: 'report', params: { alerts : response.data.alerts,
|
||||
device : response.data.device,
|
||||
capture_token : this.capture_token } });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
save_capture: function() {
|
||||
var capture_token = this.capture_token
|
||||
router.replace({ name: 'save-capture', params: { capture_token: capture_token } });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="capture-wrapper">
|
||||
<svg id="sparkline" stroke-width="3" :width="sparkwidth" :height="sparkheight" v-if="sparklines"></svg>
|
||||
<div class="center">
|
||||
<div class="footer">
|
||||
<h3 class="timer">{{timer_hours}}:{{timer_minutes}}:{{timer_seconds}}</h3>
|
||||
<p>Intercepting the communications of {{device_name}}.</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn" :class="[ loading ? 'loading' : 'btn-primary', ]" v-on:click="stop_capture()">Stop the capture</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import router from '../router'
|
||||
import sparkline from '@fnando/sparkline'
|
||||
|
||||
export default {
|
||||
name: 'capture',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
timer_hours: "00",
|
||||
timer_minutes: "00",
|
||||
timer_seconds: "00",
|
||||
loading: false,
|
||||
stats_interval: false,
|
||||
chrono_interval: false,
|
||||
sparklines: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
capture_token: String,
|
||||
device_name: String
|
||||
},
|
||||
methods: {
|
||||
set_chrono: function() {
|
||||
this.chrono_interval = setInterval(() => { this.chrono(); }, 10);
|
||||
},
|
||||
stop_capture: function() {
|
||||
this.loading = true
|
||||
axios.get(`/api/network/ap/stop`, { timeout: 30000 })
|
||||
axios.get(`/api/capture/stop`, { timeout: 30000 })
|
||||
.then(response => (this.handle_finish(response.data)))
|
||||
},
|
||||
get_stats: function() {
|
||||
axios.get(`/api/capture/stats`, { timeout: 30000 })
|
||||
.then(response => (this.handle_stats(response.data)))
|
||||
},
|
||||
handle_stats: function(data) {
|
||||
if (data.packets.length) sparkline(document.querySelector("#sparkline"), data.packets);
|
||||
},
|
||||
handle_finish: function(data) {
|
||||
clearInterval(this.chrono_interval);
|
||||
clearInterval(this.stats_interval);
|
||||
if (data.status) {
|
||||
this.loading = false
|
||||
var capture_token = this.capture_token
|
||||
router.replace({ name: 'analysis', params: { capture_token: capture_token } });
|
||||
}
|
||||
},
|
||||
chrono: function() {
|
||||
var time = Date.now() - this.capture_start
|
||||
this.timer_hours = Math.floor(time / (60 * 60 * 1000));
|
||||
this.timer_hours = (this.timer_hours < 10) ? "0" + this.timer_hours : this.timer_hours
|
||||
time = time % (60 * 60 * 1000);
|
||||
this.timer_minutes = Math.floor(time / (60 * 1000));
|
||||
this.timer_minutes = (this.timer_minutes < 10) ? "0" + this.timer_minutes : this.timer_minutes
|
||||
time = time % (60 * 1000);
|
||||
this.timer_seconds = Math.floor(time / 1000);
|
||||
this.timer_seconds = (this.timer_seconds < 10) ? "0" + this.timer_seconds : this.timer_seconds
|
||||
},
|
||||
setup_sparklines: function() {
|
||||
axios.get(`/api/misc/config`, { timeout: 60000 })
|
||||
.then(response => {
|
||||
if(response.data.sparklines){
|
||||
this.sparklines = true
|
||||
this.sparkwidth = window.screen.width + "px";
|
||||
this.sparkheight = Math.trunc(window.screen.height / 5) + "px";
|
||||
this.stats_interval = setInterval(() => { this.get_stats(); }, 500);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
// Get the config for the sparklines.
|
||||
this.setup_sparklines()
|
||||
|
||||
// Start the chrono and get the first stats.
|
||||
this.capture_start = Date.now()
|
||||
this.set_chrono();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="center">
|
||||
<div v-if="(error == false)">
|
||||
<div v-if="ssid_name">
|
||||
<div class="card apcard" v-on:click="generate_ap()">
|
||||
<div class="columns">
|
||||
<div class="column col-5">
|
||||
<center><img :src="ssid_qr" id="qrcode"></center>
|
||||
</div>
|
||||
<div class="divider-vert white-bg" data-content="OR"></div>
|
||||
<div class="column col-5"><br />
|
||||
<span class="light-grey">Network name: </span><br />
|
||||
<h4>{{ ssid_name }}</h4>
|
||||
<span class="light-grey">Network password: </span><br />
|
||||
<h4>{{ ssid_password }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br /><br /><br /> <br /><br /><br /><br /><br /><br />
|
||||
<!-- Requite a CSS MEME for that shit :) -->
|
||||
<span class="legend">Tap the white frame to generate a new network.</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<img src="@/assets/loading.svg"/>
|
||||
<p class="legend">We generate an ephemeral network for you.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>
|
||||
<strong>Unfortunately, we got some issues.</strong>
|
||||
<br /><br />
|
||||
Please verify that you've two Wifi interfaces on your device<br />
|
||||
and restart it by clicking on the button below.<br />
|
||||
</p>
|
||||
<button class="btn" v-on:click="reboot()">Reboot the device</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import router from '../router'
|
||||
|
||||
export default {
|
||||
name: 'generate-ap',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
ssid_name: false,
|
||||
ssid_qr: false,
|
||||
ssid_password: false,
|
||||
capture_token: false,
|
||||
capture_start: false,
|
||||
interval: false,
|
||||
error: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generate_ap: function() {
|
||||
clearInterval(this.interval)
|
||||
this.ssid_name = false
|
||||
axios.get(`/api/network/ap/start`, { timeout: 30000 })
|
||||
.then(response => (this.show_ap(response.data)))
|
||||
},
|
||||
show_ap: function(data) {
|
||||
if (data.status) {
|
||||
this.ssid_name = data.ssid
|
||||
this.ssid_password = data.password
|
||||
this.ssid_qr = data.qrcode
|
||||
this.start_capture() // Start the capture before client connect.
|
||||
} else {
|
||||
this.error = true
|
||||
}
|
||||
},
|
||||
start_capture: function() {
|
||||
axios.get(`/api/capture/start`, { timeout: 30000 })
|
||||
.then(response => (this.get_capture_token(response.data)))
|
||||
},
|
||||
reboot: function() {
|
||||
axios.get(`/api/misc/reboot`, { timeout: 30000 })
|
||||
.then(response => { console.log(response)})
|
||||
},
|
||||
get_capture_token: function(data) {
|
||||
if (data.status) {
|
||||
this.capture_token = data.capture_token
|
||||
this.capture_start = Date.now()
|
||||
this.get_device()
|
||||
}
|
||||
},
|
||||
get_device: function() {
|
||||
this.interval = setInterval(() => {
|
||||
axios.get(`/api/device/get/${this.capture_token}`, { timeout: 30000 })
|
||||
.then(response => (this.check_device(response.data)))
|
||||
}, 500);
|
||||
},
|
||||
check_device: function(data) {
|
||||
if (data.status) {
|
||||
clearInterval(this.interval);
|
||||
var capture_token = this.capture_token
|
||||
var capture_start = this.capture_start
|
||||
var device_name = data.name
|
||||
router.replace({
|
||||
name: 'capture',
|
||||
params: {
|
||||
capture_token: capture_token,
|
||||
capture_start: capture_start,
|
||||
device_name: device_name
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.generate_ap();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="center">
|
||||
<h3 class="lobster">Welcome to TinyCheck.</h3>
|
||||
<p>We are going to help you to check your device.</p>
|
||||
<button class="btn btn-primary" v-on:click="next()">Let's start!</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from '../router'
|
||||
|
||||
export default {
|
||||
name: 'home',
|
||||
props: { saved_ssid: String, list_ssids: Array, internet: Boolean },
|
||||
methods: {
|
||||
next: function() {
|
||||
var saved_ssid = this.saved_ssid
|
||||
var list_ssids = this.list_ssids
|
||||
var internet = this.internet
|
||||
router.push({ name: 'wifi-select', params: { saved_ssid: saved_ssid, list_ssids: list_ssids, internet:internet } });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="results">
|
||||
<div v-if="alerts.high.length >= 1" class="high-wrapper">
|
||||
<div class="center">
|
||||
<h1 class="warning-title">You have {{ nb_translate(alerts.high.length) }} high alert,<br />your device seems to be compromised.</h1>
|
||||
<button class="btn btn-report-low-light" v-on:click="new_capture()">Start a new capture</button>
|
||||
<button class="btn btn-report-high" @click="show_report=true;results=false;">Show the full report</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="alerts.moderate.length >= 1" class="med-wrapper">
|
||||
<div class="center">
|
||||
<h1 class="warning-title">You have {{ nb_translate(alerts.moderate.length) }} moderate alerts,<br />your device might be compromised.</h1>
|
||||
<button class="btn btn-report-low-light" v-on:click="new_capture()">Start a new capture</button>
|
||||
<button class="btn btn-report-moderate" @click="show_report=true;results=false;">Show the full report</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="alerts.low.length >= 1" class="low-wrapper">
|
||||
<div class="center">
|
||||
<h1 class="warning-title">You have ony {{ nb_translate(alerts.moderate.low) }} low alerts,<br /> don't hesitate to check them.</h1>
|
||||
<button class="btn btn-report-low-light" v-on:click="new_capture()">Start a new capture</button>
|
||||
<button class="btn btn-report-low" @click="show_report=true;results=false;">Show the full report</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="none-wrapper">
|
||||
<div class="center">
|
||||
<h1 class="warning-title">Everything looks fine, zero alerts.</h1>
|
||||
<button class="btn btn-report-low-light" v-on:click="save_capture()">Save the capture</button>
|
||||
<button class="btn btn-report-low" v-on:click="new_capture()">Start a new capture</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="show_report" class="report-wrapper">
|
||||
<div class="device-ctx">
|
||||
<h3 style="margin: 0;">Report for {{device.name}}</h3>
|
||||
IP Address: {{device.ip_address}}<br />Mac Address: {{device.mac_address}}
|
||||
</div>
|
||||
<ul class="alerts">
|
||||
<li class="alert" v-for="alert in alerts.high" :key="alert.message">
|
||||
<span class="high-label">High</span><span class="alert-id">{{ alert.id }}</span>
|
||||
<div class="alert-body">
|
||||
<span class="title">{{ alert.title }}</span>
|
||||
<p class="description">{{ alert.description }}</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="alert" v-for="alert in alerts.moderate" :key="alert.message">
|
||||
<span class="moderate-label">Moderate</span><span class="alert-id">{{ alert.id }}</span>
|
||||
<div class="alert-body">
|
||||
<span class="title">{{ alert.title }}</span>
|
||||
<p class="description">{{ alert.description }}</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="alert" v-for="alert in alerts.low" :key="alert.message">
|
||||
<span class="low-label">Low</span><span class="alert-id">{{ alert.id }}</span>
|
||||
<div class="alert-body">
|
||||
<span class="title">{{ alert.title }}</span>
|
||||
<p class="description">{{ alert.description }}</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="columns" id="controls-analysis">
|
||||
<div class="column col-5">
|
||||
<button class="btn width-100" @click="$router.push('generate-ap')">Start a capture</button>
|
||||
</div>
|
||||
<div class="divider-vert column col-2" data-content="OR"></div>
|
||||
<div class="column col-5">
|
||||
<button class="btn btn btn-primary width-100" v-on:click="save_capture()">Save the report</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style>
|
||||
#app {
|
||||
overflow-y: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import router from '../router'
|
||||
|
||||
export default {
|
||||
name: 'report',
|
||||
data() {
|
||||
return {
|
||||
results: true,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
device: Object,
|
||||
alerts: Array,
|
||||
capture_token: String
|
||||
},
|
||||
methods: {
|
||||
save_capture: function() {
|
||||
var capture_token = this.capture_token
|
||||
router.replace({ name: 'save-capture', params: { capture_token: capture_token } });
|
||||
},
|
||||
new_capture: function() {
|
||||
router.push({ name: 'generate-ap' })
|
||||
},
|
||||
nb_translate: function(x) {
|
||||
var nbs = ['zero','one','two','three','four', 'five','six','seven','eight','nine', 'ten', 'eleven']
|
||||
try {
|
||||
return nbs[x];
|
||||
} catch (error)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="center" v-if="save_usb && init">
|
||||
<div class="canvas-anim" :class="{'anim-connect': !saved && !usb}" v-on:click="new_capture()">
|
||||
<div class="icon-spinner" v-if="!saved && usb"></div>
|
||||
<div class="icon-success" v-if="saved"></div>
|
||||
<div class="icon-usb"></div>
|
||||
<div class="icon-usb-plug"></div>
|
||||
</div>
|
||||
<p class="legend" v-if="!saved && !usb"><br />Please connect a USB key to save your capture.</p>
|
||||
<p class="legend" v-if="!saved && usb"><br />We are saving your capture.</p>
|
||||
<p class="legend" v-if="saved"><br />You can tap the USB key to start a new capture.</p>
|
||||
</div>
|
||||
<div class="center" v-else-if="!save_usb && init">
|
||||
<div>
|
||||
<p class="legend">The capture download is going to start...<br /><br /><br /></p>
|
||||
<button class="btn btn-primary" v-on:click="new_capture()">Start another capture</button>
|
||||
<iframe :src="download_url" class="frame-download"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.canvas-anim {
|
||||
height: 120px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
width: 205px;
|
||||
|
||||
&.anim-connect {
|
||||
width: 300px;
|
||||
|
||||
.icon-usb {
|
||||
-webkit-animation: slide-right 1s cubic-bezier(0.455, 0.030, 0.515, 0.955) infinite alternate both;
|
||||
animation: slide-right 1s cubic-bezier(0.455, 0.030, 0.515, 0.955) infinite alternate both;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-usb {
|
||||
background: url('../assets/icon_usb.svg') no-repeat 0 0;
|
||||
background-size: 200px auto;
|
||||
display: block;
|
||||
height: 120px;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 0;
|
||||
width: 200px;
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.icon-usb-plug {
|
||||
background: url('../assets/icon_plug_usb.svg') no-repeat 0 0;
|
||||
background-size: cover;
|
||||
display: block;
|
||||
height: 120px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -10px;
|
||||
width: 55px;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.icon-success {
|
||||
background: url('../assets/icon_success.svg') no-repeat 0 0;
|
||||
background-size: 80px auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 120px;
|
||||
top: -25px;
|
||||
left: -40px;
|
||||
width: 80px;
|
||||
z-index: 10;
|
||||
-webkit-animation: scale-down-center 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
|
||||
animation: scale-down-center 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
|
||||
}
|
||||
|
||||
.icon-spinner {
|
||||
background: url('../assets/icon_spinner.svg') no-repeat 0 0;
|
||||
background-color: #f7f8f9;
|
||||
border-radius: 40px;
|
||||
display: block;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: -20px;
|
||||
width: 40px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@-webkit-keyframes slide-right {
|
||||
0% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateX(75px);
|
||||
transform: translateX(75px);
|
||||
}
|
||||
}
|
||||
@keyframes slide-right {
|
||||
0% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateX(75px);
|
||||
transform: translateX(75px);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes scale-down-center {
|
||||
0% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(0.5);
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
@keyframes scale-down-center {
|
||||
0% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(0.5);
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import router from '../router'
|
||||
|
||||
export default {
|
||||
name: 'save-capture',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
usb: false,
|
||||
saved: false,
|
||||
save_usb: false,
|
||||
init: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
capture_token: String
|
||||
},
|
||||
methods: {
|
||||
check_usb: function() {
|
||||
axios.get(`/api/save/usb-check`, { timeout: 30000 })
|
||||
.then(response => {
|
||||
if(response.data.status) {
|
||||
this.usb = true
|
||||
clearInterval(this.interval)
|
||||
this.save_capture()
|
||||
}
|
||||
})
|
||||
},
|
||||
save_capture: function() {
|
||||
var capture_token = this.capture_token
|
||||
axios.get(`/api/save/save-capture/${capture_token}/usb`, { timeout: 30000 })
|
||||
.then(response => {
|
||||
if(response.data.status){
|
||||
this.saved = true
|
||||
this.timeout = setTimeout(() => router.push('/'), 60000);
|
||||
}
|
||||
})
|
||||
},
|
||||
new_capture: function() {
|
||||
clearTimeout(this.timeout);
|
||||
router.push({ name: 'generate-ap' })
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/misc/config`, { timeout: 60000 })
|
||||
.then(response => {
|
||||
if(response.data.download_links){
|
||||
this.init = true
|
||||
this.save_usb = false
|
||||
this.download_url = `/api/save/save-capture/${this.capture_token}/url`
|
||||
} else {
|
||||
this.init = true
|
||||
this.save_usb = true
|
||||
this.interval = setInterval(() => { this.check_usb() }, 500);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.load_config()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="center">
|
||||
<img src="@/assets/logo.png" id="tinycheck-logo" />
|
||||
<div class="loading loading-lg loadingsplash"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from '../router'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'splash-screen',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
list_ssids: [],
|
||||
internet: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// Check if the device is already connected to internet.
|
||||
internet_check: function() {
|
||||
axios.get(`/api/network/status`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
if (response.data.internet) this.internet = true
|
||||
this.get_wifi_networks()
|
||||
})
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
// Get the WiFi networks around the box.
|
||||
get_wifi_networks: function() {
|
||||
axios.get(`/api/network/wifi/list`, { timeout: 10000 })
|
||||
.then(response => (this.append_ssids(response.data.networks)))
|
||||
.catch(err => (console.log(err)))
|
||||
},
|
||||
// Handle the get_wifi_networks answer and call goto_home.
|
||||
append_ssids: function(networks) {
|
||||
this.list_ssids = networks
|
||||
this.goto_home()
|
||||
},
|
||||
// Pass the list of ssids and the internet status as a prop to the home view.
|
||||
goto_home: function() {
|
||||
var list_ssids = this.list_ssids
|
||||
var internet = this.internet
|
||||
router.replace({ name: 'home', params: { list_ssids: list_ssids, internet: internet } });
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.internet_check();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div :class="[ keyboard == false ? 'center' : '' ]">
|
||||
<div v-if="keyboard == false">
|
||||
<div v-if="have_internet">
|
||||
<p>You seem to be already connected to a network.<br />Do you want to use the current connection?</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn" @click="have_internet = false">No, use another</button> <button class="btn" :class="[ connecting ? 'loading' : '', success ? 'btn-success' : 'btn-primary', ]" @click="$router.push({ name: 'generate-ap' })">Yes, use it.</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="enter_creds" class="wifi-login">
|
||||
<div class="form-group">
|
||||
<select class="form-select" id="ssid-select" v-model="ssid">
|
||||
<option value="" selected>Wifi name</option>
|
||||
<option v-for="ssid in ssids" v-bind:key="ssid.ssid">
|
||||
{{ ssid.ssid }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input class="form-input" type="password" id="password" v-model="password" placeholder="Wifi password" v-on:click="keyboard = (virtual_keyboard)? true : false">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn width-100" :class="[ connecting ? 'loading' : '', success ? 'btn-success' : 'btn-primary', ]" v-on:click="wifi_setup()">{{ btnval }}</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn width-100" :class="[ refreshing ? 'loading' : '' ]" v-on:click="refresh_wifi_list()">Refresh networks list</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p><strong>You seem to not be connected to Internet.</strong><br />Please configure the Wi-Fi connection.</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="enter_creds = true">Ok, let's do that.</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<input :value="input" class="keyboardinput" @input="onInputChange" placeholder="Tap on the virtual keyboard to start">
|
||||
<SimpleKeyboard @onChange="onChange" @onKeyPress="onKeyPress" :input="input" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import router from '../router'
|
||||
import SimpleKeyboard from "./SimpleKeyboard";
|
||||
|
||||
export default {
|
||||
name: 'wifi-select',
|
||||
components: {
|
||||
SimpleKeyboard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
connecting: false,
|
||||
error: false,
|
||||
success: false,
|
||||
btnval: "Connect to it.",
|
||||
ssid: "",
|
||||
selected_ssid: false,
|
||||
password: "",
|
||||
keyboard: false,
|
||||
input: "",
|
||||
ssids: [],
|
||||
have_internet: false,
|
||||
enter_creds: false,
|
||||
virtual_keyboard: false,
|
||||
refreshing: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
saved_ssid: String,
|
||||
list_ssids: Array,
|
||||
internet: Boolean
|
||||
},
|
||||
methods: {
|
||||
wifi_connect: function() {
|
||||
axios.get(`/api/network/wifi/connect`, { timeout: 60000 })
|
||||
.then(response => {
|
||||
if (response.data.status) {
|
||||
this.success = true
|
||||
this.connecting = false
|
||||
this.btnval = "Wifi connected!"
|
||||
setTimeout(() => router.push('generate-ap'), 1000);
|
||||
} else {
|
||||
this.btnval = "Wifi not connected. Please retry."
|
||||
this.connecting = false
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
},
|
||||
wifi_setup: function() {
|
||||
if (this.ssid.length && this.password.length >= 8 ){
|
||||
axios.post(`/api/network/wifi/setup`, { ssid: this.ssid, password: this.password }, { timeout: 60000 })
|
||||
.then(response => {
|
||||
if(response.data.status) {
|
||||
this.connecting = true
|
||||
this.wifi_connect()
|
||||
} else {
|
||||
console.log(response.data.message)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
},
|
||||
load_config: function() {
|
||||
axios.get(`/api/misc/config`, { timeout: 60000 })
|
||||
.then(response => {
|
||||
this.virtual_keyboard = response.data.virtual_keyboard
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
},
|
||||
onChange(input) {
|
||||
this.input = input
|
||||
this.password = this.input;
|
||||
},
|
||||
onKeyPress(button) {
|
||||
if (button == "{enter}")
|
||||
this.keyboard = false
|
||||
},
|
||||
onInputChange(input) {
|
||||
this.input = input.target.value;
|
||||
},
|
||||
append_ssids: function(networks) {
|
||||
this.ssids = networks
|
||||
},
|
||||
refresh_wifi_list: function(){
|
||||
this.refreshing = true
|
||||
axios.get(`/api/network/wifi/list`, { timeout: 10000 })
|
||||
.then(response => {
|
||||
this.refreshing = false
|
||||
this.append_ssids(response.data.networks)
|
||||
}).catch(error => {
|
||||
this.refreshing = false
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.load_config()
|
||||
|
||||
this.have_internet = (this.internet) ? true : false
|
||||
this.keyboard = false
|
||||
|
||||
if (typeof this.list_ssids == "object" && this.list_ssids.length != 0){
|
||||
this.ssids = this.list_ssids
|
||||
} else {
|
||||
this.refresh_wifi_list()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
devServer: {
|
||||
proxy: {
|
||||
'^/api': {
|
||||
target: 'http://localhost:8040',
|
||||
ws: true,
|
||||
changeOrigin: true
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||