Consistency, Routing, Progress, Styling
This commit is contained in:
parent
3d1e945fe1
commit
6d4cc4a11b
@ -25,11 +25,11 @@ def post_scans():
|
||||
if data is None or "target" not in data:
|
||||
return {"error": "Missing 'target'"}, 422
|
||||
|
||||
history_id = overseer.scan_manager.perform_scan(data["target"])
|
||||
if history_id is None:
|
||||
scan_history = overseer.scan_manager.perform_scan(data["target"])
|
||||
if scan_history is None:
|
||||
return {"error": "Unable to resolve hostname."}, 422
|
||||
|
||||
return {"status": "started", "id": history_id}
|
||||
return __normalize_scan_results([scan_history], data["target"])[0]
|
||||
|
||||
|
||||
@api.route("/scans/<string:target>", methods=["GET"])
|
||||
@ -51,7 +51,7 @@ def get_scans_by_target(target):
|
||||
page, hostname=target
|
||||
)
|
||||
|
||||
return {"data": __normalize_scan_results(scan_results)}
|
||||
return {"data": __normalize_scan_results(scan_results, target)}
|
||||
|
||||
|
||||
@api.route("/search", methods=["GET"])
|
||||
@ -66,13 +66,15 @@ def get_scans(search):
|
||||
return "SEARCH PLACEHOLDER"
|
||||
|
||||
|
||||
def __normalize_scan_results(scan_results):
|
||||
def __normalize_scan_results(scan_results, target):
|
||||
return list(
|
||||
map(
|
||||
lambda x: {
|
||||
"results": x.results.split(","),
|
||||
"created_at": x.created_at,
|
||||
"id": x.id,
|
||||
"target": target,
|
||||
"status": x.status,
|
||||
"results": x.results.split(",") if x.results != "" else [],
|
||||
"created_at": x.created_at.isoformat(),
|
||||
"error": x.error,
|
||||
},
|
||||
scan_results,
|
||||
|
@ -11,6 +11,14 @@ def main_entry():
|
||||
return make_response(render_template("index.html"))
|
||||
|
||||
|
||||
@app.route("/<path:path>", methods=["GET"])
|
||||
def catch_all(path):
|
||||
"""
|
||||
Necessary due to client side SPA route handling.
|
||||
"""
|
||||
return make_response(render_template("index.html"))
|
||||
|
||||
|
||||
@app.route("/static/<path:path>")
|
||||
def static_resources(path):
|
||||
"""
|
||||
|
@ -17,18 +17,22 @@ class ScanManager:
|
||||
|
||||
def __broadcast_thread(self):
|
||||
while not self.pending_shutdown:
|
||||
time.sleep(5)
|
||||
time.sleep(1)
|
||||
if len(self.active_scans) == 0:
|
||||
continue
|
||||
|
||||
for scan in self.active_scans:
|
||||
# WebSocket progress
|
||||
total_progress = (scan.tcp_progress + scan.udp_progress) / 2
|
||||
results = scan.get_results()
|
||||
overseer.api.send_websocket_event(
|
||||
{
|
||||
"id": scan.history_id,
|
||||
"id": scan.scan_history.id,
|
||||
"target": scan.target,
|
||||
"status": "RUNNING",
|
||||
"status": "IN_PROGRESS",
|
||||
"results": results,
|
||||
"created_at": scan.scan_history.created_at.isoformat(),
|
||||
"error": scan.scan_history.error,
|
||||
"tcp_progress": scan.tcp_progress,
|
||||
"udp_progress": scan.udp_progress,
|
||||
"total_progress": round(total_progress),
|
||||
@ -38,25 +42,20 @@ class ScanManager:
|
||||
if scan.tcp_progress + scan.udp_progress != 200:
|
||||
continue
|
||||
|
||||
# Combine ports
|
||||
results = list(map(lambda x: "%s UDP" % x, scan.udp_results))
|
||||
results.extend(
|
||||
list(map(lambda x: "%s TCP" % x, scan.tcp_results))
|
||||
) # noqa: E501
|
||||
results.sort()
|
||||
|
||||
# Update database
|
||||
overseer.database.update_scan_result(
|
||||
scan.history_id, "COMPLETE", results=results
|
||||
scan_history = overseer.database.update_scan_result(
|
||||
scan.scan_history.id, "COMPLETE", results=results
|
||||
)
|
||||
|
||||
# WebSocket completion
|
||||
overseer.api.send_websocket_event(
|
||||
{
|
||||
"id": scan.history_id,
|
||||
"id": scan_history.id,
|
||||
"target": scan.target,
|
||||
"status": "COMPLETE",
|
||||
"status": scan_history.status,
|
||||
"results": results,
|
||||
"created_at": scan_history.created_at.isoformat(),
|
||||
"error": scan_history.error,
|
||||
}
|
||||
)
|
||||
|
||||
@ -72,9 +71,12 @@ class ScanManager:
|
||||
return list(
|
||||
map(
|
||||
lambda x: {
|
||||
"id": x.history_id,
|
||||
"id": x.scan_history.id,
|
||||
"target": x.target,
|
||||
"status": "IN_PROGRESS",
|
||||
"results": x.get_results(),
|
||||
"created_at": x.scan_history.created_at.isoformat(),
|
||||
"error": x.scan_history.error,
|
||||
"tcp_progress": x.tcp_progress,
|
||||
"udp_progress": x.udp_progress,
|
||||
"total_progress": round(
|
||||
@ -100,10 +102,10 @@ class ScanManager:
|
||||
"IN_PROGRESS", hostname=target
|
||||
)
|
||||
|
||||
new_scan = Scanner(target, scan_history.id)
|
||||
new_scan = Scanner(target, scan_history)
|
||||
new_scan.start()
|
||||
self.active_scans.append(new_scan)
|
||||
return scan_history.id
|
||||
return scan_history
|
||||
|
||||
def is_ip(self, target):
|
||||
try:
|
||||
@ -114,10 +116,10 @@ class ScanManager:
|
||||
|
||||
|
||||
class Scanner(Thread):
|
||||
def __init__(self, target, history_id):
|
||||
def __init__(self, target, scan_history):
|
||||
Thread.__init__(self)
|
||||
self.target = target
|
||||
self.history_id = history_id
|
||||
self.scan_history = scan_history
|
||||
|
||||
self.port_count = 1000
|
||||
|
||||
@ -181,6 +183,14 @@ class Scanner(Thread):
|
||||
udp_thread.join()
|
||||
return {"TCP": self.tcp_results, "UDP": self.udp_results}
|
||||
|
||||
def get_results(self):
|
||||
results = list(map(lambda x: "%s UDP" % x, self.udp_results))
|
||||
results.extend(
|
||||
list(map(lambda x: "%s TCP" % x, self.tcp_results))
|
||||
) # noqa: E501
|
||||
results.sort()
|
||||
return results
|
||||
|
||||
def __scan_tcp(self):
|
||||
for port in range(1, self.port_count):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
1
src/overseer/static/css/app.a50f799c.css
Normal file
1
src/overseer/static/css/app.a50f799c.css
Normal file
@ -0,0 +1 @@
|
||||
#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 +0,0 @@
|
||||
#overseer-body[data-v-352462f2]{width:100%;height:100%}#center[data-v-352462f2]{margin:0 auto;width:1000px;height:100%;background-color:#343a46;box-shadow:inset 0 0 3px 3px #2b303a}#overseer-search[data-v-352462f2]::-moz-placeholder{text-align:center}#overseer-search[data-v-352462f2]:-ms-input-placeholder{text-align:center}#overseer-search[data-v-352462f2]::placeholder{text-align:center}#overseer-search[data-v-352462f2]{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}#overseer-header[data-v-1f75c2e8]{background-color:#c9582c;width:100%;height:60px;position:fixed;top:0}#overseer-notifications[data-v-47117d80]{height:calc(100% - 60px);width:320px;position:fixed;transition:.5s;right:0;overflow:scroll;margin-top:60px}.overseer-notification[data-v-47117d80]{height:65px;transition:.5s;background-color:#e0e3de;border-radius:2px;text-align:start;padding:10px;margin:10px}#overseer-app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#3a4040;height:100%}body,html{height:100%;width:100%;margin:0;background-color:#2b303a}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
src/overseer/static/js/app.e5dd211e.js
Normal file
2
src/overseer/static/js/app.e5dd211e.js
Normal file
File diff suppressed because one or more lines are too long
1
src/overseer/static/js/app.e5dd211e.js.map
Normal file
1
src/overseer/static/js/app.e5dd211e.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
1
src/overseer/static/js/chunk-vendors.cf069257.js.map
Normal file
1
src/overseer/static/js/chunk-vendors.cf069257.js.map
Normal file
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.efe1070d.css" rel="preload" as="style"><link href="/static/js/app.b2809d88.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.6130e7bc.js" rel="preload" as="script"><link href="/static/css/app.efe1070d.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.6130e7bc.js"></script><script src="/static/js/app.b2809d88.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_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>
|
@ -18,6 +18,7 @@
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
|
@ -2,19 +2,21 @@
|
||||
<div id="overseer-app">
|
||||
<OverseerHeader/>
|
||||
<OverseerNotifications/>
|
||||
<OverseerHome/>
|
||||
<div id="overseer-body">
|
||||
<div id="overseer-body-center">
|
||||
<router-view/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OverseerHome from './components/OverseerHome.vue'
|
||||
import OverseerHeader from './components/OverseerHeader.vue'
|
||||
import OverseerNotifications from './components/OverseerNotifications.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
OverseerHome,
|
||||
OverseerHeader,
|
||||
OverseerNotifications
|
||||
}
|
||||
@ -23,18 +25,38 @@ export default {
|
||||
|
||||
<style>
|
||||
#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;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#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: 0px auto;
|
||||
background-color: #343A46;
|
||||
box-shadow: 0 0 3px 3px #2B303A inset;
|
||||
}
|
||||
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
background-color: #2B303A;
|
||||
}
|
||||
</style>
|
||||
|
93
src/overseer_client/src/components/Loading.vue
Normal file
93
src/overseer_client/src/components/Loading.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<!-- https://loading.io/css/ -->
|
||||
<!-- CC0 Creative Commons License -->
|
||||
|
||||
<template>
|
||||
<div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.lds-roller {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-roller div {
|
||||
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
transform-origin: 40px 40px;
|
||||
}
|
||||
.lds-roller div:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
margin: -4px 0 0 -4px;
|
||||
}
|
||||
.lds-roller div:nth-child(1) {
|
||||
animation-delay: -0.036s;
|
||||
}
|
||||
.lds-roller div:nth-child(1):after {
|
||||
top: 63px;
|
||||
left: 63px;
|
||||
}
|
||||
.lds-roller div:nth-child(2) {
|
||||
animation-delay: -0.072s;
|
||||
}
|
||||
.lds-roller div:nth-child(2):after {
|
||||
top: 68px;
|
||||
left: 56px;
|
||||
}
|
||||
.lds-roller div:nth-child(3) {
|
||||
animation-delay: -0.108s;
|
||||
}
|
||||
.lds-roller div:nth-child(3):after {
|
||||
top: 71px;
|
||||
left: 48px;
|
||||
}
|
||||
.lds-roller div:nth-child(4) {
|
||||
animation-delay: -0.144s;
|
||||
}
|
||||
.lds-roller div:nth-child(4):after {
|
||||
top: 72px;
|
||||
left: 40px;
|
||||
}
|
||||
.lds-roller div:nth-child(5) {
|
||||
animation-delay: -0.18s;
|
||||
}
|
||||
.lds-roller div:nth-child(5):after {
|
||||
top: 71px;
|
||||
left: 32px;
|
||||
}
|
||||
.lds-roller div:nth-child(6) {
|
||||
animation-delay: -0.216s;
|
||||
}
|
||||
.lds-roller div:nth-child(6):after {
|
||||
top: 68px;
|
||||
left: 24px;
|
||||
}
|
||||
.lds-roller div:nth-child(7) {
|
||||
animation-delay: -0.252s;
|
||||
}
|
||||
.lds-roller div:nth-child(7):after {
|
||||
top: 63px;
|
||||
left: 17px;
|
||||
}
|
||||
.lds-roller div:nth-child(8) {
|
||||
animation-delay: -0.288s;
|
||||
}
|
||||
.lds-roller div:nth-child(8):after {
|
||||
top: 56px;
|
||||
left: 12px;
|
||||
}
|
||||
@keyframes lds-roller {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div id="overseer-header">
|
||||
<div style="float: left; margin: 8px 20px; font-weight: 700; font-size: 2em;">Overseer</div>
|
||||
<router-link to="/">
|
||||
<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="float: left; margin: 8px 20px; font-weight: 700; font-size: 2em;">Overseer</div>
|
||||
</router-link>
|
||||
<div style="float: right; margin: 10px 20px;">
|
||||
<p style="color: white; position: absolute; font-weight: 700; width: 35px; height: 35px; margin: 9px 0px; font-size: .8em;">
|
||||
{{ notificationCount }}
|
||||
@ -24,6 +28,15 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#overseer-websocket {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: 8px;
|
||||
position: absolute;
|
||||
left: 25px;
|
||||
top: 20px;
|
||||
z-index: -1;
|
||||
}
|
||||
#overseer-header {
|
||||
background-color: #C9582C;
|
||||
width: 100%;
|
||||
@ -31,4 +44,8 @@ export default {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#overseer-header a {
|
||||
color: inherit;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,48 +0,0 @@
|
||||
<template>
|
||||
<div id="overseer-body">
|
||||
<div id="center">
|
||||
<input @keyup.enter="onSubmit" id="overseer-search" placeholder="IP Address / Hostname" type="text">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'OverseerHome',
|
||||
methods: {
|
||||
onSubmit(event){
|
||||
alert(event.target.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#overseer-body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#center {
|
||||
margin: 0px auto;
|
||||
width: 1000px;
|
||||
height: 100%;
|
||||
background-color: #343A46;
|
||||
/* background-color: #CAD2C5; */
|
||||
box-shadow: 0 0 3px 3px #2B303A inset;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
</style>
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div id="overseer-notifications">
|
||||
<div class="overseer-notification" v-for="item in items" :key="item.id">
|
||||
<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">
|
||||
@ -17,6 +17,11 @@ export default {
|
||||
items() {
|
||||
return this.$store.state.notifications
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navigateToScan(item) {
|
||||
this.$router.push({ path: `/scan/${item.target}/${item.id}` })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -28,8 +33,10 @@ export default {
|
||||
position: fixed;
|
||||
transition: 0.5s;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
overflow: scroll;
|
||||
margin-top: 60px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.overseer-notification {
|
||||
@ -40,5 +47,6 @@ export default {
|
||||
text-align: start;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,8 +1,9 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import store from './store'
|
||||
import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
import store from './store';
|
||||
import router from './router';
|
||||
import VueSocketIO from 'vue-socket.io';
|
||||
import socketio from 'socket.io-client';
|
||||
import VueSocketIO from 'vue-socket.io'
|
||||
|
||||
Vue.use(new VueSocketIO({
|
||||
debug: true,
|
||||
@ -16,9 +17,8 @@ Vue.use(new VueSocketIO({
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
console.log(store)
|
||||
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
store,
|
||||
router,
|
||||
store
|
||||
}).$mount('#app')
|
||||
|
38
src/overseer_client/src/router/index.js
Normal file
38
src/overseer_client/src/router/index.js
Normal file
@ -0,0 +1,38 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import Home from '../views/Home.vue'
|
||||
import Scan from '../views/Scan.vue'
|
||||
import NotFound from '../views/NotFound.vue'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
path: '/scan/:target/:scan_id',
|
||||
name: 'Scan',
|
||||
component: Scan
|
||||
},
|
||||
{
|
||||
path: '/scan/:target',
|
||||
name: 'Scan',
|
||||
component: Scan
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
name: 'NotFound',
|
||||
component: NotFound
|
||||
}
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: '/',
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
@ -6,29 +6,58 @@ Vue.use(Vuex)
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
ws_connected: false,
|
||||
notifications: []
|
||||
notifications: [],
|
||||
scan_cache: {}
|
||||
},
|
||||
mutations: {
|
||||
"SOCKET_connect"(state, status) {
|
||||
state.ws_connected = true
|
||||
console.log("CONNECT: ", state, status);
|
||||
},
|
||||
"SOCKET_disconnect"(state, status) {
|
||||
state.ws_connected = false
|
||||
console.log("DISCONNECT: ", state, status);
|
||||
},
|
||||
"SOCKET_message"(state, message) {
|
||||
let matchedItem = state.notifications.find(item => item.id == message.id);
|
||||
if (matchedItem){
|
||||
Object.keys(message).forEach(key => {
|
||||
// matchedItem[key] = message[key]
|
||||
Vue.set(matchedItem, key, message[key])
|
||||
});
|
||||
} else {
|
||||
state.notifications.push(message);
|
||||
}
|
||||
getters: {
|
||||
getScansByTarget(state, target){
|
||||
return state.scan_cache[target]
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
}
|
||||
getScansByTarget({ commit }, target){
|
||||
fetch('/api/v1/scans/' + target)
|
||||
.then(resp => resp.json())
|
||||
.then(json => {
|
||||
commit("SET_SCANS", { target, data: json.data });
|
||||
});
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
"SET_SCANS"(state, { target, data }) {
|
||||
console.log("SET_SCANS: ", target, data)
|
||||
Vue.set(state.scan_cache, target, data)
|
||||
},
|
||||
"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);
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
45
src/overseer_client/src/views/Home.vue
Normal file
45
src/overseer_client/src/views/Home.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<input @keyup.enter="onSubmit" id="overseer-search" placeholder="IP Address / Hostname" type="text">
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'OverseerHome',
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#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;
|
||||
}
|
||||
</style>
|
6
src/overseer_client/src/views/NotFound.vue
Normal file
6
src/overseer_client/src/views/NotFound.vue
Normal file
@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div style="padding-top: 300px;">
|
||||
<h1>Error</h1>
|
||||
<h4>404 Page Not Found</h4>
|
||||
</div>
|
||||
</template>
|
161
src/overseer_client/src/views/Scan.vue
Normal file
161
src/overseer_client/src/views/Scan.vue
Normal file
@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<div id="overseer-scan" style="padding: 50px;">
|
||||
<h1 style="font-size: 2.5em; margin: 0px;">{{ $route.params.target }}</h1>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Loading from '../components/Loading.vue'
|
||||
|
||||
export default {
|
||||
name: 'Scan',
|
||||
components: {
|
||||
Loading,
|
||||
},
|
||||
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,
|
||||
}
|
||||
},
|
||||
dynamicProtocolStyle(proto) {
|
||||
return {
|
||||
"backgroundColor": proto == "TCP" ? "#E7E6FF" : "#D5EFFF",
|
||||
}
|
||||
},
|
||||
normalizeDate(dateISO) {
|
||||
let parsedDate = new Date(dateISO);
|
||||
return parsedDate.toDateString() + " " + parsedDate.toLocaleTimeString()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getRequestedScan() {
|
||||
let scanCache = this.$store.state.scan_cache;
|
||||
let target = this.$route.params.target;
|
||||
let scanID = this.$route.params.scan_id;
|
||||
|
||||
if (!scanCache[target])
|
||||
return "LOADING"
|
||||
|
||||
if (scanID)
|
||||
return scanCache[target].find(item => item.id == scanID);
|
||||
else
|
||||
return scanCache[target][0] || "NO_RESULTS";
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.$store.dispatch("getScansByTarget", this.$route.params.target);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#overseer-scan {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#results {
|
||||
width: 500px;
|
||||
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;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
li span {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
column-count: 3;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
list-style-type: none;
|
||||
padding: 0px;
|
||||
}
|
||||
</style>
|
@ -1256,6 +1256,13 @@
|
||||
dependencies:
|
||||
"@vue/cli-shared-utils" "^4.5.11"
|
||||
|
||||
"@vue/cli-plugin-router@~4.5.0":
|
||||
version "4.5.12"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.12.tgz#977c4b2b694cc03e9ef816112a5d58923493d0ac"
|
||||
integrity sha512-DYNz5AA3W7Ewt3aaiOLGdYFt4MX4w/HTEtep+kPzP9S9tAknzyoIJXkaYzhwu8ArpEYwhWgtuCcDZ8hR6++DbA==
|
||||
dependencies:
|
||||
"@vue/cli-shared-utils" "^4.5.12"
|
||||
|
||||
"@vue/cli-plugin-vuex@^4.5.11":
|
||||
version "4.5.11"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.11.tgz#f6f619bcfb66c86cc45340d73152844635e548bd"
|
||||
@ -1342,6 +1349,24 @@
|
||||
semver "^6.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
"@vue/cli-shared-utils@^4.5.12":
|
||||
version "4.5.12"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.12.tgz#0e0693d488336d284ffa658ff33b1ea22927d065"
|
||||
integrity sha512-qnIQPJ4XckMoqYh9fJ0Y91QKMIb4Hiibrm9+k4E15QHpk5RaokuOpf10SsOr2NLPCXSWsHOLo3hduZSwHPGY/Q==
|
||||
dependencies:
|
||||
"@hapi/joi" "^15.0.1"
|
||||
chalk "^2.4.2"
|
||||
execa "^1.0.0"
|
||||
launch-editor "^2.2.1"
|
||||
lru-cache "^5.1.1"
|
||||
node-ipc "^9.1.1"
|
||||
open "^6.3.0"
|
||||
ora "^3.4.0"
|
||||
read-pkg "^5.1.1"
|
||||
request "^2.88.2"
|
||||
semver "^6.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
"@vue/component-compiler-utils@^3.1.0", "@vue/component-compiler-utils@^3.1.2":
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz#8f85182ceed28e9b3c75313de669f83166d11e5d"
|
||||
|
Loading…
Reference in New Issue
Block a user