Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 6s
All 7 tool windows with full UIs: - Network Scanner: subnet scan, ARP+DNS discovery, results table with Quick Scan per host, SSH/RDP connect buttons, CSV export - Port Scanner: custom range or quick scan (24 common ports), open/closed results table with service names - Ping: remote ping with count, raw output display - Traceroute: remote traceroute, raw output display - Wake on LAN: MAC address input, broadcasts via python3 on remote host - SSH Key Generator: ed25519/RSA, copy public/private key, fingerprint - Password Generator: configurable length/charset, copy button, history Architecture: - App.vue detects tool mode via URL hash (#/tool/name?sessionId=...) - ToolWindow.vue routes to correct tool component - Tools menu in toolbar opens Tauri popup windows (WebviewWindow) - Capabilities grant tool-* windows the same permissions as main - SFTP context menu: right-click Edit/Download/Rename/Delete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
90 lines
4.3 KiB
Vue
90 lines
4.3 KiB
Vue
<template>
|
|
<div class="flex flex-col h-full p-4 gap-3">
|
|
<div class="flex items-center gap-2">
|
|
<label class="text-xs text-[#8b949e]">Subnet (first 3 octets):</label>
|
|
<input v-model="subnet" type="text" placeholder="192.168.1" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] w-40" />
|
|
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="scanning" @click="scan">
|
|
{{ scanning ? "Scanning..." : "Scan Network" }}
|
|
</button>
|
|
<button v-if="hosts.length" class="px-3 py-1.5 text-xs rounded border border-[#30363d] text-[#8b949e] hover:text-white cursor-pointer" @click="exportCsv">Export CSV</button>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-auto border border-[#30363d] rounded">
|
|
<table class="w-full text-xs">
|
|
<thead class="bg-[#161b22] sticky top-0">
|
|
<tr>
|
|
<th class="text-left px-3 py-2 text-[#8b949e] font-medium">IP Address</th>
|
|
<th class="text-left px-3 py-2 text-[#8b949e] font-medium">Hostname</th>
|
|
<th class="text-left px-3 py-2 text-[#8b949e] font-medium">MAC Address</th>
|
|
<th class="text-left px-3 py-2 text-[#8b949e] font-medium">Open Ports</th>
|
|
<th class="text-left px-3 py-2 text-[#8b949e] font-medium">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="host in hosts" :key="host.ip" class="border-t border-[#21262d] hover:bg-[#161b22]">
|
|
<td class="px-3 py-1.5 font-mono">{{ host.ip }}</td>
|
|
<td class="px-3 py-1.5">{{ host.hostname || "—" }}</td>
|
|
<td class="px-3 py-1.5 font-mono text-[#8b949e]">{{ host.mac || "—" }}</td>
|
|
<td class="px-3 py-1.5">
|
|
<span v-if="host.openPorts.length" class="text-[#3fb950]">{{ host.openPorts.join(", ") }}</span>
|
|
<button v-else class="text-[#58a6ff] hover:underline cursor-pointer" @click="quickScanHost(host)">scan</button>
|
|
</td>
|
|
<td class="px-3 py-1.5 flex gap-1">
|
|
<button class="px-2 py-0.5 text-[10px] rounded bg-[#238636] text-white cursor-pointer" @click="connectSsh(host)">SSH</button>
|
|
<button class="px-2 py-0.5 text-[10px] rounded bg-[#1f6feb] text-white cursor-pointer" @click="connectRdp(host)">RDP</button>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="!hosts.length && !scanning">
|
|
<td colspan="5" class="px-3 py-8 text-center text-[#484f58]">Enter a subnet and click Scan</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="text-[10px] text-[#484f58]">{{ hosts.length }} hosts found • Scanning through session {{ sessionId.substring(0, 8) }}...</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from "vue";
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
|
|
const props = defineProps<{ sessionId: string }>();
|
|
|
|
interface Host { ip: string; mac: string | null; hostname: string | null; vendor: string | null; openPorts: number[]; services: string[]; }
|
|
|
|
const subnet = ref("192.168.1");
|
|
const hosts = ref<Host[]>([]);
|
|
const scanning = ref(false);
|
|
|
|
async function scan(): Promise<void> {
|
|
scanning.value = true;
|
|
try {
|
|
hosts.value = await invoke<Host[]>("scan_network", { sessionId: props.sessionId, subnet: subnet.value });
|
|
} catch (err) { alert(err); }
|
|
scanning.value = false;
|
|
}
|
|
|
|
async function quickScanHost(host: Host): Promise<void> {
|
|
try {
|
|
const results = await invoke<{ port: number; open: boolean; service: string }[]>("quick_scan", { sessionId: props.sessionId, target: host.ip });
|
|
host.openPorts = results.filter(r => r.open).map(r => r.port);
|
|
} catch (err) { console.error(err); }
|
|
}
|
|
|
|
function connectSsh(host: Host): void { alert(`TODO: Open SSH tab to ${host.ip}`); }
|
|
function connectRdp(host: Host): void { alert(`TODO: Open RDP tab to ${host.ip}`); }
|
|
|
|
function exportCsv(): void {
|
|
const lines = ["IP,Hostname,MAC,OpenPorts"];
|
|
for (const h of hosts.value) {
|
|
lines.push(`${h.ip},"${h.hostname || ""}","${h.mac || ""}","${h.openPorts.join(";")}"`);
|
|
}
|
|
const blob = new Blob([lines.join("\n")], { type: "text/csv" });
|
|
const a = document.createElement("a");
|
|
a.href = URL.createObjectURL(blob);
|
|
a.download = `wraith-scan-${subnet.value}-${Date.now()}.csv`;
|
|
a.click();
|
|
}
|
|
</script>
|