init
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
name: .NET Core Desktop
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
configuration: [Debug]
|
||||||
|
|
||||||
|
runs-on: windows-latest # For a list of available runner types, refer to
|
||||||
|
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code.
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: 5.0.x
|
||||||
|
|
||||||
|
- name: Add msbuild to PATH
|
||||||
|
uses: microsoft/setup-msbuild@v1.0.2
|
||||||
|
|
||||||
|
- name: Setup Nuget
|
||||||
|
uses: Nuget/setup-nuget@v1.0.5
|
||||||
|
|
||||||
|
- name: Restore nuget packages
|
||||||
|
run: nuget restore src/MonsterMonitor.sln
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: msbuild src/MonsterMonitor.sln /p:Configuration=${{ matrix.configuration }} /p:OutputPath=/app
|
||||||
|
|
||||||
|
- name: Pack
|
||||||
|
run: cd /app && 7z a ./MonsterMonitor.zip /app/*
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: ${{ github.ref }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
|
||||||
|
- name: Upload Release Asset
|
||||||
|
id: upload-release-asset
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||||
|
asset_path: /app/MonsterMonitor.zip
|
||||||
|
asset_name: MonsterMonitor.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
+294
@@ -0,0 +1,294 @@
|
|||||||
|
# Huge kudos to all contributors to https://github.com/github/gitignore which is where
|
||||||
|
# this file came from.
|
||||||
|
|
||||||
|
syntax: glob
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
#settings
|
||||||
|
myentunnel.ini
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
|
||||||
|
# Visual Studio 2015 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# DNX
|
||||||
|
project.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding add-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/packages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/packages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/packages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignoreable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
#*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
node_modules/
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# Visual Studio design time assembly cache
|
||||||
|
*.cache
|
||||||
|
|
||||||
|
#Aeroclub lib catalog
|
||||||
|
[Ll]ib/
|
||||||
|
src/Aeroclub.WCF.CommonGateway/!Web.config
|
||||||
|
src/Aeroclub.WCF.CommonGateway/Web.config
|
||||||
|
src/Aeroclub.WCF.CommonGateway/!NLog.config
|
||||||
|
src/Aeroclub.WCF.CommonGateway/NLog.config
|
||||||
|
|
||||||
|
*.ncrunchproject
|
||||||
|
|
||||||
|
# WSCommon logs
|
||||||
|
src/Aeroclub.WCF.CommonGateway/logs/
|
||||||
|
build/nuget/*.exe
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
tools/Confluence.dll*
|
||||||
|
.sonarqube/*
|
||||||
|
paket.lock
|
||||||
|
test/*
|
||||||
|
.paket/Paket/tools/paket.exe
|
||||||
|
coverage/*
|
||||||
|
src/WscommonDoc/Help/*.json
|
||||||
|
src/WscommonDoc/Help/*.json
|
||||||
|
src/WscommonDoc/Help/*
|
||||||
|
src/WscommonDoc/icons/Help.png
|
||||||
|
src/WscommonDoc/Doc/Working/*
|
||||||
|
src/WscommonDoc/Doc/*
|
||||||
|
.paket/*
|
||||||
|
*.env
|
||||||
|
.teamcity/target/*
|
||||||
|
docfx_project/_site/*
|
||||||
|
docfx_project/api/*.yml
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1.0.0
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 16
|
||||||
|
VisualStudioVersion = 16.0.32929.386
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonsterMonitor", "src\MonsterMonitor.csproj", "{7D6104C6-6AEF-4D31-8463-FCEABDF1577A}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{7D6104C6-6AEF-4D31-8463-FCEABDF1577A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7D6104C6-6AEF-4D31-8463-FCEABDF1577A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7D6104C6-6AEF-4D31-8463-FCEABDF1577A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7D6104C6-6AEF-4D31-8463-FCEABDF1577A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
@echo off
|
||||||
|
IF %1.==. GOTO NoCommitMessage
|
||||||
|
echo %~1
|
||||||
|
pause
|
||||||
|
|
||||||
|
for /f "tokens=1 delims=." %%G IN (.semver.txt) DO set MAJOR=%%G
|
||||||
|
for /f "tokens=2 delims=." %%G IN (.semver.txt) DO set MINOR=%%G
|
||||||
|
for /f "tokens=3 delims=." %%G IN (.semver.txt) DO set PATCH=%%G
|
||||||
|
set VERSION_CURRENT=%MAJOR:~0,1%.%MINOR:~0,1%.%PATCH%
|
||||||
|
echo Current: %VERSION_CURRENT%
|
||||||
|
|
||||||
|
SET /a PATCH1 = %PATCH%+1
|
||||||
|
set VERSION=%MAJOR:~0,1%.%MINOR:~0,1%.%PATCH1%
|
||||||
|
echo %VERSION%>.semver.txt
|
||||||
|
echo Updating to %VERSION%
|
||||||
|
|
||||||
|
call tools\SetVersion.cmd %VERSION%
|
||||||
|
|
||||||
|
git add .
|
||||||
|
git commit -m "%~1"
|
||||||
|
|
||||||
|
git tag -m "Version v%VERSION%: %~1" -a v%VERSION%
|
||||||
|
git push origin v%VERSION%
|
||||||
|
|
||||||
|
git push --tags
|
||||||
|
GOTO End
|
||||||
|
pause
|
||||||
|
|
||||||
|
:NoCommitMessage
|
||||||
|
echo Please specify commit message: %0 "commit message"
|
||||||
|
GOTO End
|
||||||
|
|
||||||
|
:End
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||||
|
</startup>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
auth none
|
||||||
|
socks -i127.0.0.1 -p7683
|
||||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||||
|
<Costura />
|
||||||
|
</Weavers>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||||
|
<xs:element name="Weavers">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:all>
|
||||||
|
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:all>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX86Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX64Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinArm64Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:all>
|
||||||
|
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="DisableCompression" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="DisableCleanup" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="DisableEventSubscription" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="ExcludeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="IncludeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="UnmanagedWinX86Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="UnmanagedWinX64Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="UnmanagedWinArm64Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="PreloadOrder" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:all>
|
||||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:schema>
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor.Models
|
||||||
|
{
|
||||||
|
public sealed class AppSettings
|
||||||
|
{
|
||||||
|
private const string ProtectedPrefix = "dpapi:";
|
||||||
|
|
||||||
|
public string SshHost { get; set; } = "127.0.0.1";
|
||||||
|
public int SshPort { get; set; } = 22;
|
||||||
|
public string SshUsername { get; set; } = "user";
|
||||||
|
public string SshPasswordProtected { get; set; } = string.Empty;
|
||||||
|
public string SystemPasswordProtected { get; set; } = string.Empty;
|
||||||
|
public string ThreeProxyPasswordProtected { get; set; } = string.Empty;
|
||||||
|
public bool SavePassword { get; set; } = false;
|
||||||
|
public int RemotePort { get; set; } = 3328;
|
||||||
|
public int LocalPort { get; set; } = 7829;
|
||||||
|
public int MaxPingFailures { get; set; } = 3;
|
||||||
|
public int ReconnectTimeoutSec { get; set; } = 45;
|
||||||
|
public string SsProcessPath { get; set; } = Path.Combine("App_Data", "ss", "ss.exe");
|
||||||
|
public string SsArguments { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string GetPassword()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(SshPasswordProtected))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SshPasswordProtected.StartsWith(ProtectedPrefix, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return SshPasswordProtected;
|
||||||
|
}
|
||||||
|
|
||||||
|
var protectedData = Convert.FromBase64String(SshPasswordProtected.Substring(ProtectedPrefix.Length));
|
||||||
|
var unprotected = ProtectedData.Unprotect(protectedData, null, DataProtectionScope.CurrentUser);
|
||||||
|
return Encoding.UTF8.GetString(unprotected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPassword(string password)
|
||||||
|
{
|
||||||
|
if (!SavePassword || string.IsNullOrEmpty(password))
|
||||||
|
{
|
||||||
|
SshPasswordProtected = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(password);
|
||||||
|
var protectedData = ProtectedData.Protect(bytes, null, DataProtectionScope.CurrentUser);
|
||||||
|
SshPasswordProtected = ProtectedPrefix + Convert.ToBase64String(protectedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSystemPassword()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(SystemPasswordProtected))
|
||||||
|
{
|
||||||
|
return "STerra";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SystemPasswordProtected.StartsWith(ProtectedPrefix, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return SystemPasswordProtected;
|
||||||
|
}
|
||||||
|
|
||||||
|
var protectedData = Convert.FromBase64String(SystemPasswordProtected.Substring(ProtectedPrefix.Length));
|
||||||
|
var unprotected = ProtectedData.Unprotect(protectedData, null, DataProtectionScope.CurrentUser);
|
||||||
|
return Encoding.UTF8.GetString(unprotected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSystemPassword(string password)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(password))
|
||||||
|
{
|
||||||
|
SystemPasswordProtected = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(password);
|
||||||
|
var protectedData = ProtectedData.Protect(bytes, null, DataProtectionScope.CurrentUser);
|
||||||
|
SystemPasswordProtected = ProtectedPrefix + Convert.ToBase64String(protectedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetThreeProxyPassword()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ThreeProxyPasswordProtected))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ThreeProxyPasswordProtected.StartsWith(ProtectedPrefix, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return ThreeProxyPasswordProtected;
|
||||||
|
}
|
||||||
|
|
||||||
|
var protectedData = Convert.FromBase64String(ThreeProxyPasswordProtected.Substring(ProtectedPrefix.Length));
|
||||||
|
var unprotected = ProtectedData.Unprotect(protectedData, null, DataProtectionScope.CurrentUser);
|
||||||
|
return Encoding.UTF8.GetString(unprotected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetThreeProxyPassword(string password)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(password))
|
||||||
|
{
|
||||||
|
ThreeProxyPasswordProtected = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(password);
|
||||||
|
var protectedData = ProtectedData.Protect(bytes, null, DataProtectionScope.CurrentUser);
|
||||||
|
ThreeProxyPasswordProtected = ProtectedPrefix + Convert.ToBase64String(protectedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetSettingsPath()
|
||||||
|
{
|
||||||
|
var directory = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"MonsterMonitor");
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
return Path.Combine(directory, "settings.ini");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AppSettings Load()
|
||||||
|
{
|
||||||
|
var path = GetSettingsPath();
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return new AppSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new AppSettings();
|
||||||
|
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var line in File.ReadAllLines(path))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(line) || line.TrimStart().StartsWith("#", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var separator = line.IndexOf('=');
|
||||||
|
if (separator <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = line.Substring(0, separator).Trim();
|
||||||
|
var value = line.Substring(separator + 1).Trim();
|
||||||
|
map[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.SshHost = Get(map, nameof(SshHost), result.SshHost);
|
||||||
|
result.SshPort = GetInt(map, nameof(SshPort), result.SshPort);
|
||||||
|
result.SshUsername = Get(map, nameof(SshUsername), result.SshUsername);
|
||||||
|
result.SshPasswordProtected = Get(map, nameof(SshPasswordProtected), result.SshPasswordProtected);
|
||||||
|
result.SystemPasswordProtected = Get(map, nameof(SystemPasswordProtected), result.SystemPasswordProtected);
|
||||||
|
result.ThreeProxyPasswordProtected = Get(map, nameof(ThreeProxyPasswordProtected), result.ThreeProxyPasswordProtected);
|
||||||
|
result.SavePassword = GetBool(map, nameof(SavePassword), result.SavePassword);
|
||||||
|
result.RemotePort = GetInt(map, nameof(RemotePort), result.RemotePort);
|
||||||
|
result.LocalPort = GetInt(map, nameof(LocalPort), result.LocalPort);
|
||||||
|
result.MaxPingFailures = GetInt(map, nameof(MaxPingFailures), result.MaxPingFailures);
|
||||||
|
result.ReconnectTimeoutSec = GetInt(map, nameof(ReconnectTimeoutSec), result.ReconnectTimeoutSec);
|
||||||
|
result.SsProcessPath = Get(map, nameof(SsProcessPath), result.SsProcessPath);
|
||||||
|
result.SsArguments = Get(map, nameof(SsArguments), result.SsArguments);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(result.SystemPasswordProtected))
|
||||||
|
{
|
||||||
|
result.SetSystemPassword("STerra");
|
||||||
|
result.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
var path = GetSettingsPath();
|
||||||
|
var lines = new[]
|
||||||
|
{
|
||||||
|
$"{nameof(SshHost)}={SshHost}",
|
||||||
|
$"{nameof(SshPort)}={SshPort}",
|
||||||
|
$"{nameof(SshUsername)}={SshUsername}",
|
||||||
|
$"{nameof(SshPasswordProtected)}={SshPasswordProtected}",
|
||||||
|
$"{nameof(SystemPasswordProtected)}={SystemPasswordProtected}",
|
||||||
|
$"{nameof(ThreeProxyPasswordProtected)}={ThreeProxyPasswordProtected}",
|
||||||
|
$"{nameof(SavePassword)}={SavePassword}",
|
||||||
|
$"{nameof(RemotePort)}={RemotePort}",
|
||||||
|
$"{nameof(LocalPort)}={LocalPort}",
|
||||||
|
$"{nameof(MaxPingFailures)}={MaxPingFailures}",
|
||||||
|
$"{nameof(ReconnectTimeoutSec)}={ReconnectTimeoutSec}",
|
||||||
|
$"{nameof(SsProcessPath)}={SsProcessPath}",
|
||||||
|
$"{nameof(SsArguments)}={SsArguments}"
|
||||||
|
};
|
||||||
|
File.WriteAllLines(path, lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Get(IDictionary<string, string> map, string key, string defaultValue)
|
||||||
|
{
|
||||||
|
return map.TryGetValue(key, out var value) ? value : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetInt(IDictionary<string, string> map, string key, int defaultValue)
|
||||||
|
{
|
||||||
|
if (map.TryGetValue(key, out var value) && int.TryParse(value, out var parsed))
|
||||||
|
{
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool GetBool(IDictionary<string, string> map, string key, bool defaultValue)
|
||||||
|
{
|
||||||
|
if (map.TryGetValue(key, out var value) && bool.TryParse(value, out var parsed))
|
||||||
|
{
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net472</TargetFramework>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
<LangVersion>7.3</LangVersion>
|
||||||
|
<RootNamespace>SshTunnelMonitor</RootNamespace>
|
||||||
|
<AssemblyName>MonsterMonitor</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Costura.Fody" Version="6.0.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Fody" Version="6.9.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<Reference Include="System.Security" />
|
||||||
|
<PackageReference Include="SSH.NET" Version="2025.1.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="App_Data\ss\ss.cfg">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="App_Data\ss\ss.exe">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="logistics_tunnel_icon_148216.ico">
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using SshTunnelMonitor.UI;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
[STAThread]
|
||||||
|
private static void Main()
|
||||||
|
{
|
||||||
|
Application.ThreadException += (sender, args) =>
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
args.Exception.ToString(),
|
||||||
|
"Необработанное исключение UI-потока",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Error);
|
||||||
|
};
|
||||||
|
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
args.ExceptionObject.ToString(),
|
||||||
|
"Критическое исключение",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Error);
|
||||||
|
};
|
||||||
|
|
||||||
|
Application.EnableVisualStyles();
|
||||||
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
Application.Run(new MainForm());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using SshTunnelMonitor.Models;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor.Services
|
||||||
|
{
|
||||||
|
public sealed class AppController : IDisposable
|
||||||
|
{
|
||||||
|
private readonly LogService _log;
|
||||||
|
private readonly PowerManagementService _power;
|
||||||
|
private readonly SsConfigService _ssConfig;
|
||||||
|
private ProcessMonitorService _processMonitor;
|
||||||
|
private SshTunnelService _sshTunnel;
|
||||||
|
|
||||||
|
public AppController(LogService log, PowerManagementService power)
|
||||||
|
{
|
||||||
|
_log = log;
|
||||||
|
_power = power;
|
||||||
|
_ssConfig = new SsConfigService(_log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(AppSettings settings)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
|
||||||
|
_power.PreventSleep();
|
||||||
|
_processMonitor = new ProcessMonitorService(_log);
|
||||||
|
_sshTunnel = new SshTunnelService(_log);
|
||||||
|
|
||||||
|
_ssConfig.EnsureConfig(settings);
|
||||||
|
_processMonitor.Start(settings.SsProcessPath, settings.SsArguments);
|
||||||
|
_sshTunnel.Start(settings);
|
||||||
|
|
||||||
|
_log.Info("Сервисы приложения запущены.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_sshTunnel?.Dispose();
|
||||||
|
_sshTunnel = null;
|
||||||
|
|
||||||
|
_processMonitor?.Dispose();
|
||||||
|
_processMonitor = null;
|
||||||
|
|
||||||
|
_power.RestoreDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using SshTunnelMonitor.Models;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor.Services
|
||||||
|
{
|
||||||
|
public class AuthMonitor: IAuthMonitor
|
||||||
|
{
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
static extern IntPtr GetForegroundWindow();
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
|
||||||
|
|
||||||
|
private readonly AppSettings _settings;
|
||||||
|
|
||||||
|
public AuthMonitor(AppSettings settings)
|
||||||
|
{
|
||||||
|
_settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetActiveWindowTitle()
|
||||||
|
{
|
||||||
|
const int nChars = 256;
|
||||||
|
var buff = new StringBuilder(nChars);
|
||||||
|
var handle = GetForegroundWindow();
|
||||||
|
return GetWindowText(handle, buff, nChars) > 0 ? buff.ToString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartMonitor()
|
||||||
|
{
|
||||||
|
_ = Task.Run(async () => await CheckProcess());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckProcess()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process[] processlist = Process.GetProcesses();
|
||||||
|
foreach (Process process in processlist)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(process.MainWindowTitle) && process.MainWindowTitle.Contains("XAuth request"))
|
||||||
|
{
|
||||||
|
SetForegroundWindow(process.MainWindowHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var activeWindowTitle = GetActiveWindowTitle();
|
||||||
|
if (activeWindowTitle?.Contains("XAuth request") == true)
|
||||||
|
{
|
||||||
|
SendKeys.SendWait(_settings.GetSystemPassword());
|
||||||
|
SendKeys.SendWait("{ENTER}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(ex.Message + ex.StackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAuthMonitor
|
||||||
|
{
|
||||||
|
Task StartMonitor();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor.Services
|
||||||
|
{
|
||||||
|
public enum LogLevel
|
||||||
|
{
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warn,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class LogEntry
|
||||||
|
{
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public LogLevel Level { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class LogService
|
||||||
|
{
|
||||||
|
public event Action<LogEntry> LogReceived;
|
||||||
|
|
||||||
|
public void Log(LogLevel level, string message)
|
||||||
|
{
|
||||||
|
LogReceived?.Invoke(new LogEntry
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Now,
|
||||||
|
Level = level,
|
||||||
|
Message = message ?? string.Empty
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Info(string message) => Log(LogLevel.Info, message);
|
||||||
|
public void Warn(string message) => Log(LogLevel.Warn, message);
|
||||||
|
public void Error(string message) => Log(LogLevel.Error, message);
|
||||||
|
public void Debug(string message) => Log(LogLevel.Debug, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor.Services
|
||||||
|
{
|
||||||
|
public sealed class PowerManagementService
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
private enum ExecutionState : uint
|
||||||
|
{
|
||||||
|
ES_SYSTEM_REQUIRED = 0x00000001,
|
||||||
|
ES_DISPLAY_REQUIRED = 0x00000002,
|
||||||
|
ES_CONTINUOUS = 0x80000000
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern ExecutionState SetThreadExecutionState(ExecutionState flags);
|
||||||
|
|
||||||
|
public void PreventSleep()
|
||||||
|
{
|
||||||
|
SetThreadExecutionState(
|
||||||
|
ExecutionState.ES_CONTINUOUS |
|
||||||
|
ExecutionState.ES_SYSTEM_REQUIRED |
|
||||||
|
ExecutionState.ES_DISPLAY_REQUIRED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RestoreDefault()
|
||||||
|
{
|
||||||
|
SetThreadExecutionState(ExecutionState.ES_CONTINUOUS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor.Services
|
||||||
|
{
|
||||||
|
public sealed class ProcessMonitorService : IDisposable
|
||||||
|
{
|
||||||
|
private readonly LogService _log;
|
||||||
|
private readonly object _sync = new object();
|
||||||
|
private readonly Timer _watchdogTimer;
|
||||||
|
private Process _process;
|
||||||
|
private string _path;
|
||||||
|
private string _arguments;
|
||||||
|
private bool _stopping;
|
||||||
|
|
||||||
|
public ProcessMonitorService(LogService log)
|
||||||
|
{
|
||||||
|
_log = log;
|
||||||
|
_watchdogTimer = new Timer(WatchdogTick, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(string path, string arguments)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_path = path;
|
||||||
|
_arguments = arguments ?? string.Empty;
|
||||||
|
_stopping = false;
|
||||||
|
EnsureStarted();
|
||||||
|
_watchdogTimer.Change(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureStarted()
|
||||||
|
{
|
||||||
|
if (IsProcessRunning(_process))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_process != null)
|
||||||
|
{
|
||||||
|
_process.Exited -= ProcessOnExited;
|
||||||
|
_process.Dispose();
|
||||||
|
}
|
||||||
|
_process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = _path,
|
||||||
|
Arguments = _arguments,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
//_process.Exited += ProcessOnExited;
|
||||||
|
_process.Start();
|
||||||
|
_log.Info("Процесс ss запущен.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Error("Ошибка запуска ss: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessOnExited(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
if (_stopping)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_log.Warn("Процесс ss завершился, запускаю заново.");
|
||||||
|
EnsureStarted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WatchdogTick(object state)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
if (_stopping)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureStarted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_stopping = true;
|
||||||
|
_watchdogTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IsProcessRunning(_process))
|
||||||
|
{
|
||||||
|
_process.Kill();
|
||||||
|
_process.WaitForExit(5000);
|
||||||
|
_log.Info("Процесс ss остановлен.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Warn("Не удалось корректно завершить ss: " + ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_process != null)
|
||||||
|
{
|
||||||
|
_process.Exited -= ProcessOnExited;
|
||||||
|
_process.Dispose();
|
||||||
|
}
|
||||||
|
_process = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsProcessRunning(Process process)
|
||||||
|
{
|
||||||
|
if (process == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return !process.HasExited;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
_watchdogTimer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using SshTunnelMonitor.Models;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor.Services
|
||||||
|
{
|
||||||
|
public sealed class SsConfigService
|
||||||
|
{
|
||||||
|
private readonly LogService _log;
|
||||||
|
|
||||||
|
public SsConfigService(LogService log)
|
||||||
|
{
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnsureConfig(AppSettings settings)
|
||||||
|
{
|
||||||
|
var password = settings.GetThreeProxyPassword();
|
||||||
|
if (string.IsNullOrWhiteSpace(password))
|
||||||
|
{
|
||||||
|
password = GeneratePassword();
|
||||||
|
settings.SetThreeProxyPassword(password);
|
||||||
|
settings.Save();
|
||||||
|
_log.Info("Сгенерирован пароль прокси и сохранен в настройках.");
|
||||||
|
}
|
||||||
|
var port = settings.LocalPort;
|
||||||
|
|
||||||
|
var config = "users admin:CL:" + password + "\r\n" +
|
||||||
|
"auth strong\r\n" +
|
||||||
|
$"socks -i127.0.0.1 -p{port}";
|
||||||
|
|
||||||
|
var configPath = GetConfigPath();
|
||||||
|
var directory = Path.GetDirectoryName(configPath);
|
||||||
|
if (!string.IsNullOrEmpty(directory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(configPath, config);
|
||||||
|
_log.Info("Сконфигурирован ss.cfg: " + configPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetConfigPath()
|
||||||
|
{
|
||||||
|
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "ss", "ss.cfg");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GeneratePassword()
|
||||||
|
{
|
||||||
|
const string alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
const int length = 20;
|
||||||
|
var bytes = new byte[length];
|
||||||
|
using (var rng = RandomNumberGenerator.Create())
|
||||||
|
{
|
||||||
|
rng.GetBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
var chars = new char[length];
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
chars[i] = alphabet[bytes[i] % alphabet.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string(chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Renci.SshNet;
|
||||||
|
using Renci.SshNet.Common;
|
||||||
|
using SshTunnelMonitor.Models;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor.Services
|
||||||
|
{
|
||||||
|
public sealed class SshTunnelService : IDisposable
|
||||||
|
{
|
||||||
|
private readonly LogService _log;
|
||||||
|
private readonly object _sync = new object();
|
||||||
|
private SshClient _client;
|
||||||
|
private ForwardedPortRemote _forwardedPort;
|
||||||
|
private SshCommand _heartbeatCommand;
|
||||||
|
private CancellationTokenSource _heartbeatReadTokenSource;
|
||||||
|
private Task _heartbeatReadTask;
|
||||||
|
private CancellationTokenSource _monitorTokenSource;
|
||||||
|
private AppSettings _settings;
|
||||||
|
private bool _disposed;
|
||||||
|
private bool _reconnectInProgress;
|
||||||
|
private DateTime _lastHeartbeatUtc = DateTime.MinValue;
|
||||||
|
private bool _remoteIsWindows;
|
||||||
|
|
||||||
|
public SshTunnelService(LogService log)
|
||||||
|
{
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(AppSettings settings)
|
||||||
|
{
|
||||||
|
_settings = settings;
|
||||||
|
Connect();
|
||||||
|
StartMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Connect()
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
DisconnectCore();
|
||||||
|
|
||||||
|
var password = _settings.GetPassword();
|
||||||
|
var authMethod = new PasswordAuthenticationMethod(_settings.SshUsername, password);
|
||||||
|
var connectionInfo = new ConnectionInfo(_settings.SshHost, _settings.SshPort, _settings.SshUsername, authMethod);
|
||||||
|
|
||||||
|
_client = new SshClient(connectionInfo);
|
||||||
|
_client.ErrorOccurred += ClientOnErrorOccurred;
|
||||||
|
_client.Connect();
|
||||||
|
|
||||||
|
_forwardedPort = new ForwardedPortRemote((uint)_settings.RemotePort, "127.0.0.1", (uint)_settings.LocalPort);
|
||||||
|
_forwardedPort.Exception += ForwardedPortOnException;
|
||||||
|
_client.AddForwardedPort(_forwardedPort);
|
||||||
|
_forwardedPort.Start();
|
||||||
|
StartRemoteHeartbeatNoLock();
|
||||||
|
|
||||||
|
_log.Info(
|
||||||
|
$"SSH подключен. Туннель remote:{_settings.RemotePort} -> local:{_settings.LocalPort}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClientOnErrorOccurred(object sender, ExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
_log.Warn("Ошибка SSH: " + e.Exception.Message);
|
||||||
|
ReconnectSoon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ForwardedPortOnException(object sender, ExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
_log.Warn("Ошибка перенаправления порта: " + e.Exception.Message);
|
||||||
|
ReconnectSoon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartMonitor()
|
||||||
|
{
|
||||||
|
_monitorTokenSource = new CancellationTokenSource();
|
||||||
|
Task.Run(() => MonitorLoop(_monitorTokenSource.Token));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task MonitorLoop(CancellationToken token)
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var silenceThresholdSec = Math.Max(
|
||||||
|
5,
|
||||||
|
_settings.MaxPingFailures);
|
||||||
|
|
||||||
|
var lastHeartbeat = _lastHeartbeatUtc;
|
||||||
|
var isHeartbeatAlive = lastHeartbeat != DateTime.MinValue &&
|
||||||
|
(DateTime.UtcNow - lastHeartbeat).TotalSeconds <= silenceThresholdSec;
|
||||||
|
|
||||||
|
if (!IsConnected() || !isHeartbeatAlive)
|
||||||
|
{
|
||||||
|
_log.Warn("Нет живого вывода heartbeat-команды на удаленном сервере. Переподключаю SSH.");
|
||||||
|
await Reconnect().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Warn("Исключение мониторинга heartbeat: " + ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(2), token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsConnected()
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
return _client != null && _client.IsConnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReconnectSoon()
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1000).ConfigureAwait(false);
|
||||||
|
await Reconnect().ConfigureAwait(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Reconnect()
|
||||||
|
{
|
||||||
|
if (_reconnectInProgress || _disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_reconnectInProgress = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var timeout = Math.Min(60, Math.Max(5, _settings.ReconnectTimeoutSec));
|
||||||
|
_log.Warn("Перезапуск SSH-соединения...");
|
||||||
|
|
||||||
|
var reconnectTask = Task.Run(() =>
|
||||||
|
{
|
||||||
|
DisconnectCore();
|
||||||
|
Connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
var completed = await Task.WhenAny(reconnectTask, Task.Delay(TimeSpan.FromSeconds(timeout))).ConfigureAwait(false);
|
||||||
|
if (completed != reconnectTask)
|
||||||
|
{
|
||||||
|
_log.Error("Переподключение превысило таймаут " + timeout + "с.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_log.Info("SSH-соединение восстановлено.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Error("Ошибка переподключения SSH: " + ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_reconnectInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisconnectCore()
|
||||||
|
{
|
||||||
|
StopRemoteHeartbeatNoLock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_forwardedPort != null)
|
||||||
|
{
|
||||||
|
if (_forwardedPort.IsStarted)
|
||||||
|
{
|
||||||
|
_forwardedPort.Stop();
|
||||||
|
}
|
||||||
|
_forwardedPort.Dispose();
|
||||||
|
_forwardedPort = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors on shutdown.
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_client != null)
|
||||||
|
{
|
||||||
|
if (_client.IsConnected)
|
||||||
|
{
|
||||||
|
_client.Disconnect();
|
||||||
|
}
|
||||||
|
_client.Dispose();
|
||||||
|
_client = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors on shutdown.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartRemoteHeartbeatNoLock()
|
||||||
|
{
|
||||||
|
if (_client == null || !_client.IsConnected)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StopRemoteHeartbeatNoLock();
|
||||||
|
_remoteIsWindows = DetectRemoteWindowsNoLock();
|
||||||
|
|
||||||
|
var heartbeatCommand = _remoteIsWindows
|
||||||
|
? "ping ya.ru -t"
|
||||||
|
: "while :; do echo 'some type of ping response '$C; C=$((C+1)); sleep 1; done";
|
||||||
|
|
||||||
|
_heartbeatCommand = _client.CreateCommand(heartbeatCommand);
|
||||||
|
_heartbeatReadTokenSource = new CancellationTokenSource();
|
||||||
|
_lastHeartbeatUtc = DateTime.UtcNow;
|
||||||
|
_heartbeatCommand.BeginExecute();
|
||||||
|
|
||||||
|
_heartbeatReadTask = Task.Run(() =>
|
||||||
|
ReadHeartbeatOutputLoop(_heartbeatCommand, _heartbeatReadTokenSource.Token));
|
||||||
|
_log.Info("Запущен удаленный heartbeat через SSH (" + (_remoteIsWindows ? "Windows" : "Linux/Unix") + ").");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopRemoteHeartbeatNoLock()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_heartbeatReadTokenSource?.Cancel();
|
||||||
|
_heartbeatReadTask?.Wait(1000);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors on shutdown.
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_heartbeatReadTokenSource?.Dispose();
|
||||||
|
_heartbeatReadTokenSource = null;
|
||||||
|
_heartbeatReadTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_heartbeatCommand?.Dispose();
|
||||||
|
_heartbeatCommand = null;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors on shutdown.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadHeartbeatOutputLoop(SshCommand command, CancellationToken token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(command.OutputStream))
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var line = reader.ReadLine();
|
||||||
|
if (line == null)
|
||||||
|
{
|
||||||
|
Thread.Sleep(200);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastHeartbeatUtc = DateTime.UtcNow;
|
||||||
|
_log.Debug("HB: " + line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
_log.Warn("Ошибка чтения heartbeat-вывода: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DetectRemoteWindowsNoLock()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var command = _client.CreateCommand("cmd /c ver");
|
||||||
|
var output = command.Execute() ?? string.Empty;
|
||||||
|
return output.IndexOf("windows", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_monitorTokenSource?.Cancel();
|
||||||
|
_monitorTokenSource?.Dispose();
|
||||||
|
_monitorTokenSource = null;
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
DisconnectCore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_disposed = true;
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
using SshTunnelMonitor.Models;
|
||||||
|
using SshTunnelMonitor.Services;
|
||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor.UI
|
||||||
|
{
|
||||||
|
public sealed class MainForm : Form
|
||||||
|
{
|
||||||
|
private readonly RichTextBox _console = new RichTextBox();
|
||||||
|
private readonly Button _btnSettings = new Button();
|
||||||
|
private readonly Button _btnExit = new Button();
|
||||||
|
private readonly NotifyIcon _notifyIcon = new NotifyIcon();
|
||||||
|
private readonly Icon _trayIcon;
|
||||||
|
private readonly LogService _log = new LogService();
|
||||||
|
private readonly PowerManagementService _power = new PowerManagementService();
|
||||||
|
private AppController _controller;
|
||||||
|
private AppSettings _settings;
|
||||||
|
private bool _allowClose;
|
||||||
|
|
||||||
|
public MainForm()
|
||||||
|
{
|
||||||
|
Text = "Monster Monitor";
|
||||||
|
Width = 980;
|
||||||
|
Height = 620;
|
||||||
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
|
_trayIcon = LoadTrayIcon();
|
||||||
|
|
||||||
|
BuildUi();
|
||||||
|
BindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnLoad(EventArgs e)
|
||||||
|
{
|
||||||
|
base.OnLoad(e);
|
||||||
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||||
|
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||||
|
|
||||||
|
_settings = AppSettings.Load();
|
||||||
|
_controller = new AppController(_log, _power);
|
||||||
|
RestartServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildUi()
|
||||||
|
{
|
||||||
|
var topPanel = new Panel { Dock = DockStyle.Top, Height = 42 };
|
||||||
|
_btnSettings.Text = "Настройки";
|
||||||
|
_btnSettings.Width = 110;
|
||||||
|
_btnSettings.Location = new Point(10, 8);
|
||||||
|
|
||||||
|
_btnExit.Text = "Выход";
|
||||||
|
_btnExit.Width = 110;
|
||||||
|
_btnExit.Location = new Point(130, 8);
|
||||||
|
|
||||||
|
topPanel.Controls.Add(_btnSettings);
|
||||||
|
topPanel.Controls.Add(_btnExit);
|
||||||
|
|
||||||
|
_console.Dock = DockStyle.Fill;
|
||||||
|
_console.ReadOnly = true;
|
||||||
|
_console.BackColor = Color.Black;
|
||||||
|
_console.ForeColor = Color.White;
|
||||||
|
_console.Font = new Font("Consolas", 10f);
|
||||||
|
|
||||||
|
Controls.Add(_console);
|
||||||
|
Controls.Add(topPanel);
|
||||||
|
|
||||||
|
_notifyIcon.Text = "Monster Monitor";
|
||||||
|
_notifyIcon.Icon = _trayIcon;
|
||||||
|
_notifyIcon.Visible = true;
|
||||||
|
var trayMenu = new ContextMenuStrip();
|
||||||
|
trayMenu.Items.Add("Открыть", null, (_, __) => RestoreFromTray());
|
||||||
|
trayMenu.Items.Add("Выход", null, (_, __) => ExitApplication());
|
||||||
|
_notifyIcon.ContextMenuStrip = trayMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BindEvents()
|
||||||
|
{
|
||||||
|
Resize += MainFormOnResize;
|
||||||
|
FormClosing += MainFormOnFormClosing;
|
||||||
|
_notifyIcon.DoubleClick += (_, __) => RestoreFromTray();
|
||||||
|
_btnSettings.Click += (_, __) => OpenSettings();
|
||||||
|
_btnExit.Click += (_, __) => ExitApplication();
|
||||||
|
_log.LogReceived += AppendLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSettings()
|
||||||
|
{
|
||||||
|
using (var form = new SettingsForm(_settings))
|
||||||
|
{
|
||||||
|
if (form.ShowDialog(this) == DialogResult.OK)
|
||||||
|
{
|
||||||
|
_log.Info("Настройки обновлены. Перезапускаю сервисы.");
|
||||||
|
RestartServices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestartServices()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_controller.Start(_settings);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Error("Ошибка запуска сервисов: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainFormOnResize(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (WindowState != FormWindowState.Minimized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hide();
|
||||||
|
_notifyIcon.BalloonTipTitle = "Monster Monitor";
|
||||||
|
_notifyIcon.BalloonTipText = "Приложение свернуто в трей.";
|
||||||
|
_notifyIcon.ShowBalloonTip(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreFromTray()
|
||||||
|
{
|
||||||
|
Show();
|
||||||
|
WindowState = FormWindowState.Normal;
|
||||||
|
Activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExitApplication()
|
||||||
|
{
|
||||||
|
_allowClose = true;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainFormOnFormClosing(object sender, FormClosingEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_allowClose && e.CloseReason == CloseReason.UserClosing)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
WindowState = FormWindowState.Minimized;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_notifyIcon.Visible = false;
|
||||||
|
_controller?.Dispose();
|
||||||
|
_trayIcon?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendLog(LogEntry entry)
|
||||||
|
{
|
||||||
|
if (InvokeRequired)
|
||||||
|
{
|
||||||
|
BeginInvoke(new Action<LogEntry>(AppendLog), entry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_console.SelectionStart = _console.TextLength;
|
||||||
|
_console.SelectionLength = 0;
|
||||||
|
_console.SelectionColor = GetColor(entry.Level);
|
||||||
|
_console.AppendText($"[{entry.Timestamp:HH:mm:ss}] [{entry.Level}] {entry.Message}{Environment.NewLine}");
|
||||||
|
_console.SelectionColor = _console.ForeColor;
|
||||||
|
_console.ScrollToCaret();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color GetColor(LogLevel level)
|
||||||
|
{
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case LogLevel.Debug:
|
||||||
|
return Color.Silver;
|
||||||
|
case LogLevel.Warn:
|
||||||
|
return Color.Orange;
|
||||||
|
case LogLevel.Error:
|
||||||
|
return Color.Red;
|
||||||
|
default:
|
||||||
|
return Color.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Icon LoadTrayIcon()
|
||||||
|
{
|
||||||
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||||
|
return ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// MainForm
|
||||||
|
//
|
||||||
|
this.ClientSize = new System.Drawing.Size(284, 261);
|
||||||
|
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||||
|
this.Name = "MainForm";
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,259 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using SshTunnelMonitor.Models;
|
||||||
|
|
||||||
|
namespace SshTunnelMonitor.UI
|
||||||
|
{
|
||||||
|
public sealed class SettingsForm : Form
|
||||||
|
{
|
||||||
|
private readonly AppSettings _settings;
|
||||||
|
private readonly TextBox _txtHost = new TextBox();
|
||||||
|
private readonly NumericUpDown _numSshPort = new NumericUpDown();
|
||||||
|
private readonly TextBox _txtUser = new TextBox();
|
||||||
|
private readonly TextBox _txtPassword = new TextBox();
|
||||||
|
private readonly CheckBox _chkSavePassword = new CheckBox();
|
||||||
|
private readonly NumericUpDown _numRemotePort = new NumericUpDown();
|
||||||
|
private readonly NumericUpDown _numLocalPort = new NumericUpDown();
|
||||||
|
private readonly NumericUpDown _numMaxFailures = new NumericUpDown();
|
||||||
|
private readonly NumericUpDown _numReconnectTimeout = new NumericUpDown();
|
||||||
|
private readonly TextBox _txtSsPath = new TextBox();
|
||||||
|
private readonly TextBox _txtSsArgs = new TextBox();
|
||||||
|
private readonly TextBox _txtSystemPassword = new TextBox();
|
||||||
|
private readonly CheckBox _chkShowSystemPassword = new CheckBox();
|
||||||
|
private readonly TextBox _txtThreeProxyPassword = new TextBox();
|
||||||
|
private readonly CheckBox _chkShowThreeProxyPassword = new CheckBox();
|
||||||
|
private readonly Button _btnGenerateThreeProxyPassword = new Button();
|
||||||
|
|
||||||
|
public SettingsForm(AppSettings settings)
|
||||||
|
{
|
||||||
|
_settings = settings;
|
||||||
|
Text = "Настройки";
|
||||||
|
Width = 560;
|
||||||
|
Height = 520;
|
||||||
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
|
MaximizeBox = false;
|
||||||
|
MinimizeBox = false;
|
||||||
|
StartPosition = FormStartPosition.CenterParent;
|
||||||
|
|
||||||
|
BuildLayout();
|
||||||
|
LoadValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildLayout()
|
||||||
|
{
|
||||||
|
var panel = new TableLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
Padding = new Padding(10),
|
||||||
|
ColumnCount = 2,
|
||||||
|
RowCount = 14
|
||||||
|
};
|
||||||
|
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 44f));
|
||||||
|
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 56f));
|
||||||
|
|
||||||
|
Controls.Add(panel);
|
||||||
|
|
||||||
|
AddRow(panel, "Сервер:", _txtHost, 0);
|
||||||
|
AddRow(panel, "Порт:", _numSshPort, 1);
|
||||||
|
AddRow(panel, "Логин:", _txtUser, 2);
|
||||||
|
_txtPassword.PasswordChar = '*';
|
||||||
|
AddRow(panel, "Пароль:", _txtPassword, 3);
|
||||||
|
_chkSavePassword.Text = "Сохранить пароль";
|
||||||
|
AddRow(panel, string.Empty, _chkSavePassword, 4);
|
||||||
|
AddRow(panel, "Удаленный порт:", _numRemotePort, 5);
|
||||||
|
AddRow(panel, "Локальный порт:", _numLocalPort, 6);
|
||||||
|
AddRow(panel, "Макс. потерь:", _numMaxFailures, 7);
|
||||||
|
AddRow(panel, "Таймаут reconnect (сек):", _numReconnectTimeout, 8);
|
||||||
|
AddRow(panel, "Путь к ss:", _txtSsPath, 9);
|
||||||
|
AddRow(panel, "Аргументы ss:", _txtSsArgs, 10);
|
||||||
|
BuildSystemPasswordRow(panel, 11);
|
||||||
|
BuildThreeProxyPasswordRow(panel, 12);
|
||||||
|
|
||||||
|
foreach (var num in new[] { _numSshPort, _numRemotePort, _numLocalPort })
|
||||||
|
{
|
||||||
|
num.Minimum = 1;
|
||||||
|
num.Maximum = 65535;
|
||||||
|
}
|
||||||
|
|
||||||
|
_numMaxFailures.Minimum = 1;
|
||||||
|
_numMaxFailures.Maximum = 10;
|
||||||
|
_numReconnectTimeout.Minimum = 5;
|
||||||
|
_numReconnectTimeout.Maximum = 60;
|
||||||
|
|
||||||
|
var buttonsPanel = new FlowLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
FlowDirection = FlowDirection.RightToLeft
|
||||||
|
};
|
||||||
|
var btnSave = new Button { Text = "Сохранить", Width = 120, Height = 30 };
|
||||||
|
var btnCancel = new Button { Text = "Отмена", Width = 120, Height = 30 };
|
||||||
|
btnSave.Click += (_, __) => SaveAndClose();
|
||||||
|
btnCancel.Click += (_, __) => DialogResult = DialogResult.Cancel;
|
||||||
|
|
||||||
|
buttonsPanel.Controls.Add(btnSave);
|
||||||
|
buttonsPanel.Controls.Add(btnCancel);
|
||||||
|
panel.Controls.Add(buttonsPanel, 0, 13);
|
||||||
|
panel.SetColumnSpan(buttonsPanel, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildSystemPasswordRow(TableLayoutPanel panel, int row)
|
||||||
|
{
|
||||||
|
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 34f));
|
||||||
|
panel.Controls.Add(new Label
|
||||||
|
{
|
||||||
|
Text = "Пароль Sterra:",
|
||||||
|
TextAlign = ContentAlignment.MiddleLeft,
|
||||||
|
Dock = DockStyle.Fill
|
||||||
|
}, 0, row);
|
||||||
|
|
||||||
|
var passPanel = new FlowLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
FlowDirection = FlowDirection.LeftToRight,
|
||||||
|
WrapContents = false,
|
||||||
|
AutoSize = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_txtSystemPassword.Width = 170;
|
||||||
|
_txtSystemPassword.PasswordChar = '*';
|
||||||
|
|
||||||
|
_chkShowSystemPassword.Text = "Показать";
|
||||||
|
_chkShowSystemPassword.AutoSize = true;
|
||||||
|
_chkShowSystemPassword.CheckedChanged += (_, __) =>
|
||||||
|
{
|
||||||
|
_txtSystemPassword.PasswordChar = _chkShowSystemPassword.Checked ? '\0' : '*';
|
||||||
|
};
|
||||||
|
|
||||||
|
passPanel.Controls.Add(_txtSystemPassword);
|
||||||
|
passPanel.Controls.Add(_chkShowSystemPassword);
|
||||||
|
panel.Controls.Add(passPanel, 1, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildThreeProxyPasswordRow(TableLayoutPanel panel, int row)
|
||||||
|
{
|
||||||
|
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 34f));
|
||||||
|
panel.Controls.Add(new Label
|
||||||
|
{
|
||||||
|
Text = "Пароль прокси:",
|
||||||
|
TextAlign = ContentAlignment.MiddleLeft,
|
||||||
|
Dock = DockStyle.Fill
|
||||||
|
}, 0, row);
|
||||||
|
|
||||||
|
var passPanel = new FlowLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
FlowDirection = FlowDirection.LeftToRight,
|
||||||
|
WrapContents = false,
|
||||||
|
AutoSize = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_txtThreeProxyPassword.Width = 170;
|
||||||
|
_txtThreeProxyPassword.PasswordChar = '*';
|
||||||
|
|
||||||
|
_chkShowThreeProxyPassword.Text = "Показать";
|
||||||
|
_chkShowThreeProxyPassword.AutoSize = true;
|
||||||
|
_chkShowThreeProxyPassword.CheckedChanged += (_, __) =>
|
||||||
|
{
|
||||||
|
_txtThreeProxyPassword.PasswordChar = _chkShowThreeProxyPassword.Checked ? '\0' : '*';
|
||||||
|
};
|
||||||
|
|
||||||
|
_btnGenerateThreeProxyPassword.Text = "Сгенерировать";
|
||||||
|
_btnGenerateThreeProxyPassword.AutoSize = true;
|
||||||
|
_btnGenerateThreeProxyPassword.Click += (_, __) =>
|
||||||
|
{
|
||||||
|
_txtThreeProxyPassword.Text = GeneratePassword();
|
||||||
|
};
|
||||||
|
|
||||||
|
passPanel.Controls.Add(_txtThreeProxyPassword);
|
||||||
|
passPanel.Controls.Add(_chkShowThreeProxyPassword);
|
||||||
|
passPanel.Controls.Add(_btnGenerateThreeProxyPassword);
|
||||||
|
panel.Controls.Add(passPanel, 1, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddRow(TableLayoutPanel panel, string label, Control control, int row)
|
||||||
|
{
|
||||||
|
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 34f));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(label))
|
||||||
|
{
|
||||||
|
panel.Controls.Add(new Label
|
||||||
|
{
|
||||||
|
Text = label,
|
||||||
|
TextAlign = ContentAlignment.MiddleLeft,
|
||||||
|
Dock = DockStyle.Fill
|
||||||
|
}, 0, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
control.Dock = DockStyle.Fill;
|
||||||
|
panel.Controls.Add(control, 1, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadValues()
|
||||||
|
{
|
||||||
|
_txtHost.Text = _settings.SshHost;
|
||||||
|
_numSshPort.Value = _settings.SshPort;
|
||||||
|
_txtUser.Text = _settings.SshUsername;
|
||||||
|
_txtPassword.Text = _settings.GetPassword();
|
||||||
|
_chkSavePassword.Checked = _settings.SavePassword;
|
||||||
|
_numRemotePort.Value = _settings.RemotePort;
|
||||||
|
_numLocalPort.Value = _settings.LocalPort;
|
||||||
|
_numMaxFailures.Value = _settings.MaxPingFailures;
|
||||||
|
_numReconnectTimeout.Value = _settings.ReconnectTimeoutSec;
|
||||||
|
_txtSsPath.Text = _settings.SsProcessPath;
|
||||||
|
_txtSsArgs.Text = _settings.SsArguments;
|
||||||
|
_txtSystemPassword.Text = _settings.GetSystemPassword();
|
||||||
|
_txtThreeProxyPassword.Text = _settings.GetThreeProxyPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveAndClose()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_txtHost.Text))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "Поле сервера обязательно.", "Валидация", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_settings.SshHost = _txtHost.Text.Trim();
|
||||||
|
_settings.SshPort = (int)_numSshPort.Value;
|
||||||
|
_settings.SshUsername = _txtUser.Text.Trim();
|
||||||
|
_settings.SavePassword = _chkSavePassword.Checked;
|
||||||
|
_settings.SetPassword(_txtPassword.Text);
|
||||||
|
_settings.RemotePort = (int)_numRemotePort.Value;
|
||||||
|
_settings.LocalPort = (int)_numLocalPort.Value;
|
||||||
|
_settings.MaxPingFailures = (int)_numMaxFailures.Value;
|
||||||
|
_settings.ReconnectTimeoutSec = (int)_numReconnectTimeout.Value;
|
||||||
|
_settings.SsProcessPath = string.IsNullOrWhiteSpace(_txtSsPath.Text)
|
||||||
|
? System.IO.Path.Combine("App_Data", "ss", "ss.exe")
|
||||||
|
: _txtSsPath.Text.Trim();
|
||||||
|
_settings.SsArguments = string.IsNullOrWhiteSpace(_txtSsArgs.Text)
|
||||||
|
? System.IO.Path.Combine("App_Data", "ss", "ss.cfg")
|
||||||
|
: _txtSsArgs.Text.Trim();
|
||||||
|
_settings.SetSystemPassword(_txtSystemPassword.Text.Trim());
|
||||||
|
_settings.SetThreeProxyPassword(_txtThreeProxyPassword.Text.Trim());
|
||||||
|
_settings.Save();
|
||||||
|
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GeneratePassword()
|
||||||
|
{
|
||||||
|
const string alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
const int length = 20;
|
||||||
|
var bytes = new byte[length];
|
||||||
|
using (var rng = RandomNumberGenerator.Create())
|
||||||
|
{
|
||||||
|
rng.GetBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
var chars = new char[length];
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
chars[i] = alphabet[bytes[i] % alphabet.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string(chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@@ -0,0 +1,47 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
if [%1] == [] GOTO :USAGE
|
||||||
|
set version=%1
|
||||||
|
set fileversion=%1
|
||||||
|
set informationalversion=%1
|
||||||
|
|
||||||
|
if [%2] NEQ [] SET fileversion=%2
|
||||||
|
if [%3] NEQ [] SET informationalversion=%3
|
||||||
|
|
||||||
|
echo Setting assembly version information
|
||||||
|
|
||||||
|
for /F "delims=" %%f in ('dir /s /b Assemblyinfo.cs') do (
|
||||||
|
echo ^> %%f
|
||||||
|
|
||||||
|
pushd %%~dpf
|
||||||
|
|
||||||
|
for /F "usebackq delims=" %%g in ("AssemblyInfo.cs") do (
|
||||||
|
set ln=%%g
|
||||||
|
set skip=0
|
||||||
|
|
||||||
|
if "!ln:AssemblyVersion=!" NEQ "!ln!" set skip=1
|
||||||
|
if "!ln:AssemblyFileVersion=!" NEQ "!ln!" set skip=1
|
||||||
|
if "!ln:AssemblyInformationalVersion=!" NEQ "!ln!" set skip=1
|
||||||
|
|
||||||
|
if !skip!==0 echo !ln! >> AssemblyInfo.cs.versioned
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [assembly: AssemblyVersion^("%version%"^)] >> AssemblyInfo.cs.versioned
|
||||||
|
echo [assembly: AssemblyFileVersion^("%fileversion%"^)] >> AssemblyInfo.cs.versioned
|
||||||
|
echo [assembly: AssemblyInformationalVersion^("%informationalversion%"^)] >> AssemblyInfo.cs.versioned
|
||||||
|
|
||||||
|
copy /y AssemblyInfo.cs AssemblyInfo.cs.orig
|
||||||
|
move /y AssemblyInfo.cs.versioned AssemblyInfo.cs
|
||||||
|
|
||||||
|
popd
|
||||||
|
)
|
||||||
|
echo Done^^!
|
||||||
|
|
||||||
|
GOTO:EOF
|
||||||
|
|
||||||
|
:USAGE
|
||||||
|
echo Usage:
|
||||||
|
echo.
|
||||||
|
echo SetVersion.bat Version [FileVersion] [InformationalVersion]
|
||||||
|
echo.
|
||||||
Reference in New Issue
Block a user