Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/deploy_website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: GitHub Pages

on:
workflow_dispatch:
release:
# Rebuild so the download page picks up the newly published (pre-)release.
types: [published]
push:
branches:
- main
Expand Down Expand Up @@ -46,6 +49,9 @@ jobs:

- name: Build website
run: yarn build
env:
# Used by scripts/fetch-release-data.mjs to raise the GitHub API rate limit.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
Expand Down
48 changes: 13 additions & 35 deletions Website/docs/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,33 @@ description: "Explore NETworkManager, a free open-source Windows tool with 25+ b
keywords: [NETworkManager, network management, network tools, open source, Windows, IP scanner, port scanner, remote desktop, SSH, PuTTY, PowerShell, network troubleshooting]
---

import ToolChips from "@site/src/components/ToolChips";
import tools from "@site/src/data/tools";

# Introduction

NETworkManager is a powerful open-source tool for managing networks and troubleshooting network problems!

![NETworkManager Preview](https://github.com/BornToBeRoot/NETworkManager/raw/main/Website/static/img/preview-features.gif?raw=true)

## Features

NETworkManager comes with various built-in tools. See the documentation for detailed information about each feature:

- [Dashboard](./application/dashboard)
- [Network Interface](./application/network-interface) - Information, Bandwidth, Configure
- [WiFi](./application/wifi) - Networks, Channels
- [IP Scanner](./application/ip-scanner)
- [Port Scanner](./application/port-scanner)
- [Ping Monitor](./application/ping-monitor)
- [Traceroute](./application/traceroute)
- [DNS Lookup](./application/dns-lookup)
- [Remote Desktop](./application/remote-desktop)
- [PowerShell](./application/powershell)
- [PuTTY](./application/putty) (requires [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html))
- [TigerVNC](./application/tigervnc) (requires [TigerVNC](https://tigervnc.org/))
- [Web Console](./application/web-console)
- [SNMP](./application/snmp) - Get, Walk, Set
- [Hosts File Editor](./application/hosts-file-editor)
- [Firewall](./application/firewall)
- [SNTP Lookup](./application/sntp-lookup)
- [Discovery Protocol](./application/discovery-protocol) - LLDP, CDP
- [Wake on LAN](./application/wake-on-lan)
- [Whois](./application/whois)
- [Subnet Calculator](./application/subnet-calculator) - Calculator, Subnetting, Supernetting
- [Bit Calculator](./application/bit-calculator)
- [Lookup](./application/lookup) - OUI, Port
- [Connections](./application/connections)
- [Listeners](./application/listeners)
- [Neighbor Table](./application/neighbor-table)

## Groups and Profiles
## 🧰 Features

NETworkManager comes with **{tools.length} built-in tools** — everything a network engineer needs, in one place. Select a tool to jump to its documentation:

<ToolChips />

## 🗂️ Groups and Profiles

Organize hosts and networks in profiles with individual settings and use them seamlessly across all features. Keep sensitive data secure with encrypted profile files, and manage different customers or environments using separate profile files. See the [Groups and Profiles](./groups-and-profiles.md) section for more information.

## System-Wide Policies
## 🏢 System-Wide Policies

Administrators can enforce specific settings for all users on a machine using system-wide policies. Policies are defined in a `config.json` file placed in the application installation directory, and override user-specific settings to provide centralized control in enterprise environments. See the [System-Wide Policies](./system-wide-policies.md) section for more information.

## Customization
## 🎨 Customization

NETworkManager comes with dark and light themes and multiple accent colors. You can also provide custom theme files. See the [Appearance settings](./settings/appearance.md) for more information.

## Languages
## 🌐 Languages

Thanks to the community, NETworkManager is available in more than 16 [languages](https://app.transifex.com/BornToBeRoot/NETworkManager/dashboard/). If you want to help translate NETworkManager, you can find everything to get started in the [contributing guidelines](https://github.com/BornToBeRoot/NETworkManager/blob/main/CONTRIBUTING.md).
3 changes: 3 additions & 0 deletions Website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"fetch-release-data": "node scripts/fetch-release-data.mjs",
"prestart": "node scripts/fetch-release-data.mjs",
"prebuild": "node scripts/fetch-release-data.mjs",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
Expand Down
181 changes: 181 additions & 0 deletions Website/scripts/fetch-release-data.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Pre-fetches release data at build time and writes it to src/data/*.json:
// - release.json : current stable release (version, date, downloads, checksums)
// - prerelease.json : latest pre-release (version, date, downloads, checksums)
//
// Both files use the exact same shape and are built the same way — there is
// nothing to bump by hand. The committed JSON files only act as an offline
// fallback (last known values); every real build overwrites them with fresh data.
//
// Why build-time instead of client-side:
// - The GitHub API (api.github.com) supports CORS but is rate limited
// (60 requests/hour per IP for unauthenticated users) — doing it per visitor
// risks hitting that limit.
// - The release asset host (release-assets.githubusercontent.com) is NOT rate
// limited but sends no Access-Control-Allow-Origin header, so a browser
// fetch() of the SHA256SUMS file is blocked by CORS.
// - Node has no CORS restriction and runs once per build, so fetching here is
// reliable, includes the checksums, and adds zero per-visitor requests.
//
// Freshness: the website is rebuilt on every published (pre-)release (see the
// `release` trigger in .github/workflows/deploy_website.yml), so the prefetched
// data stays current without any client-side requests.
import { writeFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";

const __dirname = dirname(fileURLToPath(import.meta.url));
const dataDir = resolve(__dirname, "..", "src", "data");
const changelogDir = resolve(__dirname, "..", "docs", "changelog");
const releasePath = resolve(dataDir, "release.json");
const prereleasePath = resolve(dataDir, "prerelease.json");

const REPO = "BornToBeRoot/NETworkManager";

// Changelog category index — used when a release has no dedicated changelog page
// yet, so the link never points at a non-existent doc (onBrokenLinks: "throw").
const CHANGELOG_FALLBACK = "/docs/category/changelog";

// Maps the data keys to the asset filename suffixes.
const ASSET_SUFFIXES = {
setup: "_Setup.msi",
portable: "_Portable.zip",
archive: "_Archive.zip",
};

function ghHeaders() {
const headers = {
"User-Agent": "NETworkManager-website-build",
Accept: "application/vnd.github+json",
};
// Authenticate when a token is available (CI) to raise the rate limit.
const token = process.env.GITHUB_TOKEN;
if (token) headers.Authorization = `Bearer ${token}`;
return headers;
}

function formatDate(iso) {
const d = new Date(iso);
const dd = String(d.getDate()).padStart(2, "0");
const mm = String(d.getMonth() + 1).padStart(2, "0");
return `${dd}.${mm}.${d.getFullYear()}`;
}

async function writeJson(path, data) {
await writeFile(path, JSON.stringify(data, null, 2) + "\n");
}

// Downloads and parses the "<hash> <filename>" SHA256SUMS asset into a
// { setup, portable, archive } map. Throws if any variant is missing.
async function fetchChecksumMap(version) {
const url = `https://github.com/${REPO}/releases/download/${version}/NETworkManager_${version}_SHA256SUMS`;
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
const text = await res.text();

const map = {};
for (const line of text.split(/\r?\n/)) {
const m = line.trim().match(/^([0-9a-fA-F]{64})\s+(.+)$/);
if (!m) continue;
for (const [key, suffix] of Object.entries(ASSET_SUFFIXES)) {
if (m[2] === `NETworkManager_${version}${suffix}`) {
map[key] = m[1].toUpperCase();
}
}
}
for (const key of Object.keys(ASSET_SUFFIXES)) {
if (!map[key]) throw new Error(`Missing ${key} checksum for ${version}`);
}
return map;
}

// Resolves the changelog link for a stable version: its dedicated page if the
// doc exists ("2026.2.22.0" -> /docs/changelog/2026-2-22-0), else the fallback.
function stableChangelogUrl(version) {
const slug = version.replace(/\./g, "-");
return existsSync(resolve(changelogDir, `${slug}.md`))
? `/docs/changelog/${slug}`
: CHANGELOG_FALLBACK;
}

// Builds the unified data object (download URLs + checksums + changelog link)
// from a GitHub release object. Used identically for both stable and pre-release.
async function buildData(release, changelog) {
const version = release.tag_name;

const downloads = {};
for (const [key, suffix] of Object.entries(ASSET_SUFFIXES)) {
const asset = release.assets.find((a) => a.name.endsWith(suffix));
if (asset) downloads[key] = asset.browser_download_url;
}

// Checksums are best-effort — older releases may not ship a SHA256SUMS file.
let checksums = null;
try {
checksums = await fetchChecksumMap(version);
} catch (err) {
console.warn(`[release-data] Checksums unavailable for ${version}: ${err.message}`);
}
Comment thread
Copilot marked this conversation as resolved.

return {
available: true,
version,
releaseDate: formatDate(release.published_at),
changelog,
downloads,
checksums,
};
}

async function updateStable(releases) {
// Newest published, non-draft, non-pre-release.
const stable = releases.find((r) => !r.draft && !r.prerelease);
if (!stable) throw new Error("No stable release found");
await writeJson(
releasePath,
await buildData(stable, stableChangelogUrl(stable.tag_name)),
);
console.log(`[release-data] Stable updated to ${stable.tag_name}`);
}

async function updatePrerelease(releases) {
// The newest non-draft release; only show it if it is actually a pre-release
// (i.e. newer than the latest stable). Otherwise the section is hidden.
const latest = releases.find((r) => !r.draft);
if (!latest || !latest.prerelease) {
await writeJson(prereleasePath, { available: false });
console.log("[release-data] No pre-release available");
return;
}
// Pre-release changes are documented on the rolling "next release" page.
await writeJson(
prereleasePath,
await buildData(latest, "/docs/changelog/next-release"),
);
console.log(`[release-data] Pre-release updated to ${latest.tag_name}`);
}

async function main() {
const apiUrl = `https://api.github.com/repos/${REPO}/releases?per_page=30`;
const res = await fetch(apiUrl, { headers: ghHeaders() });
if (!res.ok) throw new Error(`HTTP ${res.status} for ${apiUrl}`);
const releases = await res.json();

// Each step is independent so a single failure keeps that file's committed
// fallback instead of failing the whole build.
for (const [label, fn] of [
["stable", updateStable],
["pre-release", updatePrerelease],
]) {
try {
await fn(releases);
} catch (err) {
console.warn(`[release-data] ${label} skipped: ${err.message}`);
}
Comment thread
Copilot marked this conversation as resolved.
Outdated
}
}

main().catch((err) => {
// Non-fatal: keep all committed fallbacks so offline / CI builds still succeed.
console.warn(`[release-data] Skipped update: ${err.message}`);
});
Comment thread
Copilot marked this conversation as resolved.
36 changes: 36 additions & 0 deletions Website/src/components/CopyButton/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState } from "react";
import clsx from "clsx";
import styles from "./styles.module.css";

// Copies a value to the clipboard with brief visual feedback. Used for the
// SHA-256 checksums on the download cards — the hash itself is never rendered,
// which keeps the cards compact and mobile-safe.
export default function CopyButton({
value,
idleLabel = "📋 Copy SHA-256",
copiedLabel = "✅ Copied",
className,
}) {
const [copied, setCopied] = useState(false);

async function handleCopy() {
try {
await navigator.clipboard.writeText(value);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
} catch {
// Clipboard API unavailable (e.g. insecure context) — silently ignore.
}
}
Comment thread
Copilot marked this conversation as resolved.
Outdated

return (
<button
type="button"
className={clsx(styles.copyButton, className)}
onClick={handleCopy}
aria-label="Copy SHA-256 checksum to clipboard"
>
Comment thread
Copilot marked this conversation as resolved.
{copied ? copiedLabel : idleLabel}
</button>
);
}
16 changes: 16 additions & 0 deletions Website/src/components/CopyButton/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.copyButton {
margin-top: 12px;
padding: 4px 10px;
background: transparent;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 13px;
color: var(--ifm-color-emphasis-600);
transition: color 0.15s, background 0.15s;
}

.copyButton:hover {
color: var(--ifm-color-primary);
background: var(--ifm-color-emphasis-200);
}
42 changes: 42 additions & 0 deletions Website/src/components/DownloadCard/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Link from "@docusaurus/Link";
import clsx from "clsx";
import CopyButton from "@site/src/components/CopyButton";
import styles from "./styles.module.css";

// Responsive grid wrapper for a row of <DownloadCard> elements.
export function DownloadGrid({ children }) {
return <div className={styles.grid}>{children}</div>;
}

// A single download option. `description` and `recommended` are optional, so the
// same card renders both the detailed stable variant and the compact
// pre-release variant. When a `checksum` is given, a copy button is shown.
export default function DownloadCard({
icon,
label,
sub,
color = "primary",
description,
recommended = false,
downloadUrl,
checksum,
}) {
return (
<div className={styles.card}>
{recommended && <span className={styles.badge}>★ Recommended</span>}
<div className={styles.icon} aria-hidden="true">
{icon}
</div>
<div className={styles.title}>{label}</div>
<div className={styles.sub}>{sub}</div>
{description && <p className={styles.description}>{description}</p>}
<Link
className={clsx("button", `button--${color}`, styles.button)}
to={downloadUrl}
>
⬇ Download
</Link>
{checksum && <CopyButton value={checksum} />}
</div>
);
}
Loading
Loading