Add cracker dropin interface

This commit is contained in:
Jack Kingsman
2026-01-07 17:35:22 -08:00
parent e33275a2fc
commit 89c5fb2ce4
8 changed files with 612 additions and 568 deletions

File diff suppressed because one or more lines are too long

542
frontend/dist/assets/index-BKnk_LMx.js vendored Normal file

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

@@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RemoteTerm for MeshCore</title>
<script type="module" crossorigin src="/assets/index-B-xot-vl.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DlaqriQ9.css">
<script type="module" crossorigin src="/assets/index-BKnk_LMx.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BdHgsPJQ.css">
</head>
<body>
<div id="root"></div>

View File

@@ -20,6 +20,7 @@ import {
type ConversationTimes,
} from './utils/conversationState';
import { pubkeysMatch, getContactDisplayName } from './utils/pubkey';
import { cn } from '@/lib/utils';
import type {
AppSettings,
AppSettingsUpdate,
@@ -59,6 +60,8 @@ export function App() {
const [showConfig, setShowConfig] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [undecryptedCount, setUndecryptedCount] = useState(0);
const [showCracker, setShowCracker] = useState(false);
const [crackerRunning, setCrackerRunning] = useState(false);
// Track last message times (persisted in localStorage, used for sorting)
const [lastMessageTimes, setLastMessageTimes] = useState<ConversationTimes>(getLastMessageTimes);
// Track unread counts (calculated on load and incremented during session)
@@ -634,6 +637,9 @@ export function App() {
}}
lastMessageTimes={lastMessageTimes}
unreadCounts={unreadCounts}
showCracker={showCracker}
crackerRunning={crackerRunning}
onToggleCracker={() => setShowCracker((prev) => !prev)}
/>
);
@@ -670,28 +676,8 @@ export function App() {
activeConversation.type === 'raw' ? (
<>
<div className="flex justify-between items-center px-4 py-3 border-b border-border font-medium">Raw Packet Feed</div>
<div className="flex-1 flex flex-col overflow-hidden">
<div className="flex-1 overflow-hidden min-h-0">
<RawPacketList packets={rawPackets} />
</div>
<div className="h-[280px] flex-shrink-0 border-t border-border overflow-hidden">
<CrackerPanel
packets={rawPackets}
channels={channels}
onChannelCreate={async (name, key) => {
// Create channel without navigating to it
const created = await api.createChannel(name, key);
const data = await api.getChannels();
setChannels(data);
// Try to decrypt historical packets with this key
await api.decryptHistoricalPackets({
key_type: 'channel',
channel_key: created.key,
});
fetchUndecryptedCount();
}}
/>
</div>
<div className="flex-1 overflow-hidden">
<RawPacketList packets={rawPackets} />
</div>
</>
) : (
@@ -750,6 +736,30 @@ export function App() {
</div>
</div>
{/* Global Cracker Panel - always rendered to maintain state */}
<div
className={cn(
"border-t border-border bg-background transition-all duration-200 overflow-hidden",
showCracker ? "h-[275px]" : "h-0"
)}
>
<CrackerPanel
packets={rawPackets}
channels={channels}
onChannelCreate={async (name, key) => {
const created = await api.createChannel(name, key);
const data = await api.getChannels();
setChannels(data);
await api.decryptHistoricalPackets({
key_type: 'channel',
channel_key: created.key,
});
fetchUndecryptedCount();
}}
onRunningChange={setCrackerRunning}
/>
</div>
<NewMessageModal
open={showNewMessage}
contacts={contacts}

View File

@@ -23,9 +23,10 @@ interface CrackerPanelProps {
packets: RawPacket[];
channels: Channel[];
onChannelCreate: (name: string, key: string) => Promise<void>;
onRunningChange?: (running: boolean) => void;
}
export function CrackerPanel({ packets, channels, onChannelCreate }: CrackerPanelProps) {
export function CrackerPanel({ packets, channels, onChannelCreate, onRunningChange }: CrackerPanelProps) {
const [isRunning, setIsRunning] = useState(false);
const [maxLength, setMaxLength] = useState(6);
const [retryFailedAtNextLength, setRetryFailedAtNextLength] = useState(false);
@@ -113,6 +114,11 @@ export function CrackerPanel({ packets, channels, onChannelCreate }: CrackerPane
maxLengthRef.current = maxLength;
}, [maxLength]);
// Notify parent of running state changes
useEffect(() => {
onRunningChange?.(isRunning);
}, [isRunning, onRunningChange]);
// Stats (cracking count is implicit - if progress is shown, we're cracking one)
const pendingCount = Array.from(queue.values()).filter(q => q.status === 'pending').length;
const crackedCount = Array.from(queue.values()).filter(q => q.status === 'cracked').length;

View File

@@ -18,6 +18,9 @@ interface SidebarProps {
onNewMessage: () => void;
lastMessageTimes: ConversationTimes;
unreadCounts: Record<string, number>;
showCracker: boolean;
crackerRunning: boolean;
onToggleCracker: () => void;
}
// Load sort preference from localStorage
@@ -47,6 +50,9 @@ export function Sidebar({
onNewMessage,
lastMessageTimes,
unreadCounts,
showCracker,
crackerRunning,
onToggleCracker,
}: SidebarProps) {
const [sortOrder, setSortOrder] = useState<SortOrder>(loadSortOrder);
const [searchQuery, setSearchQuery] = useState('');
@@ -219,6 +225,28 @@ export function Sidebar({
</div>
)}
{/* Cracker Toggle */}
{!query && (
<div
className={cn(
"px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent",
showCracker && "bg-accent border-l-primary"
)}
onClick={onToggleCracker}
>
<span className="text-muted-foreground text-xs">🔓</span>
<span className="flex-1 truncate">
{showCracker ? 'Hide' : 'Show'} Cracker
<span className={cn(
"ml-1 text-xs",
crackerRunning ? "text-green-500" : "text-muted-foreground"
)}>
({crackerRunning ? 'running' : 'stopped'})
</span>
</span>
</div>
)}
{/* Channels */}
{filteredChannels.length > 0 && (
<>