Add client comments & fix server side port sorting

This commit is contained in:
Evan Reichard 2021-03-21 21:02:58 -04:00
parent 464ed3d7ef
commit 008bb93111
19 changed files with 115 additions and 24 deletions

View File

@ -285,7 +285,7 @@ class Scanner(Thread):
results.extend(
list(map(lambda x: "%s TCP" % x, self.tcp_results))
) # noqa: E501
results.sort()
results.sort(key=lambda x: int(x.split(" ")[0]))
return results
def __scan_tcp(self):

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</title><link href="/static/css/app.4fe206b0.css" rel="preload" as="style"><link href="/static/js/app.e5e6eeee.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.1f751c93.js" rel="preload" as="script"><link href="/static/css/app.4fe206b0.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.1f751c93.js"></script><script src="/static/js/app.e5e6eeee.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.a6dc3078.css" rel="preload" as="style"><link href="/static/js/app.f1a42415.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.1f751c93.js" rel="preload" as="script"><link href="/static/css/app.a6dc3078.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.1f751c93.js"></script><script src="/static/js/app.f1a42415.js"></script></body></html>

View File

@ -25,6 +25,9 @@ export default {
}
},
methods: {
/**
* Emits a notification to the Notification component.
**/
toggleNotifications() {
this.$root.$emit("toggleNotifications");
}

View File

@ -32,9 +32,15 @@ export default {
}
},
methods: {
/**
* Routes the user to the provided scan.
**/
navigateToScan(scan) {
this.$router.push({ path: `/scan/${scan.target}/${scan.id}` })
},
/**
* Derives notification drawer CSS to open / close.
**/
deriveNotificationCSS(showNotifications) {
return {
"right": showNotifications ? "0px" : "-320px"
@ -42,6 +48,7 @@ export default {
}
},
mounted() {
// Sets up listener for toggleNotification event
this.$root.$on("toggleNotifications", () => {
this.showNotifications = !this.showNotifications;
});

View File

@ -1,8 +1,8 @@
<template>
<ul>
<li v-for="port in ports" :key="port" v-bind:style="getItemWidth()">
<span>{{ port.split(" ")[0] }}</span>
<span class="proto" v-bind:style="dynamicProtocolStyle(port.split(' ')[1])" >{{ port.split(" ")[1] }}</span>
<li v-for="port in ports" :key="port" v-bind:style="deriveItemWidth()">
<span style="margin: auto;">{{ port.split(" ")[0] }}</span>
<span class="proto" v-bind:style="deriveProtocolStyle(port.split(' ')[1])" >{{ port.split(" ")[1] }}</span>
</li>
</ul>
</template>
@ -12,12 +12,18 @@ export default {
name: 'PortList',
props: ["ports", "itemWidth"],
methods: {
dynamicProtocolStyle(proto) {
/**
* Derives TCP / UDP background colors (Default Wireshark colors)
**/
deriveProtocolStyle(proto) {
return {
backgroundColor: proto == "TCP" ? "#E7E6FF" : "#D5EFFF",
}
},
getItemWidth() {
/**
* Derives item width (If provided)
**/
deriveItemWidth() {
return {
width: this.itemWidth || "75px"
}
@ -31,7 +37,7 @@ export default {
border-radius: 4px;
padding: 0px 2px;
color: #0A282F;
margin-left: 5px;
margin: auto;
}
li {

View File

@ -24,6 +24,11 @@ export default {
PortList,
},
methods: {
/**
* Returns left outer join between s1 scan, and s2 scan. We use this
* to figure out what ports have been add / removed between the two
* reference scans.
**/
getPortDiff(s1, s2) {
return s1.results
.filter(p => !s2.results.includes(p));

View File

@ -1,7 +1,7 @@
<template>
<div class="scan-progress" >
<span style="right: 46.5%;">{{ title }}</span>
<div v-bind:style="scanProgress(percentage)"></div>
<div v-bind:style="deriveScanProgressStyle(percentage)"></div>
</div>
</template>
@ -10,8 +10,13 @@ export default {
name: 'ScanProgress',
props: ["percentage", "title"],
methods: {
scanProgress(perc) {
// Calculate color based on percentage - https://gist.github.com/mlocati/7210513
/**
* Derives the style to reflect the provided percentage. Also
* calculates the appropriate hex color.
*
* Reference: https://gist.github.com/mlocati/7210513
**/
deriveScanProgressStyle(perc) {
let r, g, b = 0;
if (perc < 50) {
r = 255;

View File

@ -42,6 +42,9 @@ export default {
ScanProgress,
},
methods: {
/**
* Converts a dateISO string to a more presentable format.
**/
normalizeDate(dateISO) {
let parsedDate = new Date(dateISO);
return parsedDate.toDateString() + " " + parsedDate.toLocaleTimeString()
@ -59,7 +62,7 @@ export default {
#sub-status {
text-align: center;
width: 200px;
width: 190px;
margin: -5px auto 10px auto;
border-bottom: 1px solid;
}

View File

@ -5,6 +5,9 @@ import router from './router';
import VueSocketIO from 'vue-socket.io';
import socketio from 'socket.io-client';
/**
* Initiates the Websocket connection with the API server.
**/
Vue.use(new VueSocketIO({
debug: true,
connection: socketio({ path: "/api/v1/socket.io" }),
@ -17,6 +20,9 @@ Vue.use(new VueSocketIO({
Vue.config.productionTip = false
/**
* Creates the Overseer Vue Application.
**/
new Vue({
render: h => h(App),
router,

View File

@ -7,6 +7,9 @@ import NotFound from '../views/NotFound.vue'
Vue.use(VueRouter)
/**
* Define all routes within the Overseer SPA.
**/
const routes = [
{
path: '/',

View File

@ -3,18 +3,21 @@ import Vuex from 'vuex'
Vue.use(Vuex)
/**
* Exports a new Vuex store. Responsible for API access and maintaining a cache
* of requested and pushed Websocket data.
**/
export default new Vuex.Store({
state: {
ws_connected: false,
notifications: [],
scan_cache: {}
},
getters: {
getScansByTarget(state, target){
return state.scan_cache[target]
}
},
actions: {
/**
* Queries the API for all scans for the given target, then commits the
* result to the store.
**/
getScansByTarget({ commit }, target){
return fetch('/api/v1/scans/' + target)
.then(resp => resp.json())
@ -22,6 +25,10 @@ export default new Vuex.Store({
commit("SET_TARGET_SCANS", { target, data: json.data });
});
},
/**
* Requests a scan from the API. On response, commits the new scan to
* the store.
**/
performScan({ commit }, target) {
return fetch('/api/v1/scans', {
method: 'POST',
@ -37,9 +44,15 @@ export default new Vuex.Store({
}
},
mutations: {
/**
* Sets the state of the scan cache for a given target.
**/
"SET_TARGET_SCANS"(state, { target, data }) {
Vue.set(state.scan_cache, target, data)
},
/**
* Upserts a scan in the scan cache store.
**/
"UPDATE_SCAN"(state, { scan }) {
let target = scan.target;
if (!state.scan_cache[target]) {
@ -56,6 +69,10 @@ export default new Vuex.Store({
state.scan_cache[target].unshift(scan);
}
},
/**
* Listens to all 'message' Websocket events for scan progress data
* in order to update the scan cache and notification queue.
**/
"SOCKET_message"(state, scan) {
// Update progress queue
let matchedItem = state.notifications.find(item => item.id == scan.id);
@ -69,9 +86,17 @@ export default new Vuex.Store({
// Update scan cache
this.commit("UPDATE_SCAN", { scan });
},
/**
* Listens for Websocket connect events. This is used for the green /
* red 'O' in the Overseer logo. Green = connected, Red = disconnected.
**/
"SOCKET_connect"(state) {
state.ws_connected = true
},
/**
* Listens for Websocket connect events. This is used for the green /
* red 'O' in the Overseer logo. Green = connected, Red = disconnected.
**/
"SOCKET_disconnect"(state) {
state.ws_connected = false
},

View File

@ -27,6 +27,10 @@ export default {
}
},
methods: {
/**
* Initiates a new scan for the inputted hostname / IP address.
* Routes the user to the history view of the requested target.
**/
onSubmit(event){
this.loading = true;
this.error = null;
@ -34,7 +38,8 @@ export default {
.then(scan => {
if (scan.error)
throw new Error(scan.error)
this.$router.push({ path: `/scan/${event.target.value}/${scan.id}` })
this.$router.push({ path: `/scan/${event.target.value}` })
// this.$router.push({ path: `/scan/${event.target.value}/${scan.id}` })
}).catch(err => {
this.error = err;
}).finally(() => {

View File

@ -16,7 +16,7 @@
:scan="scan"
:error="error"
:loading="loading"
style="padding: 20px; margin-top: 25px; border-radius: 5px; box-shadow: 0px 0px 10px #e0e3de;"/>
style="padding: 20px; margin-top: 25px; border-radius: 5px; box-shadow: 0px 0px 10px black;"/>
<div ref="compareContainer"
v-if="getRequestedScans.length != index + 1"
@ -49,6 +49,9 @@ export default {
}
},
computed: {
/**
* Returns requested scans based on filters and URI
**/
getRequestedScans() {
let scanCache = this.$store.state.scan_cache;
let target = this.$route.params.target;
@ -70,6 +73,10 @@ export default {
}
},
methods: {
/**
* Determines whether a comparison is expanded, and returns the
* appropriate style.
**/
deriveCompareStyle(scan) {
if (!this.openCompare.includes(scan))
return {};
@ -79,6 +86,10 @@ export default {
"max-height": "10000px",
}
},
/**
* Updates component variable to expand or collapse a specified
* scan comparison.
**/
toggleCompare(scan) {
if (this.openCompare.includes(scan))
this.openCompare = this.openCompare
@ -86,15 +97,20 @@ export default {
else
this.openCompare.push(scan);
},
/**
* Initiates a new scan for the currently displayed target and
* navigates to the history view of said target.
**/
performScan() {
this.error = null;
this.loading = true;
this.$store.dispatch("performScan", this.$route.params.target)
.then(scan => {
if (scan.error)
throw new Error(scan.error)
this.$router.push({ path: `/scan/${this.$route.params.target}` })
// If you want to navigate to the specific scan
// this.$router.push({ path: `/scan/${this.$route.params.target}/${scan.id}` })
}).catch(err => {
this.error = err;
@ -104,6 +120,7 @@ export default {
},
},
mounted(){
// Acquires scans by requested target from the Vuex store.
this.$store.dispatch("getScansByTarget", this.$route.params.target);
}
}

View File

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