First commit

This commit is contained in:
Félix Aime
2020-11-24 19:45:03 +01:00
parent c042b71634
commit 513f6b1b02
130 changed files with 76873 additions and 0 deletions
+107
View File
@@ -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
+24
View File
@@ -0,0 +1,24 @@
# tinycheck-backend
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
+12010
View File
File diff suppressed because it is too large Load Diff
+44
View File
@@ -0,0 +1,44 @@
{
"name": "tinycheck-backend",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --copy --port=4201",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.20.0",
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-router": "^3.4.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+98
View File
@@ -0,0 +1,98 @@
<template>
<div class="backend-container off-canvas off-canvas-sidebar-show">
<div class="backend-navbar">
<a class="off-canvas-toggle btn btn-link btn-action" href="#sidebar">
<i class="icon icon-menu"></i>
</a>
</div>
<div class="backend-sidebar off-canvas-sidebar" id="sidebar">
<div class="backend-brand">
<h2 @click="$router.push('/')" class="title">TinyCheck</h2>
</div>
<div class="backend-nav">
<div class="accordion-container">
<div class="accordion">
<input id="accordion-configuration" type="checkbox" name="backend-accordion-checkbox" hidden="">
<label class="accordion-header c-hand" for="accordion-configuration">Manage Device</label>
<div class="accordion-body">
<ul class="menu menu-nav">
<li class="menu-item">
<span @click="$router.push('/device/configuration')">Device config</span>
</li>
<li class="menu-item">
<span @click="$router.push('/device/network')">Network config</span>
</li>
<li class="menu-item">
<span @click="$router.push('/device/db')">Manage database</span>
</li>
<!-- <li class="menu-item">
<span @click="$router.push('/device/user')">User configuration</a>
</li> -->
</ul>
</div>
</div>
<div class="accordion">
<input id="accordion-iocs" type="checkbox" name="backend-accordion-checkbox" hidden="">
<label class="accordion-header c-hand" for="accordion-iocs">Manage IOCs</label>
<div class="accordion-body">
<ul class="menu menu-nav">
<li class="menu-item">
<span @click="$router.push('/iocs/manage')">Manage IOCs</span>
</li>
<li class="menu-item">
<span @click="$router.push('/iocs/search')">Search IOCs</span>
</li>
</ul>
</div>
</div>
<div class="accordion">
<input id="accordion-whitelist" type="checkbox" name="backend-accordion-checkbox" hidden=""/>
<label class="accordion-header c-hand" for="accordion-whitelist">Manage Whitelist</label>
<div class="accordion-body">
<ul class="menu menu-nav">
<li class="menu-item">
<span @click="$router.push('/whitelist/manage')">Manage elements</span>
</li>
<li class="menu-item">
<span @click="$router.push('/whitelist/search')">Search elements</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="shortcuts">
<a href="https://github.com/KasperskyLab/tinycheck"><img src="@/assets/github.png" class="shortcut" /></a>
<a href="https://twitter.com/tinycheck"><img src="@/assets/twitter.png" class="shortcut" /></a>
</div>
</div>
<a class="off-canvas-overlay" href="#close"></a>
<div class="off-canvas-content">
<transition name="fade" mode="out-in">
<router-view/>
</transition>
</div>
</div>
</template>
<script>
document.title = 'TinyCheck Backend'
</script>
<style>
@import './assets/spectre.min.css';
@import './assets/spectre-exp.min.css';
@import './assets/spectre-icons.min.css';
@import './assets/custom.css';
/* Face style for router stuff. */
.fade-enter-active,
.fade-leave-active {
transition-duration: 0.3s;
transition-property: opacity;
transition-timing-function: ease;
}
.fade-enter,
.fade-leave-active {
opacity: 0
}
</style>
+653
View File
@@ -0,0 +1,653 @@
/*
This CSS was forked from the awsome Spectre.css docs.
Spectre.css Docs | MIT License | github.com/picturepan2/spectre
*/
.off-canvas .off-canvas-toggle {
font-size: 1rem;
left: 1.5rem;
position: fixed;
top: 1rem
}
.off-canvas .off-canvas-sidebar {
width: 12rem
}
.off-canvas .off-canvas-content {
padding: 0
}
.backend-container {
min-height: 100vh
}
.backend-navbar {
height: 3.8rem;
position: fixed;
right: 0;
top: 0;
z-index: 100
}
.backend-navbar .btns {
position: absolute;
right: 1.5rem;
top: 1rem;
width: 14rem
}
.backend-navbar .algolia-autocomplete {
-ms-flex: 1 1 auto;
flex: 1 1 auto
}
.backend-sidebar .backend-nav {
bottom: 1.5rem;
-webkit-overflow-scrolling: touch;
overflow-y: auto;
padding: .5rem 1.5rem;
position: fixed;
top: 3.5rem;
width: 12rem
}
.backend-sidebar .accordion {
margin-bottom: .75rem
}
.backend-sidebar .accordion input~.accordion-header {
color: #455060;
font-size: .65rem;
font-weight: 600;
text-transform: uppercase
}
.backend-sidebar .accordion input:checked~.accordion-header {
color: #505c6e
}
.backend-sidebar .accordion .menu .menu-item {
font-size: .7rem;
padding-left: 1rem;
cursor:pointer;
}
.backend-sidebar .accordion .menu .menu-item>a {
background: 0 0;
color: #66758c;
display: inline-block
}
.backend-content {
-ms-flex: 1 1 auto;
flex: 1 1 auto;
padding: 0 4rem;
width: calc(100vw - 12rem)
}
.backend-content>.container {
margin-left: 0;
max-width: 800px;
padding-bottom: 1.5rem
}
.backend-content .anchor {
color: #6362dc;
display: none;
margin-left: .2rem;
padding: 0 .2rem
}
.backend-content .anchor:focus,
.backend-content .anchor:hover {
display: inline;
text-decoration: none
}
.backend-content .s-subtitle,
.backend-content .s-title {
line-height: 1.8rem;
margin-bottom: 0;
padding-bottom: 1rem;
padding-top: 1rem;
position: static
}
@supports ((position:-webkit-sticky) or (position:sticky)) {
.backend-content .s-subtitle,
.backend-content .s-title {
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 99
}
.backend-content .s-subtitle::before,
.backend-content .s-title::before {
background: #fff;
bottom: 0;
content: "";
display: block;
left: -10px;
position: absolute;
right: -10px;
top: -5px;
z-index: -1
}
}
.backend-content .s-subtitle:hover .anchor,
.backend-content .s-title:hover .anchor {
display: inline
}
.backend-content .s-subtitle+.backend-note,
.backend-content .s-title+.backend-note {
margin-top: .4rem
}
.backend-content .backend-demo {
padding-bottom: 1rem;
padding-top: 1rem
}
.backend-content .backend-demo .card {
border: 0;
box-shadow: 0 .25rem 1rem rgba(48, 55, 66, .15);
height: 100%
}
.backend-content .column {
padding: .4rem
}
.backend-content .backend-block {
border-radius: .1rem;
padding: .4rem
}
.backend-content .backend-block.bg-gray {
background: #eef0f3
}
.backend-content .backend-shape {
height: 4.8rem;
line-height: 1.2rem;
padding: 1.8rem 0;
width: 4.8rem
}
.backend-content .backend-dot {
border-radius: 50%;
display: inline-block;
height: .5rem;
padding: 0;
width: .5rem
}
.backend-content .backend-table td,
.backend-content .backend-table th {
padding: .75rem .25rem
}
.backend-content .backend-color {
border-radius: .1rem;
margin: .25rem 0;
padding: 5rem .5rem .5rem
}
.backend-content .backend-color .color-subtitle {
font-size: .7rem;
opacity: .75
}
.backend-content .code .hljs-tag {
color: #505c6e
}
.backend-content .code .hljs-comment {
color: #bcc3ce
}
.backend-content .code .hljs-class,
.backend-content .code .hljs-number,
.backend-content .code .hljs-string,
.backend-content .code .hljs-title {
color: #5755d9
}
.backend-content .code .hljs-attribute,
.backend-content .code .hljs-built_in,
.backend-content .code .hljs-keyword,
.backend-content .code .hljs-name,
.backend-content .code .hljs-variable {
color: #d73e48
}
.backend-content .code .hljs-hexcolor,
.backend-content .code .hljs-value {
color: #505c6e
}
.backend-content .c-select-all {
-webkit-user-select: all;
-moz-user-select: all;
-ms-user-select: all;
user-select: all
}
.backend-content .panel {
height: 75vh
}
.backend-content .panel .tile {
margin: .75rem 0
}
.backend-content .parallax {
margin: 2rem auto
}
.backend-content .form-autocomplete .menu {
position: static
}
.backend-content .example-tile-icon {
align-content: space-around;
align-items: center;
background: #5755d9;
border-radius: .1rem;
color: #fff;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
-ms-flex-line-pack: distribute;
font-size: 1.2rem;
height: 2rem;
width: 2rem
}
.backend-content .example-tile-icon .icon {
margin: auto
}
.backend-content .comparison-slider {
height: auto;
padding-bottom: 56.2222%
}
.backend-content .comparison-slider .filter-grayscale {
filter: grayscale(75%)
}
.backend-content .off-canvas {
position: relative
}
.backend-content .off-canvas .off-canvas-toggle {
left: .4rem;
position: absolute;
top: .4rem;
z-index: 1
}
.backend-brand {
color: #CCC;
height: 2rem;
left: 1.5rem;
position: fixed;
top: .85rem
}
.backend-brand .backend-logo {
align-items: center;
border-radius: .1rem;
display: -ms-inline-flexbox;
display: inline-flex;
-ms-flex-align: center;
font-size: .7rem;
height: 2rem;
padding: .2rem;
width: auto
}
.backend-brand .backend-logo:focus,
.backend-brand .backend-logo:hover {
text-decoration: none
}
.backend-brand .backend-logo img {
display: inline-block;
height: auto;
width: 1.6rem
}
.backend-brand .backend-logo h2 {
display: inline-block;
font-size: .8rem;
font-weight: 700;
line-height: 1.5rem;
margin-bottom: 0;
margin-left: .5rem;
margin-right: .3rem
}
.backend-footer {
color: #bcc3ce;
padding: .5rem
}
.backend-footer a {
color: #66758c
}
@media (max-width:960px) {
.off-canvas .off-canvas-toggle {
z-index: 300
}
.off-canvas .off-canvas-content {
width: 100%
}
.backend-sidebar .backend-brand {
margin: .85rem 1.5rem;
padding: 0;
position: static
}
.backend-sidebar .backend-nav {
margin-top: 1rem;
position: static
}
.backend-sidebar .menu .menu-item>a {
padding: .3rem .4rem
}
.backend-navbar {
-webkit-backdrop-filter: blur(5px);
backdrop-filter: blur(5px);
background: rgba(247, 248, 249, .65);
left: 0
}
.backend-content {
min-width: auto;
padding: 0 1.5rem;
width: 100%
}
.backend-content .s-subtitle,
.backend-content .s-title {
padding-top: 5rem;
position: static
}
.backend-content .s-subtitle::before,
.backend-content .s-title::before {
content: none
}
}
@media (max-width:600px) {
.off-canvas .off-canvas-toggle {
left: .5rem
}
.backend-navbar .btns {
right: .9rem
}
.backend-sidebar .backend-brand {
margin: .85rem 1rem
}
.backend-sidebar .backend-nav {
padding: .5rem 1rem
}
.backend-content {
padding: 0 .5rem
}
.backend-content .backend-block {
padding: .4rem .1rem
}
}
/* Ok, here the CSS specific to TinyCheck */
@font-face {
font-family: 'Lobster';
font-weight: normal;
src: url('fonts/lobster.ttf') format('truetype');
}
@font-face {
font-family: "Roboto-Bold";
src: url("fonts/Roboto-Bold.eot"); /* IE9 Compat Modes */
src: url("fonts/Roboto-Bold.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("fonts/Roboto-Bold.otf") format("opentype"), /* Open Type Font */
url("fonts/Roboto-Bold.svg") format("svg"), /* Legacy iOS */
url("fonts/Roboto-Bold.ttf") format("truetype"), /* Safari, Android, iOS */
url("fonts/Roboto-Bold.woff") format("woff"), /* Modern Browsers */
url("fonts/Roboto-Bold.woff2") format("woff2"); /* Modern Browsers */
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "Roboto-Regular";
src: url("fonts/Roboto-Regular.eot"); /* IE9 Compat Modes */
src: url("fonts/Roboto-Regular.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("fonts/Roboto-Regular.otf") format("opentype"), /* Open Type Font */
url("fonts/Roboto-Regular.svg") format("svg"), /* Legacy iOS */
url("fonts/Roboto-Regular.ttf") format("truetype"), /* Safari, Android, iOS */
url("fonts/Roboto-Regular.woff") format("woff"), /* Modern Browsers */
url("fonts/Roboto-Regular.woff2") format("woff2"); /* Modern Browsers */
font-weight: normal;
font-style: normal;
}
h1, h2, h3 {
font-family: Lobster;
color: #484848;
}
h4, h5 {
font-family: "Roboto-Bold";
color: #484848;
}
.btn {
border-radius: 5px;
}
.btn:focus, .btn:hover {
background: #494949;
border-color: #494949;
text-decoration: none;
color: #DBDBDB;
}
.px150 {
width: 150px;
}
.width-full {
width: 100%;
}
.tab-block {
margin-bottom: 35px;
}
.toast {
margin-top: 5px;
margin-bottom: 5px;
}
.frame-export {
display: none;
}
.form-upload {
position: relative;
display: block;
}
.form-upload .upload-field {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
opacity: 0;
z-index:1000;
width:100%;
height:100%;
}
.tlp-white {
font-size: .6rem;
height: 1.4rem;
padding: .05rem .3rem;
background-color: #FFF;
line-height: 1.2rem;
text-transform: uppercase;
font-weight: bold;
border: 2px solid #efefef;
vertical-align: middle;
border-radius: .1rem;
}
.tlp-green {
font-size: .6rem;
height: 1.4rem;
padding: .05rem .3rem;
background-color: #199a09cf;
line-height: 1.2rem;
text-transform: uppercase;
font-weight: bold;
border: 2px solid #0f8600cf;
vertical-align: middle;
color:#FFF;
border-radius: .1rem;
}
.tlp-amber {
font-size: .6rem;
height: 1.4rem;
padding: .05rem .3rem;
background-color: #ffc000cf;
line-height: 1.2rem;
text-transform: uppercase;
font-weight: bold;
border: 2px solid #ffc000cf;
vertical-align: middle;
color:#FFF;
border-radius: .1rem;
}
.tlp-red {
font-size: .6rem;
height: 1.4rem;
padding: .05rem .3rem;
background-color: #ff0033;
line-height: 1.2rem;
text-transform: uppercase;
font-weight: bold;
border: 2px solid #ff0033;
vertical-align: middle;
color:#FFF;
border-radius: .1rem;
}
.title {
font-family: 'Lobster';
color: #c5c5c5;
cursor: pointer;
width: fit-content;
height: 2rem;
left: 1.5rem;
position: fixed;
top: .85rem
}
#network-thumbnail {
width: 100%;
margin-top: 10px;
}
.interfaces-container {
margin-top:20px;
margin-bottom:40px;
}
.interface-label {
text-transform: uppercase;
font-size: 12px;
font-weight: bold;
margin-bottom: 10px;
text-align: center;
display: block;
}
.form-control:focus {
border-color: inherit;
-webkit-box-shadow: none;
box-shadow: none;
}
.form-input:focus {
border-color: inherit;
-webkit-box-shadow: none;
box-shadow: none;
}
.tab .tab-item a:focus {
box-shadow: none;
}
.shortcuts {
position: fixed;
bottom: 0;
left: 0;
z-index: 20000;
padding: 20px;
opacity: .2;
}
.shortcut {
width: 40px;
height: 40px;
margin-left: 10px;
cursor: pointer;
}
.whitespace {
height:100px;
display:block;
}
.alert-toaster-visible {
position:fixed;
right:15px;
top:15px;
padding:10px;
background-color: #484848;
border-radius: 5px;
color: #ccc;
font-size: 12px;
visibility: visible;
opacity: 1;
transition: opacity .5s linear;
}
.alert-toaster-hidden {
visibility: hidden;
opacity: 0;
transition: visibility 0s .5s, opacity .5s linear;
}
.comment-block {
padding: 10px;
font-size: 12px;
background-color: #FAFAFA;
}
.capi {
text-transform: capitalize;
}
.upper {
text-transform: uppercase;
}
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 805 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

+10
View File
@@ -0,0 +1,10 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = true
Vue.config.devtools = true
new Vue({
router,
render: h => h(App)
}).$mount('#app')
+63
View File
@@ -0,0 +1,63 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'default',
component: () => import('../views/home.vue'),
props: true
},
{
path: '/device/configuration',
name: 'device-configuration',
component: () => import('../views/edit-configuration.vue'),
props: true
},
{
path: '/device/network',
name: 'device-network',
component: () => import('../views/network-manage.vue'),
props: true
},
{
path: '/device/db',
name: 'db-manage',
component: () => import('../views/db-manage.vue'),
props: true
},
{
path: '/iocs/manage',
name: 'iocs-manage',
component: () => import('../views/iocs-manage.vue'),
props: true
},
{
path: '/iocs/search',
name: 'iocs-search',
component: () => import('../views/iocs-search.vue'),
props: true
},
{
path: '/whitelist/manage',
name: 'whitelist-manage',
component: () => import('../views/whitelist-manage.vue'),
props: true
},
{
path: '/whitelist/search',
name: 'whitelist-search',
component: () => import('../views/whitelist-search.vue'),
props: true
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
+75
View File
@@ -0,0 +1,75 @@
<template>
<div class="backend-content" id="content">
<div class="column col-6 col-xs-12">
<h3 class="s-title">Manage database</h3>
<ul class="tab tab-block">
<li class="tab-item">
<a href="#" v-on:click="switch_tab('import')" v-bind:class="{ active: tabs.import }">Import database</a>
</li>
<li class="tab-item">
<a href="#" v-on:click="switch_tab('export')" v-bind:class="{ active: tabs.export }">Export database</a>
</li>
</ul>
<div v-if="tabs.export">
<iframe :src="export_url" class="frame-export"></iframe>
</div>
<div v-if="tabs.import">
<label class="form-upload empty" for="upload">
<input type="file" class="upload-field" id="upload" @change="import_from_file">
<p class="empty-title h5">Drop or select a database to import.</p>
<p class="empty-subtitle">The database needs to be an export from a TinyCheck instance.</p>
</label>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'db-manage',
data() {
return {
tabs: { "import" : true, "export" : false },
jwt:""
}
},
props: { },
methods: {
switch_tab: function(tab) {
Object.keys(this.tabs).forEach(key => {
if( key == tab ){
this.tabs[key] = true
} else {
this.tabs[key] = false
}
});
},
import_from_file: function(ev) {
var formData = new FormData();
formData.append("file", ev.target.files[0]);
axios.post('/api/config/db/import', formData, {
headers: {
"Content-Type" : "multipart/form-data",
"X-Token" : this.jwt
}
})
},
async get_jwt(){
await axios.get(`/api/get-token`, { timeout: 10000 })
.then(response => {
if(response.data.token){
this.jwt = response.data.token
}
})
.catch(err => (console.log(err)))
}
},
created: function() {
this.get_jwt().then(() => {
this.export_url = `/api/config/db/export?token=${this.jwt}`
});
}
}
</script>
@@ -0,0 +1,202 @@
<template>
<div class="backend-content" id="content">
<div v-bind:class="{ 'alert-toaster-visible' : toaster.show, 'alert-toaster-hidden' : !toaster.show }">{{toaster.message}}</div>
<div class="modal active" id="modal-id" v-if="check_certificate">
<a href="#close" class="modal-overlay" aria-label="Close"></a>
<div class="modal-container">
<div class="modal-header">
<a href="#close" class="btn btn-clear float-right" aria-label="Close" @click="check_certificate = false"></a>
<div class="modal-title h5">Certificate validation</div>
</div>
<div class="modal-body">
<div class="content">
Do you trust this certificate?
<pre class="code" data-lang="CERTIFICATE">
<code>{{certificate}}</code>
</pre>
</div>
</div>
<div class="modal-footer">
<div class="modal-footer">
<button class="btn btn-primary" @click="validate_server()">Yes I trust it.</button><a class="btn btn-link" href="#modals" @click="no_trust()">No I don't trust it</a>
</div>
</div>
</div>
</div>
<div class="column col-6 col-xs-12">
<h3 class="s-title">Configuration </h3>
<h5 class="s-subtitle">Device configuration</h5>
<div class="form-group">
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'kiosk_mode')" v-model="config.frontend.kiosk_mode">
<i class="form-icon"></i> Use TinyCheck in Kiosk-mode.
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'virtual_keyboard')" v-model="config.frontend.virtual_keyboard">
<i class="form-icon"></i> Use virtual keyboard (for touch screen)
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'hide_mouse')" v-model="config.frontend.hide_mouse">
<i class="form-icon"></i> Hide mouse (for touch screen)
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('network', 'tokenized_ssids')" v-model="config.network.tokenized_ssids">
<i class="form-icon"></i> Use tokenized SSIDs (eg. [ssid-name]-[hex-str]).
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'download_links')" v-model="config.frontend.download_links">
<i class="form-icon"></i> Use in-browser download for network captures.
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'sparklines')" v-model="config.frontend.sparklines">
<i class="form-icon"></i> Show background sparklines during the capture.
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'remote_access')" v-model="config.frontend.remote_access">
<i class="form-icon"></i> Allow remote access to the frontend.
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('backend', 'remote_access')" v-model="config.backend.remote_access">
<i class="form-icon"></i> Allow remote access to the backend.
</label>
</div>
<h5 class="s-subtitle">Analysis configuration</h5>
<div class="form-group">
<label class="form-switch">
<input type="checkbox" @change="local_analysis('analysis', 'heuristics')" v-model="config.analysis.heuristics">
<i class="form-icon"></i> Use heuristic detection for suspect behaviour.
</label>
<label class="form-switch">
<input type="checkbox" @change="local_analysis('analysis', 'iocs')" v-model="config.analysis.iocs">
<i class="form-icon"></i> Use Indicator of Compromise (IoC) based detection.
</label>
<label class="form-switch">
<input type="checkbox" @change="local_analysis('analysis', 'whitelist')" v-model="config.analysis.whitelist">
<i class="form-icon"></i> Use whitelist to prevent false positives.
</label>
</div>
<h5 class="s-subtitle">User credentials</h5>
<div class="form-group">
<div class="column col-10 col-xs-12">
<div class="form-group">
<label class="form-label" for="user-login">User login</label>
<div class="input-group">
<input class="form-input" id="user-login" type="text" v-model="config.backend.login">
<button class="btn btn-primary input-group-btn px150" @click="change_login()">Update it</button>
</div>
</div>
<div class="form-group">
<label class="form-label" for="user-login">User password</label>
<div class="input-group">
<input class="form-input" id="user-login" type="password" placeholder="●●●●●●" v-model="config.backend.password">
<button class="btn btn-primary input-group-btn px150" @click="change_password()">Update it</button>
</div>
</div>
<div class="whitespace"></div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'edit-configuration',
data() {
return {
config: {},
check_certificate: false,
certificate: "",
toaster: { show: false, message : "", type : null }
}
},
props: {},
methods: {
switch_config: function(cat, key) {
axios.get(`/api/config/switch/${cat}/${key}`, {
timeout: 10000,
headers: { 'X-Token': this.jwt }
}).then(response => {
if (response.data.status) {
if (response.data.message == "Key switched to true") {
this.toaster = { show : true, message : "Configuration updated", type : "success" }
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
this.config[cat][key] = true
} else if (response.data.message == "Key switched to false") {
this.toaster = { show : true, message : "Configuration updated", type : "success" }
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
this.config[cat][key] = false
} else {
this.toaster = { show : true, message : "The key doesn't exist", type : "error" }
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
}
}
})
.catch(err => (console.log(err)))
},
load_config: function() {
axios.get(`/api/config/list`, {
timeout: 10000,
headers: { 'X-Token': this.jwt }
}).then(response => {
if (response.data) {
this.config = response.data
this.config.backend.password = ""
}
})
.catch(err => (console.log(err)))
},
async get_jwt() {
await axios.get(`/api/get-token`, { timeout: 10000 })
.then(response => {
if (response.data.token) {
this.jwt = response.data.token
}
})
.catch(err => (console.log(err)))
},
local_analysis: function(cat, key) {
this.switch_config(cat, key);
if (this.config.analysis.remote != false)
this.switch_config("analysis", "remote");
},
change_login: function() {
axios.get(`/api/config/edit/backend/login/${this.config.backend.login}`, {
timeout: 10000,
headers: { 'X-Token': this.jwt }
}).then(response => {
if (response.data.status) {
this.toaster = { show : true, message : "Login changed", type : "success" }
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
} else {
this.toaster = { show : true, message : "Login not changed", type : "error" }
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
}
})
.catch(err => (console.log(err)))
},
change_password: function() {
axios.get(`/api/config/edit/backend/password/${this.config.backend.password}`, {
timeout: 10000,
headers: { 'X-Token': this.jwt }
}).then(response => {
if (response.data.status) {
this.toaster = { show : true, message : "Password changed", type : "success" }
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
} else {
this.toaster = { show : true, message : "Password not changed", type : "error" }
setTimeout(function () { this.toaster = { show : false } }.bind(this), 1000)
}
})
.catch(err => (console.log(err)))
}
},
created: function() {
this.get_jwt().then(() => {
this.load_config();
});
}
}
</script>
+19
View File
@@ -0,0 +1,19 @@
<template>
<div class="backend-content" id="content">
<div class="column col-6 col-xs-12">
<div class="container">
<h3 class="s-title">Getting started with TinyCheck</h3>
<img src="@/assets/network-home.png" id="network-thumbnail" />
<p>TinyCheck allows you to capture easily internet communications from a smartphone or any device which can be associated to a Wi-Fi access point in order to quickly analyze and save them.
This can be used to verify if there is any suspect or malicious communication from a smartphone, by using heuristics or specific Indicators of Compromise (IoCs).</p>
<p>This little backend allows you to manage the configuration of your TinyCheck instance. You can push some IOCs for detection and whitelist elements which can be seen during legit communications in order to prevent false positives.</p>
</div>
<div class="backend-footer container grid-lg" id="copyright">
<p>Created with <span class="text-error">&hearts;</span> by <a href="https://twitter.com/felixaime" target="_blank">Félix Aimé</a>.</p>
</div>
</div>
</div>
</template>
+254
View File
@@ -0,0 +1,254 @@
<template>
<div class="backend-content" id="content">
<div class="column col-6 col-xs-12">
<h3 class="s-title">Manage IOCs</h3>
<ul class="tab tab-block">
<li class="tab-item">
<a href="#" v-on:click="switch_tab('bulk')" v-bind:class="{ active: tabs.bulk }">Bulk import</a>
</li>
<li class="tab-item">
<a href="#" v-on:click="switch_tab('file')" v-bind:class="{ active: tabs.file }">File import</a>
</li>
<li class="tab-item">
<a href="#" v-on:click="switch_tab('export')" v-bind:class="{ active: tabs.export }">Export IOCs</a>
</li>
</ul>
<div v-if="tabs.export">
<iframe :src="export_url" class="frame-export"></iframe>
</div>
<div v-if="tabs.file">
<label class="form-upload empty" for="upload">
<input type="file" class="upload-field" id="upload" @change="import_from_file">
<p class="empty-title h5">Drop or select a file to import.</p>
<p class="empty-subtitle">The file needs to be an export from a TinyCheck instance.</p>
</label>
</div>
<div v-if="tabs.bulk">
<div class="columns">
<div class="column col-4 col-xs-4">
<div class="form-group">
<select class="form-select" placeholder="test" v-model="tag">
<option value="">IOC(s) Tag</option>
<option v-for="t in tags" :value="t.tag" :key="t.tag">
{{ t.name }}
</option>
</select>
</div>
</div>
<div class="column col-4 col-xs-4">
<div class="form-group">
<select class="form-select width-full" placeholder="test" v-model="type">
<option value="">IOC(s) Type</option>
<option value="unknown">Multiple (regex parsing)</option>
<option v-for="t in types" :value="t.type" :key="t.type">
{{ t.name }}
</option>
</select>
</div>
</div>
<div class="column col-4 col-xs-4">
<div class="form-group">
<select class="form-select width-full" placeholder="test" v-model="tlp">
<option value="">IOC(s) TLP</option>
<option value="white">TLP:WHITE</option>
<option value="green">TLP:GREEN</option>
<option value="amber">TLP:AMBER</option>
<option value="red">TLP:RED</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<textarea class="form-input" id="input-example-3" placeholder="Paste your Indicators of Compromise here" rows="15" v-model="iocs"></textarea>
</div>
<div class="form-group">
<button class="btn-primary btn col-12" v-on:click="import_from_bulk()">Import the IOCs</button>
</div>
</div>
<div class="form-group" v-if="imported.length>0">
<div class="toast toast-success">
{{imported.length}} IOC<span v-if="errors.length>1">s</span> imported successfully.
</div>
</div>
<div v-if="errors.length>0">
<div class="form-group">
<div class="toast toast-error">
{{errors.length}} IOC<span v-if="errors.length>1">s</span> not imported, see details below.
</div>
</div>
<div class="form-group">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Indicator</th>
<th>Importation error</th>
</tr>
</thead>
<tbody>
<tr v-for="e in errors" v-bind:key="e.ioc">
<td>{{ e.ioc }}</td>
<td>{{ e.message }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-else-if="type_tag_error==true">
<div class="form-group">
<div class="toast toast-error">
IOC(s) not imported, see details below.
</div>
</div>
<div class="form-group">
<div class="empty">
<p class="empty-title h5">Please select a tag and a type.</p>
<p class="empty-subtitle">If different IOCs types, select "Unknown (regex parsing)".</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'manageiocs',
data() {
return {
type:"",
tag:"",
tlp:"",
iocs:"",
types:[],
tags:[],
errors:[],
imported:[],
type_tag_error: false,
wrong_ioc_file: false,
tabs: { "bulk" : true, "file" : false, "export" : false },
jwt:"",
export_url:"",
config: {},
watcher: ""
}
},
props: { },
methods: {
import_from_bulk: function() {
this.errors = []
this.imported = []
if (this.tag != "" && this.type != "" && this.tlp != ""){
this.iocs.match(/[^\r\n]+/g).forEach(ioc => {
this.import_ioc(this.tag, this.type, this.tlp, ioc);
});
this.iocs = "";
} else {
this.type_tag_error = true
}
},
import_ioc: function(tag, type, tlp, ioc) {
if (ioc != "" && ioc.slice(0,1) != "#"){
if("alert " != ioc.slice(0,6)) {
ioc = ioc.trim()
ioc = ioc.replace(" ", "")
ioc = ioc.replace("[", "")
ioc = ioc.replace("]", "")
ioc = ioc.replace("\\", "")
ioc = ioc.replace("(", "")
ioc = ioc.replace(")", "")
}
axios.get(`/api/ioc/add/${type.trim()}/${tag.trim()}/${tlp.trim()}/${ioc}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
.then(response => {
if(response.data.status){
this.imported.push(response.data);
} else if (response.data.message){
this.errors.push(response.data);
}
})
.catch(err => (console.log(err)))
}
},
delete_watcher: function(watcher) {
var i = this.config.watchers.indexOf(watcher);
this.config.watchers.splice(i, 1);
},
add_watcher: function() {
this.config.watchers.push(this.watcher);
this.watcher = "";
},
enrich_selects: function() {
axios.get(`/api/ioc/get/tags`, { timeout: 10000, headers: {'X-Token': this.jwt} })
.then(response => {
if(response.data.tags) this.tags = response.data.tags
})
.catch(err => (console.log(err)));
axios.get(`/api/ioc/get/types`, { timeout: 10000, headers: {'X-Token': this.jwt} })
.then(response => {
if(response.data.types) this.types = response.data.types
})
.catch(err => (console.log(err)));
},
switch_tab: function(tab) {
this.errors = []
this.imported = []
Object.keys(this.tabs).forEach(key => {
if( key == tab ){
this.tabs[key] = true
} else {
this.tabs[key] = false
}
});
},
import_from_file: function(ev) {
this.errors = []
this.imported = []
const file = ev.target.files[0];
const reader = new FileReader();
reader.onload = e => this.$emit("load", e.target.result);
reader.onload = () => {
try {
JSON.parse(reader.result).iocs.forEach(ioc => {
this.import_ioc(ioc["tag"], ioc["type"], ioc["tlp"], ioc["value"])
})
} catch (error) {
this.wrong_ioc_file = true
}
}
reader.readAsText(file);
},
async get_jwt(){
await axios.get(`/api/get-token`, { timeout: 10000 })
.then(response => {
if(response.data.token){
this.jwt = response.data.token
}
})
.catch(err => (console.log(err)))
},
load_config: function() {
axios.get(`/api/config/list`, {
timeout: 10000,
headers: { 'X-Token': this.jwt }
}).then(response => {
if (response.data) {
this.config = response.data
}
})
.catch(err => (console.log(err)))
}
},
created: function() {
this.get_jwt().then(() => {
this.enrich_selects();
this.load_config();
this.export_url = `/api/ioc/export?token=${this.jwt}`
});
}
}
</script>
+114
View File
@@ -0,0 +1,114 @@
<template>
<div class="backend-content" id="content">
<div class="column col-8 col-xs-12">
<h3 class="s-title">Search IOCs</h3>
<div class="form-group">
<textarea class="form-input" id="input-example-3" placeholder="Paste your IOCs here" rows="3" v-model="iocs"></textarea>
</div>
<div class="form-group">
<button class="btn btn-primary col-12" v-on:click="search_iocs()">Search</button>
</div>
<div class="form-group" v-if="results.length>0 ">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Indicator</th>
<th>Type</th>
<th>Tag</th>
<th>TLP</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr v-for="r in results" :key="r.tlp">
<td>{{ r.value }}</td>
<td class="capi">{{ r.type }}</td>
<td class="upper">{{ r.tag }}</td>
<td><label :class="['tlp-' + r.tlp]">{{ r.tlp }}</label></td>
<td><button class="btn btn-sm" v-on:click="remove(r)">Delete</button></td>
</tr>
</tbody>
</table>
</div>
<div v-else-if="first_search==false">
<div class="empty">
<p class="empty-title h5">IOC<span v-if="this.iocs.match(/[^\r\n]+/g).length>1">s</span> not found.</p>
<p class="empty-subtitle">Try wildcard search to expend your search.</p>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'iocs-search',
data() {
return {
results: [],
first_search: true,
jwt:""
}
},
props: { },
methods: {
search_iocs: function() {
this.results = []
this.first_search = false
this.iocs.match(/[^\r\n]+/g).forEach(ioc => {
ioc = ioc.trim()
if("alert " != ioc.slice(0,6)) {
ioc = ioc.replace(" ", "")
ioc = ioc.replace("[", "")
ioc = ioc.replace("]", "")
ioc = ioc.replace("\\", "")
ioc = ioc.replace("(", "")
ioc = ioc.replace(")", "")
}
axios.get(`/api/ioc/search/${ioc}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
.then(response => {
if(response.data.results.length>0){
this.results = [].concat(this.results, response.data.results);
}
})
.catch(err => (console.log(err)))
});
return true;
},
remove: function(elem){
axios.get(`/api/ioc/delete/${elem.id}`, { timeout: 10000, headers: {'X-Token': this.jwt} })
.then(response => {
if(response.data.status){
this.results = this.results.filter(function(el) { return el != elem; });
}
})
.catch(err => (console.log(err)))
},
load_config: function() {
axios.get(`/api/config/list`, {
timeout: 10000,
headers: { 'X-Token': this.jwt }
}).then(response => {
if (response.data) {
this.config = response.data
}
})
.catch(err => (console.log(err)))
},
async get_jwt(){
await axios.get(`/api/get-token`, { timeout: 10000 })
.then(response => {
if(response.data.token){
this.jwt = response.data.token
}
})
.catch(err => (console.log(err)))
}
},
created: function() {
this.get_jwt()
}
}
</script>
+116
View File
@@ -0,0 +1,116 @@
<template>
<div class="backend-content" id="content">
<div class="column col-6 col-xs-12">
<h3 class="s-title">Network configuration</h3>
<h5 class="s-subtitle">Interfaces configuration</h5>
<img src="@/assets/network.png" id="network-thumbnail" />
<div class="container interfaces-container">
<div class="columns">
<div class="column col-6">
<span class="interface-label">First interface</span>
<div class="form-group">
<div class="btn-group btn-group-block">
<button class="btn btn-sm btn-iface" @click="change_interface('in', iface)" :class="iface == config.network.in ? 'active' : ''" v-for="iface in config.interfaces" :key="iface">{{ iface.toUpperCase() }}</button>
</div>
</div>
</div>
<div class="column col-6">
<span class="interface-label">Second interface</span>
<div class="form-group">
<div class="btn-group btn-group-block">
<button class="btn btn-sm btn-iface" @click="change_interface('out', iface)" :class="iface == config.network.out ? 'active' : ''" v-for="iface in config.interfaces" :key="iface">{{ iface.toUpperCase() }}</button>
</div>
</div>
</div>
</div>
</div>
<h5 class="s-subtitle">Edit SSIDs names</h5>
<div class="form-group">
<table class="table table-striped table-hover">
<tbody>
<tr v-for="ssid in config.network.ssids" :key="ssid">
<td>{{ ssid }}</td>
<td><button class="btn btn-sm" v-on:click="delete_ssid(ssid)">Delete</button></td>
</tr>
<tr>
<td><input class="form-input" v-model="ssid" type="text" placeholder="SSID name"></td>
<td><button class="btn btn-sm" @click="add_ssid()">Add</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'manageinterface',
data() {
return {
config: {},
ssid: ""
}
},
props: {},
methods: {
async get_jwt() {
await axios.get(`/api/get-token`, { timeout: 10000 })
.then(response => {
if (response.data.token) {
this.jwt = response.data.token
}
})
.catch(err => (console.log(err)))
},
load_config: function() {
axios.get(`/api/config/list`, {
timeout: 10000,
headers: { 'X-Token': this.jwt }
}).then(response => {
if (response.data) {
this.config = response.data
}
})
.catch(err => (console.log(err)))
},
delete_ssid: function(ssid) {
var i = this.config.network.ssids.indexOf(ssid);
this.config.network.ssids.splice(i, 1);
this.update_ssids();
},
add_ssid: function() {
this.config.network.ssids.push(this.ssid);
this.ssid = "";
this.update_ssids();
},
update_ssids: function() {
axios.get(`/api/config/edit/network/ssids/${this.config.network.ssids.join("|")}`, {
timeout: 10000,
headers: { 'X-Token': this.jwt }
}).then(response => {
if (response.data.status) {
console.log(response.data)
}
})
.catch(err => (console.log(err)))
},
change_interface: function(type, iface) {
axios.get(`/api/config/edit/network/${type}/${iface}`, {
timeout: 10000,
headers: { 'X-Token': this.jwt }
}).then(response => {
if (response.data.status) this.config.network[type] = iface
})
.catch(err => (console.log(err)))
},
},
created: function() {
this.get_jwt().then(() => {
this.load_config();
});
}
}
</script>
+191
View File
@@ -0,0 +1,191 @@
<template>
<div class="backend-content" id="content">
<div class="column col-6 col-xs-12">
<h3 class="s-title">Manage whitelisted elements</h3>
<ul class="tab tab-block">
<li class="tab-item">
<a href="#" v-on:click="switch_tab('bulk')" v-bind:class="{ active: tabs.bulk }">Bulk elements import</a>
</li>
<li class="tab-item">
<a href="#" v-on:click="switch_tab('file')" v-bind:class="{ active: tabs.file }">Import from file</a>
</li>
<li class="tab-item">
<a href="#" v-on:click="switch_tab('export')" v-bind:class="{ active: tabs.export }">Export elements</a>
</li>
</ul>
<div v-if="tabs.export">
<iframe :src="export_url" class="frame-export"></iframe>
</div>
<div v-if="tabs.file">
<label class="form-upload empty" for="upload">
<input type="file" class="upload-field" id="upload" @change="import_from_file">
<p class="empty-title h5">Drop or select a file to import.</p>
<p class="empty-subtitle">The file needs to be an whitelist file export from a TinyCheck instance.</p>
</label>
</div>
<div v-if="tabs.bulk">
<div class="form-group">
<select class="form-select width-full" placeholder="test" v-model="type">
<option value="">Elements Type</option>
<option value="unknown">Multiple (regex parsing)</option>
<option v-for="t in types" :value="t.type" :key="t.type">
{{ t.name }}
</option>
</select>
</div>
<div class="form-group">
<textarea class="form-input" id="input-example-3" placeholder="Paste the elements to be whitelisted here" rows="15" v-model="elements"></textarea>
</div>
<div class="form-group">
<button class="btn-primary btn col-12" v-on:click="import_from_bulk()">Whitelist elements</button>
</div>
</div>
<div class="form-group" v-if="imported.length>0">
<div class="toast toast-success">
{{imported.length}} IOC<span v-if="errors.length>1">s</span> imported successfully.
</div>
</div>
<div v-if="errors.length>0">
<div class="form-group">
<div class="toast toast-error">
{{errors.length}} IOC<span v-if="errors.length>1">s</span> not imported, see details below.
</div>
</div>
<div class="form-group">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Element</th>
<th>Importation error</th>
</tr>
</thead>
<tbody>
<tr v-for="e in errors" :key="e.element">
<td>{{ e.element }}</td>
<td>{{ e.message }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-else-if="type_tag_error==true">
<div class="form-group">
<div class="toast toast-error">
IOC(s) not imported, see details below.
</div>
</div>
<div class="form-group">
<div class="empty">
<p class="empty-title h5">Please select a tag and a type.</p>
<p class="empty-subtitle">If different IOCs types, select "Unknown (regex parsing)".</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'manageiocs',
data() {
return {
type:"",
elements:"",
types:[],
errors:[],
imported:[],
wrong_wh_file: false,
tabs: { "bulk" : true, "file" : false, "export" : false },
jwt:"",
export_url:""
}
},
props: { },
methods: {
import_from_bulk: function() {
this.errors = []
this.imported = []
if (this.type != ""){
this.elements.match(/[^\r\n]+/g).forEach(elem => {
this.import_element(this.type, elem);
});
this.elements = "";
} else {
this.type_tag_error = true
}
},
import_element: function(type, elem) {
if (elem != "" && elem.slice(0,1) != "#"){
axios.get(`/api/whitelist/add/${type.trim()}/${elem.trim()}`, {
timeout: 10000,
headers: { "X-Token" : this.jwt }
}).then(response => {
if(response.data.status){
this.imported.push(response.data);
} else if (response.data.message){
this.errors.push(response.data);
}
})
.catch(err => (console.log(err)))
}
},
enrich_types: function() {
axios.get(`/api/whitelist/get/types`, { timeout: 10000, headers: {'X-Token': this.jwt} })
.then(response => {
if(response.data.types) this.types = response.data.types
})
.catch(err => (console.log(err)));
},
switch_tab: function(tab) {
this.errors = []
this.imported = []
Object.keys(this.tabs).forEach(key => {
if( key == tab ){
this.tabs[key] = true
} else {
this.tabs[key] = false
}
});
},
import_from_file: function(ev) {
this.errors = []
this.imported = []
const file = ev.target.files[0];
const reader = new FileReader();
reader.onload = e => this.$emit("load", e.target.result);
reader.onload = () => {
try {
JSON.parse(reader.result).elements.forEach(elem => {
this.import_element(elem["type"], elem["element"])
})
} catch (error) {
this.wrong_wh_file = true
}
}
reader.readAsText(file);
},
async get_jwt(){
await axios.get(`/api/get-token`, { timeout: 10000 })
.then(response => {
if(response.data.token){
this.jwt = response.data.token
}
})
.catch(err => (console.log(err)))
}
},
created: function() {
this.get_jwt().then(() => {
this.enrich_types();
this.export_url = `/api/whitelist/export?token=${this.jwt}`
});
}
}
</script>
@@ -0,0 +1,94 @@
<template>
<div class="backend-content" id="content">
<div class="column col-6 col-xs-12">
<h3 class="s-title">Search whitelisted elements</h3>
<div class="form-group">
<textarea class="form-input" id="input-example-3" placeholder="Paste the elements here" rows="3" v-model="elements"></textarea>
</div>
<div class="form-group">
<button class="btn btn-primary col-12" v-on:click="search_elements()">Search</button>
</div>
<div class="form-group" v-if="results.length>0 ">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Element</th>
<th>Element type</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr v-for="r in results" :key="r.element">
<td>{{ r.element }}</td>
<td>{{ r.type }}</td>
<td><button class="btn btn-sm" v-on:click="remove(r)">Delete</button></td>
</tr>
</tbody>
</table>
</div>
<div v-else-if="first_search==false">
<div class="empty">
<p class="empty-title h5">Element<span v-if="this.elements.match(/[^\r\n]+/g).length>1">s</span> not found.</p>
<p class="empty-subtitle">Try wildcard search to expend your search.</p>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'elements-search',
data() {
return {
results: [],
first_search: true,
jwt:""
}
},
props: { },
methods: {
search_elements: function() {
this.results = []
this.first_search = false
this.elements.match(/[^\r\n]+/g).forEach(elem => {
axios.get(`/api/whitelist/search/${elem.trim()}`, {
timeout: 10000,
headers: {'X-Token': this.jwt}
}).then(response => {
if(response.data.results.length>0){
this.results = [].concat(this.results, response.data.results);
}
})
.catch(err => (console.log(err)))
});
return true;
},
remove: function(elem){
axios.get(`/api/whitelist/delete/${elem.id}`, {
timeout: 10000,
headers: {'X-Token': this.jwt}
}).then(response => {
if(response.data.status){
this.results = this.results.filter(function(el) { return el != elem; });
}
})
.catch(err => (console.log(err)))
},
async get_jwt(){
await axios.get(`/api/get-token`, { timeout: 10000 })
.then(response => {
if(response.data.token){
this.jwt = response.data.token
}
})
.catch(err => (console.log(err)))
}
},
created: function() {
this.get_jwt()
}
}
</script>
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
devServer: {
proxy: {
'^/api': {
target: 'https://localhost:5000',
ws: true,
changeOrigin: true
},
}
}
}
+24
View File
@@ -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/).
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
+12248
View File
File diff suppressed because it is too large Load Diff
+48
View File
@@ -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"
]
}
+3
View File
@@ -0,0 +1,3 @@
{
"python.pythonPath": "/usr/local/opt/python@3.8/bin/python3.8"
}
+30
View File
@@ -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>
+636
View File
@@ -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;
}
}
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 805 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

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

+16
View File
@@ -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

+5
View File
@@ -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

+6
View File
@@ -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

+11
View File
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = true
Vue.config.devtools = true
new Vue({
router,
render: h => h(App)
}).$mount('#app')
+62
View File
@@ -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
+60
View File
@@ -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>
+70
View File
@@ -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>
+100
View File
@@ -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>
+119
View File
@@ -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>
+24
View File
@@ -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>
+115
View File
@@ -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>
+200
View File
@@ -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>
+53
View File
@@ -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>
+166
View File
@@ -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>
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
devServer: {
proxy: {
'^/api': {
target: 'http://localhost:8040',
ws: true,
changeOrigin: true
},
}
}
}