Update Notifications, Fix DB Cleanup, Error Handling, Move ScanResult to Component
This commit is contained in:
parent
0632c06978
commit
6b40111a3b
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
**/__pycache__/
|
||||
**/.coverage
|
||||
**/overseer.sqlite
|
||||
src/overseer.egg-info/
|
||||
build/
|
||||
dist/
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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}
|
1
src/overseer/static/css/app.adf7b5c2.css
Normal file
1
src/overseer/static/css/app.adf7b5c2.css
Normal 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}
|
2
src/overseer/static/js/app.a3974328.js
Normal file
2
src/overseer/static/js/app.a3974328.js
Normal file
File diff suppressed because one or more lines are too long
1
src/overseer/static/js/app.a3974328.js.map
Normal file
1
src/overseer/static/js/app.a3974328.js.map
Normal file
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
1
src/overseer/static/js/chunk-vendors.4fdbc4a4.js.map
Normal file
1
src/overseer/static/js/chunk-vendors.4fdbc4a4.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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>
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "overseer_client",
|
||||
"name": "Overseer",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -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>
|
||||
|
@ -1,27 +1,50 @@
|
||||
<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;
|
||||
|
62
src/overseer_client/src/components/ScanProgress.vue
Normal file
62
src/overseer_client/src/components/ScanProgress.vue
Normal 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>
|
78
src/overseer_client/src/components/ScanResult.vue
Normal file
78
src/overseer_client/src/components/ScanResult.vue
Normal 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>
|
@ -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',
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -1,26 +1,45 @@
|
||||
<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}` })
|
||||
}).catch(err => {
|
||||
// TODO: HANDLE ERROR
|
||||
console.log("ERROR: ", err);
|
||||
});
|
||||
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 => {
|
||||
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>
|
||||
|
@ -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>
|
||||
|
6
src/overseer_client/src/views/Search.vue
Normal file
6
src/overseer_client/src/views/Search.vue
Normal file
@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div style="padding-top: 300px;">
|
||||
<h1>Error</h1>
|
||||
<h4>404 Page Not Found</h4>
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue
Block a user