Consistency, Routing, Progress, Styling

This commit is contained in:
Evan Reichard 2021-03-20 19:26:20 -04:00
parent 3d1e945fe1
commit 6d4cc4a11b
26 changed files with 544 additions and 122 deletions

View File

@ -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,

View File

@ -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):
"""

View File

@ -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)

View 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}

View File

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/favicon.ico"><title>overseer_client</title><link href="/static/css/app.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>

View File

@ -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",

View File

@ -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>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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')

View 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

View File

@ -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);
}
}
},
})

View 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>

View File

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

View 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>

View File

@ -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"