1- import { useRef , useState } from "react" ;
1+ import { useEffect , useId , useRef , useState } from "react" ;
22import { SubmitHandler , useForm } from "react-hook-form" ;
33import toast from "react-hot-toast" ;
44import { BiSolidServer } from "react-icons/bi" ;
5+ import { FaCopy } from "react-icons/fa" ;
56import { useAPI } from "@client/context/API" ;
67import { TeamTokenGenericRequest } from "@shared/api" ;
78import { WEBSOCKET_ENDPOINTS } from "@shared/endpoints" ;
89
10+ type CopyButtonProps = {
11+ text : string ;
12+ tooltip ?: string ;
13+ copiedTooltip ?: string ;
14+ ariaLabel ?: string ;
15+ buttonClassName ?: string ;
16+ className ?: string ;
17+ } ;
18+
19+ async function copyToClipboard ( text : string ) {
20+ if ( navigator . clipboard ?. writeText ) {
21+ await navigator . clipboard . writeText ( text ) ;
22+ return ;
23+ }
24+ }
25+
26+ export function CopyIconButton ( {
27+ text,
28+ tooltip = "Copy" ,
29+ copiedTooltip = "Copied!" ,
30+ ariaLabel = "Copy to clipboard" ,
31+ buttonClassName = "btn btn-ghost btn-sm" ,
32+ className = "" ,
33+ } : CopyButtonProps ) {
34+ const [ copied , setCopied ] = useState ( false ) ;
35+ const liveId = useId ( ) ;
36+ const timerRef = useRef < number | null > ( null ) ;
37+
38+ useEffect ( ( ) => {
39+ return ( ) => {
40+ if ( timerRef . current ) window . clearTimeout ( timerRef . current ) ;
41+ } ;
42+ } , [ ] ) ;
43+
44+ async function handleCopy ( ) {
45+ try {
46+ await copyToClipboard ( text ) ;
47+ setCopied ( true ) ;
48+
49+ if ( timerRef . current ) window . clearTimeout ( timerRef . current ) ;
50+ timerRef . current = window . setTimeout ( ( ) => setCopied ( false ) , 1200 ) ;
51+ } catch ( err ) {
52+ console . error ( err ) ;
53+ }
54+ }
55+
56+ return (
57+ < span className = { `inline-flex items-center gap-2 ${ className } ` } >
58+ < div className = "tooltip tooltip-bottom" data-tip = { copied ? copiedTooltip : tooltip } >
59+ < button
60+ type = "button"
61+ className = { buttonClassName }
62+ onClick = { handleCopy }
63+ aria-label = { ariaLabel }
64+ aria-describedby = { liveId }
65+ >
66+ < FaCopy size = { 16 } />
67+ </ button >
68+ </ div >
69+
70+ < span id = { liveId } className = "sr-only" aria-live = "polite" >
71+ { copied ? copiedTooltip : "" }
72+ </ span >
73+ </ span >
74+ ) ;
75+ }
76+
977export const ExploitServer : React . FC = ( ) => {
1078 const { featuresConfig } = useAPI ( ) ;
1179 const [ isConnected , setIsConnected ] = useState < boolean > ( false ) ;
@@ -57,14 +125,20 @@ export const ExploitServer: React.FC = () => {
57125
58126 return (
59127 < div className = "w-full flex flex-col gap-2" >
60- < div className = "flex items-center justify-between mb-2" >
128+ < div className = "flex items-start gap-2 justify-between mb-2 flex-col lg:flex-row " >
61129 < div className = "flex items-center gap-2" >
62130 < BiSolidServer size = { 32 } />
63131 < h1 className = "text-2xl font-semibold select-none" > Exploit Server</ h1 >
64132 </ div >
65133 { featuresConfig . ipAddress . length > 0 && (
66- < span className = "indicator-item badge badge-primary" >
67- < b > IP Address:</ b > < code > { featuresConfig . ipAddress } </ code >
134+ < span className = "indicator-item badge badge-primary py-4" >
135+ < b > Host:</ b > < code > { featuresConfig . ipAddress } </ code >
136+ < CopyIconButton
137+ text = { featuresConfig . ipAddress }
138+ buttonClassName = "btn btn-ghost hover:bg-white hover:text-primary btn-square btn-xs"
139+ tooltip = "Copy Host"
140+ copiedTooltip = "Copied!"
141+ />
68142 </ span >
69143 ) }
70144 </ div >
@@ -77,8 +151,9 @@ export const ExploitServer: React.FC = () => {
77151 suggest you to < b > keep this page open</ b > .
78152 </ p >
79153 < p className = "text-info" >
80- You will be able to visualize all the network interactions made to < code > /exploit/<teamToken></ code > .
81- Type your < i > team token</ i > and click on < i > Connect</ i > to start.
154+ You will be able to visualize all the network interactions made to{ " " }
155+ < code > http://<host>/exploit/<teamToken></ code > . Type your < i > team token</ i > and click on{ " " }
156+ < i > Connect</ i > to start.
82157 </ p >
83158 < div className = "flex gap-2 mt-4" >
84159 < form onSubmit = { handleSubmit ( connectToExploitServer ) } className = "flex flex-1 gap-4 space-y-4" >
0 commit comments