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__/
|
**/__pycache__/
|
||||||
**/.coverage
|
**/.coverage
|
||||||
|
**/overseer.sqlite
|
||||||
src/overseer.egg-info/
|
src/overseer.egg-info/
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
@ -23,11 +23,14 @@ def post_scans():
|
|||||||
"""
|
"""
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if data is None or "target" not in data:
|
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"])
|
scan_history = overseer.scan_manager.perform_scan(data["target"])
|
||||||
if scan_history is None:
|
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]
|
return __normalize_scan_results([scan_history], data["target"])[0]
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class DatabaseConnector:
|
|||||||
|
|
||||||
for stale in all_stale:
|
for stale in all_stale:
|
||||||
stale.status = "FAILED"
|
stale.status = "FAILED"
|
||||||
stale.message = "Stale history"
|
stale.error = "Stale history"
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
|
@ -124,7 +124,7 @@ class ScanManager:
|
|||||||
Either a hostname or IP address of the endpoint to scan
|
Either a hostname or IP address of the endpoint to scan
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
target = socket.gethostbyname(target)
|
socket.gethostbyname(target)
|
||||||
except socket.error:
|
except socket.error:
|
||||||
return None
|
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",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div v-if="!this.$store.state.ws_connected" style="background-color: #B30000;" id="overseer-websocket"></div>
|
<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>
|
<div style="float: left; margin: 8px 20px; font-weight: 700; font-size: 2em;">Overseer</div>
|
||||||
</router-link>
|
</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;">
|
<p style="color: white; position: absolute; font-weight: 700; width: 35px; height: 35px; margin: 9px 0px; font-size: .8em;">
|
||||||
{{ notificationCount }}
|
{{ notificationCount }}
|
||||||
</p>
|
</p>
|
||||||
@ -23,6 +23,11 @@ export default {
|
|||||||
notificationCount() {
|
notificationCount() {
|
||||||
return this.$store.state.notifications.length
|
return this.$store.state.notifications.length
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleNotifications() {
|
||||||
|
this.$root.$emit("toggleNotifications");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,27 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="overseer-notifications">
|
<div id="overseer-notifications" v-bind:style="deriveNotificationCSS(showNotifications)">
|
||||||
<div v-on:click="navigateToScan(item)" class="overseer-notification" v-for="item in items" :key="item.id">
|
<div v-on:click="navigateToScan(scan)" class="overseer-notification" v-for="scan in activeScans" :key="scan.id">
|
||||||
<b>Target: </b> {{ item.target }} <br>
|
<div style="float: left; width: 100%; margin: 5px 0px;">
|
||||||
<b>Status: </b>{{ item.status }} <br>
|
<b style="float: left;">{{ scan.target }}</b>
|
||||||
<span v-if="'total_progress' in item">
|
<b style="float: right;">{{ scan.status }}</b>
|
||||||
<b>Progress: </b>{{item.total_progress}} <br>
|
</div>
|
||||||
</span>
|
<ScanProgress
|
||||||
|
:percentage="scan.status =='COMPLETE' ? 100 : scan.total_progress"
|
||||||
|
style="margin-top: 5px;"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ScanProgress from '../components/ScanProgress.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OverseerNotifications',
|
name: 'OverseerNotifications',
|
||||||
|
components: {
|
||||||
|
ScanProgress,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showNotifications: true
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
items() {
|
activeScans() {
|
||||||
return this.$store.state.notifications
|
return this.$store.state.notifications
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
navigateToScan(item) {
|
navigateToScan(scan) {
|
||||||
this.$router.push({ path: `/scan/${item.target}/${item.id}` })
|
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>
|
</script>
|
||||||
@ -40,7 +63,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.overseer-notification {
|
.overseer-notification {
|
||||||
height: 65px;
|
|
||||||
transition: 0.5s;
|
transition: 0.5s;
|
||||||
background-color: #E0E3DE;
|
background-color: #E0E3DE;
|
||||||
border-radius: 2px;
|
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 VueRouter from 'vue-router'
|
||||||
import Home from '../views/Home.vue'
|
import Home from '../views/Home.vue'
|
||||||
import Scan from '../views/Scan.vue'
|
import Scan from '../views/Scan.vue'
|
||||||
|
import Search from '../views/Search.vue'
|
||||||
import NotFound from '../views/NotFound.vue'
|
import NotFound from '../views/NotFound.vue'
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter)
|
||||||
@ -22,6 +23,11 @@ const routes = [
|
|||||||
name: 'Scan',
|
name: 'Scan',
|
||||||
component: Scan
|
component: Scan
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/search/:target',
|
||||||
|
name: 'Search',
|
||||||
|
component: Search
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
name: 'NotFound',
|
name: 'NotFound',
|
||||||
|
@ -16,48 +16,64 @@ export default new Vuex.Store({
|
|||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
getScansByTarget({ commit }, target){
|
getScansByTarget({ commit }, target){
|
||||||
fetch('/api/v1/scans/' + target)
|
return fetch('/api/v1/scans/' + target)
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.then(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: {
|
mutations: {
|
||||||
"SET_SCANS"(state, { target, data }) {
|
"SET_TARGET_SCANS"(state, { target, data }) {
|
||||||
console.log("SET_SCANS: ", target, data)
|
|
||||||
Vue.set(state.scan_cache, 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) {
|
"SOCKET_connect"(state) {
|
||||||
state.ws_connected = true
|
state.ws_connected = true
|
||||||
},
|
},
|
||||||
"SOCKET_disconnect"(state) {
|
"SOCKET_disconnect"(state) {
|
||||||
state.ws_connected = false
|
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>
|
<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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Loading from '../components/Loading.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OverseerHome',
|
name: 'OverseerHome',
|
||||||
|
components: {
|
||||||
|
Loading,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
error: null,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onSubmit(event){
|
onSubmit(event){
|
||||||
fetch('/api/v1/scans', {
|
this.loading = true;
|
||||||
method: 'POST',
|
this.error = null;
|
||||||
headers: {
|
this.$store.dispatch("performScan", event.target.value)
|
||||||
'Content-Type': 'application/json'
|
.then(scan => {
|
||||||
},
|
if (scan.error)
|
||||||
body: JSON.stringify({ "target": event.target.value })
|
throw new Error(scan.error)
|
||||||
}).then(resp => resp.json()).then(json => {
|
this.$router.push({ path: `/scan/${event.target.value}/${scan.id}` })
|
||||||
// TODO: HANDLE POTENTIAL ERROR
|
}).catch(err => {
|
||||||
console.log("RESPONSE: ", json);
|
this.error = err;
|
||||||
this.$router.push({ path: `/scan/${event.target.value}` })
|
}).finally(() => {
|
||||||
}).catch(err => {
|
this.loading = false;
|
||||||
// TODO: HANDLE ERROR
|
});
|
||||||
console.log("ERROR: ", err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,16 +49,30 @@ export default {
|
|||||||
#overseer-search::placeholder{
|
#overseer-search::placeholder{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#overseer-search {
|
#overseer-search {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 400px;
|
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: .3rem .8rem;
|
padding: .3rem .8rem;
|
||||||
text-indent: 0px;
|
text-indent: 0px;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: 0px solid;
|
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>
|
</style>
|
||||||
|
@ -1,79 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="overseer-scan" style="padding: 50px;">
|
<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 id="results">
|
||||||
<div v-if="getRequestedScan == 'LOADING'">
|
<ScanResult :scan="getRequestedScan" :error="error" :loading="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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Loading from '../components/Loading.vue'
|
import ScanResult from '../components/ScanResult.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Scan',
|
name: 'Scan',
|
||||||
components: {
|
components: {
|
||||||
Loading,
|
ScanResult,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
error: null,
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
scanProgress(perc) {
|
performScan() {
|
||||||
// Calculate color based on percentage - https://gist.github.com/mlocati/7210513
|
this.error = null;
|
||||||
let r, g, b = 0;
|
this.loading = true;
|
||||||
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 {
|
this.$store.dispatch("performScan", this.$route.params.target)
|
||||||
"width": perc + "%",
|
.then(scan => {
|
||||||
"backgroundColor": hexColor,
|
if (scan.error)
|
||||||
}
|
throw new Error(scan.error)
|
||||||
},
|
this.$router.push({ path: `/scan/${this.$route.params.target}/${scan.id}` })
|
||||||
dynamicProtocolStyle(proto) {
|
}).catch(err => {
|
||||||
return {
|
this.error = err;
|
||||||
"backgroundColor": proto == "TCP" ? "#E7E6FF" : "#D5EFFF",
|
}).finally(() => {
|
||||||
}
|
this.loading = false;
|
||||||
},
|
});
|
||||||
normalizeDate(dateISO) {
|
|
||||||
let parsedDate = new Date(dateISO);
|
|
||||||
return parsedDate.toDateString() + " " + parsedDate.toLocaleTimeString()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -83,15 +47,16 @@ export default {
|
|||||||
let scanID = this.$route.params.scan_id;
|
let scanID = this.$route.params.scan_id;
|
||||||
|
|
||||||
if (!scanCache[target])
|
if (!scanCache[target])
|
||||||
return "LOADING"
|
return { "status": "LOADING" }
|
||||||
|
|
||||||
if (scanID)
|
if (scanID)
|
||||||
return scanCache[target].find(item => item.id == scanID);
|
return scanCache[target]
|
||||||
|
.find(item => item.id == scanID) || { "status": "NO_RESULTS" };
|
||||||
else
|
else
|
||||||
return scanCache[target][0] || "NO_RESULTS";
|
return scanCache[target][0] || { "status": "NO_RESULTS" };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created(){
|
mounted(){
|
||||||
this.$store.dispatch("getScansByTarget", this.$route.params.target);
|
this.$store.dispatch("getScansByTarget", this.$route.params.target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,39 +72,6 @@ export default {
|
|||||||
margin: 150px auto 0px auto;
|
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 {
|
li {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 10px 43px;
|
margin: 10px 43px;
|
||||||
@ -158,4 +90,15 @@ ul {
|
|||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0px;
|
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>
|
</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