Update Notifications, Fix DB Cleanup, Error Handling, Move ScanResult to Component

This commit is contained in:
Evan Reichard 2021-03-21 12:28:02 -04:00
parent 0632c06978
commit 6b40111a3b
24 changed files with 347 additions and 172 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
**/__pycache__/
**/.coverage
**/overseer.sqlite
src/overseer.egg-info/
build/
dist/

View File

@ -23,11 +23,14 @@ def post_scans():
"""
data = request.get_json()
if data is None or "target" not in data:
return {"error": "Missing 'target'"}, 422
return {"error": "Missing target"}, 422
if data["target"].strip() == "":
return {"error": "Invalid target"}, 422
scan_history = overseer.scan_manager.perform_scan(data["target"])
if scan_history is None:
return {"error": "Unable to resolve hostname."}, 422
return {"error": "Unable to resolve hostname"}, 422
return __normalize_scan_results([scan_history], data["target"])[0]

View File

@ -59,7 +59,7 @@ class DatabaseConnector:
for stale in all_stale:
stale.status = "FAILED"
stale.message = "Stale history"
stale.error = "Stale history"
session.commit()
session.close()

View File

@ -124,7 +124,7 @@ class ScanManager:
Either a hostname or IP address of the endpoint to scan
"""
try:
target = socket.gethostbyname(target)
socket.gethostbyname(target)
except socket.error:
return None

View File

@ -1 +0,0 @@
#overseer-websocket[data-v-7a0962f4]{height:18px;width:18px;border-radius:8px;position:absolute;left:25px;top:20px;z-index:-1}#overseer-header[data-v-7a0962f4]{background-color:#c9582c;width:100%;height:60px;position:fixed;top:0}#overseer-header a[data-v-7a0962f4]{color:inherit}#overseer-notifications[data-v-3073f05e]{height:calc(100% - 60px);width:320px;position:fixed;transition:.5s;right:0;top:0;overflow:scroll;margin-top:60px;z-index:1}.overseer-notification[data-v-3073f05e]{height:65px;transition:.5s;background-color:#e0e3de;border-radius:2px;text-align:start;padding:10px;margin:10px;cursor:pointer}#overseer-app{height:100%;width:100%;font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#3a4040}#overseer-body{height:calc(100% - 60px);width:100%;margin-top:60px;overflow:scroll;position:absolute;color:#eaece9}#overseer-body-center{min-height:100%;width:1000px;margin:0 auto;background-color:#343a46;box-shadow:inset 0 0 3px 3px #2b303a}body,html{height:100%;width:100%;margin:0;padding:0;background-color:#2b303a}#overseer-search[data-v-b2a880be]::-moz-placeholder{text-align:center}#overseer-search[data-v-b2a880be]:-ms-input-placeholder{text-align:center}#overseer-search[data-v-b2a880be]::placeholder{text-align:center}#overseer-search[data-v-b2a880be]{width:400px;height:40px;border-radius:5px;text-align:center;margin-top:400px;font-size:1rem;padding:.3rem .8rem;text-indent:0;outline:none;border:0 solid}.lds-roller[data-v-d7e11832]{display:inline-block;position:relative;width:80px;height:80px}.lds-roller div[data-v-d7e11832]{-webkit-animation:lds-roller-data-v-d7e11832 1.2s cubic-bezier(.5,0,.5,1) infinite;animation:lds-roller-data-v-d7e11832 1.2s cubic-bezier(.5,0,.5,1) infinite;transform-origin:40px 40px}.lds-roller div[data-v-d7e11832]:after{content:" ";display:block;position:absolute;width:7px;height:7px;border-radius:50%;background:#fff;margin:-4px 0 0 -4px}.lds-roller div[data-v-d7e11832]:first-child{-webkit-animation-delay:-36ms;animation-delay:-36ms}.lds-roller div[data-v-d7e11832]:first-child:after{top:63px;left:63px}.lds-roller div[data-v-d7e11832]:nth-child(2){-webkit-animation-delay:-72ms;animation-delay:-72ms}.lds-roller div[data-v-d7e11832]:nth-child(2):after{top:68px;left:56px}.lds-roller div[data-v-d7e11832]:nth-child(3){-webkit-animation-delay:-.108s;animation-delay:-.108s}.lds-roller div[data-v-d7e11832]:nth-child(3):after{top:71px;left:48px}.lds-roller div[data-v-d7e11832]:nth-child(4){-webkit-animation-delay:-.144s;animation-delay:-.144s}.lds-roller div[data-v-d7e11832]:nth-child(4):after{top:72px;left:40px}.lds-roller div[data-v-d7e11832]:nth-child(5){-webkit-animation-delay:-.18s;animation-delay:-.18s}.lds-roller div[data-v-d7e11832]:nth-child(5):after{top:71px;left:32px}.lds-roller div[data-v-d7e11832]:nth-child(6){-webkit-animation-delay:-.216s;animation-delay:-.216s}.lds-roller div[data-v-d7e11832]:nth-child(6):after{top:68px;left:24px}.lds-roller div[data-v-d7e11832]:nth-child(7){-webkit-animation-delay:-.252s;animation-delay:-.252s}.lds-roller div[data-v-d7e11832]:nth-child(7):after{top:63px;left:17px}.lds-roller div[data-v-d7e11832]:nth-child(8){-webkit-animation-delay:-.288s;animation-delay:-.288s}.lds-roller div[data-v-d7e11832]:nth-child(8):after{top:56px;left:12px}@-webkit-keyframes lds-roller-data-v-d7e11832{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes lds-roller-data-v-d7e11832{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}#overseer-scan[data-v-09bc5538]{text-align:left}#results[data-v-09bc5538]{width:500px;margin:150px auto 0 auto}.proto[data-v-09bc5538]{border-radius:4px;padding:0 2px;color:#0a282f;margin-left:5px}.scan-progress[data-v-09bc5538]{border:1px solid #000;border-radius:6px;overflow:hidden;position:relative;text-align:center;margin-bottom:5px}.scan-progress span[data-v-09bc5538]{position:absolute;font-size:.75em;right:48%;font-weight:900;top:-2px;color:#000}.scan-progress div[data-v-09bc5538]{height:12px;background-color:green;text-align:center;font-size:.6em;width:0}li[data-v-09bc5538]{display:flex;margin:10px 43px;width:80px}li span[data-v-09bc5538]{font-weight:700}ul[data-v-09bc5538]{display:flex;-moz-column-count:3;column-count:3;flex-wrap:wrap;flex-direction:row;list-style-type:none;padding:0}

View File

@ -0,0 +1 @@
#overseer-websocket[data-v-1f35cde8]{height:18px;width:18px;border-radius:8px;position:absolute;left:25px;top:20px;z-index:-1}#overseer-header[data-v-1f35cde8]{background-color:#c9582c;width:100%;height:60px;position:fixed;top:0}#overseer-header a[data-v-1f35cde8]{color:inherit}.scan-progress[data-v-2b9f4f85]{border:1px solid #000;border-radius:6px;overflow:hidden;position:relative;text-align:center;margin-bottom:5px}.scan-progress span[data-v-2b9f4f85]{position:absolute;font-size:.75em;right:48%;font-weight:900;top:-2px;color:#000}.scan-progress div[data-v-2b9f4f85]{height:12px;transition:1s;background-color:green;text-align:center;font-size:.6em;width:0}#overseer-notifications[data-v-7218f0ce]{height:calc(100% - 60px);width:320px;position:fixed;transition:.5s;right:0;top:0;overflow:scroll;margin-top:60px;z-index:1}.overseer-notification[data-v-7218f0ce]{transition:.5s;background-color:#e0e3de;border-radius:2px;text-align:start;padding:10px;margin:10px;cursor:pointer}#overseer-app{height:100%;width:100%;font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#3a4040}#overseer-body{height:calc(100% - 60px);width:100%;margin-top:60px;overflow:scroll;position:absolute;color:#eaece9}#overseer-body-center{min-height:100%;width:1000px;margin:0 auto;background-color:#343a46;box-shadow:inset 0 0 3px 3px #2b303a}body,html{height:100%;width:100%;margin:0;padding:0;background-color:#2b303a}.lds-roller[data-v-d7e11832]{display:inline-block;position:relative;width:80px;height:80px}.lds-roller div[data-v-d7e11832]{-webkit-animation:lds-roller-data-v-d7e11832 1.2s cubic-bezier(.5,0,.5,1) infinite;animation:lds-roller-data-v-d7e11832 1.2s cubic-bezier(.5,0,.5,1) infinite;transform-origin:40px 40px}.lds-roller div[data-v-d7e11832]:after{content:" ";display:block;position:absolute;width:7px;height:7px;border-radius:50%;background:#fff;margin:-4px 0 0 -4px}.lds-roller div[data-v-d7e11832]:first-child{-webkit-animation-delay:-36ms;animation-delay:-36ms}.lds-roller div[data-v-d7e11832]:first-child:after{top:63px;left:63px}.lds-roller div[data-v-d7e11832]:nth-child(2){-webkit-animation-delay:-72ms;animation-delay:-72ms}.lds-roller div[data-v-d7e11832]:nth-child(2):after{top:68px;left:56px}.lds-roller div[data-v-d7e11832]:nth-child(3){-webkit-animation-delay:-.108s;animation-delay:-.108s}.lds-roller div[data-v-d7e11832]:nth-child(3):after{top:71px;left:48px}.lds-roller div[data-v-d7e11832]:nth-child(4){-webkit-animation-delay:-.144s;animation-delay:-.144s}.lds-roller div[data-v-d7e11832]:nth-child(4):after{top:72px;left:40px}.lds-roller div[data-v-d7e11832]:nth-child(5){-webkit-animation-delay:-.18s;animation-delay:-.18s}.lds-roller div[data-v-d7e11832]:nth-child(5):after{top:71px;left:32px}.lds-roller div[data-v-d7e11832]:nth-child(6){-webkit-animation-delay:-.216s;animation-delay:-.216s}.lds-roller div[data-v-d7e11832]:nth-child(6):after{top:68px;left:24px}.lds-roller div[data-v-d7e11832]:nth-child(7){-webkit-animation-delay:-.252s;animation-delay:-.252s}.lds-roller div[data-v-d7e11832]:nth-child(7):after{top:63px;left:17px}.lds-roller div[data-v-d7e11832]:nth-child(8){-webkit-animation-delay:-.288s;animation-delay:-.288s}.lds-roller div[data-v-d7e11832]:nth-child(8):after{top:56px;left:12px}@-webkit-keyframes lds-roller-data-v-d7e11832{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes lds-roller-data-v-d7e11832{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}#overseer-search[data-v-7ef10590]::-moz-placeholder{text-align:center}#overseer-search[data-v-7ef10590]:-ms-input-placeholder{text-align:center}#overseer-search[data-v-7ef10590]::placeholder{text-align:center}#overseer-search[data-v-7ef10590]{width:400px;height:40px;border-radius:5px;text-align:center;font-size:1rem;padding:.3rem .8rem;text-indent:0;outline:none;border:0 solid}#overseer-websocket[data-v-7ef10590]{height:40px;width:40px;border-radius:20px;position:absolute;left:19px;bottom:66px}.error[data-v-7ef10590]{margin-top:5px;color:#b30000}#scan-status[data-v-7c05d396]{color:#eaece9;text-align:center}#sub-status[data-v-7c05d396]{text-align:center;width:100%;margin:-20px 0 10px 0;border-bottom:1px solid}.proto[data-v-7c05d396]{border-radius:4px;padding:0 2px;color:#0a282f;margin-left:5px}#overseer-scan[data-v-3b3475aa]{text-align:left}#results[data-v-3b3475aa]{width:500px;margin:150px auto 0 auto}li[data-v-3b3475aa]{display:flex;margin:10px 43px;width:80px}li span[data-v-3b3475aa]{font-weight:700}ul[data-v-3b3475aa]{display:flex;-moz-column-count:3;column-count:3;flex-wrap:wrap;flex-direction:row;list-style-type:none;padding:0}#scan-button[data-v-3b3475aa]{font-size:1em;background-color:#0e6a0e;padding:8px;border-radius:10px;float:right;font-weight:700;cursor:pointer;margin:8px}

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

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

View File

@ -1 +1 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/favicon.ico"><title>overseer_client</title><link href="/static/css/app.a50f799c.css" rel="preload" as="style"><link href="/static/js/app.e5dd211e.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.cf069257.js" rel="preload" as="script"><link href="/static/css/app.a50f799c.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but overseer_client doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.cf069257.js"></script><script src="/static/js/app.e5dd211e.js"></script></body></html>
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/favicon.ico"><title>Overseer</title><link href="/static/css/app.adf7b5c2.css" rel="preload" as="style"><link href="/static/js/app.a3974328.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.4fdbc4a4.js" rel="preload" as="script"><link href="/static/css/app.adf7b5c2.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but Overseer doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.4fdbc4a4.js"></script><script src="/static/js/app.a3974328.js"></script></body></html>

View File

@ -1,5 +1,5 @@
{
"name": "overseer_client",
"name": "Overseer",
"version": "0.1.0",
"private": true,
"scripts": {

View File

@ -5,7 +5,7 @@
<div v-if="!this.$store.state.ws_connected" style="background-color: #B30000;" id="overseer-websocket"></div>
<div style="float: left; margin: 8px 20px; font-weight: 700; font-size: 2em;">Overseer</div>
</router-link>
<div style="float: right; margin: 10px 20px;">
<div v-on:click="toggleNotifications()" style="float: right; margin: 10px 20px; cursor: pointer;">
<p style="color: white; position: absolute; font-weight: 700; width: 35px; height: 35px; margin: 9px 0px; font-size: .8em;">
{{ notificationCount }}
</p>
@ -23,6 +23,11 @@ export default {
notificationCount() {
return this.$store.state.notifications.length
}
},
methods: {
toggleNotifications() {
this.$root.$emit("toggleNotifications");
}
}
}
</script>

View File

@ -1,28 +1,51 @@
<template>
<div id="overseer-notifications">
<div v-on:click="navigateToScan(item)" class="overseer-notification" v-for="item in items" :key="item.id">
<b>Target: </b> {{ item.target }} <br>
<b>Status: </b>{{ item.status }} <br>
<span v-if="'total_progress' in item">
<b>Progress: </b>{{item.total_progress}} <br>
</span>
<div id="overseer-notifications" v-bind:style="deriveNotificationCSS(showNotifications)">
<div v-on:click="navigateToScan(scan)" class="overseer-notification" v-for="scan in activeScans" :key="scan.id">
<div style="float: left; width: 100%; margin: 5px 0px;">
<b style="float: left;">{{ scan.target }}</b>
<b style="float: right;">{{ scan.status }}</b>
</div>
<ScanProgress
:percentage="scan.status =='COMPLETE' ? 100 : scan.total_progress"
style="margin-top: 5px;"
/>
</div>
</div>
</template>
<script>
import ScanProgress from '../components/ScanProgress.vue'
export default {
name: 'OverseerNotifications',
components: {
ScanProgress,
},
data() {
return {
showNotifications: true
}
},
computed: {
items() {
activeScans() {
return this.$store.state.notifications
}
},
methods: {
navigateToScan(item) {
this.$router.push({ path: `/scan/${item.target}/${item.id}` })
navigateToScan(scan) {
this.$router.push({ path: `/scan/${scan.target}/${scan.id}` })
},
deriveNotificationCSS(showNotifications) {
return {
"right": showNotifications ? "0px" : "-320px"
}
}
},
mounted() {
this.$root.$on("toggleNotifications", () => {
this.showNotifications = !this.showNotifications;
});
}
}
</script>
@ -40,7 +63,6 @@ export default {
}
.overseer-notification {
height: 65px;
transition: 0.5s;
background-color: #E0E3DE;
border-radius: 2px;

View File

@ -0,0 +1,62 @@
<template>
<div class="scan-progress" >
<span style="right: 46.5%;">{{ title }}</span>
<div v-bind:style="scanProgress(percentage)"></div>
</div>
</template>
<script>
export default {
name: 'ScanProgress',
props: ["percentage", "title"],
methods: {
scanProgress(perc) {
// Calculate color based on percentage - https://gist.github.com/mlocati/7210513
let r, g, b = 0;
if (perc < 50) {
r = 255;
g = Math.round(5.1 * perc);
} else {
g = 255;
r = Math.round(510 - 5.10 * perc)
}
let h = r * 0x10000 + g * 0x100 + b * 0x1;
let hexColor = '#' + ('000000' + h.toString(16)).slice(-6);
return {
"width": perc + "%",
"backgroundColor": hexColor,
}
},
}
}
</script>
<style scoped>
.scan-progress {
border: 1px solid black;
border-radius: 6px;
overflow: hidden;
position: relative;
text-align: center;
margin-bottom: 5px;
}
.scan-progress span {
position: absolute;
font-size: .75em;
right: 48%;
font-weight: 900;
top: -2px;
color: black;
}
.scan-progress div {
height: 12px;
transition: 1s;
background-color: green;
text-align: center;
font-size: .6em;
width: 0%;
}
</style>

View File

@ -0,0 +1,78 @@
<template>
<div v-if="scan.status == 'FAILED' || error">
<h2 id="scan-status">Error</h2>
<h5 id="sub-status" style="color: #B30000; border-bottom: unset;">{{ error || scan.error }}</h5>
</div>
<div v-else-if="scan.status == 'LOADING' || loading">
<Loading style="margin: 0px auto; display: block;" />
<h2 id="scan-status">Loading...</h2>
</div>
<div v-else-if="scan.status == 'NO_RESULTS'">
<h2 id="scan-status">No Scans Found</h2>
</div>
<div v-else>
<h2 v-if="scan.status == 'IN_PROGRESS'" id="scan-status">Scanning in Progress</h2>
<h2 v-else id="scan-status">Scan Results</h2>
<h5 id="sub-status">{{ normalizeDate(scan.created_at) }}</h5>
<div v-if="scan.status == 'IN_PROGRESS'" >
<ScanProgress title="TCP" :percentage="scan.tcp_progress" />
<ScanProgress title="UDP" :percentage="scan.udp_progress" />
<ScanProgress title="Total" :percentage="scan.total_progress" />
</div>
<ul>
<li v-for="port in scan.results" :key="port">
<span>{{ port.split(" ")[0] }}</span>
<span class="proto" v-bind:style="dynamicProtocolStyle(port.split(' ')[1])" >{{ port.split(" ")[1] }}</span>
</li>
</ul>
</div>
</template>
<script>
import Loading from '../components/Loading.vue'
import ScanProgress from '../components/ScanProgress.vue'
export default {
name: 'ScanResult',
props: ["scan", "error", "loading"],
components: {
Loading,
ScanProgress,
},
methods: {
normalizeDate(dateISO) {
let parsedDate = new Date(dateISO);
return parsedDate.toDateString() + " " + parsedDate.toLocaleTimeString()
},
dynamicProtocolStyle(proto) {
return {
"backgroundColor": proto == "TCP" ? "#E7E6FF" : "#D5EFFF",
}
},
}
}
</script>
<style scoped>
#scan-status {
color: #EAECE9;
text-align: center;
}
#sub-status {
text-align: center;
width: 100%;
margin: -20px 0px 10px 0px;
border-bottom: 1px solid;
}
.proto {
border-radius: 4px;
padding: 0px 2px;
color: #0A282F;
margin-left: 5px;
}
</style>

View File

@ -2,6 +2,7 @@ import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Scan from '../views/Scan.vue'
import Search from '../views/Search.vue'
import NotFound from '../views/NotFound.vue'
Vue.use(VueRouter)
@ -22,6 +23,11 @@ const routes = [
name: 'Scan',
component: Scan
},
{
path: '/search/:target',
name: 'Search',
component: Search
},
{
path: '*',
name: 'NotFound',

View File

@ -16,48 +16,64 @@ export default new Vuex.Store({
},
actions: {
getScansByTarget({ commit }, target){
fetch('/api/v1/scans/' + target)
return fetch('/api/v1/scans/' + target)
.then(resp => resp.json())
.then(json => {
commit("SET_SCANS", { target, data: json.data });
commit("SET_TARGET_SCANS", { target, data: json.data });
});
},
performScan({ commit }, target) {
return fetch('/api/v1/scans', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ "target": target })
}).then(async resp => {
let scan = await resp.json()
commit("UPDATE_SCAN", { scan });
return scan;
});
}
},
mutations: {
"SET_SCANS"(state, { target, data }) {
console.log("SET_SCANS: ", target, data)
"SET_TARGET_SCANS"(state, { target, data }) {
Vue.set(state.scan_cache, target, data)
},
"UPDATE_SCAN"(state, { scan }) {
let target = scan.target;
if (!state.scan_cache[target]) {
Vue.set(state.scan_cache, target, [scan])
} else {
let matchedItem = state.scan_cache[target]
.find(item => item.id == scan.id);
if (matchedItem)
Object.keys(scan).forEach(key => {
Vue.set(matchedItem, key, scan[key])
});
else
state.scan_cache[target].unshift(scan);
}
},
"SOCKET_message"(state, scan) {
// Update progress queue
let matchedItem = state.notifications.find(item => item.id == scan.id);
if (matchedItem)
Object.keys(scan).forEach(key => {
Vue.set(matchedItem, key, scan[key])
});
else
state.notifications.push(scan);
// Update scan cache
this.commit("UPDATE_SCAN", { scan });
},
"SOCKET_connect"(state) {
state.ws_connected = true
},
"SOCKET_disconnect"(state) {
state.ws_connected = false
},
"SOCKET_message"(state, message) {
// Update progress queue
let matchedItem = state.notifications.find(item => item.id == message.id);
if (matchedItem)
Object.keys(message).forEach(key => {
Vue.set(matchedItem, key, message[key])
});
else
state.notifications.push(message);
// Update scan cache
if (!state.scan_cache[message.target]) {
Vue.set(state.scan_cache, message.target, [message])
} else {
let matchedItem = state.scan_cache[message.target]
.find(item => item.id == message.id);
if (matchedItem)
Object.keys(message).forEach(key => {
Vue.set(matchedItem, key, message[key])
});
else
state.scan_cache[message.target].unshift(message);
}
}
},
})

View File

@ -1,25 +1,44 @@
<template>
<input @keyup.enter="onSubmit" id="overseer-search" placeholder="IP Address / Hostname" type="text">
<div>
<div style="padding: 250px 0px 40px; position: relative; width: 300px; margin: 0px auto;">
<div v-if="this.$store.state.ws_connected" style="background-color: #0E6A0E;" id="overseer-websocket"></div>
<div v-if="!this.$store.state.ws_connected" style="background-color: #B30000;" id="overseer-websocket"></div>
<div style="font-weight: 700; font-size: 4em; position: relative;">Overseer</div>
</div>
<input @keyup.enter="onSubmit" id="overseer-search" placeholder="IP Address / Hostname" type="text"><br>
<h5 v-if="error != null" class="error">{{ error }}</h5>
<Loading v-if="loading" style="margin-top: 50px;"/>
</div>
</template>
<script>
import Loading from '../components/Loading.vue'
export default {
name: 'OverseerHome',
components: {
Loading,
},
data() {
return {
error: null,
loading: false,
}
},
methods: {
onSubmit(event){
fetch('/api/v1/scans', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ "target": event.target.value })
}).then(resp => resp.json()).then(json => {
// TODO: HANDLE POTENTIAL ERROR
console.log("RESPONSE: ", json);
this.$router.push({ path: `/scan/${event.target.value}` })
this.loading = true;
this.error = null;
this.$store.dispatch("performScan", event.target.value)
.then(scan => {
if (scan.error)
throw new Error(scan.error)
this.$router.push({ path: `/scan/${event.target.value}/${scan.id}` })
}).catch(err => {
// TODO: HANDLE ERROR
console.log("ERROR: ", err);
this.error = err;
}).finally(() => {
this.loading = false;
});
}
}
@ -30,16 +49,30 @@ export default {
#overseer-search::placeholder{
text-align: center;
}
#overseer-search {
width: 400px;
height: 40px;
border-radius: 5px;
text-align: center;
margin-top: 400px;
font-size: 1rem;
padding: .3rem .8rem;
text-indent: 0px;
outline: none;
border: 0px solid;
}
#overseer-websocket {
height: 40px;
width: 40px;
border-radius: 20px;
position: absolute;
left: 19px;
bottom: 66px;
}
.error {
margin-top: 5px;
color: #B30000;
}
</style>

View File

@ -1,79 +1,43 @@
<template>
<div id="overseer-scan" style="padding: 50px;">
<h1 style="font-size: 2.5em; margin: 0px;">{{ $route.params.target }}</h1>
<h1 style="font-size: 2.5em; margin: 0px; float: left;">{{ $route.params.target }}</h1>
<span v-if="getRequestedScan.status == 'COMPLETE'" v-on:click="performScan()" id="scan-button">Scan Again</span>
<div id="results">
<div v-if="getRequestedScan == 'LOADING'">
<Loading/>
<h2 style="color: #EAECE9; text-align: center;">Loading...</h2>
</div>
<div v-else-if="getRequestedScan == 'NO_RESULTS'">
<h2 style="color: #EAECE9; text-align: center;">No Scans Found</h2>
</div>
<div v-else>
<h2 v-if="getRequestedScan.status == 'IN_PROGRESS'" style="color: #EAECE9; text-align: center;">Scanning in Progress</h2>
<h2 v-else style="color: #EAECE9; text-align: center;">Scan Results</h2>
<h5 style="text-align: center; width: 100%; margin: -20px 0px 10px 0px; border-bottom: 1px solid;">{{ normalizeDate(getRequestedScan.created_at) }}</h5>
<div class="scan-progress" v-if="getRequestedScan.status == 'IN_PROGRESS'" >
<span>TCP</span>
<div v-bind:style="scanProgress(getRequestedScan.tcp_progress)"></div>
</div>
<div class="scan-progress" v-if="getRequestedScan.status == 'IN_PROGRESS'" >
<span>UDP</span>
<div v-bind:style="scanProgress(getRequestedScan.udp_progress)"></div>
</div>
<div class="scan-progress" v-if="getRequestedScan.status == 'IN_PROGRESS'" >
<span style="right: 46.5%;">TOTAL</span>
<div v-bind:style="scanProgress(getRequestedScan.total_progress)"></div>
</div>
<ul>
<li v-for="port in getRequestedScan.results" :key="port">
<span>{{ port.split(" ")[0] }}</span>
<span class="proto" v-bind:style="dynamicProtocolStyle(port.split(' ')[1])" >{{ port.split(" ")[1] }}</span>
</li>
</ul>
</div>
<ScanResult :scan="getRequestedScan" :error="error" :loading="loading"/>
</div>
</div>
</template>
<script>
import Loading from '../components/Loading.vue'
import ScanResult from '../components/ScanResult.vue'
export default {
name: 'Scan',
components: {
Loading,
ScanResult,
},
data() {
return {
error: null,
loading: false
}
},
methods: {
scanProgress(perc) {
// Calculate color based on percentage - https://gist.github.com/mlocati/7210513
let r, g, b = 0;
if (perc < 50) {
r = 255;
g = Math.round(5.1 * perc);
} else {
g = 255;
r = Math.round(510 - 5.10 * perc)
}
let h = r * 0x10000 + g * 0x100 + b * 0x1;
let hexColor = '#' + ('000000' + h.toString(16)).slice(-6);
performScan() {
this.error = null;
this.loading = true;
return {
"width": perc + "%",
"backgroundColor": hexColor,
}
},
dynamicProtocolStyle(proto) {
return {
"backgroundColor": proto == "TCP" ? "#E7E6FF" : "#D5EFFF",
}
},
normalizeDate(dateISO) {
let parsedDate = new Date(dateISO);
return parsedDate.toDateString() + " " + parsedDate.toLocaleTimeString()
this.$store.dispatch("performScan", this.$route.params.target)
.then(scan => {
if (scan.error)
throw new Error(scan.error)
this.$router.push({ path: `/scan/${this.$route.params.target}/${scan.id}` })
}).catch(err => {
this.error = err;
}).finally(() => {
this.loading = false;
});
}
},
computed: {
@ -83,15 +47,16 @@ export default {
let scanID = this.$route.params.scan_id;
if (!scanCache[target])
return "LOADING"
return { "status": "LOADING" }
if (scanID)
return scanCache[target].find(item => item.id == scanID);
return scanCache[target]
.find(item => item.id == scanID) || { "status": "NO_RESULTS" };
else
return scanCache[target][0] || "NO_RESULTS";
return scanCache[target][0] || { "status": "NO_RESULTS" };
}
},
created(){
mounted(){
this.$store.dispatch("getScansByTarget", this.$route.params.target);
}
}
@ -107,39 +72,6 @@ export default {
margin: 150px auto 0px auto;
}
.proto {
border-radius: 4px;
padding: 0px 2px;
color: #0A282F;
margin-left: 5px;
}
.scan-progress {
border: 1px solid black;
border-radius: 6px;
overflow: hidden;
position: relative;
text-align: center;
margin-bottom: 5px;
}
.scan-progress span {
position: absolute;
font-size: .75em;
right: 48%;
font-weight: 900;
top: -2px;
color: black;
}
.scan-progress div {
height: 12px;
background-color: green;
text-align: center;
font-size: .6em;
width: 0%;
}
li {
display: flex;
margin: 10px 43px;
@ -158,4 +90,15 @@ ul {
list-style-type: none;
padding: 0px;
}
#scan-button {
font-size: 1em;
background-color: #0E6A0E;
padding: 8px;
border-radius: 10px;
float: right;
font-weight: 700;
cursor: pointer;
margin: 8px;
}
</style>

View File

@ -0,0 +1,6 @@
<template>
<div style="padding-top: 300px;">
<h1>Error</h1>
<h4>404 Page Not Found</h4>
</div>
</template>