Initial commit: DroidFleet Visor - Fleet management dashboard
This commit is contained in:
224
src/components/AdbConsole.tsx
Normal file
224
src/components/AdbConsole.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { AndroidVM } from '../types';
|
||||
import {
|
||||
Terminal,
|
||||
Send,
|
||||
Copy,
|
||||
Check,
|
||||
Trash2,
|
||||
Play,
|
||||
ShieldAlert,
|
||||
Cpu,
|
||||
Layers,
|
||||
Search,
|
||||
} from 'lucide-react';
|
||||
|
||||
interface AdbConsoleProps {
|
||||
vm: AndroidVM;
|
||||
vms: AndroidVM[];
|
||||
onSelectVm: (vmId: string) => void;
|
||||
onExecuteAdb: (command: string) => Promise<string>;
|
||||
}
|
||||
|
||||
export const AdbConsole: React.FC<AdbConsoleProps> = ({
|
||||
vm,
|
||||
vms,
|
||||
onSelectVm,
|
||||
onExecuteAdb,
|
||||
}) => {
|
||||
const [commandInput, setCommandInput] = useState('');
|
||||
const [terminalHistory, setTerminalHistory] = useState<
|
||||
{ command: string; output: string; timestamp: string }[]
|
||||
>([
|
||||
{
|
||||
command: 'adb connect 192.168.122.105:5555',
|
||||
output: `* daemon not running; starting now at tcp:5037\n* daemon started successfully\nconnected to ${vm.ipAddress}:${vm.adbPort}`,
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
},
|
||||
]);
|
||||
const [isExecuting, setIsExecuting] = useState(false);
|
||||
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
|
||||
const terminalEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
terminalEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [terminalHistory]);
|
||||
|
||||
const handleRunCommand = async (cmdToRun?: string) => {
|
||||
const cmd = (cmdToRun || commandInput).trim();
|
||||
if (!cmd) return;
|
||||
|
||||
setIsExecuting(true);
|
||||
const result = await onExecuteAdb(cmd);
|
||||
|
||||
setTerminalHistory((prev) => [
|
||||
...prev,
|
||||
{
|
||||
command: cmd,
|
||||
output: result,
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
},
|
||||
]);
|
||||
|
||||
setCommandInput('');
|
||||
setIsExecuting(false);
|
||||
};
|
||||
|
||||
const presetCommands = [
|
||||
{ label: 'System Build Props', cmd: 'getprop' },
|
||||
{ label: 'List Packages', cmd: 'pm list packages' },
|
||||
{ label: 'Check Root Privilege', cmd: 'su' },
|
||||
{ label: 'CPU Info', cmd: 'cat /proc/cpuinfo' },
|
||||
{ label: 'Battery Dumpsys', cmd: 'dumpsys battery' },
|
||||
{ label: 'Live Logcat', cmd: 'logcat -d' },
|
||||
];
|
||||
|
||||
const handleCopy = (text: string, index: number) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopiedIndex(index);
|
||||
setTimeout(() => setCopiedIndex(null), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Target VM Selector & Network Status Bar */}
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 bg-slate-900/80 p-4 rounded-2xl border border-slate-800">
|
||||
<div className="flex items-center space-x-3 w-full sm:w-auto">
|
||||
<Terminal className="w-5 h-5 text-emerald-400" />
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-xs font-bold text-slate-300">Target Instance:</span>
|
||||
<select
|
||||
value={vm.id}
|
||||
onChange={(e) => onSelectVm(e.target.value)}
|
||||
className="bg-slate-950 border border-slate-800 rounded-xl px-3 py-1.5 text-xs text-emerald-400 font-bold focus:outline-none focus:border-emerald-500"
|
||||
>
|
||||
{vms.map((v) => (
|
||||
<option key={v.id} value={v.id}>
|
||||
{v.name} ({v.ipAddress}:{v.adbPort})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3 text-xs font-mono">
|
||||
<span className="px-2.5 py-1 bg-emerald-950/60 border border-emerald-800 text-emerald-300 rounded-lg">
|
||||
ADB TCP: {vm.ipAddress}:{vm.adbPort}
|
||||
</span>
|
||||
<span className="px-2.5 py-1 bg-slate-950 border border-slate-800 text-slate-400 rounded-lg uppercase">
|
||||
SELinux: {vm.bootloader.selinuxMode}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preset Quick Commands */}
|
||||
<div className="space-y-2">
|
||||
<span className="text-xs font-bold text-slate-400 uppercase tracking-wider block">
|
||||
Quick ADB Shell Presets:
|
||||
</span>
|
||||
<div className="flex items-center space-x-2 overflow-x-auto pb-1">
|
||||
{presetCommands.map((preset) => (
|
||||
<button
|
||||
key={preset.cmd}
|
||||
onClick={() => handleRunCommand(preset.cmd)}
|
||||
disabled={isExecuting}
|
||||
className="px-3 py-1.5 bg-slate-900 hover:bg-slate-800 text-slate-300 border border-slate-800 hover:border-emerald-500/50 rounded-xl text-xs font-mono whitespace-nowrap transition-all flex items-center space-x-1.5 shrink-0"
|
||||
>
|
||||
<Play className="w-3 h-3 text-emerald-400" />
|
||||
<span>{preset.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Interactive ADB Shell Terminal View */}
|
||||
<div className="bg-slate-950 border border-slate-800 rounded-2xl overflow-hidden flex flex-col h-[520px] shadow-2xl">
|
||||
{/* Terminal Header */}
|
||||
<div className="bg-slate-900 px-4 py-3 border-b border-slate-800 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-rose-500/80" />
|
||||
<div className="w-3 h-3 rounded-full bg-amber-500/80" />
|
||||
<div className="w-3 h-3 rounded-full bg-emerald-500/80" />
|
||||
<span className="text-xs font-mono text-slate-400 ml-2">
|
||||
adb shell shell@{vm.profile.id}:/$
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setTerminalHistory([])}
|
||||
className="p-1.5 text-slate-400 hover:text-rose-400 hover:bg-slate-800 rounded-lg text-xs flex items-center space-x-1 transition-colors"
|
||||
title="Clear Terminal Output"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
<span className="hidden sm:inline">Clear</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Console Log Area */}
|
||||
<div className="flex-1 p-4 font-mono text-xs overflow-y-auto space-y-4">
|
||||
{terminalHistory.map((item, idx) => (
|
||||
<div key={idx} className="space-y-1.5 border-b border-slate-900/80 pb-3">
|
||||
<div className="flex items-center justify-between text-slate-400">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-emerald-400">$</span>
|
||||
<span className="font-bold text-slate-200">{item.command}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-[10px] text-slate-600">{item.timestamp}</span>
|
||||
<button
|
||||
onClick={() => handleCopy(item.output, idx)}
|
||||
className="p-1 text-slate-500 hover:text-slate-200"
|
||||
>
|
||||
{copiedIndex === idx ? (
|
||||
<Check className="w-3 h-3 text-emerald-400" />
|
||||
) : (
|
||||
<Copy className="w-3 h-3" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pre className="text-slate-300 bg-slate-900/60 p-3 rounded-xl border border-slate-800/80 whitespace-pre-wrap font-mono leading-relaxed overflow-x-auto text-[11px]">
|
||||
{item.output}
|
||||
</pre>
|
||||
</div>
|
||||
))}
|
||||
<div ref={terminalEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Terminal Command Input */}
|
||||
<div className="p-3 bg-slate-900 border-t border-slate-800">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleRunCommand();
|
||||
}}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<span className="text-emerald-400 font-mono font-bold text-xs">$</span>
|
||||
<input
|
||||
type="text"
|
||||
value={commandInput}
|
||||
onChange={(e) => setCommandInput(e.target.value)}
|
||||
placeholder="Type adb shell command (e.g. getprop, logcat, su)..."
|
||||
disabled={isExecuting}
|
||||
className="flex-1 bg-slate-950 border border-slate-800 rounded-xl px-3.5 py-2 text-xs font-mono text-slate-100 placeholder-slate-600 focus:outline-none focus:border-emerald-500"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isExecuting || !commandInput.trim()}
|
||||
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 disabled:bg-slate-800 text-white font-bold rounded-xl text-xs flex items-center space-x-1.5 transition-all"
|
||||
>
|
||||
<Send className="w-3.5 h-3.5" />
|
||||
<span>Run</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user