Skip to content

Commit

Permalink
Give structure to dashboard app (#163)
Browse files Browse the repository at this point in the history
* Separate out files for different responsibilities.

1. Add src/plugins directory.  This directory holds one typescript file
   per plugin.  Each plugin is optionally can be displayed as a tab on
   the UI.
2. Move WebsocketApi to ws.ts.  This file contains all websocket APIs
   provided by dashboard.py backend.

* Make dashboard pluggable

* Move devtools under core too

* Register tabs dynamically

* Typescript fixes for abstract interfaces

* Initialize plugin app body skeleton

* Call activated / deactivated on tab change

* Move plugin name within plugin classes and initialize plugin within proxy dashboard constructor

* templatize api development plugin

* eslint fixes

* use globs

* Remove useless constructors
  • Loading branch information
abhinavsingh authored Nov 12, 2019
1 parent 0cc4e5e commit ee7a69b
Show file tree
Hide file tree
Showing 15 changed files with 577 additions and 288 deletions.
10 changes: 9 additions & 1 deletion dashboard/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,13 @@
"@typescript-eslint"
],
"rules": {
}
},
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"rules": {
"@typescript-eslint/no-unused-vars": [2, { "args": "none" }]
}
}
]
}
2 changes: 1 addition & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "index.js",
"scripts": {
"clean": "rm -rf tsbuild",
"lint": "eslint --global $ src/*.ts",
"lint": "eslint --global $ \"src/**/*.ts\" \"test/**/*.ts\"",
"pretest": "npm run clean && npm run lint && tsc --target es5 --outDir tsbuild test/test.ts",
"test": "jasmine tsbuild/test/test.js",
"build": "npm test && rollup -c",
Expand Down
File renamed without changes.
52 changes: 52 additions & 0 deletions dashboard/src/core/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
proxy.py
~~~~~~~~
⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable
proxy server for Application debugging, testing and development.
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
*/
import { WebsocketApi } from './ws'

export interface IDashboardPlugin {
name: string
initializeTab(): JQuery<HTMLElement>
initializeSkeleton(): JQuery<HTMLElement>
activated(): void
deactivated(): void
}

export interface IPluginConstructor {
new (websocketApi: WebsocketApi): IDashboardPlugin
}

export abstract class DashboardPlugin implements IDashboardPlugin {
public abstract readonly name: string
protected websocketApi: WebsocketApi

public constructor (websocketApi: WebsocketApi) {
this.websocketApi = websocketApi
}

public makeTab (name: string, icon: string) : JQuery<HTMLElement> {
return $('<a/>')
.attr({
href: '#',
plugin_name: this.name
})
.addClass('nav-link')
.text(name)
.prepend(
$('<i/>')
.addClass('fa')
.addClass('fa-fw')
.addClass(icon)
)
}

public abstract initializeTab() : JQuery<HTMLElement>
public abstract initializeSkeleton(): JQuery<HTMLElement>
public abstract activated(): void
public abstract deactivated(): void
}
26 changes: 26 additions & 0 deletions dashboard/src/core/plugins/home.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
proxy.py
~~~~~~~~
⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable
proxy server for Application debugging, testing and development.
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
*/
import { DashboardPlugin } from '../plugin'

export class HomePlugin extends DashboardPlugin {
public name: string = 'home'

public initializeTab () : JQuery<HTMLElement> {
return this.makeTab('Home', 'fa-home')
}

public initializeSkeleton (): JQuery<HTMLElement> {
return $('<div></div>')
}

public activated (): void {}

public deactivated (): void {}
}
30 changes: 30 additions & 0 deletions dashboard/src/core/plugins/inspect_traffic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
proxy.py
~~~~~~~~
⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable
proxy server for Application debugging, testing and development.
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
*/
import { DashboardPlugin } from '../plugin'

export class InspectTrafficPlugin extends DashboardPlugin {
public name: string = 'inspect_traffic'

public initializeTab () : JQuery<HTMLElement> {
return this.makeTab('Inspect Traffic', 'fa-binoculars')
}

public initializeSkeleton (): JQuery<HTMLElement> {
return $('<div></div>')
}

public activated (): void {
this.websocketApi.enableInspection()
}

public deactivated (): void {
this.websocketApi.disableInspection()
}
}
26 changes: 26 additions & 0 deletions dashboard/src/core/plugins/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
proxy.py
~~~~~~~~
⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable
proxy server for Application debugging, testing and development.
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
*/
import { DashboardPlugin } from '../plugin'

export class SettingsPlugin extends DashboardPlugin {
public name: string = 'settings'

public initializeTab () : JQuery<HTMLElement> {
return this.makeTab('Settings', 'fa-clog')
}

public initializeSkeleton (): JQuery<HTMLElement> {
return $('<div></div>')
}

public activated (): void {}

public deactivated (): void {}
}
26 changes: 26 additions & 0 deletions dashboard/src/core/plugins/traffic_control.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
proxy.py
~~~~~~~~
⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable
proxy server for Application debugging, testing and development.
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
*/
import { DashboardPlugin } from '../plugin'

export class TrafficControlPlugin extends DashboardPlugin {
public name: string = 'traffic_control'

public initializeTab () : JQuery<HTMLElement> {
return this.makeTab('Traffic Controls', 'fa-lock')
}

public initializeSkeleton (): JQuery<HTMLElement> {
return $('<div></div>')
}

public activated (): void {}

public deactivated (): void {}
}
141 changes: 141 additions & 0 deletions dashboard/src/core/ws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
proxy.py
~~~~~~~~
⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable
proxy server for Application debugging, testing and development.
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
*/

export class WebsocketApi {
private hostname: string = window.location.hostname ? window.location.hostname : 'localhost';
private port: number = window.location.port ? Number(window.location.port) : 8899;
// TODO: Must map to route registered by dashboard.py, don't hardcode
private wsPrefix: string = '/dashboard';
private wsScheme: string = window.location.protocol === 'http:' ? 'ws' : 'wss';
private ws: WebSocket;
private wsPath: string = this.wsScheme + '://' + this.hostname + ':' + this.port + this.wsPrefix;

private mid: number = 0;
private lastPingId: number;
private lastPingTime: number;

private readonly schedulePingEveryMs: number = 1000;
private readonly scheduleReconnectEveryMs: number = 5000;

private serverPingTimer: number;
private serverConnectTimer: number;

private inspectionEnabled: boolean;

constructor () {
this.scheduleServerConnect(0)
}

public static getTime () {
const date = new Date()
return date.getTime()
}

public enableInspection () {
// TODO: Set flag to true only once response has been received from the server
this.inspectionEnabled = true
this.ws.send(JSON.stringify({ id: this.mid, method: 'enable_inspection' }))
this.mid++
}

public disableInspection () {
this.inspectionEnabled = false
this.ws.send(JSON.stringify({ id: this.mid, method: 'disable_inspection' }))
this.mid++
}

private scheduleServerConnect (after_ms: number = this.scheduleReconnectEveryMs) {
this.clearServerConnectTimer()
this.serverConnectTimer = window.setTimeout(
this.connectToServer.bind(this), after_ms)
}

private connectToServer () {
this.ws = new WebSocket(this.wsPath)
this.ws.onopen = this.onServerWSOpen.bind(this)
this.ws.onmessage = this.onServerWSMessage.bind(this)
this.ws.onerror = this.onServerWSError.bind(this)
this.ws.onclose = this.onServerWSClose.bind(this)
}

private clearServerConnectTimer () {
if (this.serverConnectTimer == null) {
return
}
window.clearTimeout(this.serverConnectTimer)
this.serverConnectTimer = null
}

private scheduleServerPing (after_ms: number = this.schedulePingEveryMs) {
this.clearServerPingTimer()
this.serverPingTimer = window.setTimeout(
this.pingServer.bind(this), after_ms)
}

private pingServer () {
this.lastPingId = this.mid
this.lastPingTime = WebsocketApi.getTime()
this.mid++
// console.log('Pinging server with id:%d', this.last_ping_id);
this.ws.send(JSON.stringify({ id: this.lastPingId, method: 'ping' }))
}

private clearServerPingTimer () {
if (this.serverPingTimer != null) {
window.clearTimeout(this.serverPingTimer)
this.serverPingTimer = null
}
this.lastPingTime = null
this.lastPingId = null
}

private onServerWSOpen (ev: MessageEvent) {
this.clearServerConnectTimer()
WebsocketApi.setServerStatusSuccess('Connected...')
this.scheduleServerPing(0)
}

private onServerWSMessage (ev: MessageEvent) {
const message = JSON.parse(ev.data)
if (message.id === this.lastPingId) {
WebsocketApi.setServerStatusSuccess(
String((WebsocketApi.getTime() - this.lastPingTime) + ' ms'))
this.clearServerPingTimer()
this.scheduleServerPing()
} else {
console.log(message)
}
}

private onServerWSError (ev: MessageEvent) {
WebsocketApi.setServerStatusDanger()
}

private onServerWSClose (ev: MessageEvent) {
this.clearServerPingTimer()
this.scheduleServerConnect()
WebsocketApi.setServerStatusDanger()
}

public static setServerStatusDanger () {
$('#proxyServerStatus').parent('div')
.removeClass('text-success')
.addClass('text-danger')
$('#proxyServerStatusSummary').text('')
}

public static setServerStatusSuccess (summary: string) {
$('#proxyServerStatus').parent('div')
.removeClass('text-danger')
.addClass('text-success')
$('#proxyServerStatusSummary').text(
'(' + summary + ')')
}
}
Loading

0 comments on commit ee7a69b

Please sign in to comment.