First real stable push since migrating

This commit is contained in:
2025-05-05 12:00:24 +08:00
parent e401b01d05
commit f16525e6eb
49 changed files with 3262 additions and 5 deletions

386
.editorconfig Normal file
View File

@@ -0,0 +1,386 @@
root = true
# All files
[*]
indent_style = space
# Xaml files
[*.xaml]
indent_size = 2
# MSBuild files
[*.{csproj,targets,props}]
indent_size = 2
# Xml files
[*.xml]
indent_size = 2
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
tab_width = 4
# New line preferences
end_of_line = crlf
insert_final_newline = false
#### .NET Coding Conventions ####
[*.{cs,vb}]
# Organize usings
dotnet_separate_import_directive_groups = true
dotnet_sort_system_directives_first = true
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_property = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
# Expression-level preferences
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_object_initializer = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
# Field preferences
dotnet_style_readonly_field = true:warning
# Parameter preferences
dotnet_code_quality_unused_parameters = all:suggestion
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
#### C# Coding Conventions ####
[*.cs]
# var preferences
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:suggestion
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_prefer_switch_expression = true:suggestion
# Null-checking preferences
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:warning
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
# Code-block preferences
csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion
# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = false:suggestion
#### Naming styles ####
[*.{cs,vb}]
# Naming rules
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.events_should_be_pascalcase.symbols = events
dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = _camelcase
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = _camelcase
dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
# Symbol specifications
dotnet_naming_symbols.interfaces.applicable_kinds = interface
dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interfaces.required_modifiers =
dotnet_naming_symbols.enums.applicable_kinds = enum
dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.enums.required_modifiers =
dotnet_naming_symbols.events.applicable_kinds = event
dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.events.required_modifiers =
dotnet_naming_symbols.methods.applicable_kinds = method
dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.methods.required_modifiers =
dotnet_naming_symbols.properties.applicable_kinds = property
dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.properties.required_modifiers =
dotnet_naming_symbols.public_fields.applicable_kinds = field
dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
dotnet_naming_symbols.public_fields.required_modifiers =
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_fields.required_modifiers =
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_static_fields.required_modifiers = static
dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types_and_namespaces.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
dotnet_naming_symbols.type_parameters.required_modifiers =
dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_constant_fields.required_modifiers = const
dotnet_naming_symbols.local_variables.applicable_kinds = local
dotnet_naming_symbols.local_variables.applicable_accessibilities = local
dotnet_naming_symbols.local_variables.required_modifiers =
dotnet_naming_symbols.local_constants.applicable_kinds = local
dotnet_naming_symbols.local_constants.applicable_accessibilities = local
dotnet_naming_symbols.local_constants.required_modifiers = const
dotnet_naming_symbols.parameters.applicable_kinds = parameter
dotnet_naming_symbols.parameters.applicable_accessibilities = *
dotnet_naming_symbols.parameters.required_modifiers =
dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
dotnet_naming_symbols.public_constant_fields.required_modifiers = const
dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
dotnet_naming_symbols.local_functions.required_modifiers =
# Naming styles
dotnet_naming_style.pascalcase.required_prefix =
dotnet_naming_style.pascalcase.required_suffix =
dotnet_naming_style.pascalcase.word_separator =
dotnet_naming_style.pascalcase.capitalization = pascal_case
dotnet_naming_style.ipascalcase.required_prefix = I
dotnet_naming_style.ipascalcase.required_suffix =
dotnet_naming_style.ipascalcase.word_separator =
dotnet_naming_style.ipascalcase.capitalization = pascal_case
dotnet_naming_style.tpascalcase.required_prefix = T
dotnet_naming_style.tpascalcase.required_suffix =
dotnet_naming_style.tpascalcase.word_separator =
dotnet_naming_style.tpascalcase.capitalization = pascal_case
dotnet_naming_style._camelcase.required_prefix = _
dotnet_naming_style._camelcase.required_suffix =
dotnet_naming_style._camelcase.word_separator =
dotnet_naming_style._camelcase.capitalization = camel_case
dotnet_naming_style.camelcase.required_prefix =
dotnet_naming_style.camelcase.required_suffix =
dotnet_naming_style.camelcase.word_separator =
dotnet_naming_style.camelcase.capitalization = camel_case
dotnet_naming_style.s_camelcase.required_prefix = s_
dotnet_naming_style.s_camelcase.required_suffix =
dotnet_naming_style.s_camelcase.word_separator =
dotnet_naming_style.s_camelcase.capitalization = camel_case
###############################
# Spell Check #
###############################
# https://learn.microsoft.com/visualstudio/ide/text-spell-checker
spelling_exclusion_path = .\exclusions.dic
spelling_checkable_types = strings,identifiers,comments
# Update this to match your prefered language, you will need to ensure you have the language pack installed for the language
spelling_languages = en-us
# This is only the level that spelling errors occur in the Error List, it is not actually a compile-time warning/error
spelling_error_severity = information

16
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "nuget" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

88
.github/workflows/build_app.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: Build LD_SysInfo
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
defaults:
run:
shell: pwsh
env:
version: "1.0.${{ github.run_number }}"
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Set up .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.x'
- name: dotnet build
run: dotnet build --configuration Release -p:Version="${{ env.version }}"
- name: dotnet test
run: dotnet test --configuration Release --no-build --collect:"XPlat Code Coverage" --results-directory ${{github.workspace}}/.build/coverage
- name: ReportGenerator
uses: danielpalme/ReportGenerator-GitHub-Action@5.4.5
with:
reports: ${{github.workspace}}/.build/coverage/**/coverage.cobertura.xml
targetdir: ${{github.workspace}}/.build/coveragereport/
reporttypes: Html;MarkdownSummaryGithub
title: 'Code Coverage'
- name: Write PR Number
if: ${{ github.event_name == 'pull_request' }}
run: |
New-Item -Type File -Value "${{ github.event.number }}" -Force -Path "${{github.workspace}}/.build/coveragereport/PullRequestNumber"
- name: Upload Code Coverage Report
uses: actions/upload-artifact@v4
with:
name: CodeCoverage
path: ${{github.workspace}}/.build/coveragereport/
if-no-files-found: error
- name: dotnet publish
if: ${{ github.event_name != 'pull_request' }}
run: |
# If publishing single file, remove the --no-build below
dotnet publish --configuration Release --no-build -p:Version="${{ env.version }}" -p:PublishDir=${{github.workspace}}/.build/publish
- name: Upload artifact for deployment job
if: ${{ github.event_name != 'pull_request' }}
uses: actions/upload-artifact@v4
with:
name: app
path: ${{github.workspace}}/.build/publish
# This will automatically mark PRs from dependabot with auto merge.
# This allows them to automatically complete if they pass the rest of the CI
auto-merge:
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
steps:
- uses: fastify/github-action-merge-dependabot@v3.11.1
with:
# For GitHub Auto Merge to work it must be enabled on the repo.
# This can be done with the script here:
# https://github.com/Keboo/DotnetTemplates/blob/main/SetupRepo.ps1
# See documentation here:
# https://docs.github.com/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request
use-github-auto-merge: true

View File

@@ -0,0 +1,42 @@
name: Code Coverage PR Comment
on:
workflow_run:
workflows: [Build LD_SysInfo]
types:
- completed
defaults:
run:
shell: pwsh
# This is required by marocchino/sticky-pull-request-comment
# You must enable read/write permissions to allow workflows to create comments on the PR.
# On your repo navigate to: Settings > Actions > General > Workflow permissions, and make sure to enable read and write permissions.
# https://github.com/marocchino/sticky-pull-request-comment#error-resource-not-accessible-by-integration
permissions:
pull-requests: write
jobs:
post-code-coverage:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: Download artifacts
run: gh run download ${{ github.event.workflow_run.id }} -n CodeCoverage --repo ${{ github.repository }}
env:
GH_TOKEN: ${{ github.token }}
- name: 'Get PR Number'
id: get-pr-number
run: |
$pr_number = (cat PullRequestNumber)
"pr_number=$pr_number" >> $env:GITHUB_OUTPUT
- uses: marocchino/sticky-pull-request-comment@v2
with:
recreate: true
number: ${{ steps.get-pr-number.outputs.pr_number }}
path: SummaryGithub.md

127
.gitignore vendored
View File

@@ -1,7 +1,7 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
@@ -29,7 +29,6 @@ x86/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
@@ -58,11 +57,14 @@ dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
# .NET
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
@@ -91,6 +93,7 @@ StyleCopReport.xml
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
@@ -240,6 +243,8 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
PublishDir/
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
@@ -294,6 +299,17 @@ node_modules/
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
@@ -350,6 +366,9 @@ ASALocalRun/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
@@ -360,4 +379,104 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# Ignore live unit testing config
*.lutconfig

32
Directory.Build.props Normal file
View File

@@ -0,0 +1,32 @@
<!--
This file allow for customizing your build process.
See: https://learn.microsoft.com/visualstudio/msbuild/customize-your-build
-->
<Project>
<!--
Uncomment if you need to enable inclusion of another Directory.Build.props file from a parent directory
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
-->
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
<!--
If you you like to see source generated files saved to disk you can enable the following:
https://learn.microsoft.com/dotnet/csharp/roslyn-sdk/source-generators-overview?WT.mc_id=DT-MVP-5003472
-->
<!--<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>-->
</PropertyGroup>
<!--
This allows all projects to share the same user secrets file.
If you want project to have their own, set it to a different GUID on each project.
See: https://learn.microsoft.com/dotnet/architecture/microservices/secure-net-microservices-web-applications/developer-app-secrets-storage
-->
<PropertyGroup Label="User Secrets">
<UserSecretsId>1ef91ee6-7b55-474e-ab2b-2d164b723a56</UserSecretsId>
</PropertyGroup>
</Project>

10
Directory.Build.targets Normal file
View File

@@ -0,0 +1,10 @@
<!--
This file allow for customizing your build process.
See: https://learn.microsoft.com/visualstudio/msbuild/customize-your-build
-->
<Project>
<!--
Uncomment if you need to enable inclusion of another Directory.Build.targets file from a parent directory
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />
-->
</Project>

45
Directory.Packages.props Normal file
View File

@@ -0,0 +1,45 @@
<!--
This enabled central package management.
This allows for controling all NuGet packages within the Directory.Packages.props file
See https://learn.microsoft.com/nuget/consume-packages/Central-Package-Management?WT.mc_id=DT-MVP-5003472
-->
<Project>
<!--
Uncomment if you need to enable inclusion of another Directory.Packages.props file from a parent directory
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Packages.props, $(MSBuildThisFileDirectory)..))" />
-->
<!-- This property enables the Central Package Management feature -->
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<!-- https://learn.microsoft.com/nuget/consume-packages/Central-Package-Management?WT.mc_id=DT-MVP-5003472#transitive-pinning -->
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<!--
This defines the set of centrally managed packages.
This would typically list all NuGet packages used within this solution.
-->
<ItemGroup>
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
<PackageVersion Include="Hardcodet.NotifyIcon.Wpf" Version="2.0.1" />
<PackageVersion Include="MaterialDesignColors" Version="5.2.2-ci976" />
<PackageVersion Include="MaterialDesignThemes" Version="5.2.2-ci976" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="System.Management" Version="10.0.0-preview.3.25171.5" />
<PackageVersion Include="WixToolset.UI.wixext" Version="6.0.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Moq.AutoMock" Version="3.5.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.3" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
</ItemGroup>
</Project>

115
LD-SysInfo/App.xaml Normal file
View File

@@ -0,0 +1,115 @@
<Application x:Class="LD_SysInfo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Set to Dark Theme with valid colors -->
<materialDesign:BundledTheme BaseTheme="Dark"
PrimaryColor="BlueGrey"
SecondaryColor="Indigo" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Custom color overrides -->
<SolidColorBrush x:Key="PrimaryBrush" Color="#4776cd" />
<SolidColorBrush x:Key="BackgroundDarkBrush" Color="#333333" />
<SolidColorBrush x:Key="TextBrush" Color="#E5E4E2" />
<SolidColorBrush x:Key="TextDarkBrush" Color="#E5E4E2" />
<SolidColorBrush x:Key="ErrorBrush" Color="#A6262A" />
<SolidColorBrush x:Key="SelectedLightBrush" Color="#E5E4E2" />
<SolidColorBrush x:Key="TitleBrush" Color="#D5484D" />
<SolidColorBrush x:Key="HoverBrush" Color="#3F70CA" />
<!-- Tab colors -->
<SolidColorBrush x:Key="TabUnselectedBrush" Color="#A9A9A9" />
<SolidColorBrush x:Key="TabUnselectedBackGround" Color="#A9A9A9" />
<SolidColorBrush x:Key="TabSelectedBrush" Color="#F3FCF0" />
<SolidColorBrush x:Key="StatusConnectedBrush" Color="green" />
<SolidColorBrush x:Key="HoverBackgroundBrush" Color="#16408D"/>
<!-- Darker blue -->
<SolidColorBrush x:Key="HoverForegroundBrush" Color="#FF173788"/>
<!-- Yellow -->
<Style x:Key="CustomRaisedButton" TargetType="Button" BasedOn="{x:Null}">
<Setter Property="Foreground" Value="{DynamicResource SelectedLightBrush}" />
<Setter Property="Background" Value="{DynamicResource PrimaryBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryBrush}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Padding" Value="10,5" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="{TemplateBinding Padding}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="{DynamicResource HoverBrush}" />
<Setter Property="Foreground" Value="{DynamicResource HoverForegroundBrush}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="{DynamicResource HoverBackgroundBrush}" />
<Setter Property="Foreground" Value="{DynamicResource HoverForegroundBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="border" Property="Background" Value="#555" />
<Setter Property="Foreground" Value="#AAA" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{DynamicResource PrimaryBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="10,5"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource HoverBackgroundBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource HoverForegroundBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- TabItem Style -->
<Style TargetType="TabItem">
<Setter Property="Background" Value="{DynamicResource TabUnselectedBackGround}" />
<Setter Property="Foreground" Value="{DynamicResource TabUnselectedBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryBrush}" />
<!-- Style for selected tab -->
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Foreground" Value="{DynamicResource TabSelectedBrush}" />
<Setter Property="Background" Value="{DynamicResource PrimaryBrush}" />
<!-- Optionally adjust the border for selected tab -->
<Setter Property="BorderBrush" Value="{DynamicResource TabSelectedBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

56
LD-SysInfo/App.xaml.cs Normal file
View File

@@ -0,0 +1,56 @@
using CommunityToolkit.Mvvm.Messaging;
using MaterialDesignThemes.Wpf;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Windows;
using System.Windows.Threading;
namespace LD_SysInfo;
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
[STAThread]
private static void Main(string[] args)
{
MainAsync(args).GetAwaiter().GetResult();
}
private static async Task MainAsync(string[] args)
{
using IHost host = CreateHostBuilder(args).Build();
await host.StartAsync().ConfigureAwait(true);
App app = new();
app.InitializeComponent();
app.MainWindow = host.Services.GetRequiredService<MainWindow>();
app.MainWindow.Visibility = Visibility.Visible;
app.Run();
await host.StopAsync().ConfigureAwait(true);
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostBuilderContext, configurationBuilder)
=> configurationBuilder.AddUserSecrets(typeof(App).Assembly))
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<MainWindow>();
services.AddSingleton<MainWindowViewModel>();
services.AddSingleton<WeakReferenceMessenger>();
services.AddSingleton<IMessenger, WeakReferenceMessenger>(provider => provider.GetRequiredService<WeakReferenceMessenger>());
services.AddSingleton(_ => Current.Dispatcher);
services.AddTransient<ISnackbarMessageQueue>(provider =>
{
Dispatcher dispatcher = provider.GetRequiredService<Dispatcher>();
return new SnackbarMessageQueue(TimeSpan.FromSeconds(3.0), dispatcher);
});
});
}

View File

@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
LD-SysInfo/DLShortcut.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,35 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public class EncryptionHelper
{
private static readonly byte[] Key = Encoding.UTF8.GetBytes("HWJGbwmF2pWdXySDExMNEbJSrXn0YCBF"); // 32 bytes for AES-256
private static readonly byte[] IV = Encoding.UTF8.GetBytes("VWYRtYCfch0sKs6k"); // 16 bytes
public static string EncryptData(string plainText)
{
using (Aes aes = Aes.Create())
{
aes.Key = Key;
aes.IV = IV;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (MemoryStream ms = new MemoryStream())
using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
using (StreamWriter sw = new StreamWriter(cs, new UTF8Encoding(false))) // 🔹 Disable BOM
{
sw.Write(plainText);
}
byte[] encryptedBytes = ms.ToArray();
string encryptedBase64 = Convert.ToBase64String(encryptedBytes);
Console.WriteLine("🔹 [DEBUG] Encrypted Data (Base64): " + encryptedBase64); // ✅ Debugging
return encryptedBase64;
}
}
}
}

View File

@@ -0,0 +1,63 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>PSG-Oversight</AssemblyName>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<StartupObject>LD_SysInfo.App</StartupObject>
<ApplicationIcon>Assets\LDShortcut.ico</ApplicationIcon>
<OutputPath>C:\Users\Sonder\source\repos\psg-oversight-app\BuildDir\bin\Debug\net8.0-windows\</OutputPath>
<IncludeSatelliteAssembliesForPublish>false</IncludeSatelliteAssembliesForPublish>
</PropertyGroup>
<!--
Uncomment to enable single file exe publishing
https://learn.microsoft.com/dotnet/core/deploying/single-file/overview
The Condition on PublishSingleFile is to prevent debugging issues while running as a single file.
Many debugging tools (Snoop, Visual Studio's UI debugging tools for XAML) will not function with PublishSingleFile set to true.
https://github.com/dotnet/runtime/issues/3773
You will also need to remove the no-build option from the `dotnet publish` command on the "dotnet publish" step in .github/workflows/build_app.yml
-->
<!--
<PropertyGroup>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishSingleFile Condition="'$(Configuration)' != 'Debug'">true</PublishSingleFile>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<SelfContained>true</SelfContained>
</PropertyGroup>
-->
<ItemGroup>
<ApplicationDefinition Remove="App.xaml" />
<None Remove="Assets\trayicon.ico" />
<None Remove="config.json" />
<Content Include="Assets\LDShortcut.ico" />
<Page Include="App.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" />
<PackageReference Include="MaterialDesignColors" />
<PackageReference Include="MaterialDesignThemes" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="System.Management" />
</ItemGroup>
<ItemGroup>
<Resource Include="Assets\trayicon.ico" />
<Content Include="config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

409
LD-SysInfo/MainWindow.xaml Normal file
View File

@@ -0,0 +1,409 @@
<Window x:Class="LD_SysInfo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LD_SysInfo"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:materialDesignAssist="clr-namespace:MaterialDesignThemes.Wpf;assembly=MaterialDesignThemes.Wpf"
mc:Ignorable="d"
Title="PSG - Oversight"
Height="500"
Width="400"
Icon="Assets/trayicon.ico"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
Foreground="{DynamicResource TextDarkBrush}">
<Window.Resources>
<ContextMenu x:Key="TrayMenu">
<MenuItem Header="Show Window" Click="ShowWindow_Click"/>
<MenuItem Header="Exit" Click="Exit_Click"/>
</ContextMenu>
<Style TargetType="TabControl">
<Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryBrush}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
<Style TargetType="TabItem">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="MinWidth" Value="118" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Grid>
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="6"
Margin="2">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
ContentSource="Header"
Margin="12,4"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<!-- Trigger for selected tab -->
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#4776cd" />
<!-- Selected Background -->
<Setter Property="BorderBrush" Value="#E5E4E2" />
<!-- Selected Border -->
<Setter Property="Foreground" Value="#E5E4E2" />
<!-- Selected Text Color -->
</Trigger>
<!-- Explicitly setting background and text for unselected tab -->
<Trigger Property="IsSelected" Value="False">
<Setter Property="Background" Value="#16408D" />
<!-- Unselected Background -->
<Setter Property="BorderBrush" Value="#E5E4E2" />
<!-- Unselected Border -->
<Setter Property="Foreground" Value="#E5E4E2" />
<!-- Unselected Text Color -->
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 🔵 Base Style for Minimize and Maximize buttons -->
<Style x:Key="WindowButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{DynamicResource TextLightBrush}"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" CornerRadius="6">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource HoverBrush}"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{DynamicResource PrimaryBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 🔴 Special Style for the Close button -->
<Style x:Key="CloseButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{DynamicResource TextLightBrush}"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" CornerRadius="6">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<!-- Hover Effect - Light Red -->
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#D32F2F"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
<!-- Pressed Effect - Darker Red -->
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{DynamicResource DangerBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource DangerTextBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="{DynamicResource TextDarkBrush}" />
<Setter Property="Background" Value="{DynamicResource PaperDarkBrush}" />
</Style>
<Style TargetType="PasswordBox">
<Setter Property="Foreground" Value="{DynamicResource TextDarkBrush}" />
<Setter Property="Background" Value="{DynamicResource PaperDarkBrush}" />
</Style>
</Window.Resources>
<!-- Wrap everything in a Grid to ensure Window has only one child -->
<Border CornerRadius="12"
Background="{DynamicResource BackgroundDarkBrush}"
SnapsToDevicePixels="True"
ClipToBounds="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<!-- Titlebar -->
<RowDefinition Height="Auto" />
<!-- Tabs + Theme -->
<RowDefinition Height="*" />
<!-- Tab Content -->
</Grid.RowDefinitions>
<!-- 🔹 Titlebar -->
<Border Grid.Row="0"
CornerRadius="12,12,0,0"
Background="{DynamicResource BackgroundDarkBrush}" Margin="0,0,2,2">
<Grid MouseDown="TitleBar_MouseDown">
<TextBlock Text="PSG - Oversight"
VerticalAlignment="Center"
Margin="10,0,0,0"
FontSize="22"
FontWeight="Bold"
Foreground="{DynamicResource TitleBrush}"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="30" Height="30" Click="MinimizeWindow_Click" Style="{StaticResource WindowButtonStyle}">
<materialDesign:PackIcon Kind="WindowMinimize" Width="20" Height="20" />
</Button>
<Button Width="30" Height="30" Click="MaximizeRestoreWindow_Click" Style="{StaticResource WindowButtonStyle}">
<materialDesign:PackIcon Kind="WindowMaximize" Width="20" Height="20" />
</Button>
<Button Width="30" Height="30" Click="CloseWindow_Click" Style="{StaticResource CloseButtonStyle}">
<materialDesign:PackIcon Kind="WindowClose" Width="20" Height="20" />
</Button>
</StackPanel>
</Grid>
</Border>
<!-- 🔹 Tabs + Theme Toggle -->
<Grid Grid.Row="1" Background="{DynamicResource BackgroundDarkBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<!-- MainTabControl with headers visible -->
<TabControl x:Name="MainTabControl"
Grid.Column="0"
Background="{DynamicResource BackgroundDarkBrush}"
BorderThickness="0"
TabStripPlacement="Top"
SelectedIndex="0"
IsTabStop="False"
Padding="0">
<TabItem Header="SysInfo" FontSize="16"/>
<TabItem Header="InstalledApps" FontSize="16"/>
<TabItem Header="Status" FontSize="16"/>
</TabControl>
<!--
<Button Grid.Column="1"
x:Name="ThemeToggleButton"
Style="{StaticResource SmallSquareButtonStyle}"
ToolTip="Toggle Theme"
Click="ThemeToggleButton_Click">
<materialDesign:PackIcon x:Name="ThemeIcon"
Kind="WeatherSunny"
Width="20" Height="20"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Button>
-->
</Grid>
<Border Grid.Row="2"
CornerRadius="0,0,12,12"
Background="Transparent"
ClipToBounds="True">
<!-- 🔹 Tab Content -->
<TabControl x:Name="MainTabContentControl"
Background="Transparent"
BorderThickness="0"
SelectedIndex="{Binding SelectedIndex, ElementName=MainTabControl, Mode=TwoWay}"
Margin="0" Padding="0,0,0,0"
>
<!-- 👇 Hides the TabItem headers -->
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</TabControl.Resources>
<!-- 🔹 SysInfo Tab -->
<TabItem Header="SysInfo">
<StackPanel Margin="5,0,5,0" VerticalAlignment="Top">
<TextBlock x:Name="HostnameTextBlock" Text="Hostname" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="OSNameTextBlock" Text="OS Name" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="OSVersionTextBlock" Text="OS Version" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="WindowsVersionTextBlock" Text="Windows Version" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="WindowsBuildTextBlock" Text="Windows Build" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="OSArchitectureTextBlock" Text="OS Architecture" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="ProcessorNameTextBlock" Text="Processor Name" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="ProcessorArchitectureTextBlock" Text="Processor Architecture" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<Button Content="Export to CSV"
Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}"
Click="ExportToCsvButton_Click"
Width="200"
Margin="5"
HorizontalAlignment="Left" />
<Button Content="Save System Info"
Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}"
Click="StoreSystemInfoButton_Click"
Width="200"
Margin="5"
HorizontalAlignment="Left" />
<CheckBox
x:Name="IncludeInstalledAppsCheckBox"
Content="Include installed Apps?"
FontSize="14"
Margin="5"
Foreground="{DynamicResource TextDarkBrush}"
Style="{StaticResource MaterialDesignCheckBox}" />
<TextBlock x:Name="StatusTextBlock"
FontSize="14"
FontWeight="SemiBold"
Margin="10,5"
Foreground="{DynamicResource ErrorBrush}" />
</StackPanel>
</TabItem>
<!-- 🔹 InstalledApps Tab -->
<TabItem Header="InstalledApps">
<StackPanel Margin="10">
<ListBox x:Name="InstalledAppsListBox" Height="300" Width="350" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Version}" FontStyle="Italic" />
<TextBlock Text="{Binding Publisher}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Load Installed Applications"
Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}"
Click="LoadInstalledAppsButton_Click"
Width="200"
Margin="5"
HorizontalAlignment="Left" />
</StackPanel>
</TabItem>
<!-- 🔹 Status Tab -->
<TabItem Header="Status">
<StackPanel Margin="10">
<TextBlock Text="Server Connectivity Status:"
FontSize="14"
FontWeight="SemiBold"
Margin="5"
Foreground="{DynamicResource TextDarkBrush}" />
<TextBlock x:Name="ConnectionStatusTextBlock"
Text="Connected to Server (Ping)"
Foreground="{DynamicResource StatusConnectedBrush}"
FontSize="14"
FontWeight="Bold" />
<StackPanel Orientation="Vertical" Margin="0,10,0,0">
<TextBox x:Name="txtUsername"
Width="200"
Margin="5"
HorizontalAlignment="Left"
/>
<PasswordBox x:Name="pwdPassword"
Width="200"
Margin="5"
HorizontalAlignment="Left" />
</StackPanel>
<Button x:Name="AuthenticateButton"
Content="Login"
Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}"
Click="AuthenticateButton_Click"
Width="200"
Margin="5"
HorizontalAlignment="Left"
/>
<Separator Margin="10" />
<TextBlock Text="Diagnostic Logs:"
FontSize="14"
FontWeight="SemiBold"
Margin="5"
Foreground="{DynamicResource TextDarkBrush}" />
<Button Content="Generate Logs"
Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}"
Click="GenerateLogsButton_Click"
Width="200"
Margin="5"
HorizontalAlignment="Left" />
</StackPanel>
</TabItem>
</TabControl>
</Border>
<!-- ✅ Tray Icon (not inside Grid) -->
<tb:TaskbarIcon x:Name="TrayIcon"
ToolTipText="LD SysInfo Running"
ContextMenu="{StaticResource TrayMenu}"
IconSource="/Assets/trayicon.ico" />
</Grid>
</Border>
</Window>

View File

@@ -0,0 +1,626 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Win32;
using System.IO;
using System.Windows.Threading;
using System.Windows.Forms;
using Hardcodet.Wpf.TaskbarNotification;
using LD_SysInfo.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Windows.Media.Animation;
using LD_SysInfo.Models;
using MaterialDesignColors;
using MaterialDesignThemes.Wpf;
using System.Windows.Input;
using Application = System.Windows.Application;
using System.Windows.Media;
namespace LD_SysInfo
{
public partial class MainWindow : Window
{
private static readonly HttpClient httpClient = new HttpClient();
private static AppConfig _config;
private static string jwtToken = null; // 🔹 Store the JWT token globally in the app
private readonly DispatcherTimer messageClearTimer;
private readonly DispatcherTimer postTimer;
private readonly DispatcherTimer keepAliveTimer;
private readonly Uri LightThemeUri = new Uri("pack://application:,,,/Themes/LightTheme.xaml", UriKind.Absolute);
private readonly Uri DarkThemeUri = new Uri("pack://application:,,,/Themes/DarkTheme.xaml", UriKind.Absolute);
private bool isDarkTheme = false;
private static readonly string ConfigPath = Path.Combine(AppContext.BaseDirectory, "config.json");
public MainWindow()
{
// ⚠️ Temporary SSL Certificate Bypass - For Testing Only
System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
InitializeComponent();
LoadConfig();
DisplaySystemInfo();
AutoLogin();
// 🔍 Perform initial connectivity check
Task.Run(async () => await InitialCheckConnectivity());
// Initialize the system tray icon event
TrayIcon.TrayMouseDoubleClick += TrayIcon_DoubleClick;
// 🔥 Initialize the DispatcherTimer for fading out messages (statically set to 5 seconds)
messageClearTimer = new DispatcherTimer();
messageClearTimer.Interval = TimeSpan.FromSeconds(5); // Set to 5 seconds (static)
messageClearTimer.Tick += MessageClearTimer_Tick;
// 🔥 Initialize the DispatcherTimer for periodic POSTs using _config.PollingInterval
postTimer = new DispatcherTimer();
postTimer.Interval = TimeSpan.FromSeconds(_config.SystemInfoInterval);
postTimer.Tick += PostTimer_Tick;
postTimer.Start();
// 🔁 Initialize the KeepAlive timer using KeepAlivePeriod from config
keepAliveTimer = new DispatcherTimer();
keepAliveTimer.Interval = TimeSpan.FromSeconds(_config.KeepAlivePeriod); // Set from config
keepAliveTimer.Tick += KeepAliveTimer_Tick;
keepAliveTimer.Start();
}
private void LoadConfig()
{
try
{
//System.Windows.MessageBox.Show($"DEBUG: AppContext.BaseDirectory = {AppContext.BaseDirectory}");
//System.Windows.MessageBox.Show($"DEBUG: ConfigPath = {ConfigPath}");
if (!File.Exists((string?)ConfigPath))
{
System.Windows.MessageBox.Show("❌ config.json not found at resolved path!");
}
_config = ConfigManager.LoadConfig(ConfigPath);
if (_config == null || string.IsNullOrWhiteSpace(_config.ClientIdentifier))
{
throw new Exception("❌ Invalid or missing ClientIdentifier in config.json.");
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"❌ Error loading config: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
_config = new AppConfig(); // Prevent application from crashing
}
}
private async Task InitialCheckConnectivity()
{
var apiClient = new ApiClient(_config);
bool isConnected = await apiClient.CheckConnectivity();
// You must update the UI from the UI thread
Dispatcher.Invoke(() =>
{
if (isConnected)
{
ConnectionStatusTextBlock.Text = "Connected to Server";
ConnectionStatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);
}
else
{
ConnectionStatusTextBlock.Text = "No Connection";
ConnectionStatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
}
});
}
private async void AutoLogin()
{
if (_config == null || _config.Auth == null ||
string.IsNullOrEmpty(_config.Auth.Username) || string.IsNullOrEmpty(_config.Auth.Password))
{
UpdateStatusUI(false);
return;
}
var apiClient = new ApiClient(_config);
LoginResponse loginResponse = await apiClient.AuthenticateAsync(_config.Auth.Username, _config.Auth.Password);
if (loginResponse != null && !string.IsNullOrEmpty(loginResponse.Token))
{
ApiClient.SetJwtToken(loginResponse.Token);
UpdateStatusUI(true);
}
else
{
UpdateStatusUI(false);
}
}
private async void AuthenticateButton_Click(object sender, RoutedEventArgs e)
{
var apiClient = new ApiClient(_config);
LoginResponse loginResponse = await apiClient.AuthenticateAsync(txtUsername.Text, pwdPassword.Password);
if (loginResponse != null && !string.IsNullOrEmpty(loginResponse.Token))
{
ApiClient.SetJwtToken(loginResponse.Token);
UpdateStatusUI(true); // 🔥 Successfully authenticated
}
else
{
UpdateStatusUI(false);
}
}
private async void CheckConnectivityButton_Click(object sender, RoutedEventArgs e)
{
var apiClient = new ApiClient(_config);
bool isConnected = await apiClient.CheckConnectivity();
if (isConnected)
{
ConnectionStatusTextBlock.Text = "Connected to Server";
ConnectionStatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);
}
else
{
ConnectionStatusTextBlock.Text = "No Connection";
ConnectionStatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
}
}
private void GenerateLogsButton_Click(object sender, RoutedEventArgs e)
{
try
{
string logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LD_SysInfo", "Logs");
Directory.CreateDirectory(logDir); // Ensure directory exists
string logFile = Path.Combine(logDir, $"log_{DateTime.Now:yyyyMMdd_HHmmss}.txt");
File.WriteAllText(logFile, "=== LD SysInfo Diagnostic Log ===\n");
File.AppendAllText(logFile, $"Timestamp: {DateTime.Now}\n");
File.AppendAllText(logFile, $"Hostname: {Environment.MachineName}\n");
File.AppendAllText(logFile, $"OS: {Environment.OSVersion}\n");
File.AppendAllText(logFile, $"Server Status: {ConnectionStatusTextBlock.Text}\n");
System.Windows.MessageBox.Show($"Logs generated successfully!\nPath: {logFile}", "Log Generated", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"Failed to generate logs.\nError: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void DisplaySystemInfo()
{
var sysInfo = SystemInfo.GetSystemInfo();
OSNameTextBlock.Text = $"OS Name: {sysInfo.OSName}";
OSVersionTextBlock.Text = $"OS Version: {sysInfo.OSVersion}";
WindowsVersionTextBlock.Text = $"Windows Version: {sysInfo.WindowsVersion}";
WindowsBuildTextBlock.Text = $"Windows Build: {sysInfo.WindowsBuild}";
OSArchitectureTextBlock.Text = $"OS Architecture: {sysInfo.OSArchitecture}";
ProcessorNameTextBlock.Text = $"Processor Name: {sysInfo.ProcessorName}";
ProcessorArchitectureTextBlock.Text = $"Processor Architecture: {sysInfo.ProcessorArchitecture}";
HostnameTextBlock.Text = $"Hostname: {sysInfo.Hostname}";
}
private void LoadInstalledAppsButton_Click(object sender, RoutedEventArgs e)
{
var applications = SystemInfo.GetInstalledApplicationsFromRegistry();
InstalledAppsListBox.ItemsSource = applications;
}
private async void StoreSystemInfoButton_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(ApiClient.GetJwtToken())) // Check token via ApiClient
{
System.Windows.MessageBox.Show("❌ Please log in first.", "Not Authenticated", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
try
{
var sysInfo = SystemInfo.GetSystemInfo();
var applications = SystemInfo.GetInstalledApplicationsFromRegistry();
var formattedApplications = new List<object>();
foreach (var app in applications)
{
formattedApplications.Add(new
{
app_name = app.Name,
app_version = app.Version,
publisher = app.Publisher
});
}
var payload = new
{
clientIdentifier = _config.ClientIdentifier,
hostname = sysInfo.Hostname,
osName = sysInfo.OSName,
osVersion = sysInfo.OSVersion,
windowsVersion = sysInfo.WindowsVersion,
windowsBuild = sysInfo.WindowsBuild,
osArchitecture = sysInfo.OSArchitecture,
processorName = sysInfo.ProcessorName,
processorArchitecture = sysInfo.ProcessorArchitecture,
gpuName = sysInfo.GpuNames,
totalMemory = sysInfo.TotalMemory,
ipAddresses = sysInfo.IpAddresses,
lastBootTime = sysInfo.LastBootTime,
drives = sysInfo.Drives,
installedApplications = formattedApplications
};
var apiClient = new ApiClient(_config);
string jsonPayload = JsonConvert.SerializeObject(payload);
Console.WriteLine("🔍 [DEBUG] Raw JSON Payload:\n" + jsonPayload);
string encryptedPayload = EncryptionHelper.EncryptData(jsonPayload);
string response = await apiClient.StoreSystemInfoAsync(encryptedPayload);
if (response.StartsWith("Error"))
{
StatusTextBlock.Text = $"❌ {response}";
StatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
}
else
{
StatusTextBlock.Text = "✅ System info stored successfully!";
StatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);
// 🔥 Trigger Fade Out Animation
messageClearTimer.Stop(); // Stop existing timer
FadeOutStatusMessage(); // Start the fade-out animation
}
}
catch (Exception ex)
{
StatusTextBlock.Text = $"❌ Error: {ex.Message}";
StatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
}
}
private void LoadThemeResources(string resourcePath)
{
var dictionaries = Application.Current.Resources.MergedDictionaries;
dictionaries.Clear();
// Add required MaterialDesign v5 resource dictionaries
dictionaries.Add(new ResourceDictionary
{
Source = new Uri("pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml", UriKind.Absolute)
});
dictionaries.Add(new ResourceDictionary
{
Source = new Uri(isDarkTheme
? "pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Dark.xaml"
: "pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Light.xaml", UriKind.Absolute)
});
// Optional: Your custom theme overrides (LightTheme.xaml or DarkTheme.xaml)
dictionaries.Add(new ResourceDictionary
{
Source = new Uri(resourcePath, UriKind.RelativeOrAbsolute)
});
}
private void ThemeToggleButton_Click(object sender, RoutedEventArgs e)
{
isDarkTheme = !isDarkTheme;
LoadThemeResources(isDarkTheme ? "Themes/DarkTheme.xaml" : "Themes/LightTheme.xaml");
}
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
this.DragMove();
}
}
private void MinimizeWindow_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void MaximizeRestoreWindow_Click(object sender, RoutedEventArgs e)
{
if (WindowState == WindowState.Maximized)
WindowState = WindowState.Normal;
else
WindowState = WindowState.Maximized;
}
private void CloseWindow_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void ExportToCsvButton_Click(object sender, RoutedEventArgs e)
{
var saveFileDialog = new Microsoft.Win32.SaveFileDialog
{
Filter = "CSV files (*.csv)|*.csv",
Title = "Save System Info as CSV",
FileName = "SystemInfo.csv"
};
if (saveFileDialog.ShowDialog() == true)
{
var sysInfo = SystemInfo.GetSystemInfo();
try
{
using (var writer = new StreamWriter(saveFileDialog.FileName))
{
writer.WriteLine("Property,Value");
writer.WriteLine($"Hostname,{sysInfo.Hostname}");
writer.WriteLine($"OS Name,{sysInfo.OSName}");
writer.WriteLine($"OS Version,{sysInfo.OSVersion}");
writer.WriteLine($"Windows Version,{sysInfo.WindowsVersion}");
writer.WriteLine($"Windows Build,{sysInfo.WindowsBuild}");
writer.WriteLine($"OS Architecture,{sysInfo.OSArchitecture}");
writer.WriteLine($"Processor Name,{sysInfo.ProcessorName}");
writer.WriteLine($"Processor Architecture,{sysInfo.ProcessorArchitecture}");
if (IncludeInstalledAppsCheckBox.IsChecked == true)
{
writer.WriteLine();
writer.WriteLine("Installed Applications:");
writer.WriteLine("Name,Version,Publisher");
var applications = SystemInfo.GetInstalledApplicationsFromRegistry();
foreach (var app in applications)
{
writer.WriteLine($"{app.Name},{app.Version},{app.Publisher}");
}
}
}
StatusTextBlock.Text = "System info exported successfully!";
messageClearTimer.Start(); // Start the timer to clear the message after 5 seconds
}
catch (Exception ex)
{
StatusTextBlock.Text = $"Error saving file: {ex.Message}";
}
}
}
private async void UpdateStatusUI(bool isAuthenticated)
{
// Ensure we're on the UI thread before updating UI elements
Dispatcher.Invoke(() =>
{
if (isAuthenticated)
{
txtUsername.Visibility = Visibility.Collapsed;
pwdPassword.Visibility = Visibility.Collapsed;
AuthenticateButton.Visibility = Visibility.Collapsed;
StatusTextBlock.Text = "✅ Authenticated - Connected to Server";
StatusTextBlock.Foreground = (SolidColorBrush)FindResource("StatusConnectedBrush"); // Use your custom brush
// 🔍 Check connectivity to the server
var apiClient = new ApiClient(_config);
Task.Run(async () =>
{
bool isConnected = await apiClient.CheckConnectivity();
// Update connection status on the UI thread
Dispatcher.Invoke(() =>
{
if (isConnected)
{
ConnectionStatusTextBlock.Text = "Connected to Server";
ConnectionStatusTextBlock.Foreground = (SolidColorBrush)FindResource("StatusConnectedBrush"); // Use your custom brush
}
else
{
ConnectionStatusTextBlock.Text = "No Connection";
ConnectionStatusTextBlock.Foreground = (SolidColorBrush)FindResource("ErrorBrush"); // Use your custom error color
}
});
});
// 🔥 Start the timer to clear the message after a delay
messageClearTimer.Stop(); // Stop any running timer first
messageClearTimer.Start();
}
else
{
txtUsername.Visibility = Visibility.Visible;
pwdPassword.Visibility = Visibility.Visible;
AuthenticateButton.Visibility = Visibility.Visible;
StatusTextBlock.Text = "❌ Not Authenticated - Please Log In";
StatusTextBlock.Foreground = (SolidColorBrush)FindResource("ErrorBrush"); // Use your custom error color
ConnectionStatusTextBlock.Text = "No Connection";
ConnectionStatusTextBlock.Foreground = (SolidColorBrush)FindResource("ErrorBrush"); // Use your custom error color
// Stop the timer if the user is not authenticated
messageClearTimer.Stop();
}
});
}
private void FadeOutStatusMessage()
{
var fadeOutAnimation = new DoubleAnimation
{
From = 1.0,
To = 0.0,
Duration = TimeSpan.FromSeconds(3),
AutoReverse = false
};
fadeOutAnimation.Completed += (s, e) =>
{
StatusTextBlock.Text = string.Empty; // Clear the text after fading
StatusTextBlock.Opacity = 1.0; // Reset the opacity for future use
};
StatusTextBlock.BeginAnimation(OpacityProperty, fadeOutAnimation);
messageClearTimer.Stop();
}
private async void PostTimer_Tick(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(ApiClient.GetJwtToken())) // Ensure authentication is active
{
StatusTextBlock.Text = "❌ Not authenticated. Please log in first.";
return;
}
var apiClient = new ApiClient(_config); // Create an instance of ApiClient
var sysInfo = SystemInfo.GetSystemInfo();
var applications = SystemInfo.GetInstalledApplicationsFromRegistry();
var formattedApplications = new List<object>();
foreach (var app in applications)
{
formattedApplications.Add(new
{
app_name = app.Name,
app_version = app.Version,
publisher = app.Publisher
});
}
var payload = new
{
clientIdentifier = _config.ClientIdentifier,
hostname = sysInfo.Hostname,
osName = sysInfo.OSName,
osVersion = sysInfo.OSVersion,
windowsVersion = sysInfo.WindowsVersion,
windowsBuild = sysInfo.WindowsBuild,
osArchitecture = sysInfo.OSArchitecture,
processorName = sysInfo.ProcessorName,
processorArchitecture = sysInfo.ProcessorArchitecture,
gpuName = sysInfo.GpuNames,
totalMemory = sysInfo.TotalMemory,
ipAddresses = sysInfo.IpAddresses,
lastBootTime = sysInfo.LastBootTime,
drives = sysInfo.Drives,
installedApplications = formattedApplications
};
string jsonPayload = JsonConvert.SerializeObject(payload);
Console.WriteLine("🔍 [DEBUG] Raw JSON Payload:\n" + jsonPayload);
string encryptedPayload = EncryptionHelper.EncryptData(jsonPayload);
string response = await apiClient.StoreSystemInfoAsync(encryptedPayload);
if (response.StartsWith("Error"))
{
StatusTextBlock.Text = $"❌ {response}";
StatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
}
else
{
StatusTextBlock.Text = "✅ System info stored successfully!";
StatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);
messageClearTimer.Stop();
FadeOutStatusMessage();
}
}
private async void KeepAliveTimer_Tick(object sender, EventArgs e)
{
var apiClient = new ApiClient(_config);
bool isConnected = await apiClient.CheckConnectivity();
Dispatcher.Invoke(() =>
{
if (isConnected)
{
ConnectionStatusTextBlock.Text = "Connected to Server (Ping)";
// Use the custom gold color for connection status
ConnectionStatusTextBlock.Foreground = (SolidColorBrush)FindResource("StatusConnectedBrush");
}
else
{
ConnectionStatusTextBlock.Text = "No Connection";
// Use the custom error color for no connection
ConnectionStatusTextBlock.Foreground = (SolidColorBrush)FindResource("ErrorBrush");
}
});
}
private void MessageClearTimer_Tick(object sender, EventArgs e)
{
FadeOutStatusMessage(); // Trigger the fade-out effect
}
private void TrayIcon_DoubleClick(object sender, RoutedEventArgs e)
{
ShowWindow();
}
private void ShowWindow_Click(object sender, RoutedEventArgs e)
{
ShowWindow();
}
private void ShowWindow()
{
this.Show();
this.WindowState = WindowState.Normal;
}
private void Exit_Click(object sender, RoutedEventArgs e)
{
TrayIcon.Dispose();
System.Windows.Application.Current.Shutdown();
}
// Minimize to system tray instead of closing
protected override void OnStateChanged(EventArgs e)
{
if (WindowState == WindowState.Minimized)
{
this.Hide(); // Hide the window when minimized
}
base.OnStateChanged(e);
}
}
}

View File

@@ -0,0 +1,36 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace LD_SysInfo;
public partial class MainWindowViewModel : ObservableObject
{
//This is using the source generators from CommunityToolkit.Mvvm to generate a RelayCommand
//See: https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/generators/observableproperty
//and: https://learn.microsoft.com/windows/communitytoolkit/mvvm/observableobject
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(IncrementCountCommand))]
private int _count;
public MainWindowViewModel()
{
}
//This is using the source generators from CommunityToolkit.Mvvm to generate a RelayCommand
//See: https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/generators/relaycommand
//and: https://learn.microsoft.com/windows/communitytoolkit/mvvm/relaycommand
[RelayCommand(CanExecute = nameof(CanIncrementCount))]
private void IncrementCount()
{
Count++;
}
private bool CanIncrementCount() => Count < 5;
[RelayCommand]
private void ClearCount()
{
Count = 0;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,206 @@
using System;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Windows;
using LD_SysInfo.Models;
namespace LD_SysInfo.Services
{
public class ApiClient
{
/// <summary>
/// Constructs the full URL for API requests.
/// </summary>
private static readonly HttpClient httpClient = CreateHttpClient();
private readonly AppConfig _config;
private static string jwtToken = null; // 🔹 Store the JWT token globally
public ApiClient(AppConfig config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
}
private string BuildUrl(string route)
{
return $"{_config.ServerUrl.TrimEnd('/')}/{route.TrimStart('/')}";
}
/// <summary>
/// Creates an HttpClient that bypasses SSL validation (for testing purposes only).
/// </summary>
public static HttpClient CreateHttpClient()
{
HttpClientHandler handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, certChain, sslPolicyErrors) =>
{
return true; // 🔥 Ignores all SSL certificate warnings (TEMPORARY!)
}
};
return new HttpClient(handler);
}
/// <summary>
/// Sets the JWT token to be used for subsequent requests.
/// </summary>
public static void SetJwtToken(string token)
{
jwtToken = token;
httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", jwtToken);
}
/// <summary>
/// Gets the JWT token to be used for subsequent requests.
/// </summary>
public static string GetJwtToken()
{
return jwtToken;
}
/// <summary>
/// Authenticates with the API using credentials from config.json.
/// </summary>
public async Task<LoginResponse> AuthenticateAsync(string username, string password)
{
try
{
string encryptedUsername = EncryptionHelper.EncryptData(username);
string encryptedPassword = EncryptionHelper.EncryptData(password);
var payload = new
{
username = encryptedUsername,
password = encryptedPassword
};
string jsonContent = JsonConvert.SerializeObject(payload);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
HttpResponseMessage response = await httpClient.PostAsync(BuildUrl("/api/auth/login"), content);
string rawResponse = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"❌ Authentication failed: {response.StatusCode} - {rawResponse}");
return null;
}
var loginResponse = JsonConvert.DeserializeObject<LoginResponse>(rawResponse);
if (!string.IsNullOrEmpty(loginResponse?.Token))
{
SetJwtToken(loginResponse.Token);
}
return loginResponse;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Exception during login: {ex.Message}");
return null;
}
}
/// <summary>
/// Checks connectivity to the server using the stored JWT token.
/// </summary>
public async Task<bool> CheckConnectivity()
{
string testUrl = "https://localhost:8443/api/system/ping-status"; // Your HTTPS URL with port 8443
try
{
if (string.IsNullOrEmpty(jwtToken))
{
throw new Exception("No JWT token stored. Please authenticate first.");
}
var response = await SendWithAutoReauthAsync(() =>
httpClient.GetAsync(testUrl)
);
if (response.IsSuccessStatusCode)
{
return true;
}
else
{
Console.WriteLine($"❌ Server Unreachable: {response.StatusCode}");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Connection Error: {ex.Message}");
return false;
}
}
/// <summary>
/// Sends the collected data to the server.
/// </summary>
public async Task<string> StoreSystemInfoAsync(string encryptedData)
{
try
{
if (string.IsNullOrEmpty(jwtToken))
return "❌ Error: Not authenticated. Please log in first.";
var payload = new { data = encryptedData };
string jsonContent = JsonConvert.SerializeObject(payload);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await SendWithAutoReauthAsync(() =>
httpClient.PostAsync(BuildUrl("/api/system-info"), content)
);
if (response.IsSuccessStatusCode)
return await response.Content.ReadAsStringAsync();
else
return $"❌ Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}";
}
catch (Exception ex)
{
return $"❌ Exception: {ex.Message}";
}
}
/// <summary>
/// Sends the collected data to the server.
/// </summary>
private async Task<HttpResponseMessage> SendWithAutoReauthAsync(Func<Task<HttpResponseMessage>> requestFunc)
{
var response = await requestFunc();
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
Console.WriteLine("⚠️ Token expired or invalid. Attempting re-auth...");
var loginResponse = await AuthenticateAsync(_config.Auth.Username, _config.Auth.Password);
if (loginResponse == null || string.IsNullOrEmpty(loginResponse.Token))
{
Console.WriteLine("❌ Re-authentication failed.");
return response; // Return original 401 response
}
// Retry original request after reauth
response = await requestFunc();
}
return response;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace LD_SysInfo.Models
{
public class LoginResponse
{
public string Token { get; set; }
public string Username { get; set; }
public int UserId { get; set; }
}
}

345
LD-SysInfo/SystemInfo.cs Normal file
View File

@@ -0,0 +1,345 @@
using Microsoft.Win32;
using System.IO;
using System.Management;
using System.Diagnostics;
using System.Net.NetworkInformation;
using Newtonsoft.Json;
using System.Windows;
namespace LD_SysInfo
{
public class AppConfig
{
[JsonProperty("ServerUrl")]
public string ServerUrl { get; set; } = "https://yourserver.com/api/status";
[JsonProperty("EnableLogging")]
public bool EnableLogging { get; set; } = true;
[JsonProperty("KeepAlivePeriod")]
public int KeepAlivePeriod { get; set; } = 30; // Ping/heartbeat every 30 seconds
[JsonProperty("SystemInfoInterval")]
public int SystemInfoInterval { get; set; } = 60; // Full system info POST every 60 seconds
[JsonProperty("ClientIdentifier")]
public string ClientIdentifier { get; set; } = "your-default-client-id";
[JsonProperty("Auth")]
public AuthConfig Auth { get; set; } = new AuthConfig();
}
public class AuthConfig
{
[JsonProperty("Username")]
public string Username { get; set; } = "testuser";
[JsonProperty("Password")]
public string Password { get; set; } = "testpassword";
}
public static class ConfigManager
{
private static readonly string ConfigPath =
Path.Combine(AppContext.BaseDirectory, "config.json");
public static AppConfig LoadConfig(string configPath)
{
//System.Windows.MessageBox.Show($"[DEBUG] Calling LoadConfig from:\n{ConfigPath}");
if (File.Exists(ConfigPath))
{
string json = File.ReadAllText(ConfigPath);
return JsonConvert.DeserializeObject<AppConfig>(json);
}
throw new FileNotFoundException("❌ Config file not found at:\n" + ConfigPath);
}
}
public class InstalledApplication
{
public string Name { get; set; }
public string Version { get; set; }
public string Publisher { get; set; }
}
public class DriveInfoSummary
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("totalSizeGB")]
public double TotalSizeGB { get; set; }
[JsonProperty("freeSpaceGB")]
public double FreeSpaceGB { get; set; }
[JsonProperty("driveType")]
public string DriveType { get; set; }
}
public class NetworkInterfaceInfo
{
[JsonProperty("interfaceName")]
public string InterfaceName { get; set; }
[JsonProperty("ipAddress")]
public string IpAddress { get; set; }
[JsonProperty("macAddress")]
public string MacAddress { get; set; }
}
public class SystemInfo
{
[JsonProperty("osName")]
public string OSName { get; set; }
[JsonProperty("osVersion")]
public string OSVersion { get; set; }
[JsonProperty("windowsVersion")]
public string WindowsVersion { get; set; }
[JsonProperty("windowsBuild")]
public string WindowsBuild { get; set; }
[JsonProperty("osArchitecture")]
public string OSArchitecture { get; set; }
[JsonProperty("processorName")]
public string ProcessorName { get; set; }
[JsonProperty("processorArchitecture")]
public string ProcessorArchitecture { get; set; }
[JsonProperty("hostname")]
public string Hostname { get; set; }
[JsonProperty("gpuNames")]
public List<string> GpuNames { get; set; }
[JsonProperty("totalMemory")]
public string TotalMemory { get; set; }
[JsonProperty("ipAddresses")]
public List<NetworkInterfaceInfo> IpAddresses { get; set; } = new();
[JsonProperty("lastBootTime")]
public string LastBootTime { get; set; }
[JsonProperty("installedApplications")]
public List<InstalledApplication> InstalledApplications { get; set; }
[JsonProperty("drives")]
public List<DriveInfoSummary> Drives { get; set; } = new();
private const string WindowsCurrentVersionKey = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
public static SystemInfo GetSystemInfo()
{
var sysInfo = new SystemInfo();
try
{
sysInfo.Hostname = Environment.MachineName;
sysInfo.OSName = GetOSFriendlyName();
sysInfo.OSVersion = Environment.OSVersion.Version.ToString();
sysInfo.WindowsVersion = GetRegistryValue(WindowsCurrentVersionKey, "DisplayVersion");
sysInfo.WindowsBuild = GetRegistryValue(WindowsCurrentVersionKey, "CurrentBuild");
sysInfo.OSArchitecture = Environment.Is64BitOperatingSystem ? "x64" : "x86";
sysInfo.ProcessorName = GetProcessorName();
sysInfo.ProcessorArchitecture = Environment.Is64BitProcess ? "x64" : "x86";
sysInfo.GpuNames = GetGpuNames();
sysInfo.TotalMemory = GetTotalMemory();
var allInterfaces = GetNetworkInterfaces();
sysInfo.IpAddresses = allInterfaces
.Where(i => !string.IsNullOrWhiteSpace(i.InterfaceName))
.ToList();
sysInfo.LastBootTime = GetLastBootTime();
PopulateDriveInfo(sysInfo);
}
catch (Exception ex)
{
string logPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LD_SysInfo", "SysInfo_ErrorLog.txt");
Directory.CreateDirectory(Path.GetDirectoryName(logPath));
File.AppendAllText(logPath, $"[{DateTime.Now}] ERROR: {ex.Message}\n");
}
return sysInfo;
}
private static void PopulateDriveInfo(SystemInfo info)
{
foreach (var drive in DriveInfo.GetDrives())
{
if (!drive.IsReady) continue;
info.Drives.Add(new DriveInfoSummary
{
Name = drive.Name,
TotalSizeGB = Math.Round(drive.TotalSize / (1024.0 * 1024 * 1024), 2),
FreeSpaceGB = Math.Round(drive.AvailableFreeSpace / (1024.0 * 1024 * 1024), 2),
DriveType = drive.DriveType.ToString()
});
}
}
private static string GetRegistryValue(string key, string valueName)
{
using RegistryKey rk = Registry.LocalMachine.OpenSubKey(key);
return rk?.GetValue(valueName)?.ToString();
}
private static string GetOSFriendlyName()
{
string productName = GetRegistryValue(WindowsCurrentVersionKey, "ProductName");
string build = GetRegistryValue(WindowsCurrentVersionKey, "CurrentBuild");
if (int.TryParse(build, out int buildNumber) && buildNumber >= 22000)
return "Windows 11 Pro";
return productName ?? "Unknown OS";
}
private static string GetProcessorName()
{
const string key = @"HARDWARE\DESCRIPTION\System\CentralProcessor\0";
return GetRegistryValue(key, "ProcessorNameString");
}
private static List<string> GetGpuNames()
{
var gpuNames = new List<string>();
try
{
using var searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_VideoController");
foreach (var obj in searcher.Get())
{
string name = obj["Name"]?.ToString();
if (!string.IsNullOrEmpty(name))
{
gpuNames.Add(name);
}
}
}
catch (Exception ex)
{
gpuNames.Add($"Error: {ex.Message}");
}
return gpuNames;
}
private static string GetTotalMemory()
{
try
{
using var searcher = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem");
foreach (var obj in searcher.Get())
{
long bytes = Convert.ToInt64(obj["TotalPhysicalMemory"]);
return $"{bytes / (1024 * 1024)} MB";
}
}
catch { }
return null;
}
private static List<NetworkInterfaceInfo> GetNetworkInterfaces()
{
var interfaces = new List<NetworkInterfaceInfo>();
try
{
foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
{
if (adapter.OperationalStatus != OperationalStatus.Up) continue;
var ip = adapter.GetIPProperties().UnicastAddresses
.FirstOrDefault(a => a.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
var mac = adapter.GetPhysicalAddress();
var macAddress = mac?.GetAddressBytes().Length > 0
? string.Join(":", mac.GetAddressBytes().Select(b => b.ToString("X2")))
: null;
if (ip != null || macAddress != null)
{
interfaces.Add(new NetworkInterfaceInfo
{
InterfaceName = adapter.Name,
IpAddress = ip?.Address.ToString(),
MacAddress = macAddress
});
}
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to gather network interfaces: {ex.Message}");
}
return interfaces;
}
private static string GetLastBootTime()
{
try
{
using var searcher = new ManagementObjectSearcher("SELECT LastBootUpTime FROM Win32_OperatingSystem WHERE Primary='true'");
foreach (var obj in searcher.Get())
{
string lastBoot = obj["LastBootUpTime"]?.ToString();
return ManagementDateTimeConverter.ToDateTime(lastBoot).ToString("o");
}
}
catch { }
return null;
}
public static List<InstalledApplication> GetInstalledApplicationsFromRegistry()
{
var applications = new List<InstalledApplication>();
string[] registryKeys = {
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
};
foreach (string key in registryKeys)
{
using var regKey = Registry.LocalMachine.OpenSubKey(key);
if (regKey == null) continue;
foreach (string subKeyName in regKey.GetSubKeyNames())
{
using var subKey = regKey.OpenSubKey(subKeyName);
string name = subKey?.GetValue("DisplayName")?.ToString();
if (!string.IsNullOrWhiteSpace(name))
{
applications.Add(new InstalledApplication
{
Name = name,
Version = subKey?.GetValue("DisplayVersion")?.ToString(),
Publisher = subKey?.GetValue("Publisher")?.ToString()
});
}
}
}
return applications;
}
}
}

View File

@@ -0,0 +1,69 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Brand Colors -->
<Color x:Key="PrimaryColorValue">#1E3A8A</Color>
<!-- Storm blue -->
<Color x:Key="SecondaryColorValue">#6B21A8</Color>
<!-- Deep violet -->
<Color x:Key="BackgroundLightValue">#1F1F1F</Color>
<!-- Dark grey -->
<Color x:Key="BackgroundDarkValue">#121212</Color>
<Color x:Key="PaperLightValue">#2A2A2A</Color>
<Color x:Key="PaperDarkValue">#1E1E1E</Color>
<Color x:Key="TextLightValue">#E5E7EB</Color>
<!-- Light grey text -->
<Color x:Key="TextDarkValue">#F9FAFB</Color>
<!-- Slightly lighter -->
<Color x:Key="DrawerDarkValue">#1A1A1A</Color>
<Color x:Key="SelectedLightValue">#374151</Color>
<!-- Slate -->
<Color x:Key="SelectedDarkValue">#2A2A2A</Color>
<Color x:Key="LinkLightValue">#60A5FA</Color>
<Color x:Key="LinkDarkValue">#3B82F6</Color>
<Color x:Key="ErrorColorValue">#DC2626</Color>
<Color x:Key="WarningColorValue">#F59E0B</Color>
<Color x:Key="SuccessColorValue">#16A34A</Color>
<Color x:Key="InfoColorValue">#0EA5E9</Color>
<!-- SolidColorBrushes -->
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColorValue}" />
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource SecondaryColorValue}" />
<SolidColorBrush x:Key="BackgroundLightBrush" Color="{StaticResource BackgroundLightValue}" />
<SolidColorBrush x:Key="BackgroundDarkBrush" Color="{StaticResource BackgroundDarkValue}" />
<SolidColorBrush x:Key="PaperLightBrush" Color="{StaticResource PaperLightValue}" />
<SolidColorBrush x:Key="PaperDarkBrush" Color="{StaticResource PaperDarkValue}" />
<SolidColorBrush x:Key="TextLightBrush" Color="{StaticResource TextLightValue}" />
<SolidColorBrush x:Key="TextDarkBrush" Color="{StaticResource TextDarkValue}" />
<SolidColorBrush x:Key="DrawerDarkBrush" Color="{StaticResource DrawerDarkValue}" />
<SolidColorBrush x:Key="SelectedLightBrush" Color="{StaticResource SelectedLightValue}" />
<SolidColorBrush x:Key="SelectedDarkBrush" Color="{StaticResource SelectedDarkValue}" />
<SolidColorBrush x:Key="LinkLightBrush" Color="{StaticResource LinkLightValue}" />
<SolidColorBrush x:Key="LinkDarkBrush" Color="{StaticResource LinkDarkValue}" />
<SolidColorBrush x:Key="ErrorBrush" Color="{StaticResource ErrorColorValue}" />
<SolidColorBrush x:Key="WarningBrush" Color="{StaticResource WarningColorValue}" />
<SolidColorBrush x:Key="SuccessBrush" Color="{StaticResource SuccessColorValue}" />
<SolidColorBrush x:Key="InfoBrush" Color="{StaticResource InfoColorValue}" />
<!-- Aliases for Dynamic Resources -->
<SolidColorBrush x:Key="TextBrush" Color="{StaticResource TextLightValue}" />
<!-- CheckBox Styling Overrides -->
<SolidColorBrush x:Key="MaterialDesignCheckBoxCheckedBackground" Color="{StaticResource PrimaryColorValue}" />
<SolidColorBrush x:Key="MaterialDesignCheckBoxCheckedBorder" Color="{StaticResource PrimaryColorValue}" />
<SolidColorBrush x:Key="MaterialDesignCheckBoxCheckedGlyph" Color="{StaticResource TextDarkValue}" />
<SolidColorBrush x:Key="MaterialDesignCheckBoxForeground" Color="{StaticResource TextLightValue}" />
<SolidColorBrush x:Key="MaterialDesignCheckBoxDisabledForeground" Color="#6B7280" />
<!-- Muted grey -->
</ResourceDictionary>

View File

@@ -0,0 +1,42 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes">
<!-- Brand Colors -->
<Color x:Key="PrimaryColorValue">#2563EB</Color>
<!-- Tailwind Blue-600 -->
<Color x:Key="SecondaryColorValue">#9333EA</Color>
<!-- Tailwind Purple-600 -->
<Color x:Key="BackgroundColorValue">#1F2937</Color>
<!-- Slate-800 -->
<Color x:Key="TextColorValue">#F3F4F6</Color>
<!-- Gray-100 -->
<Color x:Key="SelectedColorValue">#374151</Color>
<!-- Slate-700 -->
<!-- Main window base background -->
<Color x:Key="WindowBackgroundColor">#1F2937</Color>
<!-- Match background -->
<SolidColorBrush x:Key="WindowBackgroundBrush" Color="{StaticResource WindowBackgroundColor}" />
<!-- Brushes -->
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColorValue}" />
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource SecondaryColorValue}" />
<SolidColorBrush x:Key="BackgroundBrush" Color="{StaticResource BackgroundColorValue}" />
<SolidColorBrush x:Key="TextBrush" Color="{StaticResource TextColorValue}" />
<SolidColorBrush x:Key="SelectedBrush" Color="{StaticResource SelectedColorValue}" />
<SolidColorBrush x:Key="TextDarkBrush" Color="#1F2937" />
<!-- MaterialDesign3 overrides -->
<SolidColorBrush x:Key="MaterialDesignControlCheckBoxCheckedBrush" Color="{StaticResource PrimaryColorValue}" />
<SolidColorBrush x:Key="MaterialDesignControlCheckBoxUncheckedBrush" Color="#666666" />
<SolidColorBrush x:Key="MaterialDesignControlCheckBoxRippleBrush" Color="{StaticResource PrimaryColorValue}" />
<SolidColorBrush x:Key="MaterialDesignCheckBoxForeground" Color="{StaticResource TextColorValue}" />
<!-- Optional: override background/paper color if you're using ColorZoneAssist -->
<SolidColorBrush x:Key="MaterialDesignPaper" Color="{StaticResource BackgroundColorValue}" />
<SolidColorBrush x:Key="MaterialDesignBody" Color="{StaticResource BackgroundColorValue}" />
<SolidColorBrush x:Key="MaterialDesignCardBackground" Color="{StaticResource BackgroundColorValue}" />
<SolidColorBrush x:Key="MaterialDesignCheckBoxForegroundDark" Color="{StaticResource TextColorValue}" />
</ResourceDictionary>

View File

@@ -0,0 +1,40 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes">
<!-- Brand Colors -->
<Color x:Key="PrimaryColorValue">#2563EB</Color>
<!-- Tailwind Blue-600 -->
<Color x:Key="SecondaryColorValue">#9333EA</Color>
<!-- Tailwind Purple-600 -->
<Color x:Key="BackgroundColorValue">#F9FAFB</Color>
<Color x:Key="TextColorValue">#1F2937</Color>
<Color x:Key="SelectedColorValue">#D1D5DB</Color>
<!-- Main window base background -->
<Color x:Key="WindowBackgroundColor">#F9FAFB</Color>
<!-- or #FFFFFF -->
<SolidColorBrush x:Key="WindowBackgroundBrush" Color="{StaticResource WindowBackgroundColor}" />
<!-- Brushes -->
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColorValue}" />
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource SecondaryColorValue}" />
<SolidColorBrush x:Key="BackgroundBrush" Color="{StaticResource BackgroundColorValue}" />
<SolidColorBrush x:Key="TextBrush" Color="{StaticResource TextColorValue}" />
<SolidColorBrush x:Key="SelectedBrush" Color="{StaticResource SelectedColorValue}" />
<SolidColorBrush x:Key="TextLightBrush" Color="#F9FAFB" />
<!-- MaterialDesign3 overrides -->
<SolidColorBrush x:Key="MaterialDesignControlCheckBoxCheckedBrush" Color="{StaticResource PrimaryColorValue}" />
<SolidColorBrush x:Key="MaterialDesignControlCheckBoxUncheckedBrush" Color="#B0B0B0" />
<SolidColorBrush x:Key="MaterialDesignControlCheckBoxRippleBrush" Color="{StaticResource PrimaryColorValue}" />
<SolidColorBrush x:Key="MaterialDesignCheckBoxForeground" Color="{StaticResource TextColorValue}" />
<!-- Optional: override background/paper color if you're using ColorZoneAssist -->
<SolidColorBrush x:Key="MaterialDesignPaper" Color="{StaticResource BackgroundColorValue}" />
<SolidColorBrush x:Key="MaterialDesignBody" Color="{StaticResource BackgroundColorValue}" />
<SolidColorBrush x:Key="MaterialDesignCardBackground" Color="{StaticResource BackgroundColorValue}" />
<SolidColorBrush x:Key="MaterialDesignCheckBoxForegroundLight" Color="{StaticResource TextColorValue}" />
</ResourceDictionary>

11
LD-SysInfo/config.json Normal file
View File

@@ -0,0 +1,11 @@
{
"ServerUrl": "https://sys.psg.net.au:8443",
"EnableLogging": true,
"KeepAlivePeriod": 30,
"SystemInfoInterval": 600,
"Auth": {
"Username": "testuser",
"Password": "testuser"
},
"ClientIdentifier": "f2fb7a38-622a-4ce9-86e8-a0e34d24564f"
}

86
LD_SysInfo.sln Normal file
View File

@@ -0,0 +1,86 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33103.201
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{50AAD861-2EF1-4EAB-80EB-20FECB15232C}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
Directory.Packages.props = Directory.Packages.props
exclusions.dic = exclusions.dic
NuGet.config = NuGet.config
Settings.XamlStyler = Settings.XamlStyler
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{EF62C430-6899-4C85-BFFC-D71632937A3B}"
ProjectSection(SolutionItems) = preProject
.github\dependabot.yml = .github\dependabot.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{AC2A502B-963E-4FD6-ACCA-8F96EDD4190F}"
ProjectSection(SolutionItems) = preProject
.github\workflows\build_app.yml = .github\workflows\build_app.yml
.github\workflows\code_coverage_comment.yml = .github\workflows\code_coverage_comment.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LD_SysInfo", "LD-SysInfo\LD_SysInfo.csproj", "{47F52A85-3C4C-AD42-3F23-0949D5AB652D}"
EndProject
Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "OversightInstaller", "OversightInstaller\OversightInstaller.wixproj", "{99BFBED9-D563-375C-FBD6-E11D0770B009}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Debug|ARM64.Build.0 = Debug|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Debug|x64.ActiveCfg = Debug|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Debug|x64.Build.0 = Debug|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Debug|x86.ActiveCfg = Debug|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Debug|x86.Build.0 = Debug|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Release|Any CPU.Build.0 = Release|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Release|ARM64.ActiveCfg = Release|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Release|ARM64.Build.0 = Release|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Release|x64.ActiveCfg = Release|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Release|x64.Build.0 = Release|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Release|x86.ActiveCfg = Release|Any CPU
{47F52A85-3C4C-AD42-3F23-0949D5AB652D}.Release|x86.Build.0 = Release|Any CPU
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Debug|Any CPU.ActiveCfg = Debug|x64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Debug|Any CPU.Build.0 = Debug|x64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Debug|ARM64.ActiveCfg = Debug|ARM64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Debug|ARM64.Build.0 = Debug|ARM64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Debug|x64.ActiveCfg = Debug|x64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Debug|x64.Build.0 = Debug|x64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Debug|x86.ActiveCfg = Debug|x86
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Debug|x86.Build.0 = Debug|x86
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Release|Any CPU.ActiveCfg = Release|x64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Release|Any CPU.Build.0 = Release|x64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Release|ARM64.ActiveCfg = Release|ARM64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Release|ARM64.Build.0 = Release|ARM64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Release|x64.ActiveCfg = Release|x64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Release|x64.Build.0 = Release|x64
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Release|x86.ActiveCfg = Release|x86
{99BFBED9-D563-375C-FBD6-E11D0770B009}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EF62C430-6899-4C85-BFFC-D71632937A3B} = {50AAD861-2EF1-4EAB-80EB-20FECB15232C}
{AC2A502B-963E-4FD6-ACCA-8F96EDD4190F} = {EF62C430-6899-4C85-BFFC-D71632937A3B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {490F259C-CD1B-446C-A893-CD94D4954AE9}
EndGlobalSection
EndGlobal

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

View File

@@ -0,0 +1,35 @@
<Project Sdk="WixToolset.Sdk/5.0.2">
<PropertyGroup>
<Platform Condition=" '$(Platform)' == '' ">x64</Platform>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<SuppressAllWarnings>false</SuppressAllWarnings>
<Pedantic>true</Pedantic>
</PropertyGroup>
<ItemGroup>
<Compile Include="Product.wxs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Assets" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\LDShortcut.ico" />
<Content Include="Assets\windowsdesktop-runtime-8.0.13-win-x64.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LD-SysInfo\LD-SysInfo.csproj">
<Name>LD-SysInfo</Name>
<Project>{604bf69a-6bff-4637-88b5-78936c7eb68f}</Project>
<Private>True</Private>
<DoNotHarvest>True</DoNotHarvest>
<RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
<RefTargetDir>INSTALLFOLDER</RefTargetDir>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="WixToolset.UI.wixext" Version="5.0.2" />
</ItemGroup>
<Target Name="CollectSuggestedVisualStudioComponentIds" />
</Project>

View File

@@ -0,0 +1,36 @@
<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package
Name="LD SystemInfo"
Language="1033"
Version="1.0.0.1"
Manufacturer="Paragon Systems Group"
UpgradeCode="9D09C022-75B6-47B5-A211-59B7CE8E7B6B"
InstallerVersion="500">
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<Feature Id="MainFeature" Title="LD SysInfo" Level="1">
<ComponentRef Id="MainExecutable" />
<ComponentRef Id="AssetsComponent" />
</Feature>
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="LD_SysInfo">
<Directory Id="AppFolder" Name="App">
<Component Id="MainExecutable" Guid="B769A39D-4807-4D12-B0CB-808E037DFA21" >
<File Id="MainEXE" Name="LD-SysInfo.exe" Source="LD-SysInfo.exe" KeyPath="yes" />
<RegistryValue Root="HKLM" Key="Software\LD_SysInfo" Name="Installed" Type="integer" Value="1" />
</Component>
</Directory>
<Directory Id="AssetsFolder" Name="Assets">
<Component Id="AssetsComponent" Guid="5E6F1D88-36E6-44F1-BD4C-BFC58B47A10D">
<File Id="TrayIconIco" Name="trayicon.ico" Source="Assets\trayicon.ico" />
<File Id="LDShortcutIconFile" Name="LDShortcut.ico" Source="Assets\LDShortcut.ico" KeyPath="yes" />
</Component>
</Directory>
</Directory>
</StandardDirectory>
</Package>
</Wix>

39
NuGet.config Normal file
View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- `clear` ensures no additional sources are inherited from another config file. -->
<packageSources>
<clear />
<!-- `key` can be any identifier for your source. -->
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<!-- Define mappings by adding package patterns beneath the target source. -->
<packageSourceMapping>
<!-- key value for <packageSource> should match key values from <packageSources> element -->
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
<!--
As an example, you can define multiple package sources and map particular nuget packages from each source.
This defines the package sources, nuget.org and contoso.com and maps all NuGet packages with Contoso.* to it.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="contoso.com" value="https://contoso.com/packages/" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="contoso.com">
<package pattern="Contoso.*" />
</packageSource>
</packageSourceMapping>
</configuration>
-->

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,42 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<DirectoryRef Id="INSTALLFOLDER">
<Component Id="cmpUPL4zyYGpfRaDjFUWL7r0.VHZKs" Guid="F0E00FBB-B63A-4DED-A177-8E79C2A4C1CB">
<File Id="fil_giGueDAhkHg7IGVpG9lWyNnt9g" KeyPath="yes" Source="C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir\config.json" />
</Component>
<Component Id="cmpzRl5Z2HPRUNvTukjrTa1wMNAT2w" Guid="5083C4D6-8419-4182-90D1-FC8B23654508">
<File Id="filqJfymN5jNIMZnPNQcknB.hTaPkg" KeyPath="yes" Source="C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir\D3DCompiler_47_cor3.dll" />
</Component>
<Component Id="cmpvbtd5xmG17XXCLo8AarNuPfLQrI" Guid="EB8254A7-5D7C-46BE-B00C-5F9036CF9D45">
<File Id="filpqttPPxEAoIzCzmZquD9RoAwNNo" KeyPath="yes" Source="C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir\PSG-Oversight.exe" />
</Component>
<Component Id="cmp65oxzC1SOi0_nD1EF9uJcyf7AUc" Guid="03B24C6F-09A0-4CAF-95EB-2B5D8A53441A">
<File Id="filVjSW8_9Qs10tFdNDgZCMVzXvv1M" KeyPath="yes" Source="C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir\PSG-Oversight.pdb" />
</Component>
<Component Id="cmpve3SyLDvkvTkjO0hEsFmzi.34p8" Guid="0FEA7DC2-423C-4D23-A425-36FCE770A4D2">
<File Id="filQdDCeDxtlE0wp8NcxWuIdgZ5KUw" KeyPath="yes" Source="C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir\PenImc_cor3.dll" />
</Component>
<Component Id="cmp9p11pVvfHzWT3MHfTpVVHhJz4Mc" Guid="DC58272B-9CA9-4FAB-8629-6D9952F788F6">
<File Id="filCsDVY6fSKowrx91l2E6LXiyifzs" KeyPath="yes" Source="C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir\PresentationNative_cor3.dll" />
</Component>
<Component Id="cmpIlHre.6vyW_rNEMiyejz2LzF40A" Guid="D47B8F28-E758-4808-A275-EAC4B90E46F4">
<File Id="filw6DYkpHZRX_cobuySX3SfuRrVBA" KeyPath="yes" Source="C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir\vcruntime140_cor3.dll" />
</Component>
<Component Id="cmppJtSGEjpkqcE6B80qiNcp7uzu_E" Guid="7296EA6A-9666-4934-896D-EB3CFE7DADD9">
<File Id="filBmC5aLvP1Wdue27MeciP9pIZ9E0" KeyPath="yes" Source="C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir\wpfgfx_cor3.dll" />
</Component>
</DirectoryRef>
</Fragment>
<Fragment>
<ComponentGroup Id="AppFiles">
<ComponentRef Id="cmpUPL4zyYGpfRaDjFUWL7r0.VHZKs" />
<ComponentRef Id="cmpzRl5Z2HPRUNvTukjrTa1wMNAT2w" />
<ComponentRef Id="cmpvbtd5xmG17XXCLo8AarNuPfLQrI" />
<ComponentRef Id="cmp65oxzC1SOi0_nD1EF9uJcyf7AUc" />
<ComponentRef Id="cmpve3SyLDvkvTkjO0hEsFmzi.34p8" />
<ComponentRef Id="cmp9p11pVvfHzWT3MHfTpVVHhJz4Mc" />
<ComponentRef Id="cmpIlHre.6vyW_rNEMiyejz2LzF40A" />
<ComponentRef Id="cmppJtSGEjpkqcE6B80qiNcp7uzu_E" />
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -0,0 +1,34 @@
<Project Sdk="WixToolset.Sdk/6.0.0">
<ItemGroup>
<PackageReference Include="WixToolset.UI.wixext" />
</ItemGroup>
<PropertyGroup>
<!-- Force embedding files into the MSI (no external .cab file) -->
<EmbedCab>true</EmbedCab>
</PropertyGroup>
<PropertyGroup>
<WixExtensionInclude>WixToolset.UI.wixext</WixExtensionInclude>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants>PublishDir=C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Content Include="Assets\DLShortcut.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\trayicon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<WixSource Include="HarvestedFiles.wxs" />
</ItemGroup>
<ItemGroup>
<Content Include="bpl.rtf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="Assets\windowsdesktop-runtime-8.0.13-win-x64.exe" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,64 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
<Package Name="Oversight" Manufacturer="Paragon Systems Group" Version="1.0.0.0" UpgradeCode="e0e26d46-ed26-425e-a1bb-9bc76523ae6b">
<MajorUpgrade DowngradeErrorMessage="A newer version of this application is already installed." />
<WixVariable
Id="WixUILicenseRtf"
Value="bpl.rtf"
/>
<!-- 🔍 Properties -->
<Property Id="ARPPRODUCTICON" Value="DLShortcutIcon" />
<Property Id="SERVER_URL" Value="http://your-default-server/api/register" />
<Property Id="APP_EXECUTABLE" Value="PSG-Oversight.exe" />
<!-- 🔧 UI -->
<ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLFOLDER" />
<!-- 🎨 Icon for Add/Remove Programs -->
<Icon Id="DLShortcutIcon" SourceFile="Assets\DLShortcut.ico" />
<!-- 📂 Installation Directory -->
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="Oversight">
<!-- 📁 Assets Folder -->
</Directory>
</StandardDirectory>
<ComponentGroupRef Id="AppFiles" />
<!-- 📁 Start Menu Shortcut -->
<StandardDirectory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="Oversight">
<Component Id="StartMenuShortcutComponent" Guid="4C5466A9-CE33-48AE-B80C-08915A864DF2">
<Shortcut Id="StartMenuShortcut"
Directory="ApplicationProgramsFolder"
Name="Oversight"
WorkingDirectory="INSTALLFOLDER"
Target="[INSTALLFOLDER][APP_EXECUTABLE]"
Icon="DLShortcutIcon"
IconIndex="0" />
<RemoveFile Id="RemoveStartMenuShortcut" Name="Oversight.lnk" On="uninstall" />
<RemoveFolder Id="RemoveStartMenuFolder" On="uninstall" />
<RegistryValue Root="HKCU" Key="Software\Oversight" Name="StartMenuShortcut" Type="integer" Value="1" KeyPath="yes"/>
</Component>
</Directory>
</StandardDirectory>
<!-- 📦 Feature References -->
<Feature Id="Main" Title="DL SysInfo" Level="1">
<ComponentGroupRef Id="AppFiles" />
<ComponentRef Id="StartMenuShortcutComponent" />
</Feature>
<!-- 💿 Media -->
<Media Id="1" Cabinet="embedded.cab" EmbedCab="yes" />
</Package>
</Wix>

View File

@@ -0,0 +1,19 @@
{\rtf1\ansi\deff0
{\fonttbl{\f0 Calibri;}}
\f0\fs22
\b END USER LICENSE AGREEMENT (EULA)\b0\line
Effective Date: April 2025\line\line
By using this app ("the Software"), you agree to the following:\line\line
\b 1. Use at Your Own Risk\b0\line
You use this Software entirely at your own risk. We do not guarantee that it works perfectly or at all.\line\line
\b 2. No Warranty\b0\line
This app is provided "as-is". There are no warranties, no guarantees, no promises - express or implied - including (but not limited to) any implied warranties of merchantability, fitness for a particular purpose, or non-infringement.\line\line
\b 3. No Liability\b0\line
We're not responsible for any damage, data loss, downtime, corruption, stress, or existential dread you may suffer from using this app.\line
If you somehow lose data, money, your job, your marriage, or your last shred of hope - that's on you.\line\line
\b 4. Acceptance of Terms\b0\line
By using the app, you're agreeing to these terms. If you don't agree, uninstall it and walk away.\line\line
\b 5. Australian Legal Stuff\b0\line
Nothing in this EULA excludes your rights under Australian Consumer Law that can't legally be excluded - but besides that, seriously, you get no special treatment.\line\line
Continued use of the app means you accept all of this.\line
}

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1,48 @@
# psg-oversight-app
# WPF app template
This template creates a full WPF application, along with unit tests.
## Template
Create a new app in your current directory by running.
```cli
> dotnet new keboo.wpf
```
### Parameters
[Default template options](https://learn.microsoft.com/dotnet/core/tools/dotnet-new#options)
## Key Features
### Generic Host Dependency Injection
[Docs](https://learn.microsoft.com/dotnet/core/extensions/generic-host?tabs=appbuilder&WT.mc_id=DT-MVP-5003472)
### Centralized Package Management
[Docs](https://learn.microsoft.com/nuget/consume-packages/Central-Package-Management?WT.mc_id=DT-MVP-5003472)
### Build Customization
[Docs](https://learn.microsoft.com/visualstudio/msbuild/customize-by-directory?view=vs-2022&WT.mc_id=DT-MVP-5003472)
### CommunityToolkit MVVM
[Docs](https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/?WT.mc_id=DT-MVP-5003472)
### Material Design in XAML
[Repo](https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit)
### .editorconfig formatting
[Docs](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/code-style-rule-options?WT.mc_id=DT-MVP-5003472)
### Testing with Moq.AutoMocker
[Repo](https://github.com/moq/Moq.AutoMocker)
### NuGet package source mapping
[Docs](https://learn.microsoft.com/nuget/consume-packages/package-source-mapping?WT.mc_id=DT-MVP-5003472)
### Dependabot auto updating of dependencies
[Docs](https://docs.github.com/code-security/dependabot/dependabot-version-updates)
Auto merging of these PRs done with [fastify/github-action-merge-dependabot](https://github.com/fastify/github-action-merge-dependabot).
### GitHub Actions workflow with code coverage reporting
[Docs](https://docs.github.com/actions).
Code coverage provided by [coverlet-coverage/coverlet](https://github.com/coverlet-coverage/coverlet).
Code coverage report provided by [danielpalme/ReportGenerator-GitHub-Action](https://github.com/danielpalme/ReportGenerator-GitHub-Action).
The coverage reports are posted as "stciky" PR comments provided by [marocchino/sticky-pull-request-comment](https://github.com/marocchino/sticky-pull-request-comment)

44
Settings.XamlStyler Normal file
View File

@@ -0,0 +1,44 @@
{
"IndentSize": 2,
"AttributesTolerance": 2,
"KeepFirstAttributeOnSameLine": true,
"MaxAttributeCharactersPerLine": 0,
"MaxAttributesPerLine": 1,
"NewlineExemptionElements": "RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, MultiTrigger, DataTrigger, MultiDataTrigger, Condition, Setter, SolidColorBrush, Pen",
"SeparateByGroups": false,
"AttributeIndentation": 0,
"AttributeIndentationStyle": "Spaces",
"RemoveDesignTimeReferences": false,
"EnableAttributeReordering": true,
"AttributeOrderingRuleGroups": [
"x:Class",
"xmlns, xmlns:x",
"xmlns:*",
"x:Key, Key, x:Name, Name, x:Uid, Uid, Title",
"Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom",
"Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight",
"Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex",
"*:*, *",
"PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint",
"*:Freeze, mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText",
"Storyboard.*, From, To, Duration",
"TargetType",
"BasedOn"
],
"FirstLineAttributes": "",
"OrderAttributesByName": true,
"PutEndingBracketOnNewLine": false,
"RemoveEndingTagOfEmptyElement": true,
"SpaceBeforeClosingSlash": true,
"RootElementLineBreakRule": "Always",
"ReorderVSM": "First",
"ReorderGridChildren": false,
"ReorderCanvasChildren": false,
"ReorderSetters": "TargetNameThenProperty",
"FormatMarkupExtension": true,
"NoNewLineMarkupExtensions": "x:Bind, Binding, TemplateBinding, x:Static, DynamicResource, ComponentResourceKey, iconPacks:Modern, iconPacks:Material",
"ThicknessSeparator": "Comma",
"ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin",
"FormatOnSave": false,
"CommentPadding": 1
}

0
exclusions.dic Normal file
View File