diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..34ecda2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: [ master, main ] + pull_request: + branches: [ master, main ] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Restore and build + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore + run: dotnet restore .\ProcessBusSuite.sln + + - name: Build + run: dotnet build .\ProcessBusSuite.sln -c Release --no-restore /p:ContinuousIntegrationBuild=true + + - name: Run tests when present + shell: pwsh + run: | + $testProjects = Get-ChildItem -Path . -Recurse -Filter *.csproj | + Where-Object { $_.FullName -match '(Test|Tests)\\|\.(Test|Tests)\.csproj$' } + + if (-not $testProjects) { + Write-Host "No test project found. Build validation completed." + exit 0 + } + + foreach ($project in $testProjects) { + dotnet test $project.FullName -c Release --no-build --logger "trx" + } diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..d27df4a --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,42 @@ +name: Deploy GitHub Pages + +on: + push: + branches: [ master, main ] + paths: + - 'docs/**' + - '.github/workflows/pages.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + name: Deploy static docs site + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure Pages + uses: actions/configure-pages@v5 + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/release-package.yml b/.github/workflows/release-package.yml new file mode 100644 index 0000000..c6bbf8f --- /dev/null +++ b/.github/workflows/release-package.yml @@ -0,0 +1,121 @@ +name: Release Windows portable package + +on: + workflow_dispatch: + inputs: + version: + description: 'Version label, for example 1.2.0-public-beta' + required: true + default: '1.2.0-public-beta' + publish_release: + description: 'Create or update a GitHub Release' + required: true + default: false + type: boolean + prerelease: + description: 'Mark GitHub Release as prerelease' + required: true + default: true + type: boolean + draft: + description: 'Create GitHub Release as draft' + required: true + default: false + type: boolean + release_notes_file: + description: 'Markdown file used as release body' + required: true + default: 'docs/RELEASE_NOTES_v1.2.0.md' + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + package: + name: Build portable Windows package + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Resolve release version + id: version + shell: pwsh + run: | + $version = '${{ github.event.inputs.version }}' + if ([string]::IsNullOrWhiteSpace($version)) { + $version = '${{ github.ref_name }}' + } + $version = $version.Trim() + if ($version.StartsWith('v')) { $version = $version.Substring(1) } + if ([string]::IsNullOrWhiteSpace($version)) { throw 'Unable to resolve release version.' } + "value=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + Write-Host "Release version: $version" + + - name: Restore + run: dotnet restore .\ProcessBusSuite.sln + + - name: Build + run: dotnet build .\ProcessBusSuite.sln -c Release --no-restore /p:ContinuousIntegrationBuild=true + + - name: Publish portable package + shell: pwsh + run: | + .\scripts\publish-windows-portable.ps1 -Version '${{ steps.version.outputs.value }}' -Configuration Release -Runtime win-x64 + + - name: Verify portable package + shell: pwsh + run: | + $zip = ".\artifacts\release\ProcessBusInsight-v${{ steps.version.outputs.value }}-win-x64-portable.zip" + .\scripts\verify-release-package.ps1 -PackageZip $zip + + - name: Upload workflow artifact + uses: actions/upload-artifact@v4 + with: + name: ProcessBusInsight-v${{ steps.version.outputs.value }}-win-x64-portable + path: | + artifacts/release/ProcessBusInsight-v${{ steps.version.outputs.value }}-win-x64-portable.zip + artifacts/release/SHA256SUMS.txt + if-no-files-found: error + + - name: Publish GitHub Release + if: ${{ github.event_name == 'push' || github.event.inputs.publish_release == 'true' }} + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $version = '${{ steps.version.outputs.value }}' + $tag = "v$version" + $zip = "artifacts/release/ProcessBusInsight-v$version-win-x64-portable.zip" + $sha = "artifacts/release/SHA256SUMS.txt" + $notesFile = '${{ github.event.inputs.release_notes_file }}' + if ([string]::IsNullOrWhiteSpace($notesFile)) { $notesFile = 'docs/RELEASE_NOTES_v1.2.0.md' } + if (-not (Test-Path $notesFile)) { $notesFile = 'docs/RELEASE_NOTES_v1.2.0.md' } + if (-not (Test-Path $notesFile)) { throw "Release notes file not found: $notesFile" } + + $releaseArgs = @('release','create',$tag,$zip,$sha,'--title',"Process Bus Insight $tag",'--notes-file',$notesFile,'--target','${{ github.sha }}') + + $isDraft = '${{ github.event.inputs.draft }}' + $isPrerelease = '${{ github.event.inputs.prerelease }}' + if ('${{ github.event_name }}' -eq 'push') { $isPrerelease = 'false' } + if ($isDraft -eq 'true') { $releaseArgs += '--draft' } + if ($isPrerelease -eq 'true') { $releaseArgs += '--prerelease' } + + gh release view $tag *> $null + if ($LASTEXITCODE -eq 0) { + gh release upload $tag $zip $sha --clobber + } + else { + gh @releaseArgs + } diff --git a/.gitignore b/.gitignore index 5f818ba..96575e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ ################################################################################ -# Source-only repository hygiene +# Source repository hygiene ################################################################################ # IDE / editor state @@ -19,17 +19,23 @@ bin/ obj/ **/bin/ **/obj/ +out/ artifacts/ installer/ publish/ +release/ -# .NET local state +# .NET local state and packages .dotnet/ .dotnet-cli/ .dotnet-home/ .dotnet-home-build/ *.nupkg *.snupkg +TestResults/ +coverage/ +coverage.* +*.trx # Native / Visual C++ generated files *.ipch @@ -53,16 +59,36 @@ UpgradeLog*.htm *.cache *.log +# Local configuration / secrets +.env +.env.* +*.pfx +*.snk +secrets.json +appsettings.Development.json + # Local capture/runtime state *.last_interface.txt *.pcap *.pcapng *.etl +captures/ +logs/ +temp/ +tmp/ -# Local generated media / reports +# Local generated reports/media *.pdf *.gif docs/screenshot/*.png -# Codex/browser local state +# Node/web outputs if a future site toolchain is added +node_modules/ +dist/ +build/ +.parcel-cache/ +.vite/ + +# Local automation/browser state .codex/ +.playwright/ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/AGENTS.md b/AGENTS.md index 656db0a..a3e493a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -249,3 +249,19 @@ Required behavior: - Missing expected streams and unexpected live streams are both important commissioning evidence. - Binding matrix selection should drive the semantic inspector so the engineer can move from finding to evidence quickly. - Never silently auto-correct semantic mapping. Show evidence and confidence. + +## Public Repository Packaging Rule + +Public-facing repo work must keep README, landing page, release notes, and package quick-start material written for users who want to understand, download, run, build, and contribute. + +Do not write public docs as internal audit notes. Avoid wording such as "owner should", "next step for maintainer", or "audit found" in README, landing page, package notes, or release body. + +For Windows desktop releases, keep the portable package flow working: + +```text +scripts/publish-windows-portable.ps1 +scripts/verify-release-package.ps1 +.github/workflows/release-package.yml +``` + +The package should contain the published app, quick-start note, license, notice files, third-party notices, and a convenience launcher only when it helps desktop users run the app. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..71a7780 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing to Process Bus Insight + +Thank you for helping improve Process Bus Insight. + +This project is intended to stay receive-only, raw-passive, evidence-focused, and honest about timing confidence. + +## Good contribution areas + +- SV, GOOSE, and PTP decoder coverage. +- SCL examples and parser improvements. +- Validation scenarios for FAT/SAT workflows. +- UI clarity and evidence wording. +- Documentation, troubleshooting notes, and field checklists. +- Automated tests for protocol parsers and edge cases. + +## Product boundaries + +Please keep these boundaries intact: + +- Do not add IEC 61850 control workflows to the product app. +- Do not make timing claims beyond the timestamp source quality. +- Do not vendor restricted third-party binaries without license review. +- Do not add sample captures that expose private customer/project data. + +## Pull request checklist + +Before opening a pull request: + +1. Build the solution in Release mode. +2. Confirm the app still starts on Windows. +3. Keep README and docs user-facing. +4. Add or update validation notes when behavior changes. +5. Avoid committing `bin`, `obj`, `artifacts`, captures, logs, or local settings. + +## Development setup + +```powershell +git clone https://github.com/masarray/DigSubAnalyzer.git +cd DigSubAnalyzer +dotnet restore .\ProcessBusSuite.sln +dotnet build .\ProcessBusSuite.sln -c Release +``` diff --git a/Directory.Build.props b/Directory.Build.props index 13b1525..f0e7870 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,5 +6,11 @@ Apache-2.0 git en + 1.2.0 + public-beta + 1.2.0-public-beta + 1.2.0.0 + 1.2.0.0 + 1.2.0-public-beta diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..746c340 --- /dev/null +++ b/NOTICE @@ -0,0 +1,6 @@ +Process Bus Insight / DigSubAnalyzer +Copyright 2026 Process Bus Insight Contributors + +This product includes source code licensed under the Apache License, Version 2.0. + +Npcap is a runtime prerequisite for raw packet capture on Windows. Npcap is not vendored in this repository and must be installed separately by the user. diff --git a/README.md b/README.md index a732cca..f0f9cc8 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,173 @@ -# Mas Ari Digital Substation Instrument +# Process Bus Insight (DigSubAnalyzer) — IEC 61850 SV, GOOSE & PTP Analyzer for Windows -Raw-passive IEC 61850 Process Bus analyzer for FAT/SAT troubleshooting, commissioning visibility, interoperability investigation, and public engineering learning. +[![CI](https://github.com/masarray/DigSubAnalyzer/actions/workflows/ci.yml/badge.svg)](https://github.com/masarray/DigSubAnalyzer/actions/workflows/ci.yml) +[![GitHub Pages](https://github.com/masarray/DigSubAnalyzer/actions/workflows/pages.yml/badge.svg)](https://github.com/masarray/DigSubAnalyzer/actions/workflows/pages.yml) +[![Release Package](https://github.com/masarray/DigSubAnalyzer/actions/workflows/release-package.yml/badge.svg)](https://github.com/masarray/DigSubAnalyzer/actions/workflows/release-package.yml) +[![Latest Release](https://img.shields.io/github/v/release/masarray/DigSubAnalyzer?include_prereleases&label=release)](https://github.com/masarray/DigSubAnalyzer/releases) +[![License](https://img.shields.io/github/license/masarray/DigSubAnalyzer)](LICENSE) +[![Platform](https://img.shields.io/badge/platform-Windows%2010%2F11-0078d4)](#download--install--run) +[![.NET](https://img.shields.io/badge/.NET-8.0-512BD4)](#build-from-source) -![Process Bus Insight Analyzer](docs/screenshot/analyzer-overview.webp) +**Process Bus Insight** is a free, open-source **IEC 61850 Process Bus analyzer** for Windows. It gives substation automation engineers a raw-passive view of **Sampled Values (SV)**, **GOOSE**, **PTP timing context**, and **SCL expected-vs-observed validation** during FAT, SAT, commissioning, interoperability checks, and troubleshooting. -Public landing page: [`docs/index.html`](docs/index.html) +It is built for field clarity: see what traffic is present, which stream or publisher is unhealthy, what the SCL file expects, and what evidence can be copied into an engineering finding. No subscription. No license key. Apache-2.0 source license. -## Current Product Direction +> Timing note: arrival timing shown by the app is software/Npcap timestamp based. It is useful for screening and troubleshooting, not certification-grade jitter proof unless validated with suitable hardware timestamping, TAP, or trusted timing equipment. -This repository is positioned as a **receive-only raw-passive analyzer**, not an IEC 61850 client/server stack or control workflow. +![Process Bus Insight analyzer overview](docs/screenshot/analyzer-overview.webp) -The product analyzer runtime decodes SV/GOOSE from raw Ethernet frames through the internal raw engine: +## What is this? + +Process Bus Insight is a receive-only Windows desktop instrument for IEC 61850 Process Bus visibility. It listens to raw Ethernet traffic through Npcap and presents engineering-level information instead of leaving the user with packet lists only. + +The current product scope focuses on: + +- IEC 61850-9-2 / Sampled Values stream discovery and diagnostics. +- IEC 61850 GOOSE publisher discovery, sequence tracking, and typed value inspection. +- PTPv2 visibility for timing context and confidence wording. +- SCL-assisted comparison between expected engineering configuration and observed network traffic. +- Evidence-oriented views that support FAT/SAT troubleshooting discussions. + +## Why use it? + +Wireshark is powerful, but commissioning work often needs faster answers: + +- Which SV stream is live? +- Is the APPID, VLAN, MAC, svID, or confRev what the SCL says it should be? +- Which GOOSE publisher changed state? +- Is the issue in a stream, a publisher, a PTP source, or the capture path? +- What can be copied into an engineering finding without overclaiming timing accuracy? + +Process Bus Insight is designed around those questions. + +## Features + +| Area | What you get | +| --- | --- | +| SV analyzer | Stream discovery, APPID/svID/source metadata, RMS/phasor visualization, reconstructed waveform, continuity counters, missing-sample indicators, arrival timing evidence. | +| GOOSE inspector | Passive publisher discovery, stNum/sqNum visibility, event timeline, typed value decoding, changed-value summary, publisher-focused inspection. | +| PTP visibility | Passive PTPv2 context for Ethernet and UDP transport, message timeline, grandmaster/domain context where available, timing-confidence wording. | +| SCL validation | Load SCD/ICD/CID files, compare expected streams/publishers with observed network evidence, surface matched, missing, unexpected, weak, mismatched, and conflicted status. | +| Evidence workflow | Copyable evidence text, cautious timing labels, target-aware diagnostics, and practical troubleshooting context for FAT/SAT. | +| Open source | Free to use, Apache-2.0 source license, no license server, no subscription, no hidden commercial gate. | + +## Screenshots + +| Analyzer overview | SCL binding workspace | +| --- | --- | +| ![Analyzer overview](docs/screenshot/analyzer-overview.webp) | ![SCL binding workspace](docs/screenshot/scl-binding.webp) | + +| Diagnostics | GOOSE inspector | +| --- | --- | +| ![Diagnostics workspace](docs/screenshot/diagnostics-health.webp) | ![GOOSE inspector](docs/screenshot/goose-inspector.webp) | + +## Quick start + +1. Install **Npcap** on the Windows machine that will capture Process Bus traffic. +2. Download the latest portable package from [GitHub Releases](https://github.com/masarray/DigSubAnalyzer/releases). +3. Extract the ZIP to a local folder such as `C:\Tools\ProcessBusInsight`. +4. Run `ProcessBusInsight.exe`. +5. Select a real Ethernet adapter connected to the Process Bus network or TAP. +6. Start capture and review SV, GOOSE, PTP, diagnostics, and SCL binding views. + +For a field-oriented checklist, see [`docs/QUICK_START.md`](docs/QUICK_START.md). + +## Download / Install / Run + +The recommended user package is the **Windows x64 portable ZIP** created by the release workflow: ```text -ProcessBus.App.Wpf -ProcessBus.Core -ProcessBus.Iec61850.Raw +ProcessBusInsight-v1.2.0-public-beta-win-x64-portable.zip +SHA256SUMS.txt ``` -The WPF product app must not reference, load, or call external IEC 61850 subscriber stacks. +The portable package contains the published Windows app, quick-start notes, license files, third-party notices, and a small launcher batch file for convenience. Visual Studio is not required to run the portable release package. -## Public Product Positioning +Runtime notes: -Safe external wording: +- Windows 10/11 x64 is recommended. +- Npcap must be installed separately on the target machine. +- Prefer a physical Ethernet NIC or TAP for timing investigation. +- Avoid loopback, Wi-Fi Direct, virtual adapters, and unverified USB Ethernet paths for serious timing interpretation. -> Raw-passive IEC 61850 Process Bus Insight tool with SCL-aware SV, GOOSE, and PTP visibility. +## How it works -Do not claim metrology-grade microsecond jitter measurement unless the result is externally validated using hardware timestamping, a TAP, or a trusted protocol analyzer. +```text +Process Bus / TAP / Mirror Port + ↓ +Npcap raw capture + ↓ +ProcessBus.Iec61850.Raw + ↓ +SV + GOOSE + PTP decode + ↓ +ProcessBus.Core state and models + ↓ +ProcessBus.App.Wpf engineering workspace +``` -## What Works Now +The app is raw-passive: it receives and decodes traffic. It does not send IEC 61850 commands, does not control IEDs, and does not act as a publisher or subscriber stack for protection/control operation. -- WPF analyzer shell with process-bus workspace layout. -- Raw passive capture path using Npcap adapter selection. -- Internal SV APDU decoding and stream discovery. -- Passive GOOSE discovery/event surface. -- SV stream explorer, APPID/svID/source metadata, stream state, diagnostics, event log. -- Phasor, metering, and reconstructed scope from decoded RMS/phasor plus `smpCnt` timing. -- Arrival timing anomaly detection including `>=300 us` jitter excursion evidence. -- Evidence copy panel with capture-path caution and timing-confidence wording. +## Build from source -## Deliberate Limitations +Requirements: -- Timing is software timestamp based. It is valid for screening and troubleshooting, not certification-grade timing proof. -- The waveform panel is reconstructed from decoded electrical values and timing; it is not a hardware-sampled oscilloscope trace. -- Generic SCL-driven channel semantic mapping is not complete yet. -- Advanced reporting, replay, COMTRADE, FFT/THD, and control workflows are out of current scope. +- Windows 10/11. +- Visual Studio 2022 or 2026 with the .NET desktop workload. +- .NET 8 SDK. +- Npcap installed for runtime capture tests. -## Main Solution Shape +Build: -```text -ProcessBusSuite.sln -src/ - ProcessBus.App.Wpf/ WPF analyzer product shell - ProcessBus.Core/ shared models, shell state, adapter catalog - ProcessBus.Iec61850.Raw/ raw Ethernet SV/GOOSE decoder and live data source +```powershell +git clone https://github.com/masarray/DigSubAnalyzer.git +cd DigSubAnalyzer +dotnet restore .\ProcessBusSuite.sln +dotnet build .\ProcessBusSuite.sln -c Release ``` -Legacy external-library experiments and bench publisher code must stay outside the product package. If retained locally, keep them under a separate tools/legacy area and do not include them in the product solution. +Run from source: -## Build Notes +```powershell +dotnet run --project .\src\ProcessBus.App.Wpf\ProcessBus.App.Wpf.csproj -c Release +``` -- Target: .NET 8 Windows / WPF. -- Recommended IDE: Visual Studio 2026 or later with .NET desktop workload. -- Npcap is required on the target machine for raw Ethernet capture. -- For timing evaluation, prefer a physical Ethernet NIC/TAP. Avoid loopback, Wi-Fi Direct, virtual adapters, or unverified USB Ethernet paths. -- Installer build is disabled by default. Enable explicitly only when the installer script exists: +Create a local portable package: ```powershell -dotnet build .\src\ProcessBus.App.Wpf\ProcessBus.App.Wpf.csproj -c Release /p:BuildInstaller=true +.\scripts\publish-windows-portable.ps1 -Version "1.2.0-public-beta" +.\scripts\verify-release-package.ps1 -PackageZip ".\artifacts\release\ProcessBusInsight-v1.2.0-public-beta-win-x64-portable.zip" ``` -## Evidence Policy +## Documentation + +- [`docs/QUICK_START.md`](docs/QUICK_START.md) — first run and field checklist. +- [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) — adapter, Npcap, empty traffic, and timing interpretation issues. +- [`docs/VALIDATION_MATRIX.md`](docs/VALIDATION_MATRIX.md) — practical validation scope for SV, GOOSE, PTP, SCL, and evidence. +- [`docs/RELEASE_PACKAGING.md`](docs/RELEASE_PACKAGING.md) — portable release package design. +- [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) — GitHub Pages and release automation notes. +- [`docs/ROADMAP.md`](docs/ROADMAP.md) — user-facing product roadmap. +- [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) — runtime and redistribution notes. -When demonstrating the app, always show: +## Roadmap / Planned improvements -1. selected adapter raw device name, -2. stream status and APPID/svID/source MAC, -3. sequence continuity and missing-sample count, -4. current/average/max arrival jitter, -5. timing-confidence classification, -6. copied evidence text. +Near-term direction: -Use `docs/PUBLIC_PORTFOLIO_STRATEGY.md` and `docs/VALIDATION_TEST_PLAN.md` as the working checklist before publication. +- Stronger target-aware diagnostics for individual SV streams, GOOSE publishers, PTP sources, and capture path warnings. +- More complete SCL expected-vs-observed validation. +- Cleaner GOOSE semantic labels for common breaker, trip, alarm, and interlock signals. +- Evidence report export for FAT/SAT documentation. +- Capture/replay workflow for repeatable demonstration and regression testing. +- More formal validation with physical NIC/TAP and trusted timing equipment. +## Contributing -## PTP Timing Layer +Contributions are welcome when they keep the product receive-only, evidence-focused, and honest about timing confidence. Start with [`CONTRIBUTING.md`](CONTRIBUTING.md), then open an issue or pull request with a clear engineering use case. -Phase 1 adds passive PTP visibility to the raw analyzer: EtherType 0x88F7 capture, PTPv2 common header decoding, Announce Grandmaster extraction, Sync/Announce/Follow_Up rate tracking, and timing-confidence wording that distinguishes SV arrival variation from measurement-grade jitter. See `docs/PTP_TIMING_LAYER_PHASE1.md`. +Good contribution areas include decoder tests, SCL parsing examples, documentation, UI clarity, validation scenarios, and field troubleshooting notes. ## License -Process Bus Insight source code is licensed under **Apache-2.0**. See `LICENSE`. +Source code is licensed under **Apache-2.0**. See [`LICENSE`](LICENSE). -Npcap is a runtime prerequisite and is not vendored in this repository. Self-contained Windows publish artifacts include Microsoft .NET runtime/WPF files generated by `dotnet publish`; review `THIRD_PARTY_NOTICES.md` before redistributing binary packages. +Npcap is a runtime prerequisite and is not vendored in this repository. Microsoft .NET/WPF runtime files may be included in self-contained published artifacts generated by `dotnet publish`; review [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) before redistributing binary packages. diff --git a/ROADMAP.md b/ROADMAP.md index 71a9307..9e8ef8a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,348 +1,51 @@ -# Process Bus Insight Roadmap +# Roadmap — Process Bus Insight -## Product Positioning +Process Bus Insight is moving toward a practical field instrument for IEC 61850 Process Bus visibility and FAT/SAT evidence. -**Process Bus Insight** is a raw-passive IEC 61850 SV / GOOSE / PTP diagnostic instrument for digital substation commissioning, FAT/SAT, interoperability troubleshooting, and public engineering learning. +## Current focus -It should not become a Wireshark clone, a relay test set, a PTP grandmaster, or an IEC 61850 control client. Its value is: +- Strong raw-passive SV, GOOSE, and PTP visibility. +- Target-aware diagnostics for streams, publishers, timing sources, and capture path. +- SCL expected-vs-observed validation. +- Evidence wording that is useful but does not overclaim timing accuracy. +- Release packaging that lets users try the app without Visual Studio. -```text -SV + GOOSE + PTP visibility -+ target-aware diagnostics -+ timing confidence -+ typed GOOSE values -+ SCL expected-vs-observed validation -+ evidence report readiness -``` +## Planned improvements -## Runtime Boundary +### 1. Diagnostics maturity -Product projects: +- Traffic Health Navigator for SV, GOOSE, and PTP targets. +- Clear issue ownership: stream, publisher, timing source, or capture path. +- Better adapter confidence warnings. -```text -ProcessBus.App.Wpf -ProcessBus.Core -ProcessBus.Iec61850.Raw -``` +### 2. Stable instrument rendering -The product runtime must remain raw-passive and must not reference `libiec61850` or external IEC 61850 subscriber stacks. +- Strong stream key selection. +- More stable waveform/phasor update behavior during bad frames. +- Better handling of duplicate, out-of-order, and sequence-jump traffic. ---- +### 3. PTP-aware timing interpretation -## Stage 1 — Raw Passive Foundation - -Status: largely complete. - -Done: - -- Npcap raw capture path. -- Raw SV decode and stream discovery. -- Raw GOOSE decode. -- PTP transport support for Ethernet `0x88F7` and UDP `319/320`. -- Header capture controls moved to the top-right. -- Timing confidence wording introduced. - -Remaining hardening: - -- physical NIC/TAP validation, -- better adapter quality warnings, -- clean installer/release packaging. - ---- - -## Stage 2 — Target-Aware Diagnostics - -Status: current focus. - -Goal: warnings must point to the affected target. - -Required behavior: - -- Diagnostics left rail becomes **Traffic Health Navigator**. -- SV, GOOSE, and PTP targets are listed with status and issue summaries. -- Selecting a target scopes the diagnostics page. -- Global warning must say whether the affected object is an SV stream, GOOSE publisher, PTP source, or capture path. -- Analyzer and GOOSE view should follow selected target where safe. - -Done when: - -```text -If only one of many SV streams is anomalous, the user can immediately see which stream is affected and inspect that stream. -``` - ---- - -## Stage 3 — Stable Instrument Rendering - -Goal: waveform and phasor must be calm and trustworthy. - -Required behavior: - -- Stream key uses source MAC + destination MAC + VLAN + APPID + svID + confRev. -- Waveform/phasor update only from the selected stream key. -- Duplicate/out-of-order/sequence-jump frames do not directly disturb the visual instrument display. -- Diagnostics still count and expose bad frames. -- Move toward `smpCnt`-aligned display windows instead of last-packet anchoring. - -Done when: - -```text -SV instability is reported as a finding; the waveform does not randomly slide or rotate because of bad samples. -``` - ---- - -## Stage 4 — PTP-Aware Timing Architecture - -Status: in progress. - -Done: - -- PTP UDP/IPv4 from PTPSync is decoded. -- PTP event table can show Sync / Follow_Up / Announce / PDelay. -- PTP status, GM, domain, clock class, and rates are visible. - -Next: - -- PTP freshness states: never observed / live / stale / lost. -- PTP event findings: GM changed, domain changed, announce timeout, sync timeout. +- Freshness states: never observed, live, stale, lost. +- Grandmaster/domain change warnings. - Clock class interpretation. -- Timing confidence propagation to all workspaces. -- Keep software timestamp wording honest: screening only unless hardware timestamp validated. - -Done when: - -```text -User can confirm PTP traffic and timing context inside the app without Wireshark. -``` - ---- - -## Stage 5 — GOOSE Inspector Maturity - -Status: semantic pass implemented. - -Done: - -- GOOSE event timeline. -- stNum/sqNum tracking. -- typed DataSet value decode. -- changed value summary. -- SCL DataSet entry mapping for selected GOOSE publisher values. -- Inspector displays signal reference and FC/CDC/bType when a matching SCL GOOSE stream is available. - -Next: - -- cleaner timeline layout and filtering. -- per-publisher health state. -- TTL/stNum/sqNum supervision. -- quality bit interpretation and common semantic value labels for breaker position, trip, alarm, and interlock signals. - -Done when: - -```text -GOOSE can be inspected similarly to IEDScout for header fields and typed values, then enhanced with SCL semantic names. -``` - ---- - -## Stage 6 — SCL Expected-vs-Observed Validation - -Status: active productization. - -Goal: become a commissioning checker, not just a viewer. - -Required behavior: - -- load SCD / ICD / CID read-only, -- extract expected SV streams, -- extract expected GOOSE publishers, -- compare observed APPID, VLAN, MAC, svID/goID, DataSet, confRev, and entry count, -- show missing / mismatch / unexpected traffic. -- show conflicts from duplicate APPID or contradictory multi-file SCL expectations. - -Done: - -- SCL workspace imports multiple documents into one engineering context. -- Expected SV/GOOSE stream catalog is visible. -- Live binding matrix shows MATCHED, WEAK, MISSING, UNEXPECTED, MISMATCH, and CONFLICT. -- Selected binding rows show expected-vs-observed evidence side-by-side. - -Next: - -- Promote SCL-backed SV semantic element mapping into validated render-channel mapping. -- CSV/JSON evidence export for the binding matrix. -- Dedicated validation dashboard with PASS / WARNING / FAIL / UNKNOWN. - -Done when: - -```text -Engineer can load SCL, start capture, and immediately see which expected traffic is present or mismatched. -``` - ---- - -## Stage 7 — PCAP Playback and Support Reproduction - -Goal: make support and walkthroughs easy without hardware. - -Required behavior: - -- open PCAP/PCAPNG, -- replay or scan capture files, -- populate SV/GOOSE/PTP views from recorded traffic, -- ship sample PCAP + sample SCL. - -Done when: - -```text -A user can reproduce a case from a capture file without the live bench. -``` - ---- - -## Stage 8 — Reporting - -Goal: compact engineering report for FAT/SAT and troubleshooting. - -Required content: - -- observed SV streams, -- observed GOOSE publishers, -- observed PTP source, -- target-aware findings, -- SCL mismatch results when available, -- timing confidence and limitations. - -Do not overclaim timing precision. - ---- - -## Near-Term Patch Order - -1. Target-aware diagnostics and traffic health navigator. -2. Stable `smpCnt`-aligned waveform display improvements. -3. PTP freshness/event findings. -4. GOOSE filtering/typography cleanup. -5. SCL read-only mapping. -6. Expected-vs-observed validation. -7. PCAP playback. - ---- - -## Stage 2B — Stable Workspace UX and Advanced Target Explorer - -Status: in progress. - -Goal: make the analyzer feel like a stable instrument while processing live streams. - -Required behavior: - -- SV stream explorer remains stable in first-seen order. -- Diagnostics target navigator remains stable in first-seen order. -- Live severity/last-seen updates must not make SV1/SV2 cards swap position. -- Selected stream/publisher/PTP source remains locked by strong key during refresh. -- Workspace target changes show a short glass transition overlay, but live refresh does not flicker. -- Advanced left rail becomes a raw target explorer for SV / GOOSE / PTP. -- Advanced center panel shows evidence for the selected target, not only SV1. - -Done when: - -```text -A user can run multiple SV streams live, click SV1 or SV2, and the card position and selected inspector remain stable while the waveform continues updating. -``` - -## Stage 3B — Per-Stream Mapping and Phase Order Audit - -Goal: make phasor differences between SV streams explainable and auditable. - -Required behavior: - -- Mapping candidate is stored and shown per SV stream. -- Advanced view shows per-stream channel angle summary. -- App flags possible Ub/Uc or S/T swap as a warning candidate, not as a silent correction. -- Future SCL mapping must replace generic labels with actual dataset semantic names. - -Done when: - -```text -If SV1 and SV2 have different phase order, the engineer can inspect each stream and see whether the issue is payload order, mapping profile, or real signal orientation. -``` - - -## SCL Semantic Mapping Direction - -The project direction is SCL-first, not raw-packet-only. Before implementing expected-vs-observed validation, always implement semantic stream mapping: - -1. Load SCL/CID/ICD/IID/SCD. -2. Detect IEC 61850-6 edition/namespace style. -3. Parse IED, LDevice, LN/LN0, DataSet, GSEControl, SampledValueControl, Communication/GSE/SMV addresses, and DataTypeTemplates. -4. Bind live SV/GOOSE streams to expected SCL streams using APPID, MAC, VLAN, svID/goID, control block, DataSet, and confRev. -5. Map payload order to DataSet entry order. -6. Resolve signal reference, FC, CDC, bType, type ID, and enum type. -7. Then perform expected-vs-observed validation. - -Do not hardcode one global SV channel order as final truth. Mapping must be per stream and preferably SCL-backed. Without SCL, use lab profiles only as temporary hints and clearly label them as inferred. - - -## SCL Engineering Workspace Rule - -SCL is a first-class engineering context workspace, not an Advanced/debug panel. - -Required behavior: - -- The product must support multiple imported SCL documents in one engineering context. -- Supported input types are SCD, CID, ICD, IID, and XML-based SCL exports. -- The SCL workspace must show imported document cards, IED cards, SV/GOOSE semantic stream catalog, DataSet entry order, and parse warnings. -- Live stream binding must be based on stable scoring, not a single brittle key. Use APPID, destination MAC, VLAN, svID/goID, control block, DataSet, confRev, and IED/source hints. -- Conflicts must be visible: duplicate IED names, duplicate APPIDs, different confRev, different DataSet order, or multiple candidate bindings. -- Advanced may expose raw SCL details, but semantic mapping belongs in the SCL workspace. - -Done when an engineer can import several vendor files and understand which IEDs, GOOSE controls, SV controls, DataSets, and signal orders are available before expected-vs-observed validation begins. - - -## Stage 4A — SCL Engineering Workspace - -Status: implemented as first product pass. - -Goal: make SCL a first-class engineering context layer rather than a small Advanced/debug panel. - -Included: - -- New `SCL` workspace tab. -- Multiple SCL document import in one session. -- Imported document cards with edition fingerprint, counts, and warnings. -- IED cards showing source file, vendor/type hints, SV/GOOSE/DataSet counts. -- SV/GOOSE semantic stream catalog with transport, DataSet reference, entries, and live-binding candidate status. -- Detail panel showing selected stream identity and DataSet entry order with signal reference, FC, CDC, and bType. - -Next hardening steps: - -1. Expand conflict resolver UX for duplicate IED names, duplicate APPID/MAC, confRev conflicts, and DataSet order conflicts. -2. Use SCL entry order to rename GOOSE DataSet values in the GOOSE inspector. -3. Use SCL entry order to build per-stream SV channel mapping and reduce phase-order ambiguity. -4. Promote binding evidence into a formal validation dashboard. -5. Add report/export for binding evidence. - -## Stage 4B — SCL Live Binding Matrix +- Clear timestamp source and timing confidence propagation. -Status: implemented as engineering preview. +### 4. GOOSE inspector maturity -Goal: turn SCL from a passive semantic catalog into a live commissioning context that compares expected engineering streams against observed network traffic. +- Better value labels for breaker position, trip, alarm, and interlock signals. +- More complete quality interpretation. +- Cleaner event filtering and publisher health state. -Included: +### 5. Evidence and reporting -- Binding matrix in the SCL workspace. -- Expected SCL SV/GOOSE stream rows with status: `MATCHED`, `WEAK`, `MISSING`, `MISMATCH`, and `CONFLICT`. -- Unexpected live SV/GOOSE rows when traffic is observed but no imported SCL stream can explain it. -- Explainable scoring using APPID, destination MAC, VLAN, priority, svID/goID, control block, DataSet reference, and confRev where available. -- Selecting a binding row shows expected-vs-observed evidence side-by-side. -- Conflict screening catches duplicate expected APPID and contradictory duplicate control block definitions. +- Exportable FAT/SAT evidence report. +- Screenshot-friendly diagnostic summaries. +- Repeatable validation scenarios and sample captures where legally shareable. -Next hardening steps: +## Non-goals -1. Add richer per-field comparison columns: APPID, VLAN, MAC, confRev, DataSet, and score reason. -2. Add conflict resolver actions for multi-file SCL ambiguity. -3. Use SCL DataSet order to rename GOOSE inspector values. -4. Use SCL DataSet order to drive per-stream SV channel mapping. -5. Add report/export for binding evidence. +- Not a relay test set. +- Not a control client. +- Not a PTP grandmaster. +- Not a certified timing measurement instrument without validated timestamp hardware. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..395ae89 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +## Supported versions + +The public beta branch is the active supported line for security reports and responsible disclosure. + +## Reporting a vulnerability + +Please open a private security advisory on GitHub when available, or contact the repository owner through the public GitHub profile if private advisories are not enabled. + +Do not publish exploit details before the issue has been reviewed. + +## Scope + +Relevant security concerns include: + +- Unsafe parsing of crafted SCL or network input. +- Crashes triggered by malformed SV, GOOSE, PTP, or Ethernet frames. +- Accidental exposure of local capture files, logs, or project data. +- Release package integrity issues. + +## Out of scope + +- Certification of timing measurement accuracy. +- Issues caused by unsupported Npcap/driver installation. +- Vulnerabilities in third-party software not distributed by this repository. + +## Data handling + +The application is intended for local engineering use. Avoid sharing captures, screenshots, or SCL files that contain customer, site, or project-sensitive information unless they have been sanitized. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 8eb7f1a..819cf42 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -1,17 +1,17 @@ # Third-Party Notices -Process Bus Insight currently uses .NET / WPF platform assemblies and direct Npcap `wpcap` interop. +Process Bus Insight is built with Microsoft .NET and WPF for Windows desktop use. -## Source Dependencies +## Runtime prerequisite -- No external NuGet package dependency is declared by the product projects at this time. -- The product runtime is raw-passive and does not link to `libiec61850`, MZ Automation libraries, or an IEC 61850 subscriber stack. +Npcap is required on the target Windows machine for raw packet capture. Npcap is not vendored in this repository and is not bundled by the source tree. -## Runtime Requirements +Users should install Npcap from its official distribution source and follow the applicable license terms for their environment. -- Npcap is required on the target Windows machine for raw packet capture. Npcap is not vendored in this repository. -- Self-contained publish artifacts include Microsoft .NET runtime/WPF files produced by `dotnet publish`. Review Microsoft .NET runtime notices before redistributing binary packages. +## Published artifacts -## Project License +Self-contained Windows packages created with `dotnet publish` may include Microsoft .NET runtime and WPF files generated by the .NET SDK. Redistribution terms are governed by the applicable Microsoft .NET license terms. -Project source code is licensed under Apache-2.0. See `LICENSE`. +## Project source license + +Process Bus Insight source code is licensed under Apache-2.0. See `LICENSE`. diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..b4b7187 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,40 @@ +# Deployment — GitHub Pages and Release Automation + +## GitHub Pages + +The public landing page lives in `docs/index.html` and is designed for GitHub Pages. + +Recommended Pages URL: + +```text +https://masarray.github.io/DigSubAnalyzer/ +``` + +The repository includes: + +- `docs/index.html` +- `docs/styles.css` +- `docs/app.js` +- `docs/robots.txt` +- `docs/sitemap.xml` +- `docs/site.webmanifest` +- `docs/.nojekyll` + +The workflow `.github/workflows/pages.yml` uploads the `docs` folder as a static Pages artifact. + +## Release automation + +The workflow `.github/workflows/release-package.yml` creates the Windows portable package and SHA256 sums. It can publish a GitHub Release when requested. + +Recommended manual release flow: + +1. Push the repository changes to GitHub. +2. Open **Actions**. +3. Run **Release Windows portable package**. +4. Use version `1.2.0-public-beta` or the next planned version. +5. Keep `publish_release=false` for a dry artifact build. +6. Set `publish_release=true` after verifying the artifact. + +## About panel / topics + +Use `scripts/set-github-about.ps1` to preview or apply the repository description, homepage, and topics using GitHub CLI. diff --git a/docs/QUICK_START.md b/docs/QUICK_START.md new file mode 100644 index 0000000..1cca849 --- /dev/null +++ b/docs/QUICK_START.md @@ -0,0 +1,67 @@ +# Quick Start — Process Bus Insight + +Process Bus Insight is a receive-only IEC 61850 Process Bus analyzer for Windows. This guide helps you run the portable package and start a safe observation workflow. + +## 1. Prepare the Windows machine + +Recommended setup: + +- Windows 10 or Windows 11 x64. +- A physical Ethernet adapter connected to a TAP, mirror port, or isolated test network. +- Npcap installed with normal raw packet capture support. +- Local admin rights if your Windows/Npcap policy requires elevated capture access. + +Avoid these adapters for serious timing interpretation: + +- Loopback adapters. +- Wi-Fi / Wi-Fi Direct. +- Hyper-V, VMware, VPN, or virtual bridge adapters. +- Unverified USB Ethernet adapters when timing evidence matters. + +## 2. Download and extract + +1. Open the GitHub Releases page. +2. Download the latest `ProcessBusInsight-vX.Y.Z-win-x64-portable.zip`. +3. Download `SHA256SUMS.txt` if you want to verify file integrity. +4. Extract the ZIP to a local folder such as: + +```text +C:\Tools\ProcessBusInsight +``` + +## 3. Run the app + +Open the extracted folder and run: + +```text +ProcessBusInsight.exe +``` + +The package may also include `Start-ProcessBusInsight.bat` as a convenience launcher. + +## 4. Start a safe capture workflow + +1. Select the physical Ethernet adapter connected to the Process Bus traffic. +2. Start capture. +3. Confirm traffic appears in the event log. +4. Review SV stream discovery. +5. Review GOOSE publishers and event changes. +6. Review PTP timing context if present. +7. Load SCL when available and compare expected-vs-observed objects. +8. Copy evidence only after confirming adapter and capture-path confidence. + +## 5. Evidence checklist + +For a useful FAT/SAT troubleshooting screenshot or copied finding, include: + +- Selected adapter raw device name. +- SV stream APPID, svID, VLAN, source MAC, and stream status. +- GOOSE publisher/control block and stNum/sqNum state when relevant. +- PTP transport/domain/grandmaster context when relevant. +- Missing sample / sequence / arrival timing status. +- Timing confidence label and timestamp source wording. +- Expected SCL fields and observed fields when SCL is loaded. + +## 6. Timing caution + +Arrival timing shown by the app is based on host/Npcap software timestamps. Treat it as screening evidence. Do not use it as certification-grade jitter proof unless the capture path is validated with hardware timestamping, TAP, or trusted timing equipment. diff --git a/docs/RELEASE_NOTES_v1.2.0.md b/docs/RELEASE_NOTES_v1.2.0.md new file mode 100644 index 0000000..fc81fbd --- /dev/null +++ b/docs/RELEASE_NOTES_v1.2.0.md @@ -0,0 +1,27 @@ +# Release Notes — v1.2.0-public-beta + +This public beta hardens Process Bus Insight as a user-facing open-source Windows tool for IEC 61850 Process Bus visibility. + +## Highlights + +- User-facing README with clear download, run, build, documentation, and contribution guidance. +- SEO-ready GitHub Pages landing page with screenshots, FAQ, social preview metadata, and structured data. +- Windows portable release automation for GitHub Actions. +- Local PowerShell scripts to publish and verify the portable package. +- GitHub repository setup script for description, homepage, topics, and repository feature settings. +- Documentation for quick start, troubleshooting, validation matrix, deployment, release packaging, roadmap, security, and contribution flow. + +## Runtime scope + +Process Bus Insight remains receive-only and raw-passive. It decodes observed SV, GOOSE, and PTP traffic and helps compare live traffic against SCL engineering expectation. It does not send control commands or claim certified timing proof from normal host/Npcap timestamps. + +## Recommended package + +Download the portable Windows package from GitHub Releases: + +```text +ProcessBusInsight-v1.2.0-public-beta-win-x64-portable.zip +SHA256SUMS.txt +``` + +Npcap is required on the target machine and is not bundled in this repository. diff --git a/docs/RELEASE_PACKAGING.md b/docs/RELEASE_PACKAGING.md new file mode 100644 index 0000000..f1a5ee0 --- /dev/null +++ b/docs/RELEASE_PACKAGING.md @@ -0,0 +1,49 @@ +# Release Packaging — Process Bus Insight + +The release workflow creates a Windows x64 portable package for users who want to try the app without Visual Studio. + +## Output files + +A release package contains: + +```text +ProcessBusInsight-v-win-x64-portable.zip +SHA256SUMS.txt +``` + +The ZIP contains: + +```text +app/ published Windows application +ProcessBusInsight.exe convenience copy/entry point when publish output allows it +Start-ProcessBusInsight.bat simple launcher +README_QUICK_START.txt user-facing run instructions +LICENSE +NOTICE +THIRD_PARTY_NOTICES.md +``` + +## Local packaging + +Run from the repository root: + +```powershell +.\scripts\publish-windows-portable.ps1 -Version "1.2.0-public-beta" +.\scripts\verify-release-package.ps1 -PackageZip ".\artifacts\release\ProcessBusInsight-v1.2.0-public-beta-win-x64-portable.zip" +``` + +## GitHub release workflow + +The release workflow can be started manually from the GitHub Actions tab. It also runs on tags matching `v*`. + +Manual inputs: + +- `version` — release version label, for example `1.2.0-public-beta`. +- `publish_release` — when `true`, create or update a GitHub Release. +- `prerelease` — mark release as prerelease. +- `draft` — create a draft release. +- `release_notes_file` — markdown file used as release body. + +## Runtime prerequisite + +Npcap is required on the target Windows machine and is not bundled in the repository. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..9e8ef8a --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,51 @@ +# Roadmap — Process Bus Insight + +Process Bus Insight is moving toward a practical field instrument for IEC 61850 Process Bus visibility and FAT/SAT evidence. + +## Current focus + +- Strong raw-passive SV, GOOSE, and PTP visibility. +- Target-aware diagnostics for streams, publishers, timing sources, and capture path. +- SCL expected-vs-observed validation. +- Evidence wording that is useful but does not overclaim timing accuracy. +- Release packaging that lets users try the app without Visual Studio. + +## Planned improvements + +### 1. Diagnostics maturity + +- Traffic Health Navigator for SV, GOOSE, and PTP targets. +- Clear issue ownership: stream, publisher, timing source, or capture path. +- Better adapter confidence warnings. + +### 2. Stable instrument rendering + +- Strong stream key selection. +- More stable waveform/phasor update behavior during bad frames. +- Better handling of duplicate, out-of-order, and sequence-jump traffic. + +### 3. PTP-aware timing interpretation + +- Freshness states: never observed, live, stale, lost. +- Grandmaster/domain change warnings. +- Clock class interpretation. +- Clear timestamp source and timing confidence propagation. + +### 4. GOOSE inspector maturity + +- Better value labels for breaker position, trip, alarm, and interlock signals. +- More complete quality interpretation. +- Cleaner event filtering and publisher health state. + +### 5. Evidence and reporting + +- Exportable FAT/SAT evidence report. +- Screenshot-friendly diagnostic summaries. +- Repeatable validation scenarios and sample captures where legally shareable. + +## Non-goals + +- Not a relay test set. +- Not a control client. +- Not a PTP grandmaster. +- Not a certified timing measurement instrument without validated timestamp hardware. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..165fb61 --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,84 @@ +# Troubleshooting — Process Bus Insight + +This guide covers common setup and field-use issues for the Windows portable package. + +## No adapters appear + +Possible causes: + +- Npcap is not installed. +- Capture permission is blocked by Windows policy. +- The app is running without the required access rights. +- Npcap service/driver installation is incomplete. + +Actions: + +1. Install or repair Npcap. +2. Reboot the machine if the Npcap installer requests it. +3. Run the app again. +4. If your environment requires it, run as Administrator. + +## Adapter appears but no traffic is visible + +Check: + +- The selected adapter is the physical NIC connected to the test network. +- The mirror/TAP port is actually forwarding SV/GOOSE/PTP frames. +- VLAN handling on the switch/mirror port is correct. +- The traffic is not only on a different NIC. +- Windows firewall or security tools are not blocking capture access. + +A practical first check is to confirm the same adapter can see traffic in Wireshark. + +## SV streams appear unstable + +Common root causes: + +- Wrong mirror port direction or incomplete VLAN mirroring. +- NIC/driver buffering or USB Ethernet batching. +- Publisher scheduling issue. +- Network congestion, duplicate frames, or out-of-order frames. +- Using a virtual/loopback/Wi-Fi adapter. + +Use the app to separate: + +- sequence continuity problems, +- missing samples, +- arrival timing excursions, +- capture path confidence, +- and PTP context. + +## GOOSE publisher is visible but values look incomplete + +Possible causes: + +- The GOOSE payload uses a structure not fully decoded yet. +- SCL is not loaded, so semantic names are unavailable. +- DataSet order or confRev differs from the expected engineering file. + +Actions: + +1. Confirm APPID, goID/gocbRef, DataSet, confRev, and source MAC. +2. Load SCL when available. +3. Compare typed values against expected DataSet order. +4. Capture a screenshot and record the raw fields for follow-up. + +## PTP is not shown + +Check: + +- PTP may be transported as Ethernet `0x88F7` or UDP port `319/320` depending on the network. +- The mirror/TAP port must include timing traffic. +- Some lab networks do not forward PTP to the same capture point. + +PTP visibility is a context layer. The app does not become a PTP grandmaster and does not discipline the Windows clock. + +## Timing evidence looks severe + +Treat timing findings carefully: + +- Normal Windows/Npcap timestamps are software based. +- USB adapters, virtual adapters, overloaded PCs, or poor mirror ports can create misleading timing symptoms. +- Use hardware timestamping, TAP, or trusted protocol analyzer for formal timing proof. + +Use the app wording as screening evidence unless the capture path is externally validated. diff --git a/docs/VALIDATION_MATRIX.md b/docs/VALIDATION_MATRIX.md new file mode 100644 index 0000000..fc18616 --- /dev/null +++ b/docs/VALIDATION_MATRIX.md @@ -0,0 +1,31 @@ +# Validation Matrix — Process Bus Insight + +This matrix explains what the application is intended to validate and what remains outside the current product boundary. + +| Area | Current validation value | Evidence produced | Boundary | +| --- | --- | --- | --- | +| SV discovery | Detects observed Sampled Values streams from raw Ethernet frames. | APPID, svID, VLAN, MAC, stream status, counters. | Not a relay test set and not a certified measurement instrument. | +| SV continuity | Tracks sequence behavior and missing/unstable sample indicators. | Continuity state, missing count, timing excursion context. | Interpretation depends on capture path quality. | +| SV display | Shows reconstructed waveform, phasor, and metering context. | Visual engineering view for selected stream. | Not a hardware oscilloscope. | +| GOOSE discovery | Detects observed publishers and important header fields. | APPID, source MAC, DataSet, confRev, stNum, sqNum. | Does not publish or control GOOSE. | +| GOOSE value decode | Shows typed dataset values where supported. | Changed values and typed item list. | Complex vendor-specific payloads may need more decoder coverage. | +| PTP context | Detects PTP traffic and timing context where visible. | Transport, message type, domain, GM context where decoded. | Does not discipline clocks or certify time sync. | +| SCL comparison | Compares expected objects with observed traffic. | Matched, weak, missing, unexpected, mismatched, conflicted state. | SCL semantic coverage is being expanded. | +| Evidence copy | Produces readable engineering wording. | Copyable status, expected/observed fields, caution labels. | Final FAT/SAT acceptance remains project-specific. | + +## Recommended validation scenarios + +1. Known-good SV publisher with expected APPID, VLAN, MAC, svID, and confRev. +2. Wrong APPID or VLAN to confirm mismatch detection. +3. GOOSE publisher with stNum/sqNum changes and typed value changes. +4. SCL file with expected streams and at least one intentionally missing stream. +5. PTP present and PTP absent scenarios to verify timing-confidence wording. +6. Physical NIC/TAP versus virtual/loopback adapter comparison to validate capture-path warnings. + +## Evidence quality levels + +| Level | Meaning | +| --- | --- | +| Screening | Useful for first diagnosis. Capture path may influence the symptom. | +| High confidence | Physical capture path is credible and evidence is consistent across views. | +| Measurement grade | Requires externally validated timestamping/test equipment. Do not claim this from normal Windows/Npcap timestamps alone. | diff --git a/docs/index.html b/docs/index.html index 8c02f3f..ff3266b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,263 +3,214 @@ - - Process Bus Insight - IEC 61850 Process Bus Analyzer - - - - + Process Bus Insight — IEC 61850 SV, GOOSE & PTP Analyzer + + + + + + + + + + + + + + + + + + + -
- - - - -
-
-
-
-
- - Process Bus Insight -
- Process Bus Insight analyzer showing SV waveform, phasor, and metering panels -
- -
SV waveformLive phasor + metering
-
-
- -
Raw-passiveNo control workflow
-
-
+
+ + + Process Bus Insight + + + GitHub +
+ +
+
+
+

Free · Open source · Apache-2.0 · Windows

+

IEC 61850 Process Bus visibility without starting from packet noise.

+

Process Bus Insight is a raw-passive Windows analyzer for SV, GOOSE, PTP timing context, and SCL expected-vs-observed validation. It helps substation automation engineers move from traffic capture to usable FAT/SAT evidence.

+ - -
-

Raw-passive substation automation insight

-

Precision Process Bus Visibility, Designed for Real Engineers.

-

Process Bus Insight reads SV, GOOSE, PTP, and SCL context so substation automation engineers can see what is live, what is expected, what is mismatched, and what evidence supports the finding.

- +
+ SV stream diagnostics + GOOSE inspection + PTP context + SCL validation
-
- -
-
- -
-

Stream Intelligence

-

Discover SV, GOOSE, PTP, APPID, VLAN, and source identity without starting in Wireshark.

-
+
+
+
Process Bus Insight
+ Process Bus Insight analyzer overview with SV waveform, phasor, and metering panels +
+
+ +
+
FAT / SATValidate observed process-bus traffic against engineering expectation.
+
CommissioningFind live streams, publishers, PTP context, and capture-path limitations quickly.
+
TroubleshootingTurn symptoms into target-aware findings that name the affected object.
+
+ +
+

Practical engineering workflow

+

Built around the questions field engineers actually ask.

+
+
+

SV stream visibility

+

Discover Sampled Values traffic, show APPID, svID, VLAN, source identity, continuity status, phasor, metering, and reconstructed waveform context.

-
- -
-

SCL-Aware Binding

-

Match live traffic against expected SCL streams with status, score, and evidence.

-
+
+

GOOSE inspector

+

Inspect publishers, stNum/sqNum behavior, changed values, typed dataset content, and event history without turning every issue into a Wireshark exercise.

-
- -
-

Evidence Ready

-

Turn protocol observations into readable engineering facts for FAT, SAT, and troubleshooting.

-
+
+

PTP timing context

+

Confirm timing traffic presence, transport, source context, and timing-confidence wording while keeping software timestamp limitations visible.

-
- -
-

Validation layers

-

A Clearer Layer of Process Bus Intelligence

-

The tool moves from packet viewing to engineering-aware validation: observed traffic, expected SCL, semantic signal mapping, and target-aware diagnostics.

- -
-
- 01 -

Raw Capture

-

SV, GOOSE, PTP, VLAN, APPID, MAC, counters, timestamps.

-
-
- 02 -

SCL Binding

-

Expected-vs-observed matching for SV and GOOSE with conflict visibility.

- View Features -
-
- 03 -

Diagnostics

-

Target-aware findings for streams, publishers, timing source, and capture path.

-
-
- 04 -

Evidence

-

Readable status, reason, timestamp, expected fields, and observed fields.

-
-
-
- -
-
-

Workflow solutions

-

Designed for Every Process Bus Investigation.

-

From first packet discovery to SCL-driven validation, the UX is shaped around how commissioning and automation engineers actually think.

-
    -
  • Observe live SV, GOOSE, and PTP traffic.
  • -
  • Understand the stream owner and control block.
  • -
  • Validate APPID, MAC, VLAN, confRev, and DataSet order.
  • -
  • Report what is missing, unexpected, mismatched, or uncertain.
  • -
-
- -
-
- SCL binding matrix showing expected versus observed process-bus streams - Diagnostics workspace showing target-aware timing findings -
SCL
-
Timing
-
-
- -
- - - - -
-
- -
-

Product features

-

Features Crafted for Intelligent Validation

-

A product surface that helps users learn protocol flow intuitively: raw traffic first, then engineering context, then validation evidence.

- -
-
-
- -
-

SCL Binding Workspace

-

Expected-vs-observed validation with readable evidence.

-
-
- SCL workspace binding matrix -
- -
-
- -

Smart Understanding

-

Understands streams, publishers, APPID, VLAN, confRev, and SCL expected objects.

-
-
- -

Evidence Reasoning

-

Explains matched, weak, missing, unexpected, mismatched, and conflicted status.

-
-
- -

Stable Live Targets

-

Keeps explorer targets stable while live protocol evidence updates by key.

-
-
- -

Advanced Evidence

-

Exposes raw decode, channel mapping, timing context, and forensic details.

-
-
-
-
- -
-
-
-

Engineering evidence matters

-

Unlock a Smarter Way to Validate Process Bus Traffic.

-

Capture what is present. Bind it to engineering intent. Explain what is wrong. Keep timing claims honest.

- -
-
- -
-
-

User stories

-

What Engineers Should Feel

- -
-
- " -

The app makes IEC 61850 process-bus traffic easier to explain: live streams, SCL expectation, and evidence are in one readable workflow.

-
- -
Substation Automation EngineerCommissioning workflow
-
+
+

SCL expected-vs-observed

+

Load SCD, ICD, or CID files and compare expected APPID, VLAN, MAC, DataSet, confRev, and stream identity with observed live traffic.

-
- " -

Instead of hiding uncertainty, it shows timing confidence, capture path limitation, and exactly which object is affected.

-
- -
Protection and Control SpecialistProcess bus validation
-
+
+

Evidence-ready findings

+

Copy clear engineering evidence for FAT/SAT discussion: observed object, expected object, reason, status, and confidence wording.

+
+
+

Receive-only boundary

+

The app listens and decodes. It does not send control commands, publish GOOSE/SV, or act as a protection workflow controller.

-
-
- -
- -
-
+
+ SCL binding workspace showing expected versus observed traffic +
+ + +
+

Product preview

+

Readable screens for real troubleshooting sessions.

+ +
- +
+
+

Download and run

+

Portable Windows package, no Visual Studio required.

+

Download the latest Windows x64 portable ZIP from GitHub Releases, extract it, install Npcap if needed, and run the desktop app. The package includes quick-start notes, license, and third-party notices.

+
+
+ Recommended package + ProcessBusInsight-v1.2.0-public-beta-win-x64-portable.zip + Open Releases +
+
+ +
+

FAQ

+

Clear boundaries, no overclaim.

+
+ Is this a replacement for a certified relay test set? +

No. It is a visibility and troubleshooting tool. It helps screen and explain process-bus behavior, but certified timing/metrology proof still needs validated test equipment.

+
+
+ Does it send IEC 61850 control traffic? +

No. It is receive-only and raw-passive. The product boundary is intentionally safe for observation workflows.

+
+
+ Why is Npcap required? +

The app needs raw Ethernet frame capture to observe SV, GOOSE, PTP, VLAN, APPID, MAC, and related fields from the network.

+
+
+ Can I contribute? +

Yes. Decoder tests, SCL examples, validation scenarios, documentation, and UI clarity improvements are valuable contributions.

+
+
+ + + diff --git a/docs/robots.txt b/docs/robots.txt new file mode 100644 index 0000000..d15eedb --- /dev/null +++ b/docs/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://masarray.github.io/DigSubAnalyzer/sitemap.xml diff --git a/docs/site.webmanifest b/docs/site.webmanifest new file mode 100644 index 0000000..2096353 --- /dev/null +++ b/docs/site.webmanifest @@ -0,0 +1,9 @@ +{ + "name": "Process Bus Insight", + "short_name": "Process Bus Insight", + "description": "IEC 61850 Process Bus analyzer for SV, GOOSE, PTP, SCL validation, and FAT/SAT evidence.", + "start_url": "/DigSubAnalyzer/", + "display": "standalone", + "background_color": "#f4f7fb", + "theme_color": "#0b1d33" +} diff --git a/docs/sitemap.xml b/docs/sitemap.xml new file mode 100644 index 0000000..a2a5716 --- /dev/null +++ b/docs/sitemap.xml @@ -0,0 +1,8 @@ + + + + https://masarray.github.io/DigSubAnalyzer/ + weekly + 1.0 + + diff --git a/docs/styles.css b/docs/styles.css index 3af06b1..9d42b78 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -1,1522 +1,190 @@ -:root { - --bg: #f4f8fc; - --surface: rgba(255, 255, 255, 0.82); - --surface-solid: #ffffff; - --ink: #07111d; - --muted: #64778a; - --line: rgba(25, 50, 76, 0.12); - --accent: #2b9cff; - --accent-strong: #1d8fff; - --accent-soft: #75d7ff; - --blue-deep: #0c4f8c; - --green: #0c4f8c; - --navy: #07111d; - --panel: #0c1b2b; - --cyan: #62d8ff; - --warning: #f0b533; - --danger: #ff3b4f; - --shadow: 0 30px 80px rgba(7, 17, 29, 0.13); - --soft-shadow: 0 16px 40px rgba(7, 17, 29, 0.09); +:root { + --bg: #f4f7fb; + --panel: #ffffff; + --ink: #071525; + --muted: #5c6f83; + --line: rgba(7, 21, 37, 0.11); + --accent: #146ef5; + --accent-2: #0db4d7; + --navy: #0a1d33; + --soft: #eaf2ff; + --shadow: 0 24px 70px rgba(9, 27, 46, 0.14); + --shadow-soft: 0 14px 40px rgba(9, 27, 46, 0.09); --radius-xl: 34px; --radius-lg: 24px; --radius-md: 18px; --max: 1180px; } -* { - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - +* { box-sizing: border-box; } +html { scroll-behavior: smooth; } body { margin: 0; min-width: 320px; - background: - radial-gradient(circle at 15% 2%, rgba(43, 156, 255, 0.18), transparent 30%), - radial-gradient(circle at 92% 24%, rgba(98, 216, 255, 0.16), transparent 26%), - linear-gradient(180deg, #f4f8fc 0%, #ffffff 48%, #eef5fb 100%); color: var(--ink); - font-family: "Plus Jakarta Sans", Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-weight: 400; + font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + background: + radial-gradient(circle at 8% 0%, rgba(20, 110, 245, 0.14), transparent 30%), + radial-gradient(circle at 92% 12%, rgba(13, 180, 215, 0.13), transparent 28%), + linear-gradient(180deg, #f6f9fd 0%, #ffffff 42%, #eef5fb 100%); text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; - overflow-x: hidden; } -b, -strong { - font-weight: 600; -} +a { color: inherit; text-decoration: none; } +img { max-width: 100%; display: block; } -body::before { - content: ""; - position: fixed; - inset: 0; - z-index: -1; - pointer-events: none; - background: - linear-gradient(rgba(7, 17, 29, 0.025) 1px, transparent 1px), - linear-gradient(90deg, rgba(7, 17, 29, 0.025) 1px, transparent 1px); - background-size: 42px 42px; - mask-image: linear-gradient(to bottom, rgba(0,0,0,0.9), transparent 72%); -} - -a { - color: inherit; - text-decoration: none; -} - -.page-shell { - width: min(100%, 1440px); - margin: 0 auto; - background: rgba(255, 255, 255, 0.62); - box-shadow: 0 0 90px rgba(25, 40, 24, 0.1); - overflow: hidden; -} - -.scroll-progress { - position: fixed; - z-index: 50; - top: 0; - left: 0; - width: 0%; - height: 3px; - background: linear-gradient(90deg, var(--accent), var(--cyan)); - box-shadow: 0 0 16px rgba(43, 156, 255, 0.72); -} - -.material-symbols-rounded { - font-family: "Material Symbols Rounded"; - font-weight: normal; - font-style: normal; - font-size: 1em; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-flex; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: "liga"; - -webkit-font-smoothing: antialiased; - font-variation-settings: - "FILL" 0, - "wght" 500, - "GRAD" 0, - "opsz" 24; -} - -.site-header { +.topbar { position: sticky; - z-index: 40; top: 18px; - width: min(calc(100% - 56px), var(--max)); - height: 68px; - margin: 28px auto 0; - padding: 0 14px 0 18px; + z-index: 20; + width: min(calc(100% - 36px), var(--max)); + margin: 18px auto 0; + height: 64px; + padding: 0 12px 0 18px; display: grid; - grid-template-columns: 1fr auto 1fr; + grid-template-columns: 1fr auto auto; + gap: 18px; align-items: center; border: 1px solid var(--line); border-radius: 999px; - background: rgba(255, 255, 255, 0.72); + background: rgba(255, 255, 255, 0.78); backdrop-filter: blur(18px); - box-shadow: var(--soft-shadow); - transition: transform 280ms ease, background 280ms ease, box-shadow 280ms ease; - overflow: hidden; -} - -.site-header::before { - content: ""; - position: absolute; - inset: 0; - z-index: -1; - background: linear-gradient(120deg, transparent 0%, rgba(43, 156, 255, 0.12) 42%, transparent 70%); - transform: translateX(-80%); - transition: transform 900ms cubic-bezier(.2,.8,.2,1); -} - -.site-header:hover::before { - transform: translateX(80%); -} - -.site-header.is-compact { - transform: translateY(-8px); - background: rgba(255, 255, 255, 0.88); - box-shadow: 0 18px 60px rgba(39, 61, 38, 0.14); -} - -.brand { - display: inline-flex; - align-items: center; - gap: 10px; - font-weight: 600; - letter-spacing: -0.025em; + box-shadow: var(--shadow-soft); } +.brand { display: inline-flex; align-items: center; gap: 10px; font-weight: 700; letter-spacing: -0.035em; } .brand-mark { - width: 28px; - height: 28px; - border-radius: 9px; - display: inline-grid; - place-items: center; - background: linear-gradient(135deg, var(--accent), var(--cyan)); - box-shadow: inset 0 -8px 16px rgba(7, 17, 29, 0.2), 0 8px 20px rgba(43, 156, 255, 0.28); + width: 30px; + height: 30px; + border-radius: 11px; + background: linear-gradient(135deg, var(--accent), var(--accent-2)); + box-shadow: inset 0 -8px 16px rgba(0, 0, 0, 0.18), 0 10px 22px rgba(20, 110, 245, 0.26); } -.brand-mark span { - width: 12px; - height: 12px; - background: var(--green); - clip-path: polygon(50% 0, 100% 30%, 100% 76%, 50% 100%, 0 76%, 0 30%); -} +.topbar nav { display: flex; gap: 4px; padding: 5px; border: 1px solid var(--line); border-radius: 999px; background: rgba(244,247,251,.72); } +.topbar nav a { padding: 9px 14px; border-radius: 999px; color: var(--muted); font-size: 13px; font-weight: 600; transition: 180ms ease; } +.topbar nav a:hover { color: var(--ink); background: #fff; box-shadow: 0 10px 24px rgba(9, 27, 46, 0.08); } -.nav-links { - display: flex; - align-items: center; - gap: 6px; - padding: 6px; - border: 1px solid var(--line); - border-radius: 999px; - background: rgba(246, 249, 241, 0.78); -} - -.nav-links a { - padding: 9px 16px 10px; - border-radius: 999px; - color: #53697d; - font-size: 13px; - font-weight: 500; - letter-spacing: -0.012em; - transition: color 220ms ease, background 220ms ease, transform 220ms ease; -} - -.nav-links a:hover, -.nav-links a.is-active { - color: var(--ink); - background: var(--accent); - transform: translateY(-1px); -} - -.header-cta, -.button, -.mini-button { - justify-self: end; +.button { display: inline-flex; align-items: center; justify-content: center; - border: 0; - cursor: pointer; - font: inherit; - font-weight: 600; - letter-spacing: -0.018em; - transition: transform 220ms ease, box-shadow 220ms ease, background 220ms ease; - position: relative; - overflow: hidden; -} - -.header-cta::after, -.button::after, -.mini-button::after { - content: ""; - position: absolute; - inset: 0; - background: linear-gradient(110deg, transparent 0%, rgba(255,255,255,0.44) 42%, transparent 64%); - transform: translateX(-120%); - transition: transform 720ms cubic-bezier(.2,.8,.2,1); -} - -.header-cta:hover::after, -.button:hover::after, -.mini-button:hover::after { - transform: translateX(120%); -} - -.header-cta { - padding: 12px 20px; + min-height: 44px; + padding: 0 18px; border-radius: 999px; - background: var(--accent); - color: var(--ink); - box-shadow: 0 12px 28px rgba(43, 156, 255, 0.28); -} - -.header-cta:hover, -.button:hover, -.mini-button:hover { - transform: translateY(-3px); + font-weight: 700; + font-size: 14px; + letter-spacing: -0.025em; + border: 1px solid transparent; + transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease; } +.button:hover { transform: translateY(-2px); } +.button.primary { color: #fff; background: linear-gradient(135deg, var(--accent), var(--accent-2)); box-shadow: 0 18px 36px rgba(20, 110, 245, 0.24); } +.button.secondary { background: #fff; border-color: var(--line); box-shadow: var(--shadow-soft); } +.button.ghost { background: rgba(255,255,255,.64); border-color: var(--line); } -.section-grid { - width: min(calc(100% - 72px), var(--max)); - margin: 0 auto; +main { width: min(calc(100% - 36px), var(--max)); margin: 0 auto; } +.hero { + min-height: 720px; display: grid; - grid-template-columns: minmax(0, 1.05fr) minmax(360px, 0.95fr); - gap: 56px; + grid-template-columns: minmax(0, .92fr) minmax(0, 1.08fr); + gap: 46px; align-items: center; + padding: 64px 0 38px; } - -.hero { - min-height: 660px; - padding: 54px 0 28px; -} - -.hero-copy h1, -.section-centered h2, -.workflow h2, -.cta-content h2, -.testimonial-heading h2 { - margin: 0; - color: var(--ink); - font-size: clamp(32px, 3.2vw, 46px); - line-height: 1.12; - letter-spacing: -0.045em; - font-weight: 500; - text-wrap: balance; -} - -.hero-lead, -.section-lead, -.workflow-copy p, -.cta-content p { - color: var(--muted); - font-size: 15.5px; - line-height: 1.78; - letter-spacing: -0.01em; -} - -.hero-copy { - max-width: 590px; -} - -.hero-lead { - max-width: 520px; - margin-top: 22px; -} - -.eyebrow, -.pill { +.eyebrow, .section-kicker { + margin: 0 0 16px; display: inline-flex; align-items: center; - gap: 8px; - width: fit-content; - margin: 0 0 18px; - padding: 8px 13px; - border: 1px solid var(--line); + padding: 8px 12px; border-radius: 999px; - background: rgba(255, 255, 255, 0.72); - color: var(--green); + border: 1px solid rgba(20, 110, 245, 0.18); + background: rgba(20, 110, 245, 0.08); + color: #1556b7; font-size: 12px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.075em; -} - -.eyebrow::before, -.pill::before { - content: ""; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--accent-strong); - box-shadow: 0 0 0 6px rgba(43, 156, 255, 0.14); -} - -.hero-actions { - display: flex; - flex-wrap: wrap; - gap: 14px; - margin-top: 30px; -} - -.hero-actions.center { - justify-content: center; -} - -.button { - min-height: 52px; - padding: 0 24px; - border-radius: 999px; - font-size: 14.5px; -} - -.button-primary { - color: #fff; - background: linear-gradient(135deg, var(--accent), #0f6fca); - box-shadow: 0 18px 42px rgba(43, 156, 255, 0.32); -} - -.button-ghost { - color: var(--green); - background: rgba(255, 255, 255, 0.72); + font-weight: 700; + letter-spacing: .01em; +} +h1, h2, h3 { font-family: "Plus Jakarta Sans", Inter, sans-serif; letter-spacing: -0.055em; line-height: 1.05; } +h1 { margin: 0; max-width: 760px; font-size: clamp(42px, 6.5vw, 78px); font-weight: 700; } +h2 { margin: 0; max-width: 820px; font-size: clamp(32px, 4.2vw, 52px); font-weight: 700; } +h3 { margin: 0 0 10px; font-size: 20px; font-weight: 700; } +.lead { margin: 24px 0 0; max-width: 650px; color: var(--muted); font-size: 18px; line-height: 1.75; } +.actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; } +.hero-points { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 26px; } +.hero-points span { padding: 9px 12px; border: 1px solid var(--line); border-radius: 999px; background: rgba(255,255,255,.72); color: #32475c; font-size: 13px; font-weight: 600; } + +.hero-card, .workflow-preview, .download-card, .feature-card, .strip article, details, figure { border: 1px solid var(--line); -} - -.hero-visual, -.sphere-scene, -.cta-background { - will-change: transform; -} - -.product-window { - position: relative; - min-height: 560px; - padding: 12px; - border-radius: var(--radius-xl); - background: - linear-gradient(145deg, rgba(12, 27, 43, 0.98), rgba(7, 17, 29, 0.98)), - radial-gradient(circle at 50% 20%, rgba(43, 156, 255, 0.28), transparent 30%); - box-shadow: var(--shadow); - overflow: visible; - border: 1px solid rgba(43, 156, 255, 0.22); - transition: transform 420ms cubic-bezier(.2,.8,.2,1), box-shadow 420ms ease, border-color 420ms ease; -} - -.product-window::before { - content: ""; - position: absolute; - inset: -26px; - z-index: -1; - border-radius: 46px; - background: - radial-gradient(circle at 18% 20%, rgba(43, 156, 255, 0.28), transparent 26%), - radial-gradient(circle at 82% 75%, rgba(98, 216, 255, 0.2), transparent 24%); - filter: blur(4px); -} - -.window-bar { - height: 38px; - display: flex; - align-items: center; - gap: 8px; - padding: 0 14px; - color: #a7b8cb; - font-size: 12px; -} - -.window-bar span { - width: 10px; - height: 10px; - border-radius: 50%; - background: #244564; -} - -.window-bar span:nth-child(1) { background: var(--danger); } -.window-bar span:nth-child(2) { background: var(--warning); } -.window-bar span:nth-child(3) { background: #37d67a; } - -.window-bar b { - margin-left: 8px; -} - -.product-window img { - display: block; - width: 100%; - min-height: 460px; - object-fit: cover; - object-position: left top; - border-radius: 24px; - border: 1px solid rgba(43, 156, 255, 0.26); - box-shadow: 0 30px 70px rgba(0, 0, 0, 0.28); -} - -.product-window:hover { - border-color: rgba(98, 216, 255, 0.42); - box-shadow: 0 38px 100px rgba(7, 17, 29, 0.18), 0 0 0 1px rgba(43, 156, 255, 0.08); -} - -.floating-metric { - position: absolute; - display: flex; - align-items: center; - gap: 12px; - padding: 14px 16px; - border-radius: 18px; - color: #eaf3ff; - background: rgba(12, 27, 43, 0.88); - border: 1px solid rgba(43, 156, 255, 0.28); - box-shadow: 0 18px 38px rgba(7, 17, 29, 0.28); - backdrop-filter: blur(16px); -} - -.floating-metric > .material-symbols-rounded { - width: 24px; - height: 24px; - font-size: 24px; - color: var(--cyan); -} - -.floating-metric b, -.floating-metric span { - display: block; -} - -.floating-metric span { - margin-top: 2px; - color: #a7b8cb; - font-size: 12px; -} - -.metric-a { - left: 28px; - bottom: -24px; -} - -.metric-b { - right: 28px; - top: 74px; -} - -.isometric-stage { - position: relative; - min-height: 560px; - border-radius: var(--radius-xl); - background: - linear-gradient(135deg, rgba(255, 255, 255, 0.7), rgba(238, 245, 251, 0.92)), - radial-gradient(circle at 50% 30%, rgba(43, 156, 255, 0.22), transparent 30%); - box-shadow: var(--shadow); - overflow: hidden; -} - -.iso-grid { - position: absolute; - inset: 42px; - transform: rotateX(58deg) rotateZ(-42deg); - transform-origin: center; - background-image: - linear-gradient(rgba(45, 109, 47, 0.08) 1px, transparent 1px), - linear-gradient(90deg, rgba(45, 109, 47, 0.08) 1px, transparent 1px); - background-size: 52px 52px; - opacity: 0.75; -} - -.iso-card { - position: absolute; - left: 50%; - top: 48%; - width: 210px; - min-height: 240px; - padding: 18px; - transform: translate(-50%, -50%) rotateX(55deg) rotateZ(-38deg); - border: 1px solid rgba(45, 109, 47, 0.12); - border-radius: 24px; - background: linear-gradient(145deg, #ffffff, #f1f7ec); - box-shadow: 0 34px 70px rgba(39, 61, 38, 0.18); -} - -.mini-title { - color: var(--green); - font-weight: 600; - font-size: 13px; -} - -.mini-screen { - height: 76px; - margin: 14px 0; - border-radius: 16px; - background: linear-gradient(135deg, #dff4ff, #ffffff); - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 8px; - padding: 12px; -} - -.mini-screen span { - border-radius: 999px; - background: linear-gradient(180deg, var(--accent-strong), var(--green)); -} - -.mini-row { - display: flex; - justify-content: space-between; - padding: 8px 0; - border-top: 1px solid rgba(45, 109, 47, 0.09); - font-size: 12px; -} - -.mini-row i { - font-style: normal; - color: var(--green); - font-weight: 600; -} - -.iso-cube, -.iso-tile, -.iso-chip, -.iso-orbit { - position: absolute; - filter: drop-shadow(0 20px 24px rgba(39, 61, 38, 0.12)); -} - -.iso-cube { - width: 82px; - height: 82px; - background: linear-gradient(135deg, var(--accent), var(--green)); - border-radius: 18px; - transform: rotateX(55deg) rotateZ(-38deg); -} - -.cube-a { - left: 22%; - top: 26%; -} - -.cube-b { - right: 16%; - bottom: 22%; - width: 64px; - height: 64px; -} - -.iso-tile { - width: 136px; - height: 82px; - display: grid; - place-items: center; - border-radius: 20px; - background: rgba(255, 255, 255, 0.82); - border: 1px solid var(--line); - color: var(--green); - font-weight: 600; - transform: rotateX(55deg) rotateZ(-38deg); -} - -.tile-a { - left: 10%; - bottom: 18%; -} - -.tile-b { - right: 10%; - top: 18%; -} - -.iso-chip { - width: 42px; - height: 42px; - border-radius: 14px; - background: var(--accent); -} - -.chip-a { - left: 15%; - top: 12%; - animation: floaty 5s ease-in-out infinite; -} - -.chip-b { - right: 22%; - top: 48%; - animation: floaty 6.5s ease-in-out infinite reverse; -} - -.iso-orbit { - left: 42%; - top: 24%; - width: 120px; - height: 120px; - border: 8px solid rgba(43, 156, 255, 0.28); - border-top-color: var(--green); - border-radius: 50%; - transform: rotateX(58deg) rotateZ(-38deg); - animation: spin 16s linear infinite; -} - -.trust-row { - width: min(calc(100% - 72px), var(--max)); - margin: 18px auto 120px; - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 18px; -} - -.trust-card { - display: flex; - gap: 16px; - padding: 22px; - border: 1px solid var(--line); - border-radius: var(--radius-md); - background: rgba(255, 255, 255, 0.68); - box-shadow: var(--soft-shadow); - transition: transform 260ms ease, box-shadow 260ms ease, border-color 260ms ease; -} - -.trust-card:hover { - transform: translateY(-6px); - border-color: rgba(43, 156, 255, 0.22); - box-shadow: 0 24px 60px rgba(7, 17, 29, 0.12); -} - -.trust-card h3, -.layer-card h3, -.feature-card h3, -.feature-preview-header h3 { - margin: 0 0 8px; - font-size: 17px; - font-weight: 600; - letter-spacing: -0.022em; - line-height: 1.28; -} - -.trust-card p, -.layer-card p, -.feature-card p { - margin: 0; - color: var(--muted); - line-height: 1.68; - font-size: 13.5px; -} - -.icon-dot { - flex: 0 0 36px; - width: 36px; - height: 36px; - border-radius: 13px; - display: grid; - place-items: center; + background: rgba(255,255,255,.82); + box-shadow: var(--shadow-soft); +} +.hero-card { border-radius: var(--radius-xl); padding: 12px; box-shadow: var(--shadow); transform: rotate(-1deg); } +.window-chrome { height: 42px; display: flex; align-items: center; gap: 8px; padding: 0 10px 8px; color: #54677b; font-size: 13px; } +.window-chrome span { width: 10px; height: 10px; border-radius: 50%; background: #d8e1eb; } +.window-chrome b { margin-left: 6px; font-weight: 700; color: var(--ink); } +.hero-card img, .workflow-preview img, figure img { border-radius: 22px; } + +.strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: -20px; } +.strip article { border-radius: var(--radius-lg); padding: 22px; } +.strip strong { display: block; margin-bottom: 8px; font-size: 17px; } +.strip span, .feature-card p, .workflow p, .download p, details p, .footer p, li span { color: var(--muted); line-height: 1.65; } + +.section { padding: 96px 0 0; } +.section > p:not(.section-kicker) { max-width: 720px; color: var(--muted); line-height: 1.7; } +.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 30px; } +.feature-card { border-radius: var(--radius-lg); padding: 24px; min-height: 210px; } +.feature-card:hover, figure:hover, .download-card:hover { transform: translateY(-3px); box-shadow: var(--shadow); transition: 180ms ease; } + +.workflow { display: grid; grid-template-columns: .92fr 1.08fr; gap: 40px; align-items: center; } +.steps { margin: 30px 0 0; padding: 0; list-style: none; display: grid; gap: 14px; } +.steps li { padding: 18px 20px; border-radius: 20px; border: 1px solid var(--line); background: rgba(255,255,255,.72); } +.steps strong { display: block; margin-bottom: 5px; } +.workflow-preview { padding: 12px; border-radius: var(--radius-xl); } + +.gallery { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 30px; } +figure { margin: 0; border-radius: var(--radius-lg); padding: 10px; } +figcaption { padding: 12px 6px 4px; color: #354b62; font-size: 14px; font-weight: 700; } + +.download { display: grid; grid-template-columns: 1fr 420px; gap: 28px; align-items: center; } +.download-card { border-radius: var(--radius-lg); padding: 24px; display: grid; gap: 14px; } +.download-card code { display: block; padding: 14px; border-radius: 14px; background: #081b30; color: #d9f6ff; overflow-x: auto; } + +.faq { padding-bottom: 90px; } +details { border-radius: 18px; padding: 18px 20px; margin-top: 12px; } +summary { cursor: pointer; font-weight: 700; } + +.footer { + width: min(calc(100% - 36px), var(--max)); + margin: 0 auto 28px; + padding: 28px; + border-radius: var(--radius-lg); + background: var(--navy); color: #fff; - background: linear-gradient(135deg, var(--accent), #0f6fca); - box-shadow: inset 0 -7px 12px rgba(7, 17, 29, 0.18), 0 12px 24px rgba(43, 156, 255, 0.22); - position: relative; -} - -.icon-dot::after { - content: none; -} - -.icon-dot .material-symbols-rounded { - font-size: 21px; -} - -.section-centered { - width: min(calc(100% - 72px), var(--max)); - margin: 0 auto 140px; - text-align: center; -} - -.section-centered, -.workflow, -.testimonials { - position: relative; -} - -.section-centered::before, -.workflow::before { - content: ""; - position: absolute; - inset: -54px -24px auto; - height: 220px; - z-index: -1; - background: radial-gradient(circle at 50% 0%, rgba(43, 156, 255, 0.09), transparent 62%); - pointer-events: none; -} - -.section-centered .pill { - margin-left: auto; - margin-right: auto; -} - -.section-centered h2, -.workflow h2, -.cta-content h2, -.testimonial-heading h2 { - font-size: clamp(29px, 3vw, 44px); - line-height: 1.16; - letter-spacing: -0.04em; - font-weight: 500; -} - -.section-lead { - max-width: 660px; - margin: 18px auto 48px; -} - -.layer-stack { - display: grid; - grid-template-columns: repeat(4, 1fr); - align-items: stretch; - min-height: 300px; - margin-top: 36px; -} - -.layer-card { - padding: 34px 26px; - border: 1px solid var(--line); - background: rgba(255, 255, 255, 0.6); - text-align: center; - display: flex; - flex-direction: column; - justify-content: center; - position: relative; - transition: transform 260ms ease, box-shadow 260ms ease, background 260ms ease; -} - -.layer-card:not(.layer-card-active):hover { - transform: translateY(-8px); - background: rgba(255, 255, 255, 0.84); - box-shadow: var(--soft-shadow); -} - -.layer-card:first-child { - border-radius: 28px 0 0 28px; -} - -.layer-card:last-child { - border-radius: 0 28px 28px 0; -} - -.layer-card span { - color: var(--accent-strong); - font-weight: 600; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; -} - -.layer-card span .material-symbols-rounded { - font-size: 18px; -} - -.layer-card-active { - z-index: 2; - margin: -36px -8px; - border-radius: 30px; - color: #eaf3ff; - background: - radial-gradient(circle at 20% 15%, rgba(117, 215, 255, 0.35), transparent 26%), - linear-gradient(160deg, #0e2032, #0c4f8c 62%, #2b9cff); - box-shadow: 0 38px 80px rgba(43, 156, 255, 0.28); - overflow: hidden; -} - -.layer-card-active::after { - content: ""; - position: absolute; - inset: -40% -20%; - background: linear-gradient(120deg, transparent, rgba(255,255,255,0.18), transparent); - transform: translateX(-65%) rotate(8deg); - animation: premiumSweep 5.2s cubic-bezier(.2,.8,.2,1) infinite; - pointer-events: none; -} - -.layer-card-active > * { - position: relative; - z-index: 1; -} - -.layer-card-active p { - color: rgba(234, 243, 255, 0.78); -} - -.mini-button { - width: fit-content; - margin: 24px auto 0; - padding: 11px 17px; - border-radius: 999px; - background: #fff; - color: var(--green); -} - -.workflow { - grid-template-columns: minmax(310px, 0.78fr) minmax(320px, 0.92fr) minmax(220px, 0.5fr); - margin-bottom: 140px; -} - -.workflow-copy { - max-width: 420px; -} - -.workflow.reverse { - align-items: center; -} - -.check-list { - margin: 24px 0 0; - padding: 0; - list-style: none; - display: grid; - gap: 12px; -} - -.check-list li { - position: relative; - padding-left: 30px; - color: #53697d; - line-height: 1.5; -} - -.check-list li::before { - content: ""; - position: absolute; - left: 0; - top: 3px; - width: 18px; - height: 18px; - border-radius: 7px; - background: var(--accent); - box-shadow: inset 0 -4px 8px rgba(7, 17, 29, 0.16); -} - -.sphere-scene { - min-height: 460px; - display: grid; - place-items: center; - border-radius: var(--radius-xl); - background: linear-gradient(150deg, rgba(255, 255, 255, 0.78), rgba(231, 241, 250, 0.88)); - box-shadow: var(--shadow); - overflow: hidden; - border: 1px solid rgba(25, 50, 76, 0.1); -} - -.scene-platform { - position: relative; - width: min(86%, 380px); - aspect-ratio: 1; - display: grid; - place-items: center; - transform: rotateX(58deg) rotateZ(-38deg); -} - -.screenshot-stack { - position: relative; - width: min(92%, 560px); - aspect-ratio: 1.12; -} - -.shot { - position: absolute; - display: block; - width: 88%; - border-radius: 22px; - border: 1px solid rgba(43, 156, 255, 0.22); - box-shadow: 0 32px 70px rgba(7, 17, 29, 0.2); -} - -.shot-main { - right: 0; - top: 18%; - z-index: 2; -} - -.shot-back { - left: 0; - top: 0; - opacity: 0.72; - transform: scale(0.88) rotate(-3deg); - filter: saturate(0.85); -} - -.scene-sphere { - width: 130px; - height: 130px; - border-radius: 50%; - background: radial-gradient(circle at 32% 26%, #e5f7ff, var(--accent), var(--green)); - box-shadow: 0 36px 70px rgba(43, 156, 255, 0.34); - transform: rotateZ(38deg) rotateX(-58deg); - animation: floaty 5s ease-in-out infinite; -} - -.scene-ring { - position: absolute; - inset: 48px; - border: 12px solid rgba(43, 156, 255, 0.26); - border-top-color: var(--green); - border-radius: 50%; - animation: spin 18s linear infinite; -} - -.scene-panel { - position: absolute; - padding: 13px 18px; - border-radius: 14px; - background: rgba(255, 255, 255, 0.8); - color: var(--green); - font-weight: 600; - letter-spacing: -0.016em; - display: inline-flex; - align-items: center; - gap: 8px; -} - -.scene-panel .material-symbols-rounded { - color: var(--accent); - font-size: 20px; -} - -.panel-left { - left: 0; - top: 42%; -} - -.panel-right { - right: 0; - bottom: 20%; -} - -.audience-list { - display: grid; - gap: 12px; -} - -.audience-item { - min-height: 58px; - padding: 0 18px; - border: 1px solid var(--line); - border-radius: 18px; - background: rgba(255, 255, 255, 0.66); - color: #53697d; - text-align: left; - font: inherit; - font-weight: 500; - letter-spacing: -0.016em; - cursor: pointer; - transition: transform 220ms ease, background 220ms ease, color 220ms ease; -} - -.audience-item:hover, -.audience-item.is-selected { - transform: translateX(6px); - color: var(--ink); - background: var(--accent); -} - -.feature-grid { - display: grid; - grid-template-columns: minmax(0, 1.15fr) minmax(360px, 0.85fr); - gap: 22px; - overflow: visible; - border: 1px solid var(--line); - border-radius: 34px; - background: - radial-gradient(circle at 18% 12%, rgba(43, 156, 255, 0.1), transparent 28%), - linear-gradient(145deg, rgba(255,255,255,0.94), rgba(240,247,253,0.78)); - box-shadow: var(--shadow); - text-align: left; - position: relative; - padding: 22px; -} - -.feature-grid::before { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - border-radius: inherit; - background: linear-gradient(135deg, rgba(43,156,255,0.1), transparent 28%, transparent 72%, rgba(98,216,255,0.1)); -} - -.feature-preview { - position: relative; - z-index: 1; - min-height: 520px; - padding: 24px; - border-radius: 26px; - background: #fff; - border: 1px solid rgba(25, 50, 76, 0.1); - box-shadow: 0 24px 70px rgba(7, 17, 29, 0.1); - display: flex; - flex-direction: column; - gap: 22px; -} - -.feature-preview-header { - display: flex; - align-items: center; - gap: 14px; -} - -.feature-preview-header h3 { - margin: 0 0 4px; - font-size: 20px; - letter-spacing: -0.026em; - font-weight: 500; -} - -.feature-preview-header p { - margin: 0; - color: var(--muted); - line-height: 1.5; -} - -.feature-card-list { - position: relative; - z-index: 1; - display: grid; - grid-template-columns: 1fr 1fr; - gap: 16px; -} - -.feature-card { - min-height: 234px; - padding: 24px; - border-radius: 24px; - border: 1px solid rgba(25, 50, 76, 0.1); - background: rgba(255, 255, 255, 0.82); - display: flex; - flex-direction: column; - justify-content: flex-start; - gap: 16px; - transform-style: preserve-3d; - transition: transform 280ms cubic-bezier(.2,.8,.2,1), box-shadow 280ms ease, background 280ms ease; -} - -.feature-card:hover { - background: #fff; - box-shadow: 0 20px 50px rgba(7, 17, 29, 0.1), inset 0 0 0 1px rgba(43, 156, 255, 0.12); -} - -.feature-icon { - width: 42px; - height: 42px; - display: grid; - place-items: center; - border-radius: 15px; - color: #fff; - background: linear-gradient(135deg, var(--accent), #0f6fca); - box-shadow: 0 14px 30px rgba(43, 156, 255, 0.2); -} - -.feature-icon .material-symbols-rounded { - font-size: 23px; -} - -.feature-shot { - display: block; - width: 100%; - max-height: 100%; - object-fit: cover; - object-position: left top; - border-radius: 20px; - border: 1px solid rgba(25, 50, 76, 0.12); - box-shadow: 0 18px 42px rgba(7, 17, 29, 0.13); - margin: 0; - transition: transform 380ms cubic-bezier(.2,.8,.2,1), filter 380ms ease; -} - -.feature-preview:hover .feature-shot, -.feature-card:hover .feature-shot { - transform: scale(1.025); - filter: saturate(1.08) contrast(1.02); -} - -.feature-shot.small { - margin-top: 22px; - margin-bottom: 0; - max-height: 150px; -} - -.feature-shot.hero-shot { - flex: 1; - min-height: 390px; -} - -.report-mock { - padding: 22px; - border-radius: 20px; - background: linear-gradient(180deg, #f7faef, #ffffff); - border: 1px solid var(--line); - display: grid; - gap: 10px; -} - -.report-mock span, -.mini-bars span, -.flow-line span, -.paper-card span { - display: block; - height: 10px; - border-radius: 999px; - background: linear-gradient(90deg, var(--accent), rgba(43, 156, 255, 0.15)); -} - -.mini-bars { - display: flex; - align-items: end; - gap: 8px; - height: 90px; -} - -.mini-bars span { - width: 32px; -} - -.mini-bars span:nth-child(1) { height: 28px; } -.mini-bars span:nth-child(2) { height: 56px; } -.mini-bars span:nth-child(3) { height: 78px; } -.mini-bars span:nth-child(4) { height: 44px; } - -.flow-line { - display: flex; - align-items: center; - gap: 24px; -} - -.flow-line span { - width: 62px; - height: 26px; - border-radius: 13px; -} - -.evidence-orb { - min-height: 260px; - display: grid; - place-items: center; - background: radial-gradient(circle, rgba(43, 156, 255, 0.2), transparent 65%); -} - -.paper-card { - width: 220px; - padding: 22px; - border-radius: 18px; - background: #fff; - box-shadow: var(--shadow); - display: grid; - gap: 10px; -} - -.paper-card b { - margin-bottom: 10px; -} - -.cta-scene { - position: relative; - width: min(calc(100% - 72px), var(--max)); - min-height: 520px; - margin: 0 auto 140px; - display: grid; - place-items: center; - border-radius: 36px; - overflow: hidden; - background: linear-gradient(145deg, #e8f2fb, #ffffff); - box-shadow: var(--shadow); - border: 1px solid rgba(25, 50, 76, 0.1); -} - -.cta-background { - position: absolute; - inset: 0; - background: - radial-gradient(circle at 20% 60%, rgba(43, 156, 255, 0.2), transparent 22%), - radial-gradient(circle at 82% 28%, rgba(98, 216, 255, 0.18), transparent 20%), - linear-gradient(135deg, rgba(255,255,255,0), rgba(255,255,255,0.7)); -} - -.cta-background::before, -.cta-background::after { - content: ""; - position: absolute; - width: 220px; - height: 220px; - border-radius: 34px; - background: rgba(255, 255, 255, 0.72); - border: 1px solid var(--line); - transform: rotateX(60deg) rotateZ(-42deg); -} - -.cta-background::before { - left: 12%; - bottom: 8%; -} - -.cta-background::after { - right: 12%; - top: 10%; -} - -.cta-content { - position: relative; - z-index: 2; - max-width: 680px; - text-align: center; - padding: 32px; -} - -.cta-content .pill { - margin-left: auto; - margin-right: auto; -} - -.testimonials { - width: min(calc(100% - 72px), var(--max)); - margin: 0 auto 110px; - display: grid; - grid-template-columns: 0.8fr 1fr 1fr; - gap: 42px; - align-items: start; -} - -.quote-card { - padding: 30px; - border-radius: 26px; - background: rgba(255, 255, 255, 0.76); - border: 1px solid var(--line); - box-shadow: var(--soft-shadow); - transition: transform 260ms ease, box-shadow 260ms ease, border-color 260ms ease; -} - -.quote-card:hover { - transform: translateY(-6px); - border-color: rgba(43, 156, 255, 0.18); - box-shadow: 0 24px 60px rgba(7, 17, 29, 0.12); -} - -.quote-mark { - display: block; - color: var(--accent-strong); - font-size: 62px; - font-weight: 600; - line-height: 0.7; -} - -.quote-card p { - color: #53697d; - line-height: 1.7; - letter-spacing: -0.006em; -} - -.person b { - font-weight: 600; - letter-spacing: -0.018em; -} - -.person { - display: flex; - align-items: center; - gap: 12px; - margin-top: 24px; -} - -.person span { - width: 38px; - height: 38px; - border-radius: 50%; - background: linear-gradient(135deg, var(--accent), var(--cyan)); -} - -.person small { - display: block; - color: var(--muted); - margin-top: 3px; -} - -.arrow-row { - display: flex; - gap: 10px; - margin-top: 28px; -} - -.arrow-row span { - width: 42px; - height: 42px; - border-radius: 50%; - background: #fff; - border: 1px solid var(--line); - position: relative; -} - -.arrow-row span::before { - content: ""; - position: absolute; - inset: 15px; - border-top: 2px solid var(--green); - border-left: 2px solid var(--green); - transform: rotate(-45deg); -} - -.arrow-row span:nth-child(2)::before { - transform: rotate(135deg); -} - -.site-footer { - width: min(calc(100% - 72px), var(--max)); - margin: 0 auto 42px; - padding: 44px; - border-radius: 34px; - background: #fff; - box-shadow: var(--shadow); -} - -.footer-brand { display: flex; justify-content: space-between; - align-items: center; gap: 24px; - padding-bottom: 34px; - border-bottom: 1px solid var(--line); -} - -.footer-actions { - display: flex; - gap: 12px; -} - -.footer-links { - display: grid; - grid-template-columns: repeat(2, minmax(140px, 1fr)); - gap: 20px; - max-width: 420px; - margin-top: 34px; -} - -.footer-links h4 { - margin: 0 0 12px; -} - -.footer-links a { - display: block; - color: var(--muted); - margin: 8px 0; -} - -.copyright { - margin: 34px 0 0; - color: var(--muted); - font-size: 13px; -} - -.reveal { - opacity: 0; - transform: translateY(46px) scale(0.975); - filter: blur(14px); - clip-path: inset(14% 0 0 0 round 18px); - transition: - opacity 860ms cubic-bezier(.18,.8,.18,1), - transform 860ms cubic-bezier(.18,.8,.18,1), - filter 860ms ease, - clip-path 860ms cubic-bezier(.18,.8,.18,1); -} - -.reveal.is-visible { - opacity: 1; - transform: translateY(0) scale(1); - filter: blur(0); - clip-path: inset(0 0 0 0 round 0); -} - -.reveal-delay-1 { transition-delay: 90ms; } -.reveal-delay-2 { transition-delay: 180ms; } -.reveal-delay-3 { transition-delay: 270ms; } - -.tilt-card { - transform-style: preserve-3d; - transition: transform 220ms ease, box-shadow 220ms ease; -} - -@keyframes floaty { - 0%, 100% { translate: 0 0; } - 50% { translate: 0 -16px; } -} - -@keyframes spin { - to { rotate: 360deg; } -} - -@keyframes premiumSweep { - 0%, 28% { - transform: translateX(-70%) rotate(8deg); - } - 54%, 100% { - transform: translateX(70%) rotate(8deg); - } } +.footer p { margin-bottom: 0; color: rgba(255,255,255,.72); max-width: 620px; } +.footer-links { display: flex; flex-wrap: wrap; gap: 14px; align-content: flex-start; } +.footer-links a { color: rgba(255,255,255,.78); font-weight: 600; } +.footer-links a:hover { color: #fff; } @media (max-width: 980px) { - .site-header { - grid-template-columns: 1fr auto; - } - - .nav-links { - display: none; - } - - .section-grid, - .workflow { - grid-template-columns: 1fr; - } - - .hero { - min-height: auto; - } - - .trust-row, - .layer-stack, - .feature-grid, - .testimonials { - grid-template-columns: 1fr; - } - - .layer-card, - .layer-card:first-child, - .layer-card:last-child, - .layer-card-active { - margin: 0; - border-radius: 24px; - } - - .feature-card.tall, - .feature-card.wide { - grid-row: auto; - } -} - -@media (max-width: 640px) { - .site-header, - .section-grid, - .trust-row, - .section-centered, - .cta-scene, - .testimonials, - .site-footer { - width: min(calc(100% - 32px), var(--max)); - } - - .site-header { - height: auto; - top: 10px; - margin-top: 14px; - padding: 10px; - } - - .header-cta { - display: none; - } - - .hero-copy h1 { - font-size: 38px; - } - - .isometric-stage { - min-height: 430px; - } - - .footer-brand, - .footer-actions { - align-items: flex-start; - flex-direction: column; - } + .topbar { grid-template-columns: 1fr auto; height: auto; padding: 12px; border-radius: 24px; } + .topbar nav { display: none; } + .hero, .workflow, .download { grid-template-columns: 1fr; } + .hero { min-height: 0; padding-top: 48px; } + .strip, .feature-grid { grid-template-columns: 1fr; } + .gallery { grid-template-columns: 1fr; } + h1 { font-size: clamp(38px, 11vw, 58px); } +} + +@media (max-width: 560px) { + main, .topbar, .footer { width: min(calc(100% - 22px), var(--max)); } + .button { width: 100%; } + .actions { width: 100%; } + .footer { display: block; } + .footer-links { margin-top: 20px; } } - -@media (prefers-reduced-motion: reduce) { - html { - scroll-behavior: auto; - } - - *, - *::before, - *::after { - animation-duration: 1ms !important; - animation-iteration-count: 1 !important; - transition-duration: 1ms !important; - scroll-behavior: auto !important; - } - - .reveal { - opacity: 1; - transform: none; - filter: none; - } -} - diff --git a/index.html b/index.html new file mode 100644 index 0000000..cf77522 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + Process Bus Insight — IEC 61850 Process Bus Analyzer + + + + +

Open Process Bus Insight landing page

+ + diff --git a/scripts/publish-windows-portable.ps1 b/scripts/publish-windows-portable.ps1 new file mode 100644 index 0000000..1cf925b --- /dev/null +++ b/scripts/publish-windows-portable.ps1 @@ -0,0 +1,128 @@ +param( + [string]$Version = "1.2.0-public-beta", + [string]$Configuration = "Release", + [string]$Runtime = "win-x64", + [string]$AppName = "ProcessBusInsight", + [string]$ProjectPath = "src/ProcessBus.App.Wpf/ProcessBus.App.Wpf.csproj", + [string]$OutputRoot = "artifacts", + [switch]$FrameworkDependent +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +Set-Location $repoRoot + +if ([string]::IsNullOrWhiteSpace($Version)) { + throw "Version cannot be empty." +} + +$portableName = "$AppName-v$Version-$Runtime-portable" +$publishDir = Join-Path $repoRoot "$OutputRoot/publish/$portableName/app" +$stageDir = Join-Path $repoRoot "$OutputRoot/package/$portableName" +$releaseDir = Join-Path $repoRoot "$OutputRoot/release" +$zipPath = Join-Path $releaseDir "$portableName.zip" +$shaPath = Join-Path $releaseDir "SHA256SUMS.txt" +$selfContained = -not $FrameworkDependent.IsPresent + +Write-Host "Publishing $AppName $Version for $Runtime" +Write-Host "Self-contained: $selfContained" + +Remove-Item -LiteralPath $publishDir -Recurse -Force -ErrorAction SilentlyContinue +Remove-Item -LiteralPath $stageDir -Recurse -Force -ErrorAction SilentlyContinue +New-Item -ItemType Directory -Path $publishDir, $stageDir, $releaseDir | Out-Null + +$publishArgs = @( + "publish", $ProjectPath, + "-c", $Configuration, + "-r", $Runtime, + "-o", $publishDir, + "/p:PublishSingleFile=false", + "/p:DebugType=None", + "/p:DebugSymbols=false", + "/p:AssemblyName=$AppName" +) + +if ($selfContained) { + $publishArgs += "--self-contained" + $publishArgs += "true" +} +else { + $publishArgs += "--self-contained" + $publishArgs += "false" +} + +dotnet @publishArgs + +$appStageDir = Join-Path $stageDir "app" +New-Item -ItemType Directory -Path $appStageDir | Out-Null +Copy-Item -Path (Join-Path $publishDir "*") -Destination $appStageDir -Recurse -Force + +$exeInApp = Join-Path $appStageDir "$AppName.exe" +if (-not (Test-Path $exeInApp)) { + $fallbackExe = Get-ChildItem -Path $appStageDir -Filter "*.exe" -File | Select-Object -First 1 + if ($fallbackExe) { + Copy-Item $fallbackExe.FullName $exeInApp -Force + } +} + +$quickStart = @" +Process Bus Insight / DigSubAnalyzer +Windows portable package +Version: $Version + +1. Install Npcap on the Windows machine that will capture IEC 61850 Process Bus traffic. +2. Extract this ZIP to a local folder, for example C:\Tools\ProcessBusInsight. +3. Run app\$AppName.exe or Start-ProcessBusInsight.bat. +4. Select a real physical Ethernet adapter connected to a TAP, mirror port, or isolated test network. +5. Start capture and review SV, GOOSE, PTP, diagnostics, and SCL binding views. + +Timing note: +Normal Windows/Npcap timestamps are software based. Use timing findings as screening evidence unless the capture path is validated with hardware timestamping, TAP, or trusted timing equipment. + +Documentation: +https://github.com/masarray/DigSubAnalyzer#readme +"@ + +Set-Content -Path (Join-Path $stageDir "README_QUICK_START.txt") -Value $quickStart -Encoding UTF8 + +$launcher = @" +@echo off +setlocal +cd /d "%~dp0" +if exist "app\$AppName.exe" ( + start "Process Bus Insight" "app\$AppName.exe" +) else ( + echo Process Bus Insight executable was not found in the app folder. + pause +) +"@ +Set-Content -Path (Join-Path $stageDir "Start-ProcessBusInsight.bat") -Value $launcher -Encoding ASCII + +$copyMap = @( + @{ Source = "LICENSE"; Required = $true }, + @{ Source = "NOTICE"; Required = $false }, + @{ Source = "THIRD_PARTY_NOTICES.md"; Required = $false }, + @{ Source = "docs/QUICK_START.md"; Required = $false }, + @{ Source = "docs/TROUBLESHOOTING.md"; Required = $false } +) + +foreach ($item in $copyMap) { + $source = Join-Path $repoRoot $item.Source + if (Test-Path $source) { + Copy-Item $source -Destination $stageDir -Force + } + elseif ($item.Required) { + throw "Required package file missing: $($item.Source)" + } +} + +Remove-Item -LiteralPath $zipPath -Force -ErrorAction SilentlyContinue +Compress-Archive -Path (Join-Path $stageDir "*") -DestinationPath $zipPath -Force + +$hash = Get-FileHash -Path $zipPath -Algorithm SHA256 +$shaLine = "$($hash.Hash.ToLowerInvariant()) $(Split-Path $zipPath -Leaf)" +Set-Content -Path $shaPath -Value $shaLine -Encoding ASCII + +Write-Host "Created: $zipPath" +Write-Host "Created: $shaPath" diff --git a/scripts/set-github-about.ps1 b/scripts/set-github-about.ps1 new file mode 100644 index 0000000..8d9febc --- /dev/null +++ b/scripts/set-github-about.ps1 @@ -0,0 +1,80 @@ +param( + [string]$Owner = "masarray", + [string]$Repo = "DigSubAnalyzer", + [string]$HomepageUrl = "https://masarray.github.io/DigSubAnalyzer/", + [string]$Description = "Free open-source IEC 61850 Process Bus analyzer for SV, GOOSE, PTP, SCL validation, FAT/SAT troubleshooting, and commissioning evidence.", + [string[]]$Topics = @( + "iec61850", + "sampled-values", + "goose", + "ptp", + "digital-substation", + "substation-automation", + "process-bus", + "wpf", + "dotnet", + "windows", + "fat-sat", + "commissioning" + ), + [switch]$WhatIf +) + +$ErrorActionPreference = "Stop" + +function Assert-GhAvailable { + $gh = Get-Command gh -ErrorAction SilentlyContinue + if (-not $gh) { + throw "GitHub CLI 'gh' was not found. Install it from https://cli.github.com/ and login first." + } + + gh auth status --hostname github.com *> $null + if ($LASTEXITCODE -ne 0) { + throw "GitHub CLI is not logged in. Run: gh auth login" + } +} + +Assert-GhAvailable + +$repoSlug = "$Owner/$Repo" +$topicsCsv = ($Topics | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { $_.Trim().ToLowerInvariant() } | Select-Object -Unique) -join "," + +Write-Host "Repository About preview" +Write-Host " Repository : $repoSlug" +Write-Host " Homepage : $HomepageUrl" +Write-Host " Description: $Description" +Write-Host " Topics : $topicsCsv" +Write-Host " Issues : enabled" +Write-Host " Wiki : disabled" +Write-Host " Projects : disabled" +Write-Host " Discussions: disabled when supported by installed gh version" + +if ($WhatIf) { + Write-Host "WhatIf mode: no changes applied." + exit 0 +} + +$editArgs = @( + "repo", "edit", $repoSlug, + "--description", $Description, + "--homepage", $HomepageUrl, + "--enable-issues=true", + "--enable-wiki=false", + "--enable-projects=false" +) + +gh @editArgs +if ($LASTEXITCODE -ne 0) { throw "gh repo edit failed." } + +if (-not [string]::IsNullOrWhiteSpace($topicsCsv)) { + gh repo edit $repoSlug --add-topic $topicsCsv + if ($LASTEXITCODE -ne 0) { throw "Failed to apply repository topics." } +} + +# Some gh versions support --enable-discussions; older versions do not. +gh repo edit $repoSlug --enable-discussions=false *> $null +if ($LASTEXITCODE -ne 0) { + Write-Warning "Installed GitHub CLI version may not support --enable-discussions. Repository description, homepage, issues, wiki, projects, and topics were still applied." +} + +Write-Host "Repository About panel updated." diff --git a/scripts/verify-release-package.ps1 b/scripts/verify-release-package.ps1 new file mode 100644 index 0000000..9a418a5 --- /dev/null +++ b/scripts/verify-release-package.ps1 @@ -0,0 +1,43 @@ +param( + [Parameter(Mandatory=$true)] + [string]$PackageZip, + [string]$AppName = "ProcessBusInsight" +) + +$ErrorActionPreference = "Stop" + +if (-not (Test-Path $PackageZip)) { + throw "Package ZIP not found: $PackageZip" +} + +$temp = Join-Path ([System.IO.Path]::GetTempPath()) ("ProcessBusInsightVerify_" + [Guid]::NewGuid().ToString("N")) +New-Item -ItemType Directory -Path $temp | Out-Null + +try { + Expand-Archive -Path $PackageZip -DestinationPath $temp -Force + + $required = @( + "README_QUICK_START.txt", + "LICENSE", + "Start-ProcessBusInsight.bat", + "app/$AppName.exe" + ) + + foreach ($relative in $required) { + $path = Join-Path $temp $relative + if (-not (Test-Path $path)) { + throw "Package verification failed. Missing: $relative" + } + } + + $exe = Join-Path $temp "app/$AppName.exe" + $size = (Get-Item $exe).Length + if ($size -lt 1024) { + throw "Package verification failed. Executable looks too small: $size bytes" + } + + Write-Host "Package verification passed: $PackageZip" +} +finally { + Remove-Item -LiteralPath $temp -Recurse -Force -ErrorAction SilentlyContinue +} diff --git a/src/ProcessBus.App.Wpf/ProcessBus.App.Wpf.csproj b/src/ProcessBus.App.Wpf/ProcessBus.App.Wpf.csproj index 03e5e0d..8617232 100644 --- a/src/ProcessBus.App.Wpf/ProcessBus.App.Wpf.csproj +++ b/src/ProcessBus.App.Wpf/ProcessBus.App.Wpf.csproj @@ -1,6 +1,8 @@ WinExe + ProcessBusInsight + Process Bus Insight net8.0-windows win-x64 true