ntminer 5 years ago
parent
commit
23e1f08bc5
100 changed files with 6056 additions and 2015 deletions
  1. 5 4
      .gitignore
  2. 87 51
      NTMiner.sln
  3. 3 4
      README.md
  4. 13 0
      docs/Index.md
  5. 4 4
      docs/NTMiner0.md
  6. 1 1
      docs/NTMiner1.md
  7. 5 5
      docs/NTMiner2.md
  8. 5 5
      docs/NTMinerBkPool.md
  9. 2 2
      docs/Overclock.md
  10. 1 0
      docs/Server/README.md
  11. 4 3
      docs/TimingEventProducer.md
  12. 0 263
      src/AppModels/AppContext.cs
  13. 0 64
      src/AppModels/AppContext.partials.ColumnsShowViewModels.cs
  14. 0 83
      src/AppModels/AppContext.partials.MineWorkViewModels.cs
  15. 0 80
      src/AppModels/AppContext.partials.MinerGroupViewModels.cs
  16. 0 64
      src/AppModels/AppContext.partials.UserViewModels.cs
  17. 86 61
      src/AppModels/AppModels.csproj
  18. 450 0
      src/AppModels/AppRoot.cs
  19. 7 5
      src/AppModels/AppRoot.partials.CoinGroupViewModels.cs
  20. 21 26
      src/AppModels/AppRoot.partials.CoinKernelViewModels.cs
  21. 10 9
      src/AppModels/AppRoot.partials.CoinProfileViewModels.cs
  22. 39 24
      src/AppModels/AppRoot.partials.CoinViewModels.cs
  23. 21 7
      src/AppModels/AppRoot.partials.DriveSetViewModel.cs
  24. 8 7
      src/AppModels/AppRoot.partials.FileWriterViewModels.cs
  25. 8 7
      src/AppModels/AppRoot.partials.FragmentWriterViewModels.cs
  26. 13 11
      src/AppModels/AppRoot.partials.GpuProfileViewModels.cs
  27. 14 13
      src/AppModels/AppRoot.partials.GpuSpeedViewModels.cs
  28. 10 11
      src/AppModels/AppRoot.partials.GpuViewModels.cs
  29. 10 9
      src/AppModels/AppRoot.partials.GroupViewModels.cs
  30. 17 16
      src/AppModels/AppRoot.partials.KernelInputViewModels.cs
  31. 9 6
      src/AppModels/AppRoot.partials.KernelOutputKeywordViewModels.cs
  32. 12 10
      src/AppModels/AppRoot.partials.KernelOutputTranslaterViewModels.cs
  33. 9 8
      src/AppModels/AppRoot.partials.KernelOutputViewModels.cs
  34. 22 15
      src/AppModels/AppRoot.partials.KernelViewModels.cs
  35. 13 10
      src/AppModels/AppRoot.partials.PackageViewModels.cs
  36. 9 6
      src/AppModels/AppRoot.partials.PoolKernelViewModels.cs
  37. 6 4
      src/AppModels/AppRoot.partials.PoolProfileViewModels.cs
  38. 15 19
      src/AppModels/AppRoot.partials.PoolViewModels.cs
  39. 6 4
      src/AppModels/AppRoot.partials.ShareViewModels.cs
  40. 21 16
      src/AppModels/AppRoot.partials.SysDicItemViewModels.cs
  41. 10 9
      src/AppModels/AppRoot.partials.SysDicViewModels.cs
  42. 13 15
      src/AppModels/AppRoot.partials.WalletViewModels.cs
  43. 173 303
      src/AppModels/AppStatic.cs
  44. 9 8
      src/AppModels/ExtendedNotifyIcon.cs
  45. 88 257
      src/AppModels/Messages.cs
  46. 108 0
      src/AppModels/MinerStudio/EmptyMinerStudioService.cs
  47. 8 0
      src/AppModels/MinerStudio/ILocalMinerStudioService.cs
  48. 32 0
      src/AppModels/MinerStudio/IMinerStudioService.cs
  49. 4 0
      src/AppModels/MinerStudio/IServerMinerStudioService.cs
  50. 254 0
      src/AppModels/MinerStudio/Impl/LocalMinerStudioService.cs
  51. 290 0
      src/AppModels/MinerStudio/Impl/ServerMinerStudioService.cs
  52. 227 0
      src/AppModels/MinerStudio/Messages.cs
  53. 81 0
      src/AppModels/MinerStudio/MinerStudioRoot.cs
  54. 8 14
      src/AppModels/MinerStudio/MinerStudioRoot.partials.CoinSnapshotDataViewModels.cs
  55. 91 0
      src/AppModels/MinerStudio/MinerStudioRoot.partials.ColumnsShowViewModels.cs
  56. 104 0
      src/AppModels/MinerStudio/MinerStudioRoot.partials.MineWorkViewModels.cs
  57. 113 0
      src/AppModels/MinerStudio/MinerStudioRoot.partials.MinerClientConsoleViewModel.cs
  58. 111 0
      src/AppModels/MinerStudio/MinerStudioRoot.partials.MinerClientMessagesViewModel.cs
  59. 127 0
      src/AppModels/MinerStudio/MinerStudioRoot.partials.MinerClientOperationResultsViewModel.cs
  60. 99 0
      src/AppModels/MinerStudio/MinerStudioRoot.partials.MinerGroupViewModels.cs
  61. 16 14
      src/AppModels/MinerStudio/MinerStudioRoot.partials.NTMinerWalletViewModels.cs
  62. 14 12
      src/AppModels/MinerStudio/MinerStudioRoot.partials.OverClockDataViewModels.cs
  63. 12 6
      src/AppModels/MinerStudio/Vms/ChartViewModel.cs
  64. 4 3
      src/AppModels/MinerStudio/Vms/ChartsWindowViewModel.cs
  65. 29 4
      src/AppModels/MinerStudio/Vms/CoinSnapshotDataViewModel.cs
  66. 38 0
      src/AppModels/MinerStudio/Vms/CoinSnapshotViewModel.cs
  67. 40 0
      src/AppModels/MinerStudio/Vms/ColumnsShowSelectViewModel.cs
  68. 181 74
      src/AppModels/MinerStudio/Vms/ColumnsShowViewModel.cs
  69. 69 65
      src/AppModels/MinerStudio/Vms/GpuProfilesPageViewModel.cs
  70. 2 1
      src/AppModels/MinerStudio/Vms/GpuSpeedDataViewModel.cs
  71. 2 1
      src/AppModels/MinerStudio/Vms/GpuSpeedDataViewModels.cs
  72. 11 0
      src/AppModels/MinerStudio/Vms/IWsStateViewModel.cs
  73. 71 0
      src/AppModels/MinerStudio/Vms/LocalIpConfigViewModel.cs
  74. 50 0
      src/AppModels/MinerStudio/Vms/MineWorkSelectViewModel.cs
  75. 269 0
      src/AppModels/MinerStudio/Vms/MineWorkViewModel.cs
  76. 7 6
      src/AppModels/MinerStudio/Vms/MinerClientAddViewModel.cs
  77. 12 7
      src/AppModels/MinerStudio/Vms/MinerClientFinderConfigViewModel.cs
  78. 8 4
      src/AppModels/MinerStudio/Vms/MinerClientSettingViewModel.cs
  79. 298 154
      src/AppModels/MinerStudio/Vms/MinerClientViewModel.cs
  80. 1207 0
      src/AppModels/MinerStudio/Vms/MinerClientsWindowViewModel.cs
  81. 50 0
      src/AppModels/MinerStudio/Vms/MinerGroupSelectViewModel.cs
  82. 31 14
      src/AppModels/MinerStudio/Vms/MinerGroupViewModel.cs
  83. 5 5
      src/AppModels/MinerStudio/Vms/MinerNamesSeterViewModel.cs
  84. 55 0
      src/AppModels/MinerStudio/Vms/NTMinerFileSelectViewModel.cs
  85. 133 0
      src/AppModels/MinerStudio/Vms/NTMinerFileViewModel.cs
  86. 13 8
      src/AppModels/MinerStudio/Vms/NTMinerUpdaterConfigViewModel.cs
  87. 5 4
      src/AppModels/MinerStudio/Vms/NTMinerWalletPageViewModel.cs
  88. 11 5
      src/AppModels/MinerStudio/Vms/NTMinerWalletViewModel.cs
  89. 5 4
      src/AppModels/MinerStudio/Vms/OverClockDataPageViewModel.cs
  90. 6 5
      src/AppModels/MinerStudio/Vms/OverClockDataViewModel.cs
  91. 8 10
      src/AppModels/MinerStudio/Vms/RemoteDesktopLoginViewModel.cs
  92. 318 0
      src/AppModels/MinerStudio/Vms/UserPageViewModel.cs
  93. 71 0
      src/AppModels/MinerStudio/Vms/VirtualMemoryViewModel.cs
  94. 62 0
      src/AppModels/MinerStudio/Vms/WsServerNodePageViewModel.cs
  95. 13 0
      src/AppModels/RemoteDesktop/Firewall.cs
  96. 0 51
      src/AppModels/RemoteDesktop/Rdp.cs
  97. 0 7
      src/AppModels/RemoteDesktop/Rdp.partial.cs
  98. 10 3
      src/AppModels/SortableExtension.cs
  99. 4 3
      src/AppModels/View/AbstractAppViewFactory.cs
  100. 0 2
      src/AppModels/Vms/AboutPageViewModel.cs

+ 5 - 4
.gitignore

@@ -261,11 +261,10 @@ __pycache__/
 *.pyc
 
 *.litedb
-WinDivert64.sys
-WinDivert.dll
 NTMinerServices.exe.config
 NTMinerServices.exe
 NTMinerDaemon.exe
+NTMinerNoDevFee.exe
 DevConsole.exe
 NTMinerSplash.exe
 sha1
@@ -282,13 +281,14 @@ AppViews1.dll
 /src/MinerClient/Daemon/System.Net.Http.Formatting.dll
 /src/MinerClient/Daemon/System.Net.Http.dll
 /src/MinerClient/Daemon/NTMinerLogging.dll
-/src/MinerClient/Daemon/NTMinerDataObjects.dll
 /src/MinerClient/Daemon/Newtonsoft.Json.xml
 /src/MinerClient/Daemon/Newtonsoft.Json.dll
 /src/MinerClient/Daemon/log4net.xml
 /src/MinerClient/Daemon/log4net.dll
 /src/MinerClient/Daemon/LiteDB.xml
 /src/MinerClient/Daemon/LiteDB.dll
+/src/MinerClient/NoDevFee/LiteDB.xml
+/src/MinerClient/NoDevFee/LiteDB.dll
 /src/MinerStudio/NTMinerServices/System.Web.Http.xml
 /src/MinerStudio/NTMinerServices/System.Web.Http.SelfHost.xml
 /src/MinerStudio/NTMinerServices/System.Web.Http.SelfHost.dll
@@ -303,7 +303,6 @@ AppViews1.dll
 /src/MinerStudio/NTMinerServices/NTMinerRpcClient.dll
 /src/MinerStudio/NTMinerServices/NTMinerLogging.dll
 /src/MinerStudio/NTMinerServices/NTMinerlib.dll
-/src/MinerStudio/NTMinerServices/NTMinerDataObjects.dll
 /src/MinerStudio/NTMinerServices/Newtonsoft.Json.xml
 /src/MinerStudio/NTMinerServices/Newtonsoft.Json.dll
 /src/MinerStudio/NTMinerServices/log4net.xml
@@ -311,3 +310,5 @@ AppViews1.dll
 /src/MinerStudio/NTMinerServices/LiteDB.xml
 /src/MinerStudio/NTMinerServices/LiteDB.dll
 /src/MinerStudio/NTMinerServices/Aliyun.OSS.dll
+/src/MinerClient/NoDevFee/NTMinerNoDevFee.exe.config
+/src/MinerClient/NoDevFee/log4net.xml

+ 87 - 51
NTMiner.sln

@@ -10,6 +10,9 @@ EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{E66C9F7C-617A-4E97-978A-A06E3A95BEE8}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{43C57D59-9DC7-45AE-9D09-5D3D413E41EC}"
+	ProjectSection(SolutionItems) = preProject
+		docs\Server\README.md = docs\Server\README.md
+	EndProjectSection
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevConsole", "src\DevConsole\DevConsole.csproj", "{56216641-FA69-46BD-AD32-C31F9FA735C8}"
 EndProject
@@ -35,16 +38,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTMinerRpcClient", "src\NTM
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinerStudio", "src\MinerStudio\MinerStudio.csproj", "{6ED1F43A-B43F-4BE4-80DD-49297CF3DC4F}"
 	ProjectSection(ProjectDependencies) = postProject
+		{56216641-FA69-46BD-AD32-C31F9FA735C8} = {56216641-FA69-46BD-AD32-C31F9FA735C8}
 		{B3667589-D9B1-489E-AB4F-D1EC06A40A9D} = {B3667589-D9B1-489E-AB4F-D1EC06A40A9D}
 	EndProjectSection
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinerStudioSelfHost", "src\MinerStudioSelfHost\MinerStudioSelfHost.csproj", "{526C022A-3C86-4998-A8F1-F5860F183C7C}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTMinerServices", "src\NTMinerServices\NTMinerServices.csproj", "{E7B88637-7704-4701-98E0-7875BB6E8C98}"
-EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinerClient", "src\MinerClient\MinerClient.csproj", "{128B59A2-FA1A-479C-9136-7EB866B96FB4}"
 	ProjectSection(ProjectDependencies) = postProject
+		{56216641-FA69-46BD-AD32-C31F9FA735C8} = {56216641-FA69-46BD-AD32-C31F9FA735C8}
 		{B3667589-D9B1-489E-AB4F-D1EC06A40A9D} = {B3667589-D9B1-489E-AB4F-D1EC06A40A9D}
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680} = {6CA605E2-2B82-4277-8CD3-BE5EFDF00680}
+		{4B8A3AEC-4021-480A-8C5F-DCA17E6867B0} = {4B8A3AEC-4021-480A-8C5F-DCA17E6867B0}
 	EndProjectSection
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinerClientSelfHost", "src\MinerClientSelfHost\MinerClientSelfHost.csproj", "{134D71FC-0B67-4A5A-AC05-67509DE3B86B}"
@@ -54,13 +59,14 @@ EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{E32C4009-7A81-4A60-B561-286D8A0C5E06}"
 	ProjectSection(SolutionItems) = preProject
 		docs\IncorrectShare.md = docs\IncorrectShare.md
+		docs\Index.md = docs\Index.md
 		docs\NTMiner0.md = docs\NTMiner0.md
 		docs\NTMiner1.md = docs\NTMiner1.md
 		docs\NTMiner2.md = docs\NTMiner2.md
 		docs\NTMiner3.md = docs\NTMiner3.md
 		docs\NTMinerBkPool.md = docs\NTMinerBkPool.md
 		docs\Overclock.md = docs\Overclock.md
-		docs\Timer.md = docs\Timer.md
+		docs\TimingEventProducer.md = docs\TimingEventProducer.md
 	EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "View", "View", "{6F255C76-FE02-4484-8368-DD7899E06FDC}"
@@ -83,14 +89,27 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTMinerClient", "src\NTMine
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinerClientFinder", "src\MinerClientFinder\MinerClientFinder.csproj", "{2AC59227-F5C7-4594-9CC0-8FFA5DB9C6C5}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketServerTests", "src\WebSocketServerTests\WebSocketServerTests.csproj", "{637E9AD8-6410-419B-8FEE-2528130F871A}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketClientTests", "src\WebSocketClientTests\WebSocketClientTests.csproj", "{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}"
-EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTMinerHub", "src\NTMinerHub\NTMinerHub.csproj", "{E17F278A-E393-403E-9D37-E371036D7A02}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTMinerDataSchemas", "src\NTMinerDataSchemas\NTMinerDataSchemas.csproj", "{DEFC7387-F9FA-4651-A281-6612EFB7FBB6}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTMinerNoDevFee", "src\NTMinerNoDevFee\NTMinerNoDevFee.csproj", "{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{DC562605-2F35-4D5C-B576-829D8654399D}"
+	ProjectSection(SolutionItems) = preProject
+		docs\Server\README.md = docs\Server\README.md
+	EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTMinerServer", "src\NTMinerServer\NTMinerServer.csproj", "{CB2619B7-3F59-41B7-A562-4A3F117822CE}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "App", "App", "{2C8C5FC2-8B81-4228-8EBD-6B491216FBC1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiServer", "src\WebApiServer\WebApiServer.csproj", "{E7B88637-7704-4701-98E0-7875BB6E8C98}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WsServer", "src\WsServer\WsServer.csproj", "{637E9AD8-6410-419B-8FEE-2528130F871A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{E0B0D173-418C-49D0-9018-99BC6C526CF5}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -245,18 +264,6 @@ Global
 		{526C022A-3C86-4998-A8F1-F5860F183C7C}.Release|x64.Build.0 = Release|Any CPU
 		{526C022A-3C86-4998-A8F1-F5860F183C7C}.Release|x86.ActiveCfg = Release|Any CPU
 		{526C022A-3C86-4998-A8F1-F5860F183C7C}.Release|x86.Build.0 = Release|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|x64.Build.0 = Debug|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|x86.Build.0 = Debug|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|Any CPU.Build.0 = Release|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|x64.ActiveCfg = Release|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|x64.Build.0 = Release|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|x86.ActiveCfg = Release|Any CPU
-		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|x86.Build.0 = Release|Any CPU
 		{128B59A2-FA1A-479C-9136-7EB866B96FB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{128B59A2-FA1A-479C-9136-7EB866B96FB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{128B59A2-FA1A-479C-9136-7EB866B96FB4}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -353,30 +360,6 @@ Global
 		{2AC59227-F5C7-4594-9CC0-8FFA5DB9C6C5}.Release|x64.Build.0 = Release|Any CPU
 		{2AC59227-F5C7-4594-9CC0-8FFA5DB9C6C5}.Release|x86.ActiveCfg = Release|Any CPU
 		{2AC59227-F5C7-4594-9CC0-8FFA5DB9C6C5}.Release|x86.Build.0 = Release|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|x64.Build.0 = Debug|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|x86.Build.0 = Debug|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|Any CPU.Build.0 = Release|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x64.ActiveCfg = Release|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x64.Build.0 = Release|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x86.ActiveCfg = Release|Any CPU
-		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x86.Build.0 = Release|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Debug|x64.Build.0 = Debug|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Debug|x86.Build.0 = Debug|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Release|Any CPU.Build.0 = Release|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Release|x64.ActiveCfg = Release|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Release|x64.Build.0 = Release|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Release|x86.ActiveCfg = Release|Any CPU
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA}.Release|x86.Build.0 = Release|Any CPU
 		{E17F278A-E393-403E-9D37-E371036D7A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{E17F278A-E393-403E-9D37-E371036D7A02}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E17F278A-E393-403E-9D37-E371036D7A02}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -401,21 +384,68 @@ Global
 		{DEFC7387-F9FA-4651-A281-6612EFB7FBB6}.Release|x64.Build.0 = Release|Any CPU
 		{DEFC7387-F9FA-4651-A281-6612EFB7FBB6}.Release|x86.ActiveCfg = Release|Any CPU
 		{DEFC7387-F9FA-4651-A281-6612EFB7FBB6}.Release|x86.Build.0 = Release|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Debug|x64.Build.0 = Debug|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Debug|x86.Build.0 = Debug|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Release|x64.ActiveCfg = Release|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Release|x64.Build.0 = Release|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Release|x86.ActiveCfg = Release|Any CPU
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680}.Release|x86.Build.0 = Release|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Debug|x64.Build.0 = Debug|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Debug|x86.Build.0 = Debug|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Release|Any CPU.Build.0 = Release|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Release|x64.ActiveCfg = Release|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Release|x64.Build.0 = Release|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Release|x86.ActiveCfg = Release|Any CPU
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE}.Release|x86.Build.0 = Release|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|x64.Build.0 = Debug|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Debug|x86.Build.0 = Debug|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|x64.ActiveCfg = Release|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|x64.Build.0 = Release|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|x86.ActiveCfg = Release|Any CPU
+		{E7B88637-7704-4701-98E0-7875BB6E8C98}.Release|x86.Build.0 = Release|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|x64.Build.0 = Debug|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Debug|x86.Build.0 = Debug|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x64.ActiveCfg = Release|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x64.Build.0 = Release|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x86.ActiveCfg = Release|Any CPU
+		{637E9AD8-6410-419B-8FEE-2528130F871A}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
 	GlobalSection(NestedProjects) = preSolution
 		{4B8A3AEC-4021-480A-8C5F-DCA17E6867B0} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
-		{56216641-FA69-46BD-AD32-C31F9FA735C8} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
-		{436B02D6-E66E-41BA-B641-2A195702839B} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
+		{56216641-FA69-46BD-AD32-C31F9FA735C8} = {E0B0D173-418C-49D0-9018-99BC6C526CF5}
+		{436B02D6-E66E-41BA-B641-2A195702839B} = {E0B0D173-418C-49D0-9018-99BC6C526CF5}
 		{DD8E010E-D5E9-47CF-9CB4-8DC6E13D483D} = {3E8B6DD0-5039-4E01-899B-2FB28372E66E}
 		{3426F677-5026-4BB1-A0B2-DD5D9BD70D07} = {6F255C76-FE02-4484-8368-DD7899E06FDC}
 		{B3667589-D9B1-489E-AB4F-D1EC06A40A9D} = {6F255C76-FE02-4484-8368-DD7899E06FDC}
-		{5646CADA-FEAF-4991-ABDA-DBEBCD54D792} = {43C57D59-9DC7-45AE-9D09-5D3D413E41EC}
+		{5646CADA-FEAF-4991-ABDA-DBEBCD54D792} = {2C8C5FC2-8B81-4228-8EBD-6B491216FBC1}
 		{6ED1F43A-B43F-4BE4-80DD-49297CF3DC4F} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
 		{526C022A-3C86-4998-A8F1-F5860F183C7C} = {531D065C-4FA2-4B12-9475-4F61C4BFD974}
-		{E7B88637-7704-4701-98E0-7875BB6E8C98} = {43C57D59-9DC7-45AE-9D09-5D3D413E41EC}
 		{128B59A2-FA1A-479C-9136-7EB866B96FB4} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
 		{134D71FC-0B67-4A5A-AC05-67509DE3B86B} = {531D065C-4FA2-4B12-9475-4F61C4BFD974}
 		{B7107A67-A76E-4313-8C5B-3265DDE238B6} = {3E8B6DD0-5039-4E01-899B-2FB28372E66E}
@@ -423,12 +453,18 @@ Global
 		{6F255C76-FE02-4484-8368-DD7899E06FDC} = {E66C9F7C-617A-4E97-978A-A06E3A95BEE8}
 		{3E8B6DD0-5039-4E01-899B-2FB28372E66E} = {E66C9F7C-617A-4E97-978A-A06E3A95BEE8}
 		{4A3C8C8B-6642-405F-B349-57E23903F15B} = {32121ECB-19E1-4B91-86B6-369646A7E3E9}
-		{531D065C-4FA2-4B12-9475-4F61C4BFD974} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
+		{035C9774-01FC-4DF2-8225-769542A9FD5E} = {E66C9F7C-617A-4E97-978A-A06E3A95BEE8}
+		{531D065C-4FA2-4B12-9475-4F61C4BFD974} = {E66C9F7C-617A-4E97-978A-A06E3A95BEE8}
 		{B8FD9576-411F-4634-89D9-806FFD21DBA3} = {E66C9F7C-617A-4E97-978A-A06E3A95BEE8}
 		{85D052AB-44B8-46F3-9D7B-F624C24FD8BA} = {E66C9F7C-617A-4E97-978A-A06E3A95BEE8}
-		{2AC59227-F5C7-4594-9CC0-8FFA5DB9C6C5} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
-		{637E9AD8-6410-419B-8FEE-2528130F871A} = {32121ECB-19E1-4B91-86B6-369646A7E3E9}
-		{188484AC-C870-4F34-BE4C-9A0AF96DE2EA} = {32121ECB-19E1-4B91-86B6-369646A7E3E9}
+		{2AC59227-F5C7-4594-9CC0-8FFA5DB9C6C5} = {E0B0D173-418C-49D0-9018-99BC6C526CF5}
+		{6CA605E2-2B82-4277-8CD3-BE5EFDF00680} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
+		{DC562605-2F35-4D5C-B576-829D8654399D} = {E32C4009-7A81-4A60-B561-286D8A0C5E06}
+		{CB2619B7-3F59-41B7-A562-4A3F117822CE} = {43C57D59-9DC7-45AE-9D09-5D3D413E41EC}
+		{2C8C5FC2-8B81-4228-8EBD-6B491216FBC1} = {43C57D59-9DC7-45AE-9D09-5D3D413E41EC}
+		{E7B88637-7704-4701-98E0-7875BB6E8C98} = {2C8C5FC2-8B81-4228-8EBD-6B491216FBC1}
+		{637E9AD8-6410-419B-8FEE-2528130F871A} = {2C8C5FC2-8B81-4228-8EBD-6B491216FBC1}
+		{E0B0D173-418C-49D0-9018-99BC6C526CF5} = {B8FD9576-411F-4634-89D9-806FFD21DBA3}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {A5D14B0A-73FF-46E8-87F3-7E83FFC1DBDD}

+ 3 - 4
README.md

@@ -1,11 +1,10 @@
-### github网络太慢,不再频繁提交代码,有了里程碑时再提交
 点击加入 [NTMiner官方QQ群](http://qm.qq.com/cgi-bin/qm/qr?k=cvTZEdM92suKOTy0fjzdCvZkJ-tFFekn): 863725136
 
 1. 开源矿工内置的所有内核均为原版,开源矿工永远不会额外增加矿工支出;
 2. 开源矿工永远开源;
 3. 开源矿工永远不会去破解国人开发的内核;
 
-## [下载地址1](https://www.cnblogs.com/ntminer/p/11923722.html)   [下载地址2](https://github.com/ntminer/ntminer/wiki)
+## [下载地址1](https://www.cnblogs.com/ntminer/p/11923722.html)   [下载地址2](https://github.com/ntminer/NtMiner/wikis/download)
 ### 从源码编译:Visual Studio 2019
 
 ## 开源软件
@@ -16,8 +15,8 @@
 
 ## 使用说明
 ### 注意:显卡币普遍对显存有要求,90%的家用和办公电脑的显卡达不到大部分币种挖矿的最低要求。
-[在线下载](https://gitee.com/ntminer/NTMiner/wikis)或者拷贝到矿机上打开即可开始挖矿了。下图是软件主界面,这是一台具有8张p106显卡的矿机:
-![视图](https://ntminer.com/assets/img/index-pic.png?1 "NTMiner视图")
+[在线下载](https://github.com/ntminer/NtMiner/wikis)或者拷贝到矿机上打开即可开始挖矿了。下图是软件主界面,这是一台具有8张p106显卡的矿机:
+![视图](https://ntminer.com/assets/img/index-pic.png "NTMiner视图")
 从图上可以看到,这台矿机挖了31分钟了,它在挖ETH,连接的鱼池的矿池,使用鱼池的挖矿账户名挖矿,也可以选择鱼池pps矿池使用ETH钱包地址挖矿,这个挖矿账户名或钱包地址也是用户唯一需要填写的东西,其它的都不用填默认即可开挖。从图上可以看到这台矿机一天能挖0.015个ETH,约等于20块钱,去除电费(0.56元每度)后盈利10块钱。
 
 ## 开发者也是用户

+ 13 - 0
docs/Index.md

@@ -0,0 +1,13 @@
+# 一些说明
+
+代码的用户有三个:人、IDE、运行时,IDE介于人和运行时之间连接两者。
+
+* 开源矿工有大量的代码,大部分代码是为了造型为了将信息链条连贯出来避免跳跃避免魔术。
+* 挖矿端客户端、群控客户端 公用了相同的类库,类库里面有些地方会根据程序里类型的不同执行不同的逻辑,似乎是不应该公用类库,但将挖矿端和群控端看作是一个程序就行了。
+* 源码中到处都是接口,有些接口的意义可能只是为了让IDE帮助监管代码,消灭手误。
+* 名字叫*Root的类型都是静态类型,名字叫*Context的类型都不是静态类型。
+* 有些类型可以抽象为泛型比如命名为Edit*Command模式的类型,不使用泛型而使用显式的类型是为了文档、集中信息、不隐藏信息、用.NET类的类型承载信息而不是用通用类型的属性承载信息。
+* 有些需求可以通过实现BCL类库中的通用接口表达,比如枚举ICoinSet集合中的ICoin元素的需求可以通过实现IEnumerable<ICoin>接口表达,但开源矿工不这么表达,同上也是为了文档、不隐藏信息,便于人和IDE查找和定位代码,便于转到代码的位置。
+* 开源矿工编程的模式很少传参除非确实是函数才传参,或着确定具有不变性的时候才传参(所谓不变性主要是运行时执行顺序的不变性),这个编程模式是面向一棵造型稳定的树形数据结构或者叫作用域编程的,或者叫面向空间编程,如果不借助空间思维脑子里没有系统的形状阅读代码可能会有困难。
+* NTMiner.Vms命名空间下的类型很多,不做进一步编排不做进一步归类,因为人要基于图形空间思维,人先找界面,通过界面模型上的Vm属性转到Vms命名空间下的具体模型而不是直接找Vm。
+* 每个视图类型(Window、Uc)都有一个命名为Vm的ViewModel属性,该属性不一定在运行时有用因为放置该属性的目的是便于人从视图导航到视图模型。

+ 4 - 4
docs/NTMiner0.md

@@ -3,17 +3,17 @@
 ## 我们相信,所有的系统都是从0开始的。
 所谓从0开始就是从虚无开始,软件系统一定也是从0开始的。0可以称作Void、Virtual什么的,Void已经被编程语言占用了,那么我们用Virtual吧。
 ## 我们相信,所有的系统在空间结构上都是树形的,软件系统肯定不例外。
-树是一个奇妙的结构,只要你愿意你所掌握的一切知识都是树形,你未掌握的知识也是树形。不做多说,[开源矿工](https://github.com/ntminer/ntminer)系统在空间结构上和行为结构上都要有Root(根)概念。[开源矿工](https://github.com/ntminer/ntminer)有根,构建[开源矿工](https://github.com/ntminer/ntminer)的编程语言有根,[开源矿工](https://github.com/ntminer/ntminer)所生存的运行时环境也有根,下层的事物我们不做追究,[开源矿工](https://github.com/ntminer/ntminer)作为生存在操作系统时空中的一个小小的应用系统来说只感知自己所需要感知的环境即可。
+树是一个奇妙的结构,只要你愿意你所掌握的一切知识都是树形,你未掌握的知识也是树形。不做多说,[开源矿工](https://github.com/ntminer/NtMiner)系统在空间结构上和行为结构上都要有Root(根)概念。[开源矿工](https://github.com/ntminer/NtMiner)有根,构建[开源矿工](https://github.com/ntminer/NtMiner)的编程语言有根,[开源矿工](https://github.com/ntminer/NtMiner)所生存的运行时环境也有根,下层的事物我们不做追究,[开源矿工](https://github.com/ntminer/NtMiner)作为生存在操作系统时空中的一个小小的应用系统来说只感知自己所需要感知的环境即可。
 ## 我们相信,所有的词汇都应该按照望文生义理解
 你望文生义出来的意思就是本真的意思,凡是不能望文生义的或者望文生义出的意思和联想不一致的语言文字载体都已经被前人丢弃或者迟早被后人丢弃。我们不使用不能望文生义的词汇。
 
-从源代码的[VirtualRoot](https://github.com/ntminer/ntminer/blob/master/src/NTMinerlib/VirtualRoot.cs)类型开始。望文生义,这是虚无,这是根。这是第一个出现的东西,它处在[开源矿工](https://github.com/ntminer/ntminer)的最底层,所有上层建筑都建立在它之上。它下面肯定也有东西,但那是编程语言和运行环境的世界,我们的世界从VirtualRoot开始。
+从源代码的[VirtualRoot](https://github.com/ntminer/NtMiner/blob/master/src/NTMinerlib/VirtualRoot.cs)类型开始。望文生义,这是虚无,这是根。这是第一个出现的东西,它处在[开源矿工](https://github.com/ntminer/NtMiner)的最底层,所有上层建筑都建立在它之上。它下面肯定也有东西,但那是编程语言和运行环境的世界,我们的世界从VirtualRoot开始。
 ## VirtualRoot
 VirtualRoot是个静态类型,它不是被我们构建而生的,有世界的时候它就已经在那里了,它是从0开始的0,它是整个世界的根。VirtualRoot根上挂载的事物也全都是静态的在世界开始的时候就已经在那里的事物,直接挂载在VirtualRoot上的事物有:
 1. JsonSerializer
-它是粉碎机,它是重建器,它安装在系统的出入口处,[开源矿工](https://github.com/ntminer/ntminer)内部的物体流出系统前会被它打碎成下层系统的物体,下层原子世界不需要我们关注;外部系统的事物在进入我们的系统后首先会被它重建成我们的世界里事物的样子。我们的系统只有一个入口和一个出口,这唯一的出入口就是VirtualRoot,是根。
+它是粉碎机,它是重建器,它安装在系统的出入口处,[开源矿工](https://github.com/ntminer/NtMiner)内部的物体流出系统前会被它打碎成下层系统的物体,下层原子世界不需要我们关注;外部系统的事物在进入我们的系统后首先会被它重建成我们的世界里事物的样子。我们的系统只有一个入口和一个出口,这唯一的出入口就是VirtualRoot,是根。
 2. MessageDispatcher
-推进器、动力源,或者其它什么名字,照此理解就可以了,[开源矿工](https://github.com/ntminer/ntminer)系统内部运动的动力之源就是它,至于它的动力又是来自CPU这种下层世界的事物又超出我们的世界之外去了。
+推进器、动力源,或者其它什么名字,照此理解就可以了,[开源矿工](https://github.com/ntminer/NtMiner)系统内部运动的动力之源就是它,至于它的动力又是来自CPU这种下层世界的事物又超出我们的世界之外去了。
 3. CommandBus
 命令电车,它由MessageDispatcher驱动,MessageDispatcher不是装在它里面的引擎,MessageDispatcher是电。它运载命令,命令是一种消息,消息是一种空间结构体。
 4. EventBus

+ 1 - 1
docs/NTMiner1.md

@@ -21,4 +21,4 @@
 空间是一种相对静止的存在,就像人有五肢,我们可以将人体看作一棵树,头、双手、双脚5肢的相对位置在这棵树上来说是固定的相对静止的,当我们说人体5肢之间的关系是静止的时候我们将自己放在了这个树的域中,此时作为观察者的我们进入了这段树枝的世界,我们要准确的认知此时自己无法从树枝的一个枝丫跳进树外然后再由树外到达另一个枝丫,我们必须在树上运动,所以这段树枝的相对关系是静止的。观察者和参照点随运动而生,我们必须准确认知自己作为观察者当前所处的位置和观察的方向,我们必须能够意识到作为观察者的自己有没有变换位置。
 
 ## 开源矿工这棵系统树内部划分有5大域
-[开源矿工](https://github.com/ntminer/ntminer)这棵系统树内部划分有5大域。这5大作用域是:币种集、矿池集,内核集、币种x内核集、矿池x内核集。我们说[开源矿工](https://github.com/ntminer/ntminer)内部有这5大域,而不是说总共只有这5大域,因为[开源矿工](https://github.com/ntminer/ntminer)这棵树作为一个整体也是域啊,系统内部的每一个对象也是域啊,而是说这5大域是值得显式化描述的5大域。请记住它们,它们是:币种集、矿池集,内核集、币种x内核集、矿池x内核集。
+[开源矿工](https://github.com/ntminer/NtMiner)这棵系统树内部划分有5大域。这5大作用域是:币种集、矿池集,内核集、币种x内核集、矿池x内核集。我们说[开源矿工](https://github.com/ntminer/NtMiner)内部有这5大域,而不是说总共只有这5大域,因为[开源矿工](https://github.com/ntminer/NtMiner)这棵树作为一个整体也是域啊,系统内部的每一个对象也是域啊,而是说这5大域是值得显式化描述的5大域。请记住它们,它们是:币种集、矿池集,内核集、币种x内核集、矿池x内核集。

+ 5 - 5
docs/NTMiner2.md

@@ -1,18 +1,18 @@
 # 识别系统的内外
 
-这个系列每一篇都很短,主要用于将[开源矿工](https://github.com/ntminer/ntminer)架构和源代码中的每一个值得说明的概念都描述出来,因为[开源矿工](https://github.com/ntminer/ntminer)的源代码上的注释实在太少,不是因为刻意不添加注释而是因为它的源代码组织结构和运行时景象和大部分程序员平时所掌握的不太匹配,添加的注释会让人不知所云。
+这个系列每一篇都很短,主要用于将[开源矿工](https://github.com/ntminer/NtMiner)架构和源代码中的每一个值得说明的概念都描述出来,因为[开源矿工](https://github.com/ntminer/NtMiner)的源代码上的注释实在太少,不是因为刻意不添加注释而是因为它的源代码组织结构和运行时景象和大部分程序员平时所掌握的不太匹配,添加的注释会让人不知所云。
 所以决定先有这个系列,先熟悉一下抽象的大致轮廓再去接触开发就像是带着地图去那里行走不至于迷路,后续会添加注释,添加的注释往往会是一些表地点的名词和表运动的动词往往不是完整的句子。
 
 ## 数据结构
 在[上一篇](NTMiner1.md)我们知道域是地域,是资源组织结构,其实就是空间,就是计算机领域所说的数据结构。在第0篇我们信仰所有的系统在空间结构上都是树形,之所以用“坚信”、“信念”这样的词是因为我们不想证明它,我只在意这个断言能否帮助我节能高效的工作。现在我们将具体化,将树形构造定律幻化到计算机领域的数据结构,我们认为:一个位是一个具有一个分叉的树,一个字节是一个具有8个分叉的树,一个int32是一个具有4个字节分叉的树……,由性别(2个位表示男、女、未知)、年龄、姓名组成的Person数据结构是由底层树组成的高层树。并且为了概念的完整性我们认为一个位也有两个枝杈,位的两个枝杈是0和1,它们是最小的不可分割的粒子,计算机世界是离散的世界正是因为到了01后不再可以分割。
-现在当我们再次说[开源矿工](https://github.com/ntminer/ntminer)系统是一棵树的时候,在空间结构上可能就是在说[开源矿工](https://github.com/ntminer/ntminer)的根、树枝、叶子等数据结构了。
+现在当我们再次说[开源矿工](https://github.com/ntminer/NtMiner)系统是一棵树的时候,在空间结构上可能就是在说[开源矿工](https://github.com/ntminer/NtMiner)的根、树枝、叶子等数据结构了。
 
 ## 系统内外的界定
-[上一篇](NTMiner1.md)我们还说树是添加了偏移量的集合,既然是集合那一定有内外。我们认为在空间维度树的根是最大的集合、叶子是最小的集合,在行为维度叶子是最大的集合,根是最小的集合,也就是说我们认为当我们用相对静止的眼光看系统的时候我们在关注空间,当我们以运动、变化的眼光看系统的时候我们在关注时序。在前篇我们也多次提到边界、域就是地域边界,当我们认一个方向树的边界有两端,在操作系统看来[开源矿工](https://github.com/ntminer/ntminer)是个进程(资源树),操作系统的进程概念是比[开源矿工](https://github.com/ntminer/ntminer)更大的概念,涵盖的空间更大所以是更大的集合。在一棵更大的树中进程可以被看作开源矿工的根,但这超出了我们的系统进入到域外世界去了。前篇我们还提到原子也超出我们的世界了,原子只是个比喻,这里的原子指的是bit、和由bit组成的bool、int等,因为它们在我们的世界来说没有意义,币种、矿池、内核等才是我们的世界的事物,币种矿池内核等数据结构中被我们取了名称的属性(或者叫字段)也是我们的世界的事物,而属性是由bool树、int树、byte树组成的,这里的bool、int、byte、bit等概念也不属于我们的世界,它们就是我们说的原子世界,它们是计算机行为的根,因为计算机在运行时只认它们,所以说在运行时这个时序维度或者叫运动维度根是bit。两头的世界我们都不关心,它们都是我们的外。
+[上一篇](NTMiner1.md)我们还说树是添加了偏移量的集合,既然是集合那一定有内外。我们认为在空间维度树的根是最大的集合、叶子是最小的集合,在行为维度叶子是最大的集合,根是最小的集合,也就是说我们认为当我们用相对静止的眼光看系统的时候我们在关注空间,当我们以运动、变化的眼光看系统的时候我们在关注时序。在前篇我们也多次提到边界、域就是地域边界,当我们认一个方向树的边界有两端,在操作系统看来[开源矿工](https://github.com/ntminer/NtMiner)是个进程(资源树),操作系统的进程概念是比[开源矿工](https://github.com/ntminer/NtMiner)更大的概念,涵盖的空间更大所以是更大的集合。在一棵更大的树中进程可以被看作开源矿工的根,但这超出了我们的系统进入到域外世界去了。前篇我们还提到原子也超出我们的世界了,原子只是个比喻,这里的原子指的是bit、和由bit组成的bool、int等,因为它们在我们的世界来说没有意义,币种、矿池、内核等才是我们的世界的事物,币种矿池内核等数据结构中被我们取了名称的属性(或者叫字段)也是我们的世界的事物,而属性是由bool树、int树、byte树组成的,这里的bool、int、byte、bit等概念也不属于我们的世界,它们就是我们说的原子世界,它们是计算机行为的根,因为计算机在运行时只认它们,所以说在运行时这个时序维度或者叫运动维度根是bit。两头的世界我们都不关心,它们都是我们的外。
 
 ## 外属性 —— 需要感知的外部事物
-通过上一段我们认知到[开源矿工](https://github.com/ntminer/ntminer)的生存环境在我们的系统树外,组成[开源矿工](https://github.com/ntminer/ntminer)空间结构体的int、bool、bit也在我们的系统树外。[开源矿工](https://github.com/ntminer/ntminer)系统需要感知自己的生存环境,感知外部世界就是摆动自己的域内世界从而建立和域外世界的对应关系。[开源矿工](https://github.com/ntminer/ntminer)需要感知的外部世界的事物我们就称作外属性。它们是:计算机名、物理内存、虚拟内容、BIOS信息、显卡、驱动、文件系统等信息。
+通过上一段我们认知到[开源矿工](https://github.com/ntminer/NtMiner)的生存环境在我们的系统树外,组成[开源矿工](https://github.com/ntminer/NtMiner)空间结构体的int、bool、bit也在我们的系统树外。[开源矿工](https://github.com/ntminer/NtMiner)系统需要感知自己的生存环境,感知外部世界就是摆动自己的域内世界从而建立和域外世界的对应关系。[开源矿工](https://github.com/ntminer/NtMiner)需要感知的外部世界的事物我们就称作外属性。它们是:计算机名、物理内存、虚拟内容、BIOS信息、显卡、驱动、文件系统等信息。
 ## 内属性 —— 本系统内的一切事物
-[开源矿工](https://github.com/ntminer/ntminer)的内属性是什么?可以认为[开源矿工](https://github.com/ntminer/ntminer)系统内的一切事物都是内属性。[开源矿工](https://github.com/ntminer/ntminer)展示给用户的一切都是它的内属性,只展示给开发人员的事物也是它的内属性。[开源矿工](https://github.com/ntminer/ntminer)内部建立的对外部世界事物的对应也是它的内属性,只有当我们在[开源矿工](https://github.com/ntminer/ntminer)域外比如在QQ系统中说计算机名、物理内存等外部事物的时候它们才是和[开源矿工](https://github.com/ntminer/ntminer)没有关系的外属性。
+[开源矿工](https://github.com/ntminer/NtMiner)的内属性是什么?可以认为[开源矿工](https://github.com/ntminer/NtMiner)系统内的一切事物都是内属性。[开源矿工](https://github.com/ntminer/NtMiner)展示给用户的一切都是它的内属性,只展示给开发人员的事物也是它的内属性。[开源矿工](https://github.com/ntminer/NtMiner)内部建立的对外部世界事物的对应也是它的内属性,只有当我们在[开源矿工](https://github.com/ntminer/NtMiner)域外比如在QQ系统中说计算机名、物理内存等外部事物的时候它们才是和[开源矿工](https://github.com/ntminer/NtMiner)没有关系的外属性。
 
 为什么我们要感知那么多外部世界,因为只要我们感知了所有需要感知的外部事物,就是说只要我们的系统内部具有和所需认知的外部世界的对应关系,这样我们就能更好的适应生存环境,比如我们要支持无盘运行只需摆动开源矿工系统内部的一个参数即可。

+ 5 - 5
docs/NTMinerBkPool.md

@@ -2,21 +2,21 @@
 
 本文基于内核自有的备用矿池功能实现开源矿工的备用矿池,以后可能会脱离具体内核实现备用矿池。
 
-设计和实现备用矿池的功能时充分体现了为什么[开源矿工](https://github.com/ntminer/ntminer)会有10多万行代码。
+设计和实现备用矿池的功能时充分体现了为什么[开源矿工](https://github.com/ntminer/NtMiner)会有10多万行代码。
 ## 备用矿池分两种:
 ### 一种像Claymore通过写一个Claymore指定的文件实现;
 ### 一种像更合理的NBMiner通过正常的命令行参数实现,-o是主矿池-o1是备用矿池;
-这两种的不同只是外部系统向内核内部系统传送信息的方式不同,claymore通过文件传送信息的方式对直接使用claymore原版挺方便但对像[开源矿工](https://github.com/ntminer/ntminer)这样的第三方程序来说导致引入了除命令行参数外的第二种向内核输入数据的方式。
+这两种的不同只是外部系统向内核内部系统传送信息的方式不同,claymore通过文件传送信息的方式对直接使用claymore原版挺方便但对像[开源矿工](https://github.com/ntminer/NtMiner)这样的第三方程序来说导致引入了除命令行参数外的第二种向内核输入数据的方式。
 
-通过硬编码功能完备的编程语言是可以实现特定于Claymore的备用矿池的,但[开源矿工](https://github.com/ntminer/ntminer)想做的通用,想一次性解决所有像Claymore这样的内核,为此需要抽象出其中的模式写出更多的代码,但如果掌握了规则会发现[开源矿工](https://github.com/ntminer/ntminer)10万行的代码如同1万行,因为所有地方都一致。
+通过硬编码功能完备的编程语言是可以实现特定于Claymore的备用矿池的,但[开源矿工](https://github.com/ntminer/NtMiner)想做的通用,想一次性解决所有像Claymore这样的内核,为此需要抽象出其中的模式写出更多的代码,但如果掌握了规则会发现[开源矿工](https://github.com/ntminer/NtMiner)10万行的代码如同1万行,因为所有地方都一致。
 
 ## 面向类Claymore内核的文件书写器
 为了写内核的备用矿池文件,我们需要一个文件书写器,比如对应Claymore的内核配置会引用一个文件书写器,文件书写器需要知道往哪个位置写以及写什么内容。
 ### 向什么位置书写
-文件书写器向小编给定的位置书写(小编作为一个外部系统向[开源矿工](https://github.com/ntminer/ntminer)系统输入信息的唯一途径是[开源矿工](https://github.com/ntminer/ntminer)启动时从阿里云下载的server.json,这个server.json会在界面上发生用户活动时检测到更新时刷新),要写向的这个位置应是一个相对位置,最终的完整位置应由运行时根据当时的上下文信息计算得到。
+文件书写器向小编给定的位置书写(小编作为一个外部系统向[开源矿工](https://github.com/ntminer/NtMiner)系统输入信息的唯一途径是[开源矿工](https://github.com/ntminer/NtMiner)启动时从阿里云下载的server.json,这个server.json会在界面上发生用户活动时检测到更新时刷新),要写向的这个位置应是一个相对位置,最终的完整位置应由运行时根据当时的上下文信息计算得到。
 ### 书写什么内容
 书写小编指定的内容,但小编指定的内容中会有变量,这些变量需要在写之前根据当前的上下文信息赋值。以Claymore为例,小编给定的内容会是POOL: {pool1}, WALLET: {wallet1}/{worker1}, PSW: x, ESM: 0, ALLPOOLS: 0这样的内容,里面的大括号括住的是变量,这些变量需要运行时在写文件之前填充上,其中任何一个大括号括住的位置从环境中得不到值则表示不匹配(如同计算机语言的函数调用和函数签名不匹配),不匹配的书写器将被忽略,这是一个错误,如果一个书写器被运行时索引出来准备执行那么它一定是匹配的,否则就是BUG。
-是不是很熟悉?文件书写器像什么?是不是像个函数?向什么位置书写和写什么内容是它的入参,而书写的内容中又有大括号括住的如同{pool1}这样的东西,{pool1}这是函数体中用到的父级作用域中的变量,对于[开源矿工](https://github.com/ntminer/ntminer)来说它依然是一个纯函数因为它只从上下文中读取变量的值而并没有写,对于[开源矿工](https://github.com/ntminer/ntminer)来说这个函数没有输出,因为它输出到外部文件系统中去了。
+是不是很熟悉?文件书写器像什么?是不是像个函数?向什么位置书写和写什么内容是它的入参,而书写的内容中又有大括号括住的如同{pool1}这样的东西,{pool1}这是函数体中用到的父级作用域中的变量,对于[开源矿工](https://github.com/ntminer/NtMiner)来说它依然是一个纯函数因为它只从上下文中读取变量的值而并没有写,对于[开源矿工](https://github.com/ntminer/NtMiner)来说这个函数没有输出,因为它输出到外部文件系统中去了。
 
 这个设计是可以支持钱包地址形式和用户名密码形式的备用矿池的,只需给内核关联两条文件书写器记录,一条书写器中有{wallet1}这样的变量导致它只会在上下文中有钱包地址时执行,另一条书写器中有{userName1}变量导致它只会在上下文中有用户名时执行。值得指出的是{wallet1}、{userName1}虽然和{wallet}、{userName}不同,但{wallet1}的值和{wallet}相同因为{wallet1}直接只是{wallet}的别名,而{userName1}和{userName}很可能不同,因为用户名密码形式的备用矿池会在界面上拥有对应{userName1}的输入框。
 

+ 2 - 2
docs/Overclock.md

@@ -4,7 +4,7 @@
 
 在实现过程中发现,超频一直是AMD的强项,是AMD的一件历史悠久的事情,所以超频接口也是稳定可靠且版本兼容的,这一点应该给AMD个赞。
 [这是AMD的文档](https://github.com/GPUOpen-LibrariesAndSDKs/display-library)
-[用到的相关接口](https://github.com/ntminer/ntminer/blob/master/src/NTMiner.Core/Core/Gpus/Impl/Amd/AdlNativeMethods.cs)
+[用到的相关接口](https://github.com/ntminer/NtMiner/blob/master/src/NTMiner.Core/Core/Gpus/Impl/Amd/AdlNativeMethods.cs)
 
 已实现并上线,自版本号NTMiner 2.2.0.0往后都支持A卡超频。
 
@@ -15,6 +15,6 @@
 
 这个工作已经上线一段时间了,因为开源矿工的源码是完全开源的,根据传统现在需要指出如何实现这些功能。
 
-[相关代码的位置在](https://github.com/ntminer/ntminer/tree/master/src/NTMinerGpus/Gpus)
+[相关代码的位置在](https://github.com/ntminer/NtMiner/tree/master/src/NTMinerGpus/Gpus)
 
 代码已经相当清晰,希望对竞品有用。值得说明的是开源矿工的超频实现对全系显卡有效,包括NVIDIA p系列的专业矿卡,10系以及20系卡,以及全系A卡。

+ 1 - 0
docs/Server/README.md

@@ -0,0 +1 @@
+服务端都是.NET45,挖矿端都是.NET40

+ 4 - 3
docs/Timer.md → docs/TimingEventProducer.md

@@ -3,9 +3,9 @@
 大部分程序会有一些需要周期执行的事情,也有一些需要在程序启动后执行一次的事情。很明显这类事情和时间有关,因为它们都需要在特定时间发生。这类需要执行的逻辑可以由时间事件发生器触发。
 
 ## 时间事件发生器
-时间事件发生器是挂载在虚拟根[VirtualRoot](<https://github.com/ntminer/ntminer/blob/master/src/NTMinerlib/VirtualRoot.cs>)上的一个组件,它负责在特定的时间将特定类型的事件发布到系统总线上去。时间事件发生器会发布的事件有:
+时间事件发生器是挂载在虚拟根[VirtualRoot](<https://github.com/ntminer/NtMiner/blob/master/src/NTMinerlib/VirtualRoot.cs>)上的一个组件,它负责在特定的时间将特定类型的事件发布到系统总线上去。时间事件发生器会发布的事件有:
 
-[代码位置](https://github.com/ntminer/ntminer/blob/master/src/NTMinerHub/Messages.cs)
+[代码位置](https://github.com/ntminer/NtMiner/blob/master/src/NTMinerHub/Messages.cs)
 
 ```
 HasBoot1SecondEvent
@@ -35,13 +35,14 @@ Per20MinuteEvent
 Per50MinuteEvent
 Per100MinuteEvent
 Per24HourEvent
+NewDayEvent
 ```
 
 以上26种时间事件的设计灵感来源于人民币币值的设计,具体为什么是这些数值我们不做深究,实践证明这些数值正是开源矿工所需要的。
 
 ## 由谁发出这些时间事件?
 
-可以看出,这类时间事件不属于任何模块,它们是由硬件系统的cpu发出的,所以对于我们的系统来说应该把时间事件发生器放在我们的系统的虚拟根[VirtualRoot](<https://github.com/ntminer/ntminer/blob/master/src/NTMinerlib/VirtualRoot.cs>)上。
+可以看出,这类时间事件不属于任何模块,它们是由硬件系统的cpu发出的,所以对于我们的系统来说应该把时间事件发生器放在我们的系统的虚拟根[VirtualRoot](<https://github.com/ntminer/NtMiner/blob/master/src/NTMinerlib/VirtualRoot.cs>)上。
 
 ## 这些消息发送到什么地方去?
 发布到系统总线上去。每一个系统都应该有自己内部的系统总线,时间事件消息正是发布到系统自己内部的总线上去的。系统内部的各组件根据自己的需求从总线上订阅事件修建事件消息所行走的路径。

+ 0 - 263
src/AppModels/AppContext.cs

@@ -1,263 +0,0 @@
-using NTMiner.Hub;
-using NTMiner.Vms;
-using System;
-using System.Collections.Generic;
-
-namespace NTMiner {
-    public partial class AppContext {
-        public static readonly AppContext Instance = new AppContext();
-
-        public static ExtendedNotifyIcon NotifyIcon;
-
-        private static readonly List<IMessagePathId> _contextPathIds = new List<IMessagePathId>();
-
-        private AppContext() {
-        }
-
-        #region static methods
-        // 因为是上下文路径,无需返回路径标识
-        public static void AddCmdPath<TCmd>(string description, LogEnum logType, Action<TCmd> action, Type location)
-            where TCmd : ICmd {
-            var messagePathId = VirtualRoot.AddMessagePath(description, logType, action, location);
-            _contextPathIds.Add(messagePathId);
-        }
-
-        // 因为是上下文路径,无需返回路径标识
-        public static void AddEventPath<TEvent>(string description, LogEnum logType, Action<TEvent> action, Type location)
-            where TEvent : IEvent {
-            var messagePathId = VirtualRoot.AddMessagePath(description, logType, action, location);
-            _contextPathIds.Add(messagePathId);
-        }
-
-        public static void Enable() {
-            foreach (var pathId in _contextPathIds) {
-                pathId.IsEnabled = true;
-            }
-        }
-
-        public static void Disable() {
-            foreach (var pathId in _contextPathIds) {
-                pathId.IsEnabled = false;
-            }
-        }
-        #endregion
-
-        #region context
-        public MinerClientsWindowViewModel MinerClientsWindowVm {
-            get {
-                return MinerClientsWindowViewModel.Instance;
-            }
-        }
-
-        public MinerProfileViewModel MinerProfileVm {
-            get {
-                return MinerProfileViewModel.Instance;
-            }
-        }
-
-        public CoinViewModels CoinVms {
-            get {
-                return CoinViewModels.Instance;
-            }
-        }
-
-        public GpuSpeedViewModels GpuSpeedVms {
-            get {
-                return GpuSpeedViewModels.Instance;
-            }
-        }
-
-        public StartStopMineButtonViewModel StartStopMineButtonVm {
-            get {
-                return StartStopMineButtonViewModel.Instance;
-            }
-        }
-
-        public PoolKernelViewModels PoolKernelVms {
-            get {
-                return PoolKernelViewModels.Instance;
-            }
-        }
-
-        public PackageViewModels PackageVms {
-            get {
-                return PackageViewModels.Instance;
-            }
-        }
-
-        public CoinGroupViewModels CoinGroupVms {
-            get {
-                return CoinGroupViewModels.Instance;
-            }
-        }
-
-        public FileWriterViewModels FileWriterVms {
-            get {
-                return FileWriterViewModels.Instance;
-            }
-        }
-
-        public FragmentWriterViewModels FragmentWriterVms {
-            get {
-                return FragmentWriterViewModels.Instance;
-            }
-        }
-
-        public CoinKernelViewModels CoinKernelVms {
-            get {
-                return CoinKernelViewModels.Instance;
-            }
-        }
-
-        public CoinProfileViewModels CoinProfileVms {
-            get {
-                return CoinProfileViewModels.Instance;
-            }
-        }
-
-        public CoinSnapshotDataViewModels CoinSnapshotDataVms {
-            get {
-                return CoinSnapshotDataViewModels.Instance;
-            }
-        }
-
-        public ColumnsShowViewModels ColumnsShowVms {
-            get {
-                return ColumnsShowViewModels.Instance;
-            }
-        }
-
-        public DriveSetViewModel DriveSetVm {
-            get {
-                return DriveSetViewModel.Instance;
-            }
-        }
-
-        public VirtualMemorySetViewModel VirtualMemorySetVm {
-            get {
-                return VirtualMemorySetViewModel.Instance;
-            }
-        }
-
-        public GpuProfileViewModels GpuProfileVms {
-            get {
-                return GpuProfileViewModels.Instance;
-            }
-        }
-
-        public GpuViewModels GpuVms {
-            get {
-                return GpuViewModels.Instance;
-            }
-        }
-
-        public GroupViewModels GroupVms {
-            get {
-                return GroupViewModels.Instance;
-            }
-        }
-
-        public KernelInputViewModels KernelInputVms {
-            get {
-                return KernelInputViewModels.Instance;
-            }
-        }
-
-        public KernelOutputKeywordViewModels KernelOutputKeywordVms {
-            get {
-                return KernelOutputKeywordViewModels.Instance;
-            }
-        }
-
-        public KernelOutputTranslaterViewModels KernelOutputTranslaterVms {
-            get {
-                return KernelOutputTranslaterViewModels.Instance;
-            }
-        }
-
-        public KernelOutputViewModels KernelOutputVms {
-            get {
-                return KernelOutputViewModels.Instance;
-            }
-        }
-
-        public KernelViewModels KernelVms {
-            get {
-                return KernelViewModels.Instance;
-            }
-        }
-
-        public MinerGroupViewModels MinerGroupVms {
-            get {
-                return MinerGroupViewModels.Instance;
-            }
-        }
-
-        public MineWorkViewModels MineWorkVms {
-            get {
-                return MineWorkViewModels.Instance;
-            }
-        }
-
-        public OverClockDataViewModels OverClockDataVms {
-            get {
-                return OverClockDataViewModels.Instance;
-            }
-        }
-
-        public NTMinerWalletViewModels NTMinerWalletVms {
-            get {
-                return NTMinerWalletViewModels.Instance;
-            }
-        }
-
-        public PoolProfileViewModels PoolProfileVms {
-            get {
-                return PoolProfileViewModels.Instance;
-            }
-        }
-
-        public PoolViewModels PoolVms {
-            get {
-                return PoolViewModels.Instance;
-            }
-        }
-
-        public ShareViewModels ShareVms {
-            get {
-                return ShareViewModels.Instance;
-            }
-        }
-
-        public WalletViewModels WalletVms {
-            get {
-                return WalletViewModels.Instance;
-            }
-        }
-
-        public UserViewModels UserVms {
-            get {
-                return UserViewModels.Instance;
-            }
-        }
-
-        public SysDicViewModels SysDicVms {
-            get {
-                return SysDicViewModels.Instance;
-            }
-        }
-
-        public SysDicItemViewModels SysDicItemVms {
-            get {
-                return SysDicItemViewModels.Instance;
-            }
-        }
-
-        public GpuStatusBarViewModel GpuStatusBarVm {
-            get {
-                return GpuStatusBarViewModel.Instance;
-            }
-        }
-        #endregion
-    }
-}

+ 0 - 64
src/AppModels/AppContext.partials.ColumnsShowViewModels.cs

@@ -1,64 +0,0 @@
-using NTMiner.Core;
-using NTMiner.Vms;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Input;
-
-namespace NTMiner {
-    public partial class AppContext {
-        public class ColumnsShowViewModels : ViewModelBase {
-            public static readonly ColumnsShowViewModels Instance = new ColumnsShowViewModels();
-
-            private readonly Dictionary<Guid, ColumnsShowViewModel> _dicById = new Dictionary<Guid, ColumnsShowViewModel>();
-
-            public ICommand Add { get; private set; }
-
-            private ColumnsShowViewModels() {
-#if DEBUG
-                NTStopwatch.Start();
-#endif
-                this.Add = new DelegateCommand(() => {
-                    new ColumnsShowViewModel(Guid.NewGuid()).Edit.Execute(FormType.Add);
-                });
-                AddEventPath<ColumnsShowAddedEvent>("添加了列显后刷新VM内存", LogEnum.DevConsole,
-                    action: message => {
-                        if (!_dicById.ContainsKey(message.Target.GetId())) {
-                            ColumnsShowViewModel vm = new ColumnsShowViewModel(message.Target);
-                            _dicById.Add(message.Target.GetId(), vm);
-                            OnPropertyChanged(nameof(List));
-                            AppContext.Instance.MinerClientsWindowVm.ColumnsShow = vm;
-                        }
-                    }, location: this.GetType());
-                AddEventPath<ColumnsShowUpdatedEvent>("更新了列显后刷新VM内存", LogEnum.DevConsole,
-                    action: message => {
-                        if (_dicById.ContainsKey(message.Target.GetId())) {
-                            ColumnsShowViewModel entity = _dicById[message.Target.GetId()];
-                            entity.Update(message.Target);
-                        }
-                    }, location: this.GetType());
-                AddEventPath<ColumnsShowRemovedEvent>("移除了列显后刷新VM内存", LogEnum.DevConsole,
-                    action: message => {
-                        AppContext.Instance.MinerClientsWindowVm.ColumnsShow = _dicById.Values.FirstOrDefault();
-                        _dicById.Remove(message.Target.GetId());
-                        OnPropertyChanged(nameof(List));
-                    }, location: this.GetType());
-                foreach (var item in NTMinerRoot.Instance.ColumnsShowSet.AsEnumerable()) {
-                    _dicById.Add(item.GetId(), new ColumnsShowViewModel(item));
-                }
-#if DEBUG
-                var elapsedMilliseconds = NTStopwatch.Stop();
-                if (elapsedMilliseconds.ElapsedMilliseconds > NTStopwatch.ElapsedMilliseconds) {
-                    Write.DevTimeSpan($"耗时{elapsedMilliseconds} {this.GetType().Name}.ctor");
-                }
-#endif
-            }
-
-            public List<ColumnsShowViewModel> List {
-                get {
-                    return _dicById.Values.ToList();
-                }
-            }
-        }
-    }
-}

+ 0 - 83
src/AppModels/AppContext.partials.MineWorkViewModels.cs

@@ -1,83 +0,0 @@
-using NTMiner.Core;
-using NTMiner.Vms;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Input;
-
-namespace NTMiner {
-    public partial class AppContext {
-        public class MineWorkViewModels : ViewModelBase {
-            public static readonly MineWorkViewModels Instance = new MineWorkViewModels();
-            private readonly Dictionary<Guid, MineWorkViewModel> _dicById = new Dictionary<Guid, MineWorkViewModel>();
-            public ICommand Add { get; private set; }
-
-            private MineWorkViewModels() {
-#if DEBUG
-                NTStopwatch.Start();
-#endif
-                if (WpfUtil.IsInDesignMode) {
-                    return;
-                }
-                foreach (var item in NTMinerRoot.Instance.MineWorkSet.AsEnumerable()) {
-                    _dicById.Add(item.GetId(), new MineWorkViewModel(item));
-                }
-                this.Add = new DelegateCommand(() => {
-                    new MineWorkViewModel(Guid.NewGuid()).Edit.Execute(FormType.Add);
-                });
-                AddEventPath<MineWorkAddedEvent>("添加作业后刷新VM内存", LogEnum.DevConsole,
-                    action: message => {
-                        if (!_dicById.ContainsKey(message.Target.GetId())) {
-                            _dicById.Add(message.Target.GetId(), new MineWorkViewModel(message.Target));
-                            OnPropertyChanged(nameof(List));
-                            OnPropertyChanged(nameof(MineWorkVmItems));
-                            if (message.Target.GetId() == AppContext.Instance.MinerClientsWindowVm.SelectedMineWork.GetId()) {
-                                AppContext.Instance.MinerClientsWindowVm.SelectedMineWork = MineWorkViewModel.PleaseSelect;
-                            }
-                        }
-                    }, location: this.GetType());
-                AddEventPath<MineWorkUpdatedEvent>("更新作业后刷新VM内存", LogEnum.DevConsole,
-                    action: message => {
-                        _dicById[message.Target.GetId()].Update(message.Target);
-                    }, location: this.GetType());
-                AddEventPath<MineWorkRemovedEvent>("删除作业后刷新VM内存", LogEnum.DevConsole,
-                    action: message => {
-                        _dicById.Remove(message.Target.GetId());
-                        OnPropertyChanged(nameof(List));
-                        OnPropertyChanged(nameof(MineWorkVmItems));
-                        if (message.Target.GetId() == AppContext.Instance.MinerClientsWindowVm.SelectedMineWork.GetId()) {
-                            AppContext.Instance.MinerClientsWindowVm.SelectedMineWork = MineWorkViewModel.PleaseSelect;
-                        }
-                    }, location: this.GetType());
-#if DEBUG
-                var elapsedMilliseconds = NTStopwatch.Stop();
-                if (elapsedMilliseconds.ElapsedMilliseconds > NTStopwatch.ElapsedMilliseconds) {
-                    Write.DevTimeSpan($"耗时{elapsedMilliseconds} {this.GetType().Name}.ctor");
-                }
-#endif
-            }
-
-            public List<MineWorkViewModel> List {
-                get {
-                    return _dicById.Values.ToList();
-                }
-            }
-
-            private IEnumerable<MineWorkViewModel> GetMineWorkVmItems() {
-                yield return MineWorkViewModel.PleaseSelect;
-                foreach (var item in List) {
-                    yield return item;
-                }
-            }
-            public List<MineWorkViewModel> MineWorkVmItems {
-                get {
-                    return GetMineWorkVmItems().ToList();
-                }
-            }
-
-            public bool TryGetMineWorkVm(Guid id, out MineWorkViewModel mineWorkVm) {
-                return _dicById.TryGetValue(id, out mineWorkVm);
-            }
-        }
-    }
-}

+ 0 - 80
src/AppModels/AppContext.partials.MinerGroupViewModels.cs

@@ -1,80 +0,0 @@
-using NTMiner.Core;
-using NTMiner.Vms;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Input;
-
-namespace NTMiner {
-    public partial class AppContext {
-        public class MinerGroupViewModels : ViewModelBase {
-            public static readonly MinerGroupViewModels Instance = new MinerGroupViewModels();
-            private readonly Dictionary<Guid, MinerGroupViewModel> _dicById = new Dictionary<Guid, MinerGroupViewModel>();
-
-            public ICommand Add { get; private set; }
-
-            private MinerGroupViewModels() {
-#if DEBUG
-                NTStopwatch.Start();
-#endif
-                if (WpfUtil.IsInDesignMode) {
-                    return;
-                }
-                foreach (var item in NTMinerRoot.Instance.MinerGroupSet.AsEnumerable()) {
-                    _dicById.Add(item.GetId(), new MinerGroupViewModel(item));
-                }
-                this.Add = new DelegateCommand(() => {
-                    new MinerGroupViewModel(Guid.NewGuid()).Edit.Execute(FormType.Add);
-                });
-                AddEventPath<MinerGroupAddedEvent>("添加矿机分组后刷新VM内存", LogEnum.DevConsole,
-                    action: message => {
-                        if (!_dicById.ContainsKey(message.Target.GetId())) {
-                            _dicById.Add(message.Target.GetId(), new MinerGroupViewModel(message.Target));
-                            OnPropertyChanged(nameof(List));
-                            OnPropertyChanged(nameof(MinerGroupItems));
-                            AppContext.Instance.MinerClientsWindowVm.OnPropertyChanged(nameof(MinerClientsWindowViewModel.SelectedMinerGroup));
-                        }
-                    }, location: this.GetType());
-                AddEventPath<MinerGroupUpdatedEvent>("更新矿机分组后刷新VM内存", LogEnum.DevConsole,
-                    action: message => {
-                        _dicById[message.Target.GetId()].Update(message.Target);
-                    }, location: this.GetType());
-                AddEventPath<MinerGroupRemovedEvent>("删除矿机分组后刷新VM内存", LogEnum.DevConsole,
-                    action: message => {
-                        _dicById.Remove(message.Target.GetId());
-                        OnPropertyChanged(nameof(List));
-                        OnPropertyChanged(nameof(MinerGroupItems));
-                        AppContext.Instance.MinerClientsWindowVm.OnPropertyChanged(nameof(MinerClientsWindowViewModel.SelectedMinerGroup));
-                    }, location: this.GetType());
-#if DEBUG
-                var elapsedMilliseconds = NTStopwatch.Stop();
-                if (elapsedMilliseconds.ElapsedMilliseconds > NTStopwatch.ElapsedMilliseconds) {
-                    Write.DevTimeSpan($"耗时{elapsedMilliseconds} {this.GetType().Name}.ctor");
-                }
-#endif
-            }
-
-            public List<MinerGroupViewModel> List {
-                get {
-                    return _dicById.Values.ToList();
-                }
-            }
-
-            public bool TryGetMineWorkVm(Guid id, out MinerGroupViewModel minerGroupVm) {
-                return _dicById.TryGetValue(id, out minerGroupVm);
-            }
-
-            private IEnumerable<MinerGroupViewModel> GetMinerGroupItems() {
-                yield return MinerGroupViewModel.PleaseSelect;
-                foreach (var item in List) {
-                    yield return item;
-                }
-            }
-            public List<MinerGroupViewModel> MinerGroupItems {
-                get {
-                    return GetMinerGroupItems().ToList();
-                }
-            }
-        }
-    }
-}

+ 0 - 64
src/AppModels/AppContext.partials.UserViewModels.cs

@@ -1,64 +0,0 @@
-using NTMiner.User;
-using NTMiner.Vms;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Input;
-
-namespace NTMiner {
-    public partial class AppContext {
-        public class UserViewModels : ViewModelBase {
-            public static readonly UserViewModels Instance = new UserViewModels();
-            private readonly Dictionary<string, UserViewModel> _dicByLoginName = new Dictionary<string, UserViewModel>();
-
-            public ICommand Add { get; private set; }
-
-            private UserViewModels() {
-#if DEBUG
-                NTStopwatch.Start();
-#endif
-                if (WpfUtil.IsInDesignMode) {
-                    return;
-                }
-                this.Add = new DelegateCommand(() => {
-                    if (!VirtualRoot.IsMinerStudio) {
-                        return;
-                    }
-                    new UserViewModel().Edit.Execute(FormType.Add);
-                });
-                AddEventPath<UserAddedEvent>("添加了用户后", LogEnum.DevConsole,
-                    action: message => {
-                        if (!_dicByLoginName.ContainsKey(message.Target.LoginName)) {
-                            _dicByLoginName.Add(message.Target.LoginName, new UserViewModel(message.Target));
-                            OnPropertyChanged(nameof(List));
-                        }
-                    }, location: this.GetType());
-                AddEventPath<UserUpdatedEvent>("更新了用户后", LogEnum.DevConsole,
-                    action: message => {
-                        if (_dicByLoginName.TryGetValue(message.Target.LoginName, out UserViewModel vm)) {
-                            vm.Update(message.Target);
-                        }
-                    }, location: this.GetType());
-                AddEventPath<UserRemovedEvent>("移除了用户后", LogEnum.DevConsole,
-                    action: message => {
-                        _dicByLoginName.Remove(message.Target.LoginName);
-                        OnPropertyChanged(nameof(List));
-                    }, location: this.GetType());
-                foreach (var item in NTMinerRoot.Instance.UserSet.AsEnumerable()) {
-                    _dicByLoginName.Add(item.LoginName, new UserViewModel(item));
-                }
-#if DEBUG
-                var elapsedMilliseconds = NTStopwatch.Stop();
-                if (elapsedMilliseconds.ElapsedMilliseconds > NTStopwatch.ElapsedMilliseconds) {
-                    Write.DevTimeSpan($"耗时{elapsedMilliseconds} {this.GetType().Name}.ctor");
-                }
-#endif
-            }
-
-            public List<UserViewModel> List {
-                get {
-                    return _dicByLoginName.Values.ToList();
-                }
-            }
-        }
-    }
-}

+ 86 - 61
src/AppModels/AppModels.csproj

@@ -45,51 +45,64 @@
     <Reference Include="PresentationFramework" />
     <Reference Include="System" />
     <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http">
+      <HintPath>..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll</HintPath>
+    </Reference>
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xaml" />
-    <Reference Include="Microsoft.CSharp" />
     <Reference Include="WindowsBase" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="AppContext.cs" />
-    <Compile Include="AppContext.partials.CoinGroupViewModels.cs" />
-    <Compile Include="AppContext.partials.CoinKernelViewModels.cs" />
-    <Compile Include="AppContext.partials.CoinProfileViewModels.cs" />
-    <Compile Include="AppContext.partials.CoinSnapshotDataViewModels.cs" />
-    <Compile Include="AppContext.partials.CoinViewModels.cs" />
-    <Compile Include="AppContext.partials.ColumnsShowViewModels.cs" />
-    <Compile Include="AppContext.partials.DriveSetViewModel.cs" />
-    <Compile Include="AppContext.partials.FragmentWriterViewModels.cs" />
-    <Compile Include="AppContext.partials.FileWriterViewModels.cs" />
-    <Compile Include="AppContext.partials.GpuProfileViewModels.cs" />
-    <Compile Include="AppContext.partials.GpuSpeedViewModels.cs" />
-    <Compile Include="AppContext.partials.GpuViewModels.cs" />
-    <Compile Include="AppContext.partials.GroupViewModels.cs" />
-    <Compile Include="AppContext.partials.KernelInputViewModels.cs" />
-    <Compile Include="AppContext.partials.KernelOutputKeywordViewModels.cs" />
-    <Compile Include="AppContext.partials.KernelOutputTranslaterViewModels.cs" />
-    <Compile Include="AppContext.partials.KernelOutputViewModels.cs" />
-    <Compile Include="AppContext.partials.KernelViewModels.cs" />
-    <Compile Include="AppContext.partials.NTMinerWalletViewModels.cs" />
-    <Compile Include="AppContext.partials.MinerGroupViewModels.cs" />
-    <Compile Include="AppContext.partials.MineWorkViewModels.cs" />
-    <Compile Include="AppContext.partials.OverClockDataViewModels.cs" />
-    <Compile Include="AppContext.partials.PackageViewModels.cs" />
-    <Compile Include="AppContext.partials.PoolKernelViewModels.cs" />
-    <Compile Include="AppContext.partials.PoolProfileViewModels.cs" />
-    <Compile Include="AppContext.partials.PoolViewModels.cs" />
-    <Compile Include="AppContext.partials.ShareViewModels.cs" />
-    <Compile Include="AppContext.partials.SysDicItemViewModels.cs" />
-    <Compile Include="AppContext.partials.SysDicViewModels.cs" />
-    <Compile Include="AppContext.partials.UserViewModels.cs" />
-    <Compile Include="AppContext.partials.WalletViewModels.cs" />
+    <Compile Include="AppRoot.cs" />
+    <Compile Include="AppRoot.partials.CoinGroupViewModels.cs" />
+    <Compile Include="AppRoot.partials.CoinKernelViewModels.cs" />
+    <Compile Include="AppRoot.partials.CoinProfileViewModels.cs" />
+    <Compile Include="MinerStudio\MinerStudioRoot.partials.CoinSnapshotDataViewModels.cs" />
+    <Compile Include="AppRoot.partials.CoinViewModels.cs" />
+    <Compile Include="MinerStudio\MinerStudioRoot.partials.ColumnsShowViewModels.cs" />
+    <Compile Include="AppRoot.partials.DriveSetViewModel.cs" />
+    <Compile Include="AppRoot.partials.FragmentWriterViewModels.cs" />
+    <Compile Include="AppRoot.partials.FileWriterViewModels.cs" />
+    <Compile Include="AppRoot.partials.GpuProfileViewModels.cs" />
+    <Compile Include="AppRoot.partials.GpuSpeedViewModels.cs" />
+    <Compile Include="AppRoot.partials.GpuViewModels.cs" />
+    <Compile Include="AppRoot.partials.GroupViewModels.cs" />
+    <Compile Include="AppRoot.partials.KernelInputViewModels.cs" />
+    <Compile Include="AppRoot.partials.KernelOutputKeywordViewModels.cs" />
+    <Compile Include="AppRoot.partials.KernelOutputTranslaterViewModels.cs" />
+    <Compile Include="AppRoot.partials.KernelOutputViewModels.cs" />
+    <Compile Include="AppRoot.partials.KernelViewModels.cs" />
+    <Compile Include="MinerStudio\MinerStudioRoot.partials.MinerClientConsoleViewModel.cs" />
+    <Compile Include="MinerStudio\MinerStudioRoot.partials.NTMinerWalletViewModels.cs" />
+    <Compile Include="MinerStudio\MinerStudioRoot.partials.MinerGroupViewModels.cs" />
+    <Compile Include="MinerStudio\MinerStudioRoot.partials.MineWorkViewModels.cs" />
+    <Compile Include="MinerStudio\MinerStudioRoot.partials.OverClockDataViewModels.cs" />
+    <Compile Include="AppRoot.partials.PackageViewModels.cs" />
+    <Compile Include="AppRoot.partials.PoolKernelViewModels.cs" />
+    <Compile Include="AppRoot.partials.PoolProfileViewModels.cs" />
+    <Compile Include="AppRoot.partials.PoolViewModels.cs" />
+    <Compile Include="AppRoot.partials.ShareViewModels.cs" />
+    <Compile Include="AppRoot.partials.SysDicItemViewModels.cs" />
+    <Compile Include="AppRoot.partials.SysDicViewModels.cs" />
+    <Compile Include="AppRoot.partials.WalletViewModels.cs" />
     <Compile Include="AppStatic.cs" />
     <Compile Include="ExtendedNotifyIcon.cs" />
+    <Compile Include="MinerStudio\Messages.cs" />
+    <Compile Include="MinerStudio\MinerStudioRoot.cs" />
+    <Compile Include="MinerStudio\Vms\IWsStateViewModel.cs" />
     <Compile Include="Messages.cs" />
+    <Compile Include="MinerStudio\EmptyMinerStudioService.cs" />
+    <Compile Include="MinerStudio\ILocalMinerStudioService.cs" />
+    <Compile Include="MinerStudio\IMinerStudioService.cs" />
+    <Compile Include="MinerStudio\Impl\LocalMinerStudioService.cs" />
+    <Compile Include="MinerStudio\Impl\ServerMinerStudioService.cs" />
+    <Compile Include="MinerStudio\IServerMinerStudioService.cs" />
+    <Compile Include="MinerStudio\Vms\LocalIpConfigViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\NTMinerFileSelectViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\NTMinerFileViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\VirtualMemoryViewModel.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="RemoteDesktop\Firewall.cs" />
-    <Compile Include="RemoteDesktop\Rdp.partial.cs" />
-    <Compile Include="RemoteDesktop\Rdp.cs" />
     <Compile Include="RemoteDesktop\RdpInput.cs" />
     <Compile Include="SortableExtension.cs" />
     <Compile Include="View\AbstractAppViewFactory.cs" />
@@ -100,8 +113,8 @@
     <Compile Include="Vms\CalcConfigViewModel.cs" />
     <Compile Include="Vms\CalcConfigViewModels.cs" />
     <Compile Include="Vms\CalcViewModel.cs" />
-    <Compile Include="Vms\ChartsWindowViewModel.cs" />
-    <Compile Include="Vms\ChartViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\ChartsWindowViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\ChartViewModel.cs" />
     <Compile Include="Vms\CoinGroupViewModel.cs" />
     <Compile Include="Vms\CoinIncomeViewModel.cs" />
     <Compile Include="Vms\CoinKernelProfileViewModel.cs" />
@@ -109,17 +122,23 @@
     <Compile Include="Vms\CoinKernelViewModel.cs" />
     <Compile Include="Vms\CoinPageViewModel.cs" />
     <Compile Include="Vms\CoinProfileViewModel.cs" />
-    <Compile Include="Vms\EthNoDevFeeEditViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\CoinSnapshotViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\ColumnsShowSelectViewModel.cs" />
     <Compile Include="Vms\InnerPropertyViewModel.cs" />
     <Compile Include="Vms\KernelOutputKeywordViewModel.cs" />
     <Compile Include="Vms\KernelOutputKeywordsViewModel.cs" />
+    <Compile Include="Vms\LocalMessageDtoViewModel.cs" />
     <Compile Include="Vms\LogFilesViewModel.cs" />
+    <Compile Include="Vms\MainMenuViewModel.cs" />
     <Compile Include="Vms\MessagePathIdsViewModel.cs" />
     <Compile Include="Vms\MessageTypeItem`1.cs" />
-    <Compile Include="Vms\MinerClientFinderConfigViewModel.cs" />
-    <Compile Include="Vms\NTMinerWalletPageViewModel.cs" />
-    <Compile Include="Vms\NTMinerWalletViewModel.cs" />
-    <Compile Include="Vms\RemoteDesktopLoginViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\MinerClientFinderConfigViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\MinerGroupSelectViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\MineWorkSelectViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\NTMinerWalletPageViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\NTMinerWalletViewModel.cs" />
+    <Compile Include="Vms\OperationResultViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\RemoteDesktopLoginViewModel.cs" />
     <Compile Include="Vms\RestartWindowsViewModel.cs" />
     <Compile Include="Vms\ServerMessageViewModel.cs" />
     <Compile Include="Vms\ServerMessagesViewModel.cs" />
@@ -132,23 +151,23 @@
     <Compile Include="Vms\KernelOutputSelectViewModel.cs" />
     <Compile Include="Vms\KernelInputSelectViewModel.cs" />
     <Compile Include="Vms\MinerProfileViewModel.cs" />
+    <Compile Include="Vms\SignUpPageViewModel.cs" />
     <Compile Include="Vms\SpeedTableViewModel.cs" />
     <Compile Include="Vms\StartStopMineButtonViewModel.cs" />
     <Compile Include="Vms\SysDicItemSelectViewModel.cs" />
     <Compile Include="Vms\CoinSelectViewModel.cs" />
-    <Compile Include="Vms\CoinSnapshotDataViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\CoinSnapshotDataViewModel.cs" />
     <Compile Include="Vms\CoinViewModel.cs" />
-    <Compile Include="Vms\ColumnsShowPageViewModel.cs" />
-    <Compile Include="Vms\ColumnsShowViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\ColumnsShowViewModel.cs" />
     <Compile Include="Vms\DriveViewModel.cs" />
     <Compile Include="Vms\EnvironmentVariableEditViewModel.cs" />
     <Compile Include="Vms\FileDownloaderViewModel.cs" />
     <Compile Include="Vms\FileWriterPageViewModel.cs" />
     <Compile Include="Vms\FileWriterViewModel.cs" />
-    <Compile Include="Vms\GpuProfilesPageViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\GpuProfilesPageViewModel.cs" />
     <Compile Include="Vms\GpuProfileViewModel.cs" />
-    <Compile Include="Vms\GpuSpeedDataViewModel.cs" />
-    <Compile Include="Vms\GpuSpeedDataViewModels.cs" />
+    <Compile Include="MinerStudio\Vms\GpuSpeedDataViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\GpuSpeedDataViewModels.cs" />
     <Compile Include="Vms\GpuSpeedViewModel.cs" />
     <Compile Include="Vms\GpuViewModel.cs" />
     <Compile Include="Vms\CoinGroupPageViewModel.cs" />
@@ -168,19 +187,19 @@
     <Compile Include="Vms\KernelViewModel.cs" />
     <Compile Include="Vms\MainWindowViewModel.cs" />
     <Compile Include="Vms\MeasureModel.cs" />
-    <Compile Include="Vms\MinerClientAddViewModel.cs" />
-    <Compile Include="Vms\MinerClientSettingViewModel.cs" />
-    <Compile Include="Vms\MinerClientsWindowViewModel.cs" />
-    <Compile Include="Vms\MinerClientViewModel.cs" />
-    <Compile Include="Vms\MinerGroupViewModel.cs" />
-    <Compile Include="Vms\MinerNamesSeterViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\MinerClientAddViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\MinerClientSettingViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\MinerClientsWindowViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\MinerClientViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\MinerGroupViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\MinerNamesSeterViewModel.cs" />
     <Compile Include="Vms\MinerProfileIndexViewModel.cs" />
-    <Compile Include="Vms\MineWorkViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\MineWorkViewModel.cs" />
     <Compile Include="Vms\MinuteItem.cs" />
-    <Compile Include="Vms\NTMinerUpdaterConfigViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\NTMinerUpdaterConfigViewModel.cs" />
     <Compile Include="Vms\OuterPropertyViewModel.cs" />
-    <Compile Include="Vms\OverClockDataPageViewModel.cs" />
-    <Compile Include="Vms\OverClockDataViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\OverClockDataPageViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\OverClockDataViewModel.cs" />
     <Compile Include="Vms\PackagesWindowViewModel.cs" />
     <Compile Include="Vms\PackageViewModel.cs" />
     <Compile Include="Vms\PoolKernelViewModel.cs" />
@@ -198,15 +217,19 @@
     <Compile Include="Vms\SysDicPageViewModel.cs" />
     <Compile Include="Vms\SysDicViewModel.cs" />
     <Compile Include="Vms\ToolboxViewModel.cs" />
-    <Compile Include="Vms\UserPageViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\UserPageViewModel.cs" />
     <Compile Include="Vms\UserViewModel.cs" />
-    <Compile Include="Vms\VirtualMemorySetViewModel.cs" />
-    <Compile Include="Vms\VirtualMemoryViewModel.cs" />
     <Compile Include="Vms\WalletSelectViewModel.cs" />
     <Compile Include="Vms\WalletViewModel.cs" />
     <Compile Include="Vms\LocalMessageViewModel.cs" />
+    <Compile Include="MinerStudio\Vms\WsServerNodePageViewModel.cs" />
+    <Compile Include="Vms\WsServerNodeViewModel.cs" />
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\NTMiner.Controllers\NTMiner.Controllers.csproj">
+      <Project>{5911457a-357a-412d-a20c-e77c1a56b4f6}</Project>
+      <Name>NTMiner.Controllers</Name>
+    </ProjectReference>
     <ProjectReference Include="..\NTMinerClient\NTMinerClient.csproj">
       <Project>{85d052ab-44b8-46f3-9d7b-f624c24fd8ba}</Project>
       <Name>NTMinerClient</Name>
@@ -238,6 +261,8 @@
   </ItemGroup>
   <ItemGroup>
     <None Include="app.config" />
+    <Compile Include="MinerStudio\MinerStudioRoot.partials.MinerClientMessagesViewModel.cs" />
+    <Compile Include="MinerStudio\MinerStudioRoot.partials.MinerClientOperationResultsViewModel.cs" />
     <None Include="packages.config" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

+ 450 - 0
src/AppModels/AppRoot.cs

@@ -0,0 +1,450 @@
+using NTMiner.Core;
+using NTMiner.Hub;
+using NTMiner.RemoteDesktop;
+using NTMiner.Vms;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Windows;
+
+namespace NTMiner {
+    /// <summary>
+    /// 该类型不是用于绑定到视图的,AppStatic才是
+    /// </summary>
+    public static partial class AppRoot {
+        public static Action<RdpInput> RemoteDesktop;
+        public static ExtendedNotifyIcon NotifyIcon;
+        public const string LowWinMessage = "Windows版本较低,建议使用Win10系统";
+
+        private static readonly List<IMessagePathId> _contextPathIds = new List<IMessagePathId>();
+
+        static AppRoot() {
+        }
+
+        #region methods
+        // 因为是上下文路径,无需返回路径标识
+        public static void AddCmdPath<TCmd>(string description, LogEnum logType, Action<TCmd> action, Type location)
+            where TCmd : ICmd {
+            var messagePathId = VirtualRoot.AddMessagePath(description, logType, action, location);
+            _contextPathIds.Add(messagePathId);
+        }
+
+        // 因为是上下文路径,无需返回路径标识
+        public static void AddEventPath<TEvent>(string description, LogEnum logType, Action<TEvent> action, Type location)
+            where TEvent : IEvent {
+            var messagePathId = VirtualRoot.AddMessagePath(description, logType, action, location);
+            _contextPathIds.Add(messagePathId);
+        }
+
+        public static void Enable() {
+            foreach (var pathId in _contextPathIds) {
+                pathId.IsEnabled = true;
+            }
+        }
+
+        public static void Disable() {
+            foreach (var pathId in _contextPathIds) {
+                pathId.IsEnabled = false;
+            }
+        }
+        #endregion
+
+        #region MainWindowHeight MainWindowWidth
+        public static double MainWindowHeight {
+            get {
+                if (SystemParameters.WorkArea.Size.Height >= 620) {
+                    return 620;
+                }
+                else if (SystemParameters.WorkArea.Size.Height >= 520) {
+                    return 520;
+                }
+                return 480;
+            }
+        }
+
+        public static double MainWindowWidth {
+            get {
+                if (SystemParameters.WorkArea.Size.Width >= 1090) {
+                    return 1090;
+                }
+                else if (SystemParameters.WorkArea.Size.Width >= 1000) {
+                    return 1000;
+                }
+                else if (SystemParameters.WorkArea.Size.Width >= 860) {
+                    return 860;
+                }
+                else if (SystemParameters.WorkArea.Size.Width >= 800) {
+                    return 800;
+                }
+                return 640;
+            }
+        }
+        #endregion
+
+        #region 字典项
+        public static Version MinAmdDriverVersion {
+            get {
+                if (WpfUtil.IsInDesignMode) {
+                    return new Version();
+                }
+                if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "MinAmdDriverVersion", out ISysDicItem dicItem)) {
+                    if (Version.TryParse(dicItem.Value, out Version version)) {
+                        return version;
+                    }
+                }
+                return new Version(17, 10, 2);
+            }
+        }
+
+        public static Version MinNvidiaDriverVersion {
+            get {
+                if (WpfUtil.IsInDesignMode) {
+                    return new Version();
+                }
+                if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "MinNvidiaDriverVersion", out ISysDicItem dicItem)) {
+                    if (Version.TryParse(dicItem.Value, out Version version)) {
+                        return version;
+                    }
+                }
+                return new Version(399, 24);
+            }
+        }
+
+        public static string NppPackageUrl {
+            get {
+                const string url = "https://minerjson.oss-cn-beijing.aliyuncs.com/npp.zip";
+                if (WpfUtil.IsDevMode) {
+                    return url;
+                }
+                if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem("Tool", "npp", out ISysDicItem dicItem)) {
+                    return dicItem.Value;
+                }
+                return url;
+            }
+        }
+        #endregion
+
+        #region Upgrade
+        private static string GetUpdaterVersion() {
+            string version = string.Empty;
+            if (VirtualRoot.LocalAppSettingSet.TryGetAppSetting(NTKeyword.UpdaterVersionAppSettingKey, out IAppSetting setting) && setting.Value != null) {
+                version = setting.Value.ToString();
+            }
+            return version;
+        }
+
+        private static void SetUpdaterVersion(string value) {
+            VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                Key = NTKeyword.UpdaterVersionAppSettingKey,
+                Value = value
+            }));
+        }
+
+        public static void Upgrade(NTMinerAppType appType, string fileName, Action callback) {
+            RpcRoot.OfficialServer.FileUrlService.GetNTMinerUpdaterUrlAsync((downloadFileUrl, e) => {
+                try {
+                    string argument = string.Empty;
+                    if (!string.IsNullOrEmpty(fileName)) {
+                        argument = "ntminerFileName=" + fileName;
+                    }
+                    if (appType == NTMinerAppType.MinerStudio) {
+                        argument += " --minerstudio";
+                    }
+                    if (string.IsNullOrEmpty(downloadFileUrl)) {
+                        if (File.Exists(HomePath.UpdaterFileFullName)) {
+                            Windows.Cmd.RunClose(HomePath.UpdaterFileFullName, argument);
+                        }
+                        callback?.Invoke();
+                        return;
+                    }
+                    Uri uri = new Uri(downloadFileUrl);
+                    string localVersion = GetUpdaterVersion();
+                    if (string.IsNullOrEmpty(localVersion) || !File.Exists(HomePath.UpdaterFileFullName) || uri.AbsolutePath != localVersion) {
+                        VirtualRoot.Execute(new ShowFileDownloaderCommand(downloadFileUrl, "开源矿工更新器", (window, isSuccess, message, saveFileFullName) => {
+                            try {
+                                if (isSuccess) {
+                                    string updateDirFullName = Path.GetDirectoryName(HomePath.UpdaterFileFullName);
+                                    if (!Directory.Exists(updateDirFullName)) {
+                                        Directory.CreateDirectory(updateDirFullName);
+                                    }
+                                    File.Delete(HomePath.UpdaterFileFullName);
+                                    File.Move(saveFileFullName, HomePath.UpdaterFileFullName);
+                                    SetUpdaterVersion(uri.AbsolutePath);
+                                    window?.Close();
+                                    Windows.Cmd.RunClose(HomePath.UpdaterFileFullName, argument);
+                                    callback?.Invoke();
+                                }
+                                else {
+                                    VirtualRoot.ThisLocalError(nameof(AppRoot), "下载新版本:" + message, toConsole: true);
+                                    callback?.Invoke();
+                                }
+                            }
+                            catch (Exception ex) {
+                                Logger.ErrorDebugLine(ex);
+                                callback?.Invoke();
+                            }
+                        }));
+                    }
+                    else {
+                        Windows.Cmd.RunClose(HomePath.UpdaterFileFullName, argument);
+                        callback?.Invoke();
+                    }
+                }
+                catch (Exception ex) {
+                    Logger.ErrorDebugLine(ex);
+                    callback?.Invoke();
+                }
+            });
+        }
+        #endregion
+
+        #region OpenMinerClientFinder
+        private static string GetMinerClientFinderVersion() {
+            string version = string.Empty;
+            if (VirtualRoot.LocalAppSettingSet.TryGetAppSetting(NTKeyword.MinerClientFinderVersionAppSettingKey, out IAppSetting setting) && setting.Value != null) {
+                version = setting.Value.ToString();
+            }
+            return version;
+        }
+
+        private static void SetMinerClientFinderVersion(string value) {
+            VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                Key = NTKeyword.MinerClientFinderVersionAppSettingKey,
+                Value = value
+            }));
+        }
+
+        public static void OpenMinerClientFinder() {
+            RpcRoot.OfficialServer.FileUrlService.GetMinerClientFinderUrlAsync((downloadFileUrl, e) => {
+                try {
+                    if (string.IsNullOrEmpty(downloadFileUrl)) {
+                        if (File.Exists(TempPath.MinerClientFinderFileFullName)) {
+                            Windows.Cmd.RunClose(TempPath.MinerClientFinderFileFullName, string.Empty);
+                        }
+                        return;
+                    }
+                    Uri uri = new Uri(downloadFileUrl);
+                    string localVersion = GetMinerClientFinderVersion();
+                    if (string.IsNullOrEmpty(localVersion) || !File.Exists(TempPath.MinerClientFinderFileFullName) || uri.AbsolutePath != localVersion) {
+                        VirtualRoot.Execute(new ShowFileDownloaderCommand(downloadFileUrl, "下载矿机雷达", (window, isSuccess, message, saveFileFullName) => {
+                            try {
+                                if (isSuccess) {
+                                    File.Delete(TempPath.MinerClientFinderFileFullName);
+                                    File.Move(saveFileFullName, TempPath.MinerClientFinderFileFullName);
+                                    SetMinerClientFinderVersion(uri.AbsolutePath);
+                                    window?.Close();
+                                    Windows.Cmd.RunClose(TempPath.MinerClientFinderFileFullName, string.Empty);
+                                }
+                                else {
+                                    VirtualRoot.ThisLocalError(nameof(AppRoot), "下载矿机雷达:" + message, toConsole: true);
+                                }
+                            }
+                            catch (Exception ex) {
+                                Logger.ErrorDebugLine(ex);
+                            }
+                        }));
+                    }
+                    else {
+                        Windows.Cmd.RunClose(TempPath.MinerClientFinderFileFullName, string.Empty);
+                    }
+                }
+                catch (Exception ex) {
+                    Logger.ErrorDebugLine(ex);
+                }
+            });
+        }
+        #endregion
+
+        #region OpenLiteDb
+        public static void OpenLiteDb(string dbFileFullName) {
+            string liteDbExplorerDir = Path.Combine(TempPath.ToolsDirFullName, "LiteDBExplorerPortable");
+            string liteDbExplorerFileFullName = Path.Combine(liteDbExplorerDir, "LiteDbExplorer.exe");
+            if (!Directory.Exists(liteDbExplorerDir)) {
+                Directory.CreateDirectory(liteDbExplorerDir);
+            }
+            if (!File.Exists(liteDbExplorerFileFullName)) {
+                RpcRoot.OfficialServer.FileUrlService.GetLiteDbExplorerUrlAsync((downloadFileUrl, e) => {
+                    if (string.IsNullOrEmpty(downloadFileUrl)) {
+                        return;
+                    }
+                    VirtualRoot.Execute(new ShowFileDownloaderCommand(downloadFileUrl, "LiteDB数据库管理工具", (window, isSuccess, message, saveFileFullName) => {
+                        if (isSuccess) {
+                            ZipUtil.DecompressZipFile(saveFileFullName, liteDbExplorerDir);
+                            File.Delete(saveFileFullName);
+                            window?.Close();
+                            Windows.Cmd.RunClose(liteDbExplorerFileFullName, dbFileFullName);
+                        }
+                    }));
+                });
+            }
+            else {
+                Windows.Cmd.RunClose(liteDbExplorerFileFullName, dbFileFullName);
+            }
+        }
+        #endregion
+
+        #region context
+        public static MinerProfileViewModel MinerProfileVm {
+            get {
+                return MinerProfileViewModel.Instance;
+            }
+        }
+
+        public static CoinViewModels CoinVms {
+            get {
+                return CoinViewModels.Instance;
+            }
+        }
+
+        public static GpuSpeedViewModels GpuSpeedVms {
+            get {
+                return GpuSpeedViewModels.Instance;
+            }
+        }
+
+        public static StartStopMineButtonViewModel StartStopMineButtonVm {
+            get {
+                return StartStopMineButtonViewModel.Instance;
+            }
+        }
+
+        public static PoolKernelViewModels PoolKernelVms {
+            get {
+                return PoolKernelViewModels.Instance;
+            }
+        }
+
+        public static PackageViewModels PackageVms {
+            get {
+                return PackageViewModels.Instance;
+            }
+        }
+
+        public static CoinGroupViewModels CoinGroupVms {
+            get {
+                return CoinGroupViewModels.Instance;
+            }
+        }
+
+        public static FileWriterViewModels FileWriterVms {
+            get {
+                return FileWriterViewModels.Instance;
+            }
+        }
+
+        public static FragmentWriterViewModels FragmentWriterVms {
+            get {
+                return FragmentWriterViewModels.Instance;
+            }
+        }
+
+        public static CoinKernelViewModels CoinKernelVms {
+            get {
+                return CoinKernelViewModels.Instance;
+            }
+        }
+
+        public static CoinProfileViewModels CoinProfileVms {
+            get {
+                return CoinProfileViewModels.Instance;
+            }
+        }
+
+        public static DriveSetViewModel DriveSetVm {
+            get {
+                return DriveSetViewModel.Instance;
+            }
+        }
+
+        public static GpuProfileViewModels GpuProfileVms {
+            get {
+                return GpuProfileViewModels.Instance;
+            }
+        }
+
+        public static GpuViewModels GpuVms {
+            get {
+                return GpuViewModels.Instance;
+            }
+        }
+
+        public static GroupViewModels GroupVms {
+            get {
+                return GroupViewModels.Instance;
+            }
+        }
+
+        public static KernelInputViewModels KernelInputVms {
+            get {
+                return KernelInputViewModels.Instance;
+            }
+        }
+
+        public static KernelOutputKeywordViewModels KernelOutputKeywordVms {
+            get {
+                return KernelOutputKeywordViewModels.Instance;
+            }
+        }
+
+        public static KernelOutputTranslaterViewModels KernelOutputTranslaterVms {
+            get {
+                return KernelOutputTranslaterViewModels.Instance;
+            }
+        }
+
+        public static KernelOutputViewModels KernelOutputVms {
+            get {
+                return KernelOutputViewModels.Instance;
+            }
+        }
+
+        public static KernelViewModels KernelVms {
+            get {
+                return KernelViewModels.Instance;
+            }
+        }
+
+        public static PoolProfileViewModels PoolProfileVms {
+            get {
+                return PoolProfileViewModels.Instance;
+            }
+        }
+
+        public static PoolViewModels PoolVms {
+            get {
+                return PoolViewModels.Instance;
+            }
+        }
+
+        public static ShareViewModels ShareVms {
+            get {
+                return ShareViewModels.Instance;
+            }
+        }
+
+        public static WalletViewModels WalletVms {
+            get {
+                return WalletViewModels.Instance;
+            }
+        }
+
+        public static SysDicViewModels SysDicVms {
+            get {
+                return SysDicViewModels.Instance;
+            }
+        }
+
+        public static SysDicItemViewModels SysDicItemVms {
+            get {
+                return SysDicItemViewModels.Instance;
+            }
+        }
+
+        public static GpuStatusBarViewModel GpuStatusBarVm {
+            get {
+                return GpuStatusBarViewModel.Instance;
+            }
+        }
+        #endregion
+    }
+}

+ 7 - 5
src/AppModels/AppContext.partials.CoinGroupViewModels.cs → src/AppModels/AppRoot.partials.CoinGroupViewModels.cs

@@ -1,16 +1,18 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class CoinGroupViewModels : ViewModelBase {
             public static readonly CoinGroupViewModels Instance = new CoinGroupViewModels();
 
             private readonly Dictionary<Guid, CoinGroupViewModel> _dicById = new Dictionary<Guid, CoinGroupViewModel>();
             private readonly Dictionary<Guid, List<CoinGroupViewModel>> _listByGroupId = new Dictionary<Guid, List<CoinGroupViewModel>>();
             private CoinGroupViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -53,7 +55,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.CoinGroupSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.CoinGroupSet.AsEnumerable()) {
                     CoinGroupViewModel groupVm = new CoinGroupViewModel(item);
                     _dicById.Add(item.GetId(), groupVm);
                     if (!_listByGroupId.ContainsKey(item.GroupId)) {
@@ -64,7 +66,7 @@ namespace NTMiner {
             }
 
             private void OnGroupPropertyChanged(Guid groupId) {
-                if (AppContext.Instance.GroupVms.TryGetGroupVm(groupId, out GroupViewModel groupVm)) {
+                if (GroupVms.TryGetGroupVm(groupId, out GroupViewModel groupVm)) {
                     groupVm.OnPropertyChanged(nameof(groupVm.CoinVms));
                     groupVm.OnPropertyChanged(nameof(groupVm.DualCoinVms));
                     groupVm.OnPropertyChanged(nameof(groupVm.CoinGroupVms));

+ 21 - 26
src/AppModels/AppContext.partials.CoinKernelViewModels.cs → src/AppModels/AppRoot.partials.CoinKernelViewModels.cs

@@ -1,16 +1,18 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class CoinKernelViewModels : ViewModelBase {
             public static readonly CoinKernelViewModels Instance = new CoinKernelViewModels();
 
             private readonly Dictionary<Guid, CoinKernelViewModel> _dicById = new Dictionary<Guid, CoinKernelViewModel>();
             private CoinKernelViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -28,11 +30,6 @@ namespace NTMiner {
                         var coinKernelVm = new CoinKernelViewModel(message.Target);
                         _dicById.Add(message.Target.GetId(), coinKernelVm);
                         OnPropertyChanged(nameof(AllCoinKernels));
-                        if (AppContext.Instance.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
-                            coinVm.OnPropertyChanged(nameof(CoinViewModel.CoinKernel));
-                            coinVm.OnPropertyChanged(nameof(CoinViewModel.CoinKernels));
-                            coinVm.OnPropertyChanged(nameof(CoinViewModel.IsSupported));
-                        }
                         var kernelVm = coinKernelVm.Kernel;
                         if (kernelVm != null) {
                             kernelVm.OnPropertyChanged(nameof(kernelVm.CoinKernels));
@@ -40,23 +37,25 @@ namespace NTMiner {
                             kernelVm.OnPropertyChanged(nameof(kernelVm.SupportedCoinVms));
                             kernelVm.OnPropertyChanged(nameof(kernelVm.SupportedCoins));
                         }
+                        VirtualRoot.RaiseEvent(new CoinKernelVmAddedEvent(message));
                     }, location: this.GetType());
                 AddEventPath<CoinKernelUpdatedEvent>("更新了币种内核后刷新VM内存", LogEnum.DevConsole,
                     action: (message) => {
-                        CoinKernelViewModel entity = _dicById[message.Target.GetId()];
-                        var supportedGpu = entity.SupportedGpu;
-                        Guid dualCoinGroupId = entity.DualCoinGroupId;
-                        entity.Update(message.Target);
-                        if (supportedGpu != entity.SupportedGpu) {
-                            var coinKernels = AllCoinKernels.Where(a => a.KernelId == entity.Id);
-                            foreach (var coinKernel in coinKernels) {
-                                if (AppContext.Instance.CoinVms.TryGetCoinVm(coinKernel.CoinId, out CoinViewModel coinVm)) {
-                                    coinVm.OnPropertyChanged(nameof(coinVm.IsSupported));
-                                    coinVm.OnPropertyChanged(nameof(coinVm.CoinKernels));
+                        if (_dicById.TryGetValue(message.Target.GetId(), out CoinKernelViewModel vm)) {
+                            var supportedGpu = vm.SupportedGpu;
+                            Guid dualCoinGroupId = vm.DualCoinGroupId;
+                            vm.Update(message.Target);
+                            if (supportedGpu != vm.SupportedGpu) {
+                                var coinKernels = AllCoinKernels.Where(a => a.KernelId == vm.Id);
+                                foreach (var coinKernel in coinKernels) {
+                                    if (CoinVms.TryGetCoinVm(coinKernel.CoinId, out CoinViewModel coinVm)) {
+                                        coinVm.OnPropertyChanged(nameof(coinVm.IsSupported));
+                                        coinVm.OnPropertyChanged(nameof(coinVm.CoinKernels));
+                                    }
                                 }
+                                var kernelVm = vm.Kernel;
+                                kernelVm.OnPropertyChanged(nameof(kernelVm.CoinKernels));
                             }
-                            var kernelVm = entity.Kernel;
-                            kernelVm.OnPropertyChanged(nameof(kernelVm.CoinKernels));
                         }
                     }, location: this.GetType());
                 AddEventPath<CoinKernelRemovedEvent>("移除了币种内核后刷新VM内存", LogEnum.DevConsole,
@@ -64,16 +63,12 @@ namespace NTMiner {
                         if (_dicById.TryGetValue(message.Target.GetId(), out CoinKernelViewModel coinKernelVm)) {
                             _dicById.Remove(message.Target.GetId());
                             OnPropertyChanged(nameof(AllCoinKernels));
-                            if (AppContext.Instance.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
-                                coinVm.OnPropertyChanged(nameof(CoinViewModel.CoinKernel));
-                                coinVm.OnPropertyChanged(nameof(CoinViewModel.CoinKernels));
-                                coinVm.OnPropertyChanged(nameof(CoinViewModel.IsSupported));
-                            }
                             var kernelVm = coinKernelVm.Kernel;
                             kernelVm.OnPropertyChanged(nameof(kernelVm.CoinKernels));
                             kernelVm.OnPropertyChanged(nameof(kernelVm.CoinVms));
                             kernelVm.OnPropertyChanged(nameof(kernelVm.SupportedCoinVms));
                             kernelVm.OnPropertyChanged(nameof(kernelVm.SupportedCoins));
+                            VirtualRoot.RaiseEvent(new CoinKernelVmRemovedEvent(message));
                         }
                     }, location: this.GetType());
                 Init();
@@ -86,7 +81,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.CoinKernelSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.CoinKernelSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new CoinKernelViewModel(item));
                 }
             }

+ 10 - 9
src/AppModels/AppContext.partials.CoinProfileViewModels.cs → src/AppModels/AppRoot.partials.CoinProfileViewModels.cs

@@ -1,17 +1,20 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class CoinProfileViewModels : ViewModelBase {
+            private readonly object _locker = new object();
             public static readonly CoinProfileViewModels Instance = new CoinProfileViewModels();
 
             private readonly Dictionary<Guid, CoinKernelProfileViewModel> _coinKernelProfileDicById = new Dictionary<Guid, CoinKernelProfileViewModel>();
             private readonly Dictionary<Guid, CoinProfileViewModel> _coinProfileDicById = new Dictionary<Guid, CoinProfileViewModel>();
 
             private CoinProfileViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -40,13 +43,12 @@ namespace NTMiner {
 #endif
             }
 
-            private readonly object _coinProfileDicLocker = new object();
             public CoinProfileViewModel GetOrCreateCoinProfile(Guid coinId) {
                 CoinProfileViewModel coinProfile;
                 if (!_coinProfileDicById.TryGetValue(coinId, out coinProfile)) {
-                    lock (_coinProfileDicLocker) {
+                    lock (_locker) {
                         if (!_coinProfileDicById.TryGetValue(coinId, out coinProfile)) {
-                            coinProfile = new CoinProfileViewModel(NTMinerRoot.Instance.MinerProfile.GetCoinProfile(coinId));
+                            coinProfile = new CoinProfileViewModel(NTMinerContext.Instance.MinerProfile.GetCoinProfile(coinId));
                             _coinProfileDicById.Add(coinId, coinProfile);
                         }
                     }
@@ -54,13 +56,12 @@ namespace NTMiner {
                 return coinProfile;
             }
 
-            private readonly object _coinKernelProfileLocker = new object();
             public CoinKernelProfileViewModel GetOrCreateCoinKernelProfileVm(Guid coinKernelId) {
                 CoinKernelProfileViewModel coinKernelProfileVm;
                 if (!_coinKernelProfileDicById.TryGetValue(coinKernelId, out coinKernelProfileVm)) {
-                    lock (_coinKernelProfileLocker) {
+                    lock (_locker) {
                         if (!_coinKernelProfileDicById.TryGetValue(coinKernelId, out coinKernelProfileVm)) {
-                            coinKernelProfileVm = new CoinKernelProfileViewModel(NTMinerRoot.Instance.MinerProfile.GetCoinKernelProfile(coinKernelId));
+                            coinKernelProfileVm = new CoinKernelProfileViewModel(NTMinerContext.Instance.MinerProfile.GetCoinKernelProfile(coinKernelId));
                             _coinKernelProfileDicById.Add(coinKernelId, coinKernelProfileVm);
                         }
                     }

+ 39 - 24
src/AppModels/AppContext.partials.CoinViewModels.cs → src/AppModels/AppRoot.partials.CoinViewModels.cs

@@ -6,7 +6,7 @@ using System.IO;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class CoinViewModels : ViewModelBase {
             public static readonly CoinViewModels Instance = new CoinViewModels();
 
@@ -31,34 +31,49 @@ namespace NTMiner {
                 AddEventPath<CoinAddedEvent>("添加了币种后刷新VM内存", LogEnum.DevConsole,
                     action: (message) => {
                         _dicById.Add(message.Target.GetId(), new CoinViewModel(message.Target));
-                        AppContext.Instance.MinerProfileVm.OnPropertyChanged(nameof(NTMiner.AppContext.Instance.MinerProfileVm.CoinVm));
                         AllPropertyChanged();
+                        VirtualRoot.RaiseEvent(new CoinVmAddedEvent(message));
                     }, location: this.GetType());
                 AddEventPath<CoinRemovedEvent>("移除了币种后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
                         _dicById.Remove(message.Target.GetId());
-                        AppContext.Instance.MinerProfileVm.OnPropertyChanged(nameof(NTMiner.AppContext.Instance.MinerProfileVm.CoinVm));
                         AllPropertyChanged();
+                        VirtualRoot.RaiseEvent(new CoinVmRemovedEvent(message));
                     }, location: this.GetType());
+                AddEventPath<CoinKernelVmAddedEvent>("币种内核Vm集添加了新Vm后刷新币种Vm集的关联内存", LogEnum.DevConsole, action: message => {
+                    if (_dicById.TryGetValue(message.Event.Target.CoinId, out CoinViewModel coinVm)) {
+                        coinVm.OnPropertyChanged(nameof(CoinViewModel.CoinKernel));
+                        coinVm.OnPropertyChanged(nameof(CoinViewModel.CoinKernels));
+                        coinVm.OnPropertyChanged(nameof(CoinViewModel.IsSupported));
+                    }
+                }, this.GetType());
+                AddEventPath<CoinKernelVmRemovedEvent>("币种内核Vm集删除了新Vm后刷新币种Vm集的关联内存", LogEnum.DevConsole, action: message => {
+                    if (_dicById.TryGetValue(message.Event.Target.CoinId, out CoinViewModel coinVm)) {
+                        coinVm.OnPropertyChanged(nameof(CoinViewModel.CoinKernel));
+                        coinVm.OnPropertyChanged(nameof(CoinViewModel.CoinKernels));
+                        coinVm.OnPropertyChanged(nameof(CoinViewModel.IsSupported));
+                    }
+                }, this.GetType());
                 AddEventPath<CoinUpdatedEvent>("更新了币种后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
-                        CoinViewModel coinVm = _dicById[message.Target.GetId()];
-                        bool justAsDualCoin = coinVm.JustAsDualCoin;
-                        coinVm.Update(message.Target);
-                        coinVm.TestWalletVm.Address = message.Target.TestWallet;
-                        coinVm.OnPropertyChanged(nameof(coinVm.Wallets));
-                        coinVm.OnPropertyChanged(nameof(coinVm.WalletItems));
-                        if (AppContext.Instance.MinerProfileVm.CoinId == message.Target.GetId()) {
-                            AppContext.Instance.MinerProfileVm.OnPropertyChanged(nameof(NTMiner.AppContext.Instance.MinerProfileVm.CoinVm));
-                        }
-                        CoinKernelViewModel coinKernelVm = AppContext.Instance.MinerProfileVm.CoinVm.CoinKernel;
-                        if (coinKernelVm != null
-                            && coinKernelVm.CoinKernelProfile.SelectedDualCoin != null
-                            && coinKernelVm.CoinKernelProfile.SelectedDualCoin.GetId() == message.Target.GetId()) {
-                            coinKernelVm.CoinKernelProfile.OnPropertyChanged(nameof(coinKernelVm.CoinKernelProfile.SelectedDualCoin));
-                        }
-                        if (justAsDualCoin != coinVm.JustAsDualCoin) {
-                            OnPropertyChanged(nameof(MainCoins));
+                        if (_dicById.TryGetValue(message.Target.GetId(), out CoinViewModel vm)) {
+                            bool justAsDualCoin = vm.JustAsDualCoin;
+                            vm.Update(message.Target);
+                            vm.TestWalletVm.Address = message.Target.TestWallet;
+                            vm.OnPropertyChanged(nameof(vm.Wallets));
+                            vm.OnPropertyChanged(nameof(vm.WalletItems));
+                            if (MinerProfileVm.CoinId == message.Target.GetId()) {
+                                MinerProfileVm.OnPropertyChanged(nameof(MinerProfileVm.CoinVm));
+                            }
+                            CoinKernelViewModel coinKernelVm = MinerProfileVm.CoinVm.CoinKernel;
+                            if (coinKernelVm != null
+                                && coinKernelVm.CoinKernelProfile.SelectedDualCoin != null
+                                && coinKernelVm.CoinKernelProfile.SelectedDualCoin.GetId() == message.Target.GetId()) {
+                                coinKernelVm.CoinKernelProfile.OnPropertyChanged(nameof(coinKernelVm.CoinKernelProfile.SelectedDualCoin));
+                            }
+                            if (justAsDualCoin != vm.JustAsDualCoin) {
+                                OnPropertyChanged(nameof(MainCoins));
+                            }
                         }
                     }, location: this.GetType());
                 AddEventPath<CoinIconDownloadedEvent>("下载了币种图标后", LogEnum.DevConsole,
@@ -67,7 +82,7 @@ namespace NTMiner {
                             if (string.IsNullOrEmpty(message.Target.Icon)) {
                                 return;
                             }
-                            string iconFileFullName = SpecialPath.GetIconFileFullName(message.Target);
+                            string iconFileFullName = TempPath.GetIconFileFullName(message.Target.Icon);
                             if (string.IsNullOrEmpty(iconFileFullName) || !File.Exists(iconFileFullName)) {
                                 return;
                             }
@@ -95,7 +110,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.CoinSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.CoinSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new CoinViewModel(item));
                 }
                 foreach (var coinVm in _dicById.Values) {
@@ -108,7 +123,7 @@ namespace NTMiner {
             }
 
             public bool TryGetCoinVm(string coinCode, out CoinViewModel coinVm) {
-                if (NTMinerRoot.Instance.ServerContext.CoinSet.TryGetCoin(coinCode, out ICoin coin)) {
+                if (NTMinerContext.Instance.ServerContext.CoinSet.TryGetCoin(coinCode, out ICoin coin)) {
                     return TryGetCoinVm(coin.GetId(), out coinVm);
                 }
                 coinVm = CoinViewModel.Empty;
@@ -154,7 +169,7 @@ namespace NTMiner {
             private IEnumerable<CoinViewModel> GetDualPleaseSelect() {
                 yield return CoinViewModel.PleaseSelect;
                 yield return CoinViewModel.DualCoinEnabled;
-                foreach (var group in AppContext.Instance.GroupVms.List) {
+                foreach (var group in GroupVms.List) {
                     foreach (var item in group.DualCoinVms) {
                         yield return item;
                     }

+ 21 - 7
src/AppModels/AppContext.partials.DriveSetViewModel.cs → src/AppModels/AppRoot.partials.DriveSetViewModel.cs

@@ -1,11 +1,10 @@
 using NTMiner.Vms;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 using System.Windows.Input;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class DriveSetViewModel : ViewModelBase {
             public static readonly DriveSetViewModel Instance = new DriveSetViewModel();
 
@@ -14,14 +13,19 @@ namespace NTMiner {
             public ICommand Apply { get; private set; }
 
             private DriveSetViewModel() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
-                foreach (var item in DriveInfo.GetDrives().Where(a => a.DriveType == DriveType.Fixed)) {
-                    _drives.Add(new DriveViewModel(item));
+                foreach (var drive in VirtualRoot.DriveSet.AsEnumerable()) {
+                    _drives.Add(new DriveViewModel(drive));
                 }
                 this.Apply = new DelegateCommand(() => {
-                    AppContext.Instance.VirtualMemorySetVm.SetVirtualMemoryOfDrive();
+                    VirtualRoot.DriveSet.SetVirtualMemory(_drives.ToDictionary(a => a.Name, a => a.VirtualMemoryMaxSizeMb));
+                    OnPropertyChanged(nameof(TotalVirtualMemoryMb));
+                    OnPropertyChanged(nameof(IsStateChanged));
                 });
 #if DEBUG
                 var elapsedMilliseconds = NTStopwatch.Stop();
@@ -37,9 +41,19 @@ namespace NTMiner {
                 }
             }
 
-            public VirtualMemorySetViewModel VirtualMemorySet {
+            public bool IsStateChanged {
+                get {
+                    if (_drives.Any(a => a.VirtualMemoryMaxSizeMb != a.InitialVirtualMemoryMaxSizeMb)) {
+                        return true;
+                    }
+                    return false;
+                }
+            }
+
+
+            public int TotalVirtualMemoryMb {
                 get {
-                    return AppContext.Instance.VirtualMemorySetVm;
+                    return _drives.Sum(a => a.VirtualMemoryMaxSizeMb);
                 }
             }
         }

+ 8 - 7
src/AppModels/AppContext.partials.FileWriterViewModels.cs → src/AppModels/AppRoot.partials.FileWriterViewModels.cs

@@ -1,17 +1,19 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Windows.Input;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class FileWriterViewModels : ViewModelBase {
             public static readonly FileWriterViewModels Instance = new FileWriterViewModels();
             private readonly Dictionary<Guid, FileWriterViewModel> _dicById = new Dictionary<Guid, FileWriterViewModel>();
             public ICommand Add { get; private set; }
             private FileWriterViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -37,9 +39,8 @@ namespace NTMiner {
                     }, location: this.GetType());
                 AddEventPath<FileWriterUpdatedEvent>("更新了文件书写器后调整VM内存", LogEnum.DevConsole,
                     action: (message) => {
-                        if (_dicById.ContainsKey(message.Target.GetId())) {
-                            FileWriterViewModel entity = _dicById[message.Target.GetId()];
-                            entity.Update(message.Target);
+                        if (_dicById.TryGetValue(message.Target.GetId(), out FileWriterViewModel vm)) {
+                            vm.Update(message.Target);
                         }
                     }, location: this.GetType());
                 AddEventPath<FileWriterRemovedEvent>("删除了文件书写器后调整VM内存", LogEnum.DevConsole,
@@ -57,7 +58,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.FileWriterSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.FileWriterSet.AsEnumerable()) {
                     FileWriterViewModel groupVm = new FileWriterViewModel(item);
                     _dicById.Add(item.GetId(), groupVm);
                 }

+ 8 - 7
src/AppModels/AppContext.partials.FragmentWriterViewModels.cs → src/AppModels/AppRoot.partials.FragmentWriterViewModels.cs

@@ -1,17 +1,19 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Windows.Input;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class FragmentWriterViewModels : ViewModelBase {
             public static readonly FragmentWriterViewModels Instance = new FragmentWriterViewModels();
             private readonly Dictionary<Guid, FragmentWriterViewModel> _dicById = new Dictionary<Guid, FragmentWriterViewModel>();
             public ICommand Add { get; private set; }
             private FragmentWriterViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -37,9 +39,8 @@ namespace NTMiner {
                     }, location: this.GetType());
                 AddEventPath<FragmentWriterUpdatedEvent>("更新了命令行片段书写器后调整VM内存", LogEnum.DevConsole,
                     action: (message) => {
-                        if (_dicById.ContainsKey(message.Target.GetId())) {
-                            FragmentWriterViewModel entity = _dicById[message.Target.GetId()];
-                            entity.Update(message.Target);
+                        if (_dicById.TryGetValue(message.Target.GetId(), out FragmentWriterViewModel vm)) {
+                            vm.Update(message.Target);
                         }
                     }, location: this.GetType());
                 AddEventPath<FragmentWriterRemovedEvent>("删除了命令行片段书写器后调整VM内存", LogEnum.DevConsole,
@@ -57,7 +58,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.FragmentWriterSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.FragmentWriterSet.AsEnumerable()) {
                     FragmentWriterViewModel groupVm = new FragmentWriterViewModel(item);
                     _dicById.Add(item.GetId(), groupVm);
                 }

+ 13 - 11
src/AppModels/AppContext.partials.GpuProfileViewModels.cs → src/AppModels/AppRoot.partials.GpuProfileViewModels.cs

@@ -1,12 +1,11 @@
-using NTMiner.Core;
-using NTMiner.Core.Profile;
+using NTMiner.Core.Profile;
 using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class GpuProfileViewModels : ViewModelBase {
             public static readonly GpuProfileViewModels Instance = new GpuProfileViewModels();
 
@@ -14,6 +13,9 @@ namespace NTMiner {
             private readonly Dictionary<Guid, GpuProfileViewModel> _gpuAllVmDicByCoinId = new Dictionary<Guid, GpuProfileViewModel>();
 
             private GpuProfileViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -23,7 +25,7 @@ namespace NTMiner {
                             _listByCoinId.Clear();
                             _gpuAllVmDicByCoinId.Clear();
                         }
-                        var coinVm = AppContext.Instance.MinerProfileVm.CoinVm;
+                        var coinVm = MinerProfileVm.CoinVm;
                         if (coinVm != null) {
                             coinVm.OnOverClockPropertiesChanges();
                             VirtualRoot.Execute(new CoinOverClockCommand(coinVm.Id));
@@ -38,11 +40,11 @@ namespace NTMiner {
                                     vm.Update(message.Target);
                                 }
                                 else {
-                                    if (AppContext.Instance.GpuVms.TryGetGpuVm(message.Target.Index, out GpuViewModel gpuVm)) {
+                                    if (GpuVms.TryGetGpuVm(message.Target.Index, out GpuViewModel gpuVm)) {
                                         var item = new GpuProfileViewModel(message.Target, gpuVm);
                                         list.Add(item);
                                         list.Sort(new CompareByGpuIndex());
-                                        if (item.Index == NTMinerRoot.GpuAllId) {
+                                        if (item.Index == NTMinerContext.GpuAllId) {
                                             _gpuAllVmDicByCoinId.Add(message.Target.CoinId, item);
                                         }
                                     }
@@ -50,11 +52,11 @@ namespace NTMiner {
                             }
                             else {
                                 list = new List<GpuProfileViewModel>();
-                                if (AppContext.Instance.GpuVms.TryGetGpuVm(message.Target.Index, out GpuViewModel gpuVm)) {
+                                if (GpuVms.TryGetGpuVm(message.Target.Index, out GpuViewModel gpuVm)) {
                                     var item = new GpuProfileViewModel(message.Target, gpuVm);
                                     list.Add(item);
                                     list.Sort(new CompareByGpuIndex());
-                                    if (item.Index == NTMinerRoot.GpuAllId) {
+                                    if (item.Index == NTMinerContext.GpuAllId) {
                                         _gpuAllVmDicByCoinId.Add(message.Target.CoinId, item);
                                     }
                                 }
@@ -75,7 +77,7 @@ namespace NTMiner {
                 if (!_gpuAllVmDicByCoinId.TryGetValue(coinId, out GpuProfileViewModel result)) {
                     lock (_locker) {
                         if (!_gpuAllVmDicByCoinId.TryGetValue(coinId, out result)) {
-                            AppContext.Instance.GpuVms.TryGetGpuVm(NTMinerRoot.GpuAllId, out GpuViewModel gpuVm);
+                            GpuVms.TryGetGpuVm(NTMinerContext.GpuAllId, out GpuViewModel gpuVm);
                             result = GetGpuProfileVm(coinId, gpuVm);
                             _gpuAllVmDicByCoinId.Add(coinId, result);
                         }
@@ -89,7 +91,7 @@ namespace NTMiner {
                     lock (_locker) {
                         if (!_listByCoinId.TryGetValue(coinId, out list)) {
                             list = new List<GpuProfileViewModel>();
-                            foreach (var gpu in AppContext.Instance.GpuVms.Items) {
+                            foreach (var gpu in GpuVms.Items) {
                                 GpuProfileViewModel gpuProfileVm = GetGpuProfileVm(coinId, gpu);
                                 list.Add(gpuProfileVm);
                             }
@@ -102,7 +104,7 @@ namespace NTMiner {
             }
 
             private GpuProfileViewModel GetGpuProfileVm(Guid coinId, GpuViewModel gpuVm) {
-                IGpuProfile data = NTMinerRoot.Instance.GpuProfileSet.GetGpuProfile(coinId, gpuVm.Index);
+                IGpuProfile data = NTMinerContext.Instance.GpuProfileSet.GetGpuProfile(coinId, gpuVm.Index);
                 return new GpuProfileViewModel(data, gpuVm);
             }
 

+ 14 - 13
src/AppModels/AppContext.partials.GpuSpeedViewModels.cs → src/AppModels/AppRoot.partials.GpuSpeedViewModels.cs

@@ -1,12 +1,13 @@
 using NTMiner.Core;
 using NTMiner.Core.Gpus;
+using NTMiner.Mine;
 using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class GpuSpeedViewModels : ViewModelBase {
             public static readonly GpuSpeedViewModels Instance = new GpuSpeedViewModels();
 
@@ -22,7 +23,7 @@ namespace NTMiner {
             private double _incomeDualCoinCnyPerDay;
 
             private void ResetIfMainCoinSwitched() {
-                Guid mainCoinId = NTMinerRoot.Instance.MinerProfile.CoinId;
+                Guid mainCoinId = NTMinerContext.Instance.MinerProfile.CoinId;
                 if (_mainCoinId != mainCoinId) {
                     _mainCoinId = mainCoinId;
                     foreach (var item in _list) {
@@ -45,12 +46,12 @@ namespace NTMiner {
                 if (WpfUtil.IsInDesignMode) {
                     return;
                 }
-                this.GpuAllVm = AppContext.Instance.GpuVms.Items.FirstOrDefault(a => a.Index == NTMinerRoot.GpuAllId);
-                IGpusSpeed gpuSpeeds = NTMinerRoot.Instance.GpusSpeed;
+                this.GpuAllVm = GpuVms.Items.FirstOrDefault(a => a.Index == NTMinerContext.GpuAllId);
+                IGpusSpeed gpuSpeeds = NTMinerContext.Instance.GpusSpeed;
                 foreach (var item in gpuSpeeds.AsEnumerable()) {
                     this._list.Add(new GpuSpeedViewModel(item));
                 }
-                _totalSpeedVm = this._list.FirstOrDefault(a => a.GpuVm.Index == NTMinerRoot.GpuAllId);
+                _totalSpeedVm = this._list.FirstOrDefault(a => a.GpuVm.Index == NTMinerContext.GpuAllId);
                 AddEventPath<GpuShareChangedEvent>("显卡份额变更后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
                         ResetIfMainCoinSwitched();
@@ -114,7 +115,7 @@ namespace NTMiner {
                             }
                         }
                         if (index == _totalSpeedVm.GpuVm.Index) {
-                            IMineContext mineContext = NTMinerRoot.Instance.LockedMineContext;
+                            var mineContext = NTMinerContext.Instance.LockedMineContext;
                             if (mineContext == null) {
                                 IncomeMainCoinPerDay = 0;
                                 IncomeMainCoinUsdPerDay = 0;
@@ -126,14 +127,14 @@ namespace NTMiner {
                             else {
                                 if (message.IsDual) {
                                     if (mineContext is IDualMineContext dualMineContext) {
-                                        IncomePerDay incomePerDay = NTMinerRoot.Instance.CalcConfigSet.GetIncomePerHashPerDay(dualMineContext.DualCoin.Code);
+                                        IncomePerDay incomePerDay = NTMinerContext.Instance.CalcConfigSet.GetIncomePerHashPerDay(dualMineContext.DualCoin.Code);
                                         IncomeDualCoinPerDay = _totalSpeedVm.DualCoinSpeed.Value * incomePerDay.IncomeCoin;
                                         IncomeDualCoinUsdPerDay = _totalSpeedVm.DualCoinSpeed.Value * incomePerDay.IncomeUsd;
                                         IncomeDualCoinCnyPerDay = _totalSpeedVm.DualCoinSpeed.Value * incomePerDay.IncomeCny;
                                     }
                                 }
                                 else {
-                                    IncomePerDay incomePerDay = NTMinerRoot.Instance.CalcConfigSet.GetIncomePerHashPerDay(mineContext.MainCoin.Code);
+                                    IncomePerDay incomePerDay = NTMinerContext.Instance.CalcConfigSet.GetIncomePerHashPerDay(mineContext.MainCoin.Code);
                                     IncomeMainCoinPerDay = _totalSpeedVm.MainCoinSpeed.Value * incomePerDay.IncomeCoin;
                                     IncomeMainCoinUsdPerDay = _totalSpeedVm.MainCoinSpeed.Value * incomePerDay.IncomeUsd;
                                     IncomeMainCoinCnyPerDay = _totalSpeedVm.MainCoinSpeed.Value * incomePerDay.IncomeCny;
@@ -156,9 +157,9 @@ namespace NTMiner {
 
             public void Refresh() {
                 foreach (var item in this._list) {
-                    var data = NTMinerRoot.Instance.GpusSpeed.AsEnumerable().FirstOrDefault(a => a.Gpu.Index == item.GpuVm.Index);
+                    var data = NTMinerContext.Instance.GpusSpeed.AsEnumerable().FirstOrDefault(a => a.Gpu.Index == item.GpuVm.Index);
                     if (data != null) {
-                        if (item.GpuVm.Index == NTMinerRoot.GpuAllId) {
+                        if (item.GpuVm.Index == NTMinerContext.GpuAllId) {
                             TotalSpeedVm.MainCoinSpeed.UpdateSpeed(data.MainCoinSpeed.Value, data.MainCoinSpeed.SpeedOn);
                             TotalSpeedVm.DualCoinSpeed.UpdateSpeed(data.DualCoinSpeed.Value, data.DualCoinSpeed.SpeedOn);
                         }
@@ -172,7 +173,7 @@ namespace NTMiner {
 
             public MinerProfileViewModel MinerProfile {
                 get {
-                    return AppContext.Instance.MinerProfileVm;
+                    return MinerProfileVm;
                 }
             }
 
@@ -187,7 +188,7 @@ namespace NTMiner {
 
             public string ProfitCnyPerDayText {
                 get {
-                    return (this.IncomeMainCoinCnyPerDay + this.IncomeDualCoinCnyPerDay - AppContext.Instance.GpuVms.GpuAllVm.ECharge).ToString("f2");
+                    return (this.IncomeMainCoinCnyPerDay + this.IncomeDualCoinCnyPerDay - GpuVms.GpuAllVm.ECharge).ToString("f2");
                 }
             }
 
@@ -304,7 +305,7 @@ namespace NTMiner {
             /// </summary>
             public List<GpuSpeedViewModel> List {
                 get {
-                    return _list.Where(a => a.GpuVm.Index != NTMinerRoot.GpuAllId).ToList();
+                    return _list.Where(a => a.GpuVm.Index != NTMinerContext.GpuAllId).ToList();
                 }
             }
 

+ 10 - 11
src/AppModels/AppContext.partials.GpuViewModels.cs → src/AppModels/AppRoot.partials.GpuViewModels.cs

@@ -1,10 +1,9 @@
-using NTMiner.Core;
-using NTMiner.Core.Gpus.Impl;
+using NTMiner.Core.Gpus.Impl;
 using NTMiner.Vms;
 using System.Collections.Generic;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class GpuViewModels : ViewModelBase {
             public static readonly GpuViewModels Instance = new GpuViewModels();
 
@@ -22,11 +21,11 @@ namespace NTMiner {
                 if (WpfUtil.IsInDesignMode) {
                     return;
                 }
-                foreach (var gpu in NTMinerRoot.Instance.GpuSet.AsEnumerable()) {
+                foreach (var gpu in NTMinerContext.Instance.GpuSet.AsEnumerable()) {
                     _gpuVms.Add(gpu.Index, new GpuViewModel(gpu));
                 }
-                if (_gpuVms.ContainsKey(NTMinerRoot.GpuAllId)) {
-                    _gpuAllVm = _gpuVms[NTMinerRoot.GpuAllId];
+                if (_gpuVms.ContainsKey(NTMinerContext.GpuAllId)) {
+                    _gpuAllVm = _gpuVms[NTMinerContext.GpuAllId];
                 }
                 else {
                     _gpuAllVm = new GpuViewModel(Gpu.GpuAll);
@@ -36,7 +35,7 @@ namespace NTMiner {
                         foreach (var gpuVm in _gpuVms.Values) {
                             gpuVm.OnPropertyChanged(nameof(GpuViewModel.EChargeText));
                         }
-                        AppContext.Instance.GpuSpeedVms.OnPropertyChanged(nameof(GpuSpeedViewModels.ProfitCnyPerDayText));
+                        GpuSpeedVms.OnPropertyChanged(nameof(GpuSpeedViewModels.ProfitCnyPerDayText));
                     }, location: this.GetType());
                 AddEventPath<MaxTempChangedEvent>("高温红色阈值变更后更新显卡温度颜色", LogEnum.DevConsole,
                     action: message => {
@@ -49,7 +48,7 @@ namespace NTMiner {
                         foreach (var gpuVm in _gpuVms.Values) {
                             gpuVm.OnPropertyChanged(nameof(GpuViewModel.PowerUsageWText));
                         }
-                        AppContext.Instance.GpuSpeedVms.OnPropertyChanged(nameof(GpuSpeedViewModels.ProfitCnyPerDayText));
+                        GpuSpeedVms.OnPropertyChanged(nameof(GpuSpeedViewModels.ProfitCnyPerDayText));
                     }, location: this.GetType());
                 AddEventPath<GpuStateChangedEvent>("显卡状态变更后刷新VM内存", LogEnum.None,
                     action: message => {
@@ -91,7 +90,7 @@ namespace NTMiner {
                                 _gpuAllVm.OnPropertyChanged(nameof(_gpuAllVm.PowerMinMaxText));
                                 _gpuAllVm.OnPropertyChanged(nameof(_gpuAllVm.TempLimitMinMaxText));
                                 _gpuAllVm.OnPropertyChanged(nameof(_gpuAllVm.EChargeText));
-                                AppContext.Instance.GpuSpeedVms.OnPropertyChanged(nameof(GpuSpeedViewModels.ProfitCnyPerDayText));
+                                GpuSpeedVms.OnPropertyChanged(nameof(GpuSpeedViewModels.ProfitCnyPerDayText));
                             }
                             UpdateMinMax();
                         }
@@ -107,7 +106,7 @@ namespace NTMiner {
             private void UpdateMinMax() {
                 uint minFan = uint.MaxValue, maxFan = uint.MinValue;
                 foreach (var item in _gpuVms.Values) {
-                    if (item.Index == NTMinerRoot.GpuAllId) {
+                    if (item.Index == NTMinerContext.GpuAllId) {
                         continue;
                     }
                     if (item.FanSpeed > maxFan) {
@@ -121,7 +120,7 @@ namespace NTMiner {
                 this.FanSpeedMinText = minFan + " %";
                 int minTemp = int.MaxValue, maxTemp = int.MinValue;
                 foreach (var item in _gpuVms.Values) {
-                    if (item.Index == NTMinerRoot.GpuAllId) {
+                    if (item.Index == NTMinerContext.GpuAllId) {
                         continue;
                     }
                     if (item.Temperature > maxTemp) {

+ 10 - 9
src/AppModels/AppContext.partials.GroupViewModels.cs → src/AppModels/AppRoot.partials.GroupViewModels.cs

@@ -1,17 +1,19 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Windows.Input;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class GroupViewModels : ViewModelBase {
             public static readonly GroupViewModels Instance = new GroupViewModels();
             private readonly Dictionary<Guid, GroupViewModel> _dicById = new Dictionary<Guid, GroupViewModel>();
             public ICommand Add { get; private set; }
             private GroupViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -39,11 +41,10 @@ namespace NTMiner {
                     }, location: this.GetType());
                 AddEventPath<GroupUpdatedEvent>("更新了组后调整VM内存", LogEnum.DevConsole,
                     action: (message) => {
-                        if (_dicById.ContainsKey(message.Target.GetId())) {
-                            GroupViewModel entity = _dicById[message.Target.GetId()];
-                            int sortNumber = entity.SortNumber;
-                            entity.Update(message.Target);
-                            if (sortNumber != entity.SortNumber) {
+                        if (_dicById.TryGetValue(message.Target.GetId(), out GroupViewModel vm)) {
+                            int sortNumber = vm.SortNumber;
+                            vm.Update(message.Target);
+                            if (sortNumber != vm.SortNumber) {
                                 this.OnPropertyChanged(nameof(List));
                                 OnPropertyChanged(nameof(SelectionOptions));
                             }
@@ -64,7 +65,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.GroupSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.GroupSet.AsEnumerable()) {
                     GroupViewModel groupVm = new GroupViewModel(item);
                     _dicById.Add(item.GetId(), groupVm);
                 }

+ 17 - 16
src/AppModels/AppContext.partials.KernelInputViewModels.cs → src/AppModels/AppRoot.partials.KernelInputViewModels.cs

@@ -1,16 +1,18 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class KernelInputViewModels : ViewModelBase {
             public static readonly KernelInputViewModels Instance = new KernelInputViewModels();
             private readonly Dictionary<Guid, KernelInputViewModel> _dicById = new Dictionary<Guid, KernelInputViewModel>();
 
             private KernelInputViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -31,20 +33,19 @@ namespace NTMiner {
                     }, location: this.GetType());
                 AddEventPath<KernelInputUpdatedEvent>("更新了内核输入后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
-                        if (_dicById.ContainsKey(message.Target.GetId())) {
-                            var item = _dicById[message.Target.GetId()];
-                            if (item != null) {
-                                bool isSupportDualMine = item.IsSupportDualMine;
-                                string args = item.Args;
-                                item.Update(message.Target);
-                                if (args != item.Args) {
-                                    CoinViewModel coinVm = AppContext.Instance.MinerProfileVm.CoinVm;
-                                    if (coinVm != null && coinVm.CoinKernel != null && coinVm.CoinKernel.Kernel.KernelInputId == item.Id) {
-                                        NTMinerRoot.RefreshArgsAssembly.Invoke();
+                        if (_dicById.TryGetValue(message.Target.GetId(), out KernelInputViewModel vm)) {
+                            if (vm != null) {
+                                bool isSupportDualMine = vm.IsSupportDualMine;
+                                string args = vm.Args;
+                                vm.Update(message.Target);
+                                if (args != vm.Args) {
+                                    CoinViewModel coinVm = MinerProfileVm.CoinVm;
+                                    if (coinVm != null && coinVm.CoinKernel != null && coinVm.CoinKernel.Kernel.KernelInputId == vm.Id) {
+                                        NTMinerContext.RefreshArgsAssembly.Invoke("当前选用的内核引用的内核输入的形参发生了变更");
                                     }
                                 }
-                                if (isSupportDualMine != item.IsSupportDualMine) {
-                                    foreach (var coinKernelVm in AppContext.Instance.CoinKernelVms.AllCoinKernels.Where(a => a.KernelId == message.Target.GetId())) {
+                                if (isSupportDualMine != vm.IsSupportDualMine) {
+                                    foreach (var coinKernelVm in CoinKernelVms.AllCoinKernels.Where(a => a.KernelId == message.Target.GetId())) {
                                         coinKernelVm.OnPropertyChanged(nameof(coinKernelVm.IsSupportDualMine));
                                     }
                                 }
@@ -68,7 +69,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.KernelInputSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.KernelInputSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new KernelInputViewModel(item));
                 }
             }

+ 9 - 6
src/AppModels/AppContext.partials.KernelOutputKeywordViewModels.cs → src/AppModels/AppRoot.partials.KernelOutputKeywordViewModels.cs

@@ -4,13 +4,16 @@ using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class KernelOutputKeywordViewModels : ViewModelBase {
             public static readonly KernelOutputKeywordViewModels Instance = new KernelOutputKeywordViewModels();
             private readonly Dictionary<Guid, List<KernelOutputKeywordViewModel>> _dicByKernelOutputId = new Dictionary<Guid, List<KernelOutputKeywordViewModel>>();
             private readonly Dictionary<Guid, KernelOutputKeywordViewModel> _dicById = new Dictionary<Guid, KernelOutputKeywordViewModel>();
 
             private KernelOutputKeywordViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -37,8 +40,8 @@ namespace NTMiner {
                                 _dicByKernelOutputId[item.KernelOutputId].Add(vm);
                             }
                         }
-                        if (NTMinerRoot.Instance.CurrentMineContext != null) {
-                            if (AppContext.Instance.KernelOutputVms.TryGetKernelOutputVm(NTMinerRoot.Instance.CurrentMineContext.KernelOutput.GetId(), out KernelOutputViewModel kernelOutputVm)) {
+                        if (NTMinerContext.Instance.CurrentMineContext != null) {
+                            if (KernelOutputVms.TryGetKernelOutputVm(NTMinerContext.Instance.CurrentMineContext.KernelOutput.GetId(), out KernelOutputViewModel kernelOutputVm)) {
                                 kernelOutputVm.OnPropertyChanged(nameof(kernelOutputVm.KernelOutputKeywords));
                             }
                         }
@@ -48,7 +51,7 @@ namespace NTMiner {
                         if (!_dicById.ContainsKey(message.Target.GetId())) {
                             KernelOutputKeywordViewModel vm = new KernelOutputKeywordViewModel(message.Target);
                             _dicById.Add(vm.Id, vm);
-                            if (AppContext.Instance.KernelOutputVms.TryGetKernelOutputVm(vm.KernelOutputId, out KernelOutputViewModel kernelOutputVm)) {
+                            if (KernelOutputVms.TryGetKernelOutputVm(vm.KernelOutputId, out KernelOutputViewModel kernelOutputVm)) {
                                 if (!_dicByKernelOutputId.ContainsKey(vm.KernelOutputId)) {
                                     _dicByKernelOutputId.Add(vm.KernelOutputId, new List<KernelOutputKeywordViewModel>());
                                 }
@@ -68,7 +71,7 @@ namespace NTMiner {
                         if (_dicById.TryGetValue(message.Target.GetId(), out KernelOutputKeywordViewModel vm)) {
                             _dicById.Remove(vm.Id);
                             _dicByKernelOutputId[vm.KernelOutputId].Remove(vm);
-                            if (AppContext.Instance.KernelOutputVms.TryGetKernelOutputVm(vm.KernelOutputId, out KernelOutputViewModel kernelOutputVm)) {
+                            if (KernelOutputVms.TryGetKernelOutputVm(vm.KernelOutputId, out KernelOutputViewModel kernelOutputVm)) {
                                 kernelOutputVm.OnPropertyChanged(nameof(kernelOutputVm.KernelOutputKeywords));
                             }
                         }
@@ -83,7 +86,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.KernelOutputKeywordSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.KernelOutputKeywordSet.AsEnumerable()) {
                     var vm = new KernelOutputKeywordViewModel(item);
                     if (!_dicById.ContainsKey(item.GetId())) {
                         _dicById.Add(item.GetId(), vm);

+ 12 - 10
src/AppModels/AppContext.partials.KernelOutputTranslaterViewModels.cs → src/AppModels/AppRoot.partials.KernelOutputTranslaterViewModels.cs

@@ -1,17 +1,19 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class KernelOutputTranslaterViewModels : ViewModelBase {
             public static readonly KernelOutputTranslaterViewModels Instance = new KernelOutputTranslaterViewModels();
             private readonly Dictionary<Guid, List<KernelOutputTranslaterViewModel>> _dicByKernelOutputId = new Dictionary<Guid, List<KernelOutputTranslaterViewModel>>();
             private readonly Dictionary<Guid, KernelOutputTranslaterViewModel> _dicById = new Dictionary<Guid, KernelOutputTranslaterViewModel>();
 
             private KernelOutputTranslaterViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -27,7 +29,7 @@ namespace NTMiner {
                     }, location: this.GetType());
                 AddEventPath<KernelOutputTranslaterAddedEvent>("添加了内核输出翻译器后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
-                        if (AppContext.Instance.KernelOutputVms.TryGetKernelOutputVm(message.Target.KernelOutputId, out KernelOutputViewModel kernelOutputVm)) {
+                        if (KernelOutputVms.TryGetKernelOutputVm(message.Target.KernelOutputId, out KernelOutputViewModel kernelOutputVm)) {
                             if (!_dicByKernelOutputId.ContainsKey(message.Target.KernelOutputId)) {
                                 _dicByKernelOutputId.Add(message.Target.KernelOutputId, new List<KernelOutputTranslaterViewModel>());
                             }
@@ -39,10 +41,10 @@ namespace NTMiner {
                     }, location: this.GetType());
                 AddEventPath<KernelOutputTranslaterUpdatedEvent>("更新了内核输出翻译器后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
-                        if (_dicByKernelOutputId.ContainsKey(message.Target.KernelOutputId)) {
-                            var item = _dicByKernelOutputId[message.Target.KernelOutputId].FirstOrDefault(a => a.Id == message.Target.GetId());
-                            if (item != null) {
-                                item.Update(message.Target);
+                        if (_dicByKernelOutputId.TryGetValue(message.Target.KernelOutputId, out List<KernelOutputTranslaterViewModel> vms)) {
+                            var vm = vms.FirstOrDefault(a => a.Id == message.Target.GetId());
+                            if (vm != null) {
+                                vm.Update(message.Target);
                             }
                         }
                     }, location: this.GetType());
@@ -57,7 +59,7 @@ namespace NTMiner {
                         if (_dicById.ContainsKey(message.Target.GetId())) {
                             _dicById.Remove(message.Target.GetId());
                         }
-                        if (AppContext.Instance.KernelOutputVms.TryGetKernelOutputVm(message.Target.KernelOutputId, out KernelOutputViewModel kernelOutputVm)) {
+                        if (KernelOutputVms.TryGetKernelOutputVm(message.Target.KernelOutputId, out KernelOutputViewModel kernelOutputVm)) {
                             kernelOutputVm.OnPropertyChanged(nameof(kernelOutputVm.KernelOutputTranslaters));
                         }
                     }, location: this.GetType());
@@ -71,7 +73,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.KernelOutputTranslaterSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.KernelOutputTranslaterSet.AsEnumerable()) {
                     if (!_dicByKernelOutputId.ContainsKey(item.KernelOutputId)) {
                         _dicByKernelOutputId.Add(item.KernelOutputId, new List<KernelOutputTranslaterViewModel>());
                     }

+ 9 - 8
src/AppModels/AppContext.partials.KernelOutputViewModels.cs → src/AppModels/AppRoot.partials.KernelOutputViewModels.cs

@@ -1,16 +1,18 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class KernelOutputViewModels : ViewModelBase {
             public static readonly KernelOutputViewModels Instance = new KernelOutputViewModels();
             private readonly Dictionary<Guid, KernelOutputViewModel> _dicById = new Dictionary<Guid, KernelOutputViewModel>();
 
             private KernelOutputViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -32,10 +34,9 @@ namespace NTMiner {
                     }, location: this.GetType());
                 AddEventPath<KernelOutputUpdatedEvent>("更新了内核输出组后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
-                        if (_dicById.ContainsKey(message.Target.GetId())) {
-                            var item = _dicById[message.Target.GetId()];
-                            if (item != null) {
-                                item.Update(message.Target);
+                        if (_dicById.TryGetValue(message.Target.GetId(), out KernelOutputViewModel vm)) {
+                            if (vm != null) {
+                                vm.Update(message.Target);
                             }
                         }
                     }, location: this.GetType());
@@ -57,7 +58,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.KernelOutputSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.KernelOutputSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new KernelOutputViewModel(item));
                 }
             }

+ 22 - 15
src/AppModels/AppContext.partials.KernelViewModels.cs → src/AppModels/AppRoot.partials.KernelViewModels.cs

@@ -5,7 +5,7 @@ using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class KernelViewModels : ViewModelBase {
             public static readonly KernelViewModels Instance = new KernelViewModels();
             private readonly Dictionary<Guid, KernelViewModel> _dicById = new Dictionary<Guid, KernelViewModel>();
@@ -16,6 +16,9 @@ namespace NTMiner {
             }
 
             private KernelViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -32,7 +35,7 @@ namespace NTMiner {
                     action: (message) => {
                         _dicById.Add(message.Target.GetId(), new KernelViewModel(message.Target));
                         OnPropertyChanged(nameof(AllKernels));
-                        foreach (var coinKernelVm in AppContext.Instance.CoinKernelVms.AllCoinKernels.Where(a => a.KernelId == message.Target.GetId())) {
+                        foreach (var coinKernelVm in CoinKernelVms.AllCoinKernels.Where(a => a.KernelId == message.Target.GetId())) {
                             coinKernelVm.OnPropertyChanged(nameof(coinKernelVm.IsSupportDualMine));
                         }
                     }, location: this.GetType());
@@ -40,25 +43,29 @@ namespace NTMiner {
                     action: message => {
                         _dicById.Remove(message.Target.GetId());
                         OnPropertyChanged(nameof(AllKernels));
-                        foreach (var coinKernelVm in AppContext.Instance.CoinKernelVms.AllCoinKernels.Where(a => a.KernelId == message.Target.GetId())) {
+                        foreach (var coinKernelVm in CoinKernelVms.AllCoinKernels.Where(a => a.KernelId == message.Target.GetId())) {
                             coinKernelVm.OnPropertyChanged(nameof(coinKernelVm.IsSupportDualMine));
                         }
                     }, location: this.GetType());
                 AddEventPath<KernelUpdatedEvent>("更新了内核后调整VM内存", LogEnum.DevConsole,
                     action: message => {
-                        var entity = _dicById[message.Target.GetId()];
-                        PublishStatus publishStatus = entity.PublishState;
-                        Guid kernelInputId = entity.KernelInputId;
-                        entity.Update(message.Target);
-                        if (publishStatus != entity.PublishState) {
-                            foreach (var coinKernelVm in AppContext.Instance.CoinKernelVms.AllCoinKernels.Where(a => a.KernelId == entity.Id)) {
-                                foreach (var coinVm in AppContext.Instance.CoinVms.AllCoins.Where(a => a.Id == coinKernelVm.CoinId)) {
-                                    coinVm.OnPropertyChanged(nameof(coinVm.CoinKernels));
+                        if (_dicById.TryGetValue(message.Target.GetId(), out KernelViewModel vm)) {
+                            PublishStatus publishStatus = vm.PublishState;
+                            Guid kernelInputId = vm.KernelInputId;
+                            vm.Update(message.Target);
+                            if (publishStatus != vm.PublishState) {
+                                foreach (var coinKernelVm in CoinKernelVms.AllCoinKernels.Where(a => a.KernelId == vm.Id)) {
+                                    foreach (var coinVm in CoinVms.AllCoins.Where(a => a.Id == coinKernelVm.CoinId)) {
+                                        coinVm.OnPropertyChanged(nameof(coinVm.CoinKernels));
+                                    }
+                                }
+                            }
+                            if (kernelInputId != vm.KernelInputId) {
+                                CoinViewModel coinVm = MinerProfileVm.CoinVm;
+                                if (coinVm != null && coinVm.CoinKernel != null && coinVm.CoinKernel.Kernel.Id == vm.Id) {
+                                    NTMinerContext.RefreshArgsAssembly.Invoke("当前选用的内核切换了引用的内核输入");
                                 }
                             }
-                        }
-                        if (kernelInputId != entity.KernelInputId) {
-                            NTMinerRoot.RefreshArgsAssembly.Invoke();
                         }
                     }, location: this.GetType());
                 Init();
@@ -71,7 +78,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.KernelSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.KernelSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new KernelViewModel(item));
                 }
             }

+ 13 - 10
src/AppModels/AppContext.partials.PackageViewModels.cs → src/AppModels/AppRoot.partials.PackageViewModels.cs

@@ -1,16 +1,18 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class PackageViewModels : ViewModelBase {
             public static readonly PackageViewModels Instance = new PackageViewModels();
             private readonly Dictionary<Guid, PackageViewModel> _dicById = new Dictionary<Guid, PackageViewModel>();
 
             private PackageViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -27,7 +29,7 @@ namespace NTMiner {
                     action: (message) => {
                         _dicById.Add(message.Target.GetId(), new PackageViewModel(message.Target));
                         OnPropertyChanged(nameof(AllPackages));
-                        foreach (var item in AppContext.Instance.KernelVms.AllKernels) {
+                        foreach (var item in KernelVms.AllKernels) {
                             item.OnPropertyChanged(nameof(item.IsPackageValid));
                         }
                     }, location: this.GetType());
@@ -35,16 +37,17 @@ namespace NTMiner {
                     action: message => {
                         _dicById.Remove(message.Target.GetId());
                         OnPropertyChanged(nameof(AllPackages));
-                        foreach (var item in AppContext.Instance.KernelVms.AllKernels) {
+                        foreach (var item in KernelVms.AllKernels) {
                             item.OnPropertyChanged(nameof(item.IsPackageValid));
                         }
                     }, location: this.GetType());
                 AddEventPath<PackageUpdatedEvent>("更新了包后调整VM内存", LogEnum.DevConsole,
                     action: message => {
-                        var entity = _dicById[message.Target.GetId()];
-                        entity.Update(message.Target);
-                        foreach (var item in AppContext.Instance.KernelVms.AllKernels) {
-                            item.OnPropertyChanged(nameof(item.IsPackageValid));
+                        if (_dicById.TryGetValue(message.Target.GetId(), out PackageViewModel vm)) {
+                            vm.Update(message.Target);
+                            foreach (var item in KernelVms.AllKernels) {
+                                item.OnPropertyChanged(nameof(item.IsPackageValid));
+                            }
                         }
                     }, location: this.GetType());
                 Init();
@@ -57,7 +60,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.PackageSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.PackageSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new PackageViewModel(item));
                 }
             }

+ 9 - 6
src/AppModels/AppContext.partials.PoolKernelViewModels.cs → src/AppModels/AppRoot.partials.PoolKernelViewModels.cs

@@ -5,19 +5,22 @@ using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class PoolKernelViewModels : ViewModelBase {
             public static readonly PoolKernelViewModels Instance = new PoolKernelViewModels();
 
             private readonly Dictionary<Guid, PoolKernelViewModel> _dicById = new Dictionary<Guid, PoolKernelViewModel>();
             private PoolKernelViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
                 AddEventPath<PoolKernelAddedEvent>("新添了矿池内核后刷新矿池内核VM内存", LogEnum.DevConsole,
                     action: (message) => {
                         if (!_dicById.ContainsKey(message.Target.GetId())) {
-                            if (AppContext.Instance.PoolVms.TryGetPoolVm(message.Target.PoolId, out PoolViewModel poolVm)) {
+                            if (PoolVms.TryGetPoolVm(message.Target.PoolId, out PoolViewModel poolVm)) {
                                 _dicById.Add(message.Target.GetId(), new PoolKernelViewModel(message.Target));
                                 poolVm.OnPropertyChanged(nameof(poolVm.PoolKernels));
                             }
@@ -28,15 +31,15 @@ namespace NTMiner {
                         if (_dicById.ContainsKey(message.Target.GetId())) {
                             var vm = _dicById[message.Target.GetId()];
                             _dicById.Remove(message.Target.GetId());
-                            if (AppContext.Instance.PoolVms.TryGetPoolVm(vm.PoolId, out PoolViewModel poolVm)) {
+                            if (PoolVms.TryGetPoolVm(vm.PoolId, out PoolViewModel poolVm)) {
                                 poolVm.OnPropertyChanged(nameof(poolVm.PoolKernels));
                             }
                         }
                     }, location: this.GetType());
                 AddEventPath<PoolKernelUpdatedEvent>("更新了矿池内核后刷新VM内存", LogEnum.DevConsole,
                     action: (message) => {
-                        if (_dicById.ContainsKey(message.Target.GetId())) {
-                            _dicById[message.Target.GetId()].Update(message.Target);
+                        if (_dicById.TryGetValue(message.Target.GetId(), out PoolKernelViewModel vm)) {
+                            vm.Update(message.Target);
                         }
                     }, location: this.GetType());
                 Init();
@@ -49,7 +52,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (IPoolKernel item in NTMinerRoot.Instance.ServerContext.PoolKernelSet.AsEnumerable()) {
+                foreach (IPoolKernel item in NTMinerContext.Instance.ServerContext.PoolKernelSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new PoolKernelViewModel(item));
                 }
             }

+ 6 - 4
src/AppModels/AppContext.partials.PoolProfileViewModels.cs → src/AppModels/AppRoot.partials.PoolProfileViewModels.cs

@@ -1,15 +1,17 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class PoolProfileViewModels : ViewModelBase {
             public static readonly PoolProfileViewModels Instance = new PoolProfileViewModels();
             private readonly Dictionary<Guid, PoolProfileViewModel> _dicById = new Dictionary<Guid, PoolProfileViewModel>();
 
             private PoolProfileViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -36,7 +38,7 @@ namespace NTMiner {
                 if (!_dicById.TryGetValue(poolId, out PoolProfileViewModel poolProfile)) {
                     lock (_locker) {
                         if (!_dicById.TryGetValue(poolId, out poolProfile)) {
-                            poolProfile = new PoolProfileViewModel(NTMinerRoot.Instance.MinerProfile.GetPoolProfile(poolId));
+                            poolProfile = new PoolProfileViewModel(NTMinerContext.Instance.MinerProfile.GetPoolProfile(poolId));
                             _dicById.Add(poolId, poolProfile);
                         }
                     }

+ 15 - 19
src/AppModels/AppContext.partials.PoolViewModels.cs → src/AppModels/AppRoot.partials.PoolViewModels.cs

@@ -1,15 +1,17 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class PoolViewModels : ViewModelBase {
             public static readonly PoolViewModels Instance = new PoolViewModels();
             private readonly Dictionary<Guid, PoolViewModel> _dicById = new Dictionary<Guid, PoolViewModel>();
             private PoolViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -26,9 +28,9 @@ namespace NTMiner {
                     action: (message) => {
                         _dicById.Add(message.Target.GetId(), new PoolViewModel(message.Target));
                         OnPropertyChanged(nameof(AllPools));
-                        if (AppContext.Instance.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
-                            coinVm.CoinProfile.OnPropertyChanged(nameof(CoinProfileViewModel.MainCoinPool));
-                            coinVm.CoinProfile.OnPropertyChanged(nameof(CoinProfileViewModel.DualCoinPool));
+                        if (CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
+                            coinVm.CoinProfile?.OnPropertyChanged(nameof(CoinProfileViewModel.MainCoinPool));
+                            coinVm.CoinProfile?.OnPropertyChanged(nameof(CoinProfileViewModel.DualCoinPool));
                             coinVm.OnPropertyChanged(nameof(CoinViewModel.Pools));
                             coinVm.OnPropertyChanged(nameof(CoinViewModel.OptionPools));
                         }
@@ -37,16 +39,18 @@ namespace NTMiner {
                     action: (message) => {
                         _dicById.Remove(message.Target.GetId());
                         OnPropertyChanged(nameof(AllPools));
-                        if (AppContext.Instance.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
-                            coinVm.CoinProfile.OnPropertyChanged(nameof(CoinProfileViewModel.MainCoinPool));
-                            coinVm.CoinProfile.OnPropertyChanged(nameof(CoinProfileViewModel.DualCoinPool));
+                        if (CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
+                            coinVm.CoinProfile?.OnPropertyChanged(nameof(CoinProfileViewModel.MainCoinPool));
+                            coinVm.CoinProfile?.OnPropertyChanged(nameof(CoinProfileViewModel.DualCoinPool));
                             coinVm.OnPropertyChanged(nameof(CoinViewModel.Pools));
                             coinVm.OnPropertyChanged(nameof(CoinViewModel.OptionPools));
                         }
                     }, location: this.GetType());
                 AddEventPath<PoolUpdatedEvent>("更新矿池后刷新VM内存", LogEnum.DevConsole,
                     action: (message) => {
-                        _dicById[message.Target.GetId()].Update(message.Target);
+                        if (_dicById.TryGetValue(message.Target.GetId(), out PoolViewModel vm)) {
+                            vm.Update(message.Target);
+                        }
                     }, location: this.GetType());
                 Init();
 #if DEBUG
@@ -58,7 +62,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.PoolSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.PoolSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new PoolViewModel(item));
                 }
             }
@@ -72,14 +76,6 @@ namespace NTMiner {
                     return _dicById.Values.ToList();
                 }
             }
-
-            public PoolViewModel GetNextOne(Guid coinId, int sortNumber) {
-                return AllPools.Where(a => a.CoinId == coinId).GetNextOne(sortNumber);
-            }
-
-            public PoolViewModel GetUpOne(Guid coinId, int sortNumber) {
-                return AllPools.Where(a => a.CoinId == coinId).GetUpOne(sortNumber);
-            }
         }
     }
 }

+ 6 - 4
src/AppModels/AppContext.partials.ShareViewModels.cs → src/AppModels/AppRoot.partials.ShareViewModels.cs

@@ -1,15 +1,17 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class ShareViewModels {
             public static readonly ShareViewModels Instance = new ShareViewModels();
             private readonly Dictionary<Guid, ShareViewModel> _dicByCoinId = new Dictionary<Guid, ShareViewModel>();
 
             private ShareViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -29,7 +31,7 @@ namespace NTMiner {
 
             private readonly object _locker = new object();
             public ShareViewModel GetOrCreate(Guid coinId) {
-                if (!NTMinerRoot.Instance.ServerContext.CoinSet.Contains(coinId)) {
+                if (!NTMinerContext.Instance.ServerContext.CoinSet.Contains(coinId)) {
                     return new ShareViewModel(coinId);
                 }
                 if (!_dicByCoinId.TryGetValue(coinId, out ShareViewModel shareVm)) {

+ 21 - 16
src/AppModels/AppContext.partials.SysDicItemViewModels.cs → src/AppModels/AppRoot.partials.SysDicItemViewModels.cs

@@ -1,16 +1,18 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class SysDicItemViewModels : ViewModelBase {
             public static readonly SysDicItemViewModels Instance = new SysDicItemViewModels();
             private readonly Dictionary<Guid, SysDicItemViewModel> _dicById = new Dictionary<Guid, SysDicItemViewModel>();
 
             private SysDicItemViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -28,7 +30,7 @@ namespace NTMiner {
                         if (!_dicById.ContainsKey(message.Target.GetId())) {
                             _dicById.Add(message.Target.GetId(), new SysDicItemViewModel(message.Target));
                             OnPropertyChangeds();
-                            if (AppContext.Instance.SysDicVms.TryGetSysDicVm(message.Target.DicId, out SysDicViewModel sysDicVm)) {
+                            if (SysDicVms.TryGetSysDicVm(message.Target.DicId, out SysDicViewModel sysDicVm)) {
                                 sysDicVm.OnPropertyChanged(nameof(sysDicVm.SysDicItems));
                                 sysDicVm.OnPropertyChanged(nameof(sysDicVm.SysDicItemsSelect));
                             }
@@ -36,12 +38,11 @@ namespace NTMiner {
                     }, location: this.GetType());
                 AddEventPath<SysDicItemUpdatedEvent>("更新了系统字典项后调整VM内存", LogEnum.DevConsole,
                     action: (message) => {
-                        if (_dicById.ContainsKey(message.Target.GetId())) {
-                            SysDicItemViewModel entity = _dicById[message.Target.GetId()];
-                            int sortNumber = entity.SortNumber;
-                            entity.Update(message.Target);
-                            if (sortNumber != entity.SortNumber) {
-                                if (AppContext.Instance.SysDicVms.TryGetSysDicVm(entity.DicId, out SysDicViewModel sysDicVm)) {
+                        if (_dicById.TryGetValue(message.Target.GetId(), out SysDicItemViewModel vm)) {
+                            int sortNumber = vm.SortNumber;
+                            vm.Update(message.Target);
+                            if (sortNumber != vm.SortNumber) {
+                                if (SysDicVms.TryGetSysDicVm(vm.DicId, out SysDicViewModel sysDicVm)) {
                                     sysDicVm.OnPropertyChanged(nameof(sysDicVm.SysDicItems));
                                     sysDicVm.OnPropertyChanged(nameof(sysDicVm.SysDicItemsSelect));
                                 }
@@ -52,7 +53,7 @@ namespace NTMiner {
                     action: (message) => {
                         _dicById.Remove(message.Target.GetId());
                         OnPropertyChangeds();
-                        if (AppContext.Instance.SysDicVms.TryGetSysDicVm(message.Target.DicId, out SysDicViewModel sysDicVm)) {
+                        if (SysDicVms.TryGetSysDicVm(message.Target.DicId, out SysDicViewModel sysDicVm)) {
                             sysDicVm.OnPropertyChanged(nameof(sysDicVm.SysDicItems));
                             sysDicVm.OnPropertyChanged(nameof(sysDicVm.SysDicItemsSelect));
                         }
@@ -67,7 +68,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.SysDicItemSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.SysDicItemSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new SysDicItemViewModel(item));
                 }
             }
@@ -84,7 +85,7 @@ namespace NTMiner {
             public List<SysDicItemViewModel> KernelBrandItems {
                 get {
                     List<SysDicItemViewModel> list = new List<SysDicItemViewModel>();
-                    if (AppContext.Instance.SysDicVms.TryGetSysDicVm(NTKeyword.KernelBrandSysDicCode, out SysDicViewModel sysDic)) {
+                    if (SysDicVms.TryGetSysDicVm(NTKeyword.KernelBrandSysDicCode, out SysDicViewModel sysDic)) {
                         list.AddRange(List.Where(a => a.DicId == sysDic.Id).OrderBy(a => a.SortNumber));
                     }
                     return list;
@@ -96,7 +97,7 @@ namespace NTMiner {
                     List<SysDicItemViewModel> list = new List<SysDicItemViewModel> {
                         SysDicItemViewModel.PleaseSelect
                     };
-                    if (AppContext.Instance.SysDicVms.TryGetSysDicVm(NTKeyword.KernelBrandSysDicCode, out SysDicViewModel sysDic)) {
+                    if (SysDicVms.TryGetSysDicVm(NTKeyword.KernelBrandSysDicCode, out SysDicViewModel sysDic)) {
                         list.AddRange(List.Where(a => a.DicId == sysDic.Id).OrderBy(a => a.SortNumber));
                     }
                     return list;
@@ -106,7 +107,7 @@ namespace NTMiner {
             public List<SysDicItemViewModel> PoolBrandItems {
                 get {
                     List<SysDicItemViewModel> list = new List<SysDicItemViewModel>();
-                    if (AppContext.Instance.SysDicVms.TryGetSysDicVm(NTKeyword.PoolBrandSysDicCode, out SysDicViewModel sysDic)) {
+                    if (SysDicVms.TryGetSysDicVm(NTKeyword.PoolBrandSysDicCode, out SysDicViewModel sysDic)) {
                         list.AddRange(List.Where(a => a.DicId == sysDic.Id).OrderBy(a => a.SortNumber));
                     }
                     return list;
@@ -116,7 +117,7 @@ namespace NTMiner {
             public List<SysDicItemViewModel> AlgoItems {
                 get {
                     List<SysDicItemViewModel> list = new List<SysDicItemViewModel>();
-                    if (AppContext.Instance.SysDicVms.TryGetSysDicVm(NTKeyword.AlgoSysDicCode, out SysDicViewModel sysDic)) {
+                    if (SysDicVms.TryGetSysDicVm(NTKeyword.AlgoSysDicCode, out SysDicViewModel sysDic)) {
                         list.AddRange(List.Where(a => a.DicId == sysDic.Id).OrderBy(a => a.SortNumber));
                     }
                     return list;
@@ -138,6 +139,10 @@ namespace NTMiner {
                     return _dicById.Values.ToList();
                 }
             }
+
+            public IEnumerable<SysDicItemViewModel> GetSysDicItemVmsByDicId(Guid dicId) {
+                return _dicById.Values.Where(a => a.DicId == dicId);
+            }
         }
     }
 }

+ 10 - 9
src/AppModels/AppContext.partials.SysDicViewModels.cs → src/AppModels/AppRoot.partials.SysDicViewModels.cs

@@ -1,12 +1,11 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Windows.Input;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class SysDicViewModels : ViewModelBase {
             public static readonly SysDicViewModels Instance = new SysDicViewModels();
             private readonly Dictionary<Guid, SysDicViewModel> _dicById = new Dictionary<Guid, SysDicViewModel>();
@@ -14,6 +13,9 @@ namespace NTMiner {
 
             public ICommand Add { get; private set; }
             private SysDicViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -43,11 +45,10 @@ namespace NTMiner {
                     }, location: this.GetType());
                 AddEventPath<SysDicUpdatedEvent>("更新了系统字典后调整VM内存", LogEnum.DevConsole,
                     action: (message) => {
-                        if (_dicById.ContainsKey(message.Target.GetId())) {
-                            SysDicViewModel entity = _dicById[message.Target.GetId()];
-                            int sortNumber = entity.SortNumber;
-                            entity.Update(message.Target);
-                            if (sortNumber != entity.SortNumber) {
+                        if (_dicById.TryGetValue(message.Target.GetId(), out SysDicViewModel vm)) {
+                            int sortNumber = vm.SortNumber;
+                            vm.Update(message.Target);
+                            if (sortNumber != vm.SortNumber) {
                                 this.OnPropertyChanged(nameof(List));
                             }
                         }
@@ -68,7 +69,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.ServerContext.SysDicSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.ServerContext.SysDicSet.AsEnumerable()) {
                     SysDicViewModel sysDicVm = new SysDicViewModel(item);
                     _dicById.Add(item.GetId(), sysDicVm);
                     _dicByCode.Add(item.Code, sysDicVm);

+ 13 - 15
src/AppModels/AppContext.partials.WalletViewModels.cs → src/AppModels/AppRoot.partials.WalletViewModels.cs

@@ -1,15 +1,17 @@
-using NTMiner.Core;
-using NTMiner.Vms;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
-    public partial class AppContext {
+    public static partial class AppRoot {
         public class WalletViewModels : ViewModelBase {
             public static readonly WalletViewModels Instance = new WalletViewModels();
             private readonly Dictionary<Guid, WalletViewModel> _dicById = new Dictionary<Guid, WalletViewModel>();
             private WalletViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
@@ -22,26 +24,30 @@ namespace NTMiner {
                     action: (message) => {
                         _dicById.Add(message.Target.GetId(), new WalletViewModel(message.Target));
                         OnPropertyChanged(nameof(WalletList));
-                        if (AppContext.Instance.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coin)) {
+                        if (CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coin)) {
                             coin.OnPropertyChanged(nameof(CoinViewModel.Wallets));
                             coin.OnPropertyChanged(nameof(CoinViewModel.WalletItems));
                             coin.CoinKernel?.CoinKernelProfile?.SelectedDualCoin?.OnPropertyChanged(nameof(CoinViewModel.Wallets));
                         }
+                        VirtualRoot.RaiseEvent(new WalletVmAddedEvent(message));
                     }, location: this.GetType());
                 AddEventPath<WalletRemovedEvent>("删除了钱包后调整VM内存", LogEnum.DevConsole,
                     action: (message) => {
                         _dicById.Remove(message.Target.GetId());
                         OnPropertyChanged(nameof(WalletList));
-                        if (AppContext.Instance.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coin)) {
+                        if (CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coin)) {
                             coin.OnPropertyChanged(nameof(CoinViewModel.Wallets));
                             coin.OnPropertyChanged(nameof(CoinViewModel.WalletItems));
                             coin.CoinProfile?.OnPropertyChanged(nameof(CoinProfileViewModel.SelectedWallet));
                             coin.CoinKernel?.CoinKernelProfile?.SelectedDualCoin?.OnPropertyChanged(nameof(CoinViewModel.Wallets));
                         }
+                        VirtualRoot.RaiseEvent(new WalletVmRemovedEvent(message));
                     }, location: this.GetType());
                 AddEventPath<WalletUpdatedEvent>("更新了钱包后调整VM内存", LogEnum.DevConsole,
                     action: (message) => {
-                        _dicById[message.Target.GetId()].Update(message.Target);
+                        if (_dicById.TryGetValue(message.Target.GetId(), out WalletViewModel vm)) {
+                            vm.Update(message.Target);
+                        }
                     }, location: this.GetType());
                 Init();
 #if DEBUG
@@ -53,7 +59,7 @@ namespace NTMiner {
             }
 
             private void Init() {
-                foreach (var item in NTMinerRoot.Instance.MinerProfile.GetWallets()) {
+                foreach (var item in NTMinerContext.Instance.MinerProfile.GetWallets()) {
                     _dicById.Add(item.GetId(), new WalletViewModel(item));
                 }
             }
@@ -63,14 +69,6 @@ namespace NTMiner {
                     return _dicById.Values.ToList();
                 }
             }
-
-            public WalletViewModel GetUpOne(Guid coinId, int sortNumber) {
-                return WalletList.Where(a => a.CoinId == coinId).GetUpOne(sortNumber);
-            }
-
-            public WalletViewModel GetNextOne(Guid coinId, int sortNumber) {
-                return WalletList.Where(a => a.CoinId == coinId).GetNextOne(sortNumber);
-            }
         }
     }
 }

+ 173 - 303
src/AppModels/AppStatic.cs

@@ -1,11 +1,11 @@
 using NTMiner.Core;
-using NTMiner.Core.MinerClient;
 using NTMiner.Core.MinerServer;
+using NTMiner.MinerStudio;
+using NTMiner.User;
 using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.IO;
 using System.Linq;
 using System.Windows;
 using System.Windows.Input;
@@ -13,10 +13,12 @@ using System.Windows.Media;
 using System.Windows.Media.Imaging;
 
 namespace NTMiner {
-    // 注意:这里的成员只应用于绑定,不应在.cs中使用,在IDE中看到的静态源代码应用计数应为0
+    /// <summary>
+    /// 注意:这里的成员只应用于绑定,不应在.cs中使用,在IDE中看到的静态源代码应用计数应为0
+    /// </summary>
     public static class AppStatic {
         private static readonly Lazy<BitmapImage> _bigLogoImageSource = new Lazy<BitmapImage>(() => {
-            return new BitmapImage(new Uri((VirtualRoot.IsMinerStudio ? "/NTMinerWpf;component/Styles/Images/cc128.png" : "/NTMinerWpf;component/Styles/Images/logo128.png"), UriKind.RelativeOrAbsolute));
+            return new BitmapImage(new Uri((ClientAppType.IsMinerStudio ? "/NTMinerWpf;component/Styles/Images/cc128.png" : "/NTMinerWpf;component/Styles/Images/logo128.png"), UriKind.RelativeOrAbsolute));
         });
 
         public static BitmapImage BigLogoImageSource {
@@ -25,100 +27,6 @@ namespace NTMiner {
             }
         }
 
-        private static string GetUpdaterVersion() {
-            string version = string.Empty;
-            if (VirtualRoot.LocalAppSettingSet.TryGetAppSetting(NTKeyword.UpdaterVersionAppSettingKey, out IAppSetting setting) && setting.Value != null) {
-                version = setting.Value.ToString();
-            }
-            return version;
-        }
-
-        private static void SetUpdaterVersion(string value) {
-            VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
-                Key = NTKeyword.UpdaterVersionAppSettingKey,
-                Value = value
-            }));
-        }
-
-        private static string GetMinerClientFinderVersion() {
-            string version = string.Empty;
-            if (VirtualRoot.LocalAppSettingSet.TryGetAppSetting(NTKeyword.MinerClientFinderVersionAppSettingKey, out IAppSetting setting) && setting.Value != null) {
-                version = setting.Value.ToString();
-            }
-            return version;
-        }
-
-        private static void SetMinerClientFinderVersion(string value) {
-            VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
-                Key = NTKeyword.MinerClientFinderVersionAppSettingKey,
-                Value = value
-            }));
-        }
-
-        #region Upgrade
-        public static void Upgrade(string fileName, Action callback) {
-            try {                
-                RpcRoot.OfficialServer.FileUrlService.GetNTMinerUpdaterUrlAsync((downloadFileUrl, e) => {
-                    try {
-                        string argument = string.Empty;
-                        if (!string.IsNullOrEmpty(fileName)) {
-                            argument = "ntminerFileName=" + fileName;
-                        }
-                        if (VirtualRoot.IsMinerStudio) {
-                            argument += " --minerstudio";
-                        }
-                        if (string.IsNullOrEmpty(downloadFileUrl)) {
-                            if (File.Exists(SpecialPath.UpdaterFileFullName)) {
-                                Windows.Cmd.RunClose(SpecialPath.UpdaterFileFullName, argument);
-                            }
-                            callback?.Invoke();
-                            return;
-                        }
-                        Uri uri = new Uri(downloadFileUrl);
-                        string localVersion = GetUpdaterVersion();
-                        if (string.IsNullOrEmpty(localVersion) || !File.Exists(SpecialPath.UpdaterFileFullName) || uri.AbsolutePath != localVersion) {
-                            VirtualRoot.Execute(new ShowFileDownloaderCommand(downloadFileUrl, "开源矿工更新器", (window, isSuccess, message, saveFileFullName) => {
-                                try {
-                                    if (isSuccess) {
-                                        string updateDirFullName = Path.GetDirectoryName(SpecialPath.UpdaterFileFullName);
-                                        if (!Directory.Exists(updateDirFullName)) {
-                                            Directory.CreateDirectory(updateDirFullName);
-                                        }
-                                        File.Copy(saveFileFullName, SpecialPath.UpdaterFileFullName, overwrite: true);
-                                        File.Delete(saveFileFullName);
-                                        SetUpdaterVersion(uri.AbsolutePath);
-                                        window?.Close();
-                                        Windows.Cmd.RunClose(SpecialPath.UpdaterFileFullName, argument);
-                                        callback?.Invoke();
-                                    }
-                                    else {
-                                        VirtualRoot.ThisLocalError(nameof(AppStatic), message, toConsole: true);
-                                        callback?.Invoke();
-                                    }
-                                }
-                                catch(Exception ex) {
-                                    Logger.ErrorDebugLine(ex);
-                                    callback?.Invoke();
-                                }
-                            }));
-                        }
-                        else {
-                            Windows.Cmd.RunClose(SpecialPath.UpdaterFileFullName, argument);
-                            callback?.Invoke();
-                        }
-                    }
-                    catch (Exception ex) {
-                        Logger.ErrorDebugLine(ex);
-                        callback?.Invoke();
-                    }
-                });
-            }
-            catch {
-                callback?.Invoke();
-            }
-        }
-        #endregion
-
         #region IsWin10
         public static bool IsGEWin10 {
             get { return VirtualRoot.IsGEWin10; }
@@ -146,71 +54,77 @@ namespace NTMiner {
 
         #region InnerProperty
         public static string Id {
-            get { return VirtualRoot.Id.ToString(); }
+            get { return NTMinerContext.Id.ToString(); }
         }
         public static string BootOn {
-            get => NTMinerRoot.Instance.CreatedOn.ToString("yyyy-MM-dd HH:mm:ss");
+            get => NTMinerContext.Instance.CreatedOn.ToString("yyyy-MM-dd HH:mm:ss");
         }
+        /// <summary>
+        /// 为了让IDE显式的引用计数为0该文件里其它地方直接使用<see cref="EntryAssemblyInfo.HomeDirFullName"/>
+        /// </summary>
         public static string HomeDir {
-            get => EntryAssemblyInfo.HomeDirFullName;
+            get => HomePath.HomeDirFullName;
         }
+        /// <summary>
+        /// 为了让IDE显式的引用计数为0该文件里其它地方直接使用<see cref="EntryAssemblyInfo.TempDirFullName"/>
+        /// </summary>
         public static string TempDir {
             get { return EntryAssemblyInfo.TempDirFullName; }
         }
         public static string ServerDbFileFullName {
             get {
-                return EntryAssemblyInfo.ServerDbFileFullName.Replace(HomeDir, NTKeyword.HomeDirParameterName);
+                return HomePath.ServerDbFileFullName.Replace(HomePath.HomeDirFullName, NTKeyword.HomeDirParameterName);
             }
         }
         public static string LocalDbFileFullName {
-            get => EntryAssemblyInfo.LocalDbFileFullName.Replace(HomeDir, NTKeyword.HomeDirParameterName);
+            get => HomePath.LocalDbFileFullName.Replace(HomePath.HomeDirFullName, NTKeyword.HomeDirParameterName);
         }
 
         public static string ServerJsonFileFullName {
-            get { return SpecialPath.ServerJsonFileFullName.Replace(HomeDir, NTKeyword.HomeDirParameterName); }
+            get { return HomePath.ServerJsonFileFullName.Replace(HomePath.HomeDirFullName, NTKeyword.HomeDirParameterName); }
         }
 
         public static string ServerVersionJsonFileFullName {
-            get { return EntryAssemblyInfo.ServerVersionJsonFileFullName.Replace(HomeDir, NTKeyword.HomeDirParameterName); }
+            get { return HomePath.ServerVersionJsonFileFullName.Replace(HomePath.HomeDirFullName, NTKeyword.HomeDirParameterName); }
         }
 
         public static string PackagesDirFullName {
-            get { return SpecialPath.PackagesDirFullName.Replace(HomeDir, NTKeyword.HomeDirParameterName); }
+            get { return HomePath.PackagesDirFullName.Replace(HomePath.HomeDirFullName, NTKeyword.HomeDirParameterName); }
         }
 
         public static string DaemonFileFullName {
-            get { return SpecialPath.DaemonFileFullName.Replace(TempDir, NTKeyword.TempDirParameterName); }
+            get { return TempPath.DaemonFileFullName.Replace(EntryAssemblyInfo.TempDirFullName, NTKeyword.TempDirParameterName); }
         }
 
         public static string DevConsoleFileFullName {
-            get { return SpecialPath.DevConsoleFileFullName.Replace(TempDir, NTKeyword.TempDirParameterName); }
+            get { return TempPath.DevConsoleFileFullName.Replace(EntryAssemblyInfo.TempDirFullName, NTKeyword.TempDirParameterName); }
         }
 
         public static string DownloadDirFullName {
             get {
-                return SpecialPath.DownloadDirFullName.Replace(TempDir, NTKeyword.TempDirParameterName);
+                return TempPath.DownloadDirFullName.Replace(EntryAssemblyInfo.TempDirFullName, NTKeyword.TempDirParameterName);
             }
         }
 
         public static string KernelsDirFullName {
-            get { return SpecialPath.KernelsDirFullName.Replace(TempDir, NTKeyword.TempDirParameterName); }
+            get { return TempPath.KernelsDirFullName.Replace(EntryAssemblyInfo.TempDirFullName, NTKeyword.TempDirParameterName); }
         }
 
         public static string LogsDirFullName {
             get {
-                if (VirtualRoot.IsMinerClient) {
-                    return SpecialPath.TempLogsDirFullName.Replace(TempDir, NTKeyword.TempDirParameterName);
+                if (ClientAppType.IsMinerClient) {
+                    return TempPath.TempLogsDirFullName.Replace(EntryAssemblyInfo.TempDirFullName, NTKeyword.TempDirParameterName);
                 }
-                return SpecialPath.HomeLogsDirFullName.Replace(HomeDir, NTKeyword.HomeDirParameterName);
+                return HomePath.HomeLogsDirFullName.Replace(HomePath.HomeDirFullName, NTKeyword.HomeDirParameterName);
             }
         }
 
         public static string AppRuntime {
             get {
-                if (VirtualRoot.IsMinerStudio) {
+                if (ClientAppType.IsMinerStudio) {
                     return "群控客户端";
                 }
-                else if (VirtualRoot.IsMinerClient) {
+                else if (ClientAppType.IsMinerClient) {
                     return "挖矿端";
                 }
                 return "未知";
@@ -219,8 +133,11 @@ namespace NTMiner {
         #endregion
 
         #region IsMinerClient
+        /// <summary>
+        /// 为了让IDE显式的引用计数为0该文件里其它地方直接使用<see cref="TempPath.IsMinerClient"/>
+        /// </summary>
         public static bool IsMinerClient {
-            get => VirtualRoot.IsMinerClient;
+            get => ClientAppType.IsMinerClient;
         }
 
         public static Visibility IsMinerClientVisible {
@@ -228,15 +145,18 @@ namespace NTMiner {
                 if (WpfUtil.IsInDesignMode) {
                     return Visibility.Visible;
                 }
-                if (VirtualRoot.IsMinerClient) {
+                if (ClientAppType.IsMinerClient) {
                     return Visibility.Visible;
                 }
                 return Visibility.Collapsed;
             }
         }
 
+        /// <summary>
+        /// 为了让IDE显式的引用计数为0该文件里其它地方直接使用<see cref="TempPath.IsMinerStudio"/>
+        /// </summary>
         public static bool IsMinerStudio {
-            get => VirtualRoot.IsMinerStudio;
+            get => ClientAppType.IsMinerStudio;
         }
 
         public static Visibility IsMinerStudioVisible {
@@ -244,7 +164,7 @@ namespace NTMiner {
                 if (WpfUtil.IsInDesignMode) {
                     return Visibility.Visible;
                 }
-                if (VirtualRoot.IsMinerStudio) {
+                if (ClientAppType.IsMinerStudio) {
                     return Visibility.Visible;
                 }
                 return Visibility.Collapsed;
@@ -259,7 +179,7 @@ namespace NTMiner {
                 if (!DevMode.IsDevMode) {
                     return Visibility.Collapsed;
                 }
-                if (VirtualRoot.IsMinerStudio) {
+                if (ClientAppType.IsMinerStudio) {
                     return Visibility.Visible;
                 }
                 return Visibility.Collapsed;
@@ -293,7 +213,10 @@ namespace NTMiner {
         #region IsAmd
         public static Visibility IsAmdGpuVisible {
             get {
-                if (NTMinerRoot.Instance.GpuSet.GpuType == GpuType.AMD) {
+                if (WpfUtil.IsInDesignMode) {
+                    return Visibility.Visible;
+                }
+                if (NTMinerContext.Instance.GpuSet.GpuType == GpuType.AMD) {
                     return Visibility.Visible;
                 }
                 return Visibility.Collapsed;
@@ -302,7 +225,10 @@ namespace NTMiner {
 
         public static bool IsAmdGpu {
             get {
-                return NTMinerRoot.Instance.GpuSet.GpuType == GpuType.AMD;
+                if (WpfUtil.IsInDesignMode) {
+                    return true;
+                }
+                return NTMinerContext.Instance.GpuSet.GpuType == GpuType.AMD;
             }
         }
         #endregion
@@ -310,79 +236,61 @@ namespace NTMiner {
         #region IsBrand
         public static bool IsPoolBrand {
             get {
-                return NTMinerRoot.IsPoolBrand;
+                return NTMinerContext.IsPoolBrand;
             }
         }
 
         public static Visibility IsPoolBrandVisible {
             get {
-                return NTMinerRoot.IsPoolBrand ? Visibility.Visible : Visibility.Collapsed;
+                return NTMinerContext.IsPoolBrand ? Visibility.Visible : Visibility.Collapsed;
             }
         }
 
         public static Visibility IsPoolBrandCollapsed {
-            get { return NTMinerRoot.IsPoolBrand ? Visibility.Collapsed : Visibility.Visible; }
+            get { return NTMinerContext.IsPoolBrand ? Visibility.Collapsed : Visibility.Visible; }
         }
 
         public static bool IsKernelBrand {
             get {
-                return NTMinerRoot.IsKernelBrand;
+                return NTMinerContext.IsKernelBrand;
             }
         }
 
         public static Visibility IsKernelBrandVisible {
             get {
-                return NTMinerRoot.IsKernelBrand ? Visibility.Visible : Visibility.Collapsed;
+                return NTMinerContext.IsKernelBrand ? Visibility.Visible : Visibility.Collapsed;
             }
         }
 
         public static Visibility IsKernelBrandCollapsed {
-            get { return NTMinerRoot.IsKernelBrand ? Visibility.Collapsed : Visibility.Visible; }
+            get { return NTMinerContext.IsKernelBrand ? Visibility.Collapsed : Visibility.Visible; }
         }
 
         public static bool IsBrandSpecified {
-            get { return NTMinerRoot.IsBrandSpecified; }
+            get { return NTMinerContext.IsBrandSpecified; }
         }
 
         public static Visibility IsBrandSpecifiedVisible {
             get {
-                return NTMinerRoot.IsBrandSpecified ? Visibility.Visible : Visibility.Collapsed;
+                return NTMinerContext.IsBrandSpecified ? Visibility.Visible : Visibility.Collapsed;
             }
         }
 
         public static Visibility IsBrandSpecifiedCollapsed {
-            get { return NTMinerRoot.IsBrandSpecified ? Visibility.Collapsed : Visibility.Visible; }
+            get { return NTMinerContext.IsBrandSpecified ? Visibility.Collapsed : Visibility.Visible; }
         }
         #endregion
 
         #region MainWindowHeight MainWindowWidth
         public static double MainWindowHeight {
             get {
-                if (SystemParameters.WorkArea.Size.Height >= 620) {
-                    return 620;
-                }
-                else if (SystemParameters.WorkArea.Size.Height >= 520) {
-                    return 520;
-                }
-                return 480;
+                return AppRoot.MainWindowHeight;
             }
         }
 
         public static double MainWindowWidth {
             get {
-                if (SystemParameters.WorkArea.Size.Width >= 1090) {
-                    return 1090;
-                }
-                else if (SystemParameters.WorkArea.Size.Width >= 1000) {
-                    return 1000;
-                }
-                else if (SystemParameters.WorkArea.Size.Width >= 860) {
-                    return 860;
-                }
-                else if (SystemParameters.WorkArea.Size.Width >= 800) {
-                    return 800;
-                }
-                return 640;
+                return AppRoot.MainWindowWidth;
             }
         }
         #endregion
@@ -390,36 +298,42 @@ namespace NTMiner {
         #region EnumItems
         public static IEnumerable<EnumItem<SupportedGpu>> SupportedGpuEnumItems {
             get {
-                return NTMinerRoot.SupportedGpuEnumItems;
+                return NTMinerContext.SupportedGpuEnumItems;
             }
         }
 
         public static IEnumerable<EnumItem<GpuType>> GpuTypeEnumItems {
             get {
-                return NTMinerRoot.GpuTypeEnumItems;
+                return NTMinerContext.GpuTypeEnumItems;
             }
         }
 
         public static IEnumerable<EnumItem<PublishStatus>> PublishStatusEnumItems {
             get {
-                return NTMinerRoot.PublishStatusEnumItems;
+                return NTMinerContext.PublishStatusEnumItems;
             }
         }
 
         public static IEnumerable<EnumItem<MineStatus>> MineStatusEnumItems {
             get {
-                return NTMinerRoot.MineStatusEnumItems;
+                return NTMinerContext.MineStatusEnumItems;
+            }
+        }
+
+        public static IEnumerable<EnumItem<UserStatus>> UserStatusEnumItems {
+            get {
+                return NTMinerContext.UserStatusEnumItems;
             }
         }
 
         public static IEnumerable<EnumItem<ServerMessageType>> ServerMessageTypeEnumItems {
             get {
-                return NTMinerRoot.ServerMessageTypeEnumItems;
+                return NTMinerContext.ServerMessageTypeEnumItems;
             }
         }
 
         public static IEnumerable<EnumItem<LocalMessageType>> LocalMessageTypeEnumItems {
-            get { return NTMinerRoot.LocalMessageTypeEnumItems; }
+            get { return NTMinerContext.LocalMessageTypeEnumItems; }
         }
         #endregion
 
@@ -432,10 +346,13 @@ namespace NTMiner {
 
         public static string CurrentVersion {
             get {
-                return EntryAssemblyInfo.CurrentVersion.ToString();
+                return EntryAssemblyInfo.CurrentVersionStr;
             }
         }
 
+        /// <summary>
+        /// 为了让IDE显式的引用计数为0该文件里其它地方直接使用<see cref="EntryAssemblyInfo.CurrentVersionTag"/>
+        /// </summary>
         public static string VersionTag {
             get {
                 return EntryAssemblyInfo.CurrentVersionTag;
@@ -444,7 +361,7 @@ namespace NTMiner {
 
         public static string VersionFullName {
             get {
-                return $"v{EntryAssemblyInfo.CurrentVersion}({VersionTag})";
+                return $"v{EntryAssemblyInfo.CurrentVersionStr}({EntryAssemblyInfo.CurrentVersionTag})";
             }
         }
         #endregion
@@ -452,41 +369,31 @@ namespace NTMiner {
         #region Gpu
         public static Version MinAmdDriverVersion {
             get {
-                if (WpfUtil.IsInDesignMode) {
-                    return new Version();
-                }
-                if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "MinAmdDriverVersion", out ISysDicItem dicItem)) {
-                    if (Version.TryParse(dicItem.Value, out Version version)) {
-                        return version;
-                    }
-                }
-                return new Version(17, 10, 2);
+                return AppRoot.MinAmdDriverVersion;
             }
         }
 
         public static Version MinNvidiaDriverVersion {
             get {
-                if (WpfUtil.IsInDesignMode) {
-                    return new Version();
-                }
-                if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "MinNvidiaDriverVersion", out ISysDicItem dicItem)) {
-                    if (Version.TryParse(dicItem.Value, out Version version)) {
-                        return version;
-                    }
-                }
-                return new Version(399, 24);
+                return AppRoot.MinNvidiaDriverVersion;
             }
         }
 
         public static string GpuSetInfo {
             get {
-                return NTMinerRoot.Instance.GpuSetInfo;
+                if (WpfUtil.IsInDesignMode) {
+                    return "p106-100 x 8";
+                }
+                return NTMinerContext.Instance.GpuSetInfo;
             }
         }
 
         public static string DriverVersion {
             get {
-                var gpuSet = NTMinerRoot.Instance.GpuSet;
+                if (WpfUtil.IsInDesignMode) {
+                    return "0.0";
+                }
+                var gpuSet = NTMinerContext.Instance.GpuSet;
                 if (gpuSet.GpuType == GpuType.NVIDIA) {
                     var cudaVersion = gpuSet.Properties.FirstOrDefault(a => a.Code == NTKeyword.CudaVersionSysDicCode);
                     if (cudaVersion != null) {
@@ -499,15 +406,18 @@ namespace NTMiner {
 
         public static SolidColorBrush DriverVersionColor {
             get {
-                var gpuSet = NTMinerRoot.Instance.GpuSet;
+                if (WpfUtil.IsInDesignMode) {
+                    return WpfUtil.RedBrush;
+                }
+                var gpuSet = NTMinerContext.Instance.GpuSet;
                 switch (gpuSet.GpuType) {
                     case GpuType.NVIDIA:
-                        if (gpuSet.DriverVersion < MinNvidiaDriverVersion) {
+                        if (gpuSet.DriverVersion < AppRoot.MinNvidiaDriverVersion) {
                             return WpfUtil.RedBrush;
                         }
                         break;
                     case GpuType.AMD:
-                        if (gpuSet.DriverVersion < MinAmdDriverVersion) {
+                        if (gpuSet.DriverVersion < AppRoot.MinAmdDriverVersion) {
                             return WpfUtil.RedBrush;
                         }
                         break;
@@ -518,16 +428,19 @@ namespace NTMiner {
 
         public static string DriverVersionToolTip {
             get {
-                var gpuSet = NTMinerRoot.Instance.GpuSet;
+                if (WpfUtil.IsInDesignMode) {
+                    return string.Empty;
+                }
+                var gpuSet = NTMinerContext.Instance.GpuSet;
                 bool isTooLow = false;
                 switch (gpuSet.GpuType) {
                     case GpuType.NVIDIA:
-                        if (gpuSet.DriverVersion < MinNvidiaDriverVersion) {
+                        if (gpuSet.DriverVersion < AppRoot.MinNvidiaDriverVersion) {
                             isTooLow = true;
                         }
                         break;
                     case GpuType.AMD:
-                        if (gpuSet.DriverVersion < MinAmdDriverVersion) {
+                        if (gpuSet.DriverVersion < AppRoot.MinAmdDriverVersion) {
                             isTooLow = true;
                         }
                         break;
@@ -547,18 +460,17 @@ namespace NTMiner {
         public static string WindowsEdition {
             get {
                 if (WpfUtil.IsInDesignMode) {
-                    return nameof(WindowsEdition);
+                    return "WindowsEdition";
                 }
                 return _windowsEdition.Value;
             }
         }
 
-        public const string LowWinMessage = "Windows版本较低,建议使用Win10系统";
         public static string WindowsEditionToolTip {
             get {
                 // Win7下WinDivert.sys文件签名问题
                 if (VirtualRoot.IsLTWin10) {
-                    return LowWinMessage;
+                    return AppRoot.LowWinMessage;
                 }
                 return "操作系统";
             }
@@ -577,9 +489,10 @@ namespace NTMiner {
             }
         }
 
+        // 因为虚拟内存修改后重启电脑才会生效所以这里用静态绑定没有问题
         public static string TotalVirtualMemoryGbText {
             get {
-                return (AppContext.Instance.VirtualMemorySetVm.TotalVirtualMemoryMb / 1024.0).ToString("f1") + "G";
+                return (VirtualRoot.DriveSet.OSVirtualMemoryMb / 1024.0).ToString("f1") + "G";
             }
         }
         #endregion
@@ -592,54 +505,17 @@ namespace NTMiner {
 
         });
 
+        public static ICommand ShowSignUpPage { get; private set; } = new DelegateCommand(() => {
+            VirtualRoot.Execute(new ShowSignUpPageCommand());
+        });
+
         public static ICommand ShowIcons { get; private set; } = new DelegateCommand(() => {
             Views.Ucs.Icons.ShowWindow();
         });
 
         #region OpenMinerClientFinder
         public static ICommand OpenMinerClientFinder { get; private set; } = new DelegateCommand(() => {
-            try {
-                RpcRoot.OfficialServer.FileUrlService.GetMinerClientFinderUrlAsync((downloadFileUrl, e) => {
-                    try {
-                        if (string.IsNullOrEmpty(downloadFileUrl)) {
-                            if (File.Exists(SpecialPath.MinerClientFinderFileFullName)) {
-                                Windows.Cmd.RunClose(SpecialPath.MinerClientFinderFileFullName, string.Empty);
-                            }
-                            return;
-                        }
-                        Uri uri = new Uri(downloadFileUrl);
-                        string localVersion = GetMinerClientFinderVersion();
-                        if (string.IsNullOrEmpty(localVersion) || !File.Exists(SpecialPath.MinerClientFinderFileFullName) || uri.AbsolutePath != localVersion) {
-                            VirtualRoot.Execute(new ShowFileDownloaderCommand(downloadFileUrl, "开源矿工更新器", (window, isSuccess, message, saveFileFullName) => {
-                                try {
-                                    if (isSuccess) {
-                                        File.Copy(saveFileFullName, SpecialPath.MinerClientFinderFileFullName, overwrite: true);
-                                        File.Delete(saveFileFullName);
-                                        SetMinerClientFinderVersion(uri.AbsolutePath);
-                                        window?.Close();
-                                        Windows.Cmd.RunClose(SpecialPath.MinerClientFinderFileFullName, string.Empty);
-                                    }
-                                    else {
-                                        VirtualRoot.ThisLocalError(nameof(AppStatic), message, toConsole: true);
-                                    }
-                                }
-                                catch (Exception ex) {
-                                    Logger.ErrorDebugLine(ex);
-                                }
-                            }));
-                        }
-                        else {
-                            Windows.Cmd.RunClose(SpecialPath.MinerClientFinderFileFullName, string.Empty);
-                        }
-                    }
-                    catch (Exception ex) {
-                        Logger.ErrorDebugLine(ex);
-                    }
-                });
-            }
-            catch (Exception ex) {
-                Logger.ErrorDebugLine(ex);
-            }
+            AppRoot.OpenMinerClientFinder();
         });
         #endregion
 
@@ -648,7 +524,7 @@ namespace NTMiner {
                 dir = dir.Replace(NTKeyword.TempDirParameterName, EntryAssemblyInfo.TempDirFullName);
             }
             else if (dir.StartsWith(NTKeyword.HomeDirParameterName)) {
-                dir = dir.Replace(NTKeyword.HomeDirParameterName, EntryAssemblyInfo.HomeDirFullName);
+                dir = dir.Replace(NTKeyword.HomeDirParameterName, HomePath.HomeDirFullName);
             }
             Process.Start(dir);
         });
@@ -662,31 +538,31 @@ namespace NTMiner {
 
         public static string ExportServerJsonMenuName {
             get {
-                return "导出" + EntryAssemblyInfo.ServerJsonFileName;
+                return "导出" + HomePath.ExportServerJsonFileName;
             }
         }
 
         public static ICommand ExportServerJson { get; private set; } = new DelegateCommand(() => {
             try {
-                NTMinerRoot.ExportServerVersionJson(EntryAssemblyInfo.ServerVersionJsonFileFullName);
-                VirtualRoot.Out.ShowSuccess($"{EntryAssemblyInfo.ServerJsonFileName}", header: "导出成功");
+                NTMinerContext.ExportServerVersionJson(HomePath.ServerVersionJsonFileFullName);
+                VirtualRoot.Out.ShowSuccess($"{HomePath.ExportServerJsonFileName}", header: "导出成功");
             }
             catch (Exception e) {
                 Logger.ErrorDebugLine(e);
             }
         });
 
-        public static string ServerJsonFileName {
+        public static string ExportServerJsonFileName {
             get {
-                return EntryAssemblyInfo.ServerJsonFileName;
+                return HomePath.ExportServerJsonFileName;
             }
         }
 
         public static ICommand SetServerJsonVersion { get; private set; } = new DelegateCommand(() => {
-            VirtualRoot.Execute(new ShowDialogWindowCommand(message: $"您确定刷新{EntryAssemblyInfo.ServerJsonFileName}吗?", title: "确认", onYes: () => {
+            VirtualRoot.Execute(new ShowDialogWindowCommand(message: $"您确定刷新{HomePath.ExportServerJsonFileName}吗?", title: "确认", onYes: () => {
                 try {
                     VirtualRoot.Execute(new SetServerAppSettingCommand(new AppSettingData {
-                        Key = EntryAssemblyInfo.ServerJsonFileName,
+                        Key = HomePath.ExportServerJsonFileName,
                         Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")
                     }));
                     VirtualRoot.Out.ShowSuccess($"刷新成功");
@@ -710,6 +586,10 @@ namespace NTMiner {
             VirtualRoot.Execute(new ShowUserPageCommand());
         });
 
+        public static ICommand ShowWsServerNodes { get; private set; } = new DelegateCommand(() => {
+            VirtualRoot.Execute(new ShowWsServerNodePageCommand());
+        });
+
         public static ICommand ShowOverClockDatas { get; private set; } = new DelegateCommand(() => {
             VirtualRoot.Execute(new ShowOverClockDataPageCommand());
         });
@@ -718,17 +598,29 @@ namespace NTMiner {
             VirtualRoot.Execute(new ShowNTMinerWalletPageCommand());
         });
 
-        public static ICommand ShowChartsWindow { get; private set; } = new DelegateCommand(() => {
-            VirtualRoot.Execute(new ShowChartsWindowCommand());
-        });
-
         public static ICommand ShowProperty { get; private set; } = new DelegateCommand(() => {
             VirtualRoot.Execute(new ShowPropertyCommand());
         });
 
         public static ICommand JoinQQGroup { get; private set; } = new DelegateCommand(() => {
             string url = "https://jq.qq.com/?_wv=1027&k=5ZPsuCk";
-            if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "QQGroupJoinUrl", out ISysDicItem dicItem)) {
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "QQGroupJoinUrl", out ISysDicItem dicItem)) {
+                url = dicItem.Value;
+            }
+            Process.Start(url);
+        });
+
+        public static ICommand ShareTutorial { get; private set; } = new DelegateCommand(() => {
+            string url = "https://www.cnblogs.com/ntminer/p/11502291.html";
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "ShareTutorial", out ISysDicItem dicItem)) {
+                url = dicItem.Value;
+            }
+            Process.Start(url);
+        });
+
+        public static ICommand SpeedTutorial { get; private set; } = new DelegateCommand(() => {
+            string url = "https://www.cnblogs.com/ntminer/p/11180273.html";
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "SpeedTutorial", out ISysDicItem dicItem)) {
                 url = dicItem.Value;
             }
             Process.Start(url);
@@ -789,7 +681,12 @@ namespace NTMiner {
             VirtualRoot.Execute(new ShowAboutPageCommand());
         });
         public static ICommand ShowSpeedChart { get; private set; } = new DelegateCommand(() => {
-            VirtualRoot.Execute(new ShowSpeedChartsCommand());
+            if (ClientAppType.IsMinerClient) {
+                VirtualRoot.Execute(new ShowSpeedChartsCommand());
+            }
+            else {
+                VirtualRoot.Execute(new ShowChartsWindowCommand());
+            }
         });
         public static ICommand ShowNTMinerUpdaterConfig { get; private set; } = new DelegateCommand(() => {
             VirtualRoot.Execute(new ShowNTMinerUpdaterConfigCommand());
@@ -802,65 +699,30 @@ namespace NTMiner {
         });
         public static ICommand ShowHelp { get; private set; } = new DelegateCommand(() => {
             string url = "http://ntminer.com/";
-            if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "HelpUrl", out ISysDicItem dicItem)) {
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "HelpUrl", out ISysDicItem dicItem)) {
                 url = dicItem.Value;
             }
             Process.Start(url);
         });
         public static ICommand ShowMinerClients { get; private set; } = new DelegateCommand(() => {
-            VirtualRoot.Execute(new ShowMinerClientsWindowCommand());
+            VirtualRoot.Execute(new ShowMinerClientsWindowCommand(isToggle: false));
         });
         public static ICommand ShowCalcConfig { get; private set; } = new DelegateCommand(() => {
             VirtualRoot.Execute(new ShowCalcConfigCommand());
         });
         public static ICommand ShowHomeDir { get; private set; } = new DelegateCommand(() => {
-            Process.Start(EntryAssemblyInfo.HomeDirFullName);
+            Process.Start(HomePath.HomeDirFullName);
         });
         public static ICommand OpenLocalLiteDb { get; private set; } = new DelegateCommand(() => {
-            OpenLiteDb(EntryAssemblyInfo.LocalDbFileFullName);
+            AppRoot.OpenLiteDb(HomePath.LocalDbFileFullName);
         });
         public static ICommand OpenServerLiteDb { get; private set; } = new DelegateCommand(() => {
-            OpenLiteDb(EntryAssemblyInfo.ServerDbFileFullName);
+            AppRoot.OpenLiteDb(HomePath.ServerDbFileFullName);
         });
 
-        #region private method OpenLiteDb
-        private static void OpenLiteDb(string dbFileFullName) {
-            string liteDbExplorerDir = Path.Combine(SpecialPath.ToolsDirFullName, "LiteDBExplorerPortable");
-            string liteDbExplorerFileFullName = Path.Combine(liteDbExplorerDir, "LiteDbExplorer.exe");
-            if (!Directory.Exists(liteDbExplorerDir)) {
-                Directory.CreateDirectory(liteDbExplorerDir);
-            }
-            if (!File.Exists(liteDbExplorerFileFullName)) {
-                RpcRoot.OfficialServer.FileUrlService.GetLiteDbExplorerUrlAsync((downloadFileUrl, e) => {
-                    if (string.IsNullOrEmpty(downloadFileUrl)) {
-                        return;
-                    }
-                    VirtualRoot.Execute(new ShowFileDownloaderCommand(downloadFileUrl, "LiteDB数据库管理工具", (window, isSuccess, message, saveFileFullName) => {
-                        if (isSuccess) {
-                            ZipUtil.DecompressZipFile(saveFileFullName, liteDbExplorerDir);
-                            File.Delete(saveFileFullName);
-                            window?.Close();
-                            Windows.Cmd.RunClose(liteDbExplorerFileFullName, dbFileFullName);
-                        }
-                    }));
-                });
-            }
-            else {
-                Windows.Cmd.RunClose(liteDbExplorerFileFullName, dbFileFullName);
-            }
-        }
-        #endregion
-
         public static string NppPackageUrl {
             get {
-                const string url = "https://minerjson.oss-cn-beijing.aliyuncs.com/npp.zip";
-                if (WpfUtil.IsDevMode) {
-                    return url;
-                }
-                if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem("Tool", "npp", out ISysDicItem dicItem)) {
-                    return dicItem.Value;
-                }
-                return url;
+                return AppRoot.NppPackageUrl;
             }
         }
 
@@ -872,13 +734,9 @@ namespace NTMiner {
             VirtualRoot.Execute(new ShowLocalIpsCommand());
         });
 
-        public static ICommand ShowEthNoDevFee { get; private set; } = new DelegateCommand(() => {
-            VirtualRoot.Execute(new ShowEthNoDevFeeCommand());
-        });
-
         public static ICommand OpenOfficialSite { get; private set; } = new DelegateCommand(() => {
             string url = "http://ntminer.com/";
-            if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "HomePageUrl", out ISysDicItem dicItem)) {
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "HomePageUrl", out ISysDicItem dicItem)) {
                 url = dicItem.Value;
             }
             Process.Start(url);
@@ -889,7 +747,7 @@ namespace NTMiner {
                 if (WpfUtil.IsInDesignMode) {
                     return string.Empty;
                 }
-                if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "QQGroup", out ISysDicItem dicItem)) {
+                if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "QQGroup", out ISysDicItem dicItem)) {
                     return dicItem.Value;
                 }
                 return "863725136";
@@ -902,7 +760,7 @@ namespace NTMiner {
                 if (WpfUtil.IsDevMode) {
                     return txt;
                 }
-                if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "HomePageUrl", out ISysDicItem dicItem) && !string.IsNullOrEmpty(dicItem.Value)) {
+                if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "HomePageUrl", out ISysDicItem dicItem) && !string.IsNullOrEmpty(dicItem.Value)) {
                     if (dicItem.Value.StartsWith("https://")) {
                         return dicItem.Value.Substring("https://".Length);
                     }
@@ -920,7 +778,7 @@ namespace NTMiner {
                 if (WpfUtil.IsDevMode) {
                     return txt;
                 }
-                if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "AppMinerName", out ISysDicItem dicItem)) {
+                if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "AppMinerName", out ISysDicItem dicItem)) {
                     return dicItem.Value;
                 }
                 return txt;
@@ -933,7 +791,7 @@ namespace NTMiner {
                 if (WpfUtil.IsDevMode) {
                     return txt;
                 }
-                if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "AppMinerName", out ISysDicItem dicItem)) {
+                if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "AppMinerName", out ISysDicItem dicItem)) {
                     return " - " + dicItem.Description;
                 }
                 return txt;
@@ -946,7 +804,7 @@ namespace NTMiner {
                 if (WpfUtil.IsDevMode) {
                     return txt;
                 }
-                if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "AppMinerIntro", out ISysDicItem dicItem)) {
+                if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "AppMinerIntro", out ISysDicItem dicItem)) {
                     return dicItem.Value;
                 }
                 return txt;
@@ -955,15 +813,15 @@ namespace NTMiner {
 
         public static ICommand BusinessModel { get; private set; } = new DelegateCommand(() => {
             string url = "https://www.cnblogs.com/ntminer/p/11162986.html";
-            if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "BusinessModelUrl", out ISysDicItem dicItem)) {
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "BusinessModelUrl", out ISysDicItem dicItem)) {
                 url = dicItem.Value;
             }
             Process.Start(url);
         });
 
         public static ICommand OpenGithub { get; private set; } = new DelegateCommand(() => {
-            string url = "https://github.com/ntminer/ntminer";
-            if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "GithubUrl", out ISysDicItem dicItem)) {
+            string url = "https://github.com/ntminer/NtMiner";
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "GithubUrl", out ISysDicItem dicItem)) {
                 url = dicItem.Value;
             }
             Process.Start(url);
@@ -971,22 +829,34 @@ namespace NTMiner {
 
         public static ICommand OpenLGPL { get; private set; } = new DelegateCommand(() => {
             string url = "https://minerjson.oss-cn-beijing.aliyuncs.com/LGPL.png";
-            if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "LGPL", out ISysDicItem dicItem)) {
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "LGPL", out ISysDicItem dicItem)) {
                 url = dicItem.Value;
             }
             Process.Start(url);
         });
 
         public static ICommand OpenDiscussSite { get; private set; } = new DelegateCommand(() => {
-            string url = "https://github.com/ntminer/ntminer/issues";
-            if (NTMinerRoot.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "DiscussUrl", out ISysDicItem dicItem)) {
+            string url = "https://github.com/ntminer/NtMiner/issues";
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "DiscussUrl", out ISysDicItem dicItem)) {
+                url = dicItem.Value;
+            }
+            Process.Start(url);
+        });
+
+        public static ICommand MinerStudioTutorial { get; private set; } = new DelegateCommand(() => {
+            string url = "https://www.cnblogs.com/ntminer/p/11923722.html";
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "MinerStudioTutorial", out ISysDicItem dicItem)) {
                 url = dicItem.Value;
             }
             Process.Start(url);
         });
 
         public static ICommand DownloadMinerStudio { get; private set; } = new DelegateCommand(() => {
-            Process.Start("https://www.cnblogs.com/ntminer/p/11923722.html");
+            string url = "https://www.cnblogs.com/ntminer/p/11923722.html";
+            if (NTMinerContext.Instance.ServerContext.SysDicItemSet.TryGetDicItem(NTKeyword.ThisSystemSysDicCode, "DownloadMinerStudio", out ISysDicItem dicItem)) {
+                url = dicItem.Value;
+            }
+            Process.Start(url);
         });
 
         public static ICommand ShowQQGroupQrCode { get; private set; } = new DelegateCommand(() => {

+ 9 - 8
src/AppModels/ExtendedNotifyIcon.cs

@@ -1,17 +1,18 @@
 using System;
 using System.Drawing;
+using System.Reflection;
 using System.Windows.Forms;
 
 namespace NTMiner {
     public class ExtendedNotifyIcon {
         public static ExtendedNotifyIcon Create(string text, bool isMinerStudio) {
             string url;
-            if (isMinerStudio) {
-                url = "pack://application:,,,/MinerStudio;component/logo.ico";
-            }
-            else {
-                url = "pack://application:,,,/NTMiner;component/logo.ico";
+            Assembly mainAssembly = Assembly.GetEntryAssembly();
+            if (mainAssembly == null) {
+                throw new InvalidProgramException();
             }
+            var name = mainAssembly.GetName().Name;
+            url = $"pack://application:,,,/{name};component/logo.ico";
             Icon icon = new Icon(System.Windows.Application.GetResourceStream(new Uri(url)).Stream);
             return new ExtendedNotifyIcon(icon, text, isMinerStudio);
         }
@@ -22,7 +23,7 @@ namespace NTMiner {
             _isMinerStudio = isMinerStudio;
             _targetNotifyIcon = new NotifyIcon {
                 Icon = icon,
-                Visible = isMinerStudio || NTMinerRoot.Instance.MinerProfile.IsShowNotifyIcon,
+                Visible = isMinerStudio || NTMinerContext.Instance.MinerProfile.IsShowNotifyIcon,
                 Text = text,
                 ContextMenuStrip = new ContextMenuStrip {
                     BackColor = Color.White,
@@ -30,7 +31,7 @@ namespace NTMiner {
                 }
             };
             _targetNotifyIcon.ContextMenuStrip.Items.Add("退出" + text, null, (sender, e)=> {
-                AppStatic.AppExit.Execute(null);
+                VirtualRoot.Execute(new CloseNTMinerCommand("手动操作"));
             });
             _targetNotifyIcon.MouseDown += (object sender, MouseEventArgs e) => {
                 if (e.Button == MouseButtons.Left) {
@@ -43,7 +44,7 @@ namespace NTMiner {
         }
 
         public void RefreshIcon() {
-            _targetNotifyIcon.Visible = _isMinerStudio || NTMinerRoot.Instance.MinerProfile.IsShowNotifyIcon;
+            _targetNotifyIcon.Visible = _isMinerStudio || NTMinerContext.Instance.MinerProfile.IsShowNotifyIcon;
         }
     }
 }

+ 88 - 257
src/AppModels/Messages.cs

@@ -1,5 +1,7 @@
-using NTMiner.Hub;
-using NTMiner.Core;
+using NTMiner.Core;
+using NTMiner.Core.MinerClient;
+using NTMiner.Hub;
+using NTMiner.MinerStudio.Vms;
 using NTMiner.Vms;
 using System;
 using System.Windows;
@@ -16,9 +18,18 @@ namespace NTMiner {
         public Action Callback { get; private set; }
     }
 
+    [MessageType(description: "释放并执行挖矿端嵌入的工具")]
+    public class MinerClientActionCommand : Cmd {
+        public MinerClientActionCommand(MinerClientActionType actionType) {
+            this.ActionType = actionType;
+        }
+
+        public MinerClientActionType ActionType { get; private set; }
+    }
+
     [MessageType(description: "启用windows远程桌面")]
-    public class EnableWindowsRemoteDesktopCommand : Cmd {
-        public EnableWindowsRemoteDesktopCommand() {
+    public class EnableRemoteDesktopCommand : Cmd {
+        public EnableRemoteDesktopCommand() {
         }
     }
 
@@ -58,30 +69,6 @@ namespace NTMiner {
         }
     }
 
-    [MessageType(description: "打开用户列表页")]
-    public class ShowUserPageCommand : Cmd {
-        public ShowUserPageCommand() {
-        }
-    }
-
-    [MessageType(description: "打开超频菜谱列表页")]
-    public class ShowOverClockDataPageCommand : Cmd {
-        public ShowOverClockDataPageCommand() {
-        }
-    }
-
-    [MessageType(description: "打开NTMiner钱包列表页")]
-    public class ShowNTMinerWalletPageCommand : Cmd {
-        public ShowNTMinerWalletPageCommand() {
-        }
-    }
-
-    [MessageType(description: "打开群控算力图表窗口")]
-    public class ShowChartsWindowCommand : Cmd {
-        public ShowChartsWindowCommand() {
-        }
-    }
-
     [MessageType(description: "打开属性页")]
     public class ShowPropertyCommand : Cmd {
         public ShowPropertyCommand() {
@@ -128,24 +115,12 @@ namespace NTMiner {
         public string TabType { get; private set; }
     }
 
-    [MessageType(description: "打开列显页面")]
-    public class ShowColumnsShowPageCommand : Cmd {
-        public ShowColumnsShowPageCommand() {
-        }
-    }
-
     [MessageType(description: "打开关于页面")]
     public class ShowAboutPageCommand : Cmd {
         public ShowAboutPageCommand() {
         }
     }
 
-    [MessageType(description: "打开升级器设置页面")]
-    public class ShowNTMinerUpdaterConfigCommand : Cmd {
-        public ShowNTMinerUpdaterConfigCommand() {
-        }
-    }
-
     [MessageType(description: "打开矿机雷达程序设置页面")]
     public class ShowMinerClientFinderConfigCommand : Cmd {
         public ShowMinerClientFinderConfigCommand() {
@@ -168,8 +143,8 @@ namespace NTMiner {
     }
 
     [MessageType(description: "打开环境变量编辑界面")]
-    public class EnvironmentVariableEditCommand : Cmd {
-        public EnvironmentVariableEditCommand(CoinKernelViewModel coinKernelVm, EnvironmentVariable environmentVariable) {
+    public class EditEnvironmentVariableCommand : Cmd {
+        public EditEnvironmentVariableCommand(CoinKernelViewModel coinKernelVm, EnvironmentVariable environmentVariable) {
             this.CoinKernelVm = coinKernelVm;
             this.EnvironmentVariable = environmentVariable;
         }
@@ -179,8 +154,8 @@ namespace NTMiner {
     }
 
     [MessageType(description: "打开内核输入片段编辑界面")]
-    public class InputSegmentEditCommand : Cmd {
-        public InputSegmentEditCommand(CoinKernelViewModel coinKernelVm, InputSegmentViewModel segment) {
+    public class EditInputSegmentCommand : Cmd {
+        public EditInputSegmentCommand(CoinKernelViewModel coinKernelVm, InputSegmentViewModel segment) {
             this.CoinKernelVm = coinKernelVm;
             this.Segment = segment;
         }
@@ -189,37 +164,26 @@ namespace NTMiner {
         public InputSegmentViewModel Segment { get; private set; }
     }
 
-    [MessageType(description: "打开币种级内核编辑界面")]
-    public class CoinKernelEditCommand : Cmd {
-        public CoinKernelEditCommand(FormType formType, CoinKernelViewModel source) {
+    public abstract class EditCommand<T> : Cmd {
+        public EditCommand(FormType formType, T source) {
             this.FormType = formType;
             this.Source = source;
         }
 
         public FormType FormType { get; private set; }
-        public CoinKernelViewModel Source { get; private set; }
+        public T Source { get; private set; }
     }
 
-    [MessageType(description: "打开币种编辑界面")]
-    public class CoinEditCommand : Cmd {
-        public CoinEditCommand(FormType formType, CoinViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "打开币种级内核编辑界面")]
+    public class EditCoinKernelCommand : EditCommand<CoinKernelViewModel> {
+        public EditCoinKernelCommand(FormType formType, CoinKernelViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public CoinViewModel Source { get; private set; }
     }
 
-    [MessageType(description: "打开列显编辑界面")]
-    public class ColumnsShowEditCommand : Cmd {
-        public ColumnsShowEditCommand(FormType formType, ColumnsShowViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "打开币种编辑界面")]
+    public class EditCoinCommand : EditCommand<CoinViewModel> {
+        public EditCoinCommand(FormType formType, CoinViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public ColumnsShowViewModel Source { get; private set; }
     }
 
     [MessageType(description: "打开算力图界面")]
@@ -232,14 +196,9 @@ namespace NTMiner {
     }
 
     [MessageType(description: "打开币组编辑界面")]
-    public class GroupEditCommand : Cmd {
-        public GroupEditCommand(FormType formType, GroupViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    public class EditGroupCommand : EditCommand<GroupViewModel> {
+        public EditGroupCommand(FormType formType, GroupViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public GroupViewModel Source { get; private set; }
     }
 
     [MessageType(description: "打开文件书写器列表页")]
@@ -249,14 +208,9 @@ namespace NTMiner {
     }
 
     [MessageType(description: "打开文件书写器编辑界面")]
-    public class FileWriterEditCommand : Cmd {
-        public FileWriterEditCommand(FormType formType, FileWriterViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    public class EditFileWriterCommand : EditCommand<FileWriterViewModel> {
+        public EditFileWriterCommand(FormType formType, FileWriterViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public FileWriterViewModel Source { get; private set; }
     }
 
     [MessageType(description: "打开命令行片段书写器列表页")]
@@ -266,69 +220,39 @@ namespace NTMiner {
     }
 
     [MessageType(description: "打开命令行片段书写器编辑界面")]
-    public class FragmentWriterEditCommand : Cmd {
-        public FragmentWriterEditCommand(FormType formType, FragmentWriterViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    public class EditFragmentWriterCommand : EditCommand<FragmentWriterViewModel> {
+        public EditFragmentWriterCommand(FormType formType, FragmentWriterViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public FragmentWriterViewModel Source { get; private set; }
     }
 
     [MessageType(description: "打开服务器消息编辑界面")]
-    public class ServerMessageEditCommand : Cmd {
-        public ServerMessageEditCommand(FormType formType, ServerMessageViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    public class EditServerMessageCommand : EditCommand<ServerMessageViewModel> {
+        public EditServerMessageCommand(FormType formType, ServerMessageViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public ServerMessageViewModel Source { get; private set; }
     }
 
     [MessageType(description: "打开内核输入编辑界面")]
-    public class KernelInputEditCommand : Cmd {
-        public KernelInputEditCommand(FormType formType, KernelInputViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    public class EditKernelInputCommand : EditCommand<KernelInputViewModel> {
+        public EditKernelInputCommand(FormType formType, KernelInputViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public KernelInputViewModel Source { get; private set; }
     }
 
     [MessageType(description: "打开内核输出关键字编辑界面")]
-    public class KernelOutputKeywordEditCommand : Cmd {
-        public KernelOutputKeywordEditCommand(FormType formType, KernelOutputKeywordViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    public class EditKernelOutputKeywordCommand : EditCommand<KernelOutputKeywordViewModel> {
+        public EditKernelOutputKeywordCommand(FormType formType, KernelOutputKeywordViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public KernelOutputKeywordViewModel Source { get; private set; }
     }
 
     [MessageType(description: "打开内核输出翻译器编辑界面")]
-    public class KernelOutputTranslaterEditCommand : Cmd {
-        public KernelOutputTranslaterEditCommand(FormType formType, KernelOutputTranslaterViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    public class EditKernelOutputTranslaterCommand : EditCommand<KernelOutputTranslaterViewModel> {
+        public EditKernelOutputTranslaterCommand(FormType formType, KernelOutputTranslaterViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public KernelOutputTranslaterViewModel Source { get; private set; }
     }
 
     [MessageType(description: "打开内核输出编辑界面")]
-    public class KernelOutputEditCommand : Cmd {
-        public KernelOutputEditCommand(FormType formType, KernelOutputViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    public class EditKernelOutputCommand : EditCommand<KernelOutputViewModel> {
+        public EditKernelOutputCommand(FormType formType, KernelOutputViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public KernelOutputViewModel Source { get; private set; }
     }
 
     [MessageType(description: "打开内核包窗口")]
@@ -338,28 +262,8 @@ namespace NTMiner {
     }
 
     [MessageType(description: "打开内核编辑界面")]
-    public class KernelEditCommand : Cmd {
-        public KernelEditCommand(FormType formType, KernelViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
-        }
-
-        public FormType FormType { get; private set; }
-        public KernelViewModel Source { get; private set; }
-    }
-
-    [MessageType(description: "打开挖矿端远程设置界面")]
-    public class ShowMinerClientSettingCommand : Cmd {
-        public ShowMinerClientSettingCommand(MinerClientSettingViewModel vm) {
-            this.Vm = vm;
-        }
-
-        public MinerClientSettingViewModel Vm { get; private set; }
-    }
-
-    [MessageType(description: "打开群控矿机列表页")]
-    public class ShowMinerClientsWindowCommand : Cmd {
-        public ShowMinerClientsWindowCommand() {
+    public class EditKernelCommand : EditCommand<KernelViewModel> {
+        public EditKernelCommand(FormType formType, KernelViewModel source) : base(formType, source) {
         }
     }
 
@@ -394,11 +298,6 @@ namespace NTMiner {
         public CoinViewModel CoinVm { get; private set; }
     }
 
-    [MessageType(description: "打开ETH反抽水配置页")]
-    public class ShowEthNoDevFeeCommand : Cmd {
-        public ShowEthNoDevFeeCommand() { }
-    }
-
     [MessageType(description: "打开QQ群二维码")]
     public class ShowQQGroupQrCodeCommand : Cmd {
         public ShowQQGroupQrCodeCommand() {
@@ -425,154 +324,86 @@ namespace NTMiner {
         public Action OnNo { get; private set; }
     }
 
-    [MessageType(description: "打开作群控名设置界面")]
-    public class ShowMinerNamesSeterCommand : Cmd {
-        public ShowMinerNamesSeterCommand(MinerNamesSeterViewModel vm) {
-            this.Vm = vm;
-        }
-
-        public MinerNamesSeterViewModel Vm { get; private set; }
-    }
-
-    [MessageType(description: "打开群控超频界面")]
-    public class ShowGpuProfilesPageCommand : Cmd {
-        public ShowGpuProfilesPageCommand(MinerClientsWindowViewModel minerClientsWindowVm) {
-            this.MinerClientsWindowVm = minerClientsWindowVm;
+    [MessageType(description: "打开内核包编辑界面")]
+    public class EditPackageCommand : EditCommand<PackageViewModel> {
+        public EditPackageCommand(FormType formType, PackageViewModel source) : base(formType, source) {
         }
-
-        public MinerClientsWindowViewModel MinerClientsWindowVm { get; private set; }
     }
 
-    [MessageType(description: "打开添加矿机界面")]
-    public class ShowMinerClientAddCommand : Cmd {
-        public ShowMinerClientAddCommand() {
+    [MessageType(description: "打开矿池级内核编辑界面")]
+    public class EditPoolKernelCommand : EditCommand<PoolKernelViewModel> {
+        public EditPoolKernelCommand(FormType formType, PoolKernelViewModel source) : base(formType, source) {
         }
     }
 
-    [MessageType(description: "打开矿工组编辑界面")]
-    public class MinerGroupEditCommand : Cmd {
-        public MinerGroupEditCommand(FormType formType, MinerGroupViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "打开矿池编辑界面")]
+    public class EditPoolCommand : EditCommand<PoolViewModel> {
+        public EditPoolCommand(FormType formType, PoolViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public MinerGroupViewModel Source { get; private set; }
     }
 
-    [MessageType(description: "打开NTMiner钱包编辑界面")]
-    public class NTMinerWalletEditCommand : Cmd {
-        public NTMinerWalletEditCommand(FormType formType, NTMinerWalletViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "打开字典项编辑界面")]
+    public class EditSysDicItemCommand : EditCommand<SysDicItemViewModel> {
+        public EditSysDicItemCommand(FormType formType, SysDicItemViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public NTMinerWalletViewModel Source { get; private set; }
     }
 
-    [MessageType(description: "打开作业编辑界面")]
-    public class MineWorkEditCommand : Cmd {
-        public MineWorkEditCommand(FormType formType, MineWorkViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "打开字典编辑界面")]
+    public class EditSysDicCommand : EditCommand<SysDicViewModel> {
+        public EditSysDicCommand(FormType formType, SysDicViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public MineWorkViewModel Source { get; private set; }
     }
 
-    [MessageType(description: "打开超频菜谱编辑界面")]
-    public class OverClockDataEditCommand : Cmd {
-        public OverClockDataEditCommand(FormType formType, OverClockDataViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "打开内核输出关键字列表页")]
+    public class ShowKernelOutputKeywordsCommand : Cmd {
+        public ShowKernelOutputKeywordsCommand() {
         }
-
-        public FormType FormType { get; private set; }
-        public OverClockDataViewModel Source { get; private set; }
     }
 
-    [MessageType(description: "打开内核包编辑界面")]
-    public class PackageEditCommand : Cmd {
-        public PackageEditCommand(FormType formType, PackageViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
-        }
-
-        public FormType FormType { get; private set; }
-        public PackageViewModel Source { get; private set; }
+    [MessageType(description: "打开用户注册页")]
+    public class ShowSignUpPageCommand : Cmd {
+        public ShowSignUpPageCommand() { }
     }
 
-    [MessageType(description: "打开矿池级内核编辑界面")]
-    public class PoolKernelEditCommand : Cmd {
-        public PoolKernelEditCommand(FormType formType, PoolKernelViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "打开钱包地址编辑界面")]
+    public class EditWalletCommand : EditCommand<WalletViewModel> {
+        public EditWalletCommand(FormType formType, WalletViewModel source) : base(formType, source) {
         }
-
-        public FormType FormType { get; private set; }
-        public PoolKernelViewModel Source { get; private set; }
     }
 
-    [MessageType(description: "打开矿池编辑界面")]
-    public class PoolEditCommand : Cmd {
-        public PoolEditCommand(FormType formType, PoolViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "添加了币种后")]
+    public class CoinVmAddedEvent : VmEventBase<CoinAddedEvent> {
+        public CoinVmAddedEvent(CoinAddedEvent evt) : base(evt) {
         }
-
-        public FormType FormType { get; private set; }
-        public PoolViewModel Source { get; private set; }
     }
 
-    [MessageType(description: "打开字典项编辑界面")]
-    public class SysDicItemEditCommand : Cmd {
-        public SysDicItemEditCommand(FormType formType, SysDicItemViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "移除了币种后")]
+    public class CoinVmRemovedEvent : VmEventBase<CoinRemovedEvent> {
+        public CoinVmRemovedEvent(CoinRemovedEvent evt) : base(evt) {
         }
-
-        public FormType FormType { get; private set; }
-        public SysDicItemViewModel Source { get; private set; }
     }
 
-    [MessageType(description: "打开字典编辑界面")]
-    public class SysDicEditCommand : Cmd {
-        public SysDicEditCommand(FormType formType, SysDicViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "添加了钱包后")]
+    public class WalletVmAddedEvent : VmEventBase<WalletAddedEvent> {
+        public WalletVmAddedEvent(WalletAddedEvent evt) : base(evt) {
         }
-
-        public FormType FormType { get; private set; }
-        public SysDicViewModel Source { get; private set; }
     }
 
-    [MessageType(description: "打开内核输出关键字列表页")]
-    public class ShowKernelOutputKeywordsCommand : Cmd {
-        public ShowKernelOutputKeywordsCommand() {
+    [MessageType(description: "移除了钱包后")]
+    public class WalletVmRemovedEvent : VmEventBase<WalletRemovedEvent> {
+        public WalletVmRemovedEvent(WalletRemovedEvent evt) : base(evt) {
         }
     }
 
-    [MessageType(description: "打开用户编辑界面")]
-    public class UserEditCommand : Cmd {
-        public UserEditCommand(FormType formType, UserViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "添加了币种级内核后")]
+    public class CoinKernelVmAddedEvent : VmEventBase<CoinKernelAddedEvent> {
+        public CoinKernelVmAddedEvent(CoinKernelAddedEvent evt) : base(evt) {
         }
-
-        public FormType FormType { get; private set; }
-        public UserViewModel Source { get; private set; }
     }
 
-    [MessageType(description: "打开钱包地址编辑界面")]
-    public class WalletEditCommand : Cmd {
-        public WalletEditCommand(FormType formType, WalletViewModel source) {
-            this.FormType = formType;
-            this.Source = source;
+    [MessageType(description: "移除了币种级内核后")]
+    public class CoinKernelVmRemovedEvent : VmEventBase<CoinKernelRemovedEvent> {
+        public CoinKernelVmRemovedEvent(CoinKernelRemovedEvent evt) : base(evt) {
         }
-
-        public FormType FormType { get; private set; }
-        public WalletViewModel Source { get; private set; }
     }
 }

+ 108 - 0
src/AppModels/MinerStudio/EmptyMinerStudioService.cs

@@ -0,0 +1,108 @@
+using NTMiner.Core.MinerClient;
+using NTMiner.Core.MinerServer;
+using System;
+using System.Collections.Generic;
+
+namespace NTMiner.MinerStudio {
+    public class EmptyMinerStudioService : ILocalMinerStudioService {
+        public static readonly EmptyMinerStudioService Instance = new EmptyMinerStudioService();
+
+        private EmptyMinerStudioService() { }
+
+        public void AddClientsAsync(List<string> clientIps, Action<ResponseBase, Exception> callback) {
+            // 什么也不做
+        }
+
+        public void GetLatestSnapshotsAsync(int limit, Action<GetCoinSnapshotsResponse, Exception> callback) {
+            // 什么也不做
+        }
+
+        public void QueryClientsAsync(QueryClientsRequest query, Action<QueryClientsResponse, Exception> callback) {
+            // 什么也不做
+        }
+
+        public void RefreshClientsAsync(List<string> objectIds, Action<DataResponse<List<ClientData>>, Exception> callback) {
+            // 什么也不做
+        }
+
+        public void RemoveClientsAsync(List<string> objectIds, Action<ResponseBase, Exception> callback) {
+            // 什么也不做
+        }
+
+        public void EnableRemoteDesktopAsync(IMinerData client) {
+            // 什么也不做
+        }
+
+        public void BlockWAUAsync(IMinerData client) {
+            // 什么也不做
+        }
+
+        public void AtikmdagPatcherAsync(IMinerData client) {
+            // 什么也不做
+        }
+
+        public void SwitchRadeonGpuAsync(IMinerData client, bool on) {
+            // 什么也不做
+        }
+
+        public void RestartWindowsAsync(IMinerData client) {
+            // 什么也不做
+        }
+
+        public void SetAutoBootStartAsync(IMinerData client, SetAutoBootStartRequest request) {
+            // 什么也不做
+        }
+
+        public void ShutdownWindowsAsync(IMinerData client) {
+            // 什么也不做
+        }
+
+        public void StartMineAsync(IMinerData client, Guid workId) {
+            // 什么也不做
+        }
+
+        public void StopMineAsync(IMinerData client) {
+            // 什么也不做
+        }
+
+        public void UpdateClientAsync(string objectId, string propertyName, object value, Action<ResponseBase, Exception> callback) {
+            // 什么也不做
+        }
+
+        public void UpdateClientsAsync(string propertyName, Dictionary<string, object> values, Action<ResponseBase, Exception> callback) {
+            // 什么也不做
+        }
+
+        public void UpgradeNTMinerAsync(IMinerData client, string ntminerFileName) {
+            // 什么也不做
+        }
+
+        public void GetDrivesAsync(IMinerData client) {
+            // 什么也不做
+        }
+
+        public void SetVirtualMemoryAsync(IMinerData client, Dictionary<string, int> data) {
+            // 什么也不做
+        }
+
+        public void GetLocalIpsAsync(IMinerData client) {
+            // 什么也不做
+        }
+
+        public void SetLocalIpsAsync(IMinerData client, List<LocalIpInput> data) {
+            // 什么也不做
+        }
+
+        public void GetOperationResultsAsync(IMinerData client, long afterTime) {
+            // 什么也不做
+        }
+
+        public void GetGpuProfilesJsonAsync(IMinerData client) {
+            // 什么也不做
+        }
+
+        public void SaveGpuProfilesJsonAsync(IMinerData client, string json) {
+            // 什么也不做
+        }
+    }
+}

+ 8 - 0
src/AppModels/MinerStudio/ILocalMinerStudioService.cs

@@ -0,0 +1,8 @@
+using System;
+using System.Collections.Generic;
+
+namespace NTMiner.MinerStudio {
+    public interface ILocalMinerStudioService : IMinerStudioService {
+        void AddClientsAsync(List<string> clientIps, Action<ResponseBase, Exception> callback);
+    }
+}

+ 32 - 0
src/AppModels/MinerStudio/IMinerStudioService.cs

@@ -0,0 +1,32 @@
+using NTMiner.Core.MinerClient;
+using NTMiner.Core.MinerServer;
+using System;
+using System.Collections.Generic;
+
+namespace NTMiner.MinerStudio {
+    public interface IMinerStudioService {
+        void GetLatestSnapshotsAsync(int limit, Action<GetCoinSnapshotsResponse, Exception> callback);
+        void QueryClientsAsync(QueryClientsRequest query, Action<QueryClientsResponse, Exception> callback);
+        void UpdateClientAsync(string objectId, string propertyName, object value, Action<ResponseBase, Exception> callback);
+        void UpdateClientsAsync(string propertyName, Dictionary<string, object> values, Action<ResponseBase, Exception> callback);
+        void RemoveClientsAsync(List<string> objectIds, Action<ResponseBase, Exception> callback);
+
+        void EnableRemoteDesktopAsync(IMinerData client);
+        void BlockWAUAsync(IMinerData client);
+        void AtikmdagPatcherAsync(IMinerData client);
+        void SwitchRadeonGpuAsync(IMinerData client, bool on);
+        void RestartWindowsAsync(IMinerData client);
+        void ShutdownWindowsAsync(IMinerData client);
+        void SetAutoBootStartAsync(IMinerData client, SetAutoBootStartRequest request);
+        void StartMineAsync(IMinerData client, Guid workId);
+        void StopMineAsync(IMinerData client);
+        void UpgradeNTMinerAsync(IMinerData client, string ntminerFileName);
+        void GetDrivesAsync(IMinerData client);
+        void SetVirtualMemoryAsync(IMinerData client, Dictionary<string, int> data);
+        void GetLocalIpsAsync(IMinerData client);
+        void SetLocalIpsAsync(IMinerData client, List<LocalIpInput> data);
+        void GetOperationResultsAsync(IMinerData client, long afterTime);
+        void GetGpuProfilesJsonAsync(IMinerData client);
+        void SaveGpuProfilesJsonAsync(IMinerData client, string json);
+    }
+}

+ 4 - 0
src/AppModels/MinerStudio/IServerMinerStudioService.cs

@@ -0,0 +1,4 @@
+namespace NTMiner.MinerStudio {
+    public interface IServerMinerStudioService : IMinerStudioService {
+    }
+}

+ 254 - 0
src/AppModels/MinerStudio/Impl/LocalMinerStudioService.cs

@@ -0,0 +1,254 @@
+using NTMiner.Controllers;
+using NTMiner.Core;
+using NTMiner.Core.Daemon;
+using NTMiner.Core.Impl;
+using NTMiner.Core.MinerClient;
+using NTMiner.Core.MinerServer;
+using NTMiner.JsonDb;
+using NTMiner.VirtualMemory;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+
+namespace NTMiner.MinerStudio.Impl {
+    public class LocalMinerStudioService : ILocalMinerStudioService {
+        private readonly string _daemonControllerName = RpcRoot.GetControllerName<INTMinerDaemonController>();
+
+        private readonly IClientDataSet _clientDataSet;
+        private readonly ICoinSnapshotSet _coinSnapshotSet;
+
+        public LocalMinerStudioService() {
+            _clientDataSet = new ClientDataSet();
+            _coinSnapshotSet = new CoinSnapshotSet(_clientDataSet);
+        }
+
+        #region AddClientsAsync
+        public void AddClientsAsync(List<string> clientIps, Action<ResponseBase, Exception> callback) {
+            try {
+                foreach (var clientIp in clientIps) {
+                    ClientData clientData = _clientDataSet.AsEnumerable().FirstOrDefault(a => a.MinerIp == clientIp);
+                    if (clientData != null) {
+                        continue;
+                    }
+                    _clientDataSet.AddClient(clientIp);
+                }
+                callback?.Invoke(ResponseBase.Ok(), null);
+            }
+            catch (Exception e) {
+                callback?.Invoke(ResponseBase.ServerError(e.Message), e);
+            }
+        }
+        #endregion
+
+        #region RemoveClientsAsync
+        public void RemoveClientsAsync(List<string> objectIds, Action<ResponseBase, Exception> callback) {
+            try {
+                foreach (var objectId in objectIds) {
+                    _clientDataSet.RemoveByObjectId(objectId);
+                }
+                callback?.Invoke(ResponseBase.Ok(), null);
+            }
+            catch (Exception e) {
+                callback?.Invoke(ResponseBase.ServerError(e.Message), e);
+            }
+        }
+        #endregion
+
+        #region QueryClientsAsync
+        public void QueryClientsAsync(QueryClientsRequest query, Action<QueryClientsResponse, Exception> callback) {
+            try {
+                var data = _clientDataSet.QueryClients(
+                    user: null,
+                    query,
+                    out int total,
+                    out List<CoinSnapshotData> latestSnapshots,
+                    out int totalOnlineCount,
+                    out int totalMiningCount);
+                callback?.Invoke(QueryClientsResponse.Ok(data, total, latestSnapshots, totalMiningCount, totalOnlineCount), null);
+            }
+            catch (Exception e) {
+                callback?.Invoke(ResponseBase.ServerError<QueryClientsResponse>(e.Message), e);
+            }
+        }
+        #endregion
+
+        #region UpdateClientAsync
+        public void UpdateClientAsync(string objectId, string propertyName, object value, Action<ResponseBase, Exception> callback) {
+            try {
+                _clientDataSet.UpdateClient(objectId, propertyName, value);
+                callback?.Invoke(ResponseBase.Ok(), null);
+            }
+            catch (Exception e) {
+                callback?.Invoke(ResponseBase.ServerError(e.Message), e);
+            }
+        }
+        #endregion
+
+        #region UpdateClientsAsync
+        public void UpdateClientsAsync(string propertyName, Dictionary<string, object> values, Action<ResponseBase, Exception> callback) {
+            try {
+                _clientDataSet.UpdateClients(propertyName, values);
+                callback?.Invoke(ResponseBase.Ok(), null);
+            }
+            catch (Exception e) {
+                callback?.Invoke(ResponseBase.ServerError(e.Message), e);
+            }
+        }
+        #endregion
+
+        #region GetLatestSnapshotsAsync
+        public void GetLatestSnapshotsAsync(int limit, Action<GetCoinSnapshotsResponse, Exception> callback) {
+            try {
+                List<CoinSnapshotData> data = _coinSnapshotSet.GetLatestSnapshots(
+                    limit,
+                    out int totalMiningCount,
+                    out int totalOnlineCount) ?? new List<CoinSnapshotData>();
+                callback?.Invoke(GetCoinSnapshotsResponse.Ok(data, totalMiningCount, totalOnlineCount), null);
+            }
+            catch (Exception e) {
+                callback?.Invoke(ResponseBase.ServerError<GetCoinSnapshotsResponse>(e.Message), e);
+            }
+        }
+        #endregion
+
+        #region EnableRemoteDesktopAsync
+        public void EnableRemoteDesktopAsync(IMinerData client) {
+            RpcRoot.PostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.EnableRemoteDesktop), null, null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region BlockWAUAsync
+        public void BlockWAUAsync(IMinerData client) {
+            RpcRoot.PostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.BlockWAU), null, null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region AtikmdagPatcherAsync
+        public void AtikmdagPatcherAsync(IMinerData client) {
+            RpcRoot.PostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.AtikmdagPatcher), null, null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region SwitchRadeonGpuAsync
+        public void SwitchRadeonGpuAsync(IMinerData client, bool on) {
+            RpcRoot.PostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.SwitchRadeonGpu), new Dictionary<string, string> {
+                {"on", on.ToString() }
+            }, null, null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region RestartWindowsAsync
+        public void RestartWindowsAsync(IMinerData client) {
+            RpcRoot.SignPostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.RestartWindows), new SignRequest(), null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region ShutdownWindowsAsync
+        public void ShutdownWindowsAsync(IMinerData client) {
+            RpcRoot.SignPostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.ShutdownWindows), new SignRequest(), null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region UpgradeNTMinerAsync
+        // ReSharper disable once InconsistentNaming
+        public void UpgradeNTMinerAsync(IMinerData client, string ntminerFileName) {
+            UpgradeNTMinerRequest request = new UpgradeNTMinerRequest {
+                NTMinerFileName = ntminerFileName
+            };
+            RpcRoot.SignPostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.UpgradeNTMiner), request, null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region SetAutoBootStartAsync
+        public void SetAutoBootStartAsync(IMinerData client, SetAutoBootStartRequest request) {
+            RpcRoot.FirePostAsync(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.SetAutoBootStart), new Dictionary<string, string> {
+                {"autoBoot", request.AutoBoot.ToString() },
+                {"autoStart", request.AutoStart.ToString() }
+            }, null, callback: null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region StartMineAsync
+        public void StartMineAsync(IMinerData client, Guid workId) {
+            string localJson = string.Empty, serverJson = string.Empty;
+            if (workId != Guid.Empty) {
+                localJson = HomePath.ReadMineWorkLocalJsonFile(workId).Replace(NTKeyword.MinerNameParameterName, client.WorkerName);
+                serverJson = HomePath.ReadMineWorkServerJsonFile(workId);
+            }
+            WorkRequest request = new WorkRequest {
+                WorkId = workId,
+                LocalJson = localJson,
+                ServerJson = serverJson
+            };
+            RpcRoot.SignPostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.StartMine), request, null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region StopMineAsync
+        public void StopMineAsync(IMinerData client) {
+            RpcRoot.SignPostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.StopMine), new SignRequest(), null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region GetDrivesAsync
+        public void GetDrivesAsync(IMinerData client) {
+            RpcRoot.SignPostAsync<List<DriveDto>>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.GetDrives), null, (data, e) => {
+                VirtualRoot.RaiseEvent(new GetDrivesResponsedEvent(client.ClientId, data));
+            }, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region SetVirtualMemoryAsync
+        public void SetVirtualMemoryAsync(IMinerData client, Dictionary<string, int> data) {
+            RpcRoot.SignPostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.SetVirtualMemory), new DataRequest<Dictionary<string, int>> {
+                Data = data
+            }, null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region GetLocalIpsAsync
+        public void GetLocalIpsAsync(IMinerData client) {
+            RpcRoot.SignPostAsync<List<LocalIpDto>>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.GetLocalIps), null, (data, e) => {
+                VirtualRoot.RaiseEvent(new GetLocalIpsResponsedEvent(client.ClientId, data));
+            }, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region SetLocalIpsAsync
+        public void SetLocalIpsAsync(IMinerData client, List<LocalIpInput> data) {
+            RpcRoot.SignPostAsync<ResponseBase>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.SetLocalIps), new DataRequest<List<LocalIpInput>> {
+                Data = data
+            }, null, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region GetOperationResultsAsync
+        public void GetOperationResultsAsync(IMinerData client, long afterTime) {
+            RpcRoot.GetAsync<List<OperationResultData>>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.GetOperationResults), new Dictionary<string, string> {
+                {"afterTime",afterTime.ToString() }
+            }, (data, e) => {
+                if (data != null || data.Count > 0) {
+                    VirtualRoot.RaiseEvent(new ClientOperationResultsEvent(client.ClientId, data));
+                }
+            }, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region GetGpuProfilesJsonAsync
+        public void GetGpuProfilesJsonAsync(IMinerData client) {
+            RpcRoot.PostAsync<string>(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.GetGpuProfilesJson), null, (json, e) => {
+                GpuProfilesJsonDb data = VirtualRoot.JsonSerializer.Deserialize<GpuProfilesJsonDb>(json) ?? new GpuProfilesJsonDb();
+                VirtualRoot.RaiseEvent(new GetGpuProfilesResponsedEvent(client.ClientId, data));
+            }, timeountMilliseconds: 3000);
+        }
+        #endregion
+
+        #region SaveGpuProfilesJsonAsync
+        public void SaveGpuProfilesJsonAsync(IMinerData client, string json) {
+            HttpContent content = new StringContent(json);
+            RpcRoot.FirePostAsync(client.GetLocalIp(), NTKeyword.NTMinerDaemonPort, _daemonControllerName, nameof(INTMinerDaemonController.SaveGpuProfilesJson), null, content, null, timeountMilliseconds: 3000);
+        }
+        #endregion
+    }
+}

+ 290 - 0
src/AppModels/MinerStudio/Impl/ServerMinerStudioService.cs

@@ -0,0 +1,290 @@
+using NTMiner.Core.MinerClient;
+using NTMiner.Core.MinerServer;
+using NTMiner.Ws;
+using System;
+using System.Collections.Generic;
+
+namespace NTMiner.MinerStudio.Impl {
+    public class ServerMinerStudioService : IServerMinerStudioService {
+        public ServerMinerStudioService() {
+        }
+
+        #region QueryClientsAsync
+        public void QueryClientsAsync(QueryClientsRequest query, Action<QueryClientsResponse, Exception> callback) {
+            RpcRoot.OfficialServer.ClientDataService.QueryClientsAsync(query, callback);
+        }
+        #endregion
+
+        #region UpdateClientAsync
+        public void UpdateClientAsync(string objectId, string propertyName, object value, Action<ResponseBase, Exception> callback) {
+            RpcRoot.OfficialServer.ClientDataService.UpdateClientAsync(objectId, propertyName, value, callback);
+        }
+        #endregion
+
+        #region UpdateClientsAsync
+        public void UpdateClientsAsync(string propertyName, Dictionary<string, object> values, Action<ResponseBase, Exception> callback) {
+            RpcRoot.OfficialServer.ClientDataService.UpdateClientsAsync(propertyName, values, callback);
+        }
+        #endregion
+
+        #region RemoveClientsAsync
+        public void RemoveClientsAsync(List<string> objectIds, Action<ResponseBase, Exception> callback) {
+            RpcRoot.OfficialServer.ClientDataService.RemoveClientsAsync(objectIds, callback);
+        }
+        #endregion
+
+        #region GetLatestSnapshotsAsync
+        public void GetLatestSnapshotsAsync(int limit, Action<GetCoinSnapshotsResponse, Exception> callback) {
+            RpcRoot.OfficialServer.CoinSnapshotService.GetLatestSnapshotsAsync(limit, callback);
+        }
+        #endregion
+
+        #region EnableRemoteDesktopAsync
+        public void EnableRemoteDesktopAsync(IMinerData client) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.EnableRemoteDesktop) {
+                Data = new WrapperClientId {
+                    ClientId = client.ClientId
+                }
+            });
+        }
+        #endregion
+
+        #region BlockWAUAsync
+        public void BlockWAUAsync(IMinerData client) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.BlockWAU) {
+                Data = new WrapperClientId {
+                    ClientId = client.ClientId
+                }
+            });
+        }
+        #endregion
+
+        #region AtikmdagPatcherAsync
+        public void AtikmdagPatcherAsync(IMinerData client) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.AtikmdagPatcher) {
+                Data = new WrapperClientId {
+                    ClientId = client.ClientId
+                }
+            });
+        }
+        #endregion
+
+        #region SwitchRadeonGpuAsync
+        public void SwitchRadeonGpuAsync(IMinerData client, bool on) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.SwitchRadeonGpu) {
+                Data = new WrapperClientIdData {
+                    ClientId = client.ClientId,
+                    Data = on
+                }
+            });
+        }
+        #endregion
+
+        #region RestartWindowsAsync
+        public void RestartWindowsAsync(IMinerData client) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.RestartWindows) {
+                Data = new WrapperClientId {
+                    ClientId = client.ClientId
+                }
+            });
+        }
+        #endregion
+
+        #region ShutdownWindowsAsync
+        public void ShutdownWindowsAsync(IMinerData client) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.ShutdownWindows) {
+                Data = new WrapperClientId {
+                    ClientId = client.ClientId
+                }
+            });
+        }
+        #endregion
+
+        #region UpgradeNTMinerAsync
+        // ReSharper disable once InconsistentNaming
+        public void UpgradeNTMinerAsync(IMinerData client, string ntminerFileName) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.UpgradeNTMiner) {
+                Data = new WrapperClientIdData {
+                    ClientId = client.ClientId,
+                    Data = ntminerFileName
+                }
+            });
+        }
+        #endregion
+
+        #region SetAutoBootStartAsync
+        public void SetAutoBootStartAsync(IMinerData client, SetAutoBootStartRequest request) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.SetAutoBootStart) {
+                Data = new WrapperClientIdData {
+                    ClientId = client.ClientId,
+                    Data = request
+                }
+            });
+        }
+        #endregion
+
+        #region StartMineAsync
+        public void StartMineAsync(IMinerData client, Guid workId) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            // localJson和serverJson在服务端将消息通过ws通道发送给挖矿端前根据workId填充
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.StartMine) {
+                Data = new WrapperClientIdData {
+                    ClientId = client.ClientId,
+                    Data = workId
+                }
+            });
+        }
+        #endregion
+
+        #region StopMineAsync
+        public void StopMineAsync(IMinerData client) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.StopMine) {
+                Data = new WrapperClientId {
+                    ClientId = client.ClientId
+                }
+            });
+        }
+        #endregion
+
+        #region GetDrivesAsync
+        public void GetDrivesAsync(IMinerData client) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.GetDrives) {
+                Data = new WrapperClientId {
+                    ClientId = client.ClientId
+                }
+            });
+        }
+        #endregion
+
+        #region SetVirtualMemoryAsync
+        public void SetVirtualMemoryAsync(IMinerData client, Dictionary<string, int> data) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.SetVirtualMemory) {
+                Data = new WrapperClientIdData {
+                    ClientId = client.ClientId,
+                    Data = data
+                }
+            });
+        }
+        #endregion
+
+        #region GetLocalIpsAsync
+        public void GetLocalIpsAsync(IMinerData client) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.GetLocalIps) {
+                Data = new WrapperClientId {
+                    ClientId = client.ClientId
+                }
+            });
+        }
+        #endregion
+
+        #region SetLocalIpsAsync
+        public void SetLocalIpsAsync(IMinerData client, List<LocalIpInput> data) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.SetLocalIps) {
+                Data = new WrapperClientIdData {
+                    ClientId = client.ClientId,
+                    Data = data
+                }
+            });
+        }
+        #endregion
+
+        #region GetOperationResultsAsync
+        public void GetOperationResultsAsync(IMinerData client, long afterTime) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.GetOperationResults) {
+                Data = new WrapperClientIdData {
+                    ClientId = client.ClientId,
+                    Data = afterTime
+                }
+            });
+        }
+        #endregion
+
+        #region GetGpuProfilesJsonAsync
+        public void GetGpuProfilesJsonAsync(IMinerData client) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.GetGpuProfilesJson) {
+                Data = new WrapperClientId {
+                    ClientId = client.ClientId
+                }
+            });
+        }
+        #endregion
+
+        #region SaveGpuProfilesJsonAsync
+        public void SaveGpuProfilesJsonAsync(IMinerData client, string json) {
+            if (!MinerStudioRoot.WsClient.IsOpen) {
+                VirtualRoot.Out.ShowWarn("和服务器失去连接", autoHideSeconds: 4);
+                return;
+            }
+            MinerStudioRoot.WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.SaveGpuProfilesJson) {
+                Data = new WrapperClientIdData {
+                    ClientId = client.ClientId,
+                    Data = json
+                }
+            });
+        }
+        #endregion
+    }
+}

+ 227 - 0
src/AppModels/MinerStudio/Messages.cs

@@ -0,0 +1,227 @@
+using NTMiner.Core;
+using NTMiner.Core.MinerClient;
+using NTMiner.Core.MinerServer;
+using NTMiner.Hub;
+using NTMiner.JsonDb;
+using NTMiner.MinerStudio.Vms;
+using NTMiner.VirtualMemory;
+using NTMiner.Vms;
+using System;
+using System.Collections.Generic;
+
+namespace NTMiner.MinerStudio {
+    [MessageType(description: "打开用户列表页")]
+    public class ShowUserPageCommand : Cmd {
+        public ShowUserPageCommand() {
+        }
+    }
+
+    [MessageType(description: "打开外网群控服务器节点列表页")]
+    public class ShowWsServerNodePageCommand : Cmd {
+        public ShowWsServerNodePageCommand() {
+        }
+    }
+
+    [MessageType(description: "打开超频菜谱列表页")]
+    public class ShowOverClockDataPageCommand : Cmd {
+        public ShowOverClockDataPageCommand() {
+        }
+    }
+
+    [MessageType(description: "打开NTMiner钱包列表页")]
+    public class ShowNTMinerWalletPageCommand : Cmd {
+        public ShowNTMinerWalletPageCommand() {
+        }
+    }
+
+    [MessageType(description: "打开群控算力图表窗口")]
+    public class ShowChartsWindowCommand : Cmd {
+        public ShowChartsWindowCommand() {
+        }
+    }
+
+    [MessageType(description: "打开列分组页面")]
+    public class ShowColumnsShowPageCommand : Cmd {
+        public ShowColumnsShowPageCommand() {
+        }
+    }
+
+    [MessageType(description: "打开升级器设置页面")]
+    public class ShowNTMinerUpdaterConfigCommand : Cmd {
+        public ShowNTMinerUpdaterConfigCommand() {
+        }
+    }
+
+    [MessageType(description: "打开挖矿端远程设置界面")]
+    public class ShowMinerClientSettingCommand : Cmd {
+        public ShowMinerClientSettingCommand(MinerClientSettingViewModel vm) {
+            this.Vm = vm;
+        }
+
+        public MinerClientSettingViewModel Vm { get; private set; }
+    }
+
+    [MessageType(description: "打开群控矿机列表页")]
+    public class ShowMinerClientsWindowCommand : Cmd {
+        public ShowMinerClientsWindowCommand(bool isToggle) {
+            this.IsToggle = isToggle;
+        }
+
+        public bool IsToggle { get; private set; }
+    }
+
+    [MessageType(description: "打开作群控名设置界面")]
+    public class ShowMinerNamesSeterCommand : Cmd {
+        public ShowMinerNamesSeterCommand(MinerNamesSeterViewModel vm) {
+            this.Vm = vm;
+        }
+
+        public MinerNamesSeterViewModel Vm { get; private set; }
+    }
+
+    [MessageType(description: "打开群控超频界面")]
+    public class ShowGpuProfilesPageCommand : Cmd {
+        public ShowGpuProfilesPageCommand(MinerClientsWindowViewModel minerClientsWindowVm) {
+            this.MinerClientsWindowVm = minerClientsWindowVm;
+        }
+
+        public MinerClientsWindowViewModel MinerClientsWindowVm { get; private set; }
+    }
+
+    [MessageType(description: "打开群控矿机的虚拟内存界面")]
+    public class ShowMinerStudioVirtualMemoryCommand : Cmd {
+        public ShowMinerStudioVirtualMemoryCommand(VirtualMemoryViewModel vm) {
+            this.Vm = vm;
+        }
+
+        public VirtualMemoryViewModel Vm { get; private set; }
+    }
+
+    [MessageType(description: "打开群控矿机的Ip管理界面")]
+    public class ShowMinerStudioLocalIpsCommand : Cmd {
+        public ShowMinerStudioLocalIpsCommand(Vms.LocalIpConfigViewModel vm) {
+            this.Vm = vm;
+        }
+
+        public Vms.LocalIpConfigViewModel Vm { get; private set; }
+    }
+
+    [MessageType(description: "打开添加矿机界面")]
+    public class ShowMinerClientAddCommand : Cmd {
+        public ShowMinerClientAddCommand() {
+        }
+    }
+
+    [MessageType(description: "打开矿工组编辑界面")]
+    public class EditMinerGroupCommand : EditCommand<MinerGroupViewModel> {
+        public EditMinerGroupCommand(FormType formType, MinerGroupViewModel source) : base(formType, source) {
+        }
+    }
+
+    [MessageType(description: "打开NTMiner钱包编辑界面")]
+    public class EditNTMinerWalletCommand : EditCommand<NTMinerWalletViewModel> {
+        public EditNTMinerWalletCommand(FormType formType, NTMinerWalletViewModel source) : base(formType, source) {
+        }
+    }
+
+    [MessageType(description: "打开作业编辑界面")]
+    public class EditMineWorkCommand : EditCommand<MineWorkViewModel> {
+        public EditMineWorkCommand(FormType formType, MineWorkViewModel source) : base(formType, source) {
+        }
+    }
+
+    [MessageType(description: "打开超频菜谱编辑界面")]
+    public class EditOverClockDataCommand : EditCommand<OverClockDataViewModel> {
+        public EditOverClockDataCommand(FormType formType, OverClockDataViewModel source) : base(formType, source) {
+        }
+    }
+
+    public abstract class OperationResultEvent<T> : EventBase {
+        public OperationResultEvent(Guid clientId, T data) {
+            this.ClientId = clientId;
+            this.Data = data;
+        }
+
+        public Guid ClientId { get; private set; }
+        public T Data { get; private set; }
+    }
+
+    [MessageType(description: "收到了ClientConsoleOutLines消息后")]
+    public class ClientConsoleOutLinesEvent : OperationResultEvent<List<ConsoleOutLine>> {
+        public ClientConsoleOutLinesEvent(Guid clientId, List<ConsoleOutLine> data) : base(clientId, data) {
+        }
+    }
+
+    [MessageType(description: "收到了ClientLocalMessages消息后")]
+    public class ClientLocalMessagesEvent : OperationResultEvent<List<LocalMessageDto>> {
+        public ClientLocalMessagesEvent(Guid clientId, List<LocalMessageDto> data) : base(clientId, data) {
+        }
+    }
+
+    [MessageType(description: "收到了ClientOperationResults消息后")]
+    public class ClientOperationResultsEvent : OperationResultEvent<List<OperationResultData>> {
+        public ClientOperationResultsEvent(Guid clientId, List<OperationResultData> data) : base(clientId, data) {
+        }
+    }
+
+    [MessageType(description: "收到了GetGpuProfilesResponsed消息后")]
+    public class GetGpuProfilesResponsedEvent : OperationResultEvent<GpuProfilesJsonDb> {
+        public GetGpuProfilesResponsedEvent(Guid clientId, GpuProfilesJsonDb data) : base(clientId, data) {
+        }
+    }
+
+    [MessageType(description: "收到了ClientOperationReceived消息后")]
+    public class ClientOperationReceivedEvent : EventBase {
+        public ClientOperationReceivedEvent(Guid clientId) {
+            this.ClientId = clientId;
+        }
+
+        public Guid ClientId { get; private set; }
+    }
+
+    [MessageType(description: "收到了OperationResult消息后")]
+    public class OperationResultEvent : OperationResultEvent<ResponseBase> {
+        public OperationResultEvent(Guid clientId, ResponseBase data) : base(clientId, data) {
+        }
+    }
+
+    [MessageType(description: "更新给定的矿机Vm内存")]
+    public class UpdateMinerClientVmCommand : Cmd {
+        public UpdateMinerClientVmCommand(ClientData clientData) {
+            this.ClientData = clientData;
+        }
+
+        public ClientData ClientData { get; private set; }
+    }
+
+    [MessageType(description: "矿机列表页选中了和上次选中的不同的矿机时")]
+    public class MinerClientSelectionChangedEvent : EventBase {
+        public MinerClientSelectionChangedEvent(MinerClientViewModel minerClientVm) {
+            this.MinerClientVm = minerClientVm;
+        }
+
+        public MinerClientViewModel MinerClientVm { get; private set; }
+    }
+
+    [MessageType(description: "收到了GetDrives的响应")]
+    public class GetDrivesResponsedEvent : EventBase {
+        public GetDrivesResponsedEvent(Guid clientId, List<DriveDto> data) {
+            this.ClientId = clientId;
+            this.Data = data;
+        }
+
+        public Guid ClientId { get; private set; }
+        public List<DriveDto> Data { get; private set; }
+    }
+
+    [MessageType(description: "收到了GetLocalIps的响应")]
+    public class GetLocalIpsResponsedEvent : EventBase {
+        public GetLocalIpsResponsedEvent(Guid clientId, List<LocalIpDto> data) {
+            this.ClientId = clientId;
+            this.Data = data;
+        }
+
+        public Guid ClientId { get; private set; }
+        public List<LocalIpDto> Data { get; private set; }
+    }
+}

+ 81 - 0
src/AppModels/MinerStudio/MinerStudioRoot.cs

@@ -0,0 +1,81 @@
+using NTMiner.MinerStudio.Impl;
+using NTMiner.MinerStudio.Vms;
+using NTMiner.Ws;
+
+namespace NTMiner.MinerStudio {
+    public static partial class MinerStudioRoot {
+        public static IWsClient WsClient { get; private set; } = EmptyWsClient.Empty;
+        public static IMinerStudioService MinerStudioService { get; private set; } = EmptyMinerStudioService.Instance;
+        public static MinerClientConsoleViewModel MinerClientConsoleVm { get; private set; } = new MinerClientConsoleViewModel();
+        public static MinerClientMessagesViewModel MinerClientMessagesVm { get; private set; } = new MinerClientMessagesViewModel();
+        public static MinerClientOperationResultsViewModel MinerClientOperationResultsVm { get; private set; } = new MinerClientOperationResultsViewModel();
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="wsClient">之所以从外部传入是因为IWsClient的实现类在外部的类库中</param>
+        public static void Init(IWsClient wsClient) {
+            if (RpcRoot.IsOuterNet) {
+                MinerStudioService = new ServerMinerStudioService();
+                WsClient = wsClient;
+            }
+            else {
+                MinerStudioService = new LocalMinerStudioService();
+            }
+        }
+
+        public static MinerClientsWindowViewModel MinerClientsWindowVm {
+            get {
+                return MinerClientsWindowViewModel.Instance;
+            }
+        }
+
+        public static CoinSnapshotDataViewModels CoinSnapshotDataVms {
+            get {
+                return CoinSnapshotDataViewModels.Instance;
+            }
+        }
+
+        public static ColumnsShowViewModels ColumnsShowVms {
+            get {
+                return ColumnsShowViewModels.Instance;
+            }
+        }
+
+        public static MinerGroupViewModels MinerGroupVms {
+            get {
+                return MinerGroupViewModels.Instance;
+            }
+        }
+
+        public static MineWorkViewModels MineWorkVms {
+            get {
+                return MineWorkViewModels.Instance;
+            }
+        }
+
+        public static OverClockDataViewModels OverClockDataVms {
+            get {
+                return OverClockDataViewModels.Instance;
+            }
+        }
+
+        public static NTMinerWalletViewModels NTMinerWalletVms {
+            get {
+                return NTMinerWalletViewModels.Instance;
+            }
+        }
+
+        private static bool _isMinerClientMessagesVisible = false;
+        public static bool IsMinerClientMessagesVisible {
+            get { return _isMinerClientMessagesVisible; }
+        }
+
+        public static void SetIsMinerClientMessagesVisible(bool value) {
+            _isMinerClientMessagesVisible = value;
+            if (value) {
+                MinerClientMessagesVm.SendGetLocalMessagesMqMessage();
+            }
+        }
+    }
+}

+ 8 - 14
src/AppModels/AppContext.partials.CoinSnapshotDataViewModels.cs → src/AppModels/MinerStudio/MinerStudioRoot.partials.CoinSnapshotDataViewModels.cs

@@ -1,32 +1,26 @@
 using NTMiner.Core.MinerServer;
+using NTMiner.MinerStudio.Vms;
 using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 
-namespace NTMiner {
-    public partial class AppContext {
+namespace NTMiner.MinerStudio {
+    public static partial class MinerStudioRoot {
         public class CoinSnapshotDataViewModels : ViewModelBase {
             public static readonly CoinSnapshotDataViewModels Instance = new CoinSnapshotDataViewModels();
 
             private readonly Dictionary<string, CoinSnapshotDataViewModel> _dicByCoinCode = new Dictionary<string, CoinSnapshotDataViewModel>(StringComparer.OrdinalIgnoreCase);
 
             private CoinSnapshotDataViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
 #if DEBUG
                 NTStopwatch.Start();
 #endif
-                foreach (var coinVm in AppContext.Instance.CoinVms.AllCoins) {
-                    _dicByCoinCode.Add(coinVm.Code, new CoinSnapshotDataViewModel(new CoinSnapshotData {
-                        CoinCode = coinVm.Code,
-                        MainCoinMiningCount = 0,
-                        MainCoinOnlineCount = 0,
-                        DualCoinMiningCount = 0,
-                        DualCoinOnlineCount = 0,
-                        ShareDelta = 0,
-                        RejectShareDelta = 0,
-                        Speed = 0,
-                        Timestamp = DateTime.MinValue
-                    }));
+                foreach (var coinVm in AppRoot.CoinVms.AllCoins) {
+                    _dicByCoinCode.Add(coinVm.Code, new CoinSnapshotDataViewModel(CoinSnapshotData.CreateEmpty(coinVm.Code)));
                 }
 #if DEBUG
                 var elapsedMilliseconds = NTStopwatch.Stop();

+ 91 - 0
src/AppModels/MinerStudio/MinerStudioRoot.partials.ColumnsShowViewModels.cs

@@ -0,0 +1,91 @@
+using NTMiner.Core.MinerStudio;
+using NTMiner.MinerStudio.Vms;
+using NTMiner.Vms;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio {
+    public static partial class MinerStudioRoot {
+        public class ColumnsShowViewModels : ViewModelBase {
+            public static readonly ColumnsShowViewModels Instance = new ColumnsShowViewModels();
+
+            private readonly Dictionary<Guid, ColumnsShowViewModel> _dicById = new Dictionary<Guid, ColumnsShowViewModel>();
+
+            public ICommand Add { get; private set; }
+
+            private ColumnsShowViewModels() {
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
+#if DEBUG
+                NTStopwatch.Start();
+#endif
+                this.Add = new DelegateCommand(() => {
+                    WpfUtil.ShowInputDialog("列分组名称", string.Empty, string.Empty, columnsShowName => {
+                        if (string.IsNullOrEmpty(columnsShowName)) {
+                            return "列分组名称是必须的";
+                        }
+                        return string.Empty;
+                    }, onOk: columnsShowName => {
+                        ColumnsShowData entity = new ColumnsShowData {
+                            ColumnsShowName = columnsShowName,
+                            LastActivedOnText = true,
+                            BootTimeSpanText = true,
+                            MineTimeSpanText = true,
+                            Work = true,
+                            MinerGroup = true,
+                            MinerName = true,
+                            WorkerName = true,
+                            LocalIp = true,
+                            MinerIp = true,
+                            GpuType = true,
+                            MainCoinCode = true,
+                            MainCoinSpeedText = true,
+                            MainCoinRejectPercentText = true
+                        };
+                        NTMinerContext.Instance.MinerStudioContext.ColumnsShowSet.AddOrUpdate(entity);
+                    });
+                });
+                AppRoot.AddEventPath<ColumnsShowAddedOrUpdatedEvent>("添加或修改了列分组后刷新VM内存", LogEnum.DevConsole,
+                    action: message => {
+                        if (!_dicById.TryGetValue(message.Target.GetId(), out ColumnsShowViewModel vm)) {
+                            vm = new ColumnsShowViewModel(message.Target);
+                            _dicById.Add(message.Target.GetId(), vm);
+                            OnPropertyChanged(nameof(List));
+                            MinerClientsWindowVm.ColumnsShow = vm;
+                        }
+                        else {
+                            vm.Update(message.Target);
+                        }
+                    }, location: this.GetType());
+                AppRoot.AddEventPath<ColumnsRemovedEvent>("删除了列分组后刷新Vm内存", LogEnum.DevConsole, action: message => {
+                    if (_dicById.ContainsKey(message.Target.Id)) {
+                        _dicById.Remove(message.Target.Id);
+                        OnPropertyChanged(nameof(List));
+                        if (_dicById.TryGetValue(ColumnsShowData.PleaseSelect.Id, out ColumnsShowViewModel vm)) {
+                            MinerClientsWindowVm.ColumnsShow = vm;
+                        }
+                    }
+                }, this.GetType());
+
+                foreach (var item in NTMinerContext.Instance.MinerStudioContext.ColumnsShowSet.GetAll()) {
+                    _dicById.Add(item.Id, new ColumnsShowViewModel(item));
+                }
+#if DEBUG
+                var elapsedMilliseconds = NTStopwatch.Stop();
+                if (elapsedMilliseconds.ElapsedMilliseconds > NTStopwatch.ElapsedMilliseconds) {
+                    Write.DevTimeSpan($"耗时{elapsedMilliseconds} {this.GetType().Name}.ctor");
+                }
+#endif
+            }
+
+            public List<ColumnsShowViewModel> List {
+                get {
+                    return _dicById.Values.ToList();
+                }
+            }
+        }
+    }
+}

+ 104 - 0
src/AppModels/MinerStudio/MinerStudioRoot.partials.MineWorkViewModels.cs

@@ -0,0 +1,104 @@
+using NTMiner.MinerStudio.Vms;
+using NTMiner.Vms;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio {
+    public static partial class MinerStudioRoot {
+        public class MineWorkViewModels : ViewModelBase {
+            public static readonly MineWorkViewModels Instance = new MineWorkViewModels();
+            private readonly Dictionary<Guid, MineWorkViewModel> _dicById = new Dictionary<Guid, MineWorkViewModel>();
+            public ICommand Add { get; private set; }
+
+            private MineWorkViewModels() {
+#if DEBUG
+                NTStopwatch.Start();
+#endif
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
+                foreach (var item in NTMinerContext.Instance.MinerStudioContext.MineWorkSet.AsEnumerable()) {
+                    if (!_dicById.ContainsKey(item.Id)) {
+                        _dicById.Add(item.Id, new MineWorkViewModel(item));
+                    }
+                }
+                if (RpcRoot.IsOuterNet) {
+                    AppRoot.AddEventPath<MineWorkSetInitedEvent>("作业集初始化后初始化Vm内存", LogEnum.DevConsole, action: message => {
+                        foreach (var item in NTMinerContext.Instance.MinerStudioContext.MineWorkSet.AsEnumerable()) {
+                            if (!_dicById.ContainsKey(item.Id)) {
+                                _dicById.Add(item.Id, new MineWorkViewModel(item));
+                            }
+                        }
+                        OnPropertyChangeds();
+                        MinerClientsWindowViewModel.Instance.RefreshMinerClientsSelectedMineWork();
+                    }, this.GetType());
+                }            
+                this.Add = new DelegateCommand(() => {
+                    new MineWorkViewModel(Guid.NewGuid()).Edit.Execute(FormType.Add);
+                });
+                AppRoot.AddEventPath<MineWorkAddedEvent>("添加作业后刷新VM内存", LogEnum.DevConsole,
+                    action: message => {
+                        if (!_dicById.TryGetValue(message.Target.GetId(), out MineWorkViewModel vm)) {
+                            vm = new MineWorkViewModel(message.Target);
+                            _dicById.Add(message.Target.GetId(), vm);
+                            OnPropertyChangeds();
+                            if (message.Target.GetId() == MinerClientsWindowVm.SelectedMineWork.GetId()) {
+                                MinerClientsWindowVm.SelectedMineWork = MineWorkViewModel.PleaseSelect;
+                            }
+                        }
+                    }, location: this.GetType());
+                AppRoot.AddEventPath<MineWorkUpdatedEvent>("添加作业后刷新VM内存", LogEnum.DevConsole,
+                    action: message => {
+                        if (_dicById.TryGetValue(message.Target.GetId(), out MineWorkViewModel vm)) {
+                            vm.Update(message.Target);
+                        }
+                    }, location: this.GetType());
+                AppRoot.AddEventPath<MineWorkRemovedEvent>("移除了作业后刷新Vm内存", LogEnum.DevConsole, action: message => {
+                    if (_dicById.TryGetValue(message.Target.Id, out MineWorkViewModel vm)) {
+                        _dicById.Remove(vm.Id);
+                        OnPropertyChangeds();
+                        if (vm.Id == MinerClientsWindowVm.SelectedMineWork.GetId()) {
+                            MinerClientsWindowVm.SelectedMineWork = MineWorkViewModel.PleaseSelect;
+                        }
+                    }
+                }, this.GetType());
+#if DEBUG
+                var elapsedMilliseconds = NTStopwatch.Stop();
+                if (elapsedMilliseconds.ElapsedMilliseconds > NTStopwatch.ElapsedMilliseconds) {
+                    Write.DevTimeSpan($"耗时{elapsedMilliseconds} {this.GetType().Name}.ctor");
+                }
+#endif
+            }
+
+            private void OnPropertyChangeds() {
+                OnPropertyChanged(nameof(List));
+                OnPropertyChanged(nameof(MineWorkVmItems));
+            }
+
+            public List<MineWorkViewModel> List {
+                get {
+                    return _dicById.Values.ToList();
+                }
+            }
+
+            private IEnumerable<MineWorkViewModel> GetMineWorkVmItems() {
+                yield return MineWorkViewModel.PleaseSelect;
+                foreach (var item in List) {
+                    yield return item;
+                }
+            }
+
+            public List<MineWorkViewModel> MineWorkVmItems {
+                get {
+                    return GetMineWorkVmItems().ToList();
+                }
+            }
+
+            public bool TryGetMineWorkVm(Guid id, out MineWorkViewModel mineWorkVm) {
+                return _dicById.TryGetValue(id, out mineWorkVm);
+            }
+        }
+    }
+}

+ 113 - 0
src/AppModels/MinerStudio/MinerStudioRoot.partials.MinerClientConsoleViewModel.cs

@@ -0,0 +1,113 @@
+using NTMiner.MinerStudio.Vms;
+using NTMiner.Vms;
+using NTMiner.Ws;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace NTMiner.MinerStudio {
+    public static partial class MinerStudioRoot {
+        public class MinerClientConsoleViewModel : ViewModelBase {
+            private readonly List<ConsoleOutLine> _outLines = new List<ConsoleOutLine>();
+            private readonly object _locker = new object();
+            private MinerClientViewModel _minerClientVm;
+            private DateTime _latestTimestamp = Timestamp.UnixBaseTime;
+
+            public DateTime LatestTimestamp {
+                get { return _latestTimestamp; }
+                set {
+                    if (_latestTimestamp != value) {
+                        _latestTimestamp = value;
+                        OnPropertyChanged(nameof(LatestTimestamp));
+                        OnPropertyChanged(nameof(LatestTimeSpanText));
+                    }
+                }
+            }
+
+            public string LatestTimeSpanText {
+                get {
+                    if (LatestTimestamp == Timestamp.UnixBaseTime) {
+                        return "未知";
+                    }
+                    return Timestamp.GetTimeSpanText(LatestTimestamp);
+                }
+            }
+
+            public MinerClientConsoleViewModel() {
+                VirtualRoot.AddEventPath<MinerClientSelectionChangedEvent>("矿机列表页选中了和上次选中的不同的矿机时刷新矿机控制台输出", LogEnum.DevConsole, action: message => {
+                    bool isChanged = true;
+                    if (message.MinerClientVm != null && this._minerClientVm != null && this._minerClientVm.ClientId == message.MinerClientVm.ClientId) {
+                        isChanged = false;
+                    }
+                    LatestTimestamp = Timestamp.UnixBaseTime;
+                    if (isChanged) {
+                        lock (_locker) {
+                            _outLines.Clear();
+                            try {
+                                Console.Clear();
+                            }
+                            catch {
+                            }
+                            this._minerClientVm = message.MinerClientVm;
+                        }
+                        SendGetConsoleOutLinesMqMessage();
+                    }
+                }, this.GetType());
+                VirtualRoot.AddEventPath<ClientConsoleOutLinesEvent>("收到了挖矿端控制台消息", LogEnum.DevConsole, action: message => {
+                    if (this._minerClientVm == null 
+                        || this._minerClientVm.ClientId != message.ClientId 
+                        || message.Data == null 
+                        || message.Data.Count == 0) {
+                        return;
+                    }
+                    lock (_locker) {
+                        foreach (var item in message.Data) {
+                            _outLines.Add(item);
+                            Console.WriteLine(item.Line);
+                        }
+                        // 因为客户端的时间可能不准所以不能使用客户端的时间
+                        LatestTimestamp = DateTime.Now;
+                    }
+                }, this.GetType());
+                VirtualRoot.AddEventPath<Per5SecondEvent>("周期获取当前选中的那台矿机的控制台输出", LogEnum.DevConsole, action: message => {
+                    SendGetConsoleOutLinesMqMessage();
+                }, this.GetType());
+                VirtualRoot.AddEventPath<Per1SecondEvent>("客户端控制台输出倒计时秒表", LogEnum.None, action: message => {
+                    if (this._minerClientVm == null || this._latestTimestamp == Timestamp.UnixBaseTime) {
+                        return;
+                    }
+                    OnPropertyChanged(nameof(LatestTimeSpanText));
+                }, this.GetType());
+            }
+
+            private void SendGetConsoleOutLinesMqMessage() {
+                if (this._minerClientVm == null) {
+                    return;
+                }
+                long timestamp = 0;
+                var minerClientVm = this._minerClientVm;
+                lock (_locker) {
+                    var item = _outLines.LastOrDefault();
+                    if (item != null) {
+                        timestamp = item.Timestamp;
+                    }
+                }
+                if (RpcRoot.IsOuterNet) {
+                    WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.GetConsoleOutLines) {
+                        Data = new WrapperClientIdData {
+                            ClientId = minerClientVm.ClientId,
+                            Data = timestamp
+                        }
+                    });
+                }
+                else {
+                    RpcRoot.Client.MinerClientService.GetConsoleOutLinesAsync(minerClientVm.GetLocalIp(), timestamp, (data, e) => {
+                        if (data != null && data.Count > 0) {
+                            VirtualRoot.RaiseEvent(new ClientConsoleOutLinesEvent(minerClientVm.ClientId, data));
+                        }
+                    });
+                }
+            }
+        }
+    }
+}

+ 111 - 0
src/AppModels/MinerStudio/MinerStudioRoot.partials.MinerClientMessagesViewModel.cs

@@ -0,0 +1,111 @@
+using NTMiner.MinerStudio.Vms;
+using NTMiner.Vms;
+using NTMiner.Ws;
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace NTMiner.MinerStudio {
+    public static partial class MinerStudioRoot {
+        public class MinerClientMessagesViewModel : ViewModelBase {
+            private ObservableCollection<LocalMessageDtoViewModel> _vms = new ObservableCollection<LocalMessageDtoViewModel>();
+            private readonly object _locker = new object();
+            private MinerClientViewModel _minerClientVm;
+
+            private static bool _called = false;
+
+            public MinerClientMessagesViewModel() {
+                if (_called) {
+                    throw new InvalidProgramException("只能调用一次");
+                }
+                _called = true;
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
+                VirtualRoot.AddEventPath<MinerClientSelectionChangedEvent>("矿机列表页选中了和上次选中的不同的矿机时刷新矿机消息列表", LogEnum.DevConsole, action: message => {
+                    bool isChanged = true;
+                    if (message.MinerClientVm != null && this._minerClientVm != null && this._minerClientVm.ClientId == message.MinerClientVm.ClientId) {
+                        isChanged = false;
+                    }
+                    if (isChanged) {
+                        lock (_locker) {
+                            _vms.Clear();
+                            this._minerClientVm = message.MinerClientVm;
+                            OnPropertyChanged(nameof(IsNoRecord));
+                        }
+                        SendGetLocalMessagesMqMessage();
+                    }
+                }, this.GetType());
+                VirtualRoot.AddEventPath<ClientLocalMessagesEvent>("收到了挖矿端本地消息", LogEnum.DevConsole,
+                    action: message => {
+                        if (this._minerClientVm == null || this._minerClientVm.ClientId != message.ClientId) {
+                            return;
+                        }
+                        if (message.Data == null || message.Data.Count == 0) {
+                            return;
+                        }
+                        UIThread.Execute(() => () => {
+                            foreach (var item in message.Data) {
+                                _vms.Insert(0, new LocalMessageDtoViewModel(item));
+                            }
+                            OnPropertyChanged(nameof(IsNoRecord));
+                        });
+                    }, location: this.GetType());
+                VirtualRoot.AddEventPath<Per5SecondEvent>("周期获取当前选中的那台矿机的本地消息", LogEnum.DevConsole, action: message => {
+                    SendGetLocalMessagesMqMessage();
+                }, this.GetType());
+            }
+
+            public ObservableCollection<LocalMessageDtoViewModel> ClientLocalMessages {
+                get {
+                    return _vms;
+                }
+            }
+
+            public bool IsNoRecord {
+                get {
+                    return _vms.Count == 0;
+                }
+            }
+
+            private DateTime _preSendMqMessageOn = DateTime.MinValue;
+            private MinerClientViewModel _preMinerClientVm;
+            public void SendGetLocalMessagesMqMessage() {
+                if (this._minerClientVm == null || !IsMinerClientMessagesVisible) {
+                    return;
+                }
+                foreach (var vm in _vms) {
+                    vm.OnPropertyChanged(nameof(LocalMessageDtoViewModel.TimestampText));
+                }
+                if (_preSendMqMessageOn.AddSeconds(4) > DateTime.Now && _preMinerClientVm == _minerClientVm) {
+                    return;
+                }
+                _preSendMqMessageOn = DateTime.Now;
+                _preMinerClientVm = _minerClientVm;
+                long afterTime = 0;
+                var minerClientVm = this._minerClientVm;
+                lock (_locker) {
+                    var item = _vms.FirstOrDefault();
+                    if (item != null) {
+                        afterTime = item.Timestamp;
+                    }
+                }
+                if (RpcRoot.IsOuterNet) {
+                    WsClient.SendAsync(new WsMessage(Guid.NewGuid(), WsMessage.GetLocalMessages) {
+                        Data = new WrapperClientIdData {
+                            ClientId = minerClientVm.ClientId,
+                            Data = afterTime
+                        }
+                    });
+                }
+                else {
+                    RpcRoot.Client.MinerClientService.GetLocalMessagesAsync(minerClientVm.GetLocalIp(), afterTime, (data, e) => {
+                        if (data != null || data.Count > 0) {
+                            VirtualRoot.RaiseEvent(new ClientLocalMessagesEvent(minerClientVm.ClientId, data));
+                        }
+                    });
+                }
+            }
+        }
+    }
+}

+ 127 - 0
src/AppModels/MinerStudio/MinerStudioRoot.partials.MinerClientOperationResultsViewModel.cs

@@ -0,0 +1,127 @@
+using NTMiner.MinerStudio.Vms;
+using NTMiner.Vms;
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace NTMiner.MinerStudio {
+    public static partial class MinerStudioRoot {
+        public class MinerClientOperationResultsViewModel : ViewModelBase {
+            private ObservableCollection<OperationResultViewModel> _vms = new ObservableCollection<OperationResultViewModel>();
+            private readonly object _locker = new object();
+            private MinerClientViewModel _minerClientVm;
+            private const string NO_RECORD_TEXT = "没有群控操作记录";
+            private const string LOADING = "加载中";
+            private string _noRecordText;
+            private DateTime _preClientOperationResultsOn = DateTime.Now;
+
+            private static bool _called = false;
+
+            public MinerClientOperationResultsViewModel() {
+                if (_called) {
+                    throw new InvalidProgramException("只能调用一次");
+                }
+                _called = true;
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
+                VirtualRoot.AddEventPath<MinerClientSelectionChangedEvent>("矿机列表页选中了和上次选中的不同的矿机时刷新矿机本地群控响应消息列表", LogEnum.DevConsole, action: message => {
+                    bool isChanged = true;
+                    if (message.MinerClientVm != null && this._minerClientVm != null && this._minerClientVm.ClientId == message.MinerClientVm.ClientId) {
+                        isChanged = false;
+                    }
+                    if (isChanged) {
+                        lock (_locker) {
+                            _vms.Clear();
+                            this._minerClientVm = message.MinerClientVm;
+                            if (_minerClientVm != null) {
+                                this.NoRecordText = LOADING;
+                            }
+                            else {
+                                this.NoRecordText = "未选中矿机";
+                            }
+                            OnPropertyChanged(nameof(IsNoRecord));
+                        }
+                        SendGetOperationResultsMqMessage();
+                    }
+                }, this.GetType());
+                VirtualRoot.AddEventPath<ClientOperationResultsEvent>("收到了挖矿端本地群控响应消息", LogEnum.DevConsole,
+                    action: message => {
+                        if (this._minerClientVm == null || this._minerClientVm.ClientId != message.ClientId) {
+                            return;
+                        }
+                        _preClientOperationResultsOn = message.BornOn;
+                        if (message.Data == null || message.Data.Count == 0) {
+                            this.NoRecordText = NO_RECORD_TEXT;
+                            return;
+                        }
+                        UIThread.Execute(() => () => {
+                            foreach (var item in message.Data) {
+                                _vms.Insert(0, new OperationResultViewModel(item));
+                            }
+                            OnPropertyChanged(nameof(IsNoRecord));
+                        });
+                    }, location: this.GetType());
+                VirtualRoot.AddEventPath<ClientOperationReceivedEvent>("收到了挖矿端群控响应了群控操作的通知", LogEnum.DevConsole,
+                    action: message => {
+                        if (_minerClientVm != null && _minerClientVm.ClientId == message.ClientId) {
+                            SendGetOperationResultsMqMessage();
+                        }
+                    }, location: this.GetType());
+                VirtualRoot.AddEventPath<Per5SecondEvent>("周期获取当前选中的那台矿机的本地群控响应消息", LogEnum.DevConsole, action: message => {
+                    SendGetOperationResultsMqMessage();
+                }, this.GetType());
+            }
+
+            public ObservableCollection<OperationResultViewModel> ClientOperationResults {
+                get {
+                    return _vms;
+                }
+            }
+
+            public bool IsNoRecord {
+                get {
+                    return _vms.Count == 0;
+                }
+            }
+
+            public string NoRecordText {
+                get => _noRecordText;
+                set {
+                    if (_noRecordText != value) {
+                        _noRecordText = value;
+                        OnPropertyChanged(nameof(NoRecordText));
+                    }
+                }
+            }
+
+            private DateTime _preSendMqMessageOn = DateTime.MinValue;
+            private MinerClientViewModel _preMinerClientVm;
+            private void SendGetOperationResultsMqMessage() {
+                if (this._minerClientVm == null) {
+                    return;
+                }
+                foreach (var vm in _vms) {
+                    vm.OnPropertyChanged(nameof(OperationResultViewModel.TimestampText));
+                }
+                if (_preClientOperationResultsOn.AddSeconds(4) < DateTime.Now) {
+                    this.NoRecordText = NO_RECORD_TEXT;
+                }
+                if (_preSendMqMessageOn.AddSeconds(4) > DateTime.Now && _preMinerClientVm == _minerClientVm) {
+                    return;
+                }
+                _preSendMqMessageOn = DateTime.Now;
+                _preMinerClientVm = _minerClientVm;
+                long afterTime = 0;
+                var minerClientVm = this._minerClientVm;
+                lock (_locker) {
+                    var item = _vms.FirstOrDefault();
+                    if (item != null) {
+                        afterTime = item.Timestamp;
+                    }
+                }
+                MinerStudioService.GetOperationResultsAsync(minerClientVm, afterTime);
+            }
+        }
+    }
+}

+ 99 - 0
src/AppModels/MinerStudio/MinerStudioRoot.partials.MinerGroupViewModels.cs

@@ -0,0 +1,99 @@
+using NTMiner.MinerStudio.Vms;
+using NTMiner.Vms;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio {
+    public static partial class MinerStudioRoot {
+        public class MinerGroupViewModels : ViewModelBase {
+            public static readonly MinerGroupViewModels Instance = new MinerGroupViewModels();
+            private readonly Dictionary<Guid, MinerGroupViewModel> _dicById = new Dictionary<Guid, MinerGroupViewModel>();
+
+            public ICommand Add { get; private set; }
+
+            private MinerGroupViewModels() {
+#if DEBUG
+                NTStopwatch.Start();
+#endif
+                if (WpfUtil.IsInDesignMode) {
+                    return;
+                }
+                foreach (var item in NTMinerContext.Instance.MinerStudioContext.MinerGroupSet.AsEnumerable()) {
+                    if (!_dicById.ContainsKey(item.Id)) {
+                        _dicById.Add(item.Id, new MinerGroupViewModel(item));
+                    }
+                }
+                AppRoot.AddEventPath<MinerGroupSetInitedEvent>("矿工组集初始化后初始化Vm内存", LogEnum.DevConsole, action: message => {
+                    foreach (var item in NTMinerContext.Instance.MinerStudioContext.MinerGroupSet.AsEnumerable()) {
+                        if (!_dicById.ContainsKey(item.Id)) {
+                            _dicById.Add(item.Id, new MinerGroupViewModel(item));
+                        }
+                    }
+                    this.OnPropertyChangeds();
+                    MinerClientsWindowViewModel.Instance.RefreshMinerClientsSelectedMinerGroup();
+                }, this.GetType());
+                this.Add = new DelegateCommand(() => {
+                    new MinerGroupViewModel(Guid.NewGuid()).Edit.Execute(FormType.Add);
+                });
+                AppRoot.AddEventPath<MinerGroupAddedEvent>("添加矿机分组后刷新VM内存", LogEnum.DevConsole,
+                    action: message => {
+                        if (!_dicById.TryGetValue(message.Target.GetId(), out MinerGroupViewModel vm)) {
+                            vm = new MinerGroupViewModel(message.Target);
+                            _dicById.Add(message.Target.GetId(), vm);
+                            OnPropertyChangeds();
+                            MinerClientsWindowVm.OnPropertyChanged(nameof(MinerClientsWindowViewModel.SelectedMinerGroup));
+                        }
+                    }, location: this.GetType());
+                AppRoot.AddEventPath<MinerGroupUpdatedEvent>("添加矿机分组后刷新VM内存", LogEnum.DevConsole,
+                    action: message => {
+                        if (_dicById.TryGetValue(message.Target.GetId(), out MinerGroupViewModel vm)) {
+                            vm.Update(message.Target);
+                        }
+                    }, location: this.GetType());
+                AppRoot.AddEventPath<MinerGroupRemovedEvent>("移除了矿机组后刷新Vm内容", LogEnum.DevConsole, action: message => {
+                    if (_dicById.TryGetValue(message.Target.Id, out MinerGroupViewModel vm)) {
+                        _dicById.Remove(vm.Id);
+                        OnPropertyChangeds();
+                        MinerClientsWindowVm.OnPropertyChanged(nameof(MinerClientsWindowViewModel.SelectedMinerGroup));
+                    }
+                }, this.GetType());
+#if DEBUG
+                var elapsedMilliseconds = NTStopwatch.Stop();
+                if (elapsedMilliseconds.ElapsedMilliseconds > NTStopwatch.ElapsedMilliseconds) {
+                    Write.DevTimeSpan($"耗时{elapsedMilliseconds} {this.GetType().Name}.ctor");
+                }
+#endif
+            }
+
+            private void OnPropertyChangeds() {
+                OnPropertyChanged(nameof(List));
+                OnPropertyChanged(nameof(MinerGroupItems));
+            }
+
+            public List<MinerGroupViewModel> List {
+                get {
+                    return _dicById.Values.ToList();
+                }
+            }
+
+            public bool TryGetMineWorkVm(Guid id, out MinerGroupViewModel minerGroupVm) {
+                return _dicById.TryGetValue(id, out minerGroupVm);
+            }
+
+            private IEnumerable<MinerGroupViewModel> GetMinerGroupItems() {
+                yield return MinerGroupViewModel.PleaseSelect;
+                foreach (var item in List) {
+                    yield return item;
+                }
+            }
+
+            public List<MinerGroupViewModel> MinerGroupItems {
+                get {
+                    return GetMinerGroupItems().ToList();
+                }
+            }
+        }
+    }
+}

+ 16 - 14
src/AppModels/AppContext.partials.NTMinerWalletViewModels.cs → src/AppModels/MinerStudio/MinerStudioRoot.partials.NTMinerWalletViewModels.cs

@@ -1,11 +1,11 @@
-using NTMiner.Core;
+using NTMiner.MinerStudio.Vms;
 using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Windows.Input;
 
-namespace NTMiner {
-    public partial class AppContext {
+namespace NTMiner.MinerStudio {
+    public static partial class MinerStudioRoot {
         public class NTMinerWalletViewModels : ViewModelBase {
             public static readonly NTMinerWalletViewModels Instance = new NTMinerWalletViewModels();
             private readonly Dictionary<Guid, NTMinerWalletViewModel> _dicById = new Dictionary<Guid, NTMinerWalletViewModel>();
@@ -20,30 +20,32 @@ namespace NTMiner {
                     return;
                 }
                 Init(refresh: false);
-                AddEventPath<NTMinerWalletSetInitedEvent>("NTMiner钱包集初始化后", LogEnum.DevConsole,
+                AppRoot.AddEventPath<NTMinerWalletSetInitedEvent>("NTMiner钱包集初始化后", LogEnum.DevConsole,
                     action: message => {
                         Init(refresh: true);
                     }, location: this.GetType());
                 this.Add = new DelegateCommand(() => {
                     new NTMinerWalletViewModel(Guid.NewGuid()).Edit.Execute(FormType.Add);
                 });
-                AddEventPath<NTMinerWalletAddedEvent>("添加NTMiner钱包后刷新VM内存", LogEnum.DevConsole,
+                AppRoot.AddEventPath<NTMinerWalletAddedEvent>("添加NTMiner钱包后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
                         if (!_dicById.ContainsKey(message.Target.GetId())) {
                             _dicById.Add(message.Target.GetId(), new NTMinerWalletViewModel(message.Target));
-                            if (AppContext.Instance.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
+                            if (AppRoot.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
                                 coinVm.OnPropertyChanged(nameof(coinVm.NTMinerWallets));
                             }
                         }
                     }, location: this.GetType());
-                AddEventPath<NTMinerWalletUpdatedEvent>("更新NTMiner钱包后刷新VM内存", LogEnum.DevConsole,
+                AppRoot.AddEventPath<NTMinerWalletUpdatedEvent>("更新NTMiner钱包后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
-                        _dicById[message.Target.GetId()].Update(message.Target);
+                        if (_dicById.TryGetValue(message.Target.GetId(), out NTMinerWalletViewModel vm)) {
+                            vm.Update(message.Target);
+                        }
                     }, location: this.GetType());
-                AddEventPath<NTMinerWalletRemovedEvent>("删除NTMiner钱包后刷新VM内存", LogEnum.DevConsole,
+                AppRoot.AddEventPath<NTMinerWalletRemovedEvent>("删除NTMiner钱包后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
                         _dicById.Remove(message.Target.GetId());
-                        if (AppContext.Instance.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
+                        if (AppRoot.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
                             coinVm.OnPropertyChanged(nameof(coinVm.NTMinerWallets));
                         }
                     }, location: this.GetType());
@@ -57,18 +59,18 @@ namespace NTMiner {
 
             private void Init(bool refresh) {
                 _dicById.Clear();
-                foreach (var item in NTMinerRoot.Instance.NTMinerWalletSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.MinerStudioContext.NTMinerWalletSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new NTMinerWalletViewModel(item));
                 }
                 if (refresh) {
-                    foreach (var coinVm in AppContext.Instance.CoinVms.AllCoins) {
+                    foreach (var coinVm in AppRoot.CoinVms.AllCoins) {
                         coinVm.OnPropertyChanged(nameof(coinVm.NTMinerWallets));
                     }
                 }
             }
 
-            public bool TryGetMineWorkVm(Guid id, out NTMinerWalletViewModel ntMinerWalletVm) {
-                return _dicById.TryGetValue(id, out ntMinerWalletVm);
+            public bool TryGetMineWorkVm(Guid id, out NTMinerWalletViewModel ntminerWalletVm) {
+                return _dicById.TryGetValue(id, out ntminerWalletVm);
             }
 
             public IEnumerable<NTMinerWalletViewModel> Items {

+ 14 - 12
src/AppModels/AppContext.partials.OverClockDataViewModels.cs → src/AppModels/MinerStudio/MinerStudioRoot.partials.OverClockDataViewModels.cs

@@ -1,10 +1,10 @@
-using NTMiner.Core;
+using NTMiner.MinerStudio.Vms;
 using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 
-namespace NTMiner {
-    public partial class AppContext {
+namespace NTMiner.MinerStudio {
+    public static partial class MinerStudioRoot {
         public class OverClockDataViewModels : ViewModelBase {
             public static readonly OverClockDataViewModels Instance = new OverClockDataViewModels();
             private readonly Dictionary<Guid, OverClockDataViewModel> _dicById = new Dictionary<Guid, OverClockDataViewModel>();
@@ -17,27 +17,29 @@ namespace NTMiner {
                     return;
                 }
                 Init(refresh: false);
-                AddEventPath<OverClockDataSetInitedEvent>("超频建议集初始化后", LogEnum.DevConsole,
+                AppRoot.AddEventPath<OverClockDataSetInitedEvent>("超频建议集初始化后", LogEnum.DevConsole,
                     action: message => {
                         Init(refresh: true);
                     }, location: this.GetType());
-                AddEventPath<OverClockDataAddedEvent>("添加超频建议后刷新VM内存", LogEnum.DevConsole,
+                AppRoot.AddEventPath<OverClockDataAddedEvent>("添加超频建议后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
                         if (!_dicById.ContainsKey(message.Target.GetId())) {
                             _dicById.Add(message.Target.GetId(), new OverClockDataViewModel(message.Target));
-                            if (AppContext.Instance.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
+                            if (AppRoot.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
                                 coinVm.OnPropertyChanged(nameof(coinVm.OverClockDatas));
                             }
                         }
                     }, location: this.GetType());
-                AddEventPath<OverClockDataUpdatedEvent>("更新超频建议后刷新VM内存", LogEnum.DevConsole,
+                AppRoot.AddEventPath<OverClockDataUpdatedEvent>("更新超频建议后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
-                        _dicById[message.Target.GetId()].Update(message.Target);
+                        if (_dicById.TryGetValue(message.Target.GetId(), out OverClockDataViewModel vm)) {
+                            vm.Update(message.Target);
+                        }
                     }, location: this.GetType());
-                AddEventPath<OverClockDataRemovedEvent>("删除超频建议后刷新VM内存", LogEnum.DevConsole,
+                AppRoot.AddEventPath<OverClockDataRemovedEvent>("删除超频建议后刷新VM内存", LogEnum.DevConsole,
                     action: message => {
                         _dicById.Remove(message.Target.GetId());
-                        if (AppContext.Instance.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
+                        if (AppRoot.CoinVms.TryGetCoinVm(message.Target.CoinId, out CoinViewModel coinVm)) {
                             coinVm.OnPropertyChanged(nameof(coinVm.OverClockDatas));
                         }
                     }, location: this.GetType());
@@ -51,11 +53,11 @@ namespace NTMiner {
 
             private void Init(bool refresh) {
                 _dicById.Clear();
-                foreach (var item in NTMinerRoot.Instance.OverClockDataSet.AsEnumerable()) {
+                foreach (var item in NTMinerContext.Instance.OverClockDataSet.AsEnumerable()) {
                     _dicById.Add(item.GetId(), new OverClockDataViewModel(item));
                 }
                 if (refresh) {
-                    foreach (var coinVm in AppContext.Instance.CoinVms.AllCoins) {
+                    foreach (var coinVm in AppRoot.CoinVms.AllCoins) {
                         coinVm.OnPropertyChanged(nameof(coinVm.OverClockDatas));
                     }
                 }

+ 12 - 6
src/AppModels/Vms/ChartViewModel.cs → src/AppModels/MinerStudio/Vms/ChartViewModel.cs

@@ -2,12 +2,13 @@
 using LiveCharts.Configurations;
 using LiveCharts.Wpf;
 using NTMiner.Core;
+using NTMiner.Vms;
 using System;
 using System.Linq;
 using System.Windows.Input;
 using System.Windows.Media;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class ChartViewModel : ViewModelBase {
         private SeriesCollection _series;
         private AxesCollection _axisY;
@@ -70,7 +71,7 @@ namespace NTMiner.Vms {
                 new Axis() {
                     LabelFormatter = DateTimeFormatter,
                     MaxValue = now.Ticks,
-                    MinValue = now.Ticks - TimeSpan.FromMinutes(NTMinerRoot.SpeedHistoryLengthByMinute).Ticks,
+                    MinValue = now.Ticks - TimeSpan.FromMinutes(NTMinerContext.SpeedHistoryLengthByMinute).Ticks,
                     Unit=axisUnit,
                     Separator = new Separator() {
                         Step = axisStep
@@ -137,7 +138,7 @@ namespace NTMiner.Vms {
                 }
                 _isFirst = false;
                 string key = $"ChartVm.IsShow.{this.CoinVm.Code}";
-                if (NTMinerRoot.Instance.ServerAppSettingSet.TryGetAppSetting(key, out IAppSetting _appSetting)) {
+                if (VirtualRoot.LocalAppSettingSet.TryGetAppSetting(key, out IAppSetting _appSetting)) {
                     _isShow = (bool)_appSetting.Value;
                 }
                 else {
@@ -148,7 +149,12 @@ namespace NTMiner.Vms {
             set {
                 _isFirst = false;
                 _isShow = value;
-                OnPropertyChanged(nameof(IsShow));
+                OnPropertyChanged(nameof(IsShow)); 
+                string key = $"ChartVm.IsShow.{CoinVm.Code}";
+                VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                    Key = key,
+                    Value = _isShow
+                }));
             }
         }
 
@@ -156,7 +162,7 @@ namespace NTMiner.Vms {
         public CoinSnapshotDataViewModel SnapshotDataVm {
             get {
                 if (_snapshotDataVm == null) {
-                    AppContext.Instance.CoinSnapshotDataVms.TryGetSnapshotDataVm(CoinVm.Code, out _snapshotDataVm);
+                    MinerStudioRoot.CoinSnapshotDataVms.TryGetSnapshotDataVm(CoinVm.Code, out _snapshotDataVm);
                 }
                 return _snapshotDataVm;
             }
@@ -214,7 +220,7 @@ namespace NTMiner.Vms {
 
         public void SetAxisLimits(DateTime now) {
             AxisX[0].MaxValue = now.Ticks;
-            AxisX[0].MinValue = now.Ticks - TimeSpan.FromMinutes(NTMinerRoot.SpeedHistoryLengthByMinute).Ticks;
+            AxisX[0].MinValue = now.Ticks - TimeSpan.FromMinutes(NTMinerContext.SpeedHistoryLengthByMinute).Ticks;
             double maxAcceptValue = 0;
             double maxRejectValue = 0;
             if (_acceptValues != null && _acceptValues.Count != 0) {

+ 4 - 3
src/AppModels/Vms/ChartsWindowViewModel.cs → src/AppModels/MinerStudio/Vms/ChartsWindowViewModel.cs

@@ -1,7 +1,8 @@
-using System.Collections.Generic;
+using NTMiner.Vms;
+using System.Collections.Generic;
 using System.Linq;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class ChartsWindowViewModel : ViewModelBase {
         private List<ChartViewModel> _chartVms;
         private int _totalMiningCount;
@@ -46,7 +47,7 @@ namespace NTMiner.Vms {
             get {
                 if (_chartVms == null) {
                     _chartVms = new List<ChartViewModel>();
-                    foreach (var coinVm in AppContext.Instance.MinerClientsWindowVm.MineCoinVms.AllCoins.OrderBy(a => a.Code)) {
+                    foreach (var coinVm in AppRoot.CoinVms.AllCoins.OrderBy(a => a.Code)) {
                         _chartVms.Add(new ChartViewModel(coinVm));
                     }
                 }

+ 29 - 4
src/AppModels/Vms/CoinSnapshotDataViewModel.cs → src/AppModels/MinerStudio/Vms/CoinSnapshotDataViewModel.cs

@@ -1,10 +1,14 @@
 using NTMiner.Core;
 using NTMiner.Core.MinerServer;
+using NTMiner.Vms;
 using System;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class CoinSnapshotDataViewModel : ViewModelBase {
         private readonly CoinSnapshotData _data;
+        private string _speedValueText = "0.0";
+        private string _speedUnit = "H/s";
+        private string _speedText = "0.0 H/s";
 
         public CoinSnapshotDataViewModel(CoinSnapshotData data) {
             _data = data;
@@ -34,14 +38,35 @@ namespace NTMiner.Vms {
                 if (_data.Speed != value) {
                     _data.Speed = value;
                     OnPropertyChanged(nameof(Speed));
-                    OnPropertyChanged(nameof(SpeedText));
+                    value.ToUnitSpeedText(out string speedValueText, out string speedUnit);
+                    this.SpeedValueText = speedValueText;
+                    this.SpeedUnit = speedUnit;
+                    this.SpeedText = $"{speedValueText} {speedUnit}";
                 }
             }
         }
 
         public string SpeedText {
-            get {
-                return this.Speed.ToUnitSpeedText();
+            get => _speedText;
+            private set {
+                _speedText = value;
+                OnPropertyChanged(nameof(SpeedText));
+            }
+        }
+
+        public string SpeedValueText {
+            get => _speedValueText;
+            private set {
+                _speedValueText = value;
+                OnPropertyChanged(nameof(SpeedValueText));
+            }
+        }
+
+        public string SpeedUnit {
+            get => _speedUnit;
+            private set {
+                _speedUnit = value;
+                OnPropertyChanged(nameof(SpeedUnit));
             }
         }
 

+ 38 - 0
src/AppModels/MinerStudio/Vms/CoinSnapshotViewModel.cs

@@ -0,0 +1,38 @@
+using NTMiner.Core.MinerServer;
+using NTMiner.Vms;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class CoinSnapshotViewModel : ViewModelBase {
+        public static readonly CoinSnapshotViewModel PleaseSelect = new CoinSnapshotViewModel(CoinViewModel.PleaseSelect, new CoinSnapshotDataViewModel(CoinSnapshotData.CreateEmpty(CoinViewModel.PleaseSelect.Code)));
+
+        private CoinViewModel _coinVm;
+        private CoinSnapshotDataViewModel _coinSnapshotDataVm;
+
+        public CoinSnapshotViewModel(CoinViewModel coinVm, CoinSnapshotDataViewModel coinSnapshotDataVm) {
+            _coinVm = coinVm;
+            _coinSnapshotDataVm = coinSnapshotDataVm;
+        }
+
+        public CoinViewModel CoinVm {
+            get => _coinVm;
+            set {
+                _coinVm = value;
+                OnPropertyChanged(nameof(CoinVm));
+            }
+        }
+
+        public CoinSnapshotDataViewModel CoinSnapshotDataVm {
+            get => _coinSnapshotDataVm;
+            set {
+                _coinSnapshotDataVm = value;
+                OnPropertyChanged(nameof(CoinSnapshotDataVm));
+            }
+        }
+
+        public bool IsPleaseSelect {
+            get {
+                return this == PleaseSelect;
+            }
+        }
+    }
+}

+ 40 - 0
src/AppModels/MinerStudio/Vms/ColumnsShowSelectViewModel.cs

@@ -0,0 +1,40 @@
+using NTMiner.Vms;
+using System;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class ColumnsShowSelectViewModel : ViewModelBase {
+        private ColumnsShowViewModel _selectedResult;
+        public readonly Action<ColumnsShowViewModel> OnOk;
+
+        public ICommand HideView { get; set; }
+
+        [Obsolete(message: NTKeyword.WpfDesignOnly, error: true)]
+        public ColumnsShowSelectViewModel() {
+            if (!WpfUtil.IsInDesignMode) {
+                throw new InvalidProgramException(NTKeyword.WpfDesignOnly);
+            }
+        }
+
+        public ColumnsShowSelectViewModel(ColumnsShowViewModel selected, Action<ColumnsShowViewModel> onOk) {
+            _selectedResult = selected;
+            OnOk = onOk;
+        }
+
+        public ColumnsShowViewModel SelectedResult {
+            get => _selectedResult;
+            set {
+                if (_selectedResult != value) {
+                    _selectedResult = value;
+                    OnPropertyChanged(nameof(SelectedResult));
+                }
+            }
+        }
+
+        public MinerStudioRoot.ColumnsShowViewModels ColumnsShowVms {
+            get {
+                return MinerStudioRoot.ColumnsShowVms;
+            }
+        }
+    }
+}

+ 181 - 74
src/AppModels/Vms/ColumnsShowViewModel.cs → src/AppModels/MinerStudio/Vms/ColumnsShowViewModel.cs

@@ -1,5 +1,5 @@
-using NTMiner.Core;
-using NTMiner.Core.MinerServer;
+using NTMiner.Core.MinerStudio;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
@@ -7,8 +7,8 @@ using System.Linq;
 using System.Reflection;
 using System.Windows.Input;
 
-namespace NTMiner.Vms {
-    public class ColumnsShowViewModel : ViewModelBase, IColumnsShow, IEditableViewModel {
+namespace NTMiner.MinerStudio.Vms {
+    public class ColumnsShowViewModel : ViewModelBase, IColumnsShow {
         public class ColumnItem : ViewModelBase {
             private readonly ColumnsShowViewModel _vm;
 
@@ -41,8 +41,8 @@ namespace NTMiner.Vms {
         private Guid _id;
         private string _columnsShowName;
         private bool _work;
+        private bool _workerName;
         private bool _minerName;
-        private bool _clientName;
         private bool _minerIp;
         private bool _localIp;
         private bool _minerGroup;
@@ -97,40 +97,19 @@ namespace NTMiner.Vms {
         private bool _cpuStopTemperature;
         private bool _cpuLETemperatureSeconds;
         private bool _cpuStartTemperature;
-
+        private bool _cpuPerformance;
+        private bool _cpuTemperature;
+        private bool _mACAddress;
+        private bool _isRaiseHighCpuEvent;
+        private bool _highCpuPercent;
+        private bool _highCpuSeconds;
+        private bool _kernelSelfRestartCount;
+        private bool _isOuterUserEnabled;
+        private bool _outerUserId;
         private List<ColumnItem> _columnItems = null;
 
-        public List<ColumnItem> ColumnItems {
-            get {
-                if (_columnItems == null) {
-                    Type boolType = typeof(bool);
-                    var properties = new List<PropertyInfo>(typeof(ColumnsShowViewModel).GetProperties().Where(a => a.PropertyType == boolType && a.CanRead && a.CanWrite && a.GetCustomAttributes(typeof(DescriptionAttribute), inherit: false).Length != 0));
-                    _columnItems = new List<ColumnItem>(properties.Select(a => new ColumnItem(a, this) {
-                        IsChecked = (bool)a.GetValue(this, null)
-                    }));
-                }
-                return _columnItems;
-            }
-        }
-
-        private void OnColumnItemChanged(string propertyName) {
-            OnPropertyChanged(propertyName);
-            ColumnItem item = this.ColumnItems.FirstOrDefault(a => a.PropertyInfo.Name == propertyName);
-            if (item != null) {
-                item.OnPropertyChanged(nameof(item.IsChecked));
-            }
-            RpcRoot.Server.ColumnsShowService.AddOrUpdateColumnsShowAsync(new ColumnsShowData().Update(this), (response, exception) => {
-                if (!response.IsSuccess()) {
-                    VirtualRoot.Out.ShowError(response.ReadMessage(exception), autoHideSeconds: 4);
-                }
-            });
-        }
-
         public ICommand Hide { get; private set; }
-
         public ICommand Remove { get; private set; }
-        public ICommand Edit { get; private set; }
-        public ICommand Save { get; private set; }
 
         [Obsolete(message: NTKeyword.WpfDesignOnly, error: true)]
         public ColumnsShowViewModel() {
@@ -147,26 +126,13 @@ namespace NTMiner.Vms {
                     propertyInfo.SetValue(this, false, null);
                 }
             });
-            this.Save = new DelegateCommand(() => {
-                if (NTMinerRoot.Instance.ColumnsShowSet.Contains(this.Id)) {
-                    VirtualRoot.Execute(new UpdateColumnsShowCommand(this));
-                    VirtualRoot.Out.ShowSuccess($"保存成功");
-                }
-                else {
-                    VirtualRoot.Execute(new AddColumnsShowCommand(this));
-                }
-                VirtualRoot.Execute(new CloseWindowCommand(this.Id));
-            });
-            this.Edit = new DelegateCommand<FormType?>((formType) => {
-                VirtualRoot.Execute(new ColumnsShowEditCommand(formType ?? FormType.Edit, this));
-            });
             this.Remove = new DelegateCommand(() => {
                 if (this.Id == Guid.Empty) {
                     this.ShowSoftDialog(new DialogWindowViewModel(message: "该项不能删除", title: "警告", icon: "Icon_Error"));
                     return;
                 }
-                this.ShowSoftDialog(new DialogWindowViewModel(message: $"您确定删除{this.ColumnsShowName}吗?", title: "确认", onYes: () => {
-                    VirtualRoot.Execute(new RemoveColumnsShowCommand(this.Id));
+                this.ShowSoftDialog(new DialogWindowViewModel(message: $"您确定删除 “{this.ColumnsShowName}” 列分组吗?", title: "确认", onYes: () => {
+                    NTMinerContext.Instance.MinerStudioContext.ColumnsShowSet.Remove(this.Id);
                 }));
             });
         }
@@ -174,8 +140,8 @@ namespace NTMiner.Vms {
         public ColumnsShowViewModel(IColumnsShow data) : this(data.GetId()) {
             _columnsShowName = data.ColumnsShowName;
             _work = data.Work;
+            _workerName = data.WorkerName;
             _minerName = data.MinerName;
-            _clientName = data.ClientName;
             _minerIp = data.MinerIp;
             _localIp = data.LocalIp;
             _minerGroup = data.MinerGroup;
@@ -230,6 +196,37 @@ namespace NTMiner.Vms {
             _cpuStopTemperature = data.CpuStopTemperature;
             _cpuLETemperatureSeconds = data.CpuLETemperatureSeconds;
             _cpuStartTemperature = data.CpuStartTemperature;
+            _cpuPerformance = data.CpuPerformance;
+            _cpuTemperature = data.CpuTemperature;
+            _mACAddress = data.MACAddress;
+            _isRaiseHighCpuEvent = data.IsRaiseHighCpuEvent;
+            _highCpuPercent = data.HighCpuPercent;
+            _highCpuSeconds = data.HighCpuSeconds;
+            _kernelSelfRestartCount = data.KernelSelfRestartCount;
+            _isOuterUserEnabled = data.IsOuterUserEnabled;
+            _outerUserId = data.OuterUserId;
+        }
+
+        public List<ColumnItem> ColumnItems {
+            get {
+                if (_columnItems == null) {
+                    Type boolType = typeof(bool);
+                    var properties = new List<PropertyInfo>(typeof(ColumnsShowViewModel).GetProperties().Where(a => a.PropertyType == boolType && a.CanRead && a.CanWrite && a.GetCustomAttributes(typeof(DescriptionAttribute), inherit: false).Length != 0));
+                    _columnItems = new List<ColumnItem>(properties.Select(a => new ColumnItem(a, this) {
+                        IsChecked = (bool)a.GetValue(this, null)
+                    }));
+                }
+                return _columnItems;
+            }
+        }
+
+        private void OnColumnItemChanged(string propertyName) {
+            OnPropertyChanged(propertyName);
+            ColumnItem item = this.ColumnItems.FirstOrDefault(a => a.PropertyInfo.Name == propertyName);
+            if (item != null) {
+                item.OnPropertyChanged(nameof(item.IsChecked));
+            }
+            NTMinerContext.Instance.MinerStudioContext.ColumnsShowSet.AddOrUpdate(new ColumnsShowData().Update(this));
         }
 
         public bool IsPleaseSelect {
@@ -320,19 +317,7 @@ namespace NTMiner.Vms {
             }
         }
 
-        public const string CLIENT_NAME = "矿机名";
-        [Description(CLIENT_NAME)]
-        public bool ClientName {
-            get { return _clientName; }
-            set {
-                if (_clientName != value) {
-                    _clientName = value;
-                    OnColumnItemChanged(nameof(ClientName));
-                }
-            }
-        }
-
-        public const string MINER_NAME = "群控名";
+        public const string MINER_NAME = "矿机名";
         [Description(MINER_NAME)]
         public bool MinerName {
             get { return _minerName; }
@@ -344,7 +329,19 @@ namespace NTMiner.Vms {
             }
         }
 
-        public const string MINER_IP = "IP";
+        public const string WORKER_NAME = "群控名";
+        [Description(WORKER_NAME)]
+        public bool WorkerName {
+            get { return _workerName; }
+            set {
+                if (_workerName != value) {
+                    _workerName = value;
+                    OnColumnItemChanged(nameof(WorkerName));
+                }
+            }
+        }
+
+        public const string MINER_IP = "外网IP";
         [Description(MINER_IP)]
         public bool MinerIp {
             get { return _minerIp; }
@@ -357,7 +354,7 @@ namespace NTMiner.Vms {
         }
 
         public const string LOCAL_IP = "内网IP?";
-        public const string LOCAL_IP_TOOLTIP = "挖矿端从2.6.6.6开始上报内网IP";
+        public const string LOCAL_IP_TOOLTIP = "内网IP,挖矿端从2.6.6.6开始上报内网IP";
         [Description(LOCAL_IP)]
         public bool LocalIp {
             get { return _localIp; }
@@ -368,7 +365,9 @@ namespace NTMiner.Vms {
                 }
             }
         }
+        public const string WINDOWS_LOGIN_NAME = "远程桌面登录名";
 
+        public const string WINDOWS_LOGIN_PASSWORD = "远程桌面密码";
         public const string WINDOWS_LOGIN_NAME_AND_PASSWORD = "远程桌面";
         [Description(WINDOWS_LOGIN_NAME_AND_PASSWORD)]
         public bool WindowsLoginNameAndPassword {
@@ -879,7 +878,7 @@ namespace NTMiner.Vms {
             set {
                 if (_isAutoStopByCpu != value) {
                     _isAutoStopByCpu = value;
-                    OnPropertyChanged(nameof(IsAutoStopByCpu));
+                    OnColumnItemChanged(nameof(IsAutoStopByCpu));
                 }
             }
         }
@@ -891,7 +890,7 @@ namespace NTMiner.Vms {
             set {
                 if (_isAutoStartByCpu != value) {
                     _isAutoStartByCpu = value;
-                    OnPropertyChanged(nameof(IsAutoStartByCpu));
+                    OnColumnItemChanged(nameof(IsAutoStartByCpu));
                 }
             }
         }
@@ -903,19 +902,19 @@ namespace NTMiner.Vms {
             set {
                 if (_cpuGETemperatureSeconds != value) {
                     _cpuGETemperatureSeconds = value;
-                    OnPropertyChanged(nameof(CpuGETemperatureSeconds));
+                    OnColumnItemChanged(nameof(CpuGETemperatureSeconds));
                 }
             }
         }
 
-        public const string CPU_STOP_TEMPERATURE = "CPU高温";
+        public const string CPU_STOP_TEMPERATURE = "CPU高温度数";
         [Description(CPU_STOP_TEMPERATURE)]
         public bool CpuStopTemperature {
             get => _cpuStopTemperature;
             set {
                 if (_cpuStopTemperature != value) {
                     _cpuStopTemperature = value;
-                    OnPropertyChanged(nameof(CpuStopTemperature));
+                    OnColumnItemChanged(nameof(CpuStopTemperature));
                 }
             }
         }
@@ -927,19 +926,127 @@ namespace NTMiner.Vms {
             set {
                 if (_cpuLETemperatureSeconds != value) {
                     _cpuLETemperatureSeconds = value;
-                    OnPropertyChanged(nameof(CpuLETemperatureSeconds));
+                    OnColumnItemChanged(nameof(CpuLETemperatureSeconds));
                 }
             }
         }
 
-        public const string CPU_START_TEMPERATURE = "CPU低温";
+        public const string CPU_START_TEMPERATURE = "CPU低温度数";
         [Description(CPU_START_TEMPERATURE)]
         public bool CpuStartTemperature {
             get => _cpuStartTemperature;
             set {
                 if (_cpuStartTemperature != value) {
                     _cpuStartTemperature = value;
-                    OnPropertyChanged(nameof(CpuStartTemperature));
+                    OnColumnItemChanged(nameof(CpuStartTemperature));
+                }
+            }
+        }
+
+        public const string CPU_PERFORMANCE = "CPU使用率";
+        [Description(CPU_PERFORMANCE)]
+        public bool CpuPerformance {
+            get => _cpuPerformance;
+            set {
+                if (_cpuPerformance != value) {
+                    _cpuPerformance = value;
+                    OnColumnItemChanged(nameof(CpuPerformance));
+                }
+            }
+        }
+
+        public const string CPU_TEMPERATURE = "CPU温度";
+        [Description(CPU_TEMPERATURE)]
+        public bool CpuTemperature {
+            get => _cpuTemperature;
+            set {
+                if (_cpuTemperature != value) {
+                    _cpuTemperature = value;
+                    OnColumnItemChanged(nameof(CpuTemperature));
+                }
+            }
+        }
+
+        public const string MAC_ADDRESS = "网卡地址";
+        [Description(MAC_ADDRESS)]
+        public bool MACAddress {
+            get => _mACAddress;
+            set {
+                if (_mACAddress != value) {
+                    _mACAddress = value;
+                    OnColumnItemChanged(nameof(MACAddress));
+                }
+            }
+        }
+
+        public const string IS_RAISE_HIGH_CPU_EVENT = "CPU使用率高时告警";
+        [Description(IS_RAISE_HIGH_CPU_EVENT)]
+        public bool IsRaiseHighCpuEvent {
+            get => _isRaiseHighCpuEvent;
+            set {
+                if (_isRaiseHighCpuEvent != value) {
+                    _isRaiseHighCpuEvent = value;
+                    OnColumnItemChanged(nameof(IsRaiseHighCpuEvent));
+                }
+            }
+        }
+
+        public const string HIGH_CPU_PERCENT = "CPU使用率高阈值";
+        [Description(HIGH_CPU_PERCENT)]
+        public bool HighCpuPercent {
+            get => _highCpuPercent;
+            set {
+                if (_highCpuPercent != value) {
+                    _highCpuPercent = value;
+                    OnColumnItemChanged(nameof(HighCpuPercent));
+                }
+            }
+        }
+
+        public const string HIGH_CPU_SECONDS = "CPU使用率高持续多少秒时告警";
+        [Description(HIGH_CPU_SECONDS)]
+        public bool HighCpuSeconds {
+            get => _highCpuSeconds;
+            set {
+                if (_highCpuSeconds != value) {
+                    _highCpuSeconds = value;
+                    OnColumnItemChanged(nameof(HighCpuSeconds));
+                }
+            }
+        }
+
+        public const string KERNEL_SELF_RESTART_COUNT = "内核重启次数";
+        [Description(KERNEL_SELF_RESTART_COUNT)]
+        public bool KernelSelfRestartCount {
+            get => _kernelSelfRestartCount;
+            set {
+                if (_kernelSelfRestartCount != value) {
+                    _kernelSelfRestartCount = value;
+                    OnColumnItemChanged(nameof(KernelSelfRestartCount));
+                }
+            }
+        }
+
+        public const string IS_OUTER_USER_ENABLED = "外网群控";
+        [Description(IS_OUTER_USER_ENABLED)]
+        public bool IsOuterUserEnabled {
+            get => _isOuterUserEnabled;
+            set {
+                if (_isOuterUserEnabled != value) {
+                    _isOuterUserEnabled = value;
+                    OnColumnItemChanged(nameof(IsOuterUserEnabled));
+                }
+            }
+        }
+
+        public const string OUTER_USERID = "外网群控用户";
+        [Description(OUTER_USERID)]
+        public bool OuterUserId {
+            get => _outerUserId;
+            set {
+                if (_outerUserId != value) {
+                    _outerUserId = value;
+                    OnColumnItemChanged(nameof(OuterUserId));
                 }
             }
         }

+ 69 - 65
src/AppModels/Vms/GpuProfilesPageViewModel.cs → src/AppModels/MinerStudio/Vms/GpuProfilesPageViewModel.cs

@@ -1,7 +1,8 @@
 using NTMiner.Core;
-using NTMiner.JsonDb;
 using NTMiner.Core.MinerClient;
 using NTMiner.Core.Profile;
+using NTMiner.JsonDb;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -9,7 +10,7 @@ using System.Windows;
 using System.Windows.Input;
 using System.Windows.Media;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class GpuProfilesPageViewModel : ViewModelBase {
         public readonly Guid Id = Guid.NewGuid();
         private Geometry _gpuIcon;
@@ -37,7 +38,7 @@ namespace NTMiner.Vms {
                 throw new InvalidProgramException();
             }
             _minerClientVm = minerClientsWindowVm.SelectedMinerClients[0];
-            if (AppContext.Instance.CoinVms.TryGetCoinVm(_minerClientVm.MainCoinCode, out CoinViewModel outCoinVm)) {
+            if (AppRoot.CoinVms.TryGetCoinVm(_minerClientVm.MainCoinCode, out CoinViewModel outCoinVm)) {
                 this._coinVm = outCoinVm;
             }
             if (this._coinVm == null) {
@@ -65,76 +66,79 @@ namespace NTMiner.Vms {
                 }
                 string json = VirtualRoot.JsonSerializer.Serialize(jsonObj);
                 foreach (var client in minerClientsWindowVm.SelectedMinerClients) {
-                    RpcRoot.Client.NTMinerDaemonService.SaveGpuProfilesJsonAsync(client.MinerIp, json);
+                    MinerStudioRoot.MinerStudioService.SaveGpuProfilesJsonAsync(client, json);
                 }
-                VirtualRoot.Out.ShowSuccess("应用成功,请观察效果");
                 VirtualRoot.Execute(new CloseWindowCommand(this.Id));
             });
-            RpcRoot.Client.NTMinerDaemonService.GetGpuProfilesJsonAsync(_minerClientVm.MinerIp, (data, e) => {
-                _data = data;
-                if (e != null) {
-                    Write.UserError(e.Message);
+        }
+
+        public void SetData(GpuProfilesJsonDb data) {
+            _data = data;
+            if (data != null) {
+                string iconName;
+                GpuType gpuType = _minerClientVm.GpuType;
+                if (gpuType == GpuType.Empty) {
+                    gpuType = data.GpuType;
+                }
+                IsEnabled = data.Gpus != null && data.Gpus.Length != 0;
+                RedText = "超频有风险,操作需谨慎";
+                switch (_minerClientVm.GpuType) {
+                    case GpuType.NVIDIA:
+                        iconName = "Icon_Nvidia";
+                        GpuIconFill = "Red";
+                        break;
+                    case GpuType.AMD:
+                        iconName = "Icon_Amd";
+                        GpuIconFill = "Green";
+                        break;
+                    case GpuType.Empty:
+                    default:
+                        iconName = "Icon_GpuEmpty";
+                        GpuIconFill = "Gray";
+                        break;
                 }
-                else if (data != null) {
-                    string iconName;
-                    switch (_minerClientVm.GpuType) {
-                        case GpuType.NVIDIA:
-                        case GpuType.AMD:
-                            iconName = "Icon_Nvidia";
-                            GpuIconFill = "Green";
-                            RedText = "超频有风险,操作需谨慎";
-                            IsEnabled = true;
-                            break;
-                        case GpuType.Empty:
-                        default:
-                            iconName = "Icon_GpuEmpty";
-                            GpuIconFill = "Gray";
-                            RedText = "没有矿卡或矿卡未驱动";
-                            IsEnabled = false;
-                            break;
+                GpuIcon = AppUtil.GetResource<Geometry>(iconName);
+                foreach (var coinVm in AppRoot.CoinVms.MainCoins) {
+                    var coinOverClock = data.CoinOverClocks.FirstOrDefault(a => a.CoinId == coinVm.Id);
+                    var gpuProfiles = data.GpuProfiles.Where(a => a.CoinId == coinVm.Id).ToArray();
+                    if (coinOverClock == null) {
+                        coinOverClock = new CoinOverClockData() {
+                            CoinId = coinVm.Id,
+                            IsOverClockEnabled = false,
+                            IsOverClockGpuAll = true
+                        };
                     }
-                    GpuIcon = AppUtil.GetResource<Geometry>(iconName);
-                    foreach (var coinVm in AppContext.Instance.CoinVms.MainCoins) {
-                        var coinOverClock = data.CoinOverClocks.FirstOrDefault(a => a.CoinId == coinVm.Id);
-                        var gpuProfiles = data.GpuProfiles.Where(a => a.CoinId == coinVm.Id).ToArray();
-                        if (coinOverClock == null) {
-                            coinOverClock = new CoinOverClockData() {
-                                CoinId = coinVm.Id,
-                                IsOverClockEnabled = false,
-                                IsOverClockGpuAll = true
-                            };
+                    coinVm.IsOverClockEnabled = coinOverClock.IsOverClockEnabled;
+                    coinVm.IsOverClockGpuAll = coinOverClock.IsOverClockGpuAll;
+                    List<GpuProfileViewModel> gpuProfileVms = new List<GpuProfileViewModel>();
+                    GpuProfileViewModel gpuAllProfileVm = null;
+                    #region
+                    foreach (var gpu in data.Gpus.OrderBy(a => a.Index)) {
+                        var gpuProfile = gpuProfiles.FirstOrDefault(a => a.Index == gpu.Index);
+                        if (gpuProfile == null) {
+                            gpuProfile = new GpuProfileData(coinVm.Id, gpu.Index);
                         }
-                        coinVm.IsOverClockEnabled = coinOverClock.IsOverClockEnabled;
-                        coinVm.IsOverClockGpuAll = coinOverClock.IsOverClockGpuAll;
-                        List<GpuProfileViewModel> gpuProfileVms = new List<GpuProfileViewModel>();
-                        GpuProfileViewModel gpuAllProfileVm = null;
-                        #region
-                        foreach (var gpu in data.Gpus.OrderBy(a => a.Index)) {
-                            var gpuProfile = gpuProfiles.FirstOrDefault(a => a.Index == gpu.Index);
-                            if (gpuProfile == null) {
-                                gpuProfile = new GpuProfileData(coinVm.Id, gpu.Index);
-                            }
-                            var gpuVm = new GpuViewModel(gpu, data.Gpus);
-                            if (gpu.Index == NTMinerRoot.GpuAllId) {
-                                gpuAllProfileVm = new GpuProfileViewModel(gpuProfile, gpuVm);
-                            }
-                            else {
-                                gpuProfileVms.Add(new GpuProfileViewModel(gpuProfile, gpuVm));
-                            }
+                        var gpuVm = new GpuViewModel(gpu, data.Gpus);
+                        if (gpu.Index == NTMinerContext.GpuAllId) {
+                            gpuAllProfileVm = new GpuProfileViewModel(gpuProfile, gpuVm);
                         }
-                        if (gpuAllProfileVm == null) {
-                            gpuAllProfileVm = new GpuProfileViewModel(
-                                new GpuProfileData(coinVm.Id, NTMinerRoot.GpuAllId), new GpuViewModel(new GpuData {
-                                    Index = NTMinerRoot.GpuAllId,
-                                    Name = "All"
-                                }, data.Gpus));
+                        else {
+                            gpuProfileVms.Add(new GpuProfileViewModel(gpuProfile, gpuVm));
                         }
-                        #endregion
-                        coinVm.GpuAllProfileVm = gpuAllProfileVm;
-                        coinVm.GpuProfileVms = gpuProfileVms;
                     }
+                    if (gpuAllProfileVm == null) {
+                        gpuAllProfileVm = new GpuProfileViewModel(
+                            new GpuProfileData(coinVm.Id, NTMinerContext.GpuAllId), new GpuViewModel(new GpuData {
+                                GpuType = gpuType,
+                                Index = NTMinerContext.GpuAllId,
+                                Name = "All"
+                            }, data.Gpus));
+                    }
+                    #endregion
+                    coinVm.GpuAllProfileVm = gpuAllProfileVm;
+                    coinVm.GpuProfileVms = gpuProfileVms;
                 }
-            });
+            }
         }
 
         public MinerClientViewModel MinerClientVm {
@@ -165,9 +169,9 @@ namespace NTMiner.Vms {
             }
         }
 
-        public AppContext.CoinViewModels CoinVms {
+        public AppRoot.CoinViewModels CoinVms {
             get {
-                return AppContext.Instance.CoinVms;
+                return AppRoot.CoinVms;
             }
         }
 

+ 2 - 1
src/AppModels/Vms/GpuSpeedDataViewModel.cs → src/AppModels/MinerStudio/Vms/GpuSpeedDataViewModel.cs

@@ -1,9 +1,10 @@
 using NTMiner.Core;
 using NTMiner.Core.MinerClient;
+using NTMiner.Vms;
 using System;
 using System.Windows.Media;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class GpuSpeedDataViewModel : ViewModelBase, IGpuSpeedData {
         private readonly GpuSpeedData _data;
         private SolidColorBrush _temperatureForeground = MinerClientViewModel.DefaultForeground;

+ 2 - 1
src/AppModels/Vms/GpuSpeedDataViewModels.cs → src/AppModels/MinerStudio/Vms/GpuSpeedDataViewModels.cs

@@ -1,9 +1,10 @@
 using NTMiner.Core.MinerClient;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Windows.Media;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class GpuSpeedDataViewModels : ViewModelBase {
         private readonly List<GpuSpeedDataViewModel> _gpuSpeeds = new List<GpuSpeedDataViewModel>();
         private string _mainCoinCode;

+ 11 - 0
src/AppModels/MinerStudio/Vms/IWsStateViewModel.cs

@@ -0,0 +1,11 @@
+using System;
+
+namespace NTMiner.MinerStudio.Vms {
+    public interface IWsStateViewModel {
+        bool IsWsOnline { get; set; }
+        string WsDescription { get; set; }
+        int WsNextTrySecondsDelay { get; set; }
+        DateTime WsLastTryOn { get; set; }
+        bool IsConnecting { get; set; }
+    }
+}

+ 71 - 0
src/AppModels/MinerStudio/Vms/LocalIpConfigViewModel.cs

@@ -0,0 +1,71 @@
+using NTMiner.Core.MinerClient;
+using NTMiner.Vms;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class LocalIpConfigViewModel : ViewModelBase {
+        public readonly Guid Id = Guid.NewGuid();
+        private List<LocalIpViewModel> _localIpVms = new List<LocalIpViewModel>();
+        private bool _isLoading = true;
+
+        public ICommand Save { get; private set; }
+
+        public LocalIpConfigViewModel(MinerClientViewModel minerClientVm) {
+            this.MinerClientVm = minerClientVm;
+            this.Save = new DelegateCommand<LocalIpViewModel>((vm) => {
+                if (!vm.IsAutoDNSServer) {
+                    if (vm.DNSServer0Vm.IsAnyEmpty) {
+                        vm.DNSServer0Vm.SetAddress(NTKeyword.DNSServer0);
+                    }
+                    if (vm.DNSServer1Vm.IsAnyEmpty) {
+                        vm.DNSServer1Vm.SetAddress(NTKeyword.DNSServer1);
+                    }
+                }
+                MinerStudioRoot.MinerStudioService.SetLocalIpsAsync(minerClientVm, _localIpVms.Select(a => LocalIpInput.Create(a, a.IsAutoDNSServer)).ToList());
+                if (_localIpVms.Count == 1) {
+                    VirtualRoot.Execute(new CloseWindowCommand(this.Id));
+                }
+            }, (vm) => vm.IsChanged);
+        }
+
+        public bool IsLoading {
+            get {
+                return _isLoading;
+            }
+            set {
+                if (_isLoading != value) {
+                    _isLoading = value;
+                    OnPropertyChanged(nameof(IsLoading));
+                }
+            }
+        }
+
+        public MinerClientViewModel MinerClientVm {
+            get; private set;
+        }
+
+        public string DNSServer0Tooltip {
+            get {
+                return "留空表示使用腾讯:" + NTKeyword.DNSServer0;
+            }
+        }
+
+        public string DNSServer1Tooltip {
+            get {
+                return "留空表示使用阿里:" + NTKeyword.DNSServer1;
+            }
+        }
+
+        public List<LocalIpViewModel> LocalIpVms {
+            get => _localIpVms;
+            set {
+                _localIpVms = value;
+                OnPropertyChanged(nameof(LocalIpVms));
+                this.IsLoading = false;
+            }
+        }
+    }
+}

+ 50 - 0
src/AppModels/MinerStudio/Vms/MineWorkSelectViewModel.cs

@@ -0,0 +1,50 @@
+using NTMiner.Vms;
+using System;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class MineWorkSelectViewModel : ViewModelBase {
+        private MineWorkViewModel _selectedResult;
+        private string _description;
+        public readonly Action<MineWorkViewModel> OnOk;
+
+        public ICommand HideView { get; set; }
+
+        [Obsolete(message: NTKeyword.WpfDesignOnly, error: true)]
+        public MineWorkSelectViewModel() {
+            if (!WpfUtil.IsInDesignMode) {
+                throw new InvalidProgramException(NTKeyword.WpfDesignOnly);
+            }
+        }
+
+        public MineWorkSelectViewModel(string description, MineWorkViewModel selected, Action<MineWorkViewModel> onOk) {
+            _description = description;
+            _selectedResult = selected;
+            OnOk = onOk;
+        }
+
+        public string Description {
+            get => _description;
+            set {
+                _description = value;
+                OnPropertyChanged(nameof(Description));
+            }
+        }
+
+        public MineWorkViewModel SelectedResult {
+            get => _selectedResult;
+            set {
+                if (_selectedResult != value) {
+                    _selectedResult = value;
+                    OnPropertyChanged(nameof(SelectedResult));
+                }
+            }
+        }
+
+        public MinerStudioRoot.MineWorkViewModels MineWorkVms {
+            get {
+                return MinerStudioRoot.MineWorkVms;
+            }
+        }
+    }
+}

+ 269 - 0
src/AppModels/MinerStudio/Vms/MineWorkViewModel.cs

@@ -0,0 +1,269 @@
+using NTMiner.Core;
+using NTMiner.JsonDb;
+using NTMiner.Vms;
+using System;
+using System.IO;
+using System.Linq;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class MineWorkViewModel : ViewModelBase, IMineWork, IEditableViewModel {
+        public static readonly MineWorkViewModel PleaseSelect = new MineWorkViewModel(Guid.Empty) {
+            _name = "不指定"
+        };
+
+        private Guid _id;
+        private string _name;
+        private string _description;
+        private string _serverJsonSha1;
+
+        public string Sha1 { get; private set; }
+
+        public ICommand Edit { get; private set; }
+        public ICommand Remove { get; private set; }
+        public ICommand Save { get; private set; }
+
+        [Obsolete(message: NTKeyword.WpfDesignOnly, error: true)]
+        public MineWorkViewModel() {
+            if (!WpfUtil.IsInDesignMode) {
+                throw new InvalidProgramException(NTKeyword.WpfDesignOnly);
+            }
+        }
+
+        public MineWorkViewModel(IMineWork mineWork) : this(mineWork.GetId()) {
+            _name = mineWork.Name;
+            _description = mineWork.Description;
+            _serverJsonSha1 = mineWork.ServerJsonSha1;
+        }
+
+        public MineWorkViewModel(MineWorkViewModel vm) : this((IMineWork)vm) {
+            Sha1 = vm.Sha1;
+        }
+
+        public MineWorkViewModel(Guid id) {
+            _id = id;
+            this.Save = new DelegateCommand(() => {
+                if (this.Id == Guid.Empty) {
+                    return;
+                }
+                if (string.IsNullOrEmpty(this.Name)) {
+                    VirtualRoot.Out.ShowError("作业名称是必须的", autoHideSeconds: 4);
+                }
+                bool isMinerProfileChanged = false;
+                bool isShowEdit = false;
+                MineWorkData mineWorkData = new MineWorkData().Update(this); 
+                if (MinerStudioRoot.MineWorkVms.TryGetMineWorkVm(this.Id, out MineWorkViewModel vm)) {
+                    string sha1 = NTMinerContext.Instance.MinerProfile.GetSha1();
+                    // 如果作业设置变更了则一定变更了
+                    if (this.Sha1 != sha1) {
+                        isMinerProfileChanged = true;
+                    }
+                    else {
+                        // 如果作业设置没变更但作业引用的服务器数据库记录状态变更了则变更了
+                        LocalJsonDb localJsonObj = new LocalJsonDb(NTMinerContext.Instance, mineWorkData);
+                        ServerJsonDb serverJsonObj = new ServerJsonDb(NTMinerContext.Instance, localJsonObj);
+                        var serverJson = VirtualRoot.JsonSerializer.Serialize(serverJsonObj);
+                        sha1 = HashUtil.Sha1(serverJson);
+                        if (sha1 != this.ServerJsonSha1) {
+                            isMinerProfileChanged = true;
+                        }
+                    }
+                }
+                else {
+                    isMinerProfileChanged = true;
+                    isShowEdit = true;
+                }
+                if (RpcRoot.IsOuterNet) {
+                    RpcRoot.OfficialServer.UserMineWorkService.AddOrUpdateMineWorkAsync(new MineWorkData().Update(this), (r, ex) => {
+                        if (r.IsSuccess()) {
+                            if (isMinerProfileChanged) {
+                                Write.DevDebug("检测到MinerProfile状态变更");
+                                NTMinerContext.ExportWorkJson(mineWorkData, out string localJson, out string serverJson);
+                                if (!string.IsNullOrEmpty(localJson) && !string.IsNullOrEmpty(serverJson)) {
+                                    RpcRoot.OfficialServer.UserMineWorkService.ExportMineWorkAsync(this.Id, localJson, serverJson, (response, e) => {
+                                        if (response.IsSuccess()) {
+                                            if (isShowEdit) {
+                                                VirtualRoot.RaiseEvent(new MineWorkAddedEvent(Guid.Empty, this));
+                                            }
+                                            else {
+                                                VirtualRoot.RaiseEvent(new MineWorkUpdatedEvent(Guid.Empty, this));
+                                            }
+                                            if (isShowEdit) {
+                                                this.Edit.Execute(FormType.Edit);
+                                            }
+                                        }
+                                        else {
+                                            VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
+                                        }
+                                    });
+                                }
+                                if (mineWorkData.ServerJsonSha1 != this.ServerJsonSha1) {
+                                    this.ServerJsonSha1 = mineWorkData.ServerJsonSha1;
+                                }
+                            }
+                        }
+                        else {
+                            VirtualRoot.Out.ShowError(r.ReadMessage(ex), autoHideSeconds: 4);
+                        }
+                    });
+                }
+                else {
+                    if (isMinerProfileChanged) {
+                        Write.DevDebug("检测到MinerProfile状态变更");
+                        NTMinerContext.ExportWorkJson(mineWorkData, out string localJson, out string serverJson);
+                        if (!string.IsNullOrEmpty(localJson) && !string.IsNullOrEmpty(serverJson)) {
+                            try {
+                                string localJsonFileFullName = HomePath.GetMineWorkLocalJsonFileFullName(this.Id);
+                                string serverJsonFileFullName = HomePath.GetMineWorkServerJsonFileFullName(this.Id);
+                                File.WriteAllText(localJsonFileFullName, localJson);
+                                File.WriteAllText(serverJsonFileFullName, serverJson);
+                            }
+                            catch (Exception e) {
+                                VirtualRoot.Out.ShowError(e.Message, autoHideSeconds: 4);
+                                Logger.ErrorDebugLine(e);
+                            }
+                        }
+                        if (mineWorkData.ServerJsonSha1 != this.ServerJsonSha1) {
+                            this.ServerJsonSha1 = mineWorkData.ServerJsonSha1;
+                        }
+                    }
+                    if (NTMinerContext.Instance.MinerStudioContext.MineWorkSet.Contains(mineWorkData.Id)) {
+                        VirtualRoot.Execute(new UpdateMineWorkCommand(mineWorkData));
+                    }
+                    else {
+                        VirtualRoot.Execute(new AddMineWorkCommand(mineWorkData));
+                    }
+                    if (isShowEdit) {
+                        this.Edit.Execute(FormType.Edit);
+                    }
+                }
+            });
+            this.Edit = new DelegateCommand<FormType?>((formType) => {
+                if (this.Id == Guid.Empty) {
+                    return;
+                }
+                if (!MinerStudioRoot.MineWorkVms.TryGetMineWorkVm(this.Id, out MineWorkViewModel mineWorkVm)) {
+                    WpfUtil.ShowInputDialog("作业名称", string.Empty, string.Empty, workName => {
+                        if (string.IsNullOrEmpty(workName)) {
+                            return "作业名称是必须的";
+                        }
+                        return string.Empty;
+                    }, onOk: workName => {
+                        new MineWorkViewModel(this) { Name = workName }.Save.Execute(null);
+                    });
+                }
+                else {
+                    // 编辑作业前切换上下文
+                    // 根据workId下载json保存到本地并调用LocalJson.Instance.ReInit()
+                    if (RpcRoot.IsOuterNet) {
+                        RpcRoot.OfficialServer.UserMineWorkService.GetLocalJsonAsync(this.Id, (response, e) => {
+                            if (response.IsSuccess()) {
+                                string data = response.Data;
+                                if (!string.IsNullOrEmpty(data)) {
+                                    File.WriteAllText(HomePath.LocalJsonFileFullName, data);
+                                }
+                                NTMinerContext.Instance.ReInitMinerProfile();
+                                this.Sha1 = NTMinerContext.Instance.MinerProfile.GetSha1();
+                                VirtualRoot.Execute(new EditMineWorkCommand(formType ?? FormType.Edit, new MineWorkViewModel(this)));
+                            }
+                        });
+                    }
+                    else {
+                        try {
+                            string localJsonFileFullName = HomePath.GetMineWorkLocalJsonFileFullName(this.Id);
+                            string data = string.Empty;
+                            if (File.Exists(localJsonFileFullName)) {
+                                data = File.ReadAllText(localJsonFileFullName);
+                            }
+                            if (!string.IsNullOrEmpty(data)) {
+                                File.WriteAllText(HomePath.LocalJsonFileFullName, data);
+                            }
+                            else {
+                                File.Delete(HomePath.LocalJsonFileFullName);
+                            }
+                            NTMinerContext.Instance.ReInitMinerProfile();
+                            this.Sha1 = NTMinerContext.Instance.MinerProfile.GetSha1();
+                            VirtualRoot.Execute(new EditMineWorkCommand(formType ?? FormType.Edit, new MineWorkViewModel(this)));
+                        }
+                        catch (Exception e) {
+                            Logger.ErrorDebugLine(e);
+                        }
+                    }
+                }
+            });
+            this.Remove = new DelegateCommand(() => {
+                if (this.Id == Guid.Empty) {
+                    return;
+                }
+                this.ShowSoftDialog(new DialogWindowViewModel(message: $"您确定删除 “{this.Name}” 作业吗?", title: "确认", onYes: () => {
+                    VirtualRoot.Execute(new RemoveMineWorkCommand(this.Id));
+                }));
+            });
+        }
+
+        public Guid GetId() {
+            return this.Id;
+        }
+
+        public Guid Id {
+            get => _id;
+            set {
+                if (_id != value) {
+                    _id = value;
+                    OnPropertyChanged(nameof(Id));
+                }
+            }
+        }
+
+        public bool IsPleaseSelect {
+            get {
+                return this == PleaseSelect;
+            }
+        }
+
+        public string Name {
+            get { return _name; }
+            set {
+                if (_name != value) {
+                    _name = value;
+                    OnPropertyChanged(nameof(Name));
+                    if (this == PleaseSelect) {
+                        return;
+                    }
+                    if (string.IsNullOrEmpty(value)) {
+                        throw new ValidationException("名称是必须的");
+                    }
+                    if (MinerStudioRoot.MineWorkVms.List.Any(a => a.Name == value && a.Id != this.Id)) {
+                        throw new ValidationException("名称重复");
+                    }
+                }
+            }
+        }
+
+        public MinerProfileViewModel MinerProfile {
+            get {
+                return AppRoot.MinerProfileVm;
+            }
+        }
+
+        public string Description {
+            get => _description;
+            set {
+                if (_description != value) {
+                    _description = value;
+                    OnPropertyChanged(nameof(Description));
+                }
+            }
+        }
+
+        public string ServerJsonSha1 {
+            get => _serverJsonSha1;
+            set {
+                if (_serverJsonSha1 != value) {
+                    _serverJsonSha1 = value;
+                    OnPropertyChanged(nameof(ServerJsonSha1));
+                }
+            }
+        }
+    }
+}

+ 7 - 6
src/AppModels/Vms/MinerClientAddViewModel.cs → src/AppModels/MinerStudio/Vms/MinerClientAddViewModel.cs

@@ -1,10 +1,11 @@
-using System;
+using NTMiner.Vms;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Net;
 using System.Windows.Input;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class MinerClientAddViewModel : ViewModelBase {
         public readonly Guid Id = Guid.NewGuid();
         private string _leftIp = "192.168.0.100";
@@ -40,23 +41,23 @@ namespace NTMiner.Vms {
                         VirtualRoot.Out.ShowError("没有IP", autoHideSeconds: 4);
                         return;
                     }
-                    RpcRoot.Server.ClientService.AddClientsAsync(clientIps, (response, e) => {
+                    ((ILocalMinerStudioService)MinerStudioRoot.MinerStudioService).AddClientsAsync(clientIps, (response, e) => {
                         if (!response.IsSuccess()) {
                             VirtualRoot.Out.ShowError(response.ReadMessage(e));
                         }
                         else {
-                            AppContext.Instance.MinerClientsWindowVm.QueryMinerClients();
+                            MinerStudioRoot.MinerClientsWindowVm.QueryMinerClients();
                             VirtualRoot.Execute(new CloseWindowCommand(this.Id));
                         }
                     });
                 }
                 else {
-                    RpcRoot.Server.ClientService.AddClientsAsync(new List<string> { this.LeftIp }, (response, e) => {
+                    ((ILocalMinerStudioService)MinerStudioRoot.MinerStudioService).AddClientsAsync(new List<string> { this.LeftIp }, (response, e) => {
                         if (!response.IsSuccess()) {
                             VirtualRoot.Out.ShowError(response.ReadMessage(e));
                         }
                         else {
-                            AppContext.Instance.MinerClientsWindowVm.QueryMinerClients();
+                            MinerStudioRoot.MinerClientsWindowVm.QueryMinerClients();
                             VirtualRoot.Execute(new CloseWindowCommand(this.Id));
                         }
                     });

+ 12 - 7
src/AppModels/Vms/MinerClientFinderConfigViewModel.cs → src/AppModels/MinerStudio/Vms/MinerClientFinderConfigViewModel.cs

@@ -1,8 +1,10 @@
 using NTMiner.Core;
+using NTMiner.Vms;
 using System;
+using System.IO;
 using System.Windows.Input;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class MinerClientFinderConfigViewModel : ViewModelBase {
         public readonly Guid Id = Guid.NewGuid();
         public ICommand Save { get; private set; }
@@ -26,12 +28,15 @@ namespace NTMiner.Vms {
                     Logger.ErrorDebugLine(e);
                 }
             });
-            if (NTMinerRoot.Instance.ServerAppSettingSet.TryGetAppSetting(NTKeyword.MinerClientFinderFileNameAppSettingKey, out IAppSetting appSetting) && appSetting.Value != null) {
-                _fileName = appSetting.Value.ToString();
-            }
-            else {
-                _fileName = NTKeyword.MinerClientFinderFileName;
-            }
+            RpcRoot.OfficialServer.FileUrlService.GetMinerClientFinderUrlAsync((fileDownloadUrl, e) => {
+                if (!string.IsNullOrEmpty(fileDownloadUrl)) {
+                    Uri uri = new Uri(fileDownloadUrl);
+                    _fileName = Path.GetFileName(uri.LocalPath);
+                }
+                else {
+                    _fileName = NTKeyword.MinerClientFinderFileName;
+                }
+            });
         }
 
         private string _fileName;

+ 8 - 4
src/AppModels/Vms/MinerClientSettingViewModel.cs → src/AppModels/MinerStudio/Vms/MinerClientSettingViewModel.cs

@@ -1,7 +1,9 @@
-using System;
+using NTMiner.Core.MinerServer;
+using NTMiner.Vms;
+using System;
 using System.Windows.Input;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class MinerClientSettingViewModel : ViewModelBase {
         public readonly Guid Id = Guid.NewGuid();
         private bool _isAutoBoot;
@@ -24,8 +26,10 @@ namespace NTMiner.Vms {
             this.Save = new DelegateCommand(() => {
                 if (minerClients != null && minerClients.Length != 0) {
                     foreach (var item in minerClients) {
-                        RpcRoot.Client.NTMinerDaemonService.SetAutoBootStartAsync(item.MinerIp, this.IsAutoBoot, this.IsAutoStart);
-                        item.Refresh.Execute(null);
+                        MinerStudioRoot.MinerStudioService.SetAutoBootStartAsync(item, new SetAutoBootStartRequest {
+                            AutoStart = this.IsAutoStart,
+                            AutoBoot = this.IsAutoBoot
+                        });
                     }
                 }
                 VirtualRoot.Execute(new CloseWindowCommand(this.Id));

+ 298 - 154
src/AppModels/Vms/MinerClientViewModel.cs → src/AppModels/MinerStudio/Vms/MinerClientViewModel.cs

@@ -2,6 +2,7 @@
 using NTMiner.Core.MinerClient;
 using NTMiner.Core.MinerServer;
 using NTMiner.RemoteDesktop;
+using NTMiner.Vms;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -9,8 +10,8 @@ using System.Windows;
 using System.Windows.Input;
 using System.Windows.Media;
 
-namespace NTMiner.Vms {
-    public class MinerClientViewModel : ViewModelBase, IMinerData, ISpeedData, IEntity<string> {
+namespace NTMiner.MinerStudio.Vms {
+    public class MinerClientViewModel : ViewModelBase, IClientData, IEntity<string> {
         public static readonly SolidColorBrush Blue = new SolidColorBrush(Colors.Blue);
         public static readonly SolidColorBrush DefaultForeground = new SolidColorBrush(Color.FromArgb(0xFF, 0x5A, 0x5A, 0x5A));
 
@@ -27,12 +28,9 @@ namespace NTMiner.Vms {
         public ICommand RestartWindows { get; private set; }
         public ICommand ShutdownWindows { get; private set; }
         public ICommand RemoteDesktop { get; private set; }
-        // ReSharper disable once InconsistentNaming
-        public ICommand RestartNTMiner { get; private set; }
         public ICommand StartMine { get; private set; }
         public ICommand StopMine { get; private set; }
         public ICommand Remove { get; private set; }
-        public ICommand Refresh { get; private set; }
 
         private readonly ClientData _data;
         #region ctor
@@ -49,32 +47,19 @@ namespace NTMiner.Vms {
             RefreshDualCoinIncome();
             this.Remove = new DelegateCommand(() => {
                 this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定删除该矿机吗?", title: "确认", onYes: () => {
-                    RpcRoot.Server.ClientService.RemoveClientsAsync(new List<string> { this.Id }, (response, e) => {
+                    MinerStudioRoot.MinerStudioService.RemoveClientsAsync(new List<string> { this.Id }, (response, e) => {
                         if (!response.IsSuccess()) {
-                            VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
+                            VirtualRoot.Out.ShowError("删除矿机失败:" + response.ReadMessage(e), autoHideSeconds: 4, toConsole: true);
                         }
                         else {
-                            AppContext.Instance.MinerClientsWindowVm.QueryMinerClients();
+                            MinerStudioRoot.MinerClientsWindowVm.QueryMinerClients();
                         }
                     });
                 }));
             });
-            this.Refresh = new DelegateCommand(() => {
-                RpcRoot.Server.ClientService.RefreshClientsAsync(new List<string> { this.Id }, (response, e) => {
-                    if (!response.IsSuccess()) {
-                        VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
-                    }
-                    else {
-                        var data = response.Data.FirstOrDefault(a => a.Id == this.Id);
-                        if (data != null) {
-                            this.Update(data);
-                        }
-                    }
-                });
-            });
             this.RemoteDesktop = new DelegateCommand(() => {
-                if (!SpeedData.TryGetFirstIp(this.LocalIp, out string ip)) {
-                    if (SpeedData.TryGetFirstIp(this.MinerIp, out string minerIp) && Net.IpUtil.IsInnerIp(minerIp)) {
+                if (!MinerIpExtensions.TryGetFirstIp(this.LocalIp, out string ip)) {
+                    if (MinerIpExtensions.TryGetFirstIp(this.MinerIp, out string minerIp) && Net.IpUtil.IsInnerIp(minerIp)) {
                         ip = minerIp;
                     }
                     else {
@@ -84,75 +69,40 @@ namespace NTMiner.Vms {
                 }
                 if (string.IsNullOrEmpty(this.WindowsLoginName)) {
                     VirtualRoot.Execute(new ShowRemoteDesktopLoginDialogCommand(new RemoteDesktopLoginViewModel {
-                        Ip = ip,
+                        Title = "连接远程桌面 - " + ip,
                         OnOk = vm => {
                             this.WindowsLoginName = vm.LoginName;
                             this.WindowsPassword = vm.Password;
-                            Rdp.RemoteDesktop?.Invoke(new RdpInput(ip, this.WindowsLoginName, this.WindowsPassword, this.MinerName, onDisconnected: message => {
-                                VirtualRoot.Out.ShowError(message, autoHideSeconds: 4);
+                            AppRoot.RemoteDesktop?.Invoke(new RdpInput(ip, this.WindowsLoginName, this.WindowsPassword, this.MinerName, onDisconnected: message => {
+                                VirtualRoot.Out.ShowError(message, autoHideSeconds: 4, toConsole: true);
                             }));
                         }
                     }));
                 }
                 else {
-                    Rdp.RemoteDesktop?.Invoke(new RdpInput(ip, this.WindowsLoginName, this.WindowsPassword, this.MinerName, onDisconnected: message => {
-                        VirtualRoot.Out.ShowError(message, autoHideSeconds: 4);
+                    AppRoot.RemoteDesktop?.Invoke(new RdpInput(ip, this.WindowsLoginName, this.WindowsPassword, this.MinerName, onDisconnected: message => {
+                        VirtualRoot.Out.ShowError(message, autoHideSeconds: 4, toConsole: true);
                     }));
                 }
             });
             this.RestartWindows = new DelegateCommand(() => {
                 this.ShowSoftDialog(new DialogWindowViewModel(message: $"您确定重启{this.MinerName}({this.MinerIp})电脑吗?", title: "确认", onYes: () => {
-                    RpcRoot.Server.MinerClientService.RestartWindowsAsync(this, (response, e) => {
-                        if (!response.IsSuccess()) {
-                            VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
-                        }
-                    });
+                    MinerStudioRoot.MinerStudioService.RestartWindowsAsync(this);
                 }));
             });
             this.ShutdownWindows = new DelegateCommand(() => {
                 this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定关闭{this.MinerName}({this.MinerIp})电脑吗?", title: "确认", onYes: () => {
-                    RpcRoot.Server.MinerClientService.ShutdownWindowsAsync(this, (response, e) => {
-                        if (!response.IsSuccess()) {
-                            VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
-                        }
-                    });
-                }));
-            });
-            this.RestartNTMiner = new DelegateCommand(() => {
-                this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定重启{this.MinerName}({this.MinerIp})挖矿客户端吗?", title: "确认", onYes: () => {
-                    RpcRoot.Server.MinerClientService.RestartNTMinerAsync(this, (response, e) => {
-                        if (!response.IsSuccess()) {
-                            VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
-                        }
-                    });
+                    MinerStudioRoot.MinerStudioService.ShutdownWindowsAsync(this);
                 }));
             });
             this.StartMine = new DelegateCommand(() => {
-                IsMining = true;
-                RpcRoot.Server.MinerClientService.StartMineAsync(this, WorkId, (response, e) => {
-                    if (!response.IsSuccess()) {
-                        VirtualRoot.Out.ShowError($"{this.MinerName} {this.MinerIp} {response.ReadMessage(e)}");
-                    }
-                });
-                RpcRoot.Server.ClientService.UpdateClientAsync(this.Id, nameof(IsMining), IsMining, (response, e) => {
-                    if (!response.IsSuccess()) {
-                        VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
-                    }
-                });
+                this.ShowSoftDialog(new DialogWindowViewModel(message: $"{this.MinerName}({this.MinerIp}):确定开始挖矿吗?", title: "确认", onYes: () => {
+                    MinerStudioRoot.MinerStudioService.StartMineAsync(this, WorkId);
+                }));
             });
             this.StopMine = new DelegateCommand(() => {
                 this.ShowSoftDialog(new DialogWindowViewModel(message: $"{this.MinerName}({this.MinerIp}):确定停止挖矿吗?", title: "确认", onYes: () => {
-                    IsMining = false;
-                    RpcRoot.Server.MinerClientService.StopMineAsync(this, (response, e) => {
-                        if (!response.IsSuccess()) {
-                            VirtualRoot.Out.ShowError($"{this.MinerName} {this.MinerIp} {response.ReadMessage(e)}");
-                        }
-                    });
-                    RpcRoot.Server.ClientService.UpdateClientAsync(this.Id, nameof(IsMining), IsMining, (response, e) => {
-                        if (!response.IsSuccess()) {
-                            VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
-                        }
-                    });
+                    MinerStudioRoot.MinerStudioService.StopMineAsync(this);
                 }));
             });
         }
@@ -170,12 +120,18 @@ namespace NTMiner.Vms {
             UpdateByReflection.Update(this, data);
         }
 
-        public AppContext.MineWorkViewModels MineWorkVms {
-            get { return AppContext.Instance.MineWorkVms; }
+        public MinerStudioRoot.MineWorkViewModels MineWorkVms {
+            get { return MinerStudioRoot.MineWorkVms; }
         }
 
-        public AppContext.MinerGroupViewModels MinerGroupVms {
-            get { return AppContext.Instance.MinerGroupVms; }
+        public MinerStudioRoot.MinerGroupViewModels MinerGroupVms {
+            get { return MinerStudioRoot.MinerGroupVms; }
+        }
+
+        public MainMenuViewModel MainMenu {
+            get {
+                return MainMenuViewModel.Instance;
+            }
         }
 
         #region IClientData
@@ -194,6 +150,16 @@ namespace NTMiner.Vms {
             }
         }
 
+        public Guid MineContextId {
+            get { return _data.MineContextId; }
+            set {
+                if (_data.MineContextId != value) {
+                    _data.MineContextId = value;
+                    OnPropertyChanged(nameof(MineContextId));
+                }
+            }
+        }
+
         public Guid ClientId {
             get { return _data.ClientId; }
             set {
@@ -293,7 +259,7 @@ namespace NTMiner.Vms {
                     return MineWorkViewModel.PleaseSelect;
                 }
                 if (_selectedMineWork == null || _selectedMineWork.Id != WorkId) {
-                    if (AppContext.Instance.MineWorkVms.TryGetMineWorkVm(WorkId, out _selectedMineWork)) {
+                    if (MinerStudioRoot.MineWorkVms.TryGetMineWorkVm(WorkId, out _selectedMineWork)) {
                         return _selectedMineWork;
                     }
                 }
@@ -307,20 +273,15 @@ namespace NTMiner.Vms {
                     var old = _selectedMineWork;
                     this.WorkId = value.Id;
                     _selectedMineWork = value;
-                    try {
-                        RpcRoot.Server.ClientService.UpdateClientAsync(
-                            this.Id, nameof(WorkId), value.Id, (response, exception) => {
-                                if (!response.IsSuccess()) {
-                                    _selectedMineWork = old;
-                                    this.WorkId = old.Id;
-                                    VirtualRoot.Out.ShowError($"{this.MinerName} {this.MinerIp} {response.ReadMessage(exception)}");
-                                }
-                                OnPropertyChanged(nameof(SelectedMineWork));
-                            });
-                    }
-                    catch (Exception e) {
-                        Logger.ErrorDebugLine(e);
-                    }
+                    MinerStudioRoot.MinerStudioService.UpdateClientAsync(
+                        this.Id, nameof(WorkId), value.Id, (response, exception) => {
+                            if (!response.IsSuccess()) {
+                                _selectedMineWork = old;
+                                this.WorkId = old.Id;
+                                VirtualRoot.Out.ShowError($"{this.MinerName} {this.MinerIp} {response.ReadMessage(exception)}", toConsole: true);
+                            }
+                            OnPropertyChanged(nameof(SelectedMineWork));
+                        });
                 }
             }
         }
@@ -335,35 +296,50 @@ namespace NTMiner.Vms {
             }
         }
 
-        public DateTime ModifiedOn {
-            get => _data.ModifiedOn;
+        public DateTime MinerActiveOn {
+            get => _data.MinerActiveOn;
             set {
-                if (_data.ModifiedOn != value) {
-                    _data.ModifiedOn = value;
-                    OnPropertyChanged(nameof(ModifiedOn));
+                if (_data.MinerActiveOn != value) {
+                    _data.MinerActiveOn = value;
+                    OnPropertyChanged(nameof(MinerActiveOn));
                 }
                 OnPropertyChanged(nameof(IsMining));
                 OnPropertyChanged(nameof(LastActivedOnText));
-                OnPropertyChanged(nameof(IsOnline));
+            }
+        }
+
+        public bool IsOnline {
+            get => _data.IsOnline;
+            set {
+                if (_data.IsOnline != value) {
+                    _data.IsOnline = value;
+                    OnPropertyChanged(nameof(IsOnline));
+                }
+            }
+        }
+
+        public DateTime NetActiveOn {
+            get => _data.NetActiveOn;
+            set {
+                if (_data.NetActiveOn != value) {
+                    _data.NetActiveOn = value;
+                    OnPropertyChanged(nameof(NetActiveOn));
+                }
+                // 因为以下和时间有关,所以时间不等也是不等
+                OnPropertyChanged(nameof(VmIsOnline));
+                OnPropertyChanged(nameof(VmIsOnlineText));
+                OnPropertyChanged(nameof(IsMining));
+                OnPropertyChanged(nameof(BootTimeSpanText));
+                OnPropertyChanged(nameof(MineTimeSpanText));
             }
         }
 
         public string LastActivedOnText {
             get {
-                if (ModifiedOn <= Timestamp.UnixBaseTime) {
+                if (MinerActiveOn <= Timestamp.UnixBaseTime) {
                     return string.Empty;
                 }
-                TimeSpan timeSpan = DateTime.Now - ModifiedOn;
-                if (timeSpan.Days >= 1) {
-                    return timeSpan.Days + " 天前";
-                }
-                if (timeSpan.Hours > 0) {
-                    return timeSpan.Hours + " 小时前";
-                }
-                if (timeSpan.Minutes > 2) {
-                    return timeSpan.Minutes + " 分钟前";
-                }
-                return (int)timeSpan.TotalSeconds + " 秒前";
+                return Timestamp.GetTimeSpanText(MinerActiveOn);
             }
         }
 
@@ -393,20 +369,63 @@ namespace NTMiner.Vms {
 
         public string BootTimeSpanText {
             get {
-                if (BootOn <= Timestamp.UnixBaseTime || !IsOnline) {
-                    return string.Empty;
+                if (BootOn <= Timestamp.UnixBaseTime) {
+                    return "-";
+                }
+                else {
+                    if (RpcRoot.IsOuterNet) {
+                        // 即使启用了外网群控也是每2分钟才会上报一次算力,但群控客户端会向矿机列表当页的矿机发起获取算力的请求,
+                        // 但当用户刚翻倒第二页的时候或刚打开看到第一页的时候并未提前刷新,所以MinerActiveOn的周期必须大于2分钟。
+                        if (MinerActiveOn.AddSeconds(180) < DateTime.Now) {
+                            return "-";
+                        }
+                    }
+                    else if (MinerActiveOn.AddSeconds(20) < DateTime.Now) {
+                        return "-";
+                    }
                 }
                 return TimeSpanToString(DateTime.Now - BootOn);
             }
         }
 
-        private readonly bool _isInnerIp = Net.IpUtil.IsInnerIp(NTMinerRegistry.GetControlCenterHost());
-        public bool IsOnline {
+        public bool VmIsOnline {
             get {
-                if (_isInnerIp) {
-                    return this.ModifiedOn.AddSeconds(20) > DateTime.Now;
+                if (!IsOnline) {
+                    return false;
+                }
+                if (RpcRoot.IsOuterNet) {
+                    if (this.IsOuterUserEnabled) {
+                        if (NetActiveOn.AddSeconds(60) < DateTime.Now) {
+                            return false;
+                        }
+                    }
+                    else if (NetActiveOn.AddSeconds(180) < DateTime.Now) {
+                        return false;
+                    }
+                    return true;
+                }
+                if (NetActiveOn.AddSeconds(20) < DateTime.Now) {
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        public string VmIsOnlineText {
+            get {
+                if (VmIsOnline) {
+                    return "在线";
+                }
+                return "离线";
+            }
+        }
+
+        public Visibility VmIsOnlineVisible {
+            get {
+                if (RpcRoot.IsOuterNet && !this.IsOuterUserEnabled) {
+                    return Visibility.Collapsed;
                 }
-                return this.ModifiedOn.AddSeconds(130) > DateTime.Now;
+                return Visibility.Visible;
             }
         }
 
@@ -424,7 +443,7 @@ namespace NTMiner.Vms {
         public string MineTimeSpanText {
             get {
                 if (!MineStartedOn.HasValue || MineStartedOn.Value <= Timestamp.UnixBaseTime || !this.IsMining) {
-                    return string.Empty;
+                    return "-";
                 }
 
                 return TimeSpanToString(DateTime.Now - MineStartedOn.Value);
@@ -433,7 +452,7 @@ namespace NTMiner.Vms {
 
         public bool IsMining {
             get {
-                if (this.ModifiedOn.AddSeconds(130) < DateTime.Now) {
+                if (!VmIsOnline) {
                     return false;
                 }
                 return _data.IsMining;
@@ -442,42 +461,66 @@ namespace NTMiner.Vms {
                 if (_data.IsMining != value) {
                     _data.IsMining = value;
                     OnPropertyChanged(nameof(IsMining));
+                    OnPropertyChanged(nameof(IsMiningText));
+                }
+            }
+        }
+
+        public string IsMiningText {
+            get {
+                if (IsMining) {
+                    return "挖矿中";
                 }
+                return "未挖矿";
             }
         }
 
-        public string ClientName {
-            get { return _data.ClientName; }
+        public string WorkerName {
+            get {
+                return _data.WorkerName;
+            }
             set {
-                if (_data.ClientName != value) {
-                    _data.ClientName = value;
-                    OnPropertyChanged(nameof(ClientName));
+                if (_data.WorkerName != value) {
+                    var old = _data.WorkerName;
+                    _data.WorkerName = value;
+                    MinerStudioRoot.MinerStudioService.UpdateClientAsync(this.Id, nameof(WorkerName), value, (response, e) => {
+                        if (!response.IsSuccess()) {
+                            _data.WorkerName = old;
+                            VirtualRoot.Out.ShowError($"设置群控名失败:{this.WorkerName} {this.MinerIp} {response.ReadMessage(e)}", toConsole: true);
+                        }
+                        OnPropertyChanged(nameof(WorkerName));
+                        OnPropertyChanged(nameof(WorkerNameText));
+                    });
                 }
             }
         }
 
-        public void UpdateMinerName(string minerName) {
-            _data.MinerName = minerName;
+        public string WorkerNameText {
+            get {
+                if (string.IsNullOrEmpty(_data.WorkerName)) {
+                    return string.Empty;
+                }
+                return _data.WorkerName;
+            }
         }
 
         public string MinerName {
             get => _data.MinerName;
             set {
                 if (_data.MinerName != value) {
-                    var old = _data.MinerName;
                     _data.MinerName = value;
-                    RpcRoot.Server.ClientService.UpdateClientAsync(this.Id, nameof(MinerName), value, (response, e) => {
-                        if (!response.IsSuccess()) {
-                            _data.MinerName = old;
-                            VirtualRoot.Out.ShowError($"{this.MinerName} {this.MinerIp} {response.ReadMessage(e)}");
-                        }
-                        OnPropertyChanged(nameof(MinerName));
-                    });
+                    OnPropertyChanged(nameof(MinerName));
                 }
-                OnPropertyChanged(nameof(MinerName));
             }
         }
 
+        public string GetMinerOrClientName() {
+            if (string.IsNullOrEmpty(MinerName)) {
+                return WorkerName;
+            }
+            return MinerName;
+        }
+
         public Guid GroupId {
             get { return _data.GroupId; }
             set {
@@ -493,7 +536,7 @@ namespace NTMiner.Vms {
         public MinerGroupViewModel SelectedMinerGroup {
             get {
                 if (_selectedMinerGroup == null || _selectedMinerGroup.Id != GroupId) {
-                    AppContext.Instance.MinerGroupVms.TryGetMineWorkVm(GroupId, out _selectedMinerGroup);
+                    MinerStudioRoot.MinerGroupVms.TryGetMineWorkVm(GroupId, out _selectedMinerGroup);
                     if (_selectedMinerGroup == null) {
                         _selectedMinerGroup = MinerGroupViewModel.PleaseSelect;
                     }
@@ -508,19 +551,14 @@ namespace NTMiner.Vms {
                     var old = _selectedMinerGroup;
                     _selectedMinerGroup = value;
                     this.GroupId = value.Id;
-                    try {
-                        RpcRoot.Server.ClientService.UpdateClientAsync(this.Id, nameof(GroupId), value.Id, (response, exception) => {
-                            if (!response.IsSuccess()) {
-                                _selectedMinerGroup = old;
-                                this.GroupId = old.Id;
-                                VirtualRoot.Out.ShowError($"{this.MinerName} {this.MinerIp} {response.ReadMessage(exception)}");
-                            }
-                            OnPropertyChanged(nameof(SelectedMinerGroup));
-                        });
-                    }
-                    catch (Exception e) {
-                        Logger.ErrorDebugLine(e);
-                    }
+                    MinerStudioRoot.MinerStudioService.UpdateClientAsync(this.Id, nameof(GroupId), value.Id, (response, exception) => {
+                        if (!response.IsSuccess()) {
+                            _selectedMinerGroup = old;
+                            this.GroupId = old.Id;
+                            VirtualRoot.Out.ShowError($"加入分组失败:{this.MinerName} {this.MinerIp} {response.ReadMessage(exception)}", toConsole: true);
+                        }
+                        OnPropertyChanged(nameof(SelectedMinerGroup));
+                    });
                 }
             }
         }
@@ -542,14 +580,13 @@ namespace NTMiner.Vms {
                 if (_data.WindowsLoginName != value) {
                     var old = _data.WindowsLoginName;
                     _data.WindowsLoginName = value;
-                    RpcRoot.Server.ClientService.UpdateClientAsync(this.Id, nameof(WindowsLoginName), value, (response, exception) => {
+                    MinerStudioRoot.MinerStudioService.UpdateClientAsync(this.Id, nameof(WindowsLoginName), value, (response, exception) => {
                         if (!response.IsSuccess()) {
                             _data.WindowsLoginName = old;
-                            VirtualRoot.Out.ShowError($"{this.MinerName} {this.MinerIp} {response.ReadMessage(exception)}");
+                            VirtualRoot.Out.ShowError($"设置Windows远程登录用户名失败:{this.MinerName} {this.MinerIp} {response.ReadMessage(exception)}", toConsole: true);
                         }
                         OnPropertyChanged(nameof(WindowsLoginName));
                     });
-                    OnPropertyChanged(nameof(WindowsLoginName));
                 }
             }
         }
@@ -569,10 +606,10 @@ namespace NTMiner.Vms {
                 if (_data.WindowsPassword != value) {
                     var old = _data.WindowsPassword;
                     _data.WindowsPassword = value;
-                    RpcRoot.Server.ClientService.UpdateClientAsync(this.Id, nameof(WindowsPassword), value, (response, exception) => {
+                    MinerStudioRoot.MinerStudioService.UpdateClientAsync(this.Id, nameof(WindowsPassword), value, (response, exception) => {
                         if (!response.IsSuccess()) {
                             _data.WindowsPassword = old;
-                            VirtualRoot.Out.ShowError($"{this.MinerName} {this.MinerIp} {response.ReadMessage(exception)}");
+                            VirtualRoot.Out.ShowError($"设置Widnows远程登录密码失败:{this.MinerName} {this.MinerIp} {response.ReadMessage(exception)}", toConsole: true);
                         }
                         OnPropertyChanged(nameof(WindowsPassword));
                         OnPropertyChanged(nameof(WindowsPasswordStar));
@@ -615,7 +652,7 @@ namespace NTMiner.Vms {
         }
 
         private void RefreshMainCoinIncome() {
-            IncomePerDay incomePerDay = NTMinerRoot.Instance.CalcConfigSet.GetIncomePerHashPerDay(this.MainCoinCode);
+            IncomePerDay incomePerDay = NTMinerContext.Instance.CalcConfigSet.GetIncomePerHashPerDay(this.MainCoinCode);
             IncomeMainCoinPerDay = MainCoinSpeed * incomePerDay.IncomeCoin;
             IncomeMainCoinUsdPerDay = MainCoinSpeed * incomePerDay.IncomeUsd;
             IncomeMainCoinCnyPerDay = MainCoinSpeed * incomePerDay.IncomeCny;
@@ -834,7 +871,7 @@ namespace NTMiner.Vms {
         }
 
         private void RefreshDualCoinIncome() {
-            IncomePerDay incomePerDay = NTMinerRoot.Instance.CalcConfigSet.GetIncomePerHashPerDay(this.DualCoinCode);
+            IncomePerDay incomePerDay = NTMinerContext.Instance.CalcConfigSet.GetIncomePerHashPerDay(this.DualCoinCode);
             IncomeDualCoinPerDay = DualCoinSpeed * incomePerDay.IncomeCoin;
             IncomeDualCoinUsdPerDay = DualCoinSpeed * incomePerDay.IncomeUsd;
             IncomeDualCoinCnyPerDay = DualCoinSpeed * incomePerDay.IncomeCny;
@@ -948,7 +985,9 @@ namespace NTMiner.Vms {
         }
 
         public int TotalPhysicalMemoryMb {
-            get => _data.TotalPhysicalMemoryMb;
+            get {
+                return _data.TotalPhysicalMemoryMb;
+            }
             set {
                 if (_data.TotalPhysicalMemoryMb != value) {
                     _data.TotalPhysicalMemoryMb = value;
@@ -1340,7 +1379,12 @@ namespace NTMiner.Vms {
         }
 
         public int KernelSelfRestartCount {
-            get { return _data.KernelSelfRestartCount; }
+            get {
+                if (_data.KernelSelfRestartCount < 0) {
+                    return 0;
+                }
+                return _data.KernelSelfRestartCount;
+            }
             set {
                 if (_data.KernelSelfRestartCount != value) {
                     _data.KernelSelfRestartCount = value;
@@ -1389,6 +1433,106 @@ namespace NTMiner.Vms {
             }
         }
 
+        public bool IsOuterUserEnabled {
+            get { return _data.IsOuterUserEnabled; }
+            set {
+                if (_data.IsOuterUserEnabled != value) {
+                    _data.IsOuterUserEnabled = value;
+                    OnPropertyChanged(nameof(IsOuterUserEnabled));
+                }
+            }
+        }
+
+        public string OuterUserId {
+            get { return _data.OuterUserId; }
+            set {
+                if (_data.OuterUserId != value) {
+                    _data.OuterUserId = value;
+                    OnPropertyChanged(nameof(OuterUserId));
+                }
+            }
+        }
+
+        public string AESPassword {
+            get { return _data.AESPassword; }
+            set {
+                if (_data.AESPassword != value) {
+                    _data.AESPassword = value;
+                    OnPropertyChanged(nameof(AESPassword));
+                }
+            }
+        }
+
+        public DateTime AESPasswordOn {
+            get { return _data.AESPasswordOn; }
+            set {
+                if (_data.AESPasswordOn != value) {
+                    _data.AESPasswordOn = value;
+                    OnPropertyChanged(nameof(AESPasswordOn));
+                }
+            }
+        }
+
+        public bool IsAutoDisableWindowsFirewall {
+            get { return _data.IsAutoDisableWindowsFirewall; }
+            set {
+                if (_data.IsAutoDisableWindowsFirewall != value) {
+                    _data.IsAutoDisableWindowsFirewall = value;
+                    OnPropertyChanged(nameof(IsAutoDisableWindowsFirewall));
+                }
+            }
+        }
+
+        public bool IsDisableUAC {
+            get { return _data.IsDisableUAC; }
+            set {
+                if (_data.IsDisableUAC != value) {
+                    _data.IsDisableUAC = value;
+                    OnPropertyChanged(nameof(IsDisableUAC));
+                }
+            }
+        }
+
+        public bool IsDisableWAU {
+            get { return _data.IsDisableWAU; }
+            set {
+                if (_data.IsDisableWAU != value) {
+                    _data.IsDisableWAU = value;
+                    OnPropertyChanged(nameof(IsDisableWAU));
+                }
+            }
+        }
+
+        public bool IsDisableAntiSpyware {
+            get { return _data.IsDisableAntiSpyware; }
+            set {
+                if (_data.IsDisableAntiSpyware != value) {
+                    _data.IsDisableAntiSpyware = value;
+                    OnPropertyChanged(nameof(IsDisableAntiSpyware));
+                }
+            }
+        }
+
+        public DateTime MainCoinSpeedOn {
+            get { return _data.MainCoinSpeedOn; }
+            set {
+                if (_data.MainCoinSpeedOn != value) {
+                    _data.MainCoinSpeedOn = value;
+                    OnPropertyChanged(nameof(MainCoinSpeedOn));
+                }
+            }
+        }
+
+        public DateTime DualCoinSpeedOn {
+            get { return _data.DualCoinSpeedOn; }
+            set {
+                if (_data.DualCoinSpeedOn != value) {
+                    _data.DualCoinSpeedOn = value;
+                    OnPropertyChanged(nameof(DualCoinSpeedOn));
+                }
+            }
+        }
+
         public void RefreshGpusForeground(uint minTemp, uint maxTemp) {
             if (GpuTableVm == null) {
                 return;

+ 1207 - 0
src/AppModels/MinerStudio/Vms/MinerClientsWindowViewModel.cs

@@ -0,0 +1,1207 @@
+using NTMiner.Core;
+using NTMiner.Core.MinerServer;
+using NTMiner.Core.MinerStudio;
+using NTMiner.Vms;
+using NTMiner.Ws;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Windows;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class MinerClientsWindowViewModel : ViewModelBase, IWsStateViewModel {
+        public static readonly MinerClientsWindowViewModel Instance = new MinerClientsWindowViewModel(isInDesignMode: false);
+
+        private List<CoinSnapshotViewModel> _coinSnapshotVms = null;
+        private ColumnsShowViewModel _columnsShow;
+        private int _countDown = 10;
+        private List<MinerClientViewModel> _minerClients = new List<MinerClientViewModel>();
+        private MinerClientViewModel _currentMinerClient;
+        private MinerClientViewModel[] _selectedMinerClients = new MinerClientViewModel[0];
+        private int _pageIndex = 1;
+        private int _pageSize = 20;
+        private int _total;
+        private EnumItem<MineStatus> _mineStatusEnumItem;
+        private string _minerIp;
+        private string _minerName;
+        private string _version;
+        private string _kernel;
+        private string _wallet;
+        private CoinSnapshotViewModel _mineCoinVm = CoinSnapshotViewModel.PleaseSelect;
+        private string _pool;
+        private PoolViewModel _poolVm = PoolViewModel.PleaseSelect;
+        private MineWorkViewModel _selectedMineWork = MineWorkViewModel.PleaseSelect;
+        private MinerGroupViewModel _selectedMinerGroup = MinerGroupViewModel.PleaseSelect;
+        private uint _maxTemp = 80;
+        private readonly List<int> _frozenColumns = new List<int> { 8, 7, 6, 5, 4, 3 };
+        private int _frozenColumnCount = 8;
+        private uint _minTemp = 40;
+        private int _rejectPercent = 10;
+        private SortDirection _sortDirection = SortDirection.Ascending;
+
+        private bool _isWsOnline;
+        private string _wsDescription;
+        private int _wsNextTrySecondsDelay;
+        private DateTime _wsLastTryOn;
+        private bool _isConnecting;
+        private double _wsRetryIconAngle;
+
+        #region WpfCommands
+        public ICommand RestartWindows { get; private set; }
+        public ICommand ShutdownWindows { get; private set; }
+        public ICommand StartMine { get; private set; }
+        public ICommand StopMine { get; private set; }
+
+        public ICommand PageUp { get; private set; }
+        public ICommand PageDown { get; private set; }
+        public ICommand PageFirst { get; private set; }
+        public ICommand PageLast { get; private set; }
+        public ICommand PageRefresh { get; private set; }
+        public ICommand AddMinerClient { get; private set; }
+        public ICommand RemoveMinerClients { get; private set; }
+        public ICommand OneKeyWork { get; private set; }
+        public ICommand OneKeyGroup { get; private set; }
+        public ICommand OneKeyOverClock { get; private set; }
+        public ICommand OneKeyWorkerNames { get; private set; }
+        public ICommand OneKeySetting { get; private set; }
+        public ICommand EnableRemoteDesktop { get; private set; }
+        public ICommand RemoteDesktop { get; private set; }
+        public ICommand BlockWAU { get; private set; }
+        public ICommand PowerCfgOff { get; private set; }
+        public ICommand VirtualMemory { get; private set; }
+        public ICommand LocalIpConfig { get; private set; }
+        public ICommand SwitchRadeonGpu { get; private set; }
+        public ICommand AtikmdagPatcher { get; private set; }
+        public ICommand SortByMinerName { get; private set; }
+        public ICommand WsRetry { get; private set; }
+        #endregion
+
+        #region ctor
+        public MinerClientsWindowViewModel() {
+            if (WpfUtil.IsInDesignMode) {
+                return;
+            }
+        }
+
+        public MinerClientsWindowViewModel(bool isInDesignMode = true) {
+            if (WpfUtil.IsInDesignMode || isInDesignMode) {
+                return;
+            }
+            #region 状态栏的几条配置
+            var appSettings = VirtualRoot.LocalAppSettingSet;
+            if (appSettings.TryGetAppSetting(NTKeyword.FrozenColumnCountAppSettingKey, out IAppSetting frozenColumnCountAppSetting) && frozenColumnCountAppSetting.Value != null) {
+                if (int.TryParse(frozenColumnCountAppSetting.Value.ToString(), out int frozenColumnCount)) {
+                    _frozenColumnCount = frozenColumnCount;
+                }
+            }
+            if (appSettings.TryGetAppSetting(NTKeyword.MaxTempAppSettingKey, out IAppSetting maxTempAppSetting) && maxTempAppSetting.Value != null) {
+                if (uint.TryParse(maxTempAppSetting.Value.ToString(), out uint maxTemp)) {
+                    _maxTemp = maxTemp;
+                }
+            }
+            if (appSettings.TryGetAppSetting(NTKeyword.MinTempAppSettingKey, out IAppSetting minTempAppSetting) && minTempAppSetting.Value != null) {
+                if (uint.TryParse(minTempAppSetting.Value.ToString(), out uint minTemp)) {
+                    _minTemp = minTemp;
+                }
+            }
+            if (appSettings.TryGetAppSetting(NTKeyword.RejectPercentAppSettingKey, out IAppSetting rejectPercentAppSetting) && rejectPercentAppSetting.Value != null) {
+                if (int.TryParse(rejectPercentAppSetting.Value.ToString(), out int rejectPercent)) {
+                    _rejectPercent = rejectPercent;
+                }
+            }
+            #endregion
+            Guid columnsShowId = ColumnsShowData.PleaseSelect.Id;
+            if (appSettings.TryGetAppSetting(NTKeyword.ColumnsShowIdAppSettingKey, out IAppSetting columnsShowAppSetting) && columnsShowAppSetting.Value != null) {
+                if (Guid.TryParse(columnsShowAppSetting.Value.ToString(), out Guid guid)) {
+                    columnsShowId = guid;
+                }
+            }
+            _columnsShow = ColumnsShows.List.FirstOrDefault(a => a.Id == columnsShowId);
+            if (_columnsShow == null) {
+                _columnsShow = ColumnsShows.List.FirstOrDefault();
+            }
+            this._mineStatusEnumItem = NTMinerContext.MineStatusEnumItems.FirstOrDefault(a => a.Value == MineStatus.All);
+            this._pool = string.Empty;
+            this._wallet = string.Empty;
+            this.OneKeySetting = new DelegateCommand(() => {
+                VirtualRoot.Execute(new ShowMinerClientSettingCommand(new MinerClientSettingViewModel(this.SelectedMinerClients)));
+            }, CanCommand);
+            this.OneKeyWorkerNames = new DelegateCommand(() => {
+                #region
+                if (this.SelectedMinerClients.Length == 1) {
+                    var selectedMinerClient = this.SelectedMinerClients[0];
+                    WpfUtil.ShowInputDialog("群控名", selectedMinerClient.WorkerName, "注意:将在下次通过群控开始挖矿时应用至矿机", null, minerName => {
+                        selectedMinerClient.WorkerName = minerName;
+                        VirtualRoot.Out.ShowSuccess("设置群控名成功,将在下次通过群控开始挖矿时应用至矿机。", toConsole: true);
+                    });
+                }
+                else {
+                    MinerNamesSeterViewModel vm = null;
+                    vm = new MinerNamesSeterViewModel(
+                        prefix: "miner",
+                        suffix: "01",
+                        namesByObjectId: this.SelectedMinerClients.Select(a => new Tuple<string, string>(a.Id, string.Empty)).ToList(),
+                        onOk: () => {
+                            this.CountDown = 10;
+                            MinerStudioRoot.MinerStudioService.UpdateClientsAsync(nameof(MinerClientViewModel.WorkerName), vm.NamesByObjectId.ToDictionary(a => a.Item1, a => (object)a.Item2), callback: (response, e) => {
+                                if (response.IsSuccess()) {
+                                    foreach (var kv in vm.NamesByObjectId) {
+                                        var item = this.SelectedMinerClients.FirstOrDefault(a => a.Id == kv.Item1);
+                                        if (item != null) {
+                                            item.OnPropertyChanged(nameof(item.WorkerName));
+                                        }
+                                    }
+                                    QueryMinerClients();
+                                }
+                            });
+                        });
+                    VirtualRoot.Execute(new ShowMinerNamesSeterCommand(vm));
+                }
+                #endregion
+            }, CanCommand);
+            this.OneKeyWork = new DelegateCommand<MineWorkViewModel>((work) => {
+                foreach (var item in SelectedMinerClients) {
+                    item.SelectedMineWork = work;
+                }
+            });
+            this.OneKeyGroup = new DelegateCommand<MinerGroupViewModel>((group) => {
+                foreach (var item in SelectedMinerClients) {
+                    item.SelectedMinerGroup = group;
+                }
+            });
+            this.OneKeyOverClock = new DelegateCommand(() => {
+                if (this.SelectedMinerClients.Length == 1) {
+                    VirtualRoot.Execute(new ShowGpuProfilesPageCommand(this));
+                }
+            }, OnlySelectedOne);
+            this.AddMinerClient = new DelegateCommand(() => {
+                VirtualRoot.Execute(new ShowMinerClientAddCommand());
+            });
+            this.RemoveMinerClients = new DelegateCommand(() => {
+                #region
+                if (SelectedMinerClients.Length == 0) {
+                    ShowNoRecordSelected();
+                }
+                else {
+                    this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定删除选中的矿机吗?", title: "确认", onYes: () => {
+                        this.CountDown = 10;
+                        MinerStudioRoot.MinerStudioService.RemoveClientsAsync(SelectedMinerClients.Select(a => a.Id).ToList(), (response, e) => {
+                            if (!response.IsSuccess()) {
+                                VirtualRoot.Out.ShowError("删除矿机失败:" + response.ReadMessage(e), autoHideSeconds: 4, toConsole: true);
+                            }
+                            else {
+                                QueryMinerClients();
+                            }
+                        });
+                    }));
+                }
+                #endregion
+            }, CanCommand);
+            this.RestartWindows = new DelegateCommand(() => {
+                #region
+                if (SelectedMinerClients.Length == 0) {
+                    ShowNoRecordSelected();
+                }
+                else {
+                    this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定重启选中的电脑吗?", title: "确认", onYes: () => {
+                        foreach (var item in SelectedMinerClients) {
+                            MinerStudioRoot.MinerStudioService.RestartWindowsAsync(item);
+                        }
+                    }));
+                }
+                #endregion
+            }, CanCommand);
+            this.ShutdownWindows = new DelegateCommand(() => {
+                #region
+                if (SelectedMinerClients.Length == 0) {
+                    ShowNoRecordSelected();
+                }
+                else {
+                    this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定关闭选中的电脑吗?", title: "确认", onYes: () => {
+                        foreach (var item in SelectedMinerClients) {
+                            MinerStudioRoot.MinerStudioService.ShutdownWindowsAsync(item);
+                        }
+                    }));
+                }
+                #endregion
+            }, CanCommand);
+            this.StartMine = new DelegateCommand(() => {
+                #region
+                if (SelectedMinerClients.Length == 0) {
+                    ShowNoRecordSelected();
+                }
+                else {
+                    this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定将选中的矿机开始挖矿吗?", title: "确认", onYes: () => {
+                        foreach (var item in SelectedMinerClients) {
+                            // 不能直接调用item的StopMine命令,因为该命令内部会有弹窗确认
+                            MinerStudioRoot.MinerStudioService.StartMineAsync(item, item.WorkId);
+                        }
+                    }));
+                }
+                #endregion
+            }, CanCommand);
+            this.StopMine = new DelegateCommand(() => {
+                #region
+                if (SelectedMinerClients.Length == 0) {
+                    ShowNoRecordSelected();
+                }
+                else {
+                    this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定将选中的矿机停止挖矿吗?", title: "确认", onYes: () => {
+                        foreach (var item in SelectedMinerClients) {
+                            // 不能直接调用item的StopMine命令,因为该命令内部会有弹窗确认
+                            MinerStudioRoot.MinerStudioService.StopMineAsync(item);
+                        }
+                    }));
+                }
+                #endregion
+            }, CanCommand);
+            this.EnableRemoteDesktop = new DelegateCommand(() => {
+                #region
+                if (SelectedMinerClients.Length == 0) {
+                    ShowNoRecordSelected();
+                }
+                else {
+                    this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定启用选中的矿机的Windows远程桌面功能吗?", title: "确认", onYes: () => {
+                        foreach (var item in SelectedMinerClients) {
+                            MinerStudioRoot.MinerStudioService.EnableRemoteDesktopAsync(item);
+                        }
+                    }));
+                }
+                #endregion
+            }, CanCommand);
+            this.RemoteDesktop = new DelegateCommand(() => {
+                string windowsLoginName = string.Empty;
+                string windowsPassword = string.Empty;
+                if (SelectedMinerClients.Length == 0) {
+                    ShowNoRecordSelected();
+                }
+                else if (SelectedMinerClients.Length == 1) {
+                    var selectedVm = SelectedMinerClients[0];
+                    windowsLoginName = selectedVm.WindowsLoginName;
+                    windowsPassword = selectedVm.WindowsPassword;
+                }
+                VirtualRoot.Execute(new ShowRemoteDesktopLoginDialogCommand(new RemoteDesktopLoginViewModel(windowsLoginName) {
+                    Title = "设置远程桌面",
+                    Password = windowsPassword,
+                    OnOk = vm => {
+                        foreach (var item in SelectedMinerClients) {
+                            item.WindowsLoginName = vm.LoginName;
+                            item.WindowsPassword = vm.Password;
+                        }
+                        VirtualRoot.Out.ShowSuccess("设置成功,双击矿机可直接远程桌面。", toConsole: true);
+                    }
+                }));
+            }, CanCommand);
+            this.BlockWAU = new DelegateCommand(() => {
+                #region
+                if (SelectedMinerClients.Length == 0) {
+                    ShowNoRecordSelected();
+                }
+                else {
+                    this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定禁用选中的矿机的Windows自动更新功能吗?", title: "确认", onYes: () => {
+                        foreach (var item in SelectedMinerClients) {
+                            MinerStudioRoot.MinerStudioService.BlockWAUAsync(item);
+                        }
+                    }));
+                }
+                #endregion
+            }, CanCommand);
+            this.PowerCfgOff = new DelegateCommand(() => {
+                VirtualRoot.Out.ShowSuccess("挖矿端启动时已自动关闭系统休眠", header: "提示", autoHideSeconds: 0);
+            }, CanCommand);
+            this.VirtualMemory = new DelegateCommand(() => {
+                #region
+                if (SelectedMinerClients.Length == 0) {
+                    ShowNoRecordSelected();
+                }
+                else if (SelectedMinerClients.Length == 1) {
+                    VirtualRoot.Execute(new ShowMinerStudioVirtualMemoryCommand(new VirtualMemoryViewModel(SelectedMinerClients[0])));
+                }
+                else {
+                    string tail = "0. 这是统一设置虚拟内存,单选矿机时设置虚拟内存更直观;\n1. 尽量按照显存和虚拟内存一比一设置,比如6张6G显存的1060显卡应设置36G虚拟内存;\n2. 该操作优先将虚拟内存设置在操作系统所在的盘(通常是C盘,设置时会自动为系统盘留出1G空间,为非系统盘留出100M空间);\n3. 只要整机剩余磁盘总和够大就能设置成功;\n4. 若统一设置虚拟内存不满足需求可以单选矿机分别设置。";
+                    WpfUtil.ShowInputDialog("虚拟内存(Gb)", "0", tail, virtualMemoryGbText => {
+                        if (!double.TryParse(virtualMemoryGbText, out double virtualMemoryGb)) {
+                            return "数值格式错误,必须是数值(可带小数)";
+                        }
+                        return string.Empty;
+                    }, virtualMemoryGbText => {
+                        if (!double.TryParse(virtualMemoryGbText, out double virtualMemoryGb)) {
+                            VirtualRoot.Out.ShowError("数值格式错误,必须是数值(可带小数)");
+                        }
+                        else {
+                            int virtualMemoryMb = Convert.ToInt32(virtualMemoryGb * 1024);
+                            Dictionary<string, int> data = new Dictionary<string, int> {
+                                {"Auto", virtualMemoryMb }
+                            };
+                            foreach (var item in this.SelectedMinerClients) {
+                                MinerStudioRoot.MinerStudioService.SetVirtualMemoryAsync(item, data);
+                            }
+                        }
+                    });
+                }
+                #endregion
+            }, CanCommand);
+            this.LocalIpConfig = new DelegateCommand(() => {
+                #region
+                if (SelectedMinerClients.Length == 0) {
+                    ShowNoRecordSelected();
+                }
+                else if (SelectedMinerClients.Length == 1) {
+                    VirtualRoot.Execute(new ShowMinerStudioLocalIpsCommand(new LocalIpConfigViewModel(SelectedMinerClients[0])));
+                }
+                #endregion
+            }, () => this.SelectedMinerClients != null && this.SelectedMinerClients.Length == 1);
+            this.SwitchRadeonGpu = new DelegateCommand(() => {
+                var config = new DialogWindowViewModel(
+                    isConfirmNo: true,
+                    btnNoToolTip: "注意:关闭计算模式挖矿算力会减半",
+                    message: $"过程大概需要花费5到10秒钟,最好矿机没有处在挖矿中否则内核会重启。", title: "确认", onYes: () => {
+                        foreach (var item in SelectedMinerClients) {
+                            MinerStudioRoot.MinerStudioService.SwitchRadeonGpuAsync(item, on: true);
+                        }
+                    }, onNo: () => {
+                        foreach (var item in SelectedMinerClients) {
+                            MinerStudioRoot.MinerStudioService.SwitchRadeonGpuAsync(item, on: false);
+                        }
+                        return true;
+                    }, btnYesText: "开启计算模式", btnNoText: "关闭计算模式");
+                this.ShowSoftDialog(config);
+            }, CanCommand);
+            this.AtikmdagPatcher = new DelegateCommand(() => {
+                this.ShowSoftDialog(new DialogWindowViewModel(message: $"确定对选中的矿机进行A卡驱动签名吗?", title: "确认", onYes: () => {
+                    foreach (var item in SelectedMinerClients) {
+                        MinerStudioRoot.MinerStudioService.AtikmdagPatcherAsync(item);
+                    }
+                }));
+            }, CanCommand);
+            this.PageUp = new DelegateCommand(() => {
+                this.PageIndex -= 1;
+            });
+            this.PageDown = new DelegateCommand(() => {
+                this.PageIndex += 1;
+            });
+            this.PageFirst = new DelegateCommand(() => {
+                this.PageIndex = 1;
+            });
+            this.PageLast = new DelegateCommand(() => {
+                this.PageIndex = PageCount;
+            });
+            this.PageRefresh = new DelegateCommand(QueryMinerClients);
+            this.SortByMinerName = new DelegateCommand(() => {
+                if (SortDirection == SortDirection.Ascending) {
+                    SortDirection = SortDirection.Descending;
+                }
+                else {
+                    SortDirection = SortDirection.Ascending;
+                }
+            });
+            VirtualRoot.AddCmdPath<UpdateMinerClientVmCommand>(action: message => {
+                if (_minerClients == null) {
+                    return;
+                }
+                var vm = _minerClients.FirstOrDefault(a => a.Id == message.ClientData.Id);
+                if (vm != null) {
+                    vm.Update(message.ClientData);
+                }
+            }, this.GetType(), LogEnum.DevConsole);
+            if (RpcRoot.IsOuterNet) {
+                VirtualRoot.AddCmdPath<RefreshWsStateCommand>(message => {
+                    if (message.WsClientState != null) {
+                        this.IsWsOnline = message.WsClientState.Status == WsClientStatus.Open;
+                        if (message.WsClientState.ToOut) {
+                            VirtualRoot.Out.ShowWarn(message.WsClientState.Description, autoHideSeconds: 3);
+                        }
+                        if (!message.WsClientState.ToOut || !this.IsWsOnline) {
+                            this.WsDescription = message.WsClientState.Description;
+                        }
+                        if (!this.IsWsOnline) {
+                            if (message.WsClientState.LastTryOn != DateTime.MinValue) {
+                                this.WsLastTryOn = message.WsClientState.LastTryOn;
+                            }
+                            if (message.WsClientState.NextTrySecondsDelay > 0) {
+                                WsNextTrySecondsDelay = message.WsClientState.NextTrySecondsDelay;
+                            }
+                        }
+                    }
+                }, this.GetType(), LogEnum.DevConsole);
+                VirtualRoot.Execute(new RefreshWsStateCommand(MinerStudioRoot.WsClient.GetState()));
+            }
+            VirtualRoot.AddEventPath<Per1SecondEvent>("外网群控重试秒表倒计时", LogEnum.None, action: message => {
+                if (!IsWsOnline) {
+                    if (WsNextTrySecondsDelay > 0) {
+                        WsNextTrySecondsDelay--;
+                    }
+                    OnPropertyChanged(nameof(WsLastTryOnText));
+                }
+            }, this.GetType());
+            this.WsRetry = new DelegateCommand(() => {
+                if (!RpcRoot.IsOuterNet || MinerStudioRoot.WsClient.IsOpen) {
+                    IsWsOnline = true;
+                    return;
+                }
+                MinerStudioRoot.WsClient.OpenOrCloseWs(isResetFailCount: true);
+                IsConnecting = true;
+            });
+        }
+        #endregion
+
+        private bool CanCommand() {
+            return this.SelectedMinerClients != null && this.SelectedMinerClients.Length != 0;
+        }
+
+        private bool OnlySelectedOne() {
+            return this.SelectedMinerClients != null
+                    && this.SelectedMinerClients.Length == 1;
+        }
+
+        #region IWsStateViewModel的成员
+
+        // 由守护进程根据外网群控是否正常更新
+        public bool IsWsOnline {
+            get => _isWsOnline;
+            set {
+                if (_isWsOnline != value) {
+                    _isWsOnline = value;
+                    OnPropertyChanged(nameof(IsWsOnline));
+                    OnPropertyChanged(nameof(WsStateText));
+                    OnPropertyChanged(nameof(WsNextTrySecondsDelayVisible));
+                }
+            }
+        }
+
+        public string WsDescription {
+            get {
+                if (string.IsNullOrEmpty(RpcRoot.RpcUser.LoginName)) {
+                    return "未登录";
+                }
+                if (string.IsNullOrEmpty(_wsDescription)) {
+                    return WsStateText;
+                }
+                return _wsDescription;
+            }
+            set {
+                if (_wsDescription != value) {
+                    _wsDescription = value;
+                    OnPropertyChanged(nameof(WsDescription));
+                }
+            }
+        }
+
+        public int WsNextTrySecondsDelay {
+            get {
+                if (_wsNextTrySecondsDelay < 0) {
+                    return 0;
+                }
+                return _wsNextTrySecondsDelay;
+            }
+            set {
+                if (_wsNextTrySecondsDelay != value) {
+                    _wsNextTrySecondsDelay = value;
+                    OnPropertyChanged(nameof(WsNextTrySecondsDelay));
+                    OnPropertyChanged(nameof(WsNextTrySecondsDelayText));
+                    OnPropertyChanged(nameof(WsNextTrySecondsDelayVisible));
+                    IsConnecting = value <= 0;
+                }
+            }
+        }
+
+        public DateTime WsLastTryOn {
+            get => _wsLastTryOn;
+            set {
+                if (_wsLastTryOn != value) {
+                    _wsLastTryOn = value;
+                    OnPropertyChanged(nameof(WsLastTryOn));
+                    OnPropertyChanged(nameof(WsLastTryOnText));
+                }
+            }
+        }
+
+        public bool IsConnecting {
+            get => _isConnecting;
+            set {
+                if (_isConnecting != value) {
+                    _isConnecting = value;
+                    OnPropertyChanged(nameof(IsConnecting));
+                    OnPropertyChanged(nameof(WsRetryText));
+                    if (value) {
+                        VirtualRoot.SetInterval(TimeSpan.FromMilliseconds(100), perCallback: () => {
+                            WsRetryIconAngle += 40;
+                        }, stopCallback: () => {
+                            WsRetryIconAngle = 0;
+                            IsConnecting = false;
+                        }, timeout: TimeSpan.FromSeconds(10), requestStop: () => {
+                            return !IsConnecting;
+                        });
+                    }
+                }
+            }
+        }
+
+        #endregion
+
+        public MinerStudioRoot.MinerClientConsoleViewModel MinerClientConsoleVm {
+            get {
+                return MinerStudioRoot.MinerClientConsoleVm;
+            }
+        }
+
+        public double WsRetryIconAngle {
+            get { return _wsRetryIconAngle; }
+            set {
+                _wsRetryIconAngle = value;
+                OnPropertyChanged(nameof(WsRetryIconAngle));
+            }
+        }
+
+        public string WsRetryText {
+            get {
+                if (IsConnecting) {
+                    return "重试中";
+                }
+                return "立即重试";
+            }
+        }
+
+        public string WsLastTryOnText {
+            get {
+                if (IsWsOnline || WsLastTryOn == DateTime.MinValue) {
+                    return string.Empty;
+                }
+                return Timestamp.GetTimeSpanText(WsLastTryOn);
+            }
+        }
+
+        public string WsNextTrySecondsDelayText {
+            get {
+                int seconds = WsNextTrySecondsDelay;
+                if (IsWsOnline) {
+                    return string.Empty;
+                }
+                if (seconds >= 3600) {
+                    return $"{(seconds / 3600).ToString()} 小时 {(seconds % 3600 / 60).ToString()} 分钟 {(seconds % 3600 % 60).ToString()} 秒后";
+                }
+                if (seconds > 60) {
+                    return $"{(seconds / 60).ToString()} 分 {(seconds % 60).ToString()} 秒后";
+                }
+                return $"{seconds.ToString()} 秒后";
+            }
+        }
+
+        public Visibility WsNextTrySecondsDelayVisible {
+            get {
+                if (!RpcRoot.IsOuterNet) {
+                    return Visibility.Collapsed;
+                }
+                if (IsWsOnline) {
+                    return Visibility.Collapsed;
+                }
+                return Visibility.Visible;
+            }
+        }
+
+        public string WsStateText {
+            get {
+                if (IsWsOnline) {
+                    return "连接服务器成功";
+                }
+                return "离线";
+            }
+        }
+
+        public MainMenuViewModel MainMenu {
+            get {
+                return MainMenuViewModel.Instance;
+            }
+        }
+
+        public string NetTypeText {
+            get {
+                if (!RpcRoot.IsLogined) {
+                    return string.Empty;
+                }
+                if (RpcRoot.IsOuterNet) {
+                    return "外网群控";
+                }
+                return "内网群控";
+            }
+        }
+
+        public int FrozenColumnCount {
+            get => _frozenColumnCount;
+            set {
+                if (value >= 2) {
+                    _frozenColumnCount = value;
+                    OnPropertyChanged(nameof(FrozenColumnCount));
+                    VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                        Key = NTKeyword.FrozenColumnCountAppSettingKey,
+                        Value = value
+                    }));
+                }
+            }
+        }
+
+        public List<int> FrozenColumns {
+            get { return _frozenColumns; }
+        }
+
+        public int RejectPercent {
+            get => _rejectPercent;
+            set {
+                _rejectPercent = value;
+                OnPropertyChanged(nameof(RejectPercent));
+                RefreshRejectPercentForeground();
+                VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                    Key = NTKeyword.RejectPercentAppSettingKey,
+                    Value = value
+                }));
+            }
+        }
+
+        private void RefreshRejectPercentForeground() {
+            foreach (MinerClientViewModel item in MinerClients) {
+                if (item.MainCoinRejectPercent >= this.RejectPercent) {
+                    item.MainCoinRejectPercentForeground = WpfUtil.RedBrush;
+                }
+                else {
+                    item.MainCoinRejectPercentForeground = MinerClientViewModel.DefaultForeground;
+                }
+
+                if (item.DualCoinRejectPercent >= this.RejectPercent) {
+                    item.DualCoinRejectPercentForeground = WpfUtil.RedBrush;
+                }
+                else {
+                    item.DualCoinRejectPercentForeground = MinerClientViewModel.DefaultForeground;
+                }
+            }
+        }
+
+        public uint MaxTemp {
+            get => _maxTemp;
+            set {
+                if (value > this.MinTemp && value != _maxTemp) {
+                    _maxTemp = value;
+                    OnPropertyChanged(nameof(MaxTemp));
+                    RefreshMaxTempForeground();
+                    VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                        Key = NTKeyword.MaxTempAppSettingKey,
+                        Value = value
+                    }));
+                }
+            }
+        }
+
+        public uint MinTemp {
+            get => _minTemp;
+            set {
+                if (value < this.MaxTemp && value != _minTemp) {
+                    _minTemp = value;
+                    OnPropertyChanged(nameof(MinTemp));
+                    RefreshMaxTempForeground();
+                    VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                        Key = NTKeyword.MinTempAppSettingKey,
+                        Value = value
+                    }));
+                }
+            }
+        }
+
+        private void RefreshMaxTempForeground() {
+            foreach (MinerClientViewModel item in MinerClients) {
+                if (item.GpuTableVm == null) {
+                    continue;
+                }
+                if (item.GpuTableVm.MaxTemp >= this.MaxTemp) {
+                    item.GpuTableVm.TempForeground = WpfUtil.RedBrush;
+                }
+                else if (item.GpuTableVm.MaxTemp < this.MinTemp) {
+                    item.GpuTableVm.TempForeground = MinerClientViewModel.Blue;
+                }
+                else {
+                    item.GpuTableVm.TempForeground = MinerClientViewModel.DefaultForeground;
+                }
+                item.RefreshGpusForeground(this.MinTemp, this.MaxTemp);
+            }
+        }
+
+        private void ShowNoRecordSelected() {
+            VirtualRoot.Out.ShowWarn("没有选中记录", autoHideSeconds: 2);
+        }
+
+        public ColumnsShowViewModel ColumnsShow {
+            get {
+                return _columnsShow;
+            }
+            set {
+                if (_columnsShow != value && value != null) {
+                    if (_columnsShow != null) {
+                        VirtualRoot.Execute(new SetLocalAppSettingCommand(new AppSettingData {
+                            Key = NTKeyword.ColumnsShowIdAppSettingKey,
+                            Value = value.Id
+                        }));
+                    }
+                    _columnsShow = value;
+                    OnPropertyChanged(nameof(ColumnsShow));
+                }
+            }
+        }
+
+        public MinerStudioRoot.ColumnsShowViewModels ColumnsShows {
+            get {
+                return MinerStudioRoot.ColumnsShowVms;
+            }
+        }
+
+        public int CountDown {
+            get { return _countDown; }
+            set {
+                _countDown = value;
+                OnPropertyChanged(nameof(CountDown));
+            }
+        }
+
+        private static readonly List<int> _pageSizeItems = new List<int>() { 10, 20, 30, 40 };
+        public List<int> PageSizeItems {
+            get {
+                return _pageSizeItems;
+            }
+        }
+
+        public bool IsPageUpEnabled {
+            get {
+                if (this.PageIndex <= 1) {
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        public bool IsPageDownEnabled {
+            get {
+                if (this.PageIndex >= this.PageCount) {
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        public int PageIndex {
+            get => _pageIndex;
+            set {
+                _pageIndex = value;
+                OnPropertyChanged(nameof(PageIndex));
+                QueryMinerClients();
+            }
+        }
+
+        public int PageCount {
+            get {
+                return (int)Math.Ceiling((double)this.Total / this.PageSize);
+            }
+        }
+
+        public int PageSize {
+            get => _pageSize;
+            set {
+                if (_pageSize != value) {
+                    _pageSize = value;
+                    OnPropertyChanged(nameof(PageSize));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public SortDirection SortDirection {
+            get { return _sortDirection; }
+            set {
+                if (_sortDirection != value) {
+                    _sortDirection = value;
+                    OnPropertyChanged(nameof(SortDirection));
+                    OnPropertyChanged(nameof(IsSortAscending));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public bool IsSortAscending {
+            get {
+                return SortDirection == SortDirection.Ascending;
+            }
+        }
+
+        public int Total {
+            get => _total;
+            set {
+                if (_total != value) {
+                    _total = value;
+                    OnPropertyChanged(nameof(Total));
+                }
+            }
+        }
+
+        private SortDirection _lastSortDirection;
+        public void QueryMinerClients() {
+            if (!RpcRoot.IsLogined) {
+                return;
+            }
+            Guid? groupId = null;
+            if (SelectedMinerGroup == null) {
+                _selectedMinerGroup = MinerGroupViewModel.PleaseSelect;
+                OnPropertyChanged(nameof(SelectedMinerGroup));
+            }
+            if (SelectedMinerGroup != MinerGroupViewModel.PleaseSelect) {
+                groupId = SelectedMinerGroup.Id;
+            }
+            Guid? workId = null;
+            if (SelectedMineWork == null) {
+                _selectedMineWork = MineWorkViewModel.PleaseSelect;
+                OnPropertyChanged(nameof(SelectedMineWork));
+            }
+            if (SelectedMineWork != MineWorkViewModel.PleaseSelect) {
+                workId = SelectedMineWork.Id;
+            }
+            string coin = string.Empty;
+            string wallet = string.Empty;
+            if (workId == null || workId.Value == Guid.Empty) {
+                if (this.MineCoinVm != CoinSnapshotViewModel.PleaseSelect && this.MineCoinVm != null) {
+                    coin = this.MineCoinVm.CoinVm.Code;
+                }
+                if (!string.IsNullOrEmpty(Wallet)) {
+                    wallet = this.Wallet;
+                }
+            }
+            MinerStudioRoot.MinerStudioService.QueryClientsAsync(new QueryClientsRequest {
+                PageIndex = this.PageIndex,
+                PageSize = this.PageSize,
+                WorkId = workId,
+                GroupId = groupId,
+                MinerIp = this.MinerIp,
+                MinerName = this.MinerName,
+                MineState = this.MineStatusEnumItem.Value,
+                Coin = coin,
+                Pool = this.Pool,
+                Wallet = wallet,
+                Version = this.Version,
+                Kernel = this.Kernel,
+                SortDirection = SortDirection
+            }, (response, exception) => {
+                if (response.IsSuccess()) {
+                    UIThread.Execute(() => () => {
+                        this.CountDown = 10;
+                        #region 处理Response.Data
+                        bool isMinerClientsChanged = false;
+                        if (response.Data.Count == 0) {
+                            _minerClients = new List<MinerClientViewModel>();
+                            isMinerClientsChanged = true;
+                        }
+                        else {
+                            List<MinerClientViewModel> minerClients = _minerClients;
+                            var toRemoves = minerClients.Where(a => response.Data.All(b => b.Id != a.Id)).ToArray();
+                            // 如果旧列表有要删除的元素则列表变更
+                            isMinerClientsChanged = toRemoves.Length != 0;
+                            foreach (var item in toRemoves) {
+                                minerClients.Remove(item);
+                            }
+                            for (int i = 0; i < response.Data.Count; i++) {
+                                var data = response.Data[i];
+                                var item = minerClients.FirstOrDefault(a => a.Id == data.Id);
+                                if (item == null) {
+                                    // 因为内网模式时是本地调用,未经过网络传输,所以MinerClientViewModel内部的ClientData类型的
+                                    // _data字段会和response.Data[i]是同一性的,所以这里Clone一次以防止Vm.Update的时候无效
+                                    var clientData = data;
+                                    MinerClientViewModel vm;
+                                    if (!RpcRoot.IsOuterNet) {
+                                        clientData = ClientData.Clone(data);
+                                        vm = new MinerClientViewModel(clientData);
+                                    }
+                                    else {
+                                        vm = new MinerClientViewModel(clientData);
+                                    }
+                                    minerClients.Insert(i, vm);
+                                    // 如果旧列表有要添加的原色则列表变更
+                                    if (!isMinerClientsChanged) {
+                                        isMinerClientsChanged = true;
+                                    }
+                                }
+                                else {
+                                    if (item.MinerName != data.MinerName && !isMinerClientsChanged) {
+                                        isMinerClientsChanged = true;
+                                    }
+                                    item.Update(data);
+                                }
+                            }
+                            if (!isMinerClientsChanged && _lastSortDirection != this.SortDirection) {
+                                isMinerClientsChanged = true;
+                            }
+                            if (isMinerClientsChanged) {
+                                // 重建对象从而使OnPropertyChanged(nameof(MinerClients))不因为对象引用相等而失效
+                                minerClients.Sort(new MinerComparerByMinerName(_sortDirection));
+                                _minerClients = minerClients.ToList();
+                            }
+                        }
+                        RefreshPagingUi(response.Total);
+                        if (isMinerClientsChanged) {
+                            OnPropertyChanged(nameof(MinerClients));
+                        }
+                        RefreshMaxTempForeground();
+                        RefreshRejectPercentForeground();
+                        #endregion
+                        #region 处理Response.LatestSnapshots
+                        foreach (var item in CoinSnapshotVms) {
+                            if (item == CoinSnapshotViewModel.PleaseSelect) {
+                                item.CoinSnapshotDataVm.MainCoinMiningCount = response.TotalMiningCount;
+                                item.CoinSnapshotDataVm.MainCoinOnlineCount = response.TotalOnlineCount;
+                                continue;
+                            }
+                            var data = response.LatestSnapshots.FirstOrDefault(a => a.CoinCode == item.CoinVm.Code);
+                            if (data != null) {
+                                item.CoinSnapshotDataVm.Update(data);
+                            }
+                            else {
+                                item.CoinSnapshotDataVm.Update(CoinSnapshotData.CreateEmpty(item.CoinVm.Code));
+                            }
+                        }
+                        #endregion
+                        _lastSortDirection = this.SortDirection;
+                    });
+                }
+            });
+        }
+
+        private void RefreshPagingUi(int total) {
+            _total = total;
+            OnPropertyChanged(nameof(Total));
+            OnPropertyChanged(nameof(PageCount));
+            OnPropertyChanged(nameof(IsPageDownEnabled));
+            OnPropertyChanged(nameof(IsPageUpEnabled));
+            if (Total == 0) {
+                _pageIndex = 0;
+                OnPropertyChanged(nameof(PageIndex));
+            }
+            else if (PageIndex == 0) {
+                _pageIndex = 1;
+                OnPropertyChanged(nameof(PageIndex));
+            }
+        }
+
+        public void RefreshMinerClientsSelectedMinerGroup() {
+            foreach (var minerClient in this.MinerClients) {
+                minerClient.OnPropertyChanged(nameof(minerClient.SelectedMinerGroup));
+            }
+        }
+
+        public void RefreshMinerClientsSelectedMineWork() {
+            foreach (var minerClient in this.MinerClients) {
+                minerClient.OnPropertyChanged(nameof(minerClient.SelectedMineWork));
+            }
+        }
+
+        public List<MinerClientViewModel> MinerClients {
+            get {
+                return _minerClients;
+            }
+        }
+
+        public MinerClientViewModel CurrentMinerClient {
+            get { return _currentMinerClient; }
+            set {
+                _currentMinerClient = value;
+                OnPropertyChanged(nameof(CurrentMinerClient));
+                VirtualRoot.RaiseEvent(new MinerClientSelectionChangedEvent(value));
+            }
+        }
+
+        public MinerClientViewModel[] SelectedMinerClients {
+            get { return _selectedMinerClients; }
+            set {
+                _selectedMinerClients = value;
+                OnPropertyChanged(nameof(SelectedMinerClients));
+            }
+        }
+
+        public List<CoinSnapshotViewModel> CoinSnapshotVms {
+            get {
+                if (_coinSnapshotVms == null) {
+                    _coinSnapshotVms = new List<CoinSnapshotViewModel> { CoinSnapshotViewModel.PleaseSelect };
+                    foreach (var coinVm in AppRoot.CoinVms.AllCoins) {
+                        _coinSnapshotVms.Add(new CoinSnapshotViewModel(coinVm, new CoinSnapshotDataViewModel(CoinSnapshotData.CreateEmpty(coinVm.Code))));
+                    }
+                }
+                return _coinSnapshotVms;
+            }
+        }
+
+        private IEnumerable<CoinViewModel> GetDualCoinVmItems() {
+            yield return CoinViewModel.PleaseSelect;
+            yield return CoinViewModel.DualCoinEnabled;
+            foreach (var item in AppRoot.CoinVms.AllCoins) {
+                yield return item;
+            }
+        }
+        public List<CoinViewModel> DualCoinVmItems {
+            get {
+                return GetDualCoinVmItems().ToList();
+            }
+        }
+
+        public CoinSnapshotViewModel MineCoinVm {
+            get { return _mineCoinVm; }
+            set {
+                if (_mineCoinVm != value) {
+                    _mineCoinVm = value;
+                    OnPropertyChanged(nameof(MineCoinVm));
+                    this._pool = string.Empty;
+                    this._poolVm = PoolViewModel.PleaseSelect;
+                    OnPropertyChanged(nameof(PoolVm));
+                    OnPropertyChanged(nameof(IsMainCoinSelected));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public bool IsMainCoinSelected {
+            get {
+                if (MineCoinVm == CoinSnapshotViewModel.PleaseSelect) {
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        public string Pool {
+            get { return _pool; }
+            set {
+                _pool = value;
+                OnPropertyChanged(nameof(Pool));
+                this.PageIndex = 1;
+            }
+        }
+
+        public PoolViewModel PoolVm {
+            get => _poolVm;
+            set {
+                if (_poolVm != value) {
+                    _poolVm = value;
+                    if (value == null) {
+                        Pool = string.Empty;
+                    }
+                    else {
+                        Pool = value.Server;
+                    }
+                    OnPropertyChanged(nameof(PoolVm));
+                }
+            }
+        }
+
+        public string Wallet {
+            get => _wallet;
+            set {
+                if (_wallet != value) {
+                    _wallet = value;
+                    OnPropertyChanged(nameof(Wallet));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public string MinerIp {
+            get => _minerIp;
+            set {
+                if (_minerIp != value) {
+                    _minerIp = value;
+                    OnPropertyChanged(nameof(MinerIp));
+                    if (!string.IsNullOrEmpty(value)) {
+                        if (!IPAddress.TryParse(value, out IPAddress _)) {
+                            throw new ValidationException("IP地址格式不正确");
+                        }
+                    }
+                    this.PageIndex = 1;
+                }
+            }
+        }
+        public string MinerName {
+            get => _minerName;
+            set {
+                if (_minerName != value) {
+                    _minerName = value;
+                    OnPropertyChanged(nameof(MinerName));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public string Version {
+            get => _version;
+            set {
+                if (_version != value) {
+                    _version = value;
+                    OnPropertyChanged(nameof(Version));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public string Kernel {
+            get => _kernel;
+            set {
+                if (_kernel != value) {
+                    _kernel = value;
+                    OnPropertyChanged(nameof(Kernel));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public MinerStudioRoot.MineWorkViewModels MineWorkVms {
+            get {
+                return MinerStudioRoot.MineWorkVms;
+            }
+        }
+
+        public MinerStudioRoot.MinerGroupViewModels MinerGroupVms {
+            get {
+                return MinerStudioRoot.MinerGroupVms;
+            }
+        }
+
+        public MineWorkViewModel SelectedMineWork {
+            get => _selectedMineWork;
+            set {
+                _selectedMineWork = value;
+                OnPropertyChanged(nameof(SelectedMineWork));
+                OnPropertyChanged(nameof(IsMineWorkSelected));
+                this.PageIndex = 1;
+            }
+        }
+
+        public bool IsMineWorkSelected {
+            get {
+                if (SelectedMineWork != MineWorkViewModel.PleaseSelect) {
+                    return true;
+                }
+                return false;
+            }
+        }
+
+        public MinerGroupViewModel SelectedMinerGroup {
+            get => _selectedMinerGroup;
+            set {
+                _selectedMinerGroup = value;
+                OnPropertyChanged(nameof(SelectedMinerGroup));
+                this.PageIndex = 1;
+            }
+        }
+
+        public EnumItem<MineStatus> MineStatusEnumItem {
+            get => _mineStatusEnumItem;
+            set {
+                if (_mineStatusEnumItem != value) {
+                    _mineStatusEnumItem = value;
+                    OnPropertyChanged(nameof(MineStatusEnumItem));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+    }
+}

+ 50 - 0
src/AppModels/MinerStudio/Vms/MinerGroupSelectViewModel.cs

@@ -0,0 +1,50 @@
+using NTMiner.Vms;
+using System;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class MinerGroupSelectViewModel : ViewModelBase {
+        private MinerGroupViewModel _selectedResult;
+        private string _description;
+        public readonly Action<MinerGroupViewModel> OnOk;
+
+        public ICommand HideView { get; set; }
+
+        [Obsolete(message: NTKeyword.WpfDesignOnly, error: true)]
+        public MinerGroupSelectViewModel() {
+            if (!WpfUtil.IsInDesignMode) {
+                throw new InvalidProgramException(NTKeyword.WpfDesignOnly);
+            }
+        }
+
+        public MinerGroupSelectViewModel(string description, MinerGroupViewModel selected, Action<MinerGroupViewModel> onOk) {
+            _description = description;
+            _selectedResult = selected;
+            OnOk = onOk;
+        }
+
+        public string Description {
+            get { return _description; }
+            set {
+                _description = value;
+                OnPropertyChanged(nameof(Description));
+            }
+        }
+
+        public MinerGroupViewModel SelectedResult {
+            get => _selectedResult;
+            set {
+                if (_selectedResult != value) {
+                    _selectedResult = value;
+                    OnPropertyChanged(nameof(SelectedResult));
+                }
+            }
+        }
+
+        public MinerStudioRoot.MinerGroupViewModels MinerGroupVms {
+            get {
+                return MinerStudioRoot.MinerGroupVms;
+            }
+        }
+    }
+}

+ 31 - 14
src/AppModels/Vms/MinerGroupViewModel.cs → src/AppModels/MinerStudio/Vms/MinerGroupViewModel.cs

@@ -1,10 +1,10 @@
 using NTMiner.Core;
-using NTMiner.Core.MinerServer;
+using NTMiner.Vms;
 using System;
 using System.Linq;
 using System.Windows.Input;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class MinerGroupViewModel : ViewModelBase, IMinerGroup, IEditableViewModel {
         public static readonly MinerGroupViewModel PleaseSelect = new MinerGroupViewModel(Guid.Empty) {
             _name = "不指定"
@@ -14,8 +14,8 @@ namespace NTMiner.Vms {
         private string _name;
         private string _description;
 
-        public ICommand Remove { get; private set; }
         public ICommand Edit { get; private set; }
+        public ICommand Remove { get; private set; }
         public ICommand Save { get; private set; }
 
         [Obsolete(message: NTKeyword.WpfDesignOnly, error: true)]
@@ -31,31 +31,42 @@ namespace NTMiner.Vms {
                 if (this.Id == Guid.Empty) {
                     return;
                 }
-                if (NTMinerRoot.Instance.MinerGroupSet.TryGetMinerGroup(this.Id, out IMinerGroup group)) {
-                    VirtualRoot.Execute(new UpdateMinerGroupCommand(this));
+                if (RpcRoot.IsOuterNet) {
+                    RpcRoot.OfficialServer.UserMinerGroupService.AddOrUpdateMinerGroupAsync(new MinerGroupData().Update(this), (response, e) => {
+                        if (response.IsSuccess()) {
+                            if (MinerStudioRoot.MinerGroupVms.TryGetMineWorkVm(this.Id, out MinerGroupViewModel vm)) {
+                                VirtualRoot.RaiseEvent(new MinerGroupUpdatedEvent(Guid.Empty, this));
+                            }
+                            else {
+                                VirtualRoot.RaiseEvent(new MinerGroupAddedEvent(Guid.Empty, this));
+                            }
+                            VirtualRoot.Execute(new CloseWindowCommand(this.Id));
+                        }
+                    });
                 }
                 else {
-                    VirtualRoot.Execute(new AddMinerGroupCommand(this));
+                    if (NTMinerContext.Instance.MinerStudioContext.MinerGroupSet.Contains(this.Id)) {
+                        VirtualRoot.Execute(new UpdateMinerGroupCommand(this));
+                    }
+                    else {
+                        VirtualRoot.Execute(new AddMinerGroupCommand(this));
+                    }
+                    VirtualRoot.Execute(new CloseWindowCommand(this.Id));
                 }
-                VirtualRoot.Execute(new CloseWindowCommand(this.Id));
             });
             this.Edit = new DelegateCommand<FormType?>((formType) => {
                 if (this.Id == Guid.Empty) {
                     return;
                 }
-                VirtualRoot.Execute(new MinerGroupEditCommand(formType ?? FormType.Edit, this));
-            }, (formType) => {
-                return this != PleaseSelect;
+                VirtualRoot.Execute(new EditMinerGroupCommand(formType ?? FormType.Edit, this));
             });
             this.Remove = new DelegateCommand(() => {
                 if (this.Id == Guid.Empty) {
                     return;
                 }
-                this.ShowSoftDialog(new DialogWindowViewModel(message: $"您确定删除{this.Name}吗?", title: "确认", onYes: () => {
+                this.ShowSoftDialog(new DialogWindowViewModel(message: $"您确定删除{this.Name}” 矿机分组吗?", title: "确认", onYes: () => {
                     VirtualRoot.Execute(new RemoveMinerGroupCommand(this.Id));
                 }));
-            }, () => {
-                return this != PleaseSelect;
             });
         }
 
@@ -78,6 +89,12 @@ namespace NTMiner.Vms {
             }
         }
 
+        public bool IsPleaseSelect {
+            get {
+                return this == PleaseSelect;
+            }
+        }
+
         public string Name {
             get => _name;
             set {
@@ -90,7 +107,7 @@ namespace NTMiner.Vms {
                     if (string.IsNullOrEmpty(value)) {
                         throw new ValidationException("名称是必须的");
                     }
-                    if (AppContext.Instance.MinerGroupVms.List.Any(a => a.Name == value && a.Id != this.Id)) {
+                    if (MinerStudioRoot.MinerGroupVms.List.Any(a => a.Name == value && a.Id != this.Id)) {
                         throw new ValidationException("名称重复");
                     }
                 }

+ 5 - 5
src/AppModels/Vms/MinerNamesSeterViewModel.cs → src/AppModels/MinerStudio/Vms/MinerNamesSeterViewModel.cs

@@ -1,15 +1,15 @@
-using System;
+using NTMiner.Vms;
+using System;
 using System.Collections.Generic;
 using System.Windows.Input;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class MinerNamesSeterViewModel : ViewModelBase {
         public readonly Guid Id = Guid.NewGuid();
         private List<Tuple<string, string>> _namesByObjectId;
         private string _prefix;
         private string _suffix;
 
-        public bool IsOk { get; private set; }
         public ICommand Save { get; private set; }
 
         [Obsolete(message: NTKeyword.WpfDesignOnly, error: true)]
@@ -19,14 +19,14 @@ namespace NTMiner.Vms {
             }
         }
 
-        public MinerNamesSeterViewModel(string prefix, string suffix, List<Tuple<string, string>> namesByObjectId) {
+        public MinerNamesSeterViewModel(string prefix, string suffix, List<Tuple<string, string>> namesByObjectId, Action onOk) {
             _prefix = prefix;
             _suffix = suffix;
             _namesByObjectId = namesByObjectId;
             RefreshNames();
             this.Save = new DelegateCommand(() => {
-                this.IsOk = true;
                 VirtualRoot.Execute(new CloseWindowCommand(this.Id));
+                onOk?.Invoke();
             });
         }
 

+ 55 - 0
src/AppModels/MinerStudio/Vms/NTMinerFileSelectViewModel.cs

@@ -0,0 +1,55 @@
+using NTMiner.Core.MinerServer;
+using NTMiner.Vms;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class NTMinerFileSelectViewModel : ViewModelBase {
+        private List<NTMinerFileViewModel> _ntminerFileVms;
+
+        private NTMinerFileViewModel _selectedResult;
+        public readonly Action<NTMinerFileViewModel> OnOk;
+
+        public ICommand HideView { get; set; }
+
+        [Obsolete(message: NTKeyword.WpfDesignOnly, error: true)]
+        public NTMinerFileSelectViewModel() {
+            if (!WpfUtil.IsInDesignMode) {
+                throw new InvalidProgramException(NTKeyword.WpfDesignOnly);
+            }
+        }
+
+        public NTMinerFileSelectViewModel(Action<NTMinerFileViewModel> onOk) {
+            OnOk = onOk;
+            _selectedResult = null;
+            _ntminerFileVms = new List<NTMinerFileViewModel>();
+            // 因为NTMinerFiles列表是异步初始化的,下面填充几个空对象的目的是解决WPFpopup的某个BUG,否则第一次打开popup的时候位置不对。
+            for (int i = 0; i < 7; i++) {
+                _ntminerFileVms.Add(NTMinerFileViewModel.Empty);
+            }
+            RpcRoot.OfficialServer.FileUrlService.GetNTMinerFilesAsync(NTMinerAppType.MinerClient, (ntminerFiles) => {
+                NTMinerFileVms = (ntminerFiles ?? new List<NTMinerFileData>()).OrderByDescending(a => a.GetVersion()).Select(a => new NTMinerFileViewModel(a)).ToList();
+            });
+        }
+
+        public NTMinerFileViewModel SelectedResult {
+            get => _selectedResult;
+            set {
+                if (_selectedResult != value) {
+                    _selectedResult = value;
+                    OnPropertyChanged(nameof(SelectedResult));
+                }
+            }
+        }
+
+        public List<NTMinerFileViewModel> NTMinerFileVms {
+            get { return _ntminerFileVms; }
+            set {
+                _ntminerFileVms = value;
+                OnPropertyChanged(nameof(NTMinerFileVms));
+            }
+        }
+    }
+}

+ 133 - 0
src/AppModels/MinerStudio/Vms/NTMinerFileViewModel.cs

@@ -0,0 +1,133 @@
+using NTMiner.Core.MinerServer;
+using NTMiner.Vms;
+using System;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class NTMinerFileViewModel : ViewModelBase, INTMinerFile {
+        public static readonly NTMinerFileViewModel Empty = new NTMinerFileViewModel(new NTMinerFileData {
+            AppType = NTMinerAppType.MinerClient,
+            CreatedOn = Timestamp.UnixBaseTime,
+            Description = string.Empty,
+            FileName = string.Empty,
+            Id = Guid.Empty,
+            PublishOn = Timestamp.UnixBaseTime,
+            Title = string.Empty,
+            Version = "1.0.0",
+            VersionTag = string.Empty
+        });
+
+        private Version _versionData;
+        private string _fileName;
+        private string _version;
+        private string _versionTag;
+        private DateTime _createdOn;
+        private Guid _id;
+        private NTMinerAppType _appType;
+        private DateTime _publishOn;
+        private string _title;
+        private string _description;
+
+        public NTMinerFileViewModel(INTMinerFile data) {
+            _id = data.Id;
+            _appType = data.AppType;
+            _version = data.Version;
+            _fileName = data.FileName;
+            _versionTag = data.VersionTag;
+            _createdOn = data.CreatedOn;
+            _publishOn = data.PublishOn;
+            _title = data.Title;
+            _description = data.Description;
+            if (!System.Version.TryParse(_version, out _versionData)) {
+                _versionData = new Version(1, 0);
+            }
+        }
+
+        public Guid Id {
+            get => _id;
+            set {
+                _id = value;
+                OnPropertyChanged(nameof(Id));
+            }
+        }
+
+        public NTMinerAppType AppType {
+            get { return _appType; }
+            set {
+                _appType = value;
+                OnPropertyChanged(nameof(AppType));
+            }
+        }
+
+        public string FileName {
+            get => _fileName;
+            set {
+                _fileName = value;
+                OnPropertyChanged(nameof(FileName));
+            }
+        }
+
+        public string Version {
+            get => _version;
+            set {
+                _version = value;
+                OnPropertyChanged(nameof(Version));
+                if (!System.Version.TryParse(this.Version, out _versionData)) {
+                    _versionData = new Version(1, 0);
+                }
+                OnPropertyChanged(nameof(VersionData));
+            }
+        }
+
+        public string Title {
+            get => _title;
+            set {
+                _title = value;
+                OnPropertyChanged(nameof(Title));
+            }
+        }
+
+        public string Description {
+            get { return _description; }
+            set {
+                _description = value;
+                OnPropertyChanged(nameof(Description));
+            }
+        }
+
+        public Version VersionData {
+            get {
+                return _versionData;
+            }
+        }
+
+        public string VersionTag {
+            get => _versionTag;
+            set {
+                _versionTag = value;
+                OnPropertyChanged(nameof(VersionTag));
+            }
+        }
+
+        public DateTime CreatedOn {
+            get => _createdOn;
+            set {
+                _createdOn = value;
+                OnPropertyChanged(nameof(CreatedOn));
+            }
+        }
+
+        public string PublishOnText {
+            get {
+                return this.PublishOn.ToString("yyyy-MM-dd");
+            }
+        }
+
+        public DateTime PublishOn {
+            get => _publishOn;
+            set {
+                _publishOn = value;
+                OnPropertyChanged(nameof(PublishOn));
+            }
+        }
+    }
+}

+ 13 - 8
src/AppModels/Vms/NTMinerUpdaterConfigViewModel.cs → src/AppModels/MinerStudio/Vms/NTMinerUpdaterConfigViewModel.cs

@@ -1,8 +1,10 @@
 using NTMiner.Core;
+using NTMiner.Vms;
 using System;
+using System.IO;
 using System.Windows.Input;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class NTMinerUpdaterConfigViewModel : ViewModelBase {
         public readonly Guid Id = Guid.NewGuid();
         public ICommand Save { get; private set; }
@@ -14,7 +16,7 @@ namespace NTMiner.Vms {
             this.Save = new DelegateCommand(() => {
                 try {
                     if (string.IsNullOrEmpty(this.FileName)) {
-                        this.FileName = NTKeyword.NTMinerUpdaterFileName;
+                        this.FileName = HomePath.NTMinerUpdaterFileName;
                     }
                     VirtualRoot.Execute(new SetServerAppSettingCommand(new AppSettingData {
                         Key = NTKeyword.NTMinerUpdaterFileNameAppSettingKey,
@@ -26,12 +28,15 @@ namespace NTMiner.Vms {
                     Logger.ErrorDebugLine(e);
                 }
             });
-            if (NTMinerRoot.Instance.ServerAppSettingSet.TryGetAppSetting(NTKeyword.NTMinerUpdaterFileNameAppSettingKey, out IAppSetting appSetting) && appSetting.Value != null) {
-                _fileName = appSetting.Value.ToString();
-            }
-            else {
-                _fileName = NTKeyword.NTMinerUpdaterFileName;
-            }
+            RpcRoot.OfficialServer.FileUrlService.GetNTMinerUpdaterUrlAsync((fileDownloadUrl, e) => {
+                if (!string.IsNullOrEmpty(fileDownloadUrl)) {
+                    Uri uri = new Uri(fileDownloadUrl);
+                    _fileName = Path.GetFileName(uri.LocalPath);
+                }
+                else {
+                    _fileName = HomePath.NTMinerUpdaterFileName;
+                }
+            });
         }
 
         private string _fileName;

+ 5 - 4
src/AppModels/Vms/NTMinerWalletPageViewModel.cs → src/AppModels/MinerStudio/Vms/NTMinerWalletPageViewModel.cs

@@ -1,6 +1,7 @@
-using System.Linq;
+using NTMiner.Vms;
+using System.Linq;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class NTMinerWalletPageViewModel : ViewModelBase {
         private CoinViewModel _currentCoin;
 
@@ -11,9 +12,9 @@ namespace NTMiner.Vms {
             _currentCoin = CoinVms.MainCoins.FirstOrDefault();
         }
 
-        public AppContext.CoinViewModels CoinVms {
+        public AppRoot.CoinViewModels CoinVms {
             get {
-                return AppContext.Instance.CoinVms;
+                return AppRoot.CoinVms;
             }
         }
 

+ 11 - 5
src/AppModels/Vms/NTMinerWalletViewModel.cs → src/AppModels/MinerStudio/Vms/NTMinerWalletViewModel.cs

@@ -1,9 +1,9 @@
-using NTMiner.Core;
-using NTMiner.Core.MinerServer;
+using NTMiner.Core.MinerServer;
+using NTMiner.Vms;
 using System;
 using System.Windows.Input;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class NTMinerWalletViewModel : ViewModelBase, INTMinerWallet, IEditableViewModel {
         private Guid _id;
         private Guid _coinId;
@@ -26,7 +26,7 @@ namespace NTMiner.Vms {
                 if (this.Id == Guid.Empty) {
                     return;
                 }
-                if (NTMinerRoot.Instance.NTMinerWalletSet.TryGetNTMinerWallet(Id, out INTMinerWallet _)) {
+                if (NTMinerContext.Instance.MinerStudioContext.NTMinerWalletSet.TryGetNTMinerWallet(Id, out INTMinerWallet _)) {
                     VirtualRoot.Execute(new UpdateNTMinerWalletCommand(this));
                 }
                 else {
@@ -38,7 +38,7 @@ namespace NTMiner.Vms {
                 if (this.Id == Guid.Empty) {
                     return;
                 }
-                VirtualRoot.Execute(new NTMinerWalletEditCommand(formType ?? FormType.Edit, this));
+                VirtualRoot.Execute(new EditNTMinerWalletCommand(formType ?? FormType.Edit, this));
             });
             this.Remove = new DelegateCommand(() => {
                 if (this.Id == Guid.Empty) {
@@ -82,5 +82,11 @@ namespace NTMiner.Vms {
                 OnPropertyChanged(nameof(Wallet));
             }
         }
+
+        public MainMenuViewModel MainMenu {
+            get {
+                return MainMenuViewModel.Instance;
+            }
+        }
     }
 }

+ 5 - 4
src/AppModels/Vms/OverClockDataPageViewModel.cs → src/AppModels/MinerStudio/Vms/OverClockDataPageViewModel.cs

@@ -1,6 +1,7 @@
-using System.Linq;
+using NTMiner.Vms;
+using System.Linq;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class OverClockDataPageViewModel : ViewModelBase {
         private CoinViewModel _currentCoin;
 
@@ -11,9 +12,9 @@ namespace NTMiner.Vms {
             _currentCoin = CoinVms.MainCoins.FirstOrDefault();
         }
 
-        public AppContext.CoinViewModels CoinVms {
+        public AppRoot.CoinViewModels CoinVms {
             get {
-                return AppContext.Instance.CoinVms;
+                return AppRoot.CoinVms;
             }
         }
 

+ 6 - 5
src/AppModels/Vms/OverClockDataViewModel.cs → src/AppModels/MinerStudio/Vms/OverClockDataViewModel.cs

@@ -1,11 +1,12 @@
 using NTMiner.Core;
 using NTMiner.Core.MinerServer;
+using NTMiner.Vms;
 using System;
 using System.Linq;
 using System.Windows;
 using System.Windows.Input;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class OverClockDataViewModel : ViewModelBase, IOverClockData, IEditableViewModel {
         private Guid _id;
         private Guid _coinId;
@@ -37,7 +38,7 @@ namespace NTMiner.Vms {
                 if (this.Id == Guid.Empty) {
                     return;
                 }
-                if (NTMinerRoot.Instance.OverClockDataSet.TryGetOverClockData(this.Id, out IOverClockData group)) {
+                if (NTMinerContext.Instance.OverClockDataSet.TryGetOverClockData(this.Id, out IOverClockData group)) {
                     VirtualRoot.Execute(new UpdateOverClockDataCommand(this));
                 }
                 else {
@@ -49,7 +50,7 @@ namespace NTMiner.Vms {
                 if (this.Id == Guid.Empty) {
                     return;
                 }
-                VirtualRoot.Execute(new OverClockDataEditCommand(formType ?? FormType.Edit, this));
+                VirtualRoot.Execute(new EditOverClockDataCommand(formType ?? FormType.Edit, this));
             });
             this.Remove = new DelegateCommand(() => {
                 if (this.Id == Guid.Empty) {
@@ -132,7 +133,7 @@ namespace NTMiner.Vms {
 
         public EnumItem<GpuType> GpuTypeEnumItem {
             get {
-                return NTMinerRoot.GpuTypeEnumItems.FirstOrDefault(a => a.Value == GpuType);
+                return NTMinerContext.GpuTypeEnumItems.FirstOrDefault(a => a.Value == GpuType);
             }
             set {
                 if (GpuType != value.Value) {
@@ -214,7 +215,7 @@ namespace NTMiner.Vms {
         public CoinViewModel CoinVm {
             get {
                 if (_coinVm == null) {
-                    if (!AppContext.Instance.CoinVms.TryGetCoinVm(this.CoinId, out _coinVm)) {
+                    if (!AppRoot.CoinVms.TryGetCoinVm(this.CoinId, out _coinVm)) {
                         _coinVm = CoinViewModel.Empty;
                     }
                 }

+ 8 - 10
src/AppModels/Vms/RemoteDesktopLoginViewModel.cs → src/AppModels/MinerStudio/Vms/RemoteDesktopLoginViewModel.cs

@@ -1,7 +1,8 @@
-using System;
+using NTMiner.Vms;
+using System;
 using System.Windows.Input;
 
-namespace NTMiner.Vms {
+namespace NTMiner.MinerStudio.Vms {
     public class RemoteDesktopLoginViewModel : ViewModelBase {
         public readonly Guid Id = Guid.NewGuid();
 
@@ -17,16 +18,16 @@ namespace NTMiner.Vms {
                 return;
             }
             this.Ok = new DelegateCommand(() => {
-                if (string.IsNullOrEmpty(LoginName)) {
-                    VirtualRoot.Out.ShowError("登录名不能为空", autoHideSeconds: 4);
-                    return;
-                }
                 VirtualRoot.Execute(new CloseWindowCommand(this.Id));
                 OnOk?.Invoke(this);
             });
         }
 
-        public string Ip {
+        public RemoteDesktopLoginViewModel(string loginName) : this() {
+            this._loginName = loginName;
+        }
+
+        public string Title {
             get; set;
         }
 
@@ -35,9 +36,6 @@ namespace NTMiner.Vms {
             set {
                 _loginName = value;
                 OnPropertyChanged(nameof(LoginName));
-                if (string.IsNullOrEmpty(value)) {
-                    throw new ValidationException("登录名不能为空");
-                }
             }
         }
         public string Password {

+ 318 - 0
src/AppModels/MinerStudio/Vms/UserPageViewModel.cs

@@ -0,0 +1,318 @@
+using NTMiner.User;
+using NTMiner.Vms;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class UserPageViewModel : ViewModelBase {
+        private ObservableCollection<UserViewModel> _queryResults = new ObservableCollection<UserViewModel>();
+        private int _pageIndex = 1;
+        private int _pageSize = 20;
+        private int _total;
+        private string _loginName = string.Empty;
+        private string _email = string.Empty;
+        private string _mobile = string.Empty;
+        private string _role = string.Empty;
+        private EnumItem<UserStatus> _userStatusEnumItem;
+        private UserViewModel _selectedUserVm;
+
+        public ICommand PageUp { get; private set; }
+        public ICommand PageDown { get; private set; }
+        public ICommand PageFirst { get; private set; }
+        public ICommand PageLast { get; private set; }
+        public ICommand PageRefresh { get; private set; }
+        public ICommand Remove { get; private set; }
+        public ICommand AddAdminRole { get; private set; }
+        public ICommand RemoveAdminRole { get; private set; }
+
+        public UserPageViewModel() {
+            if (WpfUtil.IsInDesignMode) {
+                return;
+            }
+            this._userStatusEnumItem = NTMinerContext.UserStatusEnumItems.FirstOrDefault(a => a.Value == UserStatus.All);
+            this.PageUp = new DelegateCommand(() => {
+                this.PageIndex -= 1;
+            });
+            this.PageDown = new DelegateCommand(() => {
+                this.PageIndex += 1;
+            });
+            this.PageFirst = new DelegateCommand(() => {
+                this.PageIndex = 1;
+            });
+            this.PageLast = new DelegateCommand(() => {
+                this.PageIndex = PageCount;
+            });
+            this.PageRefresh = new DelegateCommand(Refresh);
+            this.Remove = new DelegateCommand<UserViewModel>(vm => {
+                if (!ClientAppType.IsMinerStudio) {
+                    return;
+                }
+                if (string.IsNullOrEmpty(vm.LoginName)) {
+                    return;
+                }
+                if (vm.LoginName == RpcRoot.RpcUser.LoginName) {
+                    VirtualRoot.Out.ShowWarn("不能删除自己", header: "提示", autoHideSeconds: 4);
+                    return;
+                }
+                this.ShowSoftDialog(new DialogWindowViewModel(message: $"您确定删除{vm.LoginName}吗?", title: "确认", onYes: () => {
+                    RpcRoot.OfficialServer.UserService.RemoveUserAsync(vm.LoginName, (response, e) => {
+                        if (response.IsSuccess()) {
+                            Refresh();
+                        }
+                        else {
+                            VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
+                        }
+                    });
+                }));
+            });
+            this.AddAdminRole = new DelegateCommand<UserViewModel>(vm => {
+                if (!ClientAppType.IsMinerStudio) {
+                    return;
+                }
+                if (string.IsNullOrEmpty(vm.LoginName)) {
+                    return;
+                }
+                if (vm.LoginName == RpcRoot.RpcUser.LoginName) {
+                    VirtualRoot.Out.ShowWarn("不能操作自己", header: "提示", autoHideSeconds: 4);
+                    return;
+                }
+                this.ShowSoftDialog(new DialogWindowViewModel(message: $"您确定把{vm.LoginName}设成超管吗?", title: "确认", onYes: () => {
+                    RpcRoot.OfficialServer.UserService.AddAdminRoleAsync(vm.LoginName, (response, e) => {
+                        if (response.IsSuccess()) {
+                            Refresh();
+                        }
+                        else {
+                            VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
+                        }
+                    });
+                }));
+            }, vm => this.SelectedUserVm != null && !this.SelectedUserVm.IsAdmin());
+            this.RemoveAdminRole = new DelegateCommand<UserViewModel>(vm => {
+                if (!ClientAppType.IsMinerStudio) {
+                    return;
+                }
+                if (string.IsNullOrEmpty(vm.LoginName)) {
+                    return;
+                }
+                if (vm.LoginName == RpcRoot.RpcUser.LoginName) {
+                    VirtualRoot.Out.ShowWarn("不能操作自己", header: "提示", autoHideSeconds: 4);
+                    return;
+                }
+                this.ShowSoftDialog(new DialogWindowViewModel(message: $"您确定移除{vm.LoginName}的超管角色吗?", title: "确认", onYes: () => {
+                    RpcRoot.OfficialServer.UserService.RemoveAdminRoleAsync(vm.LoginName, (response, e) => {
+                        if (response.IsSuccess()) {
+                            Refresh();
+                        }
+                        else {
+                            VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
+                        }
+                    });
+                }));
+            }, vm => this.SelectedUserVm != null && this.SelectedUserVm.IsAdmin());
+            Refresh();
+        }
+
+        public void Refresh() {
+            RpcRoot.OfficialServer.UserService.QueryUsersAsync(new QueryUsersRequest {
+                PageIndex = this.PageIndex,
+                PageSize = this.PageSize,
+                LoginName = this.LoginName,
+                Email = this.Email,
+                Mobile = this.Mobile,
+                Role = this.Role,
+                UserStatus = UserStatusEnumItem.Value
+            }, (response, e) => {
+                if (response.IsSuccess()) {
+                    UIThread.Execute(() => () => {
+                        for (int i = 0; i < response.Data.Count; i++) {
+                            var item = response.Data[i];
+                            if (_queryResults.Count > i) {
+                                var exist = _queryResults[i];
+                                if (exist.LoginName != item.LoginName) {
+                                    _queryResults.Insert(i, new UserViewModel(item));
+                                }
+                                else {
+                                    exist.Update(item);
+                                }
+                            }
+                            else {
+                                _queryResults.Add(new UserViewModel(item));
+                            }
+                        }
+                        while (_queryResults.Count > response.Data.Count) {
+                            _queryResults.RemoveAt(_queryResults.Count - 1);
+                        }
+                        OnPropertyChanged(nameof(IsNodeRecordVisible));
+                        RefreshPagingUi(response.Total);
+                    });
+                }
+                else {
+                    VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
+                }
+            });
+        }
+
+        private void RefreshPagingUi(int total) {
+            _total = total;
+            OnPropertyChanged(nameof(Total));
+            OnPropertyChanged(nameof(PageCount));
+            OnPropertyChanged(nameof(IsPageDownEnabled));
+            OnPropertyChanged(nameof(IsPageUpEnabled));
+            if (Total == 0) {
+                _pageIndex = 0;
+                OnPropertyChanged(nameof(PageIndex));
+            }
+            else if (PageIndex == 0) {
+                _pageIndex = 1;
+                OnPropertyChanged(nameof(PageIndex));
+            }
+        }
+
+        public UserViewModel SelectedUserVm {
+            get => _selectedUserVm;
+            set {
+                if (_selectedUserVm != value) {
+                    _selectedUserVm = value;
+                    OnPropertyChanged(nameof(SelectedUserVm));
+                }
+            }
+        }
+
+        public int PageIndex {
+            get => _pageIndex;
+            set {
+                _pageIndex = value;
+                OnPropertyChanged(nameof(PageIndex));
+                Refresh();
+            }
+        }
+
+        public int PageCount {
+            get {
+                return (int)Math.Ceiling((double)this.Total / this.PageSize);
+            }
+        }
+
+        public int PageSize {
+            get => _pageSize;
+            set {
+                if (_pageSize != value) {
+                    _pageSize = value;
+                    OnPropertyChanged(nameof(PageSize));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        private static readonly List<int> _pageSizeItems = new List<int>() { 10, 20, 30, 40 };
+        public List<int> PageSizeItems {
+            get {
+                return _pageSizeItems;
+            }
+        }
+
+        public bool IsPageUpEnabled {
+            get {
+                if (this.PageIndex <= 1) {
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        public bool IsPageDownEnabled {
+            get {
+                if (this.PageIndex >= this.PageCount) {
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        public int Total {
+            get => _total;
+            set {
+                if (_total != value) {
+                    _total = value;
+                    OnPropertyChanged(nameof(Total));
+                }
+            }
+        }
+
+        public string LoginName {
+            get => _loginName;
+            set {
+                if (_loginName != value) {
+                    _loginName = value;
+                    OnPropertyChanged(nameof(LoginName));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public string Email {
+            get { return _email; }
+            set {
+                if (_email != value) {
+                    _email = value;
+                    OnPropertyChanged(nameof(Email));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public string Mobile {
+            get { return _mobile; }
+            set {
+                if (_mobile != value) {
+                    _mobile = value;
+                    OnPropertyChanged(nameof(Mobile));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public EnumItem<UserStatus> UserStatusEnumItem {
+            get => _userStatusEnumItem;
+            set {
+                if (_userStatusEnumItem != value) {
+                    _userStatusEnumItem = value;
+                    OnPropertyChanged(nameof(UserStatusEnumItem));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public string Role {
+            get { return _role; }
+            set {
+                if (_role != value) {
+                    _role = value;
+                    OnPropertyChanged(nameof(Role));
+                    this.PageIndex = 1;
+                }
+            }
+        }
+
+        public ObservableCollection<UserViewModel> QueryResults {
+            get => _queryResults;
+            set {
+                _queryResults = value;
+                OnPropertyChanged(nameof(QueryResults));
+            }
+        }
+
+        public Visibility IsNodeRecordVisible {
+            get {
+                if (QueryResults.Count == 0) {
+                    return Visibility.Visible;
+                }
+                return Visibility.Collapsed;
+            }
+        }
+    }
+}

+ 71 - 0
src/AppModels/MinerStudio/Vms/VirtualMemoryViewModel.cs

@@ -0,0 +1,71 @@
+using NTMiner.Vms;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class VirtualMemoryViewModel : ViewModelBase {
+        private List<DriveViewModel> _drives = new List<DriveViewModel>();
+        private bool _isLoading = true;
+
+        public ICommand Apply { get; private set; }
+
+        [Obsolete(message: NTKeyword.WpfDesignOnly, error: true)]
+        public VirtualMemoryViewModel() {
+            if (!WpfUtil.IsInDesignMode) {
+                throw new InvalidProgramException();
+            }
+        }
+
+        public VirtualMemoryViewModel(MinerClientViewModel minerClientVm) {
+            this.MinerClientVm = minerClientVm;
+            this.Apply = new DelegateCommand(() => {
+                MinerStudioRoot.MinerStudioService.SetVirtualMemoryAsync(minerClientVm, _drives.ToDictionary(a => a.Name, a => a.VirtualMemoryMaxSizeMb));
+                OnPropertyChanged(nameof(TotalVirtualMemoryMb));
+                OnPropertyChanged(nameof(IsStateChanged));
+            });
+        }
+
+        public bool IsLoading {
+            get => _isLoading;
+            set {
+                if (_isLoading != value) {
+                    _isLoading = value;
+                    OnPropertyChanged(nameof(IsLoading));
+                }
+            }
+        }
+
+        public MinerClientViewModel MinerClientVm { get; set; }
+
+        public List<DriveViewModel> Drives {
+            get {
+                return _drives;
+            }
+            set {
+                _drives = value;
+                OnPropertyChanged(nameof(Drives));
+                OnPropertyChanged(nameof(IsStateChanged));
+                OnPropertyChanged(nameof(TotalVirtualMemoryMb));
+                IsLoading = false;
+            }
+        }
+
+        public bool IsStateChanged {
+            get {
+                if (_drives.Any(a => a.VirtualMemoryMaxSizeMb != a.InitialVirtualMemoryMaxSizeMb)) {
+                    return true;
+                }
+                return false;
+            }
+        }
+
+
+        public int TotalVirtualMemoryMb {
+            get {
+                return _drives.Sum(a => a.VirtualMemoryMaxSizeMb);
+            }
+        }
+    }
+}

+ 62 - 0
src/AppModels/MinerStudio/Vms/WsServerNodePageViewModel.cs

@@ -0,0 +1,62 @@
+using NTMiner.Vms;
+using System.Collections.ObjectModel;
+using System.Windows;
+
+namespace NTMiner.MinerStudio.Vms {
+    public class WsServerNodePageViewModel : ViewModelBase {
+        private readonly ObservableCollection<WsServerNodeViewModel> _wsServerNodeVms = new ObservableCollection<WsServerNodeViewModel>();
+
+        public WsServerNodePageViewModel() {
+            if (WpfUtil.IsInDesignMode) {
+                return;
+            }
+            Refresh();
+        }
+
+        public void Refresh() {
+            RpcRoot.OfficialServer.WsServerNodeService.GetNodesAsync((response, e) => {
+                if (response.IsSuccess()) {
+                    UIThread.Execute(() => () => {
+                        for (int i = 0; i < response.Data.Count; i++) {
+                            var item = response.Data[i];
+                            if (_wsServerNodeVms.Count > i) {
+                                var exist = _wsServerNodeVms[i];
+                                if (exist.Address != item.Address) {
+                                    _wsServerNodeVms.Insert(i, new WsServerNodeViewModel(item));
+                                }
+                                else {
+                                    exist.Update(item);
+                                }
+                            }
+                            else {
+                                _wsServerNodeVms.Add(new WsServerNodeViewModel(item));
+                            }
+                        }
+                        while (_wsServerNodeVms.Count > response.Data.Count) {
+                            _wsServerNodeVms.RemoveAt(_wsServerNodeVms.Count - 1);
+                        }
+                        OnPropertyChanged(nameof(IsNodeRecordVisible));
+                    });
+                }
+                else {
+                    VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4);
+                }
+            });
+        }
+
+        public ObservableCollection<WsServerNodeViewModel> WsServerNodeVms {
+            get {
+                return _wsServerNodeVms;
+            }
+        }
+
+        public Visibility IsNodeRecordVisible {
+            get {
+                if (WsServerNodeVms.Count == 0) {
+                    return Visibility.Visible;
+                }
+                return Visibility.Collapsed;
+            }
+        }
+    }
+}

+ 13 - 0
src/AppModels/RemoteDesktop/Firewall.cs

@@ -24,6 +24,7 @@ namespace NTMiner.RemoteDesktop {
         private const NET_FW_SCOPE_ MinerClientScope = NET_FW_SCOPE_.NET_FW_SCOPE_ALL;
         private const string MinerClientRuleName = "MinerClient";
         private const string NTMinerDaemonRuleName = "NTMinerDaemon";
+        private const string WsServerRuleName = "WsServer";
 
         #region DisableFirewall
         public static bool DisableFirewall() {
@@ -51,6 +52,9 @@ namespace NTMiner.RemoteDesktop {
         #endregion
 
         #region EnableFirewall
+        /// <summary>
+        /// 仅供单元测试
+        /// </summary>
         public static bool EnableFirewall() {
             FirewallStatus state = Status(FirewallDomain.Domain);
             if (state == RemoteDesktop.FirewallStatus.Enabled) {
@@ -101,6 +105,9 @@ namespace NTMiner.RemoteDesktop {
             }
         }
 
+        /// <summary>
+        /// 仅供单元测试
+        /// </summary>
         public static void RemoveMinerClientRule() {
             FirewallStatus state = Status(FirewallDomain.Domain);
             if (state == RemoteDesktop.FirewallStatus.Disabled) {
@@ -154,6 +161,9 @@ namespace NTMiner.RemoteDesktop {
             }
         }
 
+        /// <summary>
+        /// 仅供单元测试
+        /// </summary>
         public static void RemoveRdpRule() {
             FirewallStatus state = Status(FirewallDomain.Domain);
             if (state == RemoteDesktop.FirewallStatus.Disabled) {
@@ -172,6 +182,9 @@ namespace NTMiner.RemoteDesktop {
             }
         }
 
+        /// <summary>
+        /// 仅供单元测试
+        /// </summary>
         public static bool IsRdpRuleExists() {
             FirewallStatus state = Status(FirewallDomain.Domain);
             if (state == RemoteDesktop.FirewallStatus.Disabled) {

+ 0 - 51
src/AppModels/RemoteDesktop/Rdp.cs

@@ -1,51 +0,0 @@
-using Microsoft.Win32;
-
-namespace NTMiner.RemoteDesktop {
-    public static partial class Rdp {
-        public static void SetRdpEnabled(bool enabled) {
-            if (enabled) {
-                SetRdpRegistryValue(0);
-            }
-            else {
-                SetRdpRegistryValue(1);
-            }
-        }
-
-        public static bool GetRdpEnabled() {
-            using (RegistryKey localMachine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default),
-                               rdpKey = localMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Terminal Server", true)) {
-                if (!int.TryParse(rdpKey.GetValue("fDenyTSConnections").ToString(), out int currentValue)) {
-                    currentValue = -1;
-                }
-                return currentValue == 0;
-            }
-        }
-
-        #region private SetRdpRegistryValue
-        private static void SetRdpRegistryValue(int value) {
-            using (RegistryKey localMachine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default), 
-                               rdpKey = localMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Terminal Server", true)) {
-                if (!int.TryParse(rdpKey.GetValue("fDenyTSConnections").ToString(), out int currentValue)) {
-                    currentValue = -1;
-                }
-
-                //Value was not found do not proceed with change.
-                if (currentValue == -1) {
-                    return;
-                }
-                else if (value == 1 && currentValue == 1) {
-                    Write.DevDebug("RDP is already disabled. No changes will be made.");
-                    return;
-                }
-                else if (value == 0 && currentValue == 0) {
-                    Write.DevDebug("RDP is already enabled. No changes will be made.");
-                    return;
-                }
-                else {
-                    rdpKey.SetValue("fDenyTSConnections", value);
-                }
-            }
-        }
-        #endregion
-    }
-}

+ 0 - 7
src/AppModels/RemoteDesktop/Rdp.partial.cs

@@ -1,7 +0,0 @@
-using System;
-
-namespace NTMiner.RemoteDesktop {
-    public static partial class Rdp {
-        public static Action<RdpInput> RemoteDesktop;
-    }
-}

+ 10 - 3
src/AppModels/SortableExtension.cs

@@ -1,13 +1,20 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Linq;
 
 namespace NTMiner {
     public static class SortableExtension {
-        public static T GetNextOne<T>(this IEnumerable<T> items, int sortNumber) where T : ISortable {
+        public static T GetNextOne<T>(this IEnumerable<T> items, int sortNumber, Func<T, bool> predicate = null) where T : ISortable {
+            if (predicate != null) {
+                items = items.Where(a => predicate(a));
+            }
             return items.OrderBy(a => a.SortNumber).FirstOrDefault(a => a.SortNumber > sortNumber);
         }
 
-        public static T GetUpOne<T>(this IEnumerable<T> items, int sortNumber) where T : ISortable {
+        public static T GetUpOne<T>(this IEnumerable<T> items, int sortNumber, Func<T, bool> predicate = null) where T : ISortable {
+            if (predicate != null) {
+                items = items.Where(a => predicate(a));
+            }
             return items.OrderByDescending(a => a.SortNumber).FirstOrDefault(a => a.SortNumber < sortNumber);
         }
     }

+ 4 - 3
src/AppModels/View/AbstractAppViewFactory.cs

@@ -29,12 +29,13 @@ namespace NTMiner.View {
                 lock (_locker) {
                     if (_mainWindow == null) {
                         _mainWindow = CreateMainWindow();
+                        NTMinerContext.RefreshArgsAssembly.Invoke("主界面创建后");
                         _mainWindow.Show();
                     }
                 }
             }
             else {
-                AppContext.Enable();
+                AppRoot.Enable();
                 bool needActive = _mainWindow.WindowState != WindowState.Minimized;
                 _mainWindow.ShowWindow(isToggle);
                 if (needActive) {
@@ -51,7 +52,7 @@ namespace NTMiner.View {
             try {
                 switch (appType) {
                     case NTMinerAppType.MinerClient:
-                        RpcRoot.Client.MinerClientService.ShowMainWindowAsync(NTKeyword.MinerClientPort, (isSuccess, exception) => {
+                        RpcRoot.Client.MinerClientService.ShowMainWindowAsync((isSuccess, exception) => {
                             if (!isSuccess) {
                                 RestartNTMiner();
                             }
@@ -59,7 +60,7 @@ namespace NTMiner.View {
                         });
                         break;
                     case NTMinerAppType.MinerStudio:
-                        RpcRoot.Client.MinerStudioService.ShowMainWindowAsync(NTKeyword.MinerStudioPort, (isSuccess, exception) => {
+                        RpcRoot.Client.MinerStudioService.ShowMainWindowAsync((isSuccess, exception) => {
                             if (!isSuccess) {
                                 RestartNTMiner();
                             }

+ 0 - 2
src/AppModels/Vms/AboutPageViewModel.cs

@@ -5,8 +5,6 @@ namespace NTMiner.Vms {
         public AboutPageViewModel() {
         }
 
-        public Version CurrentVersion => EntryAssemblyInfo.CurrentVersion;
-
         public int ThisYear => DateTime.Now.Year;
     }
 }

Some files were not shown because too many files changed in this diff