From 2633ddfdd38bd7141fa34f2d474aa888a6b7e492 Mon Sep 17 00:00:00 2001 From: Felix Stubner Date: Thu, 24 Sep 2026 20:54:43 +0100 Subject: [PATCH 01/29] Add a comparison with nmap, Angry IP Scanner and Advanced IP Scanner to the landing page Compare.astro comes from product-site-template, where nvx's comparison section was generalised, so the next template sync sees the same file on both sides. One change: the note under the table drops the template's max-width:62ch, which wrapped a two-sentence note into a narrow column under a full-width table -- the same fix nvx made. It can go back to the template on the next sync. The rows are ordered by the question a reader is asking (where it runs, how you use it, what it finds, what you get out) and every cell was checked on 2026-09-24 against each project's own docs and source; the sources and the reasoning behind each non-obvious cell are in compare.ts. Two rows are ones NetsCLI loses: UDP/OS/version detection, and the note says what Advanced IP Scanner is for. Remote actions went into the note rather than a row, since they are remote management, not scanning. This replaces the 25-row docs page proposed in #470. --- site/src/components/Compare.astro | 129 +++++++++++++++++++++++++ site/src/data/site-content/compare.ts | 69 +++++++++++++ site/src/data/site-content/sections.ts | 4 +- site/src/data/site-content/types.ts | 14 ++- site/src/pages/index.astro | 3 +- 5 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 site/src/components/Compare.astro create mode 100644 site/src/data/site-content/compare.ts diff --git a/site/src/components/Compare.astro b/site/src/components/Compare.astro new file mode 100644 index 00000000..d103441f --- /dev/null +++ b/site/src/components/Compare.astro @@ -0,0 +1,129 @@ +--- +import { + compareColumns, + compareCopy, + compareNoteHtml, + compareRows, +} from '../data/site-content/compare'; + +const highlightIndex = compareColumns.findIndex((c) => c.highlight); +--- + +
+
+

{compareCopy.heading}

+

+ + +

+ + + + + {compareColumns.map((c, i) => ( + + ))} + + + + {compareRows.map((row) => ( + + + {row.cells.map((cell, i) => ( + + ))} + + ))} + +
Feature{c.name}
{row.feature} + {cell} +
+
+ +

+

+
+ + diff --git a/site/src/data/site-content/compare.ts b/site/src/data/site-content/compare.ts new file mode 100644 index 00000000..1fbee988 --- /dev/null +++ b/site/src/data/site-content/compare.ts @@ -0,0 +1,69 @@ +import type { ComparisonColumn, ComparisonRow, SectionCopy } from './types'; + +// The comparison matrix. Every cell describes what ships, out of the box. +// +// Checked on 2026-09-24 against each project's own website, documentation and +// source: nmap 7.991 (nmap.org changelog, reference guide, NSE index), Angry IP +// Scanner 3.10.0 (angryip.org, the GitHub release and the source), Advanced IP +// Scanner 2.5.4594.1 (advanced-ip-scanner.com, its help pages, and Famatech's +// replies on radmin-club.com). NetsCLI's column was checked against this +// repository's code, not against the site copy. +// +// A dash means the tool's own documentation and source show no such feature. +// Where a feature exists but is undocumented -- Advanced IP Scanner's console +// version, which Famatech confirms but never describes -- the cell says so +// rather than guessing either way. +// +// Rows are ordered by the question a reader is asking -- where it runs, how +// you use it, what it finds, what you get out -- rather than grouped under +// headings, so the template's component is used unchanged. +// +// Two rows are there because NetsCLI loses them. UDP, OS and version detection +// is the reason people pick nmap, and leaving it out would make the table an +// advert. Remote actions (RDP, Radmin, shutdown, Wake-on-LAN) were a row on +// 2026-09-24 and went into the note instead: they are remote management, not +// scanning, the one row comparing a different kind of thing. +export const compareCopy: SectionCopy = { + heading: 'How NetsCLI compares', + leadHtml: + 'Against the scanners people already use. Checked against each project’s own documentation and source on 24 September 2026: nmap 7.991, Angry IP Scanner 3.10.0 and Advanced IP Scanner 2.5.4594.1.', +}; + +export const compareColumns: ComparisonColumn[] = [ + { name: 'NetsCLI', highlight: true }, + { name: 'nmap' }, + { name: 'Angry IP Scanner' }, + { name: 'Advanced IP Scanner' }, +]; + +export const compareRows: ComparisonRow[] = [ + // nmap's BSD support is source builds; its installers cover the other three. + { feature: 'Platforms', cells: ['Windows, macOS, Linux', 'Windows, macOS, Linux, BSD', 'Windows, macOS, Linux', 'Windows'] }, + // Advanced IP Scanner is free, but no source code or licence text is + // published anywhere official -- "free, no source" is what can be shown. + { feature: 'Licence', cells: ['MIT', 'NPSL, source available', 'GPLv2', 'Free, no source'] }, + { feature: 'Command line, for scripts', cells: ['✓', '✓', '✓', 'Console version, undocumented'] }, + { feature: 'Terminal UI', cells: ['✓', '—', '—', '—'] }, + { feature: 'Desktop app', cells: ['✓', 'Zenmap', '✓', '✓'] }, + { feature: 'AI agents (MCP server)', cells: ['✓', '—', '—', '—'] }, + // NetsCLI and Angry IP Scanner both do TCP connect scans. Advanced IP + // Scanner's docs cover checks for HTTP, HTTPS, FTP, RDP, Radmin and shared + // folders; Famatech sells port scanning as Advanced Port Scanner. + { feature: 'TCP ports', cells: ['✓', '✓', '✓', 'Set services only'] }, + { feature: 'UDP, OS and version detection', cells: ['—', '✓', '—', '—'] }, + // nmap resolves targets and does reverse DNS built in; record types beyond + // that come from specific NSE scripts (dns-srv-enum and others), not a + // general lookup. + { feature: 'DNS records (MX, TXT, SRV…)', cells: ['✓', 'Via scripts', '—', '—'] }, + // nmap: broadcast-dns-service-discovery. Angry IP Scanner queries mDNS only + // to name a local host when reverse DNS has no answer. + { feature: 'mDNS devices', cells: ['✓', 'Via script', 'Hostnames only', '—'] }, + // NetsCLI's CSV comes from the desktop app; the CLI and TUI give JSON (and + // YAML, Markdown). nmap's "text" is normal and grepable output; it has no + // JSON. Angry IP Scanner also writes an IP:port list. + { feature: 'Export formats', cells: ['JSON, YAML, CSV', 'XML, text', 'CSV, XML, SQL, text', 'CSV, XML, HTML'] }, +]; + +/** Shown under the table: where the other two are the better choice. */ +export const compareNoteHtml = + 'nmap is the deeper tool for audits and security work, with SYN and UDP scans, service and OS detection, and over 600 scripts. Advanced IP Scanner is built around acting on the Windows machines it finds: RDP and Radmin sessions, remote shutdown and Wake-on-LAN. NetsCLI does neither.'; diff --git a/site/src/data/site-content/sections.ts b/site/src/data/site-content/sections.ts index 9af98ba5..288bbf6f 100644 --- a/site/src/data/site-content/sections.ts +++ b/site/src/data/site-content/sections.ts @@ -2,7 +2,7 @@ import type { LandingLayout, LandingSection } from './types'; // Which sections the landing page renders, in order. // -// The four are independent: each reads its own content file and none assumes +// The sections are independent: each reads its own content file and none assumes // what sits above it. So a product with no install story drops 'install' // rather than filling it with something weak, and one whose visitors ask // questions before they read features can put 'faq' second. @@ -10,7 +10,7 @@ import type { LandingLayout, LandingSection } from './types'; // A name that appears twice renders twice; a name whose content is empty // renders nothing. Dropping the last section is fine -- the footer is not in // this list, and neither is the nav. -export const sections: LandingSection[] = ['hero', 'surfaces', 'install', 'faq']; +export const sections: LandingSection[] = ['hero', 'surfaces', 'compare', 'install', 'faq']; // How the landing page is laid out above the fold, and the rhythm that // follows from it. diff --git a/site/src/data/site-content/types.ts b/site/src/data/site-content/types.ts index c4069f7b..9c528da5 100644 --- a/site/src/data/site-content/types.ts +++ b/site/src/data/site-content/types.ts @@ -212,6 +212,18 @@ export interface Analytics { cloudflareToken?: string; } +/** A comparison column. `highlight` marks this product's, which is tinted. */ +export interface ComparisonColumn { + name: string; + highlight?: boolean; +} + +/** A capability row. `cells` is one per column, in order: '✓', '—', or a word. */ +export interface ComparisonRow { + feature: string; + cells: string[]; +} + export interface SectionCopy { heading: string; /** HTML allowed — typically short tagline with an anchor link. */ @@ -223,7 +235,7 @@ export type LandingLayout = 'centered' | 'split'; /** A section the landing page can render. Adding one here means adding a * component for it in src/pages/index.astro's map. */ -export type LandingSection = 'hero' | 'surfaces' | 'install' | 'faq'; +export type LandingSection = 'hero' | 'surfaces' | 'compare' | 'install' | 'faq'; /** One group in the docs sidebar, as Starlight expects it. */ export interface DocsSection { diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index a491615e..9c74129f 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -3,6 +3,7 @@ import Page from '../layouts/Page.astro'; import Nav from '../components/Nav.astro'; import Hero from '../components/Hero.astro'; import Surfaces from '../components/Surfaces.astro'; +import Compare from '../components/Compare.astro'; import Install from '../components/Install.astro'; import Faq from '../components/Faq.astro'; import Footer from '../components/Footer.astro'; @@ -14,7 +15,7 @@ const githubRepo = site.social.repo; // One component per section name. The list in site-content/sections.ts is // the order; this is only the lookup, and a name with no entry here is a // type error rather than a silently missing section. -const COMPONENTS = { hero: Hero, surfaces: Surfaces, install: Install, faq: Faq }; +const COMPONENTS = { hero: Hero, surfaces: Surfaces, compare: Compare, install: Install, faq: Faq }; ---